diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index d0fa863..7fae84b 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -155,10 +155,15 @@ class DeskServer: focused: int next_z: int btn_down: int - close_down: int # 正在被按下的关闭键所属窗口(-1 = 无) + # 标题栏按钮的捕获态:btn_win = 窗口下标(-1 = 无),btn_kind = 0/1关闭/2最小化/3最大化 + btn_win: int + btn_kind: int drag_win: int drag_dx: int drag_dy: int + # 按住鼠标时的捕获窗口:内容区按下后,移动/松开事件始终投给它(滚动条等 + # 控件靠按住期间的 HOVER 更新位置;移出窗口也不能丢)。 + capture_win: int # 复用缓冲 msg: desk.DeskMsg evt: desk.DeskMsg @@ -202,10 +207,12 @@ class DeskServer: self.focused = -1 self.next_z = 1 self.btn_down = 0 - self.close_down = -1 + self.btn_win = -1 + self.btn_kind = 0 self.drag_win = -1 self.drag_dx = 0 self.drag_dy = 0 + self.capture_win = -1 # 预构造一行背景,渲染时整行 memcpy self.bg_row = uint32*(syscall.mmap(0, uint64(w) * uint64(4), uint64(syscall.PROT_READ | syscall.PROT_WRITE), @@ -305,6 +312,120 @@ class DeskServer: def slot(self, win: int) -> desk.WinSlot*: return desk.WinSlot*(self.shm + desk.desk_slot_off(win)) + def _content_h(self, h: int) -> int: + ch: int = h - TITLE_H - TITLE_GAP + if ch < 1: ch = 1 + return ch + + # 调整窗口外框几何。像素缓冲只在容量不足时扩容:arena 是 bump 分配, + # 容量内复用可避免反复最大化/还原把 arena 耗尽。返回 1 成功 / 0 失败。 + def _set_window_rect(self, s: desk.WinSlot*, x: int, y: int, w: int, h: int) -> int: + ch: int = self._content_h(h) + need: int = w * ch + if w <= 0 or need <= 0: return 0 + if need > int(s.fb_cap): + off: uint32 = desk.desk_arena_alloc(self.shm, uint32(need * 4)) + if off == uint32(0): return 0 + s.fb_off = off + s.fb_cap = uint32(need) + b: uint32* = uint32*(self.shm + uint64(off)) + col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50)) + for k in range(need): + b[k] = col + s.ux = x + s.uy = y + s.w = w + s.h = h + s.fb_w = uint32(w) + s.fb_h = uint32(ch) + return 1 + + def _focus_top_visible(self): + best: int = -1 + bz: int = -1 + for i in range(desk.DESK_MAX_WIN): + s: desk.WinSlot* = self.slot(i) + if s.used != 0 and s.active != 0 and s.minimized == 0: + if s.z_order > bz: + bz = s.z_order + best = i + self.focused = best + + def _close_window(self, win: int): + s: desk.WinSlot* = self.slot(win) + if s.used == 0: return + # 关闭:通知一次 CLOSE 后把槽位置空回收;window.vp 的 is_active() 见到 used==0 + # 即返回 0,app 的 while 循环自然退出。像素缓冲不回收(arena 是 bump 分配)。 + self.push_event(win, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0, 0) + self.mark_win(s.uy, s.h) + s.used = 0 + if self.focused == win: self.focused = -1 + if self.drag_win == win: self.drag_win = -1 + if self.capture_win == win: self.capture_win = -1 + if self.btn_win == win: + self.btn_win = -1 + self.btn_kind = 0 + self._focus_top_visible() + self.mark_all_windows() + self.mark_titles() + + def _minimize_window(self, win: int): + s: desk.WinSlot* = self.slot(win) + if s.used == 0 or s.minimized != 0: return + self.mark_win(s.uy, s.h) + s.minimized = 1 + if self.focused == win: self.focused = -1 + if self.drag_win == win: self.drag_win = -1 + if self.capture_win == win: self.capture_win = -1 + self._focus_top_visible() + self.mark_all_windows() + self.mark_titles() + + def _maximize_window(self, win: int): + s: desk.WinSlot* = self.slot(win) + if s.used == 0 or s.maximized != 0: return + ox: int = s.ux + oy: int = s.uy + ow: int = s.w + oh: int = s.h + mw: int = self.fb_w + mh: int = self.fb_h - TASKBAR_H + if mh < TITLE_H + TITLE_GAP + 1: return + if self._set_window_rect(s, 0, 0, mw, mh) == 0: return + s.restore_x = ox + s.restore_y = oy + s.restore_w = ow + s.restore_h = oh + s.maximized = 1 + self.mark_win(oy, oh) + self.mark_win(s.uy, s.h) + self.push_event(win, uint32(desk.DESK_EVT_RESIZE), int(s.fb_w), int(s.fb_h), 0, 0) + self.mark_all_windows() + self.mark_titles() + + def _restore_window(self, win: int) -> int: + s: desk.WinSlot* = self.slot(win) + if s.used == 0 or s.maximized == 0: return 0 + oy: int = s.uy + oh: int = s.h + if self._set_window_rect(s, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0: + return 0 + s.maximized = 0 + self.mark_win(oy, oh) + self.mark_win(s.uy, s.h) + self.push_event(win, uint32(desk.DESK_EVT_RESIZE), int(s.fb_w), int(s.fb_h), 0, 0) + self.mark_all_windows() + self.mark_titles() + return 1 + + def _toggle_maximize(self, win: int): + s: desk.WinSlot* = self.slot(win) + if s.used == 0: return + if s.maximized != 0: + self._restore_window(win) + else: + self._maximize_window(win) + # 最高 z 序(用于判定活动窗口;无可见窗口返回 -1) def _top_z(self) -> int: m: int = -1 @@ -338,26 +459,33 @@ class DeskServer: 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)) + cap_px: int = w * content_h + buf_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(cap_px) * uint32(4)) if buf_off == uint32(0): return -1 s.used = 1 s.pid = pid s.active = 1 s.minimized = 0 + s.maximized = 0 s.ux = x s.uy = y s.w = w s.h = h + s.restore_x = x + s.restore_y = y + s.restore_w = w + s.restore_h = h s.style = style s.cursor = 0 s.fb_off = buf_off + s.fb_cap = uint32(cap_px) s.fb_w = uint32(w) s.fb_h = uint32(content_h) string.memset(&s.title, 0, desk.DESK_TITLE_MAX) # 像素缓冲先清成窗口底色,避免 app 还没画就露出垃圾 buf: uint32* = uint32*(self.shm + uint64(buf_off)) col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50)) - for k in range(w * h): + for k in range(cap_px): buf[k] = col s.z_order = self.next_z self.next_z += 1 @@ -720,7 +848,9 @@ class DeskServer: 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): + def _draw_min_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, pressed: int): + if pressed != 0: + self.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70))) cx: int = bx + bw / 2 cy: int = by + bh / 2 lw: int = 5 @@ -728,7 +858,9 @@ class DeskServer: 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): + def _draw_max_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, pressed: int): + if pressed != 0: + self.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70))) cx: int = bx + bw / 2 cy: int = by + bh / 2 sz: int = 4 @@ -770,7 +902,24 @@ class DeskServer: return 1 return 0 - def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int, close_pressed: int): + # 标题栏按钮命中:1=关闭 2=最小化 3=最大化/还原(与绘制同一套几何) + def _title_btn_hit(self, s: desk.WinSlot*, x: int, y: int) -> int: + bw: int = self._close_btn_w(s.style) + bh: int = self._close_btn_h(s.style) + pad: int = self._close_btn_pad(s.style) + gap: int = 0 + if s.style == STYLE_WIN7 or s.style == STYLE_WINXP: gap = 1 + by: int = s.uy + (TITLE_H - bh) / 2 + if y < by or y >= by + bh: return 0 + close_x: int = s.ux + s.w - bw - pad + max_x: int = close_x - bw - gap + min_x: int = max_x - bw - gap + if x >= close_x and x < close_x + bw: return 1 + if x >= max_x and x < max_x + bw: return 3 + if x >= min_x and x < min_x + bw: return 2 + return 0 + + def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int, pressed_kind: int): wx: int = s.ux wy: int = s.uy ww: int = s.w @@ -788,9 +937,9 @@ class DeskServer: yend: int = y1 if yend > by + bh: yend = by + bh while yy < yend: - self._draw_close_row(close_x, by, bw, bh, yy, style, close_pressed) - self._draw_min_row(min_x, by, bw, bh, yy, style) - self._draw_max_row(max_x, by, bw, bh, yy, style) + self._draw_close_row(close_x, by, bw, bh, yy, style, 1 if pressed_kind == 1 else 0) + self._draw_min_row(min_x, by, bw, bh, yy, style, 1 if pressed_kind == 2 else 0) + self._draw_max_row(max_x, by, bw, bh, yy, style, 1 if pressed_kind == 3 else 0) yy += 1 # ---------------- 任务栏 ---------------- @@ -817,7 +966,7 @@ class DeskServer: 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) @@ -825,7 +974,13 @@ class DeskServer: if s.minimized != 0: s.minimized = 0 self.mark_win(s.uy, s.h) - self.focus(win) + 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 @@ -1069,9 +1224,9 @@ class DeskServer: title_alpha, buf, fw, fh, content_y0) yy += 1 - close_pressed: int = 0 - if self.close_down == win: close_pressed = 1 - self._draw_window_btns(s, y0, y1, th, close_pressed) + pressed_kind: int = 0 + if self.btn_win == win: pressed_kind = self.btn_kind + self._draw_window_btns(s, y0, y1, th, pressed_kind) if s.title[0] != 0: text_y: int = wy + (th - vpui.CHAR_H) / 2 if text_y + vpui.CHAR_H > y0 and text_y < y1: @@ -1194,14 +1349,8 @@ class DeskServer: 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 - if self.close_down == win: self.close_down = -1 - self.mark_all_windows() - self.mark_titles() + self._close_window(win) + return elif ty == uint32(desk.DESK_REQ_MOVE): self.mark_win(s.uy, s.h) s.ux = m.a @@ -1209,17 +1358,9 @@ class DeskServer: 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) + if self._set_window_rect(s, s.ux, s.uy, m.a, m.b) != 0: + s.maximized = 0 + self.mark_win(s.uy, s.h) elif ty == uint32(desk.DESK_REQ_TITLE): string.memcpy(&s.title, &m.title, uint64(desk.DESK_TITLE_MAX)) self.mark(s.uy, TITLE_H) @@ -1363,14 +1504,23 @@ class DeskServer: self.mark(self.cy - 1, CURSOR_BMP_H + 2) self.pcx = self.cx self.pcy = self.cy - # 按住关闭键时移出按钮:撤销按下态并置脏恢复 - if self.close_down >= 0: - cm: desk.WinSlot* = self.slot(self.close_down) - if cm.used == 0 or self._close_btn_hit(cm, self.cx, self.cy) == 0: + # 按住标题栏按钮时移出热区:撤销按下态并置脏恢复 + 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.close_down = -1 - # 悬停:把 HOVER 投给光标下那个窗口(窗口内坐标) - if self.btn_down == 0: + 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) @@ -1379,7 +1529,9 @@ class DeskServer: self.cx - hs.ux, self.cy - hs.uy - TITLE_H - TITLE_GAP, 0) if saw_press != 0: self.btn_down = 1 - self.close_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) @@ -1389,20 +1541,26 @@ class DeskServer: hit = self.hit_test(self.cx, self.cy) if hit >= 0: s: desk.WinSlot* = self.slot(hit) - # 关闭键热区复用 _close_btn_hit(与 _draw_window_btns 同一套几何) - in_close: int = self._close_btn_hit(s, self.cx, self.cy) + kind: int = self._title_btn_hit(s, self.cx, self.cy) self.focus(hit) - if in_close != 0: - # 只进入按下态:松手时才真正关窗,保证按下反馈至少能刷出一帧。 - self.close_down = hit + if kind != 0: + # 标题栏按钮:只进入按下态,松手且仍在同一按钮上才执行 + self.btn_win = hit + self.btn_kind = kind self.mark(s.uy, TITLE_H) elif self.cy < s.uy + TITLE_H: - # 标题栏(非关闭按钮)⇒ 开始拖动 - self.drag_win = hit - self.drag_dx = self.cx - s.ux - self.drag_dy = self.cy - s.uy + # 标题栏 ⇒ 拖动;最大化窗口先还原再拖 + 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: - # 内容区 ⇒ 按窗口内坐标把点击投给 app + # 内容区 ⇒ 捕获鼠标:按下后的移动/松开都投给同一窗口 + 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) @@ -1410,32 +1568,30 @@ class DeskServer: # 否则 btn_down 卡在 1,后续点击永远检测不到按下沿。 if cur == 0 and self.btn_down != 0: self.btn_down = 0 - cw_win: int = self.close_down - self.close_down = -1 - if cw_win >= 0: - cs: desk.WinSlot* = self.slot(cw_win) + bw: int = self.btn_win + bk: int = self.btn_kind + self.btn_win = -1 + self.btn_kind = 0 + if bw >= 0: + bs: desk.WinSlot* = self.slot(bw) + if bs.used != 0: + self.mark(bs.uy, TITLE_H) + if self._title_btn_hit(bs, self.cx, self.cy) == bk: + if bk == 1: + self._close_window(bw) + elif bk == 2: + self._minimize_window(bw) + elif bk == 3: + self._toggle_maximize(bw) + elif self.drag_win < 0 and self.capture_win >= 0: + # 松手:投给捕获窗口(即使光标已移出窗口) + cs: desk.WinSlot* = self.slot(self.capture_win) if cs.used != 0: - self.mark(cs.uy, TITLE_H) - if self._close_btn_hit(cs, self.cx, self.cy) != 0: - # 关闭:通知一次 CLOSE 后把槽位置空回收;window.vp 的 is_active() - # 见到 used==0 即返回 0,app 的 while 循环自然退出。 - # 像素缓冲不回收(arena 是 bump 分配),只放槽位。 - self.push_event(cw_win, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0, 0) - self.mark_win(cs.uy, cs.h) - cs.used = 0 - if self.focused == cw_win: self.focused = -1 - if self.drag_win == cw_win: self.drag_win = -1 - self.mark_all_windows() - self.mark_titles() - elif 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), + self.push_event(self.capture_win, uint32(desk.DESK_EVT_MOUSE), desk.DESK_MOUSE_RELEASE, - self.cx - rs.ux, self.cy - rs.uy - TITLE_H - TITLE_GAP, 0) + self.cx - cs.ux, self.cy - cs.uy - TITLE_H - TITLE_GAP, 0) self.drag_win = -1 + self.capture_win = -1 if self.btn_down != 0 and self.drag_win >= 0: s2: desk.WinSlot* = self.slot(self.drag_win) if s2.used != 0: diff --git a/VKernel/Kernel/drivers/devfs/devfs.vp b/VKernel/Kernel/drivers/devfs/devfs.vp index 90f47b7..bba1e95 100644 --- a/VKernel/Kernel/drivers/devfs/devfs.vp +++ b/VKernel/Kernel/drivers/devfs/devfs.vp @@ -575,7 +575,7 @@ def devfs_register_fb0(fb: rawptr, width: int, height: int, pitch: int, format: # 这里用 devfs 设备 + mmap 达到同样效果)。 # ============================================================================ # 共享区大小:vpsdk/desk.vp 里有一份同名同值的镜像(用户在 mmap 时必须先知道长度)。 -define DESK_SHM_SIZE = 8388608 +define DESK_SHM_SIZE = 33554432 virtual class shm_device(devfs_file): shm_phys: uint64 diff --git a/vpsdk/desk.vp b/vpsdk/desk.vp index b458175..bb0bf15 100644 --- a/vpsdk/desk.vp +++ b/vpsdk/desk.vp @@ -26,7 +26,8 @@ from stdint import * # 必须与内核 VKernel/Kernel/drivers/devfs/devfs.vp 的 DESK_SHM_SIZE 一致 # (mmap 时用户必须先给出长度,所以这里不能靠"问内核")。 -define DESK_SHM_SIZE = 8388608 +# 32MB:1920×1080 全屏窗口的内容缓冲约 7.3MB,最大化需要足够 arena。 +define DESK_SHM_SIZE = 33554432 define DESK_MAGIC = 0x4B534544 # 'DESK' define DESK_VERSION = 1 @@ -86,17 +87,23 @@ class WinSlot: pid: int active: int minimized: int + maximized: int z_order: int ux: int uy: int w: int h: int + # 最大化前的还原几何 + restore_x: int + restore_y: int + restore_w: int + restore_h: int style: int cursor: int fb_off: uint32 fb_w: uint32 fb_h: uint32 - _pad2: uint32 + fb_cap: uint32 # 像素缓冲容量(以像素计;扩容后复用,避免 arena 泄漏) title: [DESK_TITLE_MAX]char