/*---------------------------------------------------------------------------*/ // Author : hiyohiyo // Mail : hiyohiyo@crystalmark.info // Web : https://crystalmark.info/ // License : MIT License /*---------------------------------------------------------------------------*/ #include "stdafx.h" #include "ImageToast.h" #ifndef CMK_MIN #define CMK_MIN(a,b) (( (a) < (b) ) ? (a) : (b)) #endif #ifndef CMK_MAX #define CMK_MAX(a,b) (( (a) > (b) ) ? (a) : (b)) #endif using namespace Gdiplus; static const UINT IDT_CLOSE = 1; static const UINT IDT_FADE = 2; BEGIN_MESSAGE_MAP(CImageToast, CWnd) ON_WM_CREATE() ON_WM_TIMER() ON_WM_LBUTTONDOWN() ON_WM_RBUTTONDOWN() ON_WM_ERASEBKGND() ON_WM_KEYDOWN() END_MESSAGE_MAP() CImageToast::CImageToast() {} CImageToast::~CImageToast() { if (m_hDib) { ::DeleteObject(m_hDib); m_hDib = nullptr; } } int CImageToast::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CWnd::OnCreate(lpCreateStruct) == -1) return -1; return 0; } BOOL CImageToast::OnEraseBkgnd(CDC* /*pDC*/) { return TRUE; } void CImageToast::OnLButtonDown(UINT, CPoint) { OpenUrlIfAny(); BeginClose(FALSE); } void CImageToast::OnRButtonDown(UINT, CPoint) { BeginClose(FALSE); } void CImageToast::OnKeyDown(UINT, UINT, UINT) { BeginClose(FALSE); } BOOL CImageToast::EnsureWindowCreated() { if (m_hWnd && ::IsWindow(m_hWnd)) return TRUE; CString cls = AfxRegisterWndClass(0, ::LoadCursor(nullptr, IDC_HAND), 0, 0); DWORD ex = WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_LAYERED; DWORD st = WS_POPUP; if (!CreateEx(ex, cls, _T(""), st, CRect(0,0,0,0), nullptr, 0)) return FALSE; ShowWindow(SW_SHOWNOACTIVATE); return TRUE; } static BOOL CreateARGBDIB(HDC hdc, int w, int h, HBITMAP& outBmp, void** outBits) { BITMAPINFO bi{}; bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi.bmiHeader.biWidth = w; bi.bmiHeader.biHeight = -h; // top-down bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; void* bits = nullptr; HBITMAP hbmp = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, &bits, nullptr, 0); if (!hbmp || !bits) return FALSE; outBmp = hbmp; *outBits = bits; return TRUE; } BOOL CImageToast::LoadPngToDIB(LPCWSTR path) { Gdiplus::Bitmap bmp(path); if (bmp.GetLastStatus() != Gdiplus::Ok) return FALSE; const int w = (int)bmp.GetWidth(); const int h = (int)bmp.GetHeight(); if (w <= 0 || h <= 0) return FALSE; HDC hdcScreen = ::GetDC(nullptr); HBITMAP hbmp = nullptr; void* bits = nullptr; if (!CreateARGBDIB(hdcScreen, w, h, hbmp, &bits)) { ::ReleaseDC(nullptr, hdcScreen); return FALSE; } HDC hdcMem = ::CreateCompatibleDC(hdcScreen); HGDIOBJ oldBmp = ::SelectObject(hdcMem, hbmp); { Gdiplus::Graphics g(hdcMem); g.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); Gdiplus::SolidBrush clearBrush(Gdiplus::Color(0, 0, 0, 0)); g.FillRectangle(&clearBrush, 0, 0, w, h); g.DrawImage(&bmp, 0, 0, w, h); } ::SelectObject(hdcMem, oldBmp); ::DeleteDC(hdcMem); ::ReleaseDC(nullptr, hdcScreen); if (m_hDib) ::DeleteObject(m_hDib); m_hDib = hbmp; m_bmpSize.cx = w; m_bmpSize.cy = h; return TRUE; } void CImageToast::UpdateLayered() { if (!m_hWnd || !::IsWindow(m_hWnd) || !m_hDib) return; HDC hdcScreen = ::GetDC(nullptr); HDC hdcMem = ::CreateCompatibleDC(hdcScreen); HBITMAP hOld = (HBITMAP)::SelectObject(hdcMem, m_hDib); POINT ptSrc{ 0,0 }; SIZE sz{ m_bmpSize.cx, m_bmpSize.cy }; BLENDFUNCTION bf{}; bf.BlendOp = AC_SRC_OVER; bf.SourceConstantAlpha = m_curAlpha; bf.AlphaFormat = AC_SRC_ALPHA; POINT cursor{}; ::GetCursorPos(&cursor); HMONITOR mon = ::MonitorFromPoint(cursor, MONITOR_DEFAULTTONEAREST); MONITORINFO mi{ sizeof(mi) }; ::GetMonitorInfo(mon, &mi); RECT wa = mi.rcWork; POINT ptDst{}; ptDst.x = wa.right - sz.cx - m_margin; ptDst.y = wa.bottom - sz.cy - m_margin; ::UpdateLayeredWindow(m_hWnd, hdcScreen, &ptDst, &sz, hdcMem, &ptSrc, 0, &bf, ULW_ALPHA); ::SelectObject(hdcMem, hOld); ::DeleteDC(hdcMem); ::ReleaseDC(nullptr, hdcScreen); } void CImageToast::StartFadeTimer(BOOL fadeIn) { if (!m_enableFade) return; m_closing = !fadeIn; SetTimer(IDT_FADE, 16, nullptr); } void CImageToast::BeginClose(BOOL force) { if (!m_hWnd) return; if (force || !m_enableFade) { KillTimer(IDT_FADE); KillTimer(IDT_CLOSE); DestroyWindow(); return; } if (!m_closing) { m_closing = TRUE; SetTimer(IDT_FADE, 16, nullptr); } } void CImageToast::OnTimer(UINT_PTR id) { if (id == IDT_CLOSE) { KillTimer(IDT_CLOSE); BeginClose(FALSE); return; } if (id == IDT_FADE) { int stepIn = CMK_MAX(1, (int)m_maxAlpha * 16 / CMK_MAX(1, m_fadeInMs)); int stepOut = CMK_MAX(1, (int)m_maxAlpha * 16 / CMK_MAX(1, m_fadeOutMs)); if (!m_closing) { if (m_curAlpha < m_maxAlpha) { m_curAlpha = (BYTE)CMK_MIN((int)m_maxAlpha, (int)m_curAlpha + stepIn); UpdateLayered(); } else { KillTimer(IDT_FADE); } } else { if (m_curAlpha > 0) { m_curAlpha = (BYTE)CMK_MAX(0, (int)m_curAlpha - stepOut); UpdateLayered(); } else { KillTimer(IDT_FADE); DestroyWindow(); } } return; } CWnd::OnTimer(id); } BOOL CImageToast::Show(LPCWSTR pngPath, UINT showMillis, BOOL enableFade, BYTE maxAlpha, int margin, int fadeInMs, int fadeOutMs, LPCWSTR urlToOpen) { m_pngPath = pngPath; m_showMillis = showMillis; m_enableFade = enableFade; m_maxAlpha = maxAlpha; m_margin = margin; m_fadeInMs = fadeInMs; m_fadeOutMs = fadeOutMs; m_curAlpha = enableFade ? 0 : maxAlpha; m_closing = FALSE; m_opened = FALSE; m_url = (urlToOpen ? urlToOpen : L""); if (!EnsureWindowCreated()) return FALSE; if (!LoadPngToDIB(m_pngPath)) { BeginClose(TRUE); return FALSE; } UpdateLayered(); ShowWindow(SW_SHOWNOACTIVATE); SetTimer(IDT_CLOSE, m_showMillis, nullptr); if (m_enableFade) StartFadeTimer(TRUE); return TRUE; } void CImageToast::SetLink(LPCWSTR urlToOpen) { m_url = (urlToOpen ? urlToOpen : L""); } void CImageToast::CloseNow() { BeginClose(FALSE); } void CImageToast::OpenUrlIfAny() { if (m_opened) return; if (m_url.IsEmpty()) return; m_opened = TRUE; ::ShellExecuteW(nullptr, _T("open"), m_url, nullptr, nullptr, SW_SHOWNORMAL); }