Files
ViperOS/Apps/Explorer/desk_server.vp
T
TermiNexus 632bc877df feat(window): 鼠标捕获与窗口最小化/最大化
鼠标:
- 内容区按下后记录 capture_win,按住期间的移动仍投 HOVER、松开投 RELEASE
  (即使光标移出窗口)。此前 btn_down != 0 时完全不发 HOVER,滚动条
  拖动拿不到位置更新。
- 标题栏按钮按下态改为通用 btn_win/btn_kind,支持关闭/最小化/最大化。

窗口:
- WinSlot 新增 maximized、restore_*、fb_cap 字段
- 最小化:隐藏窗口并把焦点交给最高可见窗口;任务栏点击恢复,
  再点一次已聚焦窗口则最小化
- 最大化:保存还原几何,铺满工作区(不含任务栏)并发送 RESIZE 事件;
  再次点击还原;拖动最大化窗口会先还原
- 像素缓冲按 fb_cap 复用,仅在容量不足时从 arena 扩容,避免反复
  最大化/还原耗尽 bump arena
- 1920x1080 最大化需要约 7.3MB 内容缓冲:DESK_SHM_SIZE 8MB → 32MB
2026-10-27 12:43:19 +08:00

1660 lines
72 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import vpsdk.desk as desk
import vpsdk.devfs as devfs
import vpsdk.fs as fs
import vpsdk.process as process
import vpsdk.syscall as syscall
import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表
from stdint import *
import string
import viperlib
# 【诊断·临时】首个鼠标包已收到的标记(0 = 未收到)
_mouse_logged: int = 0
# 首次读到键盘字节时打一次(区分"内核没数据"与"事件没送达 app")
_kbd_logged: int = 0
# 【诊断·临时】累计收到的 DESK_REQ_FLUSH 次数(=0 说明 app 从没请求过合成)
_flush_cnt: int = 0
# ============================================================================
# explorer 的窗口服务端 + 合成器(S3c):app 把窗口像素写进共享区 arena 后压一条
# DESK_REQ_FLUSH,本服务端按 Z 序逐行合成进 framebuffer;输入来自 /dev/input/*,
# 负责光标、命中测试、焦点与事件投递。未实现:任务栏时钟(用户态无时间源)、拖边缩放。
# ============================================================================
define TITLE_H = 32 # 必须与 vpsdk/vpui.vp 的 TITLE_H 一致(app 用 h - TITLE_H - 2 算内容高)
define TITLE_GAP = 2
define CURSOR_BMP_W = 16
define CURSOR_BMP_H = 16
# 任务栏与窗口装饰尺寸(对齐内核桌面 desktop.vp)
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 的窗口圆角半径
# 键盘投递限流:单轮最多投递 KB_DELIVER_MAX 字节,剩余留在 kb_pend 下一轮继续;
# 事件环满时同样停在当前字节,绝不把失败当成功吞掉输入。
define KB_DELIVER_MAX = 64
define KB_PEND_MAX = 64
# 窗口样式(与 vpsdk/window.vp 的 STYLE_* 对齐);当前 CREATE 一律按 CLASSIC 建窗
define STYLE_CLASSIC = 0
define STYLE_WIN10 = 1
define STYLE_WIN11 = 2
define STYLE_WIN7 = 3
define STYLE_WINXP = 4
# 窗口阴影(0 = 关闭)
define SHADOW_ENABLED = 1
define SHADOW_LEFT = 8
define SHADOW_RIGHT = 12
define SHADOW_TOP = 4
define SHADOW_BOTTOM = 14
define SHADOW_ALPHA = 100
# 光标 16×16 位图模板(取自 desktop.vp 的历史实现):0 = 透明、1 = 黑边、2 = 白心。
# 同一历史文件里的 _CURSOR_IBEAM / _CURSOR_RESIZE_* 待接 WinSlot.cursor 后再启用。
_CURSOR_ARROW: [256]uint8 = [
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,
1,2,2,2,1,1,1,1,1,1,0,0,0,0,0,0,
1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
]
# 圆角窗口的角点/边框判定(移植自内核桌面 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 映射后的线性地址
fb_fd: int # /dev/fb0 的 fd(等 vblank 用)
fb_w: int
fb_h: int
pitch_px: int
fmt: uint32 # 0 = RGBA, 1 = BGRA
bg_row: uint32* # 一行背景的预构造副本
back: uint32* # 后备缓冲:绘制先落这里,整帧后逐行拷到 fb
# 共享区
shm: uint64
# 输入
kfd: int
mfd: int
# 键盘待投递字节(设备已读出但事件环暂时投不进去的部分)
kb_pend: [KB_PEND_MAX]char
kb_pend_off: int
kb_pend_len: int
# 脏行
dirty: uint8* # 每行 1 字节
dirty_any: int
# 光标 / 焦点 / 拖拽
cx: int
cy: int
pcx: int
pcy: int
focused: int
next_z: int
btn_down: int
# 标题栏按钮的捕获态:btn_win = 窗口下标(-1 = 无),btn_kind = 0/1关闭/2最小化/3最大化
btn_win: int
btn_kind: int
drag_win: int
drag_dx: int
drag_dy: int
# 按住鼠标时的捕获窗口:内容区按下后,移动/松开事件始终投给它(滚动条等
# 控件靠按住期间的 HOVER 更新位置;移出窗口也不能丢)。
capture_win: int
# 复用缓冲
msg: desk.DeskMsg
evt: desk.DeskMsg
# ---- 颜色:按 framebuffer 格式打包(与内核 gfx.COLOR_RGB 同口径)----
def rgb(self, r: uint32, g: uint32, b: uint32) -> uint32:
if self.fmt == uint32(1):
return (uint32(255) << 24) | (r << 16) | (g << 8) | b
return (uint32(255) << 24) | (b << 16) | (g << 8) | r
# ---- 按 alpha/255 把 fg 混到 bg 上(RGBA/BGRA 同为 R/B@0,16、G@8,可共用 SWAR)----
def _blend(self, bg: uint32, fg: uint32, a: int) -> uint32:
ia: int = 255 - a
v: int = ((int(fg & uint32(0x00FF00FF)) * a) + (int(bg & uint32(0x00FF00FF)) * ia)) >> 8
g: int = ((int((fg >> 8) & uint32(0xFF)) * a) + (int((bg >> 8) & uint32(0xFF)) * ia)) >> 8
return (uint32(255) << 24) | (uint32(v) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8)
def init(self, fb: uint32*, w: int, h: int, pitch: int, fmt: uint32,
shm: uint64, kfd: int, mfd: int, fb_fd: int):
self.fb = fb
self.fb_fd = fb_fd
self.fb_w = w
self.fb_h = h
self.pitch_px = pitch / 4
self.fmt = fmt
self.shm = shm
self.kfd = kfd
self.mfd = mfd
self.kb_pend_off = 0
self.kb_pend_len = 0
# dirty 与 bg_row 必须真正分配(匿名 mmap),不能把整数当地址用
self.dirty = uint8*(syscall.mmap(0, uint64(h) + uint64(64),
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
syscall.MMAP_FD_ANON, 0))
self.dirty_any = 0
self.cx = w / 2
self.cy = h / 2
self.pcx = self.cx
self.pcy = self.cy
self.focused = -1
self.next_z = 1
self.btn_down = 0
self.btn_win = -1
self.btn_kind = 0
self.drag_win = -1
self.drag_dx = 0
self.drag_dy = 0
self.capture_win = -1
# 预构造一行背景,渲染时整行 memcpy
self.bg_row = uint32*(syscall.mmap(0, uint64(w) * uint64(4),
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
syscall.MMAP_FD_ANON, 0))
col: uint32 = self.rgb(uint32(25), uint32(25), uint32(35))
for i in range(w):
self.bg_row[i] = col
# 后备缓冲:整屏像素,行距 = fb_w(与 framebuffer 的 pitch 无关)
back_va: uint64 = syscall.mmap(0, uint64(w) * uint64(h) * uint64(4),
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
syscall.MMAP_FD_ANON, 0)
if back_va == 0:
devfs.serial_puts("[explorer] back buffer alloc FAILED\n")
self.back = self.fb # 退化:直写 framebuffer,保留原行为
else:
self.back = uint32*(back_va)
self.mark_all()
# ---------------- 脏行 ----------------
def mark(self, y0: int, h: int):
y: int = y0
while y < y0 + h:
if y >= 0 and y < self.fb_h:
if self.dirty[y] == 0:
self.dirty[y] = 1
self.dirty_any = 1
y += 1
def mark_all(self):
for y in range(self.fb_h):
self.dirty[y] = 1
self.dirty_any = 1
def clear_dirty(self):
for y in range(self.fb_h):
self.dirty[y] = 0
self.dirty_any = 0
# 窗口移动/增删时连同阴影一起置脏(阴影超出窗口外框)
def mark_win(self, y: int, h: int):
self.mark(y - SHADOW_TOP, h + SHADOW_TOP + SHADOW_BOTTOM)
# 窗口画在任务栏之上,覆盖到底部条带时必须把任务栏一并置脏,否则残留旧内容。
tb: int = self.fb_h - TASKBAR_H
if y + h + SHADOW_BOTTOM > tb:
self.mark(tb, TASKBAR_H)
# 窗口集合/层级变化:所有可见窗口整体置脏(含阴影带)
def mark_all_windows(self):
for i in range(desk.DESK_MAX_WIN):
w: desk.WinSlot* = self.slot(i)
if w.used != 0 and w.active != 0 and w.minimized == 0:
self.mark_win(w.uy, w.h)
# 活动窗口切换会改变标题栏配色:把所有窗口的标题栏行置脏
def mark_titles(self):
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0:
self.mark(s.uy, TITLE_H)
# 任务栏图标随焦点/窗口集合变化,一起刷新
self.mark(self.fb_h - TASKBAR_H, TASKBAR_H)
# ---------------- 基本绘制 ----------------
def row_fill(self, x0: int, y: int, w: int, col: uint32):
if y < 0 or y >= self.fb_h or w <= 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
string.memset32(rawptr(uint64(self.back) + uint64((base + x) * 4)), col, uint64(dw))
def row_blit(self, x0: int, y: int, src: uint32*, src_off: int, w: int):
"""把 src[src_off .. src_off+w) 拷到屏幕 (x0, y),裁剪到屏幕内。"""
if y < 0 or y >= self.fb_h or w <= 0: return
sx: int = 0
dw: int = w
dx: int = x0
if dx < 0:
sx = 0 - dx
dw = dw + dx
dx = 0
if dx + dw > self.fb_w:
dw = self.fb_w - dx
if dw <= 0: return
dst: uint64 = uint64(self.back) + uint64(y * self.fb_w + dx) * uint64(4)
srcp: uint64 = uint64(src) + uint64(src_off + sx) * uint64(4)
string.memcpy(rawptr(dst), rawptr(srcp), uint64(dw) * uint64(4))
# ---------------- 窗口槽 ----------------
def slot(self, win: int) -> desk.WinSlot*:
return desk.WinSlot*(self.shm + desk.desk_slot_off(win))
def _content_h(self, h: int) -> int:
ch: int = h - TITLE_H - TITLE_GAP
if ch < 1: ch = 1
return ch
# 调整窗口外框几何。像素缓冲只在容量不足时扩容:arena 是 bump 分配,
# 容量内复用可避免反复最大化/还原把 arena 耗尽。返回 1 成功 / 0 失败。
def _set_window_rect(self, s: desk.WinSlot*, x: int, y: int, w: int, h: int) -> int:
ch: int = self._content_h(h)
need: int = w * ch
if w <= 0 or need <= 0: return 0
if need > int(s.fb_cap):
off: uint32 = desk.desk_arena_alloc(self.shm, uint32(need * 4))
if off == uint32(0): return 0
s.fb_off = off
s.fb_cap = uint32(need)
b: uint32* = uint32*(self.shm + uint64(off))
col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50))
for k in range(need):
b[k] = col
s.ux = x
s.uy = y
s.w = w
s.h = h
s.fb_w = uint32(w)
s.fb_h = uint32(ch)
return 1
def _focus_top_visible(self):
best: int = -1
bz: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
if s.z_order > bz:
bz = s.z_order
best = i
self.focused = best
def _close_window(self, win: int):
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
# 关闭:通知一次 CLOSE 后把槽位置空回收;window.vp 的 is_active() 见到 used==0
# 即返回 0,app 的 while 循环自然退出。像素缓冲不回收(arena 是 bump 分配)。
self.push_event(win, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0, 0)
self.mark_win(s.uy, s.h)
s.used = 0
if self.focused == win: self.focused = -1
if self.drag_win == win: self.drag_win = -1
if self.capture_win == win: self.capture_win = -1
if self.btn_win == win:
self.btn_win = -1
self.btn_kind = 0
self._focus_top_visible()
self.mark_all_windows()
self.mark_titles()
def _minimize_window(self, win: int):
s: desk.WinSlot* = self.slot(win)
if s.used == 0 or s.minimized != 0: return
self.mark_win(s.uy, s.h)
s.minimized = 1
if self.focused == win: self.focused = -1
if self.drag_win == win: self.drag_win = -1
if self.capture_win == win: self.capture_win = -1
self._focus_top_visible()
self.mark_all_windows()
self.mark_titles()
def _maximize_window(self, win: int):
s: desk.WinSlot* = self.slot(win)
if s.used == 0 or s.maximized != 0: return
ox: int = s.ux
oy: int = s.uy
ow: int = s.w
oh: int = s.h
mw: int = self.fb_w
mh: int = self.fb_h - TASKBAR_H
if mh < TITLE_H + TITLE_GAP + 1: return
if self._set_window_rect(s, 0, 0, mw, mh) == 0: return
s.restore_x = ox
s.restore_y = oy
s.restore_w = ow
s.restore_h = oh
s.maximized = 1
self.mark_win(oy, oh)
self.mark_win(s.uy, s.h)
self.push_event(win, uint32(desk.DESK_EVT_RESIZE), int(s.fb_w), int(s.fb_h), 0, 0)
self.mark_all_windows()
self.mark_titles()
def _restore_window(self, win: int) -> int:
s: desk.WinSlot* = self.slot(win)
if s.used == 0 or s.maximized == 0: return 0
oy: int = s.uy
oh: int = s.h
if self._set_window_rect(s, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0:
return 0
s.maximized = 0
self.mark_win(oy, oh)
self.mark_win(s.uy, s.h)
self.push_event(win, uint32(desk.DESK_EVT_RESIZE), int(s.fb_w), int(s.fb_h), 0, 0)
self.mark_all_windows()
self.mark_titles()
return 1
def _toggle_maximize(self, win: int):
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
if s.maximized != 0:
self._restore_window(win)
else:
self._maximize_window(win)
# 最高 z 序(用于判定活动窗口;无可见窗口返回 -1)
def _top_z(self) -> int:
m: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
if s.z_order > m: m = s.z_order
return m
# 键盘目标:焦点窗口有效就用它,否则取 z 序最高的可见窗口(无则 -1)
def _key_target(self) -> int:
if self.focused >= 0:
f: desk.WinSlot* = self.slot(self.focused)
if f.used != 0 and f.active != 0 and f.minimized == 0:
return self.focused
best: int = -1
best_z: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
if s.z_order > best_z:
best_z = s.z_order
best = i
return best
def alloc_window(self, pid: int, x: int, y: int, w: int, h: int, style: int) -> int:
"""找空槽并分配像素缓冲,返回 win_id(失败 -1)。入参 w/h 是含标题栏的外框;
内容缓冲 w × (h - TITLE_H - TITLE_GAP),与 vpui 的 `bh = h - TITLE_H - 2` 一致。"""
content_h: int = h - TITLE_H - TITLE_GAP
if content_h < 1: content_h = 1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used == 0:
cap_px: int = w * content_h
buf_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(cap_px) * uint32(4))
if buf_off == uint32(0): return -1
s.used = 1
s.pid = pid
s.active = 1
s.minimized = 0
s.maximized = 0
s.ux = x
s.uy = y
s.w = w
s.h = h
s.restore_x = x
s.restore_y = y
s.restore_w = w
s.restore_h = h
s.style = style
s.cursor = 0
s.fb_off = buf_off
s.fb_cap = uint32(cap_px)
s.fb_w = uint32(w)
s.fb_h = uint32(content_h)
string.memset(&s.title, 0, desk.DESK_TITLE_MAX)
# 像素缓冲先清成窗口底色,避免 app 还没画就露出垃圾
buf: uint32* = uint32*(self.shm + uint64(buf_off))
col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50))
for k in range(cap_px):
buf[k] = col
s.z_order = self.next_z
self.next_z += 1
self.focused = i
self.mark_win(y, h)
self.mark_titles()
return i
return -1
# ---------------- 文本(8x16 点阵,字体表复用 vpui.font)----------------
def draw_char(self, x: int, y: int, ch: char, col: uint32):
ci: int = int(uint8(ch))
if ci >= 128: ci = ci & 127
fy: int = 0
while fy < vpui.CHAR_H:
ty: int = y + fy
if ty >= 0 and ty < self.fb_h:
row: uint8 = vpui.font[ci][fy]
if row != uint8(0):
base: int = ty * self.fb_w
fx: int = 0
while fx < vpui.CHAR_W:
if row & (0x80 >> fx):
px: int = x + fx
if px >= 0 and px < self.fb_w:
self.back[base + px] = col
fx += 1
fy += 1
def draw_str(self, x: int, y: int, s: const str, col: uint32):
cx: int = x
i: int = 0
while s[i] != 0:
self.draw_char(cx, y, s[i], col)
cx += vpui.CHAR_W
i += 1
# ---------------- 窗口阴影 ----------------
# 单个阴影像素的 alpha(与原逐像素算法完全相同)
def _shadow_alpha(self, adx: int, ady: int, me_x: int, me_y: int) -> int:
dist: int = adx
if ady > dist: dist = ady
dist = dist + ((adx + ady - dist) * 3) / 8
me: int = me_x
if adx + ady > 0:
me = (me_x * adx + me_y * ady) / (adx + ady)
if me > 0 and dist > 0 and dist < me:
t_val: int = (256 * dist) / me
t_inv: int = 256 - t_val
return (SHADOW_ALPHA * t_inv * t_inv) / 65536
return 0
def _draw_shadow(self, s: desk.WinSlot*, y0: int, y1: int):
wx: int = s.ux
wy: int = s.uy
ww: int = s.w
wh: int = s.h
sl: int = SHADOW_LEFT
sr: int = SHADOW_RIGHT
st: int = SHADOW_TOP
sb: int = SHADOW_BOTTOM
# 左右窄带(dy=0)的 alpha 只随 adx 变化,预建表
lt: [16]int
rt: [16]int
tl: [16]int
tr: [16]int
i: int = 0
while i <= sl:
lt[i] = self._shadow_alpha(i, 0, sl, st)
i += 1
i = 0
while i <= sr:
rt[i] = self._shadow_alpha(i, 0, sr, st)
i += 1
yy: int = y0
if yy < wy - st: yy = wy - st
yend: int = y1
if yend > wy + wh + sb: yend = wy + wh + sb
while yy < yend:
if yy >= 0 and yy < self.fb_h:
side: int = 0
ady: int = 0
me_y: int = st
if yy < wy:
ady = wy - yy
elif yy >= wy + wh:
ady = yy - (wy + wh - 1)
me_y = sb
else:
side = 1
tm: int = 0
if side == 0:
i = 0
while i <= sl:
tl[i] = self._shadow_alpha(i, ady, sl, me_y)
i += 1
i = 0
while i <= sr:
tr[i] = self._shadow_alpha(i, ady, sr, me_y)
i += 1
tm = self._shadow_alpha(0, ady, sl, me_y)
px: int = wx - sl
if px < 0: px = 0
xend: int = wx + ww + sr
if xend > self.fb_w: xend = self.fb_w
base: int = yy * self.fb_w
while px < xend:
a: int = 0
adx: int = 0
if px < wx:
adx = wx - px
if side != 0: a = lt[adx]
else: a = tl[adx]
elif px >= wx + ww:
adx = px - (wx + ww - 1)
if side != 0: a = rt[adx]
else: a = tr[adx]
elif side == 0:
a = tm
if a > 0:
idx: int = base + px
bg: uint32 = self.back[idx]
ia: int = 255 - a
rb: int = (int(bg & uint32(0x00FF00FF)) * ia) >> 8
g: int = (int((bg >> 8) & uint32(0xFF)) * ia) >> 8
self.back[idx] = (uint32(255) << 24) | (uint32(rb) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8)
px += 1
yy += 1
# ---------------- 窗口逐行绘制(按样式)----------------
# 把 col 以 alpha 混到后备缓冲已有像素上(WIN7 半透明标题栏用);逐像素内联 SWAR
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
ia: int = 255 - a
frb: int = int(col & uint32(0x00FF00FF)) * a
fg: int = int((col >> 8) & uint32(0xFF)) * a
base: int = y * self.fb_w
i: int = 0
while i < dw:
idx: int = base + x + i
bg: uint32 = self.back[idx]
v: int = (frb + int(bg & uint32(0x00FF00FF)) * ia) >> 8
g: int = (fg + int((bg >> 8) & uint32(0xFF)) * ia) >> 8
self.back[idx] = (uint32(255) << 24) | (uint32(v) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8)
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)
elif 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)
else:
self.row_fill(wx, yy, ww, row_col)
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)
# 圆角带的行:只有左右各 r 列需要逐像素,中段按直行整段处理
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
cyc: int = r
if rel >= r: cyc = wh - r - 1
if ww > r + r:
seg: int = 0
while seg < 2:
xs: int = wx
xe: int = wx + r
cxc: int = r
if seg == 1:
xs = wx + ww - r
xe = wx + ww
cxc = ww - r - 1
px: int = xs
while px < xe:
if px >= 0 and px < self.fb_w:
rx: int = px - wx
if _is_in_corner(rx, rel, cxc, cyc, r) != 0:
on_border: int = _is_on_border(rx, rel, ww, wh, r)
idx: int = yy * self.fb_w + px
if on_border == 0 and do_blit != 0:
self.back[idx] = buf[content_row * fw + rx]
px += 1
continue
col: uint32 = row_col
if on_border != 0:
col = outline_col
ca: int = _corner_alpha(rx, rel, cxc, cyc, r)
if on_border != 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 on_border == 0:
ca = title_alpha
if ca > 0:
if ca < 255:
self.back[idx] = self._blend(self.back[idx], col, ca)
else:
self.back[idx] = col
px += 1
seg += 1
mid0: int = wx + r
mid1: int = wx + ww - r
midw: int = mid1 - mid0
if midw > 0:
if rel == 0 or rel == wh - 1:
self.row_fill(mid0, yy, midw, outline_col)
elif do_blit != 0:
self.row_blit(mid0, yy, buf, content_row * fw + r, midw)
elif row_is_title != 0 and title_alpha < 255:
self._blend_row(mid0, yy, midw, row_col, title_alpha)
else:
self.row_fill(mid0, yy, midw, row_col)
return
# 窄窗回退:整行逐像素
px: int = wx
xend: int = wx + ww
while px < xend:
if px >= 0 and px < self.fb_w:
rx: int = px - wx
cxr: int = r
if rx >= ww - r: cxr = ww - r - 1
if _is_in_corner(rx, rel, cxr, cyc, r) != 0:
on_border: int = _is_on_border(rx, rel, ww, wh, r)
idx: int = yy * self.fb_w + px
if on_border == 0 and do_blit != 0:
self.back[idx] = buf[content_row * fw + rx]
px += 1
continue
col: uint32 = row_col
if on_border != 0:
col = outline_col
ca: int = _corner_alpha(rx, rel, cxr, cyc, r)
if on_border != 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 on_border == 0:
ca = title_alpha
if ca > 0:
if ca < 255:
self.back[idx] = self._blend(self.back[idx], col, ca)
else:
self.back[idx] = col
px += 1
# ---------------- 窗口按钮(按样式逐行绘制)----------------
# 圆角块的一行是否保留该列
def _round_keep(self, lx: int, lry: int, bw: int, bh: int, r: int) -> int:
if lry < r:
if lx < r: return _is_in_corner(lx, lry, r, r, r)
if lx >= bw - r: return _is_in_corner(lx, lry, bw - r - 1, r, r)
elif lry >= bh - r:
if lx < r: return _is_in_corner(lx, lry, r, bh - r - 1, r)
if lx >= bw - r: return _is_in_corner(lx, lry, bw - r - 1, bh - r - 1, r)
return 1
# 关闭键底色圆角(WIN7/WINXP 的红色圆角块):整行只做一次 row_fill
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
lo: int = 0
while lo < bw and self._round_keep(lo, lry, bw, bh, r) == 0:
lo += 1
hi: int = bw
while hi > lo and self._round_keep(hi - 1, lry, bw, bh, r) == 0:
hi -= 1
if hi > lo:
self.row_fill(bx + lo, yy, hi - lo, col)
def _draw_close_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, pressed: 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:
base_col: uint32 = self.rgb(uint32(40), uint32(40), uint32(70))
if pressed != 0: base_col = self.rgb(uint32(20), uint32(20), uint32(45))
self.row_fill(bx, yy, bw, base_col)
elif style == STYLE_WIN7 or style == STYLE_WINXP:
base_col2: uint32 = self.rgb(uint32(200), uint32(50), uint32(50))
if pressed != 0: base_col2 = self.rgb(uint32(140), uint32(25), uint32(25))
self._round_fill_row(bx, by, bw, bh, yy, base_col2, 3)
x_col = self.rgb(uint32(255), uint32(255), uint32(255))
else:
x_col = self.rgb(uint32(150), uint32(150), uint32(150))
# WIN10/WIN11 平时无底色,按下时补一块深灰底做按压反馈
if pressed != 0:
self.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70)))
i: int = yy - cy
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, bw: int, bh: int, yy: int, style: int, pressed: int):
if pressed != 0:
self.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70)))
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 - lw, yy, lw * 2 + 1, self.rgb(uint32(180), uint32(180), uint32(180)))
def _draw_max_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, pressed: int):
if pressed != 0:
self.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70)))
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 - 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 - 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
# 关闭键命中:与 _draw_window_btns 共用同一套几何
def _close_btn_hit(self, s: desk.WinSlot*, x: int, y: int) -> int:
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
if x >= bx and x < bx + cw and y >= by and y < by + ch:
return 1
return 0
# 标题栏按钮命中:1=关闭 2=最小化 3=最大化/还原(与绘制同一套几何)
def _title_btn_hit(self, s: desk.WinSlot*, x: int, y: int) -> int:
bw: int = self._close_btn_w(s.style)
bh: int = self._close_btn_h(s.style)
pad: int = self._close_btn_pad(s.style)
gap: int = 0
if s.style == STYLE_WIN7 or s.style == STYLE_WINXP: gap = 1
by: int = s.uy + (TITLE_H - bh) / 2
if y < by or y >= by + bh: return 0
close_x: int = s.ux + s.w - bw - pad
max_x: int = close_x - bw - gap
min_x: int = max_x - bw - gap
if x >= close_x and x < close_x + bw: return 1
if x >= max_x and x < max_x + bw: return 3
if x >= min_x and x < min_x + bw: return 2
return 0
def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int, pressed_kind: int):
wx: int = s.ux
wy: int = s.uy
ww: int = s.w
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 + bh: yend = by + bh
while yy < yend:
self._draw_close_row(close_x, by, bw, bh, yy, style, 1 if pressed_kind == 1 else 0)
self._draw_min_row(min_x, by, bw, bh, yy, style, 1 if pressed_kind == 2 else 0)
self._draw_max_row(max_x, by, bw, bh, yy, style, 1 if pressed_kind == 3 else 0)
yy += 1
# ---------------- 任务栏 ----------------
# 第 ordinal 个图标的左边界;绘制与命中测试共用
def _taskbar_icon_x(self, ordinal: int) -> int:
return 4 + TASKBAR_LOGO_SZ + 80 + ordinal * (TASKBAR_ICON_SZ + TASKBAR_ICON_PAD)
# 图标行的顶边界;绘制与命中测试共用
def _taskbar_icon_y(self) -> int:
return self.fb_h - TASKBAR_H + (TASKBAR_H - TASKBAR_ICON_SZ) / 2
# 任务栏图标命中:返回窗口下标(-1 = 未命中);筛选与 ordinal 顺序同绘制
def _taskbar_icon_hit(self, x: int, y: int) -> int:
iy: int = self._taskbar_icon_y()
if y < iy or y >= iy + TASKBAR_ICON_SZ: return -1
cnt: int = 0
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used == 0 or s.active == 0:
continue
ix: int = self._taskbar_icon_x(cnt)
if x >= ix and x < ix + TASKBAR_ICON_SZ:
return i
cnt += 1
return -1
# 点击图标:最小化先恢复;已聚焦则最小化(任务栏切换);否则聚焦
def _activate_from_taskbar(self, win: int):
if win < 0 or win >= desk.DESK_MAX_WIN: return
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
if s.minimized != 0:
s.minimized = 0
self.mark_win(s.uy, s.h)
self.focus(win)
self.mark_all_windows()
self.mark_titles()
elif self.focused == win:
self._minimize_window(win)
else:
self.focus(win)
def _draw_taskbar_rows(self, y0: int, y1: int):
bar_y: int = self.fb_h - TASKBAR_H
if y1 <= bar_y or y0 >= self.fb_h: return
bar_col: uint32 = self.rgb(uint32(32), uint32(32), uint32(32))
yy: int = y0
if yy < bar_y: yy = bar_y
while yy < y1 and yy < self.fb_h:
self.row_fill(0, yy, self.fb_w, bar_col)
yy += 1
# 顶部分隔线
if bar_y >= y0 and bar_y < y1:
self.row_fill(0, bar_y, self.fb_w, self.rgb(uint32(60), uint32(60), uint32(60)))
# logo:内亮外环的蓝色圆盘
logo_x: int = 4
logo_y: int = bar_y + (TASKBAR_H - TASKBAR_LOGO_SZ) / 2
lr: int = TASKBAR_LOGO_SZ / 2
lc_ring: uint32 = self.rgb(uint32(80), uint32(120), uint32(220))
lc_core: uint32 = self.rgb(uint32(100), uint32(160), uint32(255))
yy = y0
if yy < logo_y: yy = logo_y
while yy < y1 and yy < logo_y + TASKBAR_LOGO_SZ:
ly2: int = yy - logo_y - lr
lx: int = 0
while lx < TASKBAR_LOGO_SZ:
lx2: int = lx - lr
d2: int = lx2 * lx2 + ly2 * ly2
if d2 <= lr * lr:
px: int = logo_x + lx
if px >= 0 and px < self.fb_w:
if d2 <= (lr * lr) / 2:
self.back[yy * self.fb_w + px] = lc_core
else:
self.back[yy * self.fb_w + px] = lc_ring
lx += 1
yy += 1
# 品牌文字
text_y: int = bar_y + (TASKBAR_H - vpui.CHAR_H) / 2
if text_y + vpui.CHAR_H > y0 and text_y < y1:
self.draw_str(logo_x + TASKBAR_LOGO_SZ + 8, text_y, "ViperOS",
self.rgb(uint32(255), uint32(255), uint32(255)))
# 窗口图标:有边框方块 + 标题首字符(焦点/最小化配色不同)
icon_y: int = self._taskbar_icon_y()
cnt: int = 0
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used == 0 or s.active == 0:
continue
ix: int = self._taskbar_icon_x(cnt)
ic_bg: uint32 = self.rgb(uint32(40), uint32(40), uint32(70))
ic_bd: uint32 = self.rgb(uint32(80), uint32(80), uint32(140))
if i == self.focused:
ic_bg = self.rgb(uint32(60), uint32(60), uint32(120))
ic_bd = self.rgb(uint32(120), uint32(120), uint32(220))
elif s.minimized != 0:
ic_bg = self.rgb(uint32(30), uint32(30), uint32(50))
yy = y0
if yy < icon_y: yy = icon_y
while yy < y1 and yy < icon_y + TASKBAR_ICON_SZ:
rel: int = yy - icon_y
self.row_fill(ix, yy, TASKBAR_ICON_SZ, ic_bg)
if rel == 0 or rel == TASKBAR_ICON_SZ - 1:
self.row_fill(ix, yy, TASKBAR_ICON_SZ, ic_bd)
else:
self.row_fill(ix, yy, 1, ic_bd)
self.row_fill(ix + TASKBAR_ICON_SZ - 1, yy, 1, ic_bd)
yy += 1
if s.title[0] != 0:
ch_col: uint32 = self.rgb(uint32(255), uint32(255), uint32(255))
if i != self.focused:
ch_col = self.rgb(uint32(180), uint32(180), uint32(200))
ch_x: int = ix + (TASKBAR_ICON_SZ - vpui.CHAR_W) / 2
ch_y: int = icon_y + (TASKBAR_ICON_SZ - vpui.CHAR_H) / 2
if ch_y + vpui.CHAR_H > y0 and ch_y < y1:
self.draw_char(ch_x, ch_y, s.title[0], ch_col)
cnt += 1
# ---------------- 渲染 ----------------
def render_range(self, y0: int, y1: int):
# 窗口按 Z 序升序(选择排序,窗口数 ≤ 8);顺带缓存几何/样式,避免逐行查槽
order: [desk.DESK_MAX_WIN]int
sx: [desk.DESK_MAX_WIN]int
sy: [desk.DESK_MAX_WIN]int
sw: [desk.DESK_MAX_WIN]int
sh: [desk.DESK_MAX_WIN]int
smode: [desk.DESK_MAX_WIN]int
cnt: int = 0
top_z: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
order[cnt] = i
sx[i] = s.ux
sy[i] = s.uy
sw[i] = s.w
sh[i] = s.h
if s.style == STYLE_WIN7:
smode[i] = 2
elif s.style == STYLE_WIN11 or s.style == STYLE_WINXP:
smode[i] = 1
else:
smode[i] = 0
if s.z_order > top_z: top_z = s.z_order
cnt += 1
for a in range(cnt - 1):
for b in range(a + 1, cnt):
za: int = self.slot(order[a]).z_order
zb: int = self.slot(order[b]).z_order
if za > zb:
t: int = order[a]
order[a] = order[b]
order[b] = t
# 1) 背景:只铺"没有被窗口覆盖"的 x 区间;混合样式的可见行再单独铺一次底
yy: int = y0
while yy < y1:
xx: int = 0
for i in range(cnt):
w: int = order[i]
if yy < sy[w] or yy >= sy[w] + sh[w]: continue
if sx[w] > xx:
self.row_blit(xx, yy, self.bg_row, xx, sx[w] - xx)
ex: int = sx[w] + sw[w]
if ex > xx: xx = ex
if smode[w] != 0:
rel: int = yy - sy[w]
need: int = 0
if smode[w] == 2:
if rel < TITLE_H or rel >= sh[w] - WIN_RADIUS: need = 1
elif rel < WIN_RADIUS or rel >= sh[w] - WIN_RADIUS:
need = 1
if need != 0:
self.row_blit(sx[w], yy, self.bg_row, sx[w], sw[w])
if xx < self.fb_w:
self.row_blit(xx, yy, self.bg_row, xx, self.fb_w - xx)
yy += 1
# 2) 任务栏(先于窗口,窗口会盖住它 —— 与内核桌面同序)
self._draw_taskbar_rows(y0, y1)
# 3) 窗口
for i in range(cnt):
self.render_window_rows(order[i], y0, y1, top_z)
# 4) 光标
self.render_cursor_rows(y0, y1)
def render_window_rows(self, win: int, y0: int, y1: int, top_z: int):
s: desk.WinSlot* = self.slot(win)
wx: int = s.ux
wy: int = s.uy
wh: int = s.h
th: int = TITLE_H
style: int = s.style
active: int = 0
if s.z_order == top_z: active = 1
# 每样式调色板(对齐内核桌面 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))
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))
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))
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))
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))
outline_col = self.rgb(uint32(100), uint32(130), uint32(170))
else:
title_col = self.rgb(uint32(180), uint32(195), uint32(210))
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))
outline_col = self.rgb(uint32(43), uint32(87), uint32(151))
else:
title_col = self.rgb(uint32(140), uint32(160), uint32(195))
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:
self._draw_shadow(s, y0, y1)
if wy + wh <= y0 or wy >= y1: return
buf: uint32* = uint32*(self.shm + uint64(s.fb_off))
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
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._draw_flat_row(s, yy, rel, row_col, row_is_title, border_col,
title_alpha, buf, fw, fh, content_y0)
yy += 1
pressed_kind: int = 0
if self.btn_win == win: pressed_kind = self.btn_kind
self._draw_window_btns(s, y0, y1, th, pressed_kind)
if s.title[0] != 0:
text_y: int = wy + (th - vpui.CHAR_H) / 2
if text_y + vpui.CHAR_H > y0 and text_y < y1:
tcol: uint32 = self.rgb(uint32(255), uint32(255), uint32(255))
if active == 0: tcol = self.rgb(uint32(170), uint32(170), uint32(170))
self.draw_str(wx + 8, text_y, &s.title[0], tcol)
def render_cursor_rows(self, y0: int, y1: int):
# 0 透明(不写像素,靠重画背景恢复),1 黑边,2 白心;同行连续同色合并成一次 row_fill。
cy0: int = self.cy
cy1: int = self.cy + CURSOR_BMP_H
if cy1 <= y0 or cy0 >= y1: return
y: int = cy0
if y < y0: y = y0
while y < cy1 and y < y1:
row: int = (y - cy0) * CURSOR_BMP_W
col: int = 0
while col < CURSOR_BMP_W:
v: uint8 = _CURSOR_ARROW[row + col]
if v == 0:
col += 1
continue
start: int = col
col += 1
while col < CURSOR_BMP_W and _CURSOR_ARROW[row + col] == v:
col += 1
px: int = self.cx + start
seg_w: int = col - start
if px + seg_w > 0 and px < self.fb_w:
if px < 0:
seg_w = seg_w + px
px = 0
if px + seg_w > self.fb_w:
seg_w = self.fb_w - px
if seg_w > 0:
if v == 1:
self.row_fill(px, y, seg_w, self.rgb(uint32(0), uint32(0), uint32(0)))
else:
self.row_fill(px, y, seg_w, self.rgb(uint32(255), uint32(255), uint32(255)))
y += 1
def render(self):
if self.dirty_any == 0: return
devfs.fb_wait_vblank(self.fb_fd)
# 逐脏段渲染并立刻提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、
# 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交(4MB memcpy),帧率直接崩。
# 段内先全画进 back 再逐行提交,保证每一行提交出去的都是完整一帧的内容。
y: int = 0
while y < self.fb_h:
if self.dirty[y] == 0:
y += 1
continue
y0: int = y
while y < self.fb_h and self.dirty[y] != 0:
y += 1
y1: int = y
self.render_range(y0, y1)
yy: int = y0
while yy < y1:
if self.dirty[yy] != 0:
# fb 行距 = pitch_px,back 行距 = fb_w
string.memcpy(rawptr(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4)),
rawptr(uint64(self.back) + uint64(yy * self.fb_w) * uint64(4)),
uint64(self.fb_w) * uint64(4))
yy += 1
self.clear_dirty()
# ---------------- 事件投递 ----------------
def push_event(self, win: int, etype: uint32, a: int, b: int, c: int, d: int) -> int:
"""按收件窗口的 pid 投进该进程自己的事件环。a..d 是消息的 4 个载荷位。"""
s: desk.WinSlot* = self.slot(win)
self.evt.type = etype
self.evt.win = win
self.evt.pid = s.pid
self.evt.a = a
self.evt.b = b
self.evt.c = c
self.evt.d = d
return desk.ring_push(self.shm + desk.desk_evt_ring_off(s.pid), self.evt)
def push_event_pid(self, pid: int, win: int, etype: uint32, a: int, b: int, c: int, d: int) -> int:
"""建窗答复专用:此时窗口槽还没有 pid,直接按请求里带的 pid 投递。"""
self.evt.type = etype
self.evt.win = win
self.evt.pid = pid
self.evt.a = a
self.evt.b = b
self.evt.c = c
self.evt.d = d
return desk.ring_push(self.shm + desk.desk_evt_ring_off(pid), self.evt)
# ---------------- 请求处理 ----------------
def handle_request(self, m: desk.DeskMsg):
global _flush_cnt
if m.type == uint32(desk.DESK_REQ_CREATE):
# pid 随请求携带(窗口还没诞生),标题内联在消息里
_sb: [96]char
viperlib.snprintf(&_sb, 96, "[explorer] create style=%d w=%d h=%d\n",
int(m.style), m.c, m.d)
devfs.serial_puts(&_sb)
nw: int = self.alloc_window(m.pid, m.a, m.b, m.c, m.d, int(m.style))
if nw >= 0:
ns: desk.WinSlot* = self.slot(nw)
string.memcpy(&ns.title, &m.title, uint64(desk.DESK_TITLE_MAX))
# 【临时诊断】分配成功后立刻回读槽位:坐标/尺寸/缓冲偏移是否真的落进去了。
_sb2: [160]char
viperlib.snprintf(&_sb2, 160, "[explorer] slot%d x=%d y=%d w=%d h=%d off=%u\n",
nw, ns.ux, ns.uy, ns.w, ns.h, ns.fb_off)
devfs.serial_puts(&_sb2)
else:
# 【临时诊断】建窗失败(多半是 arena 分配不到)会让 app 拿到 win_id=-1
devfs.serial_puts("[explorer] alloc_window FAILED\n")
self.mark_all_windows()
self.mark_titles()
self.push_event_pid(m.pid, nw, uint32(desk.DESK_EVT_CREATED), nw, 0, 0, 0)
return
win: int = m.win
if win < 0 or win >= desk.DESK_MAX_WIN: return
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
ty: uint32 = m.type
if ty == uint32(desk.DESK_REQ_DESTROY):
self._close_window(win)
return
elif ty == uint32(desk.DESK_REQ_MOVE):
self.mark_win(s.uy, s.h)
s.ux = m.a
s.uy = m.b
self.mark_win(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_RESIZE):
self.mark_win(s.uy, s.h)
if self._set_window_rect(s, s.ux, s.uy, m.a, m.b) != 0:
s.maximized = 0
self.mark_win(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_TITLE):
string.memcpy(&s.title, &m.title, uint64(desk.DESK_TITLE_MAX))
self.mark(s.uy, TITLE_H)
elif ty == uint32(desk.DESK_REQ_FLUSH):
# 【临时诊断】首条 FLUSH:证明 app 的"请合成"真的到达了合成器
if _flush_cnt == 0:
_fb1: [128]char
viperlib.snprintf(&_fb1, 128, "[explorer] first FLUSH win=%d uy=%d h=%d\n", win, s.uy, s.h)
devfs.serial_puts(&_fb1)
_flush_cnt += 1
self.mark(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_ACTIVATE):
self.next_z += 1
s.z_order = self.next_z
self.focused = win
self.mark(0, self.fb_h)
def poll_requests(self):
# 单轮最多处理一整圈:head 已前进,剩余请求留到下一轮,不会重复也不会自旋。
n: int = 0
while n < desk.DESK_RING_SLOTS and desk.ring_pop(self.shm + desk.desk_req_ring_off(), &self.msg) != 0:
self.handle_request(self.msg)
n += 1
# ---------------- 输入 ----------------
def hit_test(self, x: int, y: int) -> int:
best: int = -1
best_z: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
if x >= s.ux and x < s.ux + s.w and y >= s.uy and y < s.uy + s.h:
if s.z_order > best_z:
best_z = s.z_order
best = i
return best
def focus(self, win: int):
# 只允许聚焦"活"的槽位:死槽位上的 z_order/focused 会把焦点指向已销毁窗口。
if win < 0 or win >= desk.DESK_MAX_WIN: return
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
self.next_z += 1
s.z_order = self.next_z
self.focused = win
# 层级变化会改变互相覆盖的关系:所有可见窗口整体置脏
self.mark_all_windows()
self.mark_titles()
def _move_cursor(self, dx: int, dy: int) -> int:
"""把相对位移累加到光标并钳制到屏幕内;返回是否发生移动。"""
if dx == 0 and dy == 0: return 0
self.cx = self.cx + dx
self.cy = self.cy + dy
if self.cx < 0: self.cx = 0
if self.cy < 0: self.cy = 0
if self.cx >= self.fb_w: self.cx = self.fb_w - 1
if self.cy >= self.fb_h: self.cy = self.fb_h - 1
return 1
# 把 kb_pend 里的字节投给焦点窗口,返回本轮实际投递数。
# ring_push 失败 = 目标事件环满:停在当前字节,下一轮再投,字节绝不丢。
def _deliver_kb(self, limit: int) -> int:
target: int = self._key_target()
if target < 0:
# 没有收件窗口:无处投递,只能丢弃(保持旧行为,不让 pending 无限增长)。
self.kb_pend_off = 0
self.kb_pend_len = 0
return 0
sent: int = 0
while self.kb_pend_len > 0 and sent < limit:
b: char = self.kb_pend[self.kb_pend_off]
if self.push_event(target, uint32(desk.DESK_EVT_KEY), int(b), 0, 0, 0) != 0:
break
self.kb_pend_off += 1
self.kb_pend_len -= 1
sent += 1
if self.kb_pend_len == 0:
self.kb_pend_off = 0
return sent
def poll_input(self):
global _mouse_logged, _kbd_logged
# --- 键盘:设备侧已给出 ASCII,逐字符投给焦点窗口(无焦点/失效则顶层窗口)---
# 单轮预算内的投递 + 一次补读:洪流时一轮只推进最多 KB_DELIVER_MAX 字节,
# 其余留在 kb_pend,保证请求/输入/渲染仍能每轮推进,不会被一次洪流拖死。
sent: int = self._deliver_kb(KB_DELIVER_MAX)
if sent < KB_DELIVER_MAX and self.kb_pend_len == 0:
kb: [16]char
kn: int = fs.read(self.kfd, &kb, uint32(16))
if kn > 0:
# 【诊断·临时】首个键盘字节:证明 内核 → devfs → explorer 这段是通的
if _kbd_logged == 0:
_kbd_logged = 1
_kb: [96]char
viperlib.snprintf(&_kb, 96, "[explorer] kbd n=%d ch=%d\n", kn, int(kb[0]))
devfs.serial_puts(&_kb)
if kn > 16: kn = 16
for i in range(kn):
self.kb_pend[i] = kb[i]
self.kb_pend_off = 0
self.kb_pend_len = kn
self._deliver_kb(KB_DELIVER_MAX - sent)
# --- 鼠标:内核每次返回一个包。serve 一轮耗时毫秒级,若每轮只取一个包,
# PS/2 100Hz 的连续输入会排队累积 ⇒ 光标滞后;故把已到的包一次取完,
# 位移累加、按键以最后一个包为准。---
mp: [16]uint8
mn: int = fs.read(self.mfd, &mp, uint32(16))
if mn > 0:
# 【诊断·临时】首个鼠标包:证明 中断→devfs→本进程 链路已通
if _mouse_logged == 0:
_mouse_logged = 1
_mb: [128]char
viperlib.snprintf(&_mb, 128, "[explorer] mouse pkt n=%d btn=%d\n", mn, int(mp[0]))
devfs.serial_puts(&_mb)
moved: int = 0
if mn >= 9:
moved = self._move_cursor(int(*(int32*(uint64(&mp) + uint64(1)))),
int(*(int32*(uint64(&mp) + uint64(5)))))
# 批次内逐包跟踪按下/释放沿:只看最后一包会漏掉"按下+释放落在同一批"的快速点击
cur: int = self.btn_down
saw_press: int = 0
b0: int = int(mp[0]) & 1
if b0 != 0 and cur == 0: saw_press = 1
cur = b0
mn = fs.read(self.mfd, &mp, uint32(16))
# 单轮也有上限:剩下的包留到下一轮,不能被输入洪流卡在本轮。
batches: int = 0
while mn > 0 and batches < 64:
batches += 1
if mn >= 9:
if self._move_cursor(int(*(int32*(uint64(&mp) + uint64(1)))),
int(*(int32*(uint64(&mp) + uint64(5))))) != 0:
moved = 1
bi: int = int(mp[0]) & 1
if bi != 0 and cur == 0: saw_press = 1
cur = bi
mn = fs.read(self.mfd, &mp, uint32(16))
if moved != 0:
self.mark(self.pcy - 1, CURSOR_BMP_H + 2)
self.mark(self.cy - 1, CURSOR_BMP_H + 2)
self.pcx = self.cx
self.pcy = self.cy
# 按住标题栏按钮时移出热区:撤销按下态并置脏恢复
if self.btn_win >= 0:
cm: desk.WinSlot* = self.slot(self.btn_win)
if cm.used == 0 or self._title_btn_hit(cm, self.cx, self.cy) != self.btn_kind:
if cm.used != 0: self.mark(cm.uy, TITLE_H)
self.btn_win = -1
self.btn_kind = 0
if self.btn_down != 0 and self.capture_win >= 0:
# 内容区按住期间:移动事件持续投给捕获窗口。滚动条/滑块拖动
# 依赖按住时的 HOVER;坐标允许越界,由控件自行判断。
cs: desk.WinSlot* = self.slot(self.capture_win)
if cs.used != 0:
self.push_event(self.capture_win, uint32(desk.DESK_EVT_MOUSE),
desk.DESK_MOUSE_HOVER,
self.cx - cs.ux, self.cy - cs.uy - TITLE_H - TITLE_GAP, 0)
elif self.btn_down == 0:
# 悬停:把 HOVER 投给光标下那个窗口(窗口内坐标)
hw: int = self.hit_test(self.cx, self.cy)
if hw >= 0:
hs: desk.WinSlot* = self.slot(hw)
self.push_event(hw, uint32(desk.DESK_EVT_MOUSE),
desk.DESK_MOUSE_HOVER,
self.cx - hs.ux, self.cy - hs.uy - TITLE_H - TITLE_GAP, 0)
if saw_press != 0:
self.btn_down = 1
self.btn_win = -1 # 新手势开始:清掉上一次可能丢失释放沿留下的按下态
self.btn_kind = 0
self.capture_win = -1
# 任务栏图标优先:命中则激活并跳过窗口命中/拖动
hit: int = -1
tw: int = self._taskbar_icon_hit(self.cx, self.cy)
if tw >= 0:
self._activate_from_taskbar(tw)
else:
hit = self.hit_test(self.cx, self.cy)
if hit >= 0:
s: desk.WinSlot* = self.slot(hit)
kind: int = self._title_btn_hit(s, self.cx, self.cy)
self.focus(hit)
if kind != 0:
# 标题栏按钮:只进入按下态,松手且仍在同一按钮上才执行
self.btn_win = hit
self.btn_kind = kind
self.mark(s.uy, TITLE_H)
elif self.cy < s.uy + TITLE_H:
# 标题栏 ⇒ 拖动;最大化窗口先还原再拖
restored_ok: int = 1
if s.maximized != 0:
restored_ok = self._restore_window(hit)
s = self.slot(hit)
if restored_ok != 0:
self.drag_win = hit
self.drag_dx = self.cx - s.ux
self.drag_dy = self.cy - s.uy
else:
# 内容区 ⇒ 捕获鼠标:按下后的移动/松开都投给同一窗口
self.capture_win = hit
self.push_event(hit, uint32(desk.DESK_EVT_MOUSE),
desk.DESK_MOUSE_PRESS,
self.cx - s.ux, self.cy - s.uy - TITLE_H - TITLE_GAP, 1)
# 独立判断(不是 elif):同批内按下并松开时,按下分支之后还要立刻走释放,
# 否则 btn_down 卡在 1,后续点击永远检测不到按下沿。
if cur == 0 and self.btn_down != 0:
self.btn_down = 0
bw: int = self.btn_win
bk: int = self.btn_kind
self.btn_win = -1
self.btn_kind = 0
if bw >= 0:
bs: desk.WinSlot* = self.slot(bw)
if bs.used != 0:
self.mark(bs.uy, TITLE_H)
if self._title_btn_hit(bs, self.cx, self.cy) == bk:
if bk == 1:
self._close_window(bw)
elif bk == 2:
self._minimize_window(bw)
elif bk == 3:
self._toggle_maximize(bw)
elif self.drag_win < 0 and self.capture_win >= 0:
# 松手:投给捕获窗口(即使光标已移出窗口)
cs: desk.WinSlot* = self.slot(self.capture_win)
if cs.used != 0:
self.push_event(self.capture_win, uint32(desk.DESK_EVT_MOUSE),
desk.DESK_MOUSE_RELEASE,
self.cx - cs.ux, self.cy - cs.uy - TITLE_H - TITLE_GAP, 0)
self.drag_win = -1
self.capture_win = -1
if self.btn_down != 0 and self.drag_win >= 0:
s2: desk.WinSlot* = self.slot(self.drag_win)
if s2.used != 0:
self.mark_win(s2.uy, s2.h)
s2.ux = self.cx - self.drag_dx
s2.uy = self.cy - self.drag_dy
self.mark_win(s2.uy, s2.h)
# ---------------- 主循环 ----------------
def serve(self):
# 进主循环前先整屏刷一次:内核启动期的 VGA/gfx 测试内容(例如蓝色测试块)不在任何
# 脏行里,只靠后续增量渲染会永远留在屏幕上。
self.mark_all()
self.render()
# 【临时诊断】首帧结果:back[0] 是"合成出来的像素",fb[0] 是"提交到屏幕的像素"。
# 两者都等于背景色 ⇒ 通路正常(问题在别处);back 有值而 fb 为 0 ⇒ 提交路径断了。
_f1: [176]char
# 额外取一个任务栏行上的像素(底边往上 8 行):期望 0xff202020(任务栏底色 32,32,32)。
# 它能证明"首帧确实把任务栏也提交到了 fb",从而把问题限定在"窗口合成"或"显示侧"。
_tb_y: int = self.fb_h - 8
_tb_px: uint32 = self.fb[_tb_y * self.pitch_px + 2]
viperlib.snprintf(&_f1, 176, "[explorer] frame1 back0=0x%x fb0=0x%x tb=0x%x w=%d h=%d pitch_px=%d\n",
self.back[0], self.fb[0], _tb_px, self.fb_w, self.fb_h, self.pitch_px)
devfs.serial_puts(&_f1)
_fc: int = 0 # 【临时诊断】帧计数
while True:
self.poll_requests()
self.poll_input()
self.render()
# 【临时诊断】每 240 帧打一次:槽位数、最上层窗口的矩形/缓冲偏移,以及该窗口中心
# 已合成出来的像素(用来判断"窗口到底有没有被合成")。
_fc = _fc + 1
if (_fc % 240) == 0:
# 【临时诊断】统计槽位 + 最上层窗口的矩形/缓冲偏移,并取窗口中心那个已合成像素。
_uc: int = 0
_tz: int = -1
_tw: int = -1
for _i in range(desk.DESK_MAX_WIN):
_s: desk.WinSlot* = self.slot(_i)
if _s.used != 0: _uc += 1
if _s.used != 0 and _s.active != 0 and _s.minimized == 0 and _s.z_order > _tz:
_tz = _s.z_order
_tw = _i
_px: uint32 = uint32(0)
_spx: uint32 = uint32(0)
_wx: int = -1
_wy: int = -1
_ww: int = -1
_wh: int = -1
_woff: uint32 = uint32(0)
if _tw >= 0:
_ws: desk.WinSlot* = self.slot(_tw)
_wx = _ws.ux; _wy = _ws.uy; _ww = _ws.w; _wh = _ws.h; _woff = _ws.fb_off
_cxp: int = _wx + _ww / 2
_cyp: int = _wy + _wh / 2
if _cxp >= 0 and _cxp < self.fb_w and _cyp >= 0 and _cyp < self.fb_h:
_px = self.back[_cyp * self.fb_w + _cxp]
# 共享区里该窗口缓冲的首像素:app 到底有没有往这块内存里画
_sbuf: uint32* = uint32*(self.shm + uint64(_woff))
_spx = _sbuf[0]
_f2: [256]char
viperlib.snprintf(&_f2, 256, "[explorer] frame%d dirty=%d used=%d top=%d rect=%d,%d,%d,%d off=%u px=0x%x shm0=0x%x flush=%d\n",
_fc, self.dirty_any, _uc, _tw, _wx, _wy, _ww, _wh, _woff, _px, _spx, _flush_cnt)
devfs.serial_puts(&_f2)
process.yield_cpu()