feat: 다운로드 창 다크 테마 적용
- 배경 #2E2C2F, 타이틀 #BACDB0(강조색), 상태 텍스트 밝은 회색 - WM_CTLCOLORSTATIC으로 STATIC 컨트롤 색상 제어 - SetWindowTheme + PBM_SETBARCOLOR로 진행 막대 색상 변경 - "A301" 타이틀 레이블(13pt bold) + 상태 레이블(9pt) 분리 - 웹사이트 색상 팔레트와 통일 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
98
main.go
98
main.go
@@ -34,6 +34,7 @@ const (
|
|||||||
wmClose uint32 = 0x0010
|
wmClose uint32 = 0x0010
|
||||||
wmSetFont uint32 = 0x0030
|
wmSetFont uint32 = 0x0030
|
||||||
wmSetText uint32 = 0x000C
|
wmSetText uint32 = 0x000C
|
||||||
|
wmCtlColorStatic uint32 = 0x0138
|
||||||
wmAppDone uint32 = 0x8001
|
wmAppDone uint32 = 0x8001
|
||||||
|
|
||||||
wsPopup uintptr = 0x80000000
|
wsPopup uintptr = 0x80000000
|
||||||
@@ -46,6 +47,10 @@ const (
|
|||||||
pbsSmooth uintptr = 0x01
|
pbsSmooth uintptr = 0x01
|
||||||
pbmSetRange32 uint32 = 0x0406
|
pbmSetRange32 uint32 = 0x0406
|
||||||
pbmSetPos uint32 = 0x0402
|
pbmSetPos uint32 = 0x0402
|
||||||
|
pbmSetBarColor uint32 = 0x0409
|
||||||
|
pbmSetBkColor uint32 = 0x2001
|
||||||
|
|
||||||
|
setBkModeTransparent = 1
|
||||||
|
|
||||||
swShow = 5
|
swShow = 5
|
||||||
smCxScreen = 0
|
smCxScreen = 0
|
||||||
@@ -59,7 +64,19 @@ const (
|
|||||||
idYes = 6
|
idYes = 6
|
||||||
|
|
||||||
iccProgressClass uint32 = 0x00000020
|
iccProgressClass uint32 = 0x00000020
|
||||||
logpixelsX = 88
|
)
|
||||||
|
|
||||||
|
// rgb builds a COLORREF from R, G, B components.
|
||||||
|
func rgb(r, g, b uint8) uintptr {
|
||||||
|
return uintptr(r) | (uintptr(g) << 8) | (uintptr(b) << 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 웹사이트 색상과 동일한 팔레트
|
||||||
|
var (
|
||||||
|
colorBg = rgb(46, 44, 47) // #2E2C2F
|
||||||
|
colorText = rgb(200, 200, 200) // 밝은 회색
|
||||||
|
colorAccent = rgb(186, 205, 176) // #BACDB0
|
||||||
|
colorProgressBg = rgb(65, 63, 67) // bg보다 약간 밝은 색
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -68,6 +85,7 @@ var (
|
|||||||
gdi32 = windows.NewLazySystemDLL("gdi32.dll")
|
gdi32 = windows.NewLazySystemDLL("gdi32.dll")
|
||||||
shell32 = windows.NewLazySystemDLL("shell32.dll")
|
shell32 = windows.NewLazySystemDLL("shell32.dll")
|
||||||
comctl32 = windows.NewLazySystemDLL("comctl32.dll")
|
comctl32 = windows.NewLazySystemDLL("comctl32.dll")
|
||||||
|
uxtheme = windows.NewLazySystemDLL("uxtheme.dll")
|
||||||
|
|
||||||
messageBoxWProc = user32.NewProc("MessageBoxW")
|
messageBoxWProc = user32.NewProc("MessageBoxW")
|
||||||
registerClassExWProc = user32.NewProc("RegisterClassExW")
|
registerClassExWProc = user32.NewProc("RegisterClassExW")
|
||||||
@@ -88,12 +106,18 @@ var (
|
|||||||
shellExecuteWProc = shell32.NewProc("ShellExecuteW")
|
shellExecuteWProc = shell32.NewProc("ShellExecuteW")
|
||||||
getModuleHandleWProc = kernel32.NewProc("GetModuleHandleW")
|
getModuleHandleWProc = kernel32.NewProc("GetModuleHandleW")
|
||||||
createFontIndirectWProc = gdi32.NewProc("CreateFontIndirectW")
|
createFontIndirectWProc = gdi32.NewProc("CreateFontIndirectW")
|
||||||
|
createSolidBrushProc = gdi32.NewProc("CreateSolidBrush")
|
||||||
|
setTextColorProc = gdi32.NewProc("SetTextColor")
|
||||||
|
setBkModeProc = gdi32.NewProc("SetBkMode")
|
||||||
deleteObjectProc = gdi32.NewProc("DeleteObject")
|
deleteObjectProc = gdi32.NewProc("DeleteObject")
|
||||||
initCommonControlsExProc = comctl32.NewProc("InitCommonControlsEx")
|
initCommonControlsExProc = comctl32.NewProc("InitCommonControlsEx")
|
||||||
|
setWindowThemeProc = uxtheme.NewProc("SetWindowTheme")
|
||||||
|
|
||||||
wndProcCb uintptr
|
wndProcCb uintptr
|
||||||
|
titleLabelHwnd uintptr
|
||||||
progressLabelHwnd uintptr
|
progressLabelHwnd uintptr
|
||||||
progressBarHwnd uintptr
|
progressBarHwnd uintptr
|
||||||
|
hBrushBg uintptr
|
||||||
)
|
)
|
||||||
|
|
||||||
type wndClassExW struct {
|
type wndClassExW struct {
|
||||||
@@ -169,10 +193,14 @@ func dpiScale(px int, dpi uint32) uintptr {
|
|||||||
|
|
||||||
// ── Font helpers ─────────────────────────────────────────────────────────────
|
// ── Font helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func createUIFont(pointSize int, dpi uint32) uintptr {
|
func createUIFont(pointSize int, dpi uint32, bold bool) uintptr {
|
||||||
|
weight := int32(400) // FW_NORMAL
|
||||||
|
if bold {
|
||||||
|
weight = 700 // FW_BOLD
|
||||||
|
}
|
||||||
lf := logFontW{
|
lf := logFontW{
|
||||||
lfHeight: -int32(pointSize) * int32(dpi) / 72,
|
lfHeight: -int32(pointSize) * int32(dpi) / 72,
|
||||||
lfWeight: 400, // FW_NORMAL
|
lfWeight: weight,
|
||||||
lfCharSet: 1, // DEFAULT_CHARSET
|
lfCharSet: 1, // DEFAULT_CHARSET
|
||||||
lfQuality: 5, // CLEARTYPE_QUALITY
|
lfQuality: 5, // CLEARTYPE_QUALITY
|
||||||
}
|
}
|
||||||
@@ -219,6 +247,16 @@ func progressWndProc(hwnd, uMsg, wParam, lParam uintptr) uintptr {
|
|||||||
case wmAppDone:
|
case wmAppDone:
|
||||||
destroyWindowProc.Call(hwnd)
|
destroyWindowProc.Call(hwnd)
|
||||||
return 0
|
return 0
|
||||||
|
case wmCtlColorStatic:
|
||||||
|
// 다크 테마: 배경 브러시 + 텍스트 색 지정
|
||||||
|
hdc := wParam
|
||||||
|
setBkModeProc.Call(hdc, setBkModeTransparent)
|
||||||
|
if lParam == titleLabelHwnd {
|
||||||
|
setTextColorProc.Call(hdc, colorAccent)
|
||||||
|
} else {
|
||||||
|
setTextColorProc.Call(hdc, colorText)
|
||||||
|
}
|
||||||
|
return hBrushBg
|
||||||
}
|
}
|
||||||
ret, _, _ := defWindowProcWProc.Call(hwnd, uMsg, wParam, lParam)
|
ret, _, _ := defWindowProcWProc.Call(hwnd, uMsg, wParam, lParam)
|
||||||
return ret
|
return ret
|
||||||
@@ -234,7 +272,7 @@ func setProgress(text string, pct int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadWithProgress shows a DPI-aware progress window and downloads+extracts the zip.
|
// downloadWithProgress shows a DPI-aware dark-themed progress window and downloads+extracts the zip.
|
||||||
// Must be called from the main goroutine (Win32 message loop requirement).
|
// Must be called from the main goroutine (Win32 message loop requirement).
|
||||||
func downloadWithProgress(downloadURL, destDir string) error {
|
func downloadWithProgress(downloadURL, destDir string) error {
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
@@ -244,6 +282,10 @@ func downloadWithProgress(downloadURL, destDir string) error {
|
|||||||
dpi := getSystemDPI()
|
dpi := getSystemDPI()
|
||||||
s := func(px int) uintptr { return dpiScale(px, dpi) }
|
s := func(px int) uintptr { return dpiScale(px, dpi) }
|
||||||
|
|
||||||
|
// 배경 브러시 생성 (window proc에서도 사용)
|
||||||
|
hBrushBg, _, _ = createSolidBrushProc.Call(colorBg)
|
||||||
|
defer deleteObjectProc.Call(hBrushBg)
|
||||||
|
|
||||||
hInstance, _, _ := getModuleHandleWProc.Call(0)
|
hInstance, _, _ := getModuleHandleWProc.Call(0)
|
||||||
|
|
||||||
className, _ := windows.UTF16PtrFromString("A301Progress")
|
className, _ := windows.UTF16PtrFromString("A301Progress")
|
||||||
@@ -252,55 +294,81 @@ func downloadWithProgress(downloadURL, destDir string) error {
|
|||||||
lpfnWndProc: wndProcCb,
|
lpfnWndProc: wndProcCb,
|
||||||
hInstance: hInstance,
|
hInstance: hInstance,
|
||||||
lpszClassName: className,
|
lpszClassName: className,
|
||||||
hbrBackground: 16, // COLOR_BTNFACE+1
|
hbrBackground: hBrushBg,
|
||||||
}
|
}
|
||||||
registerClassExWProc.Call(uintptr(unsafe.Pointer(&wc)))
|
registerClassExWProc.Call(uintptr(unsafe.Pointer(&wc)))
|
||||||
|
|
||||||
screenW, _, _ := getSystemMetricsProc.Call(smCxScreen)
|
screenW, _, _ := getSystemMetricsProc.Call(smCxScreen)
|
||||||
screenH, _, _ := getSystemMetricsProc.Call(smCyScreen)
|
screenH, _, _ := getSystemMetricsProc.Call(smCyScreen)
|
||||||
winW := s(440)
|
winW := s(440)
|
||||||
winH := s(130)
|
winH := s(152)
|
||||||
x := (screenW - winW) / 2
|
x := (screenW - winW) / 2
|
||||||
y := (screenH - winH) / 2
|
y := (screenH - winH) / 2
|
||||||
|
|
||||||
title, _ := windows.UTF16PtrFromString("A301 - 게임 설치")
|
// 창 타이틀은 비워서 타이틀바를 최소화
|
||||||
|
titleStr, _ := windows.UTF16PtrFromString("A301 런처")
|
||||||
hwnd, _, _ := createWindowExWProc.Call(
|
hwnd, _, _ := createWindowExWProc.Call(
|
||||||
0,
|
0,
|
||||||
uintptr(unsafe.Pointer(className)),
|
uintptr(unsafe.Pointer(className)),
|
||||||
uintptr(unsafe.Pointer(title)),
|
uintptr(unsafe.Pointer(titleStr)),
|
||||||
wsPopup|wsCaption|wsSysMenu|wsVisible,
|
wsPopup|wsCaption|wsSysMenu|wsVisible,
|
||||||
x, y, winW, winH,
|
x, y, winW, winH,
|
||||||
0, 0, hInstance, 0,
|
0, 0, hInstance, 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
font := createUIFont(9, dpi)
|
titleFont := createUIFont(13, dpi, true)
|
||||||
defer deleteObjectProc.Call(font)
|
defer deleteObjectProc.Call(titleFont)
|
||||||
|
statusFont := createUIFont(9, dpi, false)
|
||||||
|
defer deleteObjectProc.Call(statusFont)
|
||||||
|
|
||||||
// 상태 레이블 (텍스트)
|
|
||||||
staticClass, _ := windows.UTF16PtrFromString("STATIC")
|
staticClass, _ := windows.UTF16PtrFromString("STATIC")
|
||||||
|
|
||||||
|
// ── "A301" 타이틀 레이블 ──
|
||||||
|
// 클라이언트 영역 레이아웃 (base 96 DPI):
|
||||||
|
// y=14 h=28 → "A301" 타이틀 (13pt bold, 강조색)
|
||||||
|
// y=52 h=20 → 상태 텍스트 (9pt, 밝은 회색)
|
||||||
|
// y=82 h=18 → 진행 막대
|
||||||
|
titleText, _ := windows.UTF16PtrFromString("A301")
|
||||||
|
titleLabelHwnd, _, _ = createWindowExWProc.Call(
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(staticClass)),
|
||||||
|
uintptr(unsafe.Pointer(titleText)),
|
||||||
|
wsChild|wsVisible|ssCenter,
|
||||||
|
s(20), s(14), winW-s(40), s(28),
|
||||||
|
hwnd, 0, hInstance, 0,
|
||||||
|
)
|
||||||
|
sendMessageWProc.Call(titleLabelHwnd, uintptr(wmSetFont), titleFont, 1)
|
||||||
|
|
||||||
|
// ── 상태 레이블 ──
|
||||||
initText, _ := windows.UTF16PtrFromString("게임 파일을 다운로드하는 중...")
|
initText, _ := windows.UTF16PtrFromString("게임 파일을 다운로드하는 중...")
|
||||||
progressLabelHwnd, _, _ = createWindowExWProc.Call(
|
progressLabelHwnd, _, _ = createWindowExWProc.Call(
|
||||||
0,
|
0,
|
||||||
uintptr(unsafe.Pointer(staticClass)),
|
uintptr(unsafe.Pointer(staticClass)),
|
||||||
uintptr(unsafe.Pointer(initText)),
|
uintptr(unsafe.Pointer(initText)),
|
||||||
wsChild|wsVisible|ssCenter,
|
wsChild|wsVisible|ssCenter,
|
||||||
s(16), s(20), winW-s(32), s(22),
|
s(20), s(52), winW-s(40), s(20),
|
||||||
hwnd, 0, hInstance, 0,
|
hwnd, 0, hInstance, 0,
|
||||||
)
|
)
|
||||||
sendMessageWProc.Call(progressLabelHwnd, uintptr(wmSetFont), font, 1)
|
sendMessageWProc.Call(progressLabelHwnd, uintptr(wmSetFont), statusFont, 1)
|
||||||
|
|
||||||
// 진행 막대
|
// ── 진행 막대 ──
|
||||||
progressClass, _ := windows.UTF16PtrFromString("msctls_progress32")
|
progressClass, _ := windows.UTF16PtrFromString("msctls_progress32")
|
||||||
progressBarHwnd, _, _ = createWindowExWProc.Call(
|
progressBarHwnd, _, _ = createWindowExWProc.Call(
|
||||||
0,
|
0,
|
||||||
uintptr(unsafe.Pointer(progressClass)),
|
uintptr(unsafe.Pointer(progressClass)),
|
||||||
0,
|
0,
|
||||||
wsChild|wsVisible|pbsSmooth,
|
wsChild|wsVisible|pbsSmooth,
|
||||||
s(16), s(52), winW-s(32), s(22),
|
s(20), s(82), winW-s(40), s(18),
|
||||||
hwnd, 0, hInstance, 0,
|
hwnd, 0, hInstance, 0,
|
||||||
)
|
)
|
||||||
sendMessageWProc.Call(progressBarHwnd, uintptr(pbmSetRange32), 0, 100)
|
sendMessageWProc.Call(progressBarHwnd, uintptr(pbmSetRange32), 0, 100)
|
||||||
|
|
||||||
|
// 비주얼 스타일 비활성화 → PBM_SETBARCOLOR/PBM_SETBKCOLOR 적용 가능
|
||||||
|
empty, _ := windows.UTF16PtrFromString("")
|
||||||
|
setWindowThemeProc.Call(progressBarHwnd, uintptr(unsafe.Pointer(empty)), uintptr(unsafe.Pointer(empty)))
|
||||||
|
sendMessageWProc.Call(progressBarHwnd, uintptr(pbmSetBarColor), 0, colorAccent)
|
||||||
|
sendMessageWProc.Call(progressBarHwnd, uintptr(pbmSetBkColor), 0, colorProgressBg)
|
||||||
|
|
||||||
showWindowProc.Call(hwnd, swShow)
|
showWindowProc.Call(hwnd, swShow)
|
||||||
updateWindowProc.Call(hwnd)
|
updateWindowProc.Call(hwnd)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user