|
@@ -14,813 +14,791 @@ using std::shared_ptr;
|
|
|
namespace brightray {
|
|
|
|
|
|
static COLORREF GetAccentColor() {
|
|
|
- bool success = false;
|
|
|
- if (IsAppThemed()) {
|
|
|
- HKEY hkey;
|
|
|
- if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
|
|
- TEXT("SOFTWARE\\Microsoft\\Windows\\DWM"), 0,
|
|
|
- KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) {
|
|
|
- COLORREF color;
|
|
|
- DWORD type, size;
|
|
|
- if (RegQueryValueEx(hkey, TEXT("AccentColor"), nullptr,
|
|
|
- &type,
|
|
|
- reinterpret_cast<BYTE*>(&color),
|
|
|
- &(size = sizeof(color))) == ERROR_SUCCESS &&
|
|
|
- type == REG_DWORD) {
|
|
|
- // convert from RGBA
|
|
|
- color = RGB(GetRValue(color),
|
|
|
- GetGValue(color),
|
|
|
- GetBValue(color));
|
|
|
- success = true;
|
|
|
- } else if (
|
|
|
- RegQueryValueEx(hkey, TEXT("ColorizationColor"), nullptr,
|
|
|
- &type,
|
|
|
- reinterpret_cast<BYTE*>(&color),
|
|
|
- &(size = sizeof(color))) == ERROR_SUCCESS &&
|
|
|
- type == REG_DWORD) {
|
|
|
- // convert from BGRA
|
|
|
- color = RGB(GetBValue(color),
|
|
|
- GetGValue(color),
|
|
|
- GetRValue(color));
|
|
|
- success = true;
|
|
|
- }
|
|
|
-
|
|
|
- RegCloseKey(hkey);
|
|
|
-
|
|
|
- if (success) return color;
|
|
|
- }
|
|
|
+ bool success = false;
|
|
|
+ if (IsAppThemed()) {
|
|
|
+ HKEY hkey;
|
|
|
+ if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
|
|
+ TEXT("SOFTWARE\\Microsoft\\Windows\\DWM"), 0,
|
|
|
+ KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) {
|
|
|
+ COLORREF color;
|
|
|
+ DWORD type, size;
|
|
|
+ if (RegQueryValueEx(hkey, TEXT("AccentColor"), nullptr, &type,
|
|
|
+ reinterpret_cast<BYTE*>(&color),
|
|
|
+ &(size = sizeof(color))) == ERROR_SUCCESS &&
|
|
|
+ type == REG_DWORD) {
|
|
|
+ // convert from RGBA
|
|
|
+ color = RGB(GetRValue(color), GetGValue(color), GetBValue(color));
|
|
|
+ success = true;
|
|
|
+ } else if (RegQueryValueEx(hkey, TEXT("ColorizationColor"), nullptr,
|
|
|
+ &type, reinterpret_cast<BYTE*>(&color),
|
|
|
+ &(size = sizeof(color))) == ERROR_SUCCESS &&
|
|
|
+ type == REG_DWORD) {
|
|
|
+ // convert from BGRA
|
|
|
+ color = RGB(GetBValue(color), GetGValue(color), GetRValue(color));
|
|
|
+ success = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ RegCloseKey(hkey);
|
|
|
+
|
|
|
+ if (success)
|
|
|
+ return color;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- return GetSysColor(COLOR_ACTIVECAPTION);
|
|
|
+ return GetSysColor(COLOR_ACTIVECAPTION);
|
|
|
}
|
|
|
|
|
|
// Stretches a bitmap to the specified size, preserves alpha channel
|
|
|
static HBITMAP StretchBitmap(HBITMAP bitmap, unsigned width, unsigned height) {
|
|
|
- // We use StretchBlt for the scaling, but that discards the alpha channel.
|
|
|
- // So we first create a separate grayscale bitmap from the alpha channel,
|
|
|
- // scale that separately, and copy it back to the scaled color bitmap.
|
|
|
+ // We use StretchBlt for the scaling, but that discards the alpha channel.
|
|
|
+ // So we first create a separate grayscale bitmap from the alpha channel,
|
|
|
+ // scale that separately, and copy it back to the scaled color bitmap.
|
|
|
|
|
|
- BITMAP bm;
|
|
|
- if (!GetObject(bitmap, sizeof(bm), &bm))
|
|
|
- return NULL;
|
|
|
+ BITMAP bm;
|
|
|
+ if (!GetObject(bitmap, sizeof(bm), &bm))
|
|
|
+ return NULL;
|
|
|
|
|
|
- if (width == 0 || height == 0)
|
|
|
- return NULL;
|
|
|
+ if (width == 0 || height == 0)
|
|
|
+ return NULL;
|
|
|
|
|
|
- HBITMAP result_bitmap = NULL;
|
|
|
+ HBITMAP result_bitmap = NULL;
|
|
|
|
|
|
- HDC hdc_screen = GetDC(NULL);
|
|
|
+ HDC hdc_screen = GetDC(NULL);
|
|
|
|
|
|
- HBITMAP alpha_src_bitmap;
|
|
|
- {
|
|
|
- BITMAPINFOHEADER bmi = { sizeof(BITMAPINFOHEADER) };
|
|
|
- bmi.biWidth = bm.bmWidth;
|
|
|
- bmi.biHeight = bm.bmHeight;
|
|
|
- bmi.biPlanes = bm.bmPlanes;
|
|
|
- bmi.biBitCount = bm.bmBitsPixel;
|
|
|
- bmi.biCompression = BI_RGB;
|
|
|
-
|
|
|
- void* alpha_src_bits;
|
|
|
- alpha_src_bitmap =
|
|
|
- CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
- DIB_RGB_COLORS, &alpha_src_bits, NULL, 0);
|
|
|
-
|
|
|
- if (alpha_src_bitmap) {
|
|
|
- if (GetDIBits(hdc_screen, bitmap, 0, 0, 0,
|
|
|
- reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
- DIB_RGB_COLORS) &&
|
|
|
- bmi.biSizeImage > 0 &&
|
|
|
- (bmi.biSizeImage % 4) == 0) {
|
|
|
- auto buf = reinterpret_cast<BYTE*>(
|
|
|
- _aligned_malloc(bmi.biSizeImage, sizeof(DWORD)));
|
|
|
-
|
|
|
- if (buf) {
|
|
|
- GetDIBits(hdc_screen, bitmap, 0, bm.bmHeight, buf,
|
|
|
- reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
- DIB_RGB_COLORS);
|
|
|
-
|
|
|
- const DWORD *src = reinterpret_cast<DWORD*>(buf);
|
|
|
- const DWORD *end =
|
|
|
- reinterpret_cast<DWORD*>(buf + bmi.biSizeImage);
|
|
|
-
|
|
|
- BYTE* dest = reinterpret_cast<BYTE*>(alpha_src_bits);
|
|
|
-
|
|
|
- for (; src != end; ++src, ++dest) {
|
|
|
- BYTE a = *src >> 24;
|
|
|
- *dest++ = a;
|
|
|
- *dest++ = a;
|
|
|
- *dest++ = a;
|
|
|
- }
|
|
|
-
|
|
|
- _aligned_free(buf);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ HBITMAP alpha_src_bitmap;
|
|
|
+ {
|
|
|
+ BITMAPINFOHEADER bmi = {sizeof(BITMAPINFOHEADER)};
|
|
|
+ bmi.biWidth = bm.bmWidth;
|
|
|
+ bmi.biHeight = bm.bmHeight;
|
|
|
+ bmi.biPlanes = bm.bmPlanes;
|
|
|
+ bmi.biBitCount = bm.bmBitsPixel;
|
|
|
+ bmi.biCompression = BI_RGB;
|
|
|
+
|
|
|
+ void* alpha_src_bits;
|
|
|
+ alpha_src_bitmap =
|
|
|
+ CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
+ DIB_RGB_COLORS, &alpha_src_bits, NULL, 0);
|
|
|
|
|
|
if (alpha_src_bitmap) {
|
|
|
- BITMAPINFOHEADER bmi = { sizeof(BITMAPINFOHEADER) };
|
|
|
- bmi.biWidth = width;
|
|
|
- bmi.biHeight = height;
|
|
|
- bmi.biPlanes = 1;
|
|
|
- bmi.biBitCount = 32;
|
|
|
- bmi.biCompression = BI_RGB;
|
|
|
-
|
|
|
- void* color_bits;
|
|
|
- auto color_bitmap =
|
|
|
- CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
- DIB_RGB_COLORS, &color_bits, NULL, 0);
|
|
|
-
|
|
|
- void* alpha_bits;
|
|
|
- auto alpha_bitmap =
|
|
|
- CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
- DIB_RGB_COLORS, &alpha_bits, NULL, 0);
|
|
|
-
|
|
|
- HDC hdc = CreateCompatibleDC(NULL);
|
|
|
- HDC hdc_src = CreateCompatibleDC(NULL);
|
|
|
-
|
|
|
- if (color_bitmap && alpha_bitmap && hdc && hdc_src) {
|
|
|
- SetStretchBltMode(hdc, HALFTONE);
|
|
|
-
|
|
|
- // resize color channels
|
|
|
- SelectObject(hdc, color_bitmap);
|
|
|
- SelectObject(hdc_src, bitmap);
|
|
|
- StretchBlt(hdc, 0, 0, width, height,
|
|
|
- hdc_src, 0, 0, bm.bmWidth, bm.bmHeight,
|
|
|
- SRCCOPY);
|
|
|
-
|
|
|
- // resize alpha channel
|
|
|
- SelectObject(hdc, alpha_bitmap);
|
|
|
- SelectObject(hdc_src, alpha_src_bitmap);
|
|
|
- StretchBlt(hdc, 0, 0, width, height,
|
|
|
- hdc_src, 0, 0, bm.bmWidth, bm.bmHeight,
|
|
|
- SRCCOPY);
|
|
|
-
|
|
|
- // flush before touching the bits
|
|
|
- GdiFlush();
|
|
|
-
|
|
|
- // apply the alpha channel
|
|
|
- auto dest = reinterpret_cast<BYTE*>(color_bits);
|
|
|
- auto src = reinterpret_cast<const BYTE*>(alpha_bits);
|
|
|
- auto end = src + (width * height * 4);
|
|
|
- while (src != end) {
|
|
|
- dest[3] = src[0];
|
|
|
- dest += 4;
|
|
|
- src += 4;
|
|
|
- }
|
|
|
+ if (GetDIBits(hdc_screen, bitmap, 0, 0, 0,
|
|
|
+ reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS) &&
|
|
|
+ bmi.biSizeImage > 0 && (bmi.biSizeImage % 4) == 0) {
|
|
|
+ auto buf = reinterpret_cast<BYTE*>(
|
|
|
+ _aligned_malloc(bmi.biSizeImage, sizeof(DWORD)));
|
|
|
|
|
|
- // create the resulting bitmap
|
|
|
- result_bitmap = CreateDIBitmap(hdc_screen, &bmi, CBM_INIT,
|
|
|
- color_bits,
|
|
|
- reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
- DIB_RGB_COLORS);
|
|
|
- }
|
|
|
+ if (buf) {
|
|
|
+ GetDIBits(hdc_screen, bitmap, 0, bm.bmHeight, buf,
|
|
|
+ reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS);
|
|
|
+
|
|
|
+ const DWORD* src = reinterpret_cast<DWORD*>(buf);
|
|
|
+ const DWORD* end = reinterpret_cast<DWORD*>(buf + bmi.biSizeImage);
|
|
|
|
|
|
- if (hdc_src) DeleteDC(hdc_src);
|
|
|
- if (hdc) DeleteDC(hdc);
|
|
|
+ BYTE* dest = reinterpret_cast<BYTE*>(alpha_src_bits);
|
|
|
|
|
|
- if (alpha_bitmap) DeleteObject(alpha_bitmap);
|
|
|
- if (color_bitmap) DeleteObject(color_bitmap);
|
|
|
+ for (; src != end; ++src, ++dest) {
|
|
|
+ BYTE a = *src >> 24;
|
|
|
+ *dest++ = a;
|
|
|
+ *dest++ = a;
|
|
|
+ *dest++ = a;
|
|
|
+ }
|
|
|
|
|
|
- DeleteObject(alpha_src_bitmap);
|
|
|
+ _aligned_free(buf);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (alpha_src_bitmap) {
|
|
|
+ BITMAPINFOHEADER bmi = {sizeof(BITMAPINFOHEADER)};
|
|
|
+ bmi.biWidth = width;
|
|
|
+ bmi.biHeight = height;
|
|
|
+ bmi.biPlanes = 1;
|
|
|
+ bmi.biBitCount = 32;
|
|
|
+ bmi.biCompression = BI_RGB;
|
|
|
+
|
|
|
+ void* color_bits;
|
|
|
+ auto color_bitmap =
|
|
|
+ CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
+ DIB_RGB_COLORS, &color_bits, NULL, 0);
|
|
|
+
|
|
|
+ void* alpha_bits;
|
|
|
+ auto alpha_bitmap =
|
|
|
+ CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
|
|
|
+ DIB_RGB_COLORS, &alpha_bits, NULL, 0);
|
|
|
+
|
|
|
+ HDC hdc = CreateCompatibleDC(NULL);
|
|
|
+ HDC hdc_src = CreateCompatibleDC(NULL);
|
|
|
+
|
|
|
+ if (color_bitmap && alpha_bitmap && hdc && hdc_src) {
|
|
|
+ SetStretchBltMode(hdc, HALFTONE);
|
|
|
+
|
|
|
+ // resize color channels
|
|
|
+ SelectObject(hdc, color_bitmap);
|
|
|
+ SelectObject(hdc_src, bitmap);
|
|
|
+ StretchBlt(hdc, 0, 0, width, height, hdc_src, 0, 0, bm.bmWidth,
|
|
|
+ bm.bmHeight, SRCCOPY);
|
|
|
+
|
|
|
+ // resize alpha channel
|
|
|
+ SelectObject(hdc, alpha_bitmap);
|
|
|
+ SelectObject(hdc_src, alpha_src_bitmap);
|
|
|
+ StretchBlt(hdc, 0, 0, width, height, hdc_src, 0, 0, bm.bmWidth,
|
|
|
+ bm.bmHeight, SRCCOPY);
|
|
|
+
|
|
|
+ // flush before touching the bits
|
|
|
+ GdiFlush();
|
|
|
+
|
|
|
+ // apply the alpha channel
|
|
|
+ auto dest = reinterpret_cast<BYTE*>(color_bits);
|
|
|
+ auto src = reinterpret_cast<const BYTE*>(alpha_bits);
|
|
|
+ auto end = src + (width * height * 4);
|
|
|
+ while (src != end) {
|
|
|
+ dest[3] = src[0];
|
|
|
+ dest += 4;
|
|
|
+ src += 4;
|
|
|
+ }
|
|
|
+
|
|
|
+ // create the resulting bitmap
|
|
|
+ result_bitmap =
|
|
|
+ CreateDIBitmap(hdc_screen, &bmi, CBM_INIT, color_bits,
|
|
|
+ reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS);
|
|
|
}
|
|
|
|
|
|
- ReleaseDC(NULL, hdc_screen);
|
|
|
+ if (hdc_src)
|
|
|
+ DeleteDC(hdc_src);
|
|
|
+ if (hdc)
|
|
|
+ DeleteDC(hdc);
|
|
|
+
|
|
|
+ if (alpha_bitmap)
|
|
|
+ DeleteObject(alpha_bitmap);
|
|
|
+ if (color_bitmap)
|
|
|
+ DeleteObject(color_bitmap);
|
|
|
|
|
|
- return result_bitmap;
|
|
|
+ DeleteObject(alpha_src_bitmap);
|
|
|
+ }
|
|
|
+
|
|
|
+ ReleaseDC(NULL, hdc_screen);
|
|
|
+
|
|
|
+ return result_bitmap;
|
|
|
}
|
|
|
|
|
|
-DesktopNotificationController::Toast::Toast(
|
|
|
- HWND hwnd, shared_ptr<NotificationData>* data) :
|
|
|
- hwnd_(hwnd), data_(*data) {
|
|
|
- HDC hdc_screen = GetDC(NULL);
|
|
|
- hdc_ = CreateCompatibleDC(hdc_screen);
|
|
|
- ReleaseDC(NULL, hdc_screen);
|
|
|
+DesktopNotificationController::Toast::Toast(HWND hwnd,
|
|
|
+ shared_ptr<NotificationData>* data)
|
|
|
+ : hwnd_(hwnd), data_(*data) {
|
|
|
+ HDC hdc_screen = GetDC(NULL);
|
|
|
+ hdc_ = CreateCompatibleDC(hdc_screen);
|
|
|
+ ReleaseDC(NULL, hdc_screen);
|
|
|
}
|
|
|
|
|
|
DesktopNotificationController::Toast::~Toast() {
|
|
|
- DeleteDC(hdc_);
|
|
|
- if (bitmap_) DeleteBitmap(bitmap_);
|
|
|
- if (scaled_image_) DeleteBitmap(scaled_image_);
|
|
|
+ DeleteDC(hdc_);
|
|
|
+ if (bitmap_)
|
|
|
+ DeleteBitmap(bitmap_);
|
|
|
+ if (scaled_image_)
|
|
|
+ DeleteBitmap(scaled_image_);
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::Register(HINSTANCE hinstance) {
|
|
|
- WNDCLASSEX wc = { sizeof(wc) };
|
|
|
- wc.lpfnWndProc = &Toast::WndProc;
|
|
|
- wc.lpszClassName = class_name_;
|
|
|
- wc.cbWndExtra = sizeof(Toast*);
|
|
|
- wc.hInstance = hinstance;
|
|
|
- wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
|
-
|
|
|
- RegisterClassEx(&wc);
|
|
|
+ WNDCLASSEX wc = {sizeof(wc)};
|
|
|
+ wc.lpfnWndProc = &Toast::WndProc;
|
|
|
+ wc.lpszClassName = class_name_;
|
|
|
+ wc.cbWndExtra = sizeof(Toast*);
|
|
|
+ wc.hInstance = hinstance;
|
|
|
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
|
+
|
|
|
+ RegisterClassEx(&wc);
|
|
|
}
|
|
|
|
|
|
-LRESULT DesktopNotificationController::Toast::WndProc(
|
|
|
- HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
|
|
|
- switch (message) {
|
|
|
- case WM_CREATE:
|
|
|
- {
|
|
|
- auto& cs = reinterpret_cast<const CREATESTRUCT*&>(lparam);
|
|
|
- auto data =
|
|
|
- static_cast<shared_ptr<NotificationData>*>(cs->lpCreateParams);
|
|
|
- auto inst = new Toast(hwnd, data);
|
|
|
- SetWindowLongPtr(hwnd, 0, (LONG_PTR)inst);
|
|
|
- }
|
|
|
- break;
|
|
|
+LRESULT DesktopNotificationController::Toast::WndProc(HWND hwnd,
|
|
|
+ UINT message,
|
|
|
+ WPARAM wparam,
|
|
|
+ LPARAM lparam) {
|
|
|
+ switch (message) {
|
|
|
+ case WM_CREATE: {
|
|
|
+ auto& cs = reinterpret_cast<const CREATESTRUCT*&>(lparam);
|
|
|
+ auto data =
|
|
|
+ static_cast<shared_ptr<NotificationData>*>(cs->lpCreateParams);
|
|
|
+ auto inst = new Toast(hwnd, data);
|
|
|
+ SetWindowLongPtr(hwnd, 0, (LONG_PTR)inst);
|
|
|
+ } break;
|
|
|
|
|
|
case WM_NCDESTROY:
|
|
|
- delete Get(hwnd);
|
|
|
- SetWindowLongPtr(hwnd, 0, 0);
|
|
|
- return 0;
|
|
|
+ delete Get(hwnd);
|
|
|
+ SetWindowLongPtr(hwnd, 0, 0);
|
|
|
+ return 0;
|
|
|
|
|
|
case WM_MOUSEACTIVATE:
|
|
|
- return MA_NOACTIVATE;
|
|
|
+ return MA_NOACTIVATE;
|
|
|
|
|
|
case WM_TIMER:
|
|
|
- if (wparam == TimerID_AutoDismiss) {
|
|
|
- Get(hwnd)->AutoDismiss();
|
|
|
- }
|
|
|
- return 0;
|
|
|
+ if (wparam == TimerID_AutoDismiss) {
|
|
|
+ Get(hwnd)->AutoDismiss();
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
|
|
|
- case WM_LBUTTONDOWN:
|
|
|
- {
|
|
|
- auto inst = Get(hwnd);
|
|
|
+ case WM_LBUTTONDOWN: {
|
|
|
+ auto inst = Get(hwnd);
|
|
|
|
|
|
- inst->Dismiss();
|
|
|
+ inst->Dismiss();
|
|
|
|
|
|
- Notification notification(inst->data_);
|
|
|
- if (inst->is_close_hot_)
|
|
|
- inst->data_->controller->OnNotificationDismissed(notification);
|
|
|
- else
|
|
|
- inst->data_->controller->OnNotificationClicked(notification);
|
|
|
- }
|
|
|
- return 0;
|
|
|
+ Notification notification(inst->data_);
|
|
|
+ if (inst->is_close_hot_)
|
|
|
+ inst->data_->controller->OnNotificationDismissed(notification);
|
|
|
+ else
|
|
|
+ inst->data_->controller->OnNotificationClicked(notification);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
|
|
|
- case WM_MOUSEMOVE:
|
|
|
- {
|
|
|
- auto inst = Get(hwnd);
|
|
|
- if (!inst->is_highlighted_) {
|
|
|
- inst->is_highlighted_ = true;
|
|
|
+ case WM_MOUSEMOVE: {
|
|
|
+ auto inst = Get(hwnd);
|
|
|
+ if (!inst->is_highlighted_) {
|
|
|
+ inst->is_highlighted_ = true;
|
|
|
|
|
|
- TRACKMOUSEEVENT tme = { sizeof(tme), TME_LEAVE, hwnd };
|
|
|
- TrackMouseEvent(&tme);
|
|
|
- }
|
|
|
+ TRACKMOUSEEVENT tme = {sizeof(tme), TME_LEAVE, hwnd};
|
|
|
+ TrackMouseEvent(&tme);
|
|
|
+ }
|
|
|
|
|
|
- POINT cursor = { GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
|
|
|
- inst->is_close_hot_ =
|
|
|
- (PtInRect(&inst->close_button_rect_, cursor) != FALSE);
|
|
|
+ POINT cursor = {GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)};
|
|
|
+ inst->is_close_hot_ =
|
|
|
+ (PtInRect(&inst->close_button_rect_, cursor) != FALSE);
|
|
|
|
|
|
- if (!inst->is_non_interactive_)
|
|
|
- inst->CancelDismiss();
|
|
|
+ if (!inst->is_non_interactive_)
|
|
|
+ inst->CancelDismiss();
|
|
|
|
|
|
- inst->UpdateContents();
|
|
|
- }
|
|
|
- return 0;
|
|
|
+ inst->UpdateContents();
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
|
|
|
- case WM_MOUSELEAVE:
|
|
|
- {
|
|
|
- auto inst = Get(hwnd);
|
|
|
- inst->is_highlighted_ = false;
|
|
|
- inst->is_close_hot_ = false;
|
|
|
- inst->UpdateContents();
|
|
|
+ case WM_MOUSELEAVE: {
|
|
|
+ auto inst = Get(hwnd);
|
|
|
+ inst->is_highlighted_ = false;
|
|
|
+ inst->is_close_hot_ = false;
|
|
|
+ inst->UpdateContents();
|
|
|
|
|
|
- if (!inst->ease_out_active_ && inst->ease_in_pos_ == 1.0f)
|
|
|
- inst->ScheduleDismissal();
|
|
|
+ if (!inst->ease_out_active_ && inst->ease_in_pos_ == 1.0f)
|
|
|
+ inst->ScheduleDismissal();
|
|
|
|
|
|
- // Make sure stack collapse happens if needed
|
|
|
- inst->data_->controller->StartAnimation();
|
|
|
- }
|
|
|
- return 0;
|
|
|
-
|
|
|
- case WM_WINDOWPOSCHANGED:
|
|
|
- {
|
|
|
- auto& wp = reinterpret_cast<WINDOWPOS*&>(lparam);
|
|
|
- if (wp->flags & SWP_HIDEWINDOW) {
|
|
|
- if (!IsWindowVisible(hwnd))
|
|
|
- Get(hwnd)->is_highlighted_ = false;
|
|
|
- }
|
|
|
- }
|
|
|
- break;
|
|
|
+ // Make sure stack collapse happens if needed
|
|
|
+ inst->data_->controller->StartAnimation();
|
|
|
}
|
|
|
-
|
|
|
- return DefWindowProc(hwnd, message, wparam, lparam);
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ case WM_WINDOWPOSCHANGED: {
|
|
|
+ auto& wp = reinterpret_cast<WINDOWPOS*&>(lparam);
|
|
|
+ if (wp->flags & SWP_HIDEWINDOW) {
|
|
|
+ if (!IsWindowVisible(hwnd))
|
|
|
+ Get(hwnd)->is_highlighted_ = false;
|
|
|
+ }
|
|
|
+ } break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return DefWindowProc(hwnd, message, wparam, lparam);
|
|
|
}
|
|
|
|
|
|
HWND DesktopNotificationController::Toast::Create(
|
|
|
- HINSTANCE hinstance, shared_ptr<NotificationData>& data) {
|
|
|
- return CreateWindowEx(WS_EX_LAYERED | WS_EX_NOACTIVATE | WS_EX_TOPMOST,
|
|
|
- class_name_, nullptr, WS_POPUP, 0, 0, 0, 0,
|
|
|
- NULL, NULL, hinstance, &data);
|
|
|
+ HINSTANCE hinstance,
|
|
|
+ shared_ptr<NotificationData>& data) {
|
|
|
+ return CreateWindowEx(WS_EX_LAYERED | WS_EX_NOACTIVATE | WS_EX_TOPMOST,
|
|
|
+ class_name_, nullptr, WS_POPUP, 0, 0, 0, 0, NULL, NULL,
|
|
|
+ hinstance, &data);
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::Draw() {
|
|
|
- const COLORREF accent = GetAccentColor();
|
|
|
-
|
|
|
- COLORREF back_color;
|
|
|
- {
|
|
|
- // base background color is 2/3 of accent
|
|
|
- // highlighted adds a bit of intensity to every channel
|
|
|
-
|
|
|
- int h = is_highlighted_ ? (0xff / 20) : 0;
|
|
|
-
|
|
|
- back_color = RGB(min(0xff, (GetRValue(accent) * 2 / 3) + h),
|
|
|
- min(0xff, (GetGValue(accent) * 2 / 3) + h),
|
|
|
- min(0xff, (GetBValue(accent) * 2 / 3) + h));
|
|
|
- }
|
|
|
-
|
|
|
- const float back_luma =
|
|
|
- (GetRValue(back_color) * 0.299f / 255) +
|
|
|
- (GetGValue(back_color) * 0.587f / 255) +
|
|
|
- (GetBValue(back_color) * 0.114f / 255);
|
|
|
-
|
|
|
- const struct { float r, g, b; } back_f = {
|
|
|
- GetRValue(back_color) / 255.0f,
|
|
|
- GetGValue(back_color) / 255.0f,
|
|
|
- GetBValue(back_color) / 255.0f,
|
|
|
- };
|
|
|
-
|
|
|
- COLORREF fore_color, dimmed_color;
|
|
|
- {
|
|
|
- // based on the lightness of background, we draw foreground in light
|
|
|
- // or dark shades of gray blended onto the background with slight
|
|
|
- // transparency to avoid sharp contrast
|
|
|
-
|
|
|
- constexpr float alpha = 0.9f;
|
|
|
- constexpr float intensity_light[] = { (1.0f * alpha), (0.8f * alpha) };
|
|
|
- constexpr float intensity_dark[] = { (0.1f * alpha), (0.3f * alpha) };
|
|
|
-
|
|
|
- // select foreground intensity values (light or dark)
|
|
|
- auto& i = (back_luma < 0.6f) ? intensity_light : intensity_dark;
|
|
|
-
|
|
|
- float r, g, b;
|
|
|
-
|
|
|
- r = i[0] + back_f.r * (1 - alpha);
|
|
|
- g = i[0] + back_f.g * (1 - alpha);
|
|
|
- b = i[0] + back_f.b * (1 - alpha);
|
|
|
- fore_color = RGB(r * 0xff, g * 0xff, b * 0xff);
|
|
|
-
|
|
|
- r = i[1] + back_f.r * (1 - alpha);
|
|
|
- g = i[1] + back_f.g * (1 - alpha);
|
|
|
- b = i[1] + back_f.b * (1 - alpha);
|
|
|
- dimmed_color = RGB(r * 0xff, g * 0xff, b * 0xff);
|
|
|
- }
|
|
|
-
|
|
|
- // Draw background
|
|
|
- {
|
|
|
- auto brush = CreateSolidBrush(back_color);
|
|
|
-
|
|
|
- RECT rc = { 0, 0, toast_size_.cx, toast_size_.cy };
|
|
|
- FillRect(hdc_, &rc, brush);
|
|
|
+ const COLORREF accent = GetAccentColor();
|
|
|
|
|
|
- DeleteBrush(brush);
|
|
|
- }
|
|
|
-
|
|
|
- SetBkMode(hdc_, TRANSPARENT);
|
|
|
-
|
|
|
- const auto close = L'\x2715';
|
|
|
- auto caption_font = data_->controller->GetCaptionFont();
|
|
|
- auto body_font = data_->controller->GetBodyFont();
|
|
|
+ COLORREF back_color;
|
|
|
+ {
|
|
|
+ // base background color is 2/3 of accent
|
|
|
+ // highlighted adds a bit of intensity to every channel
|
|
|
+
|
|
|
+ int h = is_highlighted_ ? (0xff / 20) : 0;
|
|
|
+
|
|
|
+ back_color = RGB(min(0xff, (GetRValue(accent) * 2 / 3) + h),
|
|
|
+ min(0xff, (GetGValue(accent) * 2 / 3) + h),
|
|
|
+ min(0xff, (GetBValue(accent) * 2 / 3) + h));
|
|
|
+ }
|
|
|
+
|
|
|
+ const float back_luma = (GetRValue(back_color) * 0.299f / 255) +
|
|
|
+ (GetGValue(back_color) * 0.587f / 255) +
|
|
|
+ (GetBValue(back_color) * 0.114f / 255);
|
|
|
+
|
|
|
+ const struct {
|
|
|
+ float r, g, b;
|
|
|
+ } back_f = {
|
|
|
+ GetRValue(back_color) / 255.0f,
|
|
|
+ GetGValue(back_color) / 255.0f,
|
|
|
+ GetBValue(back_color) / 255.0f,
|
|
|
+ };
|
|
|
+
|
|
|
+ COLORREF fore_color, dimmed_color;
|
|
|
+ {
|
|
|
+ // based on the lightness of background, we draw foreground in light
|
|
|
+ // or dark shades of gray blended onto the background with slight
|
|
|
+ // transparency to avoid sharp contrast
|
|
|
+
|
|
|
+ constexpr float alpha = 0.9f;
|
|
|
+ constexpr float intensity_light[] = {(1.0f * alpha), (0.8f * alpha)};
|
|
|
+ constexpr float intensity_dark[] = {(0.1f * alpha), (0.3f * alpha)};
|
|
|
+
|
|
|
+ // select foreground intensity values (light or dark)
|
|
|
+ auto& i = (back_luma < 0.6f) ? intensity_light : intensity_dark;
|
|
|
+
|
|
|
+ float r, g, b;
|
|
|
+
|
|
|
+ r = i[0] + back_f.r * (1 - alpha);
|
|
|
+ g = i[0] + back_f.g * (1 - alpha);
|
|
|
+ b = i[0] + back_f.b * (1 - alpha);
|
|
|
+ fore_color = RGB(r * 0xff, g * 0xff, b * 0xff);
|
|
|
+
|
|
|
+ r = i[1] + back_f.r * (1 - alpha);
|
|
|
+ g = i[1] + back_f.g * (1 - alpha);
|
|
|
+ b = i[1] + back_f.b * (1 - alpha);
|
|
|
+ dimmed_color = RGB(r * 0xff, g * 0xff, b * 0xff);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Draw background
|
|
|
+ {
|
|
|
+ auto brush = CreateSolidBrush(back_color);
|
|
|
+
|
|
|
+ RECT rc = {0, 0, toast_size_.cx, toast_size_.cy};
|
|
|
+ FillRect(hdc_, &rc, brush);
|
|
|
+
|
|
|
+ DeleteBrush(brush);
|
|
|
+ }
|
|
|
+
|
|
|
+ SetBkMode(hdc_, TRANSPARENT);
|
|
|
+
|
|
|
+ const auto close = L'\x2715';
|
|
|
+ auto caption_font = data_->controller->GetCaptionFont();
|
|
|
+ auto body_font = data_->controller->GetBodyFont();
|
|
|
+
|
|
|
+ TEXTMETRIC tm_cap;
|
|
|
+ SelectFont(hdc_, caption_font);
|
|
|
+ GetTextMetrics(hdc_, &tm_cap);
|
|
|
+
|
|
|
+ auto text_offset_x = margin_.cx;
|
|
|
+
|
|
|
+ BITMAP image_info = {};
|
|
|
+ if (scaled_image_) {
|
|
|
+ GetObject(scaled_image_, sizeof(image_info), &image_info);
|
|
|
+
|
|
|
+ text_offset_x += margin_.cx + image_info.bmWidth;
|
|
|
+ }
|
|
|
+
|
|
|
+ // calculate close button rect
|
|
|
+ POINT close_pos;
|
|
|
+ {
|
|
|
+ SIZE extent = {};
|
|
|
+ GetTextExtentPoint32W(hdc_, &close, 1, &extent);
|
|
|
+
|
|
|
+ close_button_rect_.right = toast_size_.cx;
|
|
|
+ close_button_rect_.top = 0;
|
|
|
+
|
|
|
+ close_pos.x = close_button_rect_.right - margin_.cy - extent.cx;
|
|
|
+ close_pos.y = close_button_rect_.top + margin_.cy;
|
|
|
+
|
|
|
+ close_button_rect_.left = close_pos.x - margin_.cy;
|
|
|
+ close_button_rect_.bottom = close_pos.y + extent.cy + margin_.cy;
|
|
|
+ }
|
|
|
+
|
|
|
+ // image
|
|
|
+ if (scaled_image_) {
|
|
|
+ HDC hdc_image = CreateCompatibleDC(NULL);
|
|
|
+ SelectBitmap(hdc_image, scaled_image_);
|
|
|
+ BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
|
|
|
+ AlphaBlend(hdc_, margin_.cx, margin_.cy, image_info.bmWidth,
|
|
|
+ image_info.bmHeight, hdc_image, 0, 0, image_info.bmWidth,
|
|
|
+ image_info.bmHeight, blend);
|
|
|
+ DeleteDC(hdc_image);
|
|
|
+ }
|
|
|
+
|
|
|
+ // caption
|
|
|
+ {
|
|
|
+ RECT rc = {text_offset_x, margin_.cy, close_button_rect_.left,
|
|
|
+ toast_size_.cy};
|
|
|
|
|
|
- TEXTMETRIC tm_cap;
|
|
|
SelectFont(hdc_, caption_font);
|
|
|
- GetTextMetrics(hdc_, &tm_cap);
|
|
|
-
|
|
|
- auto text_offset_x = margin_.cx;
|
|
|
-
|
|
|
- BITMAP image_info = {};
|
|
|
- if (scaled_image_) {
|
|
|
- GetObject(scaled_image_, sizeof(image_info), &image_info);
|
|
|
-
|
|
|
- text_offset_x += margin_.cx + image_info.bmWidth;
|
|
|
- }
|
|
|
-
|
|
|
- // calculate close button rect
|
|
|
- POINT close_pos;
|
|
|
- {
|
|
|
- SIZE extent = {};
|
|
|
- GetTextExtentPoint32W(hdc_, &close, 1, &extent);
|
|
|
-
|
|
|
- close_button_rect_.right = toast_size_.cx;
|
|
|
- close_button_rect_.top = 0;
|
|
|
-
|
|
|
- close_pos.x = close_button_rect_.right - margin_.cy - extent.cx;
|
|
|
- close_pos.y = close_button_rect_.top + margin_.cy;
|
|
|
-
|
|
|
- close_button_rect_.left = close_pos.x - margin_.cy;
|
|
|
- close_button_rect_.bottom = close_pos.y + extent.cy + margin_.cy;
|
|
|
- }
|
|
|
-
|
|
|
- // image
|
|
|
- if (scaled_image_) {
|
|
|
- HDC hdc_image = CreateCompatibleDC(NULL);
|
|
|
- SelectBitmap(hdc_image, scaled_image_);
|
|
|
- BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
|
|
|
- AlphaBlend(hdc_, margin_.cx, margin_.cy,
|
|
|
- image_info.bmWidth, image_info.bmHeight,
|
|
|
- hdc_image, 0, 0,
|
|
|
- image_info.bmWidth, image_info.bmHeight,
|
|
|
- blend);
|
|
|
- DeleteDC(hdc_image);
|
|
|
- }
|
|
|
-
|
|
|
- // caption
|
|
|
- {
|
|
|
- RECT rc = {
|
|
|
- text_offset_x,
|
|
|
- margin_.cy,
|
|
|
- close_button_rect_.left,
|
|
|
- toast_size_.cy
|
|
|
- };
|
|
|
-
|
|
|
- SelectFont(hdc_, caption_font);
|
|
|
- SetTextColor(hdc_, fore_color);
|
|
|
- DrawText(hdc_, data_->caption.data(), (UINT)data_->caption.length(),
|
|
|
- &rc, DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);
|
|
|
- }
|
|
|
-
|
|
|
- // body text
|
|
|
- if (!data_->body_text.empty()) {
|
|
|
- RECT rc = {
|
|
|
- text_offset_x,
|
|
|
- 2 * margin_.cy + tm_cap.tmAscent,
|
|
|
- toast_size_.cx - margin_.cx,
|
|
|
- toast_size_.cy - margin_.cy
|
|
|
- };
|
|
|
-
|
|
|
- SelectFont(hdc_, body_font);
|
|
|
- SetTextColor(hdc_, dimmed_color);
|
|
|
- DrawText(hdc_, data_->body_text.data(), (UINT)data_->body_text.length(),
|
|
|
- &rc,
|
|
|
- DT_LEFT | DT_WORDBREAK | DT_NOPREFIX |
|
|
|
- DT_END_ELLIPSIS | DT_EDITCONTROL);
|
|
|
- }
|
|
|
-
|
|
|
- // close button
|
|
|
- {
|
|
|
- SelectFont(hdc_, caption_font);
|
|
|
- SetTextColor(hdc_, is_close_hot_ ? fore_color : dimmed_color);
|
|
|
- ExtTextOut(hdc_, close_pos.x, close_pos.y, 0, nullptr,
|
|
|
- &close, 1, nullptr);
|
|
|
- }
|
|
|
+ SetTextColor(hdc_, fore_color);
|
|
|
+ DrawText(hdc_, data_->caption.data(), (UINT)data_->caption.length(), &rc,
|
|
|
+ DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);
|
|
|
+ }
|
|
|
+
|
|
|
+ // body text
|
|
|
+ if (!data_->body_text.empty()) {
|
|
|
+ RECT rc = {text_offset_x, 2 * margin_.cy + tm_cap.tmAscent,
|
|
|
+ toast_size_.cx - margin_.cx, toast_size_.cy - margin_.cy};
|
|
|
+
|
|
|
+ SelectFont(hdc_, body_font);
|
|
|
+ SetTextColor(hdc_, dimmed_color);
|
|
|
+ DrawText(hdc_, data_->body_text.data(), (UINT)data_->body_text.length(),
|
|
|
+ &rc,
|
|
|
+ DT_LEFT | DT_WORDBREAK | DT_NOPREFIX | DT_END_ELLIPSIS |
|
|
|
+ DT_EDITCONTROL);
|
|
|
+ }
|
|
|
+
|
|
|
+ // close button
|
|
|
+ {
|
|
|
+ SelectFont(hdc_, caption_font);
|
|
|
+ SetTextColor(hdc_, is_close_hot_ ? fore_color : dimmed_color);
|
|
|
+ ExtTextOut(hdc_, close_pos.x, close_pos.y, 0, nullptr, &close, 1, nullptr);
|
|
|
+ }
|
|
|
|
|
|
- is_content_updated_ = true;
|
|
|
+ is_content_updated_ = true;
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::Invalidate() {
|
|
|
- is_content_updated_ = false;
|
|
|
+ is_content_updated_ = false;
|
|
|
}
|
|
|
|
|
|
bool DesktopNotificationController::Toast::IsRedrawNeeded() const {
|
|
|
- return !is_content_updated_;
|
|
|
+ return !is_content_updated_;
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::UpdateBufferSize() {
|
|
|
- if (hdc_) {
|
|
|
- SIZE new_size;
|
|
|
- {
|
|
|
- TEXTMETRIC tm_cap = {};
|
|
|
- HFONT font = data_->controller->GetCaptionFont();
|
|
|
- if (font) {
|
|
|
- SelectFont(hdc_, font);
|
|
|
- if (!GetTextMetrics(hdc_, &tm_cap)) return;
|
|
|
+ if (hdc_) {
|
|
|
+ SIZE new_size;
|
|
|
+ {
|
|
|
+ TEXTMETRIC tm_cap = {};
|
|
|
+ HFONT font = data_->controller->GetCaptionFont();
|
|
|
+ if (font) {
|
|
|
+ SelectFont(hdc_, font);
|
|
|
+ if (!GetTextMetrics(hdc_, &tm_cap))
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ TEXTMETRIC tm_body = {};
|
|
|
+ font = data_->controller->GetBodyFont();
|
|
|
+ if (font) {
|
|
|
+ SelectFont(hdc_, font);
|
|
|
+ if (!GetTextMetrics(hdc_, &tm_body))
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this->margin_ = {tm_cap.tmAveCharWidth * 2, tm_cap.tmAscent / 2};
|
|
|
+
|
|
|
+ new_size.cx = margin_.cx + (32 * tm_cap.tmAveCharWidth) + margin_.cx;
|
|
|
+ new_size.cy = margin_.cy + (tm_cap.tmHeight) + margin_.cy;
|
|
|
+
|
|
|
+ if (!data_->body_text.empty())
|
|
|
+ new_size.cy += margin_.cy + (3 * tm_body.tmHeight);
|
|
|
+
|
|
|
+ if (data_->image) {
|
|
|
+ BITMAP bm;
|
|
|
+ if (GetObject(data_->image, sizeof(bm), &bm)) {
|
|
|
+ // cap the image size
|
|
|
+ const int max_dim_size = 80;
|
|
|
+
|
|
|
+ auto width = bm.bmWidth;
|
|
|
+ auto height = bm.bmHeight;
|
|
|
+ if (width < height) {
|
|
|
+ if (height > max_dim_size) {
|
|
|
+ width = width * max_dim_size / height;
|
|
|
+ height = max_dim_size;
|
|
|
}
|
|
|
-
|
|
|
- TEXTMETRIC tm_body = {};
|
|
|
- font = data_->controller->GetBodyFont();
|
|
|
- if (font) {
|
|
|
- SelectFont(hdc_, font);
|
|
|
- if (!GetTextMetrics(hdc_, &tm_body)) return;
|
|
|
+ } else {
|
|
|
+ if (width > max_dim_size) {
|
|
|
+ height = height * max_dim_size / width;
|
|
|
+ width = max_dim_size;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- this->margin_ = { tm_cap.tmAveCharWidth * 2, tm_cap.tmAscent / 2 };
|
|
|
-
|
|
|
- new_size.cx =
|
|
|
- margin_.cx + (32 * tm_cap.tmAveCharWidth) + margin_.cx;
|
|
|
- new_size.cy =
|
|
|
- margin_.cy + (tm_cap.tmHeight) + margin_.cy;
|
|
|
-
|
|
|
- if (!data_->body_text.empty())
|
|
|
- new_size.cy += margin_.cy + (3 * tm_body.tmHeight);
|
|
|
-
|
|
|
- if (data_->image) {
|
|
|
- BITMAP bm;
|
|
|
- if (GetObject(data_->image, sizeof(bm), &bm)) {
|
|
|
- // cap the image size
|
|
|
- const int max_dim_size = 80;
|
|
|
-
|
|
|
- auto width = bm.bmWidth;
|
|
|
- auto height = bm.bmHeight;
|
|
|
- if (width < height) {
|
|
|
- if (height > max_dim_size) {
|
|
|
- width = width * max_dim_size / height;
|
|
|
- height = max_dim_size;
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (width > max_dim_size) {
|
|
|
- height = height * max_dim_size / width;
|
|
|
- width = max_dim_size;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- ScreenMetrics scr;
|
|
|
- SIZE image_draw_size = { scr.X(width), scr.Y(height) };
|
|
|
-
|
|
|
- new_size.cx += image_draw_size.cx + margin_.cx;
|
|
|
-
|
|
|
- auto height_with_image =
|
|
|
- margin_.cy + (image_draw_size.cy) + margin_.cy;
|
|
|
-
|
|
|
- if (new_size.cy < height_with_image)
|
|
|
- new_size.cy = height_with_image;
|
|
|
-
|
|
|
- UpdateScaledImage(image_draw_size);
|
|
|
- }
|
|
|
- }
|
|
|
+ ScreenMetrics scr;
|
|
|
+ SIZE image_draw_size = {scr.X(width), scr.Y(height)};
|
|
|
+
|
|
|
+ new_size.cx += image_draw_size.cx + margin_.cx;
|
|
|
+
|
|
|
+ auto height_with_image =
|
|
|
+ margin_.cy + (image_draw_size.cy) + margin_.cy;
|
|
|
+
|
|
|
+ if (new_size.cy < height_with_image)
|
|
|
+ new_size.cy = height_with_image;
|
|
|
+
|
|
|
+ UpdateScaledImage(image_draw_size);
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (new_size.cx != this->toast_size_.cx ||
|
|
|
- new_size.cy != this->toast_size_.cy) {
|
|
|
- HDC hdc_screen = GetDC(NULL);
|
|
|
- auto new_bitmap = CreateCompatibleBitmap(hdc_screen,
|
|
|
- new_size.cx, new_size.cy);
|
|
|
- ReleaseDC(NULL, hdc_screen);
|
|
|
-
|
|
|
- if (new_bitmap) {
|
|
|
- if (SelectBitmap(hdc_, new_bitmap)) {
|
|
|
- RECT dirty1 = {}, dirty2 = {};
|
|
|
- if (toast_size_.cx < new_size.cx) {
|
|
|
- dirty1 = { toast_size_.cx, 0,
|
|
|
- new_size.cx, toast_size_.cy };
|
|
|
- }
|
|
|
- if (toast_size_.cy < new_size.cy) {
|
|
|
- dirty2 = { 0, toast_size_.cy,
|
|
|
- new_size.cx, new_size.cy };
|
|
|
- }
|
|
|
-
|
|
|
- if (this->bitmap_) DeleteBitmap(this->bitmap_);
|
|
|
- this->bitmap_ = new_bitmap;
|
|
|
- this->toast_size_ = new_size;
|
|
|
-
|
|
|
- Invalidate();
|
|
|
-
|
|
|
- // Resize also the DWM buffer to prevent flicker during
|
|
|
- // window resizing. Make sure any existing data is not
|
|
|
- // overwritten by marking the dirty region.
|
|
|
- {
|
|
|
- POINT origin = { 0, 0 };
|
|
|
-
|
|
|
- UPDATELAYEREDWINDOWINFO ulw;
|
|
|
- ulw.cbSize = sizeof(ulw);
|
|
|
- ulw.hdcDst = NULL;
|
|
|
- ulw.pptDst = nullptr;
|
|
|
- ulw.psize = &toast_size_;
|
|
|
- ulw.hdcSrc = hdc_;
|
|
|
- ulw.pptSrc = &origin;
|
|
|
- ulw.crKey = 0;
|
|
|
- ulw.pblend = nullptr;
|
|
|
- ulw.dwFlags = 0;
|
|
|
- ulw.prcDirty = &dirty1;
|
|
|
- auto b1 = UpdateLayeredWindowIndirect(hwnd_, &ulw);
|
|
|
- ulw.prcDirty = &dirty2;
|
|
|
- auto b2 = UpdateLayeredWindowIndirect(hwnd_, &ulw);
|
|
|
- _ASSERT(b1 && b2);
|
|
|
- }
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- DeleteBitmap(new_bitmap);
|
|
|
- }
|
|
|
+ if (new_size.cx != this->toast_size_.cx ||
|
|
|
+ new_size.cy != this->toast_size_.cy) {
|
|
|
+ HDC hdc_screen = GetDC(NULL);
|
|
|
+ auto new_bitmap =
|
|
|
+ CreateCompatibleBitmap(hdc_screen, new_size.cx, new_size.cy);
|
|
|
+ ReleaseDC(NULL, hdc_screen);
|
|
|
+
|
|
|
+ if (new_bitmap) {
|
|
|
+ if (SelectBitmap(hdc_, new_bitmap)) {
|
|
|
+ RECT dirty1 = {}, dirty2 = {};
|
|
|
+ if (toast_size_.cx < new_size.cx) {
|
|
|
+ dirty1 = {toast_size_.cx, 0, new_size.cx, toast_size_.cy};
|
|
|
+ }
|
|
|
+ if (toast_size_.cy < new_size.cy) {
|
|
|
+ dirty2 = {0, toast_size_.cy, new_size.cx, new_size.cy};
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->bitmap_)
|
|
|
+ DeleteBitmap(this->bitmap_);
|
|
|
+ this->bitmap_ = new_bitmap;
|
|
|
+ this->toast_size_ = new_size;
|
|
|
+
|
|
|
+ Invalidate();
|
|
|
+
|
|
|
+ // Resize also the DWM buffer to prevent flicker during
|
|
|
+ // window resizing. Make sure any existing data is not
|
|
|
+ // overwritten by marking the dirty region.
|
|
|
+ {
|
|
|
+ POINT origin = {0, 0};
|
|
|
+
|
|
|
+ UPDATELAYEREDWINDOWINFO ulw;
|
|
|
+ ulw.cbSize = sizeof(ulw);
|
|
|
+ ulw.hdcDst = NULL;
|
|
|
+ ulw.pptDst = nullptr;
|
|
|
+ ulw.psize = &toast_size_;
|
|
|
+ ulw.hdcSrc = hdc_;
|
|
|
+ ulw.pptSrc = &origin;
|
|
|
+ ulw.crKey = 0;
|
|
|
+ ulw.pblend = nullptr;
|
|
|
+ ulw.dwFlags = 0;
|
|
|
+ ulw.prcDirty = &dirty1;
|
|
|
+ auto b1 = UpdateLayeredWindowIndirect(hwnd_, &ulw);
|
|
|
+ ulw.prcDirty = &dirty2;
|
|
|
+ auto b2 = UpdateLayeredWindowIndirect(hwnd_, &ulw);
|
|
|
+ _ASSERT(b1 && b2);
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ DeleteBitmap(new_bitmap);
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::UpdateScaledImage(const SIZE& size) {
|
|
|
- BITMAP bm;
|
|
|
- if (!GetObject(scaled_image_, sizeof(bm), &bm) ||
|
|
|
- bm.bmWidth != size.cx ||
|
|
|
- bm.bmHeight != size.cy) {
|
|
|
- if (scaled_image_) DeleteBitmap(scaled_image_);
|
|
|
- scaled_image_ = StretchBitmap(data_->image, size.cx, size.cy);
|
|
|
- }
|
|
|
+ BITMAP bm;
|
|
|
+ if (!GetObject(scaled_image_, sizeof(bm), &bm) || bm.bmWidth != size.cx ||
|
|
|
+ bm.bmHeight != size.cy) {
|
|
|
+ if (scaled_image_)
|
|
|
+ DeleteBitmap(scaled_image_);
|
|
|
+ scaled_image_ = StretchBitmap(data_->image, size.cx, size.cy);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::UpdateContents() {
|
|
|
- Draw();
|
|
|
-
|
|
|
- if (IsWindowVisible(hwnd_)) {
|
|
|
- RECT rc;
|
|
|
- GetWindowRect(hwnd_, &rc);
|
|
|
- POINT origin = { 0, 0 };
|
|
|
- SIZE size = { rc.right - rc.left, rc.bottom - rc.top };
|
|
|
- UpdateLayeredWindow(hwnd_, NULL, nullptr, &size,
|
|
|
- hdc_, &origin, 0, nullptr, 0);
|
|
|
- }
|
|
|
+ Draw();
|
|
|
+
|
|
|
+ if (IsWindowVisible(hwnd_)) {
|
|
|
+ RECT rc;
|
|
|
+ GetWindowRect(hwnd_, &rc);
|
|
|
+ POINT origin = {0, 0};
|
|
|
+ SIZE size = {rc.right - rc.left, rc.bottom - rc.top};
|
|
|
+ UpdateLayeredWindow(hwnd_, NULL, nullptr, &size, hdc_, &origin, 0, nullptr,
|
|
|
+ 0);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::Dismiss() {
|
|
|
- if (!is_non_interactive_) {
|
|
|
- // Set a flag to prevent further interaction. We don't disable the HWND
|
|
|
- // because we still want to receive mouse move messages in order to keep
|
|
|
- // the toast under the cursor and not collapse it while dismissing.
|
|
|
- is_non_interactive_ = true;
|
|
|
-
|
|
|
- AutoDismiss();
|
|
|
- }
|
|
|
+ if (!is_non_interactive_) {
|
|
|
+ // Set a flag to prevent further interaction. We don't disable the HWND
|
|
|
+ // because we still want to receive mouse move messages in order to keep
|
|
|
+ // the toast under the cursor and not collapse it while dismissing.
|
|
|
+ is_non_interactive_ = true;
|
|
|
+
|
|
|
+ AutoDismiss();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::AutoDismiss() {
|
|
|
- KillTimer(hwnd_, TimerID_AutoDismiss);
|
|
|
- StartEaseOut();
|
|
|
+ KillTimer(hwnd_, TimerID_AutoDismiss);
|
|
|
+ StartEaseOut();
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::CancelDismiss() {
|
|
|
- KillTimer(hwnd_, TimerID_AutoDismiss);
|
|
|
- ease_out_active_ = false;
|
|
|
- ease_out_pos_ = 0;
|
|
|
+ KillTimer(hwnd_, TimerID_AutoDismiss);
|
|
|
+ ease_out_active_ = false;
|
|
|
+ ease_out_pos_ = 0;
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::ScheduleDismissal() {
|
|
|
- ULONG duration;
|
|
|
- if (!SystemParametersInfo(SPI_GETMESSAGEDURATION, 0, &duration, 0)) {
|
|
|
- duration = 5;
|
|
|
- }
|
|
|
- SetTimer(hwnd_, TimerID_AutoDismiss, duration * 1000, nullptr);
|
|
|
+ ULONG duration;
|
|
|
+ if (!SystemParametersInfo(SPI_GETMESSAGEDURATION, 0, &duration, 0)) {
|
|
|
+ duration = 5;
|
|
|
+ }
|
|
|
+ SetTimer(hwnd_, TimerID_AutoDismiss, duration * 1000, nullptr);
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::ResetContents() {
|
|
|
- if (scaled_image_) {
|
|
|
- DeleteBitmap(scaled_image_);
|
|
|
- scaled_image_ = NULL;
|
|
|
- }
|
|
|
+ if (scaled_image_) {
|
|
|
+ DeleteBitmap(scaled_image_);
|
|
|
+ scaled_image_ = NULL;
|
|
|
+ }
|
|
|
|
|
|
- Invalidate();
|
|
|
+ Invalidate();
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::PopUp(int y) {
|
|
|
- vertical_pos_target_ = vertical_pos_ = y;
|
|
|
- StartEaseIn();
|
|
|
+ vertical_pos_target_ = vertical_pos_ = y;
|
|
|
+ StartEaseIn();
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::SetVerticalPosition(int y) {
|
|
|
- // Don't restart animation if current target is the same
|
|
|
- if (y == vertical_pos_target_)
|
|
|
- return;
|
|
|
-
|
|
|
- // Make sure the new animation's origin is at the current position
|
|
|
- vertical_pos_ += static_cast<int>(
|
|
|
- (vertical_pos_target_ - vertical_pos_) * stack_collapse_pos_);
|
|
|
-
|
|
|
- // Set new target position and start the animation
|
|
|
- vertical_pos_target_ = y;
|
|
|
- stack_collapse_start_ = GetTickCount();
|
|
|
- data_->controller->StartAnimation();
|
|
|
+ // Don't restart animation if current target is the same
|
|
|
+ if (y == vertical_pos_target_)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Make sure the new animation's origin is at the current position
|
|
|
+ vertical_pos_ += static_cast<int>((vertical_pos_target_ - vertical_pos_) *
|
|
|
+ stack_collapse_pos_);
|
|
|
+
|
|
|
+ // Set new target position and start the animation
|
|
|
+ vertical_pos_target_ = y;
|
|
|
+ stack_collapse_start_ = GetTickCount();
|
|
|
+ data_->controller->StartAnimation();
|
|
|
}
|
|
|
|
|
|
-HDWP DesktopNotificationController::Toast::Animate(
|
|
|
- HDWP hdwp, const POINT& origin) {
|
|
|
- UpdateBufferSize();
|
|
|
+HDWP DesktopNotificationController::Toast::Animate(HDWP hdwp,
|
|
|
+ const POINT& origin) {
|
|
|
+ UpdateBufferSize();
|
|
|
|
|
|
- if (IsRedrawNeeded())
|
|
|
- Draw();
|
|
|
+ if (IsRedrawNeeded())
|
|
|
+ Draw();
|
|
|
|
|
|
- POINT src_origin = { 0, 0 };
|
|
|
+ POINT src_origin = {0, 0};
|
|
|
|
|
|
- UPDATELAYEREDWINDOWINFO ulw;
|
|
|
- ulw.cbSize = sizeof(ulw);
|
|
|
- ulw.hdcDst = NULL;
|
|
|
- ulw.pptDst = nullptr;
|
|
|
- ulw.psize = nullptr;
|
|
|
- ulw.hdcSrc = hdc_;
|
|
|
- ulw.pptSrc = &src_origin;
|
|
|
- ulw.crKey = 0;
|
|
|
- ulw.pblend = nullptr;
|
|
|
- ulw.dwFlags = 0;
|
|
|
- ulw.prcDirty = nullptr;
|
|
|
+ UPDATELAYEREDWINDOWINFO ulw;
|
|
|
+ ulw.cbSize = sizeof(ulw);
|
|
|
+ ulw.hdcDst = NULL;
|
|
|
+ ulw.pptDst = nullptr;
|
|
|
+ ulw.psize = nullptr;
|
|
|
+ ulw.hdcSrc = hdc_;
|
|
|
+ ulw.pptSrc = &src_origin;
|
|
|
+ ulw.crKey = 0;
|
|
|
+ ulw.pblend = nullptr;
|
|
|
+ ulw.dwFlags = 0;
|
|
|
+ ulw.prcDirty = nullptr;
|
|
|
|
|
|
- POINT pt = { 0, 0 };
|
|
|
- SIZE size = { 0, 0 };
|
|
|
- BLENDFUNCTION blend;
|
|
|
- UINT dwpFlags = SWP_NOACTIVATE | SWP_SHOWWINDOW |
|
|
|
- SWP_NOREDRAW | SWP_NOCOPYBITS;
|
|
|
+ POINT pt = {0, 0};
|
|
|
+ SIZE size = {0, 0};
|
|
|
+ BLENDFUNCTION blend;
|
|
|
+ UINT dwpFlags =
|
|
|
+ SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOREDRAW | SWP_NOCOPYBITS;
|
|
|
|
|
|
- auto ease_in_pos = AnimateEaseIn();
|
|
|
- auto ease_out_pos = AnimateEaseOut();
|
|
|
- auto stack_collapse_pos = AnimateStackCollapse();
|
|
|
+ auto ease_in_pos = AnimateEaseIn();
|
|
|
+ auto ease_out_pos = AnimateEaseOut();
|
|
|
+ auto stack_collapse_pos = AnimateStackCollapse();
|
|
|
|
|
|
- auto y_offset = (vertical_pos_target_ - vertical_pos_) * stack_collapse_pos;
|
|
|
+ auto y_offset = (vertical_pos_target_ - vertical_pos_) * stack_collapse_pos;
|
|
|
|
|
|
- size.cx = static_cast<int>(toast_size_.cx * ease_in_pos);
|
|
|
- size.cy = toast_size_.cy;
|
|
|
+ size.cx = static_cast<int>(toast_size_.cx * ease_in_pos);
|
|
|
+ size.cy = toast_size_.cy;
|
|
|
|
|
|
- pt.x = origin.x - size.cx;
|
|
|
- pt.y = static_cast<int>(origin.y - vertical_pos_ - y_offset - size.cy);
|
|
|
+ pt.x = origin.x - size.cx;
|
|
|
+ pt.y = static_cast<int>(origin.y - vertical_pos_ - y_offset - size.cy);
|
|
|
|
|
|
- ulw.pptDst = &pt;
|
|
|
- ulw.psize = &size;
|
|
|
+ ulw.pptDst = &pt;
|
|
|
+ ulw.psize = &size;
|
|
|
|
|
|
- if (ease_in_active_ && ease_in_pos == 1.0f) {
|
|
|
- ease_in_active_ = false;
|
|
|
- ScheduleDismissal();
|
|
|
- }
|
|
|
+ if (ease_in_active_ && ease_in_pos == 1.0f) {
|
|
|
+ ease_in_active_ = false;
|
|
|
+ ScheduleDismissal();
|
|
|
+ }
|
|
|
|
|
|
- this->ease_in_pos_ = ease_in_pos;
|
|
|
- this->stack_collapse_pos_ = stack_collapse_pos;
|
|
|
+ this->ease_in_pos_ = ease_in_pos;
|
|
|
+ this->stack_collapse_pos_ = stack_collapse_pos;
|
|
|
|
|
|
- if (ease_out_pos != this->ease_out_pos_) {
|
|
|
- blend.BlendOp = AC_SRC_OVER;
|
|
|
- blend.BlendFlags = 0;
|
|
|
- blend.SourceConstantAlpha = (BYTE)(255 * (1.0f - ease_out_pos));
|
|
|
- blend.AlphaFormat = 0;
|
|
|
+ if (ease_out_pos != this->ease_out_pos_) {
|
|
|
+ blend.BlendOp = AC_SRC_OVER;
|
|
|
+ blend.BlendFlags = 0;
|
|
|
+ blend.SourceConstantAlpha = (BYTE)(255 * (1.0f - ease_out_pos));
|
|
|
+ blend.AlphaFormat = 0;
|
|
|
|
|
|
- ulw.pblend = &blend;
|
|
|
- ulw.dwFlags = ULW_ALPHA;
|
|
|
+ ulw.pblend = &blend;
|
|
|
+ ulw.dwFlags = ULW_ALPHA;
|
|
|
|
|
|
- this->ease_out_pos_ = ease_out_pos;
|
|
|
+ this->ease_out_pos_ = ease_out_pos;
|
|
|
|
|
|
- if (ease_out_pos == 1.0f) {
|
|
|
- ease_out_active_ = false;
|
|
|
+ if (ease_out_pos == 1.0f) {
|
|
|
+ ease_out_active_ = false;
|
|
|
|
|
|
- dwpFlags &= ~SWP_SHOWWINDOW;
|
|
|
- dwpFlags |= SWP_HIDEWINDOW;
|
|
|
- }
|
|
|
+ dwpFlags &= ~SWP_SHOWWINDOW;
|
|
|
+ dwpFlags |= SWP_HIDEWINDOW;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (stack_collapse_pos == 1.0f) {
|
|
|
- vertical_pos_ = vertical_pos_target_;
|
|
|
- }
|
|
|
+ if (stack_collapse_pos == 1.0f) {
|
|
|
+ vertical_pos_ = vertical_pos_target_;
|
|
|
+ }
|
|
|
|
|
|
- // `UpdateLayeredWindowIndirect` updates position, size, and transparency.
|
|
|
- // `DeferWindowPos` updates z-order, and also position and size in case
|
|
|
- // ULWI fails, which can happen when one of the dimensions is zero (e.g.
|
|
|
- // at the beginning of ease-in).
|
|
|
+ // `UpdateLayeredWindowIndirect` updates position, size, and transparency.
|
|
|
+ // `DeferWindowPos` updates z-order, and also position and size in case
|
|
|
+ // ULWI fails, which can happen when one of the dimensions is zero (e.g.
|
|
|
+ // at the beginning of ease-in).
|
|
|
|
|
|
- auto ulw_result = UpdateLayeredWindowIndirect(hwnd_, &ulw);
|
|
|
- hdwp = DeferWindowPos(hdwp, hwnd_, HWND_TOPMOST,
|
|
|
- pt.x, pt.y, size.cx, size.cy, dwpFlags);
|
|
|
- return hdwp;
|
|
|
+ auto ulw_result = UpdateLayeredWindowIndirect(hwnd_, &ulw);
|
|
|
+ hdwp = DeferWindowPos(hdwp, hwnd_, HWND_TOPMOST, pt.x, pt.y, size.cx, size.cy,
|
|
|
+ dwpFlags);
|
|
|
+ return hdwp;
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::StartEaseIn() {
|
|
|
- _ASSERT(!ease_in_active_);
|
|
|
- ease_in_start_ = GetTickCount();
|
|
|
- ease_in_active_ = true;
|
|
|
- data_->controller->StartAnimation();
|
|
|
+ _ASSERT(!ease_in_active_);
|
|
|
+ ease_in_start_ = GetTickCount();
|
|
|
+ ease_in_active_ = true;
|
|
|
+ data_->controller->StartAnimation();
|
|
|
}
|
|
|
|
|
|
void DesktopNotificationController::Toast::StartEaseOut() {
|
|
|
- _ASSERT(!ease_out_active_);
|
|
|
- ease_out_start_ = GetTickCount();
|
|
|
- ease_out_active_ = true;
|
|
|
- data_->controller->StartAnimation();
|
|
|
+ _ASSERT(!ease_out_active_);
|
|
|
+ ease_out_start_ = GetTickCount();
|
|
|
+ ease_out_active_ = true;
|
|
|
+ data_->controller->StartAnimation();
|
|
|
}
|
|
|
|
|
|
bool DesktopNotificationController::Toast::IsStackCollapseActive() const {
|
|
|
- return (vertical_pos_ != vertical_pos_target_);
|
|
|
+ return (vertical_pos_ != vertical_pos_target_);
|
|
|
}
|
|
|
|
|
|
float DesktopNotificationController::Toast::AnimateEaseIn() {
|
|
|
- if (!ease_in_active_)
|
|
|
- return ease_in_pos_;
|
|
|
+ if (!ease_in_active_)
|
|
|
+ return ease_in_pos_;
|
|
|
|
|
|
- constexpr DWORD duration = 500;
|
|
|
- auto elapsed = GetTickCount() - ease_in_start_;
|
|
|
- float time = std::min(duration, elapsed) / static_cast<float>(duration);
|
|
|
+ constexpr DWORD duration = 500;
|
|
|
+ auto elapsed = GetTickCount() - ease_in_start_;
|
|
|
+ float time = std::min(duration, elapsed) / static_cast<float>(duration);
|
|
|
|
|
|
- // decelerating exponential ease
|
|
|
- const float a = -8.0f;
|
|
|
- auto pos = (std::exp(a * time) - 1.0f) / (std::exp(a) - 1.0f);
|
|
|
+ // decelerating exponential ease
|
|
|
+ const float a = -8.0f;
|
|
|
+ auto pos = (std::exp(a * time) - 1.0f) / (std::exp(a) - 1.0f);
|
|
|
|
|
|
- return pos;
|
|
|
+ return pos;
|
|
|
}
|
|
|
|
|
|
float DesktopNotificationController::Toast::AnimateEaseOut() {
|
|
|
- if (!ease_out_active_)
|
|
|
- return ease_out_pos_;
|
|
|
+ if (!ease_out_active_)
|
|
|
+ return ease_out_pos_;
|
|
|
|
|
|
- constexpr DWORD duration = 120;
|
|
|
- auto elapsed = GetTickCount() - ease_out_start_;
|
|
|
- float time = std::min(duration, elapsed) / static_cast<float>(duration);
|
|
|
+ constexpr DWORD duration = 120;
|
|
|
+ auto elapsed = GetTickCount() - ease_out_start_;
|
|
|
+ float time = std::min(duration, elapsed) / static_cast<float>(duration);
|
|
|
|
|
|
- // accelerating circle ease
|
|
|
- auto pos = 1.0f - std::sqrt(1 - time * time);
|
|
|
+ // accelerating circle ease
|
|
|
+ auto pos = 1.0f - std::sqrt(1 - time * time);
|
|
|
|
|
|
- return pos;
|
|
|
+ return pos;
|
|
|
}
|
|
|
|
|
|
float DesktopNotificationController::Toast::AnimateStackCollapse() {
|
|
|
- if (!IsStackCollapseActive())
|
|
|
- return stack_collapse_pos_;
|
|
|
+ if (!IsStackCollapseActive())
|
|
|
+ return stack_collapse_pos_;
|
|
|
|
|
|
- constexpr DWORD duration = 500;
|
|
|
- auto elapsed = GetTickCount() - stack_collapse_start_;
|
|
|
- float time = std::min(duration, elapsed) / static_cast<float>(duration);
|
|
|
+ constexpr DWORD duration = 500;
|
|
|
+ auto elapsed = GetTickCount() - stack_collapse_start_;
|
|
|
+ float time = std::min(duration, elapsed) / static_cast<float>(duration);
|
|
|
|
|
|
- // decelerating exponential ease
|
|
|
- const float a = -8.0f;
|
|
|
- auto pos = (std::exp(a * time) - 1.0f) / (std::exp(a) - 1.0f);
|
|
|
+ // decelerating exponential ease
|
|
|
+ const float a = -8.0f;
|
|
|
+ auto pos = (std::exp(a * time) - 1.0f) / (std::exp(a) - 1.0f);
|
|
|
|
|
|
- return pos;
|
|
|
+ return pos;
|
|
|
}
|
|
|
|
|
|
-} // namespace brightray
|
|
|
+} // namespace brightray
|