feat(explorer): 窗口样式差异可见化(标题栏/边框圆角/按钮按样式区分)

参考 desktop.vp 的 _draw_app_window 与三枚按钮函数,把 5 种样式的差异做到一眼可辨:标题栏 CLASSIC 蓝紫纯色 / WIN10 近黑 / WIN11 深灰 / WIN7 浅蓝 Aero 渐变(alpha 玻璃) / WINXP 蓝渐变;边框 CLASSIC-win10 直角、WIN7/11/XP 圆角 r=8;按钮 CLASSIC 16x16 深蓝底灰 X、WIN10/11 46x30 无底色灰字形、WIN7 28x20、WINXP 21x21 且红底白 X。新增 _is_in_corner/_is_on_border/_corner_alpha 与按行绘制的 _draw_flat_row/_draw_round_row/_blend_row/_title_row_color;关闭键热区改为与绘制共用同一套几何函数,避免点击错位。标题栏高度仍统一用 TITLE_H=32:app 侧内容区高度依赖 vpui 的 TITLE_H,改高度会让内容错位,故差异只由配色/圆角/按钮体现。
This commit is contained in:
TermiNexus committed 2026-10-16 23:04:21 +08:00
1 parent aab4643dfa
commit 78b0ca8db7
1 file changed
+333 -71
+333 -71
View File
@@ -21,9 +21,6 @@ _mouse_logged: int = 0
define TITLE_H = 32 # 必须与 vpsdk/vpui.vp 的 TITLE_H 一致(app 用 h - TITLE_H - 2 算内容高)
define TITLE_GAP = 2
define CLOSE_SZ = 14
define CLOSE_PAD = 5
define BTN_GAP = 2
define CURSOR_BMP_W = 16
define CURSOR_BMP_H = 16
# 任务栏与窗口装饰尺寸(对齐内核桌面 desktop.vp)
@@ -31,6 +28,13 @@ define TASKBAR_H = 50
define TASKBAR_LOGO_SZ = 32
define TASKBAR_ICON_SZ = 36
define TASKBAR_ICON_PAD = 6
# 窗口按钮尺寸(按样式;poll_input 的关闭键热区必须复用同一套取值)
define BTN_SIZE = 16 # CLASSIC
define BTN_WIN_W = 46 # WIN10/WIN11:宽条,高 = TITLE_H - 2
define BTN_W7_W = 28 # WIN7
define BTN_W7_H = 20
define BTN_XP_SZ = 21 # WINXP
define WIN_RADIUS = 8 # WIN11/WIN7/WINXP 的窗口圆角半径
# 窗口样式(与 vpsdk/window.vp 的 STYLE_* 对齐);当前 CREATE 一律按 CLASSIC 建窗
define STYLE_CLASSIC = 0
define STYLE_WIN10 = 1
@@ -67,6 +71,52 @@ _CURSOR_ARROW: [256]uint8 = [
]
# 圆角窗口的角点/边框判定(移植自内核桌面 desktop.vp)
def _is_in_corner(px: int, py: int, cx: int, cy: int, r: int) -> int:
dx: int = px - cx
dy: int = py - cy
if dx < 0: dx = 0 - dx
if dy < 0: dy = 0 - dy
if dx <= r and dy <= r:
if dx * dx + dy * dy > r * r: return 0
return 1
def _is_on_border(rx: int, ry: int, ww: int, wh: int, r: int) -> int:
if rx == 0 or rx == ww - 1 or ry == 0 or ry == wh - 1:
return 1
if ry < r:
if rx < r:
dx: int = rx - r
dy: int = ry - r
if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1
elif rx >= ww - r:
dx = rx - (ww - r - 1)
dy = ry - r
if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1
elif ry >= wh - r:
if rx < r:
dx = rx - r
dy = ry - (wh - r - 1)
if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1
elif rx >= ww - r:
dx = rx - (ww - r - 1)
dy = ry - (wh - r - 1)
if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1
return 0
def _corner_alpha(px: int, py: int, cx: int, cy: int, r: int) -> int:
dx: int = px - cx
dy: int = py - cy
if dx < 0: dx = 0 - dx
if dy < 0: dy = 0 - dy
if dx <= r and dy <= r:
d2: int = dx * dx + dy * dy
if d2 > r * r: return 0
if d2 >= (r + 1) * (r + 1): return 160
return 255
return 255
class DeskServer:
# 屏幕
fb: uint32* # /dev/fb0 映射后的线性地址
@@ -380,54 +430,261 @@ class DeskServer:
px += 1
yy += 1
# ---------------- 窗口按钮(按行绘字形,CLASSIC 配色)----------------
def _draw_close_row(self, bx: int, by: int, sz: int, yy: int):
self.row_fill(bx, yy, sz, self.rgb(uint32(40), uint32(40), uint32(70)))
x_col: uint32 = self.rgb(uint32(150), uint32(150), uint32(150))
cx: int = bx + sz / 2
cy: int = by + sz / 2
# ---------------- 窗口逐行绘制(按样式)----------------
# 把 col 以 alpha 混到后备缓冲已有像素上(WIN7 半透明标题栏用)
def _blend_row(self, x0: int, y: int, w: int, col: uint32, a: int):
if y < 0 or y >= self.fb_h or w <= 0 or a <= 0: return
x: int = x0
dw: int = w
if x < 0:
dw = dw + x
x = 0
if x + dw > self.fb_w:
dw = self.fb_w - x
if dw <= 0: return
base: int = y * self.fb_w
i: int = 0
while i < dw:
idx: int = base + x + i
self.back[idx] = self._blend(self.back[idx], col, a)
i += 1
# 标题栏每行颜色:WIN7/WINXP 竖直渐变 + 首两行高光 + 末行暗边,其余样式纯色
def _title_row_color(self, style: int, rel: int, th: int, active: int, title_col: uint32) -> uint32:
if style != STYLE_WIN7 and style != STYLE_WINXP:
return title_col
ratio: int = (rel * 100) / th
if ratio > 100: ratio = 100
r: int = 0
g: int = 0
b: int = 0
if style == STYLE_WIN7:
if active != 0:
r = 156 + ((186 - 156) * ratio) / 100
g = 181 + ((209 - 181) * ratio) / 100
b = 209 + ((233 - 209) * ratio) / 100
else:
r = 180 + ((200 - 180) * ratio) / 100
g = 195 + ((210 - 195) * ratio) / 100
b = 210 + ((225 - 210) * ratio) / 100
else:
if active != 0:
r = 107 + ((58 - 107) * ratio) / 100
g = 158 + ((124 - 158) * ratio) / 100
b = 212 + ((195 - 212) * ratio) / 100
else:
r = 150 + ((115 - 150) * ratio) / 100
g = 170 + ((135 - 170) * ratio) / 100
b = 205 + ((175 - 205) * ratio) / 100
col: uint32 = self.rgb(uint32(r), uint32(g), uint32(b))
if rel == 0:
if style == STYLE_WIN7:
col = self._blend(col, self.rgb(uint32(220), uint32(235), uint32(255)), 50)
else:
col = self._blend(col, self.rgb(uint32(200), uint32(225), uint32(255)), 50)
elif rel == 1:
if style == STYLE_WIN7:
col = self._blend(col, self.rgb(uint32(210), uint32(225), uint32(250)), 20)
else:
col = self._blend(col, self.rgb(uint32(200), uint32(225), uint32(255)), 25)
if rel == th - 1:
if style == STYLE_WIN7:
col = self._blend(col, self.rgb(uint32(130), uint32(160), uint32(200)), 25)
else:
col = self._blend(col, self.rgb(uint32(100), uint32(160), uint32(230)), 40)
return col
# 直边行:整行底色 + 内容 blit + 左右/上下 1px 边框
def _draw_flat_row(self, s: desk.WinSlot*, yy: int, rel: int, row_col: uint32,
row_is_title: int, border_col: uint32, title_alpha: int,
buf: uint32*, fw: int, fh: int, content_y0: int):
wx: int = s.ux
ww: int = s.w
wh: int = s.h
content_row: int = yy - content_y0
if row_is_title != 0 and title_alpha < 255:
self._blend_row(wx + 1, yy, ww - 2, row_col, title_alpha)
else:
self.row_fill(wx, yy, ww, row_col)
if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh:
self.row_blit(wx + 1, yy, buf, content_row * fw + 1, ww - 2)
if rel == 0 or rel == wh - 1:
self.row_fill(wx, yy, ww, border_col)
else:
self.row_fill(wx, yy, 1, border_col)
self.row_fill(wx + ww - 1, yy, 1, border_col)
# 圆角带的行:逐像素按圆角形状裁剪,画边框/标题渐变/内容
def _draw_round_row(self, s: desk.WinSlot*, yy: int, rel: int, row_col: uint32,
row_is_title: int, outline_col: uint32, r: int, title_alpha: int,
buf: uint32*, fw: int, fh: int, content_y0: int):
wx: int = s.ux
ww: int = s.w
wh: int = s.h
content_row: int = yy - content_y0
do_blit: int = 0
if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh:
do_blit = 1
px: int = wx
xend: int = wx + ww
while px < xend:
if px >= 0 and px < self.fb_w:
rx: int = px - wx
in_corner: int = 1
on_border: int = 0
if rel < r:
if rx < r:
in_corner = _is_in_corner(rx, rel, r, r, r)
elif rx >= ww - r:
in_corner = _is_in_corner(rx, rel, ww - r - 1, r, r)
elif rel >= wh - r:
if rx < r:
in_corner = _is_in_corner(rx, rel, r, wh - r - 1, r)
elif rx >= ww - r:
in_corner = _is_in_corner(rx, rel, ww - r - 1, wh - r - 1, r)
if in_corner == 0:
on_border = _is_on_border(rx, rel, ww, wh, r)
if on_border == 0:
px += 1
continue
is_border_pixel: int = 0
if on_border != 0 or _is_on_border(rx, rel, ww, wh, r) != 0:
is_border_pixel = 1
idx: int = yy * self.fb_w + px
if is_border_pixel == 0 and do_blit != 0:
self.back[idx] = buf[content_row * fw + rx]
px += 1
continue
col: uint32 = row_col
if is_border_pixel != 0:
col = outline_col
ca: int = 255
if rel < r:
if rx < r:
ca = _corner_alpha(rx, rel, r, r, r)
elif rx >= ww - r:
ca = _corner_alpha(rx, rel, ww - r - 1, r, r)
elif rel >= wh - r:
if rx < r:
ca = _corner_alpha(rx, rel, r, wh - r - 1, r)
elif rx >= ww - r:
ca = _corner_alpha(rx, rel, ww - r - 1, wh - r - 1, r)
if is_border_pixel != 0 and ca > 0 and ca < 255:
ca = 255
if row_is_title != 0 and title_alpha < 255 and rel >= 1 and rx >= 1 and rx < ww - 1 and ca == 255 and is_border_pixel == 0:
ca = title_alpha
if ca <= 0:
px += 1
continue
if ca < 255:
self.back[idx] = self._blend(self.back[idx], col, ca)
else:
self.back[idx] = col
px += 1
# ---------------- 窗口按钮(按样式逐行绘制)----------------
# 关闭键底色圆角(WIN7/WINXP 的红色圆角块)
def _round_fill_row(self, bx: int, by: int, bw: int, bh: int, yy: int, col: uint32, r: int):
lry: int = yy - by
if lry < 0 or lry >= bh: return
lx: int = 0
while lx < bw:
keep: int = 1
if lry < r:
if lx < r:
keep = _is_in_corner(lx, lry, r, r, r)
elif lx >= bw - r:
keep = _is_in_corner(lx, lry, bw - r - 1, r, r)
elif lry >= bh - r:
if lx < r:
keep = _is_in_corner(lx, lry, r, bh - r - 1, r)
elif lx >= bw - r:
keep = _is_in_corner(lx, lry, bw - r - 1, bh - r - 1, r)
if keep != 0:
self.row_fill(bx + lx, yy, 1, col)
lx += 1
def _draw_close_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int):
lry: int = yy - by
if lry < 0 or lry >= bh: return
cx: int = bx + bw / 2
cy: int = by + bh / 2
d: int = 3
if bw >= 30: d = 4
x_col: uint32 = self.rgb(uint32(200), uint32(200), uint32(200))
if style == STYLE_CLASSIC:
self.row_fill(bx, yy, bw, self.rgb(uint32(40), uint32(40), uint32(70)))
elif style == STYLE_WIN7 or style == STYLE_WINXP:
self._round_fill_row(bx, by, bw, bh, yy, self.rgb(uint32(200), uint32(50), uint32(50)), 3)
x_col = self.rgb(uint32(255), uint32(255), uint32(255))
else:
x_col = self.rgb(uint32(150), uint32(150), uint32(150))
i: int = yy - cy
if i >= 0 - 3 and i <= 3:
if i >= 0 - d and i <= d:
self.row_fill(cx + i, yy, 1, x_col)
self.row_fill(cx - i, yy, 1, x_col)
def _draw_min_row(self, bx: int, by: int, sz: int, yy: int):
cx: int = bx + sz / 2
cy: int = by + sz / 2
def _draw_min_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int):
cx: int = bx + bw / 2
cy: int = by + bh / 2
lw: int = 5
if bw >= 30: lw = 8
if yy == cy + 3:
self.row_fill(cx - 5, yy, 11, self.rgb(uint32(180), uint32(180), uint32(180)))
self.row_fill(cx - lw, yy, lw * 2 + 1, self.rgb(uint32(180), uint32(180), uint32(180)))
def _draw_max_row(self, bx: int, by: int, sz: int, yy: int):
col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180))
cx: int = bx + sz / 2
cy: int = by + sz / 2
q: int = 4
def _draw_max_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int):
cx: int = bx + bw / 2
cy: int = by + bh / 2
sz: int = 4
if bw >= 30: sz = 6
dy: int = yy - cy
if dy < 0: dy = 0 - dy
if dy > q: return
if dy == q or yy == cy - q + 1:
self.row_fill(cx - q, yy, 2 * q + 1, col)
if dy < 0 - sz or dy > sz: return
col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180))
if dy == 0 - sz or dy == 0 - sz + 1 or dy == sz:
self.row_fill(cx - sz, yy, sz * 2 + 1, col)
else:
self.row_fill(cx - q, yy, 1, col)
self.row_fill(cx + q, yy, 1, col)
self.row_fill(cx - sz, yy, 1, col)
self.row_fill(cx + sz, yy, 1, col)
# 关闭键宽/高/右内边距(按样式;poll_input 热区复用这三者)
def _close_btn_w(self, style: int) -> int:
if style == STYLE_WIN10 or style == STYLE_WIN11: return BTN_WIN_W
if style == STYLE_WIN7: return BTN_W7_W
if style == STYLE_WINXP: return BTN_XP_SZ
return BTN_SIZE
def _close_btn_h(self, style: int) -> int:
if style == STYLE_WIN10 or style == STYLE_WIN11: return TITLE_H - 2
if style == STYLE_WIN7: return BTN_W7_H
if style == STYLE_WINXP: return BTN_XP_SZ
return BTN_SIZE
def _close_btn_pad(self, style: int) -> int:
if style == STYLE_WIN10 or style == STYLE_WIN11: return 0
if style == STYLE_WIN7 or style == STYLE_WINXP: return 5
return 4
def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int):
wx: int = s.ux
wy: int = s.uy
ww: int = s.w
sz: int = CLOSE_SZ
by: int = wy + (th - sz) / 2
close_x: int = wx + ww - sz - CLOSE_PAD
max_x: int = close_x - sz - BTN_GAP
min_x: int = max_x - sz - BTN_GAP
style: int = s.style
bw: int = self._close_btn_w(style)
bh: int = self._close_btn_h(style)
by: int = wy + (th - bh) / 2
close_x: int = wx + ww - bw - self._close_btn_pad(style)
gap: int = 0
if style == STYLE_WIN7 or style == STYLE_WINXP: gap = 1
max_x: int = close_x - bw - gap
min_x: int = max_x - bw - gap
yy: int = y0
if yy < by: yy = by
yend: int = y1
if yend > by + sz: yend = by + sz
if yend > by + bh: yend = by + bh
while yy < yend:
self._draw_close_row(close_x, by, sz, yy)
self._draw_min_row(min_x, by, sz, yy)
self._draw_max_row(max_x, by, sz, yy)
self._draw_close_row(close_x, by, bw, bh, yy, style)
self._draw_min_row(min_x, by, bw, bh, yy, style)
self._draw_max_row(max_x, by, bw, bh, yy, style)
yy += 1
# ---------------- 任务栏 ----------------
@@ -540,61 +797,70 @@ class DeskServer:
s: desk.WinSlot* = self.slot(win)
wx: int = s.ux
wy: int = s.uy
ww: int = s.w
wh: int = s.h
th: int = TITLE_H
style: int = s.style
active: int = 0
if s.z_order == self._top_z(): active = 1
# 样式配色:CLASSIC 为默认;其余样式仅配色/分隔线可区分(几何同 CLASSIC)
# 每样式调色板(对齐内核桌面 desktop.vp 的 _draw_app_window)
title_col: uint32 = self.rgb(uint32(50), uint32(50), uint32(90))
border_col: uint32 = self.rgb(uint32(100), uint32(100), uint32(180))
outline_col: uint32 = self.rgb(uint32(100), uint32(100), uint32(180))
content_col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50))
has_separator: int = 1
rounded: int = 0
title_alpha: int = 255
if style == STYLE_WIN10:
has_separator = 0
if active != 0:
title_col = self.rgb(uint32(0), uint32(0), uint32(0))
border_col = self.rgb(uint32(0), uint32(0), uint32(0))
content_col = self.rgb(uint32(32), uint32(32), uint32(32))
outline_col = self.rgb(uint32(80), uint32(80), uint32(80))
else:
title_col = self.rgb(uint32(32), uint32(32), uint32(32))
border_col = self.rgb(uint32(32), uint32(32), uint32(32))
content_col = self.rgb(uint32(24), uint32(24), uint32(24))
outline_col = self.rgb(uint32(60), uint32(60), uint32(60))
border_col = title_col
elif style == STYLE_WIN11:
has_separator = 0
rounded = 1
if active != 0:
title_col = self.rgb(uint32(32), uint32(32), uint32(32))
border_col = self.rgb(uint32(32), uint32(32), uint32(32))
content_col = self.rgb(uint32(32), uint32(32), uint32(32))
outline_col = self.rgb(uint32(70), uint32(70), uint32(70))
else:
title_col = self.rgb(uint32(40), uint32(40), uint32(40))
border_col = self.rgb(uint32(40), uint32(40), uint32(40))
content_col = self.rgb(uint32(40), uint32(40), uint32(40))
outline_col = self.rgb(uint32(60), uint32(60), uint32(60))
border_col = title_col
elif style == STYLE_WIN7:
has_separator = 0
rounded = 1
title_alpha = 102
content_col = self.rgb(uint32(240), uint32(240), uint32(240))
if active != 0:
title_col = self.rgb(uint32(156), uint32(181), uint32(209))
border_col = self.rgb(uint32(156), uint32(181), uint32(209))
content_col = self.rgb(uint32(240), uint32(240), uint32(240))
outline_col = self.rgb(uint32(100), uint32(130), uint32(170))
else:
title_col = self.rgb(uint32(180), uint32(195), uint32(210))
border_col = self.rgb(uint32(180), uint32(195), uint32(210))
content_col = self.rgb(uint32(240), uint32(240), uint32(240))
outline_col = self.rgb(uint32(140), uint32(155), uint32(175))
border_col = title_col
elif style == STYLE_WINXP:
has_separator = 0
rounded = 1
content_col = self.rgb(uint32(235), uint32(235), uint32(235))
if active != 0:
title_col = self.rgb(uint32(107), uint32(158), uint32(212))
border_col = self.rgb(uint32(107), uint32(158), uint32(212))
content_col = self.rgb(uint32(235), uint32(235), uint32(235))
outline_col = self.rgb(uint32(43), uint32(87), uint32(151))
else:
title_col = self.rgb(uint32(140), uint32(160), uint32(195))
border_col = self.rgb(uint32(140), uint32(160), uint32(195))
content_col = self.rgb(uint32(235), uint32(235), uint32(235))
outline_col = self.rgb(uint32(100), uint32(115), uint32(145))
border_col = title_col
else:
if active != 0:
title_col = self.rgb(uint32(70), uint32(70), uint32(130))
border_col = self.rgb(uint32(130), uint32(130), uint32(220))
outline_col = border_col
# 阴影画在窗口本体之前(混到已合成的下层像素上),其行范围可能超出窗口外框
if SHADOW_ENABLED != 0:
@@ -605,33 +871,26 @@ class DeskServer:
fw: int = int(s.fb_w)
fh: int = int(s.fb_h)
content_y0: int = wy + th + TITLE_GAP
r: int = WIN_RADIUS
yy: int = y0
if yy < wy: yy = wy
yend: int = y1
if yend > wy + wh: yend = wy + wh
while yy < yend:
rel: int = yy - wy
if rel == 0:
self.row_fill(wx, yy, ww, border_col) # 上边框
elif rel < th:
self.row_fill(wx, yy, ww, title_col) # 标题栏
elif rel == th:
if has_separator != 0:
self.row_fill(wx, yy, ww, border_col) # 标题/内容分隔线
else:
self.row_fill(wx, yy, ww, content_col)
elif rel == th + 1:
self.row_fill(wx, yy, ww, content_col)
elif rel == wh - 1:
self.row_fill(wx, yy, ww, border_col) # 下边框
row_is_title: int = 0
row_col: uint32 = content_col
if rel < th:
row_is_title = 1
row_col = self._title_row_color(style, rel, th, active, title_col)
elif rel == th and has_separator != 0:
row_col = border_col # 标题/内容分隔线(CLASSIC)
if rounded != 0 and (rel < r or rel >= wh - r):
self._draw_round_row(s, yy, rel, row_col, row_is_title, outline_col, r,
title_alpha, buf, fw, fh, content_y0)
else:
self.row_fill(wx, yy, ww, content_col)
row: int = yy - content_y0
if row >= 0 and row < fh:
self.row_blit(wx + 1, yy, buf, row * fw + 1, ww - 2)
if rel > 0 and rel < wh - 1:
self.row_fill(wx, yy, 1, border_col) # 左边框
self.row_fill(wx + ww - 1, yy, 1, border_col) # 右边框
self._draw_flat_row(s, yy, rel, row_col, row_is_title, border_col,
title_alpha, buf, fw, fh, content_y0)
yy += 1
self._draw_window_btns(s, y0, y1, th)
@@ -866,10 +1125,13 @@ class DeskServer:
hit: int = self.hit_test(self.cx, self.cy)
if hit >= 0:
s: desk.WinSlot* = self.slot(hit)
bx: int = s.ux + s.w - CLOSE_SZ - CLOSE_PAD
by: int = s.uy + (TITLE_H - CLOSE_SZ) / 2
# 关闭键热区按样式尺寸算,必须与 _draw_window_btns 一致
cw: int = self._close_btn_w(s.style)
ch: int = self._close_btn_h(s.style)
bx: int = s.ux + s.w - cw - self._close_btn_pad(s.style)
by: int = s.uy + (TITLE_H - ch) / 2
in_close: int = 0
if self.cx >= bx and self.cx < bx + CLOSE_SZ and self.cy >= by and self.cy < by + CLOSE_SZ:
if self.cx >= bx and self.cx < bx + cw and self.cy >= by and self.cy < by + ch:
in_close = 1
self.focus(hit)
if in_close != 0: