Hide native title bar, add custom frameless window controls
- Remove WS_CAPTION via SetWindowLong to hide native Windows title bar - Keep WS_THICKFRAME for window resizing from edges - Add custom Win11-style window control buttons (minimize, maximize, close) - Close button turns red on hover (#c42b1c) matching Windows 11 - Title bar is draggable via -webkit-app-region: drag - Bind windowMinimize/windowMaximize/windowClose Go functions to JS - Center system status indicator in title bar Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
64
main.go
64
main.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/jchv/go-webview2"
|
"github.com/jchv/go-webview2"
|
||||||
"github.com/mumur/driver-booster/internal/bridge"
|
"github.com/mumur/driver-booster/internal/bridge"
|
||||||
@@ -22,6 +23,51 @@ import (
|
|||||||
//go:embed ui/*
|
//go:embed ui/*
|
||||||
var uiFS embed.FS
|
var uiFS embed.FS
|
||||||
|
|
||||||
|
var (
|
||||||
|
user32 = syscall.NewLazyDLL("user32.dll")
|
||||||
|
procGetWindowLong = user32.NewProc("GetWindowLongW")
|
||||||
|
procSetWindowLong = user32.NewProc("SetWindowLongW")
|
||||||
|
procSetWindowPos = user32.NewProc("SetWindowPos")
|
||||||
|
procPostMessage = user32.NewProc("PostMessageW")
|
||||||
|
procShowWindow = user32.NewProc("ShowWindow")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
gwlStyle = -16
|
||||||
|
wsCaption = 0x00C00000
|
||||||
|
wsSysMenu = 0x00080000
|
||||||
|
wsThickFrame = 0x00040000
|
||||||
|
wsMinBox = 0x00020000
|
||||||
|
wsMaxBox = 0x00010000
|
||||||
|
wsVisible = 0x10000000
|
||||||
|
wsPopup = 0x80000000
|
||||||
|
|
||||||
|
swpFrameChanged = 0x0020
|
||||||
|
swpNoMove = 0x0002
|
||||||
|
swpNoSize = 0x0001
|
||||||
|
swpNoZOrder = 0x0004
|
||||||
|
|
||||||
|
wmSysCommand = 0x0112
|
||||||
|
scMinimize = 0xF020
|
||||||
|
scMaximize = 0xF030
|
||||||
|
scRestore = 0xF120
|
||||||
|
scClose = 0xF060
|
||||||
|
|
||||||
|
swMaximize = 3
|
||||||
|
swRestore = 9
|
||||||
|
)
|
||||||
|
|
||||||
|
func removeWindowFrame(hwnd uintptr) {
|
||||||
|
gwlStyleUintptr := uintptr(0xFFFFFFF0) // GWL_STYLE = -16 as unsigned
|
||||||
|
style, _, _ := procGetWindowLong.Call(hwnd, gwlStyleUintptr)
|
||||||
|
// Remove caption (title bar) but keep thick frame (resizable) and min/max boxes
|
||||||
|
newStyle := (style &^ wsCaption) | wsThickFrame | wsMinBox | wsMaxBox
|
||||||
|
procSetWindowLong.Call(hwnd, gwlStyleUintptr, newStyle)
|
||||||
|
// Force redraw frame
|
||||||
|
procSetWindowPos.Call(hwnd, 0, 0, 0, 0, 0,
|
||||||
|
swpFrameChanged|swpNoMove|swpNoSize|swpNoZOrder)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Start embedded HTTP server for UI assets
|
// Start embedded HTTP server for UI assets
|
||||||
uiContent, err := fs.Sub(uiFS, "ui")
|
uiContent, err := fs.Sub(uiFS, "ui")
|
||||||
@@ -74,6 +120,24 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer w.Destroy()
|
defer w.Destroy()
|
||||||
|
|
||||||
|
// Remove native title bar to use our custom one
|
||||||
|
hwnd := uintptr(unsafe.Pointer(w.Window()))
|
||||||
|
removeWindowFrame(hwnd)
|
||||||
|
|
||||||
|
// Bind window control functions for custom title bar buttons
|
||||||
|
w.Bind("windowMinimize", func() {
|
||||||
|
procPostMessage.Call(hwnd, wmSysCommand, scMinimize, 0)
|
||||||
|
})
|
||||||
|
w.Bind("windowMaximize", func() {
|
||||||
|
procPostMessage.Call(hwnd, wmSysCommand, scMaximize, 0)
|
||||||
|
})
|
||||||
|
w.Bind("windowRestore", func() {
|
||||||
|
procPostMessage.Call(hwnd, wmSysCommand, scRestore, 0)
|
||||||
|
})
|
||||||
|
w.Bind("windowClose", func() {
|
||||||
|
procPostMessage.Call(hwnd, wmSysCommand, scClose, 0)
|
||||||
|
})
|
||||||
|
|
||||||
// Bind Go functions to JS
|
// Bind Go functions to JS
|
||||||
w.Bind("goGetSysInfo", func() string {
|
w.Bind("goGetSysInfo", func() string {
|
||||||
info := b.SysInfo.Collect()
|
info := b.SysInfo.Collect()
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<!-- Title Bar -->
|
<!-- Custom Title Bar (frameless window) -->
|
||||||
<div class="titlebar">
|
<div class="titlebar" id="titlebar">
|
||||||
<div class="titlebar-left">
|
<div class="titlebar-left">
|
||||||
<img src="icon-app.png" class="app-logo-img" alt="">
|
<img src="icon-app.png" class="app-logo-img" alt="">
|
||||||
<div class="app-title">
|
<div class="app-title">
|
||||||
@@ -17,12 +17,23 @@
|
|||||||
<span class="app-edition">PRO</span>
|
<span class="app-edition">PRO</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-right">
|
<div class="titlebar-center">
|
||||||
<div class="sys-status">
|
<div class="sys-status">
|
||||||
<div class="status-led"></div>
|
<div class="status-led"></div>
|
||||||
<span>System Protected</span>
|
<span>System Protected</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="titlebar-controls">
|
||||||
|
<button class="win-btn win-minimize" onclick="windowMinimize()" title="Minimize">
|
||||||
|
<svg width="10" height="1" viewBox="0 0 10 1"><rect width="10" height="1" fill="currentColor"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="win-btn win-maximize" onclick="windowMaximize()" title="Maximize">
|
||||||
|
<svg width="10" height="10" viewBox="0 0 10 10"><rect x="0.5" y="0.5" width="9" height="9" fill="none" stroke="currentColor" stroke-width="1"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="win-btn win-close" onclick="windowClose()" title="Close">
|
||||||
|
<svg width="10" height="10" viewBox="0 0 10 10"><line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="1.2"/><line x1="10" y1="0" x2="0" y2="10" stroke="currentColor" stroke-width="1.2"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Toolbar Tabs -->
|
<!-- Toolbar Tabs -->
|
||||||
|
|||||||
42
ui/style.css
42
ui/style.css
@@ -67,31 +67,53 @@ body {
|
|||||||
/* ---- App Shell ---- */
|
/* ---- App Shell ---- */
|
||||||
.app { display:flex; flex-direction:column; height:100vh; background:var(--mica); }
|
.app { display:flex; flex-direction:column; height:100vh; background:var(--mica); }
|
||||||
|
|
||||||
/* ---- Title Bar (Win11 style) ---- */
|
/* ---- Custom Title Bar (frameless window) ---- */
|
||||||
.titlebar {
|
.titlebar {
|
||||||
display:flex; justify-content:space-between; align-items:center;
|
display:flex; align-items:center;
|
||||||
padding:10px 16px;
|
padding:0 0 0 12px;
|
||||||
|
height:38px;
|
||||||
background:var(--bg);
|
background:var(--bg);
|
||||||
border-bottom:1px solid var(--border);
|
border-bottom:1px solid var(--border);
|
||||||
flex-shrink:0;
|
flex-shrink:0;
|
||||||
-webkit-app-region:drag;
|
-webkit-app-region:drag;
|
||||||
|
user-select:none;
|
||||||
}
|
}
|
||||||
.titlebar-left { display:flex; align-items:center; gap:10px; -webkit-app-region:no-drag; }
|
.titlebar-left { display:flex; align-items:center; gap:8px; -webkit-app-region:no-drag; flex-shrink:0; }
|
||||||
.app-title { display:flex; align-items:center; gap:8px; }
|
.titlebar-center { flex:1; display:flex; align-items:center; justify-content:center; }
|
||||||
.app-name { font-size:13px; font-weight:600; color:var(--text); letter-spacing:.3px; }
|
.app-title { display:flex; align-items:center; gap:6px; }
|
||||||
|
.app-name { font-size:12px; font-weight:600; color:var(--text); letter-spacing:.3px; }
|
||||||
.app-edition {
|
.app-edition {
|
||||||
font-size:10px; font-weight:600; color:var(--accent);
|
font-size:9px; font-weight:600; color:var(--accent);
|
||||||
background:var(--accent-subtle); padding:1px 6px;
|
background:var(--accent-subtle); padding:1px 5px;
|
||||||
border-radius:var(--radius-sm); letter-spacing:.5px;
|
border-radius:var(--radius-sm); letter-spacing:.5px;
|
||||||
}
|
}
|
||||||
.sys-status { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--text-sec); }
|
.sys-status { display:flex; align-items:center; gap:6px; font-size:11px; color:var(--text-dim); }
|
||||||
.status-led {
|
.status-led {
|
||||||
width:8px; height:8px; border-radius:50%;
|
width:6px; height:6px; border-radius:50%;
|
||||||
background:var(--green); box-shadow:0 0 6px var(--green);
|
background:var(--green); box-shadow:0 0 6px var(--green);
|
||||||
animation:pulse 2.5s ease-in-out infinite;
|
animation:pulse 2.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||||
|
|
||||||
|
/* Window control buttons (Win11 style) */
|
||||||
|
.titlebar-controls {
|
||||||
|
display:flex; align-items:stretch;
|
||||||
|
height:38px; flex-shrink:0;
|
||||||
|
-webkit-app-region:no-drag;
|
||||||
|
}
|
||||||
|
.win-btn {
|
||||||
|
width:46px; height:38px;
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
background:transparent; border:none;
|
||||||
|
color:var(--text-sec); cursor:pointer;
|
||||||
|
transition:background .1s ease;
|
||||||
|
font-family:inherit;
|
||||||
|
}
|
||||||
|
.win-btn:hover { background:rgba(255,255,255,0.06); color:var(--text); }
|
||||||
|
.win-btn:active { background:rgba(255,255,255,0.04); }
|
||||||
|
.win-close:hover { background:#c42b1c; color:white; }
|
||||||
|
.win-close:active { background:#b52a1c; }
|
||||||
|
|
||||||
/* ---- Toolbar (Win11 Tabs / NavigationView) ---- */
|
/* ---- Toolbar (Win11 Tabs / NavigationView) ---- */
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display:flex; gap:2px; padding:6px 12px;
|
display:flex; gap:2px; padding:6px 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user