From ecabd5951838db58308034632194bd19b0bfca67 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 16 Oct 2026 21:47:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(explorer):=20=E8=BF=98=E5=8E=9F=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E6=A0=B7=E5=BC=8F=E3=80=81=E9=98=B4=E5=BD=B1=E3=80=81?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=A0=8F=E4=B8=8E=E4=B8=89=E6=9E=9A=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E6=8C=89=E9=92=AE=EF=BC=8C=E7=B2=BE=E7=AE=80=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对齐被删除的内核桌面 desktop.vp 的视觉:窗口改为四边框 + 标题栏(活动/非活动配色)+ 标题内容分隔线 + 内容按 1px 内缩合成,并按 WinSlot.style 选择样式配色;新增最小化/最大化/关闭三枚按钮;新增矩形窗口阴影(SHADOW_* + self._blend 按 framebuffer 格式做 alpha 混合);任务栏改为深色底 + 分 opper线 + 蓝色 logo 圆盘 + ViperOS 文本 + 每个活动窗口的带边框图标(焦点/普通/最小化三色)与标题首字符,高度 28→50 对齐旧桌面。置脏范围改用 mark_win(含阴影带)/ mark_titles(活动窗口切换时刷新所有标题栏)。同时精简全文注释(不保留历史沿革叙述)。任务栏时钟未实现:用户态无时间源;圆角未实现:style 尚未透传到 CREATE 请求;最小化/最大化仅外观未接线。 --- Apps/Explorer/desk_server.vp | 483 ++++++++++++++++++++++++++++------- 1 file changed, 393 insertions(+), 90 deletions(-) diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index f216db4..5cfcfe1 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -3,42 +3,50 @@ 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 点阵字体表(vpsdk/font 尚无独立模块,不抄第二份) +import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表 from stdint import * import string import viperlib -# 【诊断·临时】首次收到鼠标包的证据(0 = 还没打过)。用于把"内核没有鼠标中断"与 -# "explorer 收到包但没画光标/没投事件"这两种完全不同的故障方向分开。 +# 【诊断·临时】首个鼠标包已收到的标记(0 = 未收到) _mouse_logged: int = 0 # ============================================================================ -# explorer 的窗口服务端 + 合成器(S3c)。 -# -# 数据面:app 把窗口像素写进共享区 arena 里自己那块,压一条 DESK_REQ_FLUSH; -# 本服务端合成时按 Z 序把它们逐行 memcpy 进 framebuffer(不搬像素)。 -# 控制面:请求环(app → explorer)与每窗口事件环(explorer → app)。 -# 输入:读数从 /dev/input/keyboard、/dev/input/mouse(内核 devfs 设备已完成 -# 扫描码→ASCII 与鼠标包解码),本服务端负责光标、命中测试、焦点与事件投递。 -# -# 本切片未做(后续):最小化、窗口侧边框与样式区分、任务栏时钟(用户态还没有时间源)、 -# 拖边缩放、心跳无响应检测。字体表直接复用 vpui.font(8x16 点阵),未另抄一份。 +# 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 CLOSE_SZ = 14 define CLOSE_PAD = 5 -# 光标是 16×16 位图,模板直接取自 VKernel/Kernel/services/desktop.vp(S3e 删除前的实现, -# 见 git 13eee8e^):0 = 透明、1 = 黑边、2 = 白心。同一个历史文件里还有 4 套模板, -# 将来接上 WinSlot.cursor 即可启用:_CURSOR_IBEAM / _CURSOR_RESIZE_EW / -# _CURSOR_RESIZE_NS / _CURSOR_RESIZE_NWSE。 +define BTN_GAP = 2 define CURSOR_BMP_W = 16 define CURSOR_BMP_H = 16 -define TASKBAR_H = 28 +# 任务栏与窗口装饰尺寸(对齐内核桌面 desktop.vp) +define TASKBAR_H = 50 +define TASKBAR_LOGO_SZ = 32 +define TASKBAR_ICON_SZ = 36 +define TASKBAR_ICON_PAD = 6 +# 窗口样式(与 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, @@ -66,8 +74,8 @@ class DeskServer: fb_w: int fb_h: int pitch_px: int - fmt: uint32 # 0 = RGBA, 1 = BGRA(与内核 gfx 口径一致) - bg_row: uint32* # 一行背景的预构造副本(避免逐像素填) + fmt: uint32 # 0 = RGBA, 1 = BGRA + bg_row: uint32* # 一行背景的预构造副本 # 共享区 shm: uint64 # 输入 @@ -97,6 +105,31 @@ class DeskServer: return (uint32(255) << 24) | (r << 16) | (g << 8) | b return (uint32(255) << 24) | (b << 16) | (g << 8) | r + # ---- 按 framebuffer 格式把 fg 以 alpha/255 混到 bg 上 ---- + def _blend(self, bg: uint32, fg: uint32, a: int) -> uint32: + ia: int = 255 - a + if self.fmt == uint32(1): + br: int = int((bg >> 16) & 0xFF) + bg_g: int = int((bg >> 8) & 0xFF) + bg_b: int = int(bg & 0xFF) + fr: int = int((fg >> 16) & 0xFF) + fg_g: int = int((fg >> 8) & 0xFF) + fg_b: int = int(fg & 0xFF) + r: int = (fr * a + br * ia) >> 8 + g: int = (fg_g * a + bg_g * ia) >> 8 + b: int = (fg_b * a + bg_b * ia) >> 8 + return (uint32(255) << 24) | (uint32(r) << 16) | (uint32(g) << 8) | uint32(b) + br = int(bg & 0xFF) + bg_g = int((bg >> 8) & 0xFF) + bg_b = int((bg >> 16) & 0xFF) + fr = int(fg & 0xFF) + fg_g = int((fg >> 8) & 0xFF) + fg_b = int((fg >> 16) & 0xFF) + r = (fr * a + br * ia) >> 8 + g = (fg_g * a + bg_g * ia) >> 8 + b = (fg_b * a + bg_b * ia) >> 8 + return (uint32(255) << 24) | (uint32(b) << 16) | (uint32(g) << 8) | uint32(r) + 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 @@ -129,7 +162,7 @@ class DeskServer: uint64(syscall.PROT_READ | syscall.PROT_WRITE), uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS), syscall.MMAP_FD_ANON, 0)) - col: uint32 = self.rgb(uint32(18), uint32(24), uint32(32)) + col: uint32 = self.rgb(uint32(25), uint32(25), uint32(35)) for i in range(w): self.bg_row[i] = col self.mark_all() @@ -154,6 +187,17 @@ class DeskServer: 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) + + # 活动窗口切换会改变标题栏配色:把所有窗口的标题栏行置脏 + 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) + # ---------------- 基本绘制 ---------------- def row_fill(self, x0: int, y: int, w: int, col: uint32): if y < 0 or y >= self.fb_h or w <= 0: return @@ -165,12 +209,11 @@ class DeskServer: if x + dw > self.fb_w: dw = self.fb_w - x if dw <= 0: return - # rep stosd 整段填色(原为逐像素循环)——任务栏/标题栏每帧要填上万像素,是热点之一。 base: int = y * self.pitch_px string.memset32(rawptr(uint64(self.fb) + 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)。裁剪到屏幕内。""" + """把 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 @@ -190,13 +233,18 @@ class DeskServer: def slot(self, win: int) -> desk.WinSlot*: return desk.WinSlot*(self.shm + desk.desk_slot_off(win)) - def alloc_window(self, pid: int, x: int, y: int, w: int, h: int, style: int) -> int: - """找一个空槽并分配像素缓冲。返回 win_id,失败 -1。 + # 最高 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 - 尺寸约定(必须与 vpui 对齐):入参 w/h 是**含标题栏**的外框;内容的像素缓冲是 - w × (h - TITLE_H - TITLE_GAP),与 vpui 的 `bh = h - TITLE_H - 2` 一致。 - arena 只是 bump 分配、不支持回收,所以像素缓冲按 w*h(外框)预留一点余量。 - """ + 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): @@ -226,11 +274,12 @@ class DeskServer: s.z_order = self.next_z self.next_z += 1 self.focused = i - self.mark(y, h) + self.mark_win(y, h) + self.mark_titles() return i return -1 - # ---------------- 文本(8x16 点阵,字体表直接复用 vpui.font)---------------- + # ---------------- 文本(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 @@ -258,19 +307,197 @@ class DeskServer: cx += vpui.CHAR_W i += 1 + # ---------------- 窗口阴影 ---------------- + 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 + sa: int = SHADOW_ALPHA + shadow_col: uint32 = self.rgb(uint32(0), uint32(0), uint32(0)) + 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: + px: int = wx - sl + if px < 0: px = 0 + xend: int = wx + ww + sr + if xend > self.fb_w: xend = self.fb_w + while px < xend: + if px < wx or px >= wx + ww or yy < wy or yy >= wy + wh: + cpx: int = px + if cpx < wx: cpx = wx + elif cpx > wx + ww - 1: cpx = wx + ww - 1 + cpy: int = yy + if cpy < wy: cpy = wy + elif cpy > wy + wh - 1: cpy = wy + wh - 1 + dx: int = px - cpx + dy: int = yy - cpy + adx: int = dx + if adx < 0: adx = 0 - adx + ady: int = dy + if ady < 0: ady = 0 - ady + dist: int = adx + if ady > dist: dist = ady + dist = dist + ((adx + ady - dist) * 3) / 8 + me_x: int = sl + if dx > 0: me_x = sr + me_y: int = st + if dy > 0: me_y = sb + 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 + a: int = (sa * t_inv * t_inv) / 65536 + if a > 0: + idx: int = yy * self.pitch_px + px + self.fb[idx] = self._blend(self.fb[idx], shadow_col, a) + px += 1 + yy += 1 + + # ---------------- 窗口按钮(按行绘字形,CLASSIC 配色)---------------- + def _draw_close_row(self, bx: int, by: int, sz: int, yy: int): + self.row_fill(bx, yy, sz, self.rgb(uint32(40), uint32(40), uint32(70))) + x_col: uint32 = self.rgb(uint32(150), uint32(150), uint32(150)) + cx: int = bx + sz / 2 + cy: int = by + sz / 2 + i: int = yy - cy + if i >= 0 - 3 and i <= 3: + self.row_fill(cx + i, yy, 1, x_col) + self.row_fill(cx - i, yy, 1, x_col) + + def _draw_min_row(self, bx: int, by: int, sz: int, yy: int): + cx: int = bx + sz / 2 + cy: int = by + sz / 2 + if yy == cy + 3: + self.row_fill(cx - 5, yy, 11, self.rgb(uint32(180), uint32(180), uint32(180))) + + def _draw_max_row(self, bx: int, by: int, sz: int, yy: int): + col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180)) + cx: int = bx + sz / 2 + cy: int = by + sz / 2 + q: int = 4 + dy: int = yy - cy + if dy < 0: dy = 0 - dy + if dy > q: return + if dy == q or yy == cy - q + 1: + self.row_fill(cx - q, yy, 2 * q + 1, col) + else: + self.row_fill(cx - q, yy, 1, col) + self.row_fill(cx + q, yy, 1, col) + + def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int): + wx: int = s.ux + wy: int = s.uy + ww: int = s.w + sz: int = CLOSE_SZ + by: int = wy + (th - sz) / 2 + close_x: int = wx + ww - sz - CLOSE_PAD + max_x: int = close_x - sz - BTN_GAP + min_x: int = max_x - sz - BTN_GAP + yy: int = y0 + if yy < by: yy = by + yend: int = y1 + if yend > by + sz: yend = by + sz + while yy < yend: + self._draw_close_row(close_x, by, sz, yy) + self._draw_min_row(min_x, by, sz, yy) + self._draw_max_row(max_x, by, sz, yy) + yy += 1 + + # ---------------- 任务栏 ---------------- + 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.fb[yy * self.pitch_px + px] = lc_core + else: + self.fb[yy * self.pitch_px + 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_x0: int = logo_x + TASKBAR_LOGO_SZ + 80 + icon_y: int = bar_y + (TASKBAR_H - TASKBAR_ICON_SZ) / 2 + 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 = icon_x0 + cnt * (TASKBAR_ICON_SZ + TASKBAR_ICON_PAD) + 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): # 1) 背景 for yy in range(y0, y1): self.row_blit(0, yy, self.bg_row, 0, self.fb_w) - # 2) 任务栏(在窗口之前画 —— 与旧内核桌面同序,窗口会盖住它) - tb_y: int = self.fb_h - TASKBAR_H - if y1 > tb_y: - for yy in range(y0, y1): - if yy >= tb_y: - self.row_fill(0, yy, self.fb_w, self.rgb(uint32(28), uint32(34), uint32(48))) - self.draw_str(10, tb_y + (TASKBAR_H - vpui.CHAR_H) / 2, "ViperOS", - self.rgb(uint32(200), uint32(210), uint32(230))) + # 2) 任务栏(先于窗口,窗口会盖住它 —— 与内核桌面同序) + self._draw_taskbar_rows(y0, y1) # 3) 窗口按 Z 序升序(选择排序,窗口数 ≤ 8) order: [desk.DESK_MAX_WIN]int cnt: int = 0 @@ -287,7 +514,6 @@ class DeskServer: t: int = order[a] order[a] = order[b] order[b] = t - # 3) 逐个窗口:只重画落在 [y0,y1) 的那几行 for i in range(cnt): self.render_window_rows(order[i], y0, y1) # 4) 光标 @@ -295,34 +521,112 @@ class DeskServer: def render_window_rows(self, win: int, y0: int, y1: int): s: desk.WinSlot* = self.slot(win) - x: int = s.ux - w: int = s.w - for yy in range(y0, y1): - if yy < s.uy or yy >= s.uy + s.h: continue - rel: int = yy - s.uy - if rel < TITLE_H: - self.row_fill(x, yy, w, self.rgb(uint32(52), uint32(78), uint32(120))) - # 关闭按钮(红块)落在这一行就画上 - bx: int = x + w - CLOSE_SZ - CLOSE_PAD - by: int = s.uy + (TITLE_H - CLOSE_SZ) / 2 - if yy >= by and yy < by + CLOSE_SZ: - self.row_fill(bx, yy, CLOSE_SZ, self.rgb(uint32(210), uint32(70), uint32(70))) + wx: int = s.ux + wy: int = s.uy + ww: int = s.w + wh: int = s.h + th: int = TITLE_H + style: int = s.style + active: int = 0 + if s.z_order == self._top_z(): active = 1 + # 样式配色:CLASSIC 为默认;其余样式仅配色/分隔线可区分(几何同 CLASSIC) + title_col: uint32 = self.rgb(uint32(50), uint32(50), uint32(90)) + border_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 + if style == STYLE_WIN10: + has_separator = 0 + if active != 0: + title_col = self.rgb(uint32(0), uint32(0), uint32(0)) + border_col = self.rgb(uint32(0), uint32(0), uint32(0)) + content_col = self.rgb(uint32(32), uint32(32), uint32(32)) else: - cy: int = rel - TITLE_H - TITLE_GAP - cw: int = int(s.fb_w) - if cw > w: cw = w - if cy >= 0 and cy < int(s.fb_h): - buf: uint32* = uint32*(self.shm + uint64(s.fb_off)) - self.row_blit(x, yy, buf, cy * int(s.fb_w), cw) - # 标题文字:本段脏行与标题栏相交就整条重画(文字很短,代价可忽略) - if y0 < s.uy + TITLE_H and y1 > s.uy: - self.draw_str(x + 8, s.uy + (TITLE_H - vpui.CHAR_H) / 2, &s.title[0], - self.rgb(uint32(236), uint32(240), uint32(248))) + title_col = self.rgb(uint32(32), uint32(32), uint32(32)) + border_col = self.rgb(uint32(32), uint32(32), uint32(32)) + content_col = self.rgb(uint32(24), uint32(24), uint32(24)) + elif style == STYLE_WIN11: + has_separator = 0 + if active != 0: + title_col = self.rgb(uint32(32), uint32(32), uint32(32)) + border_col = self.rgb(uint32(32), uint32(32), uint32(32)) + content_col = self.rgb(uint32(32), uint32(32), uint32(32)) + else: + title_col = self.rgb(uint32(40), uint32(40), uint32(40)) + border_col = self.rgb(uint32(40), uint32(40), uint32(40)) + content_col = self.rgb(uint32(40), uint32(40), uint32(40)) + elif style == STYLE_WIN7: + has_separator = 0 + if active != 0: + title_col = self.rgb(uint32(156), uint32(181), uint32(209)) + border_col = self.rgb(uint32(156), uint32(181), uint32(209)) + content_col = self.rgb(uint32(240), uint32(240), uint32(240)) + else: + title_col = self.rgb(uint32(180), uint32(195), uint32(210)) + border_col = self.rgb(uint32(180), uint32(195), uint32(210)) + content_col = self.rgb(uint32(240), uint32(240), uint32(240)) + elif style == STYLE_WINXP: + has_separator = 0 + if active != 0: + title_col = self.rgb(uint32(107), uint32(158), uint32(212)) + border_col = self.rgb(uint32(107), uint32(158), uint32(212)) + content_col = self.rgb(uint32(235), uint32(235), uint32(235)) + else: + title_col = self.rgb(uint32(140), uint32(160), uint32(195)) + border_col = self.rgb(uint32(140), uint32(160), uint32(195)) + content_col = self.rgb(uint32(235), uint32(235), uint32(235)) + else: + if active != 0: + title_col = self.rgb(uint32(70), uint32(70), uint32(130)) + border_col = self.rgb(uint32(130), uint32(130), uint32(220)) + + # 阴影画在窗口本体之前(混到已合成的下层像素上),其行范围可能超出窗口外框 + 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 + yy: int = y0 + if yy < wy: yy = wy + yend: int = y1 + if yend > wy + wh: yend = wy + wh + while yy < yend: + rel: int = yy - wy + if rel == 0: + self.row_fill(wx, yy, ww, border_col) # 上边框 + elif rel < th: + self.row_fill(wx, yy, ww, title_col) # 标题栏 + elif rel == th: + if has_separator != 0: + self.row_fill(wx, yy, ww, border_col) # 标题/内容分隔线 + else: + self.row_fill(wx, yy, ww, content_col) + elif rel == th + 1: + self.row_fill(wx, yy, ww, content_col) + elif rel == wh - 1: + self.row_fill(wx, yy, ww, border_col) # 下边框 + else: + self.row_fill(wx, yy, ww, content_col) + row: int = yy - content_y0 + if row >= 0 and row < fh: + self.row_blit(wx + 1, yy, buf, row * fw + 1, ww - 2) + if rel > 0 and rel < wh - 1: + self.row_fill(wx, yy, 1, border_col) # 左边框 + self.row_fill(wx + ww - 1, yy, 1, border_col) # 右边框 + 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): - # 按 16×16 位图模板绘制光标(模板取自 desktop.vp 的历史实现): - # 0 = 透明 —— **不写像素**,背景由 render_range 先重画窗口内容恢复,所以移动不留残影; - # 1 = 黑边(外轮廓)、2 = 白心。同一行里连续的同色像素合并成一次 row_fill。 + # 0 透明(不写像素,靠重画背景恢复),1 黑边,2 白心;同行连续同色合并成一次 row_fill。 cy0: int = self.cy cy1: int = self.cy + CURSOR_BMP_H if cy1 <= y0 or cy0 >= y1: return @@ -357,8 +661,7 @@ class DeskServer: def render(self): if self.dirty_any == 0: return - # 写屏前等垂直回扫:直接写 framebuffer 会与显示器扫描并发 ⇒ 撕裂/闪烁。 - # 用户态没有 IO 权限(不能 inb 0x3DA),由内核代等。 + # 写屏前等垂直回扫(用户态不能 inb 0x3DA,由内核代等),避免撕裂 devfs.fb_wait_vblank(self.fb_fd) y: int = 0 while y < self.fb_h: @@ -373,7 +676,7 @@ class DeskServer: # ---------------- 事件投递 ---------------- def push_event(self, win: int, etype: uint32, a: int, b: int, c: int, d: int) -> int: - """按收件窗口的 pid 投进**该进程自己的**事件环。a..d 是消息的 4 个载荷位。""" + """按收件窗口的 pid 投进该进程自己的事件环。a..d 是消息的 4 个载荷位。""" s: desk.WinSlot* = self.slot(win) self.evt.type = etype self.evt.win = win @@ -398,7 +701,7 @@ class DeskServer: # ---------------- 请求处理 ---------------- def handle_request(self, m: desk.DeskMsg): if m.type == uint32(desk.DESK_REQ_CREATE): - # CREATE:pid 由请求携带(窗口还没诞生,事件环只能按 pid 找到),标题内联在消息里 + # pid 随请求携带(窗口还没诞生),标题内联在消息里 nw: int = self.alloc_window(m.pid, m.a, m.b, m.c, m.d, 0) if nw >= 0: ns: desk.WinSlot* = self.slot(nw) @@ -411,19 +714,20 @@ class DeskServer: if s.used == 0: return ty: uint32 = m.type if ty == uint32(desk.DESK_REQ_DESTROY): - self.mark(s.uy, s.h) + self.mark_win(s.uy, s.h) s.used = 0 if self.focused == win: self.focused = -1 + self.mark_titles() elif ty == uint32(desk.DESK_REQ_MOVE): - self.mark(s.uy, s.h) + self.mark_win(s.uy, s.h) s.ux = m.a s.uy = m.b - self.mark(s.uy, s.h) + self.mark_win(s.uy, s.h) elif ty == uint32(desk.DESK_REQ_RESIZE): - self.mark(s.uy, s.h) + 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 分配、不能回收:换尺寸一律重新切片,旧块留给后续复用(不做 free) + # 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 @@ -431,7 +735,7 @@ class DeskServer: s.h = m.b s.fb_w = uint32(m.a) s.fb_h = uint32(new_ch) - self.mark(s.uy, s.h) + 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) @@ -466,10 +770,10 @@ class DeskServer: self.next_z += 1 s.z_order = self.next_z self.focused = win - self.mark(s.uy, s.h) + 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 @@ -490,14 +794,13 @@ class DeskServer: while i < kn: self.push_event(self.focused, uint32(desk.DESK_EVT_KEY), int(kb[i]), 0, 0, 0) i += 1 - # --- 鼠标:内核每次返回**一个包**的相对位移。serve 一轮要跑 poll_requests + poll_input - # + render(毫秒级),若每轮只取一个包,PS/2 100Hz 的连续输入会排队累积 ⇒ 光标 - # 明显滞后。所以这里把队列里已到的包一次取完:位移全部累加,按键以最后一个包为准 - # (那才是"当前状态")。 + # --- 鼠标:内核每次返回一个包。serve 一轮耗时毫秒级,若每轮只取一个包, + # PS/2 100Hz 的连续输入会排队累积 ⇒ 光标滞后;故把已到的包一次取完, + # 位移累加、按键以最后一个包为准。--- mp: [16]uint8 mn: int = fs.read(self.mfd, &mp, uint32(16)) if mn > 0: - # 【诊断·临时】首个鼠标包:证明 内核 PS/2 中断 → devfs mouse 设备 → 本进程 这条链是通的。 + # 【诊断·临时】首个鼠标包:证明 中断→devfs→本进程 链路已通 if _mouse_logged == 0: _mouse_logged = 1 _mb: [128]char @@ -541,21 +844,21 @@ class DeskServer: in_close = 1 self.focus(hit) if in_close != 0: - # 关闭:先通知一次 CLOSE,随后把槽位置空回收 —— window.vp 的 is_active() - # 见到 used==0 即返回 0,app 的 `while is_active()` 自然退出并调 process.exit。 - # 像素缓冲不回收(arena 是 bump 分配),只放槽位 ⇒ 缓冲区永不复用, - # 因此不会出现"新窗口复用旧块、被尚未退出的老 app 写坏"的别名问题。 + # 关闭:通知一次 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(s.uy, s.h) + self.mark_win(s.uy, s.h) s.used = 0 if self.focused == hit: self.focused = -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 + # 内容区 ⇒ 按窗口内坐标把点击投给 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) @@ -573,10 +876,10 @@ class DeskServer: elif self.btn_down != 0 and self.drag_win >= 0: s2: desk.WinSlot* = self.slot(self.drag_win) if s2.used != 0: - self.mark(s2.uy, s2.h) + self.mark_win(s2.uy, s2.h) s2.ux = self.cx - self.drag_dx s2.uy = self.cy - self.drag_dy - self.mark(s2.uy, s2.h) + self.mark_win(s2.uy, s2.h) # ---------------- 主循环 ---------------- def serve(self):