Files
ViperOS/Apps/Explorer/desk_server.vp
T

2331 lines
100 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 点阵字体表
import vpsdk.fonttools.fonttools as fonttools
import vpsdk.imagesee.imagesee as imagesee
import vpsdk.imagesee.image as image
import vpsdk.stdlib as stdlib
from stdint import *
import string
# ============================================================================
# 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
# 光标形状(与 vpsdk/window.vp 的 CURSOR_* 对齐)
define CURSOR_DEFAULT = 0
define CURSOR_IBEAM = 1
define CURSOR_RESIZE_EW = 2
define CURSOR_RESIZE_NS = 3
define CURSOR_RESIZE_NWSE = 4
define CURSOR_HAND = 5
define CURSOR_RESIZE_NESW = 6
# 窗口边缘拖拽缩放的热区宽度 / 最小外框尺寸
define RESIZE_BORDER = 5
define MIN_WIN_W = 160
define MIN_WIN_H = 100
# 窗口阴影(0 = 关闭)
define SHADOW_ENABLED = 1
# 画面同步开关:
# 1 = 每帧合成前等待垂直回扫(减少撕裂,VMware 下可能受 0x3DA 可用性影响)
# 0 = 不等回扫,最低输入延迟(拖动最跟手,但可能出现撕裂)
define SCREEN_SYNC = 1
define SHADOW_LEFT = 8
define SHADOW_RIGHT = 12
define SHADOW_TOP = 4
define SHADOW_BOTTOM = 14
define SHADOW_ALPHA = 100
_ttf_font: fonttools.Font* = None
_ttf_atlas: uint8* = None
_ttf_state: int = 0
def _ensure_ttf():
global _ttf_font, _ttf_atlas, _ttf_state
if _ttf_state != 0: return
f: fonttools.Font* = fonttools.load("/FONTS/FONT.TTF")
if f is None:
_ttf_state = -1
return
a: uint8* = fonttools.build_ascii_atlas(f, vpui.CHAR_W, vpui.CHAR_H)
if a is None:
_ttf_state = -1
return
_ttf_font = f
_ttf_atlas = a
_ttf_state = 1
# 光标 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,
]
# 其余光标模板同样是 16×16、0=透明 1=黑边 2=白心。
_CURSOR_IBEAM: [256]uint8 = [
0,0,0,0,0,1,1,1,1,1,1,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,0,1,2,2,1,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,1,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,1,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,1,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,1,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,1,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,1,2,2,2,2,1,0,0,0,0,0,
0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,
]
_CURSOR_HAND: [256]uint8 = [
0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,1,1,1,0,0,0,0,
0,0,0,0,0,1,2,2,2,2,2,2,1,1,0,0,
0,0,0,0,1,2,1,2,2,1,2,2,2,1,0,0,
0,0,0,1,2,2,1,2,2,1,2,2,2,1,0,0,
0,0,1,2,2,2,1,2,2,1,2,2,2,1,0,0,
0,0,1,2,2,2,1,2,2,1,2,2,1,0,0,0,
0,0,1,2,2,2,1,2,2,1,2,2,1,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,2,2,2,2,1,0,0,0,0,
0,0,0,0,1,1,2,2,2,2,2,1,0,0,0,0,
0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,
]
_CURSOR_EW: [256]uint8 = [
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,0,
0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,
0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,
0,0,1,1,2,0,0,0,0,0,0,2,1,1,0,0,
0,1,1,2,2,1,1,1,1,1,1,2,2,1,1,0,
1,1,2,2,2,2,2,2,2,2,2,2,2,2,1,1,
0,1,1,2,2,2,2,2,2,2,2,2,2,1,1,0,
0,0,1,1,2,1,1,1,1,1,1,2,1,1,0,0,
0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,
0,0,0,0,1,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,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
]
_CURSOR_NS: [256]uint8 = [
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,0,
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,0,
0,0,0,1,1,2,2,2,2,2,1,1,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,1,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,1,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,1,0,0,0,0,0,0,
0,0,0,1,1,2,2,2,2,2,1,1,0,0,0,0,
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,0,
0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,1,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,
]
_CURSOR_NWSE: [256]uint8 = [
0,0,0,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,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,
0,1,1,2,2,2,0,0,0,0,0,0,0,0,0,0,
0,0,1,2,2,1,1,0,0,0,0,0,0,0,0,0,
0,0,1,2,1,2,1,1,0,0,0,0,0,0,0,0,
0,0,1,0,1,1,2,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,2,1,1,0,1,0,0,
0,0,0,0,0,0,0,0,1,1,2,1,2,1,0,0,
0,0,0,0,0,0,0,0,0,1,1,2,2,1,0,0,
0,0,0,0,0,0,0,0,0,0,2,2,2,1,1,0,
0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,
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,
]
_CURSOR_NESW: [256]uint8 = [
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,1,0,
0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,
0,0,0,0,0,0,0,0,0,0,2,2,2,1,1,0,
0,0,0,0,0,0,0,0,0,1,1,2,2,1,0,0,
0,0,0,0,0,0,0,0,1,1,2,1,2,1,0,0,
0,0,0,0,0,0,0,1,1,2,1,1,0,1,0,0,
0,0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,
0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,0,
0,0,1,0,1,1,2,1,1,0,0,0,0,0,0,0,
0,0,1,2,1,2,1,1,0,0,0,0,0,0,0,0,
0,0,1,2,2,1,1,0,0,0,0,0,0,0,0,0,
0,1,1,2,2,2,0,0,0,0,0,0,0,0,0,0,
0,1,1,1,1,1,1,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,
]
# 圆角窗口的角点/边框判定(移植自内核桌面 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
# ---------------------------------------------------------------------------
# 把已合成的一整段像素提交到 framebuffer。
#
# 用 non-temporal store(movnti)+ sfence:即使 framebuffer 是回写映射,
# 也绕过数据缓存直接写到内存,扫描输出能立刻看到;同时 8 字节顺序写会合并
# 成写合并缓冲,比逐字节/逐像素写快得多。QEMU TCG 下等价于普通写,不受影响。
# ---------------------------------------------------------------------------
def _fb_copy(dst: uint64, src: uint64, nbytes: uint64):
if nbytes == 0: return
if (nbytes & uint64(7)) == 0:
qwords: uint64 = nbytes >> 3
__asm__(f"""mov rdi, {asminp(dst, ASM_DESCR.REG_ANY)}
mov rsi, {asminp(src, ASM_DESCR.REG_ANY)}
mov rcx, {asminp(qwords, ASM_DESCR.REG_ANY)}
Lfbcopy:
mov rax, [rsi]
movnti qword ptr [rdi], rax
add rsi, 8
add rdi, 8
dec rcx
jnz Lfbcopy
sfence""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_RSI])
else:
string.memcpy(rawptr(dst), rawptr(src), nbytes)
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* # 一行背景的预构造副本
bg_full: uint32* # 壁纸(整屏);None = 使用 bg_row 纯色背景
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
# 边缘拖拽缩放:窗口下标、边掩码(1左 2右 4上 8下)、起始几何/指针
resize_win: int
resize_edge: int
resize_ox: int
resize_oy: int
resize_ow: int
resize_oh: int
resize_sx: int
resize_sy: int
# 内容双缓冲:vis_* 是当前正在合成的内容;s.fb_off/fb_w/fb_h 是应用正在
# 绘制的新内容。应用 FLUSH 后才交换,避免 resize 过程中按新步长读旧内容。
vis_off: [desk.DESK_MAX_WIN]uint32
vis_cap: [desk.DESK_MAX_WIN]uint32
vis_w: [desk.DESK_MAX_WIN]int
vis_h: [desk.DESK_MAX_WIN]int
spare_off: [desk.DESK_MAX_WIN]uint32
spare_cap: [desk.DESK_MAX_WIN]uint32
# 标题栏按钮的 hover 状态(用于变色和刷新)
hover_btn_win: int
hover_btn_kind: 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
# 桌面壁纸:/sys/images/screen/background.jpg,等比“ cover”裁切铺满屏幕。
# 加载失败时保持 init() 里预构造的纯色 bg_row,不影响启动。
def _load_background(self):
img: image.Image* = imagesee.load("/sys/images/screen/background.jpg")
if img is None:
# FAT32 is shared with other booting apps; retry once before
# falling back to the solid color.
process.yield_cpu()
img = imagesee.load("/sys/images/screen/background.jpg")
if img is None:
devfs.serial_puts("[explorer] background: not found (solid color)\n")
return
w: int = self.fb_w
h: int = self.fb_h
if w <= 0 or h <= 0: return
buf: uint32* = uint32*(stdlib.malloc(uint64(w * h * 4)))
if buf is None: return
iw: int = img.width
ih: int = img.height
if iw <= 0 or ih <= 0: return
sw: int = iw
sh: int = ih
ox: int = 0
oy: int = 0
if iw * h > ih * w:
# 图片更宽:按高度铺满,左右裁切
sw = (ih * w) / h
if sw < 1: sw = 1
ox = (iw - sw) / 2
else:
sh = (iw * h) / w
if sh < 1: sh = 1
oy = (ih - sh) / 2
xoff: int32* = int32*(stdlib.malloc(uint64(w * 4)))
if xoff is None: return
x: int = 0
while x < w:
sx: int = ox + (x * sw) / w
if sx < 0: sx = 0
if sx >= iw: sx = iw - 1
xoff[x] = int32(sx)
x = x + 1
y: int = 0
while y < h:
sy: int = oy + (y * sh) / h
if sy < 0: sy = 0
if sy >= ih: sy = ih - 1
srow: int = sy * iw
drow: int = y * w
if self.fmt == uint32(1):
# ImageSee 的 0xAARRGGBB 与 BGRA framebuffer 内存布局一致
x = 0
while x < w:
buf[drow + x] = img.pixels[srow + int(xoff[x])] | uint32(0xFF000000)
x = x + 1
else:
x = 0
while x < w:
c: uint32 = img.pixels[srow + int(xoff[x])]
r: uint32 = (c >> 16) & uint32(0xFF)
g: uint32 = (c >> 8) & uint32(0xFF)
b: uint32 = c & uint32(0xFF)
buf[drow + x] = (uint32(0xFF) << 24) | (b << 16) | (g << 8) | r
x = x + 1
y = y + 1
self.bg_full = buf
devfs.serial_puts("[explorer] background: image loaded\n")
def bg_src(self, y: int) -> uint32*:
if self.bg_full is not None:
return uint32*(uint64(self.bg_full) + uint64(y * self.fb_w) * uint64(4))
return self.bg_row
# ---- 按 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
self.resize_win = -1
self.resize_edge = 0
self.resize_ox = 0
self.resize_oy = 0
self.resize_ow = 0
self.resize_oh = 0
self.resize_sx = 0
self.resize_sy = 0
i0: int = 0
for i0 in range(desk.DESK_MAX_WIN):
self.vis_off[i0] = uint32(0)
self.vis_cap[i0] = uint32(0)
self.vis_w[i0] = 0
self.vis_h[i0] = 0
self.spare_off[i0] = uint32(0)
self.spare_cap[i0] = uint32(0)
self.hover_btn_win = -1
self.hover_btn_kind = 0
# 预构造一行背景,渲染时整行 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
# 可选壁纸:整屏缓冲,失败则继续用上面的纯色行
self.bg_full = None
self._load_background()
# 后备缓冲:整屏像素,行距 = 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:
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))
# 把一行中 [x0, x0+w) 与黑色按 alpha 混合,用于按钮 hover 的“稍微变深”。
def _dim_row(self, x0: int, y: int, w: int, alpha: int):
if y < 0 or y >= self.fb_h or w <= 0: return
x: int = x0
if x < 0: x = 0
xe: int = x0 + w
if xe > self.fb_w: xe = self.fb_w
base: int = y * self.fb_w
while x < xe:
self.back[base + x] = self._blend(self.back[base + x], uint32(0), alpha)
x += 1
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
# 指针落在窗口哪条边(1左 2右 4上 8下,可组合成角);0 = 不在边缘/最大化
def _resize_edge_at(self, s: desk.WinSlot*, x: int, y: int) -> int:
if s.maximized != 0: return 0
if x < s.ux or x >= s.ux + s.w or y < s.uy or y >= s.uy + s.h: return 0
e: int = 0
if x < s.ux + RESIZE_BORDER: e |= 1
if x >= s.ux + s.w - RESIZE_BORDER: e |= 2
if y < s.uy + RESIZE_BORDER: e |= 4
if y >= s.uy + s.h - RESIZE_BORDER: e |= 8
# 标题栏按钮区域不参与缩放(否则按钮上沿会被当成顶边)
if e != 0 and self._title_btn_hit(s, x, y) != 0: return 0
return e
def _edge_cursor(self, e: int) -> int:
if e == 0: return CURSOR_DEFAULT
if (e & 1) != 0 and (e & 4) != 0: return CURSOR_RESIZE_NWSE
if (e & 2) != 0 and (e & 8) != 0: return CURSOR_RESIZE_NWSE
if (e & 2) != 0 and (e & 4) != 0: return CURSOR_RESIZE_NESW
if (e & 1) != 0 and (e & 8) != 0: return CURSOR_RESIZE_NESW
if (e & 3) != 0: return CURSOR_RESIZE_EW
if (e & 12) != 0: return CURSOR_RESIZE_NS
return CURSOR_DEFAULT
def _hover_resize_edge(self) -> int:
hw: int = self.hit_test(self.cx, self.cy)
if hw < 0: return 0
return self._resize_edge_at(self.slot(hw), self.cx, self.cy)
# 调整窗口外框几何。像素缓冲只在容量不足时扩容:arena 是 bump 分配,
# 容量内复用可避免反复最大化/还原把 arena 耗尽。返回 1 成功 / 0 失败。
def _set_window_rect(self, s: desk.WinSlot*, win: int, 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 w == int(s.fb_w) and ch == int(s.fb_h):
s.ux = x
s.uy = y
s.w = w
s.h = h
return 1
target_off: uint32 = s.fb_off
target_cap: uint32 = s.fb_cap
# 当前绘制目标若正在显示(上次已 FLUSH),或容量不足,就换到备用/新缓冲。
# 这样 resize 期间合成器仍读旧 vis 缓冲,绝不会按新步长读旧内容。
if target_off == self.vis_off[win] or need > int(target_cap):
if self.spare_off[win] != uint32(0) and need <= int(self.spare_cap[win]):
target_off = self.spare_off[win]
target_cap = self.spare_cap[win]
self.spare_off[win] = uint32(0)
self.spare_cap[win] = uint32(0)
else:
alloc_px: int = need
grow: int = int(target_cap) + int(target_cap) / 2
if grow > alloc_px: alloc_px = grow
off: uint32 = desk.desk_arena_alloc(self.shm, uint32(alloc_px * 4))
if off == uint32(0): return 0
# 旧 target 还没显示过,可留作下一次 resize 的备用
if s.fb_off != self.vis_off[win] and s.fb_off != uint32(0):
self.spare_off[win] = s.fb_off
self.spare_cap[win] = s.fb_cap
target_off = off
target_cap = uint32(alloc_px)
s.fb_off = target_off
s.fb_cap = target_cap
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 _drag_resize(self):
s: desk.WinSlot* = self.slot(self.resize_win)
if s.used == 0:
self.resize_win = -1
return
dx: int = self.cx - self.resize_sx
dy: int = self.cy - self.resize_sy
nx: int = self.resize_ox
ny: int = self.resize_oy
nw: int = self.resize_ow
nh: int = self.resize_oh
if (self.resize_edge & 1) != 0:
nx = self.resize_ox + dx
nw = self.resize_ow - dx
if (self.resize_edge & 2) != 0:
nw = self.resize_ow + dx
if (self.resize_edge & 4) != 0:
ny = self.resize_oy + dy
nh = self.resize_oh - dy
if (self.resize_edge & 8) != 0:
nh = self.resize_oh + dy
if nw < MIN_WIN_W:
if (self.resize_edge & 1) != 0: nx = self.resize_ox + self.resize_ow - MIN_WIN_W
nw = MIN_WIN_W
if nh < MIN_WIN_H:
if (self.resize_edge & 4) != 0: ny = self.resize_oy + self.resize_oh - MIN_WIN_H
nh = MIN_WIN_H
if nx < 0: nx = 0
if ny < 0: ny = 0
if nx + nw > self.fb_w: nw = self.fb_w - nx
if ny + nh > self.fb_h: nh = self.fb_h - ny
if nx == s.ux and ny == s.uy and nw == s.w and nh == s.h: return
self.mark_win(s.uy, s.h)
if self._set_window_rect(s, self.resize_win, nx, ny, nw, nh) != 0:
self.push_event(self.resize_win, uint32(desk.DESK_EVT_RESIZE),
int(s.fb_w), int(s.fb_h), 0, 0)
self.mark_win(s.uy, s.h)
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.resize_win == win: self.resize_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
if self.resize_win == win: self.resize_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, win, 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, win, 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:
# 槽位复用前整块清零:避免上一任窗口的 pid/几何/缓冲区偏移残留。
string.memset(s, 0, desk.desk_slot_size())
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
# 第二块内容缓冲:应用始终画在非显示缓冲,FLUSH 后交换。
buf2_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(cap_px) * uint32(4))
if buf2_off == uint32(0): buf2_off = buf_off
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)
self.vis_off[i] = buf2_off
self.vis_cap[i] = uint32(cap_px)
self.vis_w[i] = w
self.vis_h[i] = content_h
self.spare_off[i] = uint32(0)
self.spare_cap[i] = uint32(0)
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
if buf2_off != buf_off:
buf2: uint32* = uint32*(self.shm + uint64(buf2_off))
for k2 in range(cap_px):
buf2[k2] = 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))
_ensure_ttf()
if _ttf_state == 1 and ci < 128:
fy: int = 0
while fy < vpui.CHAR_H:
ty: int = y + fy
if ty >= 0 and ty < self.fb_h:
base: int = ty * self.fb_w
fx: int = 0
while fx < vpui.CHAR_W:
a: int = int(_ttf_atlas[(ci * vpui.CHAR_H + fy) * vpui.CHAR_W + fx])
if a != 0:
px: int = x + fx
if px >= 0 and px < self.fb_w:
idx: int = base + px
if a >= 255:
self.back[idx] = col
else:
dst: uint32 = self.back[idx]
dr: int = int(dst & 0xFF)
dg: int = int((dst >> 8) & 0xFF)
db: int = int((dst >> 16) & 0xFF)
sr: int = int(col & 0xFF)
sg: int = int((col >> 8) & 0xFF)
sb: int = int((col >> 16) & 0xFF)
r: int = (sr * a + dr * (255 - a)) / 255
g: int = (sg * a + dg * (255 - a)) / 255
b: int = (sb * a + db * (255 - a)) / 255
self.back[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16)
fx += 1
fy += 1
return
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。qx/qy 是到「内缩圆角矩形」的距离(外接矩形四边各内缩 r),
# r 是圆角半径:圆角外的像素同样会得到阴影,且 iso-alpha 线沿圆角展开,
# 而不是沿外接矩形展开(否则圆角窗口的阴影仍是方的)。
def _shadow_alpha(self, qx: int, qy: int, me_x: int, me_y: int, r: int) -> int:
s: int = qx + qy
if s <= 0: return 0
dist: int = qx
if qy > dist: dist = qy
dist = dist + ((s - dist) * 3) / 8 - r
if dist <= 0: return 0
me: int = (me_x * qx + me_y * qy) / s
if me > 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, r: 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
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:
# qy=0 表示该行位于窗口竖直方向的中段(不受圆角影响)
qy: int = 0
me_y: int = st
if yy < wy:
qy = wy - yy + r
elif yy >= wy + wh:
qy = yy - (wy + wh - 1) + r
elif yy < wy + r:
qy = wy + r - yy
elif yy >= wy + wh - r:
qy = yy - (wy + wh - 1 - r)
if yy >= wy + wh - r: me_y = sb
i: int = 0
if qy == 0:
while i <= sl:
lt[i] = self._shadow_alpha(i + r, 0, sl, st, r)
i += 1
i = 0
while i <= sr:
rt[i] = self._shadow_alpha(i + r, 0, sr, st, r)
i += 1
else:
while i <= sl:
tl[i] = self._shadow_alpha(i + r, qy, sl, me_y, r)
i += 1
i = 0
while i <= sr:
tr[i] = self._shadow_alpha(i + r, qy, sr, me_y, r)
i += 1
tm: int = self._shadow_alpha(0, qy, sl, me_y, r)
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
if px < wx:
adx: int = wx - px
if qy == 0: a = lt[adx]
else: a = tl[adx]
elif px >= wx + ww:
adx = px - (wx + ww - 1)
if qy == 0: a = rt[adx]
else: a = tr[adx]
elif qy > 0:
# 窗口内部:只有圆角带里、圆角之外的像素需要补阴影
qx: int = 0
if px < wx + r:
qx = wx + r - px
elif px >= wx + ww - r:
qx = px - (wx + ww - 1 - r)
if qx > 0:
me_x: int = sl
if px >= wx + ww - r: me_x = sr
a = self._shadow_alpha(qx, qy, me_x, me_y, r)
else:
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 and buf is not None:
blit_w: int = ww - 2
if blit_w > fw - 2: blit_w = fw - 2
if blit_w > 0:
self.row_blit(wx + 1, yy, buf, content_row * fw + 1, blit_w)
if blit_w < ww - 2:
self.row_fill(wx + 1 + blit_w, yy, (ww - 2) - blit_w, row_col)
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 and buf is not None:
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 and rx < fw:
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:
mid_blit_w: int = midw
if mid_blit_w > fw - r: mid_blit_w = fw - r
if mid_blit_w > 0:
self.row_blit(mid0, yy, buf, content_row * fw + r, mid_blit_w)
if mid_blit_w < midw:
self.row_fill(mid0 + mid_blit_w, yy, midw - mid_blit_w, row_col)
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 and rx < fw:
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
# 窗口本身是否允许画在该像素(圆角样式会裁掉四角)
def _window_round_keep(self, s: desk.WinSlot*, x: int, y: int) -> int:
if s.style != STYLE_WIN11 and s.style != STYLE_WIN7 and s.style != STYLE_WINXP:
return 1
rx: int = x - s.ux
ry: int = y - s.uy
if rx < 0 or rx >= s.w or ry < 0 or ry >= s.h: return 0
return self._round_keep(rx, ry, s.w, s.h, WIN_RADIUS)
# 标题栏按钮底色:按需做按钮圆角(r=0 即方角)+ 窗口圆角裁剪,
# 保证按钮本身是方的,只有与窗口圆角重合的角被丢弃。
def _close_fill_row(self, s: desk.WinSlot*, 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:
if self._round_keep(lo, lry, bw, bh, r) != 0 and self._window_round_keep(s, bx + lo, yy) != 0:
break
lo += 1
hi: int = bw
while hi > lo:
if self._round_keep(hi - 1, lry, bw, bh, r) != 0 and self._window_round_keep(s, bx + hi - 1, yy) != 0:
break
hi -= 1
if hi > lo:
self.row_fill(bx + lo, yy, hi - lo, col)
def _draw_close_row(self, s: desk.WinSlot*, bx: int, by: int, bw: int, bh: int, yy: int, style: int, hovered: 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))
active: int = 1 if (hovered != 0 or pressed != 0) else 0
if style == STYLE_CLASSIC:
base_col: uint32 = self.rgb(uint32(40), uint32(40), uint32(70))
if active != 0: base_col = self.rgb(uint32(200), uint32(50), uint32(50))
if pressed != 0: base_col = self.rgb(uint32(140), uint32(25), uint32(25))
self.row_fill(bx, yy, bw, base_col)
if active != 0: x_col = self.rgb(uint32(255), uint32(255), uint32(255))
elif style == STYLE_WIN7:
base_col2: uint32 = self.rgb(uint32(200), uint32(50), uint32(50))
if hovered != 0: base_col2 = self.rgb(uint32(220), uint32(60), uint32(60))
if pressed != 0: base_col2 = self.rgb(uint32(140), uint32(25), uint32(25))
# WIN7 特判:红底画成圆(不是圆角矩形)。在 bw×bh 的命中区内
# 居中一个 bh×bh 的圆,圆心与 X 图标中心重合。
circle_x: int = bx + (bw - bh) / 2
self._close_fill_row(s, circle_x, by, bh, bh, yy, base_col2, bh / 2)
x_col = self.rgb(uint32(255), uint32(255), uint32(255))
elif style == STYLE_WINXP:
base_col3: uint32 = self.rgb(uint32(200), uint32(50), uint32(50))
if hovered != 0: base_col3 = self.rgb(uint32(220), uint32(60), uint32(60))
if pressed != 0: base_col3 = self.rgb(uint32(140), uint32(25), uint32(25))
self._close_fill_row(s, bx, by, bw, bh, yy, base_col3, 0)
x_col = self.rgb(uint32(255), uint32(255), uint32(255))
else:
x_col = self.rgb(uint32(150), uint32(150), uint32(150))
# WIN10/WIN11 平时无底色;hover 变红、按下更深,图标保持白色
if active != 0:
bg: uint32 = self.rgb(uint32(200), uint32(50), uint32(50))
if pressed != 0: bg = self.rgb(uint32(140), uint32(25), uint32(25))
if style == STYLE_WIN11:
# 按钮本身保持方角;只丢弃窗口圆角轮廓之外的部分
self._close_fill_row(s, bx, by, bw, bh, yy, bg, 0)
else:
self.row_fill(bx, yy, bw, bg)
x_col = self.rgb(uint32(255), uint32(255), uint32(255))
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)
# 方框描边的一行(x0..x0+size,size 为半宽/边宽)
def _draw_square_row(self, x0: int, y0: int, size: int, yy: int, col: uint32):
if yy < y0 or yy > y0 + size: return
if yy == y0 or yy == y0 + size:
self.row_fill(x0, yy, size + 1, col)
else:
self.row_fill(x0, yy, 1, col)
self.row_fill(x0 + size, yy, 1, col)
def _draw_min_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, hovered: int, pressed: int):
if pressed != 0:
self._dim_row(bx, yy, bw, 90)
elif hovered != 0:
self._dim_row(bx, yy, bw, 50)
cx: int = bx + bw / 2
cy: int = by + bh / 2
lw: int = 5
if bw >= 30: lw = 8
col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180))
if hovered != 0 or pressed != 0: col = self.rgb(uint32(255), uint32(255), uint32(255))
if yy == cy + 3:
self.row_fill(cx - lw, yy, lw * 2 + 1, col)
def _draw_max_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, hovered: int, pressed: int, maximized: int):
if pressed != 0:
self._dim_row(bx, yy, bw, 90)
elif hovered != 0:
self._dim_row(bx, yy, bw, 50)
cx: int = bx + bw / 2
cy: int = by + bh / 2
sz: int = 4
if bw >= 30: sz = 6
col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180))
if hovered != 0 or pressed != 0: col = self.rgb(uint32(255), uint32(255), uint32(255))
if maximized != 0:
# 还原图标:两个错位方框(右上在后,左下在前)
self._draw_square_row(cx - 1, cy - sz, sz, yy, col)
self._draw_square_row(cx - sz, cy - 1, sz, yy, col)
else:
# 单方框;上边框只画一行(不再加厚)
self._draw_square_row(cx - sz, cy - sz, sz * 2, yy, 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, hover_kind: 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(s, close_x, by, bw, bh, yy, style,
1 if hover_kind == 1 else 0, 1 if pressed_kind == 1 else 0)
self._draw_min_row(min_x, by, bw, bh, yy, style,
1 if hover_kind == 2 else 0, 1 if pressed_kind == 2 else 0)
self._draw_max_row(max_x, by, bw, bh, yy, style,
1 if hover_kind == 3 else 0, 1 if pressed_kind == 3 else 0,
1 if s.maximized != 0 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_src(yy), 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_src(yy), sx[w], sw[w])
if xx < self.fb_w:
self.row_blit(xx, yy, self.bg_src(yy), xx, self.fb_w - xx)
yy += 1
# 2) 窗口
for i in range(cnt):
self.render_window_rows(order[i], y0, y1, top_z)
# 3) 任务栏:最后画,始终盖在窗口之上(视觉层级高于所有窗口)
self._draw_taskbar_rows(y0, y1)
# 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 and s.maximized == 0:
shadow_r: int = 0
if rounded != 0: shadow_r = WIN_RADIUS
self._draw_shadow(s, y0, y1, shadow_r)
if wy + wh <= y0 or wy >= y1: return
# 合成器只读“最后一次 FLUSH 的可见缓冲”。resize 期间 s.fb_* 已经是新尺寸,
# 但应用还没画完,绝不能拿新步长去读旧缓冲(否则就是截图里的乱码)。
v_off: uint32 = self.vis_off[win]
fw: int = self.vis_w[win]
fh: int = self.vis_h[win]
buf: uint32* = None
if v_off >= uint32(desk.DESK_OFF_ARENA) and fw > 0 and fh > 0:
need_bytes: uint64 = uint64(fw) * uint64(fh) * uint64(4)
if uint64(v_off) + need_bytes <= uint64(desk.DESK_SHM_SIZE):
buf = uint32*(self.shm + uint64(v_off))
else:
fw = 0
fh = 0
else:
fw = 0
fh = 0
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
hover_kind: int = 0
if self.hover_btn_win == win: hover_kind = self.hover_btn_kind
self._draw_window_btns(s, y0, y1, th, hover_kind, 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)
# 光标带置脏:光标模板可能有 -8 的居中偏移,统一按 26 行覆盖旧/新位置
def _mark_cursor_at(self, py: int):
self.mark(py - 9, CURSOR_BMP_H + 10)
def _mark_cursor_band(self):
self._mark_cursor_at(self.cy)
def _cursor_kind(self) -> int:
# 缩放拖动期间锁定边缘光标
if self.resize_win >= 0:
return self._edge_cursor(self.resize_edge)
e: int = self._hover_resize_edge()
if e != 0:
return self._edge_cursor(e)
hw: int = self.hit_test(self.cx, self.cy)
if hw >= 0:
s: desk.WinSlot* = self.slot(hw)
if self.cy < s.uy + TITLE_H: return CURSOR_DEFAULT
c: int = s.cursor
if c < 0 or c > CURSOR_RESIZE_NESW: c = CURSOR_DEFAULT
return c
if self._taskbar_icon_hit(self.cx, self.cy) >= 0:
return CURSOR_HAND
return CURSOR_DEFAULT
def _cursor_value(self, kind: int, idx: int) -> uint8:
if kind == CURSOR_IBEAM: return _CURSOR_IBEAM[idx]
if kind == CURSOR_HAND: return _CURSOR_HAND[idx]
if kind == CURSOR_RESIZE_EW: return _CURSOR_EW[idx]
if kind == CURSOR_RESIZE_NS: return _CURSOR_NS[idx]
if kind == CURSOR_RESIZE_NWSE: return _CURSOR_NWSE[idx]
if kind == CURSOR_RESIZE_NESW: return _CURSOR_NESW[idx]
return _CURSOR_ARROW[idx]
def render_cursor_rows(self, y0: int, y1: int):
# 0 透明(不写像素,靠重画背景恢复),1 黑边,2 白心;同行连续同色合并成一次 row_fill。
kind: int = self._cursor_kind()
dx: int = 0
dy: int = 0
if kind == CURSOR_HAND:
dx = -7
elif kind != CURSOR_DEFAULT:
# I 形与缩放光标的热点在形状中心
dx = -8
dy = -8
cy0: int = self.cy + dy
cy1: int = cy0 + 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 = self._cursor_value(kind, row + col)
if v == 0:
col += 1
continue
start: int = col
col += 1
while col < CURSOR_BMP_W and self._cursor_value(kind, row + col) == v:
col += 1
px: int = self.cx + dx + 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
if SCREEN_SYNC != 0:
# 可选:等垂直回扫,减少撕裂。内核侧是有界轮询(2048 次),
# 不会像旧版 2×100000 次端口读那样拖慢一帧。
devfs.fb_wait_vblank(self.fb_fd)
# 逐脏段渲染并提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、
# 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交,帧率直接崩。
# 段内先全画进 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)
# back 与 fb 行距一致时整段一次 memcpy:大范围拖动时比逐行 1080 次
# rep movsb 少很多固定开销;行距不同才退回逐行。
if self.pitch_px == self.fb_w:
_fb_copy(uint64(self.fb) + uint64(y0 * self.pitch_px) * uint64(4),
uint64(self.back) + uint64(y0 * self.fb_w) * uint64(4),
uint64((y1 - y0) * self.fb_w) * uint64(4))
else:
yy: int = y0
while yy < y1:
_fb_copy(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4),
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 draw_text_request(self, win: int, x: int, y: int, color: uint32, text: char*):
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
_ensure_ttf()
buf: uint32* = uint32*(self.shm + uint64(s.fb_off))
bw: int = int(s.fb_w)
bh: int = int(s.fb_h)
cx: int = x
i: int = 0
while text[i] != 0:
ci: int = int(uint8(text[i]))
if _ttf_state == 1 and ci < 128:
fy: int = 0
while fy < vpui.CHAR_H:
ty: int = y + fy
if ty >= 0 and ty < bh:
base: int = ty * bw
fx: int = 0
while fx < vpui.CHAR_W:
a: int = int(_ttf_atlas[(ci * vpui.CHAR_H + fy) * vpui.CHAR_W + fx])
if a != 0:
px: int = cx + fx
if px >= 0 and px < bw:
idx: int = base + px
if a >= 255:
buf[idx] = color
else:
dst: uint32 = buf[idx]
dr: int = int(dst & 0xFF)
dg: int = int((dst >> 8) & 0xFF)
db: int = int((dst >> 16) & 0xFF)
sr: int = int(color & 0xFF)
sg: int = int((color >> 8) & 0xFF)
sb: int = int((color >> 16) & 0xFF)
r: int = (sr * a + dr * (255 - a)) / 255
g: int = (sg * a + dg * (255 - a)) / 255
b: int = (sb * a + db * (255 - a)) / 255
buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16)
fx += 1
fy += 1
else:
c2: int = ci & 127
fy = 0
while fy < vpui.CHAR_H:
ty = y + fy
if ty >= 0 and ty < bh:
row: uint8 = vpui.font[c2][fy]
if row != uint8(0):
base = ty * bw
fx = 0
while fx < vpui.CHAR_W:
if row & (0x80 >> fx):
px = cx + fx
if px >= 0 and px < bw:
buf[base + px] = color
fx += 1
fy += 1
cx += vpui.CHAR_W
i += 1
self.mark(s.uy, s.h)
# ---------------- 请求处理 ----------------
def handle_request(self, m: desk.DeskMsg):
if m.type == uint32(desk.DESK_REQ_CREATE):
# pid 随请求携带(窗口还没诞生),标题内联在消息里
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))
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_TEXT):
self.draw_text_request(win, m.a, m.b, uint32(m.c), &m.title[0])
return
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, win, 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_CURSOR):
if s.cursor != m.a:
s.cursor = m.a
# 光标正压在这个窗口上:立即按新形状重画光标带
if self.hit_test(self.cx, self.cy) == win:
self._mark_cursor_band()
elif ty == uint32(desk.DESK_REQ_FLUSH):
# present=0:PING/保活,不交换缓冲,避免显示未绘制的新缓冲。
if m.a == 0:
return
old_off: uint32 = self.vis_off[win]
old_cap: uint32 = self.vis_cap[win]
need: int = int(s.fb_w) * int(s.fb_h)
if s.fb_off != old_off:
# 本帧画在新的 offscreen 缓冲里:切到它,旧 vis 作为下一帧目标。
self.vis_off[win] = s.fb_off
self.vis_cap[win] = s.fb_cap
self.vis_w[win] = int(s.fb_w)
self.vis_h[win] = int(s.fb_h)
if old_off >= uint32(desk.DESK_OFF_ARENA) and int(old_cap) >= need:
s.fb_off = old_off
s.fb_cap = old_cap
else:
nw: uint32 = desk.desk_arena_alloc(self.shm, uint32(need * 4))
if nw != uint32(0):
s.fb_off = nw
s.fb_cap = uint32(need)
else:
s.fb_off = old_off
s.fb_cap = old_cap
else:
# 首帧或单缓冲回退:vis 已经是当前内容,为下一帧准备第二块。
nw2: uint32 = desk.desk_arena_alloc(self.shm, uint32(need * 4))
if nw2 != uint32(0):
s.fb_off = nw2
s.fb_cap = uint32(need)
# 最小化窗口仍在后台绘制/FLUSH:不要置脏,否则合成器每帧都要重画背景。
if s.minimized == 0:
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:
# 任务栏层级最高:覆盖区域内不接受窗口命中(图标命中由 _taskbar_icon_hit 单独处理)
if y >= self.fb_h - TASKBAR_H: return -1
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):
# --- 键盘:设备侧已给出 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:
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:
moved: int = 0
wheel: int = 0
if mn >= 10:
wv: int = int(mp[9])
if wv >= 128: wv = wv - 256
wheel = wheel + wv
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
if mn >= 10:
wv2: int = int(mp[9])
if wv2 >= 128: wv2 = wv2 - 256
wheel = wheel + wv2
mn = fs.read(self.mfd, &mp, uint32(16))
if wheel != 0:
# 滚轮:投给光标下那个窗口(窗口内坐标),由控件决定滚动行为。
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_WHEEL,
self.cx - hs.ux, self.cy - hs.uy - TITLE_H - TITLE_GAP, wheel)
if moved != 0:
self._mark_cursor_at(self.pcy)
self._mark_cursor_at(self.cy)
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)
edge: int = self._resize_edge_at(s, self.cx, self.cy)
kind: int = self._title_btn_hit(s, self.cx, self.cy)
self.focus(hit)
if edge != 0:
# 窗口边缘 ⇒ 拖拽缩放
self.resize_win = hit
self.resize_edge = edge
self.resize_ox = s.ux
self.resize_oy = s.uy
self.resize_ow = s.w
self.resize_oh = s.h
self.resize_sx = self.cx
self.resize_sy = self.cy
elif 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.resize_win >= 0:
# 缩放结束:补发一次最终 RESIZE,防止拖动期间事件环满导致 app 停在旧尺寸。
rs: desk.WinSlot* = self.slot(self.resize_win)
if rs.used != 0:
self.push_event(self.resize_win, uint32(desk.DESK_EVT_RESIZE),
int(rs.fb_w), int(rs.fb_h), 0, 0)
self.mark_win(rs.uy, rs.h)
self.resize_win = -1
self._mark_cursor_band()
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.resize_win >= 0:
self._drag_resize()
elif 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)
self._update_btn_hover()
# 标题栏按钮 hover 变化时只重画相关标题栏(用于变色反馈)
def _update_btn_hover(self):
nw: int = -1
nk: int = 0
if self.btn_down == 0:
hw: int = self.hit_test(self.cx, self.cy)
if hw >= 0:
s: desk.WinSlot* = self.slot(hw)
if self.cy < s.uy + TITLE_H:
k: int = self._title_btn_hit(s, self.cx, self.cy)
if k != 0:
nw = hw
nk = k
if nw == self.hover_btn_win and nk == self.hover_btn_kind: return
if self.hover_btn_win >= 0:
os: desk.WinSlot* = self.slot(self.hover_btn_win)
if os.used != 0: self.mark(os.uy, TITLE_H)
if nw >= 0:
ns: desk.WinSlot* = self.slot(nw)
self.mark(ns.uy, TITLE_H)
self.hover_btn_win = nw
self.hover_btn_kind = nk
# ---------------- 主循环 ----------------
def serve(self):
# 进主循环前先整屏刷一次:内核启动期的 VGA/gfx 测试内容(例如蓝色测试块)不在任何
# 脏行里,只靠后续增量渲染会永远留在屏幕上。
self.mark_all()
self.render()
while True:
self.poll_requests()
self.poll_input()
self.render()
process.yield_cpu()