Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 87 additions & 7 deletions JarkViewer/src/jarkUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -445,15 +445,85 @@ void jarkUtils::copyImageToClipboard(const cv::Mat& image) {
}


// 全屏切换遮挡窗口的窗口过程:纯色填充(颜色匹配当前主题),不响应任何交互。
// 作用:在主窗口切换样式/位置时盖住整个屏幕,让用户看不到 DWM 的过渡帧
// (DWM 在 SWP_FRAMECHANGED 后会异步重算非客户区,期间用 Aero 兼容模板作为回退帧,
// 这就是标题栏/最小化最大化关闭按钮短暂降级成 aero 样式的根因)。
static LRESULT CALLBACK JarkFullScreenMaskWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_ERASEBKGND: {
HDC dc = (HDC)wp;
RECT rc; GetClientRect(hwnd, &rc);
// currentTheme.BG 是 0xAARRGGBB 形式(小端 BGRA as uint32)
uint32_t bg = GlobalVar::currentTheme.BG;
HBRUSH brush = CreateSolidBrush(RGB((bg >> 16) & 0xFF, (bg >> 8) & 0xFF, bg & 0xFF));
FillRect(dc, &rc, brush);
DeleteObject(brush);
return 1;
}
case WM_PAINT: {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
RECT rc; GetClientRect(hwnd, &rc);
uint32_t bg = GlobalVar::currentTheme.BG;
HBRUSH brush = CreateSolidBrush(RGB((bg >> 16) & 0xFF, (bg >> 8) & 0xFF, bg & 0xFF));
FillRect(ps.hdc, &rc, brush);
DeleteObject(brush);
EndPaint(hwnd, &ps);
return 0;
}
case WM_SETCURSOR:
// 遮挡期间隐藏鼠标,避免在纯色块上出现突兀的光标
SetCursor(nullptr);
return TRUE;
}
return DefWindowProcW(hwnd, msg, wp, lp);
}

static void JarkEnsureFullScreenMaskClassRegistered() {
static bool registered = false;
if (registered) return;
WNDCLASSEXW wc = { sizeof(wc) };
wc.lpfnWndProc = JarkFullScreenMaskWndProc;
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpszClassName = L"JarkFullScreenMaskWnd";
wc.hCursor = nullptr;
RegisterClassExW(&wc);
registered = true;
}

void jarkUtils::ToggleFullScreen(HWND hwnd) {
static RECT preRect{};
static DWORD preStyle = 0;
static DWORD preExStyle = 0;

static bool isFullScreen = false;

// 取主窗口所在显示器,遮挡窗口只盖这一个屏幕即可
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO monitorInfo = { sizeof(monitorInfo) };
GetMonitorInfo(monitor, &monitorInfo);

// 先创建并显示全屏遮挡窗口,盖住整个屏幕。
// 关键:遮挡窗口本身是 WS_POPUP(无标题栏),DWM 不会为它画 Aero 过渡帧;
// 它盖在主窗口之上,主窗口切换样式时 DWM 产生的 Aero 回退帧用户就看不到了。
JarkEnsureFullScreenMaskClassRegistered();
HWND maskWnd = CreateWindowExW(
WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,
L"JarkFullScreenMaskWnd", L"",
WS_POPUP,
monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top,
monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left,
monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top,
nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
ShowWindow(maskWnd, SW_SHOWNOACTIVATE);
// 强制遮挡窗口立即绘制一帧,确保切换前屏幕已被纯色覆盖
UpdateWindow(maskWnd);
// 让 DWM 把这一帧合成上屏
DwmFlush();

// 此时屏幕已被遮挡窗口覆盖,安全地切换主窗口样式/位置
if (isFullScreen) {
// 退出全屏模式,恢复之前的窗口状态
SetWindowLong(hwnd, GWL_STYLE, preStyle);
SetWindowLong(hwnd, GWL_EXSTYLE, preExStyle);
SetWindowPos(hwnd, nullptr, preRect.left, preRect.top,
Expand All @@ -462,16 +532,10 @@ void jarkUtils::ToggleFullScreen(HWND hwnd) {
SWP_NOZORDER | SWP_FRAMECHANGED);
}
else {
// 保存当前窗口状态
GetWindowRect(hwnd, &preRect);
preStyle = GetWindowLong(hwnd, GWL_STYLE);
preExStyle = GetWindowLong(hwnd, GWL_EXSTYLE);

// 切换到全屏模式
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO monitorInfo = { sizeof(monitorInfo) };
GetMonitorInfo(monitor, &monitorInfo);

SetWindowLong(hwnd, GWL_STYLE, preStyle & ~(WS_CAPTION | WS_THICKFRAME));
SetWindowLong(hwnd, GWL_EXSTYLE, preExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));

Expand All @@ -482,6 +546,22 @@ void jarkUtils::ToggleFullScreen(HWND hwnd) {
SWP_NOZORDER | SWP_FRAMECHANGED);
}

// 同步排空待处理消息,确保窗口过程已处理完样式/位置变更
MSG msg;
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
// 让 DWM 完成过渡帧合成(此时主窗口的 Aero 回退帧被遮挡窗口盖住,用户看不到)
DwmFlush();
// 给 DWM 足够时间完成最终样式渲染(经验值,约一帧多)
Sleep(30);
// 再 flush 一次确保最终帧已上屏
DwmFlush();

// 销毁遮挡窗口,露出已经完成切换的主窗口
DestroyWindow(maskWnd);

isFullScreen = !isFullScreen;
}

Expand Down
73 changes: 35 additions & 38 deletions JarkViewer/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1112,7 +1112,7 @@ class JarkViewerApp : public D3D11App {
// 内置的用于提示的图像
}
else { // 普通图像 画边框
const uint32_t lineColor = 0xFF808080;
const uint32_t lineColor = GlobalVar::isCurrentUIDarkMode ? 0xFF333333 : 0xFF000000;//我更喜欢这个颜色
if (0 < xStart and xStart < canvasW) {
const int yMax = std::min(yEnd + 1, canvasH);
for (int y = std::max(yStart - 1, 0); y < yMax; y++) {
Expand Down Expand Up @@ -1660,7 +1660,7 @@ class JarkViewerApp : public D3D11App {
if (operateAction.action == ActionENUM::none &&
curPar.zoomCur == curPar.zoomTarget &&
curPar.slideCur == curPar.slideTarget &&
(curPar.imageAssetPtr->format != ImageFormat::Animated ||
(curPar.imageAssetPtr->format != ImageFormat::Animated ||
(curPar.imageAssetPtr->format == ImageFormat::Animated && curPar.isAnimationPause))) {

Sleep(1); // Windows机制限制,实际时长最小只能 15.6ms
Expand Down Expand Up @@ -1900,30 +1900,26 @@ class JarkViewerApp : public D3D11App {
} break;

case ActionENUM::zoomIn: {
if (curPar.zoomIndex < curPar.zoomList.size() - 1) {
curPar.zoomIndex++;

auto zoomNext = curPar.zoomList[curPar.zoomIndex];
if (curPar.zoomTarget && zoomNext != curPar.zoomTarget) {
computeZoomSlide(zoomNext);
}
// 无极缩放,步长 30%
constexpr double zoomFactor = 1.3;
int64_t zoomNext = (int64_t)(curPar.zoomTarget * zoomFactor);
if (zoomNext <= curPar.zoomTarget) zoomNext = curPar.zoomTarget + 1;
if (curPar.zoomTarget && zoomNext != curPar.zoomTarget) {
computeZoomSlide(zoomNext);
curPar.zoomTarget = zoomNext;
smoothShift = true;
}
} break;

case ActionENUM::zoomOut: {
// 无极缩放,步长 30%
constexpr double zoomFactor = 1.3;
// 不宜缩太小
if (curPar.zoomTarget <= curPar.ZOOM_BASE && (curPar.zoomTarget * std::min(curPar.width, curPar.height) / curPar.ZOOM_BASE) < 4)
int64_t zoomNext = std::max<int64_t>((int64_t)(curPar.zoomTarget / zoomFactor), 1);
if (zoomNext <= curPar.ZOOM_BASE && (zoomNext * std::min(curPar.width, curPar.height) / curPar.ZOOM_BASE) < 4)
break;

if (curPar.zoomIndex > 0) {
curPar.zoomIndex--;

auto zoomNext = curPar.zoomList[curPar.zoomIndex];
if (curPar.zoomTarget && zoomNext != curPar.zoomTarget) {
computeZoomSlide(zoomNext);
}
if (curPar.zoomTarget && zoomNext != curPar.zoomTarget) {
computeZoomSlide(zoomNext);
curPar.zoomTarget = zoomNext;
smoothShift = true;
}
Expand Down Expand Up @@ -2019,36 +2015,37 @@ class JarkViewerApp : public D3D11App {
}

if (curPar.zoomCur != curPar.zoomTarget || curPar.slideCur != curPar.slideTarget) {
if (GlobalVar::settingParameter.isAllowZoomAnimation && smoothShift) { // 简单缩放动画
const int progressMax = 1 << 8;
static int progressCnt = progressMax;
if (GlobalVar::settingParameter.isAllowZoomAnimation && smoothShift) {
// 250ms 固定时长动画
using namespace std::chrono;
static constexpr auto animDuration = milliseconds(250);
static steady_clock::time_point animStartTime;
static int64_t zoomInit = 0;
static int64_t zoomTargetInit = 0;
static Cood slideInit{}, slideTargetInit{};

//未开始进行动画 或 动画未完成就有新缩放操作
if (progressCnt >= progressMax || zoomTargetInit != curPar.zoomTarget || slideTargetInit != curPar.slideTarget) {
progressCnt = 1;
// 动画重新开始(新操作打断旧动画)
if (zoomTargetInit != curPar.zoomTarget || slideTargetInit != curPar.slideTarget) {
animStartTime = steady_clock::now();
zoomInit = curPar.zoomCur;
zoomTargetInit = curPar.zoomTarget;
slideInit = curPar.slideCur;
slideTargetInit = curPar.slideTarget;
}

auto elapsed = duration_cast<milliseconds>(steady_clock::now() - animStartTime);
if (elapsed >= animDuration) {
curPar.zoomCur = curPar.zoomTarget;
curPar.slideCur = curPar.slideTarget;
smoothShift = false;
}
else {
auto addDelta = ((progressMax - progressCnt) / 4);
if (addDelta <= 1) {
progressCnt = progressMax;
curPar.zoomCur = curPar.zoomTarget;
curPar.slideCur = curPar.slideTarget;
smoothShift = false;
}
else {
progressCnt += addDelta;
curPar.zoomCur = zoomInit + (curPar.zoomTarget - zoomInit) * progressCnt / progressMax;
const double t = (double)progressCnt / progressMax;
curPar.slideCur.x = (int)std::round(slideInit.x + (curPar.slideTarget.x - slideInit.x) * t);
curPar.slideCur.y = (int)std::round(slideInit.y + (curPar.slideTarget.y - slideInit.y) * t);
}
// 归一化时间 [0, 1],三次方缓出 (cubic ease-out)
double t = (double)elapsed.count() / (double)animDuration.count();
double easedT = 1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t);
curPar.zoomCur = zoomInit + (int64_t)((curPar.zoomTarget - zoomInit) * easedT);
curPar.slideCur.x = (int)std::round(slideInit.x + (curPar.slideTarget.x - slideInit.x) * easedT);
curPar.slideCur.y = (int)std::round(slideInit.y + (curPar.slideTarget.y - slideInit.y) * easedT);
}
}
else {
Expand Down