270 lines
7.0 KiB
C++
270 lines
7.0 KiB
C++
/*---------------------------------------------------------------------------*/
|
|
// 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);
|
|
}
|