feat(window): 标题栏 hover、最大化图标、边缘缩放与控件光标
1) 标题栏按钮 hover:min/max 背景按 alpha 压暗,close 变红、图标变白; 按下态更深。新增 hover_btn_win/kind,仅在变化时重画标题栏。 2) 最大化图标改为单线方框(上边框不再两行加厚);最大化后显示 两个错位重叠方框作为“还原”。 3) 窗口边缘 5px 热区支持拖拽缩放:左/右 ↔、上/下 ↕、四角斜向; 缩放中实时更新几何与内容缓冲(按 1.5 倍扩容,避免 arena 抖动), 并向 app 发 RESIZE。 4) 新增 DESK_REQ_CURSOR / window.set_cursor_type 真正下发光标类型; explorer 按窗口 cursor + 边缘热区选择箭头 / I 形 / 手指 / 缩放光标。 5) vpui.Tk 在 HOVER 时按最上层控件设置光标:Entry/Text → I 形, Button → 手指;Entry 自己的光标请求移除,避免与集中逻辑冲突。
This commit is contained in:
1 parent
6ecd891f64
commit
b9efc30d71
4 files changed
+422
-41
No files matched your search
+391
-33
@@ -49,6 +49,18 @@ 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
|
||||
define SHADOW_LEFT = 8
|
||||
@@ -78,6 +90,120 @@ _CURSOR_ARROW: [256]uint8 = [
|
||||
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:
|
||||
@@ -164,6 +290,18 @@ class DeskServer:
|
||||
# 按住鼠标时的捕获窗口:内容区按下后,移动/松开事件始终投给它(滚动条等
|
||||
# 控件靠按住期间的 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
|
||||
# 标题栏按钮的 hover 状态(用于变色和刷新)
|
||||
hover_btn_win: int
|
||||
hover_btn_kind: int
|
||||
# 复用缓冲
|
||||
msg: desk.DeskMsg
|
||||
evt: desk.DeskMsg
|
||||
@@ -213,6 +351,16 @@ class DeskServer:
|
||||
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
|
||||
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),
|
||||
@@ -291,6 +439,18 @@ class DeskServer:
|
||||
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
|
||||
@@ -317,6 +477,34 @@ class DeskServer:
|
||||
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*, x: int, y: int, w: int, h: int) -> int:
|
||||
@@ -324,13 +512,18 @@ class DeskServer:
|
||||
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))
|
||||
# 交互缩放时按 1.5 倍增长,避免拖拽过程中每加几像素就切一块新 arena;
|
||||
# 最大化的一次性大跨度仍会直接按 need 分配。
|
||||
alloc_px: int = need
|
||||
grow: int = int(s.fb_cap) + int(s.fb_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
|
||||
s.fb_off = off
|
||||
s.fb_cap = uint32(need)
|
||||
s.fb_cap = uint32(alloc_px)
|
||||
b: uint32* = uint32*(self.shm + uint64(off))
|
||||
col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50))
|
||||
for k in range(need):
|
||||
for k in range(alloc_px):
|
||||
b[k] = col
|
||||
s.ux = x
|
||||
s.uy = y
|
||||
@@ -340,6 +533,45 @@ class DeskServer:
|
||||
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, 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
|
||||
@@ -362,6 +594,7 @@ class DeskServer:
|
||||
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
|
||||
@@ -377,6 +610,7 @@ class DeskServer:
|
||||
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()
|
||||
@@ -823,7 +1057,7 @@ class DeskServer:
|
||||
if hi > lo:
|
||||
self.row_fill(bx + lo, yy, hi - lo, col)
|
||||
|
||||
def _draw_close_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, pressed: int):
|
||||
def _draw_close_row(self, 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
|
||||
@@ -831,50 +1065,73 @@ class DeskServer:
|
||||
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 pressed != 0: base_col = self.rgb(uint32(20), uint32(20), uint32(45))
|
||||
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 or style == STYLE_WINXP:
|
||||
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))
|
||||
self._round_fill_row(bx, by, bw, bh, yy, base_col2, 3)
|
||||
x_col = self.rgb(uint32(255), uint32(255), uint32(255))
|
||||
else:
|
||||
x_col = self.rgb(uint32(150), uint32(150), uint32(150))
|
||||
# WIN10/WIN11 平时无底色,按下时补一块深灰底做按压反馈
|
||||
if pressed != 0:
|
||||
self.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70)))
|
||||
# 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))
|
||||
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)
|
||||
|
||||
def _draw_min_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, pressed: int):
|
||||
# 方框描边的一行(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.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70)))
|
||||
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, self.rgb(uint32(180), uint32(180), uint32(180)))
|
||||
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, pressed: int):
|
||||
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.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70)))
|
||||
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
|
||||
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)
|
||||
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.row_fill(cx - sz, yy, 1, col)
|
||||
self.row_fill(cx + sz, yy, 1, col)
|
||||
# 单方框;上边框只画一行(不再加厚)
|
||||
self._draw_square_row(cx - sz, cy - sz, sz * 2, yy, col)
|
||||
|
||||
# 关闭键宽/高/右内边距(按样式;poll_input 热区复用这三者)
|
||||
def _close_btn_w(self, style: int) -> int:
|
||||
@@ -921,7 +1178,7 @@ class DeskServer:
|
||||
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):
|
||||
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
|
||||
@@ -939,9 +1196,13 @@ 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, 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)
|
||||
self._draw_close_row(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
|
||||
|
||||
# ---------------- 任务栏 ----------------
|
||||
@@ -1233,7 +1494,9 @@ class DeskServer:
|
||||
|
||||
pressed_kind: int = 0
|
||||
if self.btn_win == win: pressed_kind = self.btn_kind
|
||||
self._draw_window_btns(s, y0, y1, th, pressed_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:
|
||||
@@ -1241,10 +1504,53 @@ class DeskServer:
|
||||
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。
|
||||
cy0: int = self.cy
|
||||
cy1: int = self.cy + CURSOR_BMP_H
|
||||
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
|
||||
@@ -1252,15 +1558,15 @@ class DeskServer:
|
||||
row: int = (y - cy0) * CURSOR_BMP_W
|
||||
col: int = 0
|
||||
while col < CURSOR_BMP_W:
|
||||
v: uint8 = _CURSOR_ARROW[row + col]
|
||||
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 _CURSOR_ARROW[row + col] == v:
|
||||
while col < CURSOR_BMP_W and self._cursor_value(kind, row + col) == v:
|
||||
col += 1
|
||||
px: int = self.cx + start
|
||||
px: int = self.cx + dx + start
|
||||
seg_w: int = col - start
|
||||
if px + seg_w > 0 and px < self.fb_w:
|
||||
if px < 0:
|
||||
@@ -1371,6 +1677,12 @@ class DeskServer:
|
||||
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):
|
||||
# 【临时诊断】首条 FLUSH:证明 app 的"请合成"真的到达了合成器
|
||||
if _flush_cnt == 0:
|
||||
@@ -1509,8 +1821,8 @@ class DeskServer:
|
||||
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._mark_cursor_at(self.pcy)
|
||||
self._mark_cursor_at(self.cy)
|
||||
self.pcx = self.cx
|
||||
self.pcy = self.cy
|
||||
# 按住标题栏按钮时移出热区:撤销按下态并置脏恢复
|
||||
@@ -1550,9 +1862,20 @@ class DeskServer:
|
||||
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 kind != 0:
|
||||
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
|
||||
@@ -1592,6 +1915,15 @@ class DeskServer:
|
||||
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)
|
||||
@@ -1601,13 +1933,39 @@ class DeskServer:
|
||||
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:
|
||||
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):
|
||||
|
||||
@@ -53,6 +53,7 @@ define DESK_REQ_RESIZE = 4 # a=w b=h
|
||||
define DESK_REQ_TITLE = 5 # title 在数据区
|
||||
define DESK_REQ_FLUSH = 6 # 像素已写,请求合成该窗口
|
||||
define DESK_REQ_ACTIVATE = 7 # 置顶并聚焦
|
||||
define DESK_REQ_CURSOR = 8 # a=cursor type(app 请求窗口内光标形状)
|
||||
# 消息类型:事件(explorer → app)
|
||||
define DESK_EVT_CREATED = 101 # a=win_id(失败为 -1)
|
||||
define DESK_EVT_CLOSE = 102 # 用户点了关闭按钮,app 应当退出
|
||||
|
||||
+24
-6
@@ -828,20 +828,14 @@ virtual class Entry(Widget):
|
||||
if self.contains(ex, ey):
|
||||
if not self.hovered:
|
||||
self.hovered = 1
|
||||
tk: Tk* = self.tk
|
||||
window.set_cursor_type(tk.win_id, window.CURSOR_IBEAM)
|
||||
return 1
|
||||
else:
|
||||
if self.hovered:
|
||||
self.hovered = 0
|
||||
tk2: Tk* = self.tk
|
||||
window.set_cursor_type(tk2.win_id, window.CURSOR_DEFAULT)
|
||||
return 1
|
||||
elif etype == window.EVENT_TYPE_HOVER_LEAVE:
|
||||
if self.hovered:
|
||||
self.hovered = 0
|
||||
tk3: Tk* = self.tk
|
||||
window.set_cursor_type(tk3.win_id, window.CURSOR_DEFAULT)
|
||||
return 1
|
||||
elif etype == window.EVENT_TYPE_CLICK:
|
||||
if self.contains(ex, ey):
|
||||
@@ -1550,6 +1544,7 @@ virtual class Tk(Wm, Misc):
|
||||
widgets: [MAX_WIDGETS]Widget*
|
||||
widget_count: int
|
||||
focused_widget: Widget*
|
||||
hovered_widget: Widget*
|
||||
_win_x: int
|
||||
_win_y: int
|
||||
_title: [32]char
|
||||
@@ -1569,6 +1564,7 @@ virtual class Tk(Wm, Misc):
|
||||
self.tk = Tk*(self)
|
||||
self.widget_count = 0
|
||||
self.focused_widget = None
|
||||
self.hovered_widget = None
|
||||
self.win_id = -1
|
||||
self._win_x = 0
|
||||
self._win_y = 0
|
||||
@@ -1923,6 +1919,27 @@ virtual class Tk(Wm, Misc):
|
||||
evt.x = vk
|
||||
return 1
|
||||
|
||||
# 指针下的最上层控件决定光标形状:Entry/Text → I 形,Button → 手指。
|
||||
# 与控件自己的 hover 视觉解耦(后者仍由 dispatch_event 维护)。
|
||||
def _update_cursor_for_hover(self, ex: int, ey: int):
|
||||
hw: Widget* = None
|
||||
i: int = self.widget_count - 1
|
||||
while i >= 0:
|
||||
w: Widget* = self.widgets[i]
|
||||
if w.visible and w.contains(ex, ey):
|
||||
hw = w
|
||||
break
|
||||
i -= 1
|
||||
if hw == self.hovered_widget: return
|
||||
self.hovered_widget = hw
|
||||
ct: int = window.CURSOR_DEFAULT
|
||||
if hw is not None:
|
||||
if hw.widget_type == WIDGET_ENTRY or hw.widget_type == WIDGET_TEXT:
|
||||
ct = window.CURSOR_IBEAM
|
||||
elif hw.widget_type == WIDGET_BUTTON:
|
||||
ct = window.CURSOR_HAND
|
||||
window.set_cursor_type(self.win_id, ct)
|
||||
|
||||
def mainloop(self):
|
||||
self.redraw()
|
||||
evt: window.InputEvent
|
||||
@@ -1956,6 +1973,7 @@ virtual class Tk(Wm, Misc):
|
||||
if handled: need_redraw = 1
|
||||
else:
|
||||
if etype == window.EVENT_TYPE_HOVER:
|
||||
self._update_cursor_for_hover(evt.x, evt.y)
|
||||
i: int = self.widget_count - 1
|
||||
while i >= 0:
|
||||
w: Widget* = self.widgets[i]
|
||||
|
||||
+6
-2
@@ -30,6 +30,8 @@ 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 VK_LEFT = -1
|
||||
define VK_RIGHT = -2
|
||||
@@ -241,8 +243,10 @@ def poll_event(evt: InputEvent*) -> int:
|
||||
return 0
|
||||
|
||||
def set_cursor_type(win_id: int, cursor_t: int):
|
||||
# TODO(S3c-2):光标形状目前由 explorer 统一画成方框,尚无按窗口切换的请求。
|
||||
pass
|
||||
"""请求窗口内光标形状(输入框 I 形、按钮手指、窗口边缘缩放等)。"""
|
||||
if _connect() != 0: return
|
||||
if win_id < 0 or win_id >= desk.DESK_MAX_WIN: return
|
||||
_send(uint32(desk.DESK_REQ_CURSOR), win_id, cursor_t, 0, 0, 0, None)
|
||||
|
||||
def set_title(win_id: int, title: const str) -> int:
|
||||
return _send(uint32(desk.DESK_REQ_TITLE), win_id, 0, 0, 0, 0, title)
|
||||
|
||||
Reference in new issue
Block a user