批量取包后只保留最后一包的按键状态:快速点击的按下+释放落在同一批里,最终 btn=0,按下沿永远检测不到(表现为必须长按)。改为批次内维护 cur 并记录 saw_press(任一分包出现 0→1)。同时把释放与拖动从 elif 改为独立 if:同批内按下并松开时,按下分支之后必须立刻走释放,否则 btn_down 卡在 1,后续点击继续失效。
1370 lines
59 KiB
Plaintext
1370 lines
59 KiB
Plaintext
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
|
||
|
||
|
||
# ============================================================================
|
||
# 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 的窗口圆角半径
|
||
# 窗口样式(与 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
|
||
# 脏行
|
||
dirty: uint8* # 每行 1 字节
|
||
dirty_any: int
|
||
# 光标 / 焦点 / 拖拽
|
||
cx: int
|
||
cy: int
|
||
pcx: int
|
||
pcy: int
|
||
focused: int
|
||
next_z: int
|
||
btn_down: int
|
||
drag_win: int
|
||
drag_dx: int
|
||
drag_dy: 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
|
||
# 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.drag_win = -1
|
||
self.drag_dx = 0
|
||
self.drag_dy = 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
|
||
# 后备缓冲:整屏像素,行距 = 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))
|
||
|
||
# 最高 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:
|
||
buf_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(w) * uint32(h) * uint32(4))
|
||
if buf_off == uint32(0): return -1
|
||
s.used = 1
|
||
s.pid = pid
|
||
s.active = 1
|
||
s.minimized = 0
|
||
s.ux = x
|
||
s.uy = y
|
||
s.w = w
|
||
s.h = h
|
||
s.style = style
|
||
s.cursor = 0
|
||
s.fb_off = buf_off
|
||
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(w * h):
|
||
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):
|
||
lry: int = yy - by
|
||
if lry < 0 or lry >= bh: return
|
||
cx: int = bx + bw / 2
|
||
cy: int = by + bh / 2
|
||
d: int = 3
|
||
if bw >= 30: d = 4
|
||
x_col: uint32 = self.rgb(uint32(200), uint32(200), uint32(200))
|
||
if style == STYLE_CLASSIC:
|
||
self.row_fill(bx, yy, bw, self.rgb(uint32(40), uint32(40), uint32(70)))
|
||
elif style == STYLE_WIN7 or style == STYLE_WINXP:
|
||
self._round_fill_row(bx, by, bw, bh, yy, self.rgb(uint32(200), uint32(50), uint32(50)), 3)
|
||
x_col = self.rgb(uint32(255), uint32(255), uint32(255))
|
||
else:
|
||
x_col = self.rgb(uint32(150), uint32(150), uint32(150))
|
||
i: int = yy - cy
|
||
if i >= 0 - 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):
|
||
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):
|
||
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
|
||
|
||
def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int):
|
||
wx: int = s.ux
|
||
wy: int = s.uy
|
||
ww: int = s.w
|
||
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)
|
||
self._draw_min_row(min_x, by, bw, bh, yy, style)
|
||
self._draw_max_row(max_x, by, bw, bh, yy, style)
|
||
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
|
||
|
||
# 点击图标:最小化先恢复,再走既有 focus(置顶并整体置脏)
|
||
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)
|
||
|
||
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
|
||
|
||
self._draw_window_btns(s, y0, y1, th)
|
||
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):
|
||
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))
|
||
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.mark(s.uy, s.h)
|
||
s.used = 0
|
||
if self.focused == win: self.focused = -1
|
||
# 拖拽中的窗口被销毁:必须清掉 drag_win,否则后续移动会继续操作这个死槽位。
|
||
if self.drag_win == win: self.drag_win = -1
|
||
self.mark_all_windows()
|
||
self.mark_titles()
|
||
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)
|
||
new_ch: int = m.b - TITLE_H - TITLE_GAP
|
||
if new_ch < 1: new_ch = 1
|
||
# arena 是 bump 分配、不能回收:换尺寸一律重新切片,旧块留给后续复用
|
||
new_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(m.a) * uint32(m.b) * uint32(4))
|
||
if new_off != uint32(0):
|
||
s.fb_off = new_off
|
||
s.w = m.a
|
||
s.h = m.b
|
||
s.fb_w = uint32(m.a)
|
||
s.fb_h = uint32(new_ch)
|
||
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):
|
||
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
|
||
|
||
def poll_input(self):
|
||
global _mouse_logged, _kbd_logged
|
||
# --- 键盘:设备侧已给出 ASCII,逐字符投给焦点窗口(无焦点/失效则顶层窗口)---
|
||
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)
|
||
# 无目标窗口(target < 0)时本批字节丢弃:这里只读一次、不重试,不会空转。
|
||
target: int = self._key_target()
|
||
if target >= 0:
|
||
i: int = 0
|
||
while i < kn:
|
||
self.push_event(target, uint32(desk.DESK_EVT_KEY), int(kb[i]), 0, 0, 0)
|
||
i += 1
|
||
# --- 鼠标:内核每次返回一个包。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))
|
||
while mn > 0:
|
||
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
|
||
# 悬停:把 HOVER 投给光标下那个窗口(窗口内坐标)
|
||
if self.btn_down == 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_HOVER,
|
||
self.cx - hs.ux, self.cy - hs.uy - TITLE_H - TITLE_GAP, 0)
|
||
if saw_press != 0:
|
||
self.btn_down = 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)
|
||
# 关闭键热区按样式尺寸算,必须与 _draw_window_btns 一致
|
||
cw: int = self._close_btn_w(s.style)
|
||
ch: int = self._close_btn_h(s.style)
|
||
bx: int = s.ux + s.w - cw - self._close_btn_pad(s.style)
|
||
by: int = s.uy + (TITLE_H - ch) / 2
|
||
in_close: int = 0
|
||
if self.cx >= bx and self.cx < bx + cw and self.cy >= by and self.cy < by + ch:
|
||
in_close = 1
|
||
self.focus(hit)
|
||
if in_close != 0:
|
||
# 关闭:通知一次 CLOSE 后把槽位置空回收;window.vp 的 is_active()
|
||
# 见到 used==0 即返回 0,app 的 while 循环自然退出。
|
||
# 像素缓冲不回收(arena 是 bump 分配),只放槽位。
|
||
self.push_event(hit, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0, 0)
|
||
self.mark_win(s.uy, s.h)
|
||
s.used = 0
|
||
if self.focused == hit: self.focused = -1
|
||
if self.drag_win == hit: self.drag_win = -1
|
||
self.mark_titles()
|
||
elif self.cy < s.uy + TITLE_H:
|
||
# 标题栏(非关闭按钮)⇒ 开始拖动
|
||
self.drag_win = hit
|
||
self.drag_dx = self.cx - s.ux
|
||
self.drag_dy = self.cy - s.uy
|
||
else:
|
||
# 内容区 ⇒ 按窗口内坐标把点击投给 app
|
||
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
|
||
if self.drag_win < 0:
|
||
# 松手:投 RELEASE 给光标下的窗口
|
||
rw: int = self.hit_test(self.cx, self.cy)
|
||
if rw >= 0:
|
||
rs: desk.WinSlot* = self.slot(rw)
|
||
self.push_event(rw, uint32(desk.DESK_EVT_MOUSE),
|
||
desk.DESK_MOUSE_RELEASE,
|
||
self.cx - rs.ux, self.cy - rs.uy - TITLE_H - TITLE_GAP, 0)
|
||
self.drag_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()
|
||
while True:
|
||
self.poll_requests()
|
||
self.poll_input()
|
||
self.render()
|
||
process.yield_cpu()
|