Files
ViperOS/vpsdk/vpui.vp
T
TermiNexus 266e6fbcfb feat(keyboard): 恢复 Shift/CapsLock 与方向键等特殊键
旧内核桌面 desktop.vp 里有完整的键盘解码(修饰键状态、特殊键映射、重复),S3e 下沉后丢了,只剩一个简化 scancode_to_ascii(shift/caps 字段从未赋值、方向键返回 0 被丢)。① 内核 keyboard.vp:新增解码层 _decode_scancode + 输出队列,跟踪 Shift(0x2A/0x36)/CapsLock(0x3A)/Ctrl/Alt,Shift 输出大写与上档符号、Caps 只翻转字母区;E0 前缀的独立方向键/Home/End/Delete/PageUp/PageDown/Insert 输出 ANSI 序列(ESC[A/B/C/D、ESC[H/F、ESC[3~/5~/6~/2~);释放码只清状态不产字符。devfs 的 ASCII 字节流契约不变。② vpui:Tk 新增 _ansi_state/_ansi_param 与 _fold_ansi_key(),在 KEY 分发前折叠 ANSI 序列,映射到既有 VK_* 语义(Text/Entry 里那些分支不再是死代码)。Trim:Insert 无 VK_INSERT 定义、被吞掉;typematic 重复未恢复;USB 键盘未涉及。
2026-10-17 13:01:56 +08:00

1879 lines
78 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import vpsdk.window as window
import vpsdk.process as process
import vpsdk.devfs as devfs
import vpsdk.stdlib as stdlib
import string
define TITLE_H = 32
define MAX_WIDGETS = 24
define CHAR_W = 8
define CHAR_H = 16
define WIDGET_LABEL = 1
define WIDGET_BUTTON = 2
define WIDGET_ENTRY = 3
define WIDGET_TEXT = 4
define WIDGET_SCROLLBAR = 5
define WIDGET_FBCANVAS = 6
define WIDGET_FRAME = 7
define VERTICAL = 1
define HORIZONTAL = 2
define TEXT_MAX_LINES = 200
define PACK_TOP = 1
define PACK_BOTTOM = 2
define PACK_LEFT = 3
define PACK_RIGHT = 4
define FILL_NONE = 0
define FILL_X = 1
define FILL_Y = 2
define FILL_BOTH = 3
define STICKY_NONE = 0
define STICKY_W = 1
define STICKY_E = 2
define STICKY_N = 4
define STICKY_S = 8
define STICKY_NSEW = 15
_g_wl_type: [MAX_WIDGETS]int
_g_wl_side: [MAX_WIDGETS]int
_g_wl_fill: [MAX_WIDGETS]int
_g_wl_expand: [MAX_WIDGETS]int
_g_wl_padx: [MAX_WIDGETS]int
_g_wl_pady: [MAX_WIDGETS]int
_g_wl_row: [MAX_WIDGETS]int
_g_wl_col: [MAX_WIDGETS]int
_g_wl_rowspan: [MAX_WIDGETS]int
_g_wl_colspan: [MAX_WIDGETS]int
_g_wl_sticky: [MAX_WIDGETS]int
_g_layout_init: int
_hex_tbl: [17]char = "0123456789ABCDEF"
_hex_buf: [20]char = "0000000000000000000"
def _serial_puts(s: const str):
# S4:SERIAL_PUTS 私有 syscall 已删除,改走 /dev/serial0 设备
devfs.serial_puts(s)
def _ptr_to_hex(val: uint64, buf: rawptr):
buf[0] = 48
buf[1] = 120
i: int = 15
while i >= 0:
nibble: int = int(val & uint64(0xF))
buf[2 + i] = _hex_tbl[nibble]
val = val >> 4
i -= 1
buf[18] = 0
def _fill_rect(fb: uint32*, fw: int, fh: int, x1: int, y1: int, w: int, h: int, color: uint32):
if not fb: return
if x1 < 0: w += x1; x1 = 0
if y1 < 0: h += y1; y1 = 0
if x1 + w > fw: w = fw - x1
if y1 + h > fh: h = fh - y1
if w <= 0 or h <= 0: return
# 逐行批量填充(string.memset32 → rep stosd):整窗填充从"每个像素一次带边界判断的写"
# 降成"每行一次 rep stosd"。app 每帧都要把整窗填一遍背景色,逐像素在 QEMU 下是主要热点。
py: int = y1
while py < y1 + h:
string.memset32(rawptr(uint64(fb) + uint64((py * fw + x1) * 4)), color, uint64(w))
py += 1
def _draw_hline(fb: uint32*, fw: int, fh: int, x1: int, y: int, w: int, color: uint32):
if not fb: return
if y < 0 or y >= fh: return
for px in range(x1, x1 + w):
if px >= 0 and px < fw:
fb[y * fw + px] = color
def _draw_vline(fb: uint32*, fw: int, fh: int, x: int, y1: int, h: int, color: uint32):
if not fb: return
if x < 0 or x >= fw: return
for py in range(y1, y1 + h):
if py >= 0 and py < fh:
fb[py * fw + x] = color
def _lighten(color: uint32, amount: int) -> uint32:
r: int = int(color & 0xFF)
g: int = int((color >> 8) & 0xFF)
b: int = int((color >> 16) & 0xFF)
r = r + ((255 - r) * amount) / 256
g = g + ((255 - g) * amount) / 256
b = b + ((255 - b) * amount) / 256
return uint32(r | (g << 8) | (b << 16))
def _darken(color: uint32, amount: int) -> uint32:
r: int = int(color & 0xFF)
g: int = int((color >> 8) & 0xFF)
b: int = int((color >> 16) & 0xFF)
r = r - (r * amount) / 256
g = g - (g * amount) / 256
b = b - (b * amount) / 256
return uint32(r | (g << 8) | (b << 16))
font: [256][16]uint8 = [
[0x00,0x00,0x00,0x00,0x00,0x40,0x55,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x02,0x56,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x42,0x56,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x55,0x55,0x57,0x71,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x57,0x54,0x57,0x71,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x57,0x54,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x57,0x51,0x52,0x72,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x57,0x55,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x57,0x55,0x57,0x71,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x56,0x51,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x54,0x54,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x50,0x50,0x57,0x74,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x51,0x51,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x57,0x55,0x57,0x74,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x53,0x54,0x56,0x74,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x37,0x15,0x15,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x32,0x16,0x12,0x12,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x20,0x6E,0x22,0x2E,0x28,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x37,0x11,0x13,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x35,0x15,0x17,0x11,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x37,0x14,0x17,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x37,0x14,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x37,0x11,0x12,0x12,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x37,0x15,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x37,0x15,0x17,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x36,0x11,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x34,0x14,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x30,0x10,0x17,0x14,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x31,0x11,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x37,0x15,0x17,0x14,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x33,0x14,0x16,0x14,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00],
[0x6C,0x6C,0x6C,0x6C,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x36,0x36,0x36,0x7F,0x7F,0x36,0x36,0x36,0x7F,0x7F,0x36,0x36,0x36,0x00,0x00,0x00],
[0x0C,0x0C,0x3E,0x7F,0x68,0x68,0x3E,0x0B,0x0B,0x7F,0x3E,0x18,0x18,0x00,0x00,0x00],
[0x60,0x60,0x66,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x66,0x06,0x06,0x00,0x00,0x00],
[0x38,0x6C,0x6C,0x6C,0x6C,0x6C,0x38,0x6C,0x6D,0x66,0x66,0x6F,0x3B,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x1C,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x1C,0x00,0x00],
[0x38,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x38,0x00,0x00],
[0x00,0x00,0x18,0x18,0x7E,0x3C,0x3C,0x3C,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x18,0x18,0x18,0x7E,0x7E,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x30,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00],
[0x00,0x00,0x06,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x60,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x66,0x6E,0x6E,0x7E,0x76,0x76,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x18,0x38,0x78,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x06,0x06,0x0E,0x1C,0x38,0x70,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x06,0x06,0x1C,0x1C,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x0C,0x0C,0x1C,0x1C,0x3C,0x2C,0x6C,0x7E,0x7E,0x0C,0x0C,0x0C,0x0C,0x00,0x00,0x00],
[0x7E,0x7E,0x60,0x60,0x7C,0x7E,0x06,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x7E,0x7E,0x06,0x06,0x0C,0x0C,0x18,0x18,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x66,0x66,0x7E,0x3C,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x30,0x00],
[0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0x60,0x30,0x18,0x0C,0x06,0x02,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x40,0x60,0x30,0x18,0x0C,0x06,0x06,0x0C,0x18,0x30,0x60,0x40,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x06,0x0C,0x0C,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x66,0x6E,0x6A,0x6A,0x6A,0x6E,0x60,0x60,0x7C,0x3C,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00],
[0x7C,0x7E,0x66,0x66,0x66,0x7C,0x7C,0x66,0x66,0x66,0x66,0x7E,0x7C,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x78,0x7C,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0x7C,0x78,0x00,0x00,0x00],
[0x7E,0x7E,0x60,0x60,0x60,0x7C,0x7C,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00],
[0x7E,0x7E,0x60,0x60,0x60,0x7C,0x7C,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x60,0x60,0x60,0x6E,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x66,0x66,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00],
[0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00],
[0x3E,0x3E,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x6C,0x7C,0x38,0x00,0x00,0x00],
[0x66,0x66,0x66,0x6C,0x6C,0x78,0x78,0x78,0x6C,0x6C,0x66,0x66,0x66,0x00,0x00,0x00],
[0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00],
[0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00],
[0x66,0x66,0x66,0x66,0x76,0x76,0x7E,0x6E,0x6E,0x66,0x66,0x66,0x66,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x6A,0x6A,0x6C,0x7E,0x36,0x00,0x00,0x00],
[0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x6C,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00],
[0x3C,0x7E,0x66,0x60,0x60,0x78,0x3C,0x0E,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00],
[0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x18,0x00,0x00,0x00],
[0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x77,0x77,0x22,0x00,0x00,0x00],
[0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x3C,0x3C,0x66,0x66,0x66,0x66,0x00,0x00,0x00],
[0x66,0x66,0x66,0x66,0x7E,0x3C,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00],
[0x7E,0x7E,0x06,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00],
[0x78,0x78,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x78,0x78,0x00,0x00],
[0xC0,0xC0,0x60,0x60,0x30,0x30,0x18,0x0C,0x0C,0x06,0x06,0x03,0x03,0x00,0x00,0x00],
[0x1E,0x1E,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x1E,0x1E,0x00,0x00],
[0x3C,0x7E,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00],
[0x30,0x38,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x3C,0x3E,0x06,0x3E,0x7E,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00],
[0x60,0x60,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x7C,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x60,0x60,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x06,0x06,0x06,0x06,0x3E,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x7E,0x7E,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x1C,0x3E,0x30,0x30,0x30,0x7C,0x7C,0x30,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x3E,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x3E,0x3C,0x00],
[0x60,0x60,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00],
[0x18,0x18,0x00,0x00,0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00],
[0x0C,0x0C,0x00,0x00,0x3C,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x7C,0x78,0x00],
[0x60,0x60,0x60,0x60,0x66,0x66,0x6C,0x78,0x78,0x6C,0x6C,0x66,0x66,0x00,0x00,0x00],
[0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x36,0x7F,0x6B,0x6B,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x60,0x60,0x60,0x60,0x00],
[0x00,0x00,0x00,0x00,0x3E,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x07,0x07,0x00],
[0x00,0x00,0x00,0x00,0x6C,0x7E,0x76,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x3C,0x7E,0x62,0x70,0x3C,0x0E,0x46,0x7E,0x3C,0x00,0x00,0x00],
[0x30,0x30,0x30,0x30,0x7C,0x7C,0x30,0x30,0x30,0x30,0x30,0x3C,0x1C,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x18,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x7F,0x36,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x66,0x66,0x3C,0x3C,0x18,0x3C,0x3C,0x66,0x66,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x7E,0x7C,0x00],
[0x00,0x00,0x00,0x00,0x7E,0x7E,0x0C,0x0C,0x18,0x30,0x30,0x7E,0x7E,0x00,0x00,0x00],
[0x0E,0x1E,0x18,0x18,0x18,0x18,0x30,0x30,0x18,0x18,0x18,0x18,0x1E,0x0E,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00],
[0x70,0x78,0x18,0x18,0x18,0x18,0x0C,0x0C,0x18,0x18,0x18,0x18,0x78,0x70,0x00,0x00],
[0x31,0x79,0x6B,0x4F,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x13,0x24,0x26,0x24,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
]
def _draw_char(buf: uint32*, bw: int, bh: int, x: int, y: int, ch: char, color: uint32):
if not buf: return
if bw <= 0 or bh <= 0: return
ci: uint8 = uint8(ch)
if ci >= 128: ci = 0
fy: int = 0
while fy < CHAR_H:
if y + fy < 0 or y + fy >= bh:
fy += 1
continue
row: uint8 = font[ci][fy]
if row == 0:
fy += 1
continue
for fx in range(CHAR_W):
if row & (0x80 >> fx):
px: int = x + fx
if px >= 0 and px < bw:
buf[(y + fy) * bw + px] = color
fy += 1
def _draw_text(buf: uint32*, bw: int, bh: int, x: int, y: int, s: const str, color: uint32):
if not buf: return
if bw <= 0 or bh <= 0: return
cx: int = x
cy: int = y
i: int = 0
while True:
ch: char = s[i]
if ch == 0: break
if ch == 10:
cy += 16
cx = x
else:
_draw_char(buf, bw, bh, cx, cy, ch, color)
cx += 8
i += 1
# ============================================================================
# tkinter 风格的抽象基类层次(仿 tkinter 原始抽象)
#
# Misc(mixin:master/tk 公共状态 + winfo_master/toplevel、bind 等公共方法)
# ├─ Tk(Wm, Misc) 主窗口类(Wm 声明 WM 协议面,Tk 提供落地实现)
# └─ BaseWidget(Misc)
# └─ Widget(BaseWidget) 所有普通控件的抽象基类(Button/Label/Entry… 都继承 Widget)
#
# 字段模型(与 Test/MultiInheritTest 一致):父类字段被**展平**进子类,子类自己的方法可
# 直接访问全部展平字段;字段偏移按"定义该方法的那一层"自己的展平布局计算(该测试里
# ColoredCircle(ShapeM, ColorM) = (ptr, i32, f64):偏移 0 是唯一的共享 vptr,各基类字段依次
# 跟在后面,虚表被合并)。由此有三条硬约束:
# ① Misc 的字段必须在所有派生类里落到同一偏移 —— 偏移 0 被共享 vptr 占用,Misc 的字段
# 从 8 起,只要它前面没有"带字段的基类"就成立;
# ② 因此 `Wm`(Tk 的最左基类)必须**无字段**,否则会把 Misc 的字段整体后移,而 Misc 自己
# 的方法仍按自身布局取偏移 ⇒ 静默错位;
# ③ 基类方法里**不要写 `self.<虚拟方法>()` 的自我分派**:多继承下非最左基类的方法在派生
# 类的合并虚表里整体后移,而方法体内按"定义类自己的虚表"取索引 ⇒ 会指到别的槽位。
# 所以 winfo_*/title 这类"需要看具体对象状态"的方法一律在 Misc / Wm 里只做声明,由
# Tk / Widget 覆写实现;外部按具体类型调用即可正常走虚表。
# ============================================================================
# ---- bind 回调注册表:按 (对象, 事件类型) 记录回调。
# tkinter 用 <Button-1> 这类字符串作为 sequence,这里改用 window.EVENT_TYPE_* 整型
# (本运行时的事件类型就是这些整型,比较字符串在 VPC 模型下既慢又易错)。
define MAX_BINDINGS = 64
_bind_obj: [MAX_BINDINGS]Misc* = [None]
_bind_seq: [MAX_BINDINGS]int = [0]
_bind_cb: [MAX_BINDINGS]rawptr = [None]
def _bind_add(obj: Misc*, seq: int, func: rawptr) -> int:
i: int = 0
while i < MAX_BINDINGS:
if _bind_obj[i] is None:
_bind_obj[i] = obj
_bind_seq[i] = seq
_bind_cb[i] = func
return i
i += 1
return -1
def _bind_remove(obj: Misc*, seq: int) -> int:
i: int = 0
while i < MAX_BINDINGS:
if _bind_obj[i] is obj and _bind_seq[i] == seq:
_bind_obj[i] = None
_bind_cb[i] = None
return 0
i += 1
return -1
def _bind_invoke(obj: Misc*, seq: int, evt: window.InputEvent*) -> int:
i: int = 0
while i < MAX_BINDINGS:
if _bind_obj[i] is obj and _bind_seq[i] == seq:
cb: rawptr = _bind_cb[i]
if cb is None: return 0
# 与 Button 回调同一约定:rawptr 是 VPC 生成的闭包对象 {fn, env},
# fn 期望 rdi = env ⇒ 必须取 [cb+8] 传 rdi、再由 [cb] 调用。
__asm__(f"""push r12
push r13
mov r13, {asminp(cb, ASM_DESCR.REG_ANY)}
mov rdi, qword ptr [r13 + 8]
mov r12, rsp
and rsp, -16
call qword ptr [r13]
mov rsp, r12
pop r13
pop r12""",
op=[ASM_DESCR.CLOBBER_MEMORY,
ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX,
ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI,
ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
return 1
i += 1
return 0
# ---- Misc:Tk 与所有控件的公共 mixin ----
# 只放"所有 Tk 对象都有的东西":公共状态(master/tk)、公共方法(winfo_master/toplevel、bind),
# 以及需要各具体类自己回答的查询(winfo_x/… 这里只声明,由 Tk / Widget 覆写)。
# 约束:这里**不做 `self.<虚拟方法>()` 的自我分派**。多继承下非最左基类的方法在派生类的
# 合并虚表里会整体后移,而方法体内按"定义类自己的虚表"取索引 ⇒ 会指到别的槽位。
# 因此查询一律由具体类覆写、由外部按具体类型调用;Misc 自身只用自己声明的字段。
virtual class Misc:
master: Misc* # 父容器;主窗口为 None
tk: Tk* # 所属顶层窗口;主窗口指向自身
# 公共状态初始化(各具体类的 __init__ 调用;不依赖基类 __init__ 链)
def _misc_setup(self, master: Misc* = None):
self.master = master
self.tk = None
if master is not None:
self.tk = master.tk
# ---- winfo 系(tkinter Misc.winfo_* 的对应物)----
def winfo_master(self) -> Misc*:
return self.master
def winfo_toplevel(self) -> Tk*:
return self.tk
# ---- bind 系(回调在 BaseWidget.dispatch_event 里派发)----
def bind(self, seq: int, func: rawptr) -> int:
return _bind_add(Misc*(self), seq, func)
def unbind(self, seq: int) -> int:
return _bind_remove(Misc*(self), seq)
def _invoke_binding(self, seq: int, evt: window.InputEvent*) -> int:
return _bind_invoke(Misc*(self), seq, evt)
# ---- 查询/生命周期:抽象,由 Tk / Widget 覆写 ----
def winfo_exists(self) -> int:
return 0
def winfo_ismapped(self) -> int:
return 0
def winfo_x(self) -> int:
return 0
def winfo_y(self) -> int:
return 0
def winfo_width(self) -> int:
return 0
def winfo_height(self) -> int:
return 0
def destroy(self):
pass
def update(self):
pass
# ---- Wm:顶层窗口的"窗口管理器协议"(tkinter Wm 的对应物)----
# 无字段(见文件头的布局约束),这里只**声明**协议面,落地实现由持有窗口状态的 Tk 覆写。
# 同样不做自我分派(理由见 Misc 上方注释)。
virtual class Wm:
def title(self, text: str):
pass
def geometry(self, x: int, y: int, w: int, h: int):
pass
def resizable(self, rw: int, rh: int):
pass
# ---- BaseWidget:控件公共部分(tkinter BaseWidget 的对应物)----
virtual class BaseWidget(Misc):
# 事件派发:先跑 bind 注册的回调,再交给控件自己的 handle_event。
# 放在基类是为了让 Tk 的事件循环只调这一个入口,各控件无需关心 bind。
def dispatch_event(self, evt: window.InputEvent*) -> int:
hit: int = self._invoke_binding(int(evt.type), evt)
handled: int = self.handle_event(evt)
if hit or handled:
return 1
return 0
# 控件自身的事件处理(抽象,由具体控件覆写)
def handle_event(self, evt: window.InputEvent*) -> int:
return 0
# 抽象:具体控件在 Widget 层覆写(visible 是 Widget 的字段,不能在这里访问)
def destroy(self):
pass
def _register_widget(master: Misc*, widget: Widget*) -> int:
tk: Tk* = master.tk
if tk is None: return -1
idx: int = tk.widget_count
if idx < MAX_WIDGETS:
tk.widgets[idx] = widget
tk.widget_count = idx + 1
return idx
virtual class Widget(BaseWidget):
x: int
y: int
w: int
h: int
visible: int
widget_type: int
focused: int
hovered: int
def destroy(self):
self.visible = 0
# ---- Misc 查询的覆写:控件用自己的几何信息回答 winfo_* ----
def winfo_exists(self) -> int:
return self.visible
def winfo_ismapped(self) -> int:
return self.visible
def winfo_x(self) -> int:
return self.x
def winfo_y(self) -> int:
return self.y
def winfo_width(self) -> int:
return self.w
def winfo_height(self) -> int:
return self.h
def draw(self, fb: uint32*, fw: int, fh: int):
pass
def handle_event(self, evt: window.InputEvent*) -> int:
return 0
def contains(self, px: int, py: int) -> int:
if px >= self.x and px < self.x + self.w and py >= self.y and py < self.y + self.h:
return 1
return 0
def place(self, x: int = 0, y: int = 0, w: int = 0, h: int = 0):
self.x = x
self.y = y
if w > 0:
self.w = w
if h > 0:
self.h = h
# 容器中坐标叠加:master 不是顶层窗口(即 Frame 这类容器)时,子控件先按 Tk 的口径算位置,
# 再叠加容器自身原点 ⇒ 视觉上落在容器内部。本运行时的 pack/grid 是 Tk 级、按全局下标走,
# 所以这里只做"原点叠加"这一层,不做真正的嵌套布局。
def _container_offset(root: Tk*, w: Widget*):
if w.master is None: return
if uint64(w.master) == uint64(root): return
pw: Widget* = Widget*(w.master)
if pw.widget_type == WIDGET_FRAME:
w.x += pw.x
w.y += pw.y
# ---- Frame:容器控件(tkinter Frame 的对应物)----
# 可作 master 使用:子控件经 _misc_setup 继承它的 tk,照常注册进顶层窗口;位置再叠加本
# 容器原点(见 _container_offset)。
virtual class Frame(Widget):
fill_color: uint32
border_color: uint32
border_w: int
def __init__(self, master: Misc*, w: int, h: int):
self.x = 0
self.y = 0
self.w = w
self.h = h
self.visible = 1
self._misc_setup(master)
self.widget_type = WIDGET_FRAME
self.focused = 0
self.hovered = 0
self.fill_color = uint32(0xFF2A2A3A)
self.border_color = uint32(0xFF505070)
self.border_w = 1
_register_widget(master, Widget*(self))
def draw(self, fb: uint32*, fw: int, fh: int):
_fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.fill_color)
if self.border_w > 0:
_draw_hline(fb, fw, fh, self.x, self.y, self.w, self.border_color)
_draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, self.border_color)
_draw_vline(fb, fw, fh, self.x, self.y, self.h, self.border_color)
_draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, self.border_color)
def handle_event(self, evt: window.InputEvent*) -> int:
return 0
def config(self, fill_color: uint32 = None, border_color: uint32 = None):
if fill_color is not None:
self.fill_color = fill_color
if border_color is not None:
self.border_color = border_color
virtual class Label(Widget):
text: [64]char
color: uint32
def __init__(self, master: Misc*, text: str, color: uint32):
self.x = 0
self.y = 0
self.w = 0
self.h = 0
self.visible = 1
self._misc_setup(master)
self.widget_type = WIDGET_LABEL
self.focused = 0
self.hovered = 0
self.color = color
i: int = 0
for ch in text:
if i >= 63: break
self.text[i] = ch
i += 1
self.text[i] = 0
_register_widget(master, Widget*(self))
def draw(self, fb: uint32*, fw: int, fh: int):
_draw_text(fb, fw, fh, self.x, self.y, &self.text[0], self.color)
def handle_event(self, evt: window.InputEvent*) -> int:
return 0
def config(self, text: const str = None, color: uint32 = None):
if text is not None:
i: int = 0
while i < 63:
ch: char = *(text + i)
self.text[i] = ch
if ch == 0: break
i += 1
self.text[i] = 0
if color is not None:
self.color = color
virtual class Button(Widget):
text: [32]char
fill_color: uint32
text_color: uint32
pressed: int
callback: rawptr
def __init__(self, master: Misc*, w: int, h: int, text: str, callback: rawptr):
self.x = 0
self.y = 0
self.w = w
self.h = h
self.visible = 1
self._misc_setup(master)
self.widget_type = WIDGET_BUTTON
self.focused = 0
self.hovered = 0
self.fill_color = uint32(0x3C3C5A)
self.text_color = uint32(0xFFFFFFFF)
self.pressed = 0
self.callback = callback
i: int = 0
for ch in text:
if i >= 31: break
self.text[i] = ch
i += 1
self.text[i] = 0
_register_widget(master, Widget*(self))
def draw(self, fb: uint32*, fw: int, fh: int):
fill: uint32 = self.fill_color
border: uint32 = uint32(0x8080B0)
if self.pressed:
fill = _lighten(self.fill_color, 60)
border = uint32(0xA0A0D0)
elif self.hovered:
fill = _lighten(self.fill_color, 30)
border = uint32(0xA0A0D0)
_fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, fill)
top_left: uint32 = _lighten(fill, 80)
bottom_right: uint32 = _darken(fill, 80)
if self.pressed:
top_left = _darken(fill, 60)
bottom_right = _lighten(fill, 40)
_draw_hline(fb, fw, fh, self.x, self.y, self.w, top_left)
_draw_hline(fb, fw, fh, self.x, self.y + 1, self.w, top_left)
_draw_vline(fb, fw, fh, self.x, self.y, self.h, top_left)
_draw_vline(fb, fw, fh, self.x + 1, self.y, self.h, top_left)
_draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, bottom_right)
_draw_hline(fb, fw, fh, self.x, self.y + self.h - 2, self.w, bottom_right)
_draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, bottom_right)
_draw_vline(fb, fw, fh, self.x + self.w - 2, self.y, self.h, bottom_right)
_draw_hline(fb, fw, fh, self.x - 1, self.y - 1, self.w + 2, border)
_draw_hline(fb, fw, fh, self.x - 1, self.y + self.h, self.w + 2, border)
_draw_vline(fb, fw, fh, self.x - 1, self.y - 1, self.h + 2, border)
_draw_vline(fb, fw, fh, self.x + self.w, self.y - 1, self.h + 2, border)
tl: int = int(string.strlen(&self.text[0]))
tx: int = self.x + (self.w - 8 * tl) / 2
ty: int = self.y + (self.h - CHAR_H) / 2
_draw_text(fb, fw, fh, tx, ty, &self.text[0], self.text_color)
def handle_event(self, evt: window.InputEvent*) -> int:
etype: uint32 = evt.type
ex: int = evt.x
ey: int = evt.y
if etype == window.EVENT_TYPE_HOVER:
if self.contains(ex, ey):
if not self.hovered:
self.hovered = 1
return 1
else:
if self.hovered and not self.pressed:
self.hovered = 0
return 1
elif etype == window.EVENT_TYPE_HOVER_LEAVE:
if self.hovered:
self.hovered = 0
return 1
elif etype == window.EVENT_TYPE_CLICK:
if self.contains(ex, ey):
self.pressed = 1
return 1
elif etype == window.EVENT_TYPE_RELEASE:
if self.pressed:
self.pressed = 0
if self.contains(ex, ey) and self.callback is not None:
# 闭包调用约定(VPC 生成):callback 是堆上的 {fn, env} 对象,fn 期望 rdi = env。
# 之前直接 `call r13`(r13 = 闭包对象地址)会跳到堆上执行
# (现场:rip=0x2040400a 在堆区、cr2=0x1、err=0x5)。
__asm__(f"""push r12
push r13
mov r13, {asminp(self.callback, ASM_DESCR.REG_ANY)}
mov rdi, qword ptr [r13 + 8]
mov r12, rsp
and rsp, -16
call qword ptr [r13]
mov rsp, r12
pop r13
pop r12""",
op=[ASM_DESCR.CLOBBER_MEMORY,
ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX,
ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI,
ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
return 1
return 0
def config(self, text: const str = None, fill_color: uint32 = None, text_color: uint32 = None, callback: rawptr = None):
if text is not None:
i: int = 0
while i < 31:
ch: char = *(text + i)
self.text[i] = ch
if ch == 0: break
i += 1
self.text[i] = 0
if fill_color is not None:
self.fill_color = fill_color
if text_color is not None:
self.text_color = text_color
if callback is not None:
self.callback = callback
virtual class Entry(Widget):
text: [64]char
text_len: int
cursor_pos: int
max_len: int
bg_color: uint32
border_color: uint32
text_color: uint32
cursor_color: uint32
def __init__(self, master: Misc*, w: int, h: int):
self.x = 0
self.y = 0
self.w = w
self.h = h
self.visible = 1
self._misc_setup(master)
self.widget_type = WIDGET_ENTRY
self.focused = 0
self.hovered = 0
self.text_len = 0
self.cursor_pos = 0
self.max_len = 30
self.bg_color = uint32(0xFF231919)
self.border_color = uint32(0x606080)
self.text_color = uint32(0x000000)
self.cursor_color = uint32(0x000000)
for i in range(64):
self.text[i] = 0
_register_widget(master, Widget*(self))
def draw(self, fb: uint32*, fw: int, fh: int):
bg: uint32 = self.bg_color
bdr: uint32 = self.border_color
if self.focused:
bg = uint32(0xFFFFFF)
bdr = uint32(0x4488FF)
elif self.hovered:
bdr = uint32(0x8080B0)
_fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, bg)
_draw_hline(fb, fw, fh, self.x, self.y, self.w, bdr)
_draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, bdr)
_draw_vline(fb, fw, fh, self.x, self.y, self.h, bdr)
_draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, bdr)
if self.text_len > 0:
tc: uint32 = self.text_color
if not self.focused:
tc = uint32(0xCCCCCC)
_draw_text(fb, fw, fh, self.x + 6, self.y + 7, &self.text[0], tc)
elif not self.focused:
_draw_text(fb, fw, fh, self.x + 6, self.y + 7, "Type here...", uint32(0x888888))
if self.focused:
cx_pos: int = self.x + 6 + self.cursor_pos * CHAR_W
if cx_pos < self.x + self.w - 2:
_draw_vline(fb, fw, fh, cx_pos, self.y + 5, self.h - 10, self.cursor_color)
def handle_event(self, evt: window.InputEvent*) -> int:
etype: uint32 = evt.type
ex: int = evt.x
ey: int = evt.y
if etype == window.EVENT_TYPE_HOVER:
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):
if not self.focused:
self.focused = 1
tk4: Tk* = self.tk
tk4.focused_widget = Widget*(self)
rel_x: int = ex - self.x - 6
if rel_x < 0: rel_x = 0
new_pos: int = (rel_x + CHAR_W / 2) / CHAR_W
if new_pos > self.text_len: new_pos = self.text_len
self.cursor_pos = new_pos
return 1
else:
if self.focused:
self.focused = 0
tk5: Tk* = self.tk
tk5.focused_widget = None
return 1
elif etype == window.EVENT_TYPE_KEY:
if self.focused:
ebtn: uint8 = evt.button
if ebtn == window.KEY_STATE_RELEASE:
return 0
ch: int = ex
if ch == 8:
if self.cursor_pos > 0:
for i in range(self.cursor_pos, self.text_len):
self.text[i - 1] = self.text[i]
self.text_len -= 1
self.cursor_pos -= 1
self.text[self.text_len] = 0
return 1
elif ch == window.VK_LEFT:
if self.cursor_pos > 0:
self.cursor_pos -= 1
return 1
elif ch == window.VK_RIGHT:
if self.cursor_pos < self.text_len:
self.cursor_pos += 1
return 1
elif ch == window.VK_HOME:
self.cursor_pos = 0
return 1
elif ch == window.VK_END:
self.cursor_pos = self.text_len
return 1
elif ch == window.VK_DELETE:
if self.cursor_pos < self.text_len:
for i in range(self.cursor_pos, self.text_len - 1):
self.text[i] = self.text[i + 1]
self.text_len -= 1
self.text[self.text_len] = 0
return 1
elif ch >= 32 and ch < 127:
if self.text_len < self.max_len:
i: int = self.text_len
while i > self.cursor_pos:
self.text[i] = self.text[i - 1]
i -= 1
self.text[self.cursor_pos] = char(ch)
self.text_len += 1
self.cursor_pos += 1
self.text[self.text_len] = 0
return 1
return 0
def get(self) -> const str:
return &self.text[0]
def delete(self, start: int, end: int):
self.text_len = 0
for i in range(64):
self.text[i] = 0
def config(self, bg_color: uint32 = None, text_color: uint32 = None, border_color: uint32 = None):
if bg_color is not None:
self.bg_color = bg_color
if text_color is not None:
self.text_color = text_color
if border_color is not None:
self.border_color = border_color
virtual class Scrollbar(Widget):
orient: int
command: rawptr
slider_first: int
slider_last: int
action: int
action_value: int
pressed: int
dragging: int
drag_start_y: int
drag_start_first: int
trough_color: uint32
slider_color: uint32
arrow_color: uint32
slider_hover: int
def __init__(self, master: Misc*, w: int, h: int, orient: int):
self.x = 0
self.y = 0
self.w = w
self.h = h
self.visible = 1
self._misc_setup(master)
self.widget_type = WIDGET_SCROLLBAR
self.focused = 0
self.hovered = 0
self.orient = orient
self.command = None
self.slider_first = 0
self.slider_last = 1000
self.action = 0
self.action_value = 0
self.pressed = 0
self.dragging = 0
self.drag_start_y = 0
self.drag_start_first = 0
self.trough_color = uint32(0x1E1E2E)
self.slider_color = uint32(0x4A4A6A)
self.arrow_color = uint32(0xAAAAAA)
self.slider_hover = 0
_register_widget(master, Widget*(self))
def draw(self, fb: uint32*, fw: int, fh: int):
_fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.trough_color)
if self.orient != VERTICAL: return
arrow_sz: int = self.w
if arrow_sz > 16: arrow_sz = 16
self._draw_arrow_btn(fb, fw, fh, self.x, self.y, self.w, arrow_sz, 0, 1 if self.pressed == 1 else 0)
self._draw_arrow_btn(fb, fw, fh, self.x, self.y + self.h - arrow_sz, self.w, arrow_sz, 1, 1 if self.pressed == 2 else 0)
trough_y: int = self.y + arrow_sz
trough_h: int = self.h - 2 * arrow_sz
if trough_h <= 0: return
if self.slider_last > self.slider_first:
sl_h: int = (self.slider_last - self.slider_first) * trough_h / 1000
if sl_h < 8: sl_h = 8
sl_y: int = trough_y + self.slider_first * trough_h / 1000
if sl_y + sl_h > trough_y + trough_h:
sl_y = trough_y + trough_h - sl_h
if sl_y < trough_y: sl_y = trough_y
color: uint32 = self.slider_color
if self.dragging:
color = _lighten(self.slider_color, 40)
elif self.slider_hover:
color = _lighten(self.slider_color, 20)
_fill_rect(fb, fw, fh, self.x + 1, sl_y, self.w - 2, sl_h, color)
top_c: uint32 = _lighten(color, 60)
bot_c: uint32 = _darken(color, 60)
_draw_hline(fb, fw, fh, self.x + 1, sl_y, self.w - 2, top_c)
_draw_hline(fb, fw, fh, self.x + 1, sl_y + sl_h - 1, self.w - 2, bot_c)
_draw_vline(fb, fw, fh, self.x + 1, sl_y, sl_h, top_c)
_draw_vline(fb, fw, fh, self.x + self.w - 2, sl_y, sl_h, bot_c)
def _draw_arrow_btn(self, fb: uint32*, fw: int, fh: int, bx: int, by: int, bw: int, bh: int, direction: int, is_pressed: int):
color: uint32 = self.slider_color
if is_pressed:
color = _lighten(self.slider_color, 30)
_fill_rect(fb, fw, fh, bx, by, bw, bh, color)
top_c: uint32 = _lighten(color, 60)
bot_c: uint32 = _darken(color, 60)
if is_pressed:
top_c = _darken(color, 40)
bot_c = _lighten(color, 30)
_draw_hline(fb, fw, fh, bx, by, bw, top_c)
_draw_hline(fb, fw, fh, bx, by + bh - 1, bw, bot_c)
_draw_vline(fb, fw, fh, bx, by, bh, top_c)
_draw_vline(fb, fw, fh, bx + bw - 1, by, bh, bot_c)
cx: int = bx + bw / 2
cy: int = by + bh / 2
if direction == 0:
_fill_rect(fb, fw, fh, cx - 1, cy - 3, 3, 1, self.arrow_color)
_fill_rect(fb, fw, fh, cx - 2, cy - 2, 5, 1, self.arrow_color)
_fill_rect(fb, fw, fh, cx - 3, cy - 1, 7, 1, self.arrow_color)
else:
_fill_rect(fb, fw, fh, cx - 3, cy - 1, 7, 1, self.arrow_color)
_fill_rect(fb, fw, fh, cx - 2, cy, 5, 1, self.arrow_color)
_fill_rect(fb, fw, fh, cx - 1, cy + 1, 3, 1, self.arrow_color)
def handle_event(self, evt: window.InputEvent*) -> int:
etype: uint32 = evt.type
ex: int = evt.x
ey: int = evt.y
if etype == window.EVENT_TYPE_HOVER:
if self.dragging:
self._handle_drag(ey)
return 1
if self.contains(ex, ey):
if not self.slider_hover:
self.slider_hover = 1
return 1
else:
if self.slider_hover and self.pressed == 0:
self.slider_hover = 0
return 1
return 0
elif etype == window.EVENT_TYPE_HOVER_LEAVE:
if self.slider_hover:
self.slider_hover = 0
if not self.dragging:
self.pressed = 0
return 1
return 0
elif etype == window.EVENT_TYPE_CLICK:
if self.contains(ex, ey):
if self.orient == VERTICAL:
arrow_sz: int = self.w
if arrow_sz > 16: arrow_sz = 16
if ey < self.y + arrow_sz:
self.pressed = 1
self.action = 1
self._fire_command()
return 1
elif ey >= self.y + self.h - arrow_sz:
self.pressed = 2
self.action = 2
self._fire_command()
return 1
else:
trough_y: int = self.y + arrow_sz
trough_h: int = self.h - 2 * arrow_sz
if trough_h <= 0: return 1
sl_h: int = (self.slider_last - self.slider_first) * trough_h / 1000
if sl_h < 8: sl_h = 8
sl_y: int = trough_y + self.slider_first * trough_h / 1000
if sl_y + sl_h > trough_y + trough_h:
sl_y = trough_y + trough_h - sl_h
if ey >= sl_y and ey < sl_y + sl_h:
self.pressed = 4
self.dragging = 1
self.drag_start_y = ey
self.drag_start_first = self.slider_first
else:
self.pressed = 3
if ey < sl_y:
self.action = 3
else:
self.action = 4
self._fire_command()
return 1
return 0
elif etype == window.EVENT_TYPE_RELEASE:
if self.pressed:
self.pressed = 0
self.dragging = 0
return 1
return 0
return 0
def _handle_drag(self, ey: int):
arrow_sz: int = self.w
if arrow_sz > 16: arrow_sz = 16
trough_y: int = self.y + arrow_sz
trough_h: int = self.h - 2 * arrow_sz
if trough_h <= 0: return
delta_y: int = ey - self.drag_start_y
delta_first: int = delta_y * 1000 / trough_h
new_first: int = self.drag_start_first + delta_first
slider_size: int = self.slider_last - self.slider_first
if slider_size <= 0: slider_size = 1
if new_first < 0: new_first = 0
if new_first + slider_size > 1000: new_first = 1000 - slider_size
if new_first != self.slider_first:
self.slider_first = new_first
self.slider_last = new_first + slider_size
self.action = 5
self.action_value = new_first
self._fire_command()
def _fire_command(self):
if self.command is not None:
# 同 Button.handle_event:command 是闭包 {fn, env},必须 rdi = env 后 call fn。
__asm__(f"""push r12
push r13
mov r13, {asminp(self.command, ASM_DESCR.REG_ANY)}
mov rdi, qword ptr [r13 + 8]
mov r12, rsp
and rsp, -16
call qword ptr [r13]
mov rsp, r12
pop r13
pop r12""",
op=[ASM_DESCR.CLOBBER_MEMORY,
ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX,
ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI,
ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
def set(self, first: int, last: int):
self.slider_first = first
self.slider_last = last
virtual class FBCanvas(Widget):
_fb: uint32*
_cw: int
_ch: int
def __init__(self, master: Misc*, w: int, h: int):
self.x = 0
self.y = 0
self.w = w
self.h = h
self.visible = 1
self._misc_setup(master)
self.widget_type = WIDGET_FBCANVAS
self.focused = 0
self.hovered = 0
self._fb = None
self._cw = w
self._ch = h
_register_widget(master, Widget*(self))
def draw(self, fb: uint32*, fw: int, fh: int):
if not self._fb: return
if self._cw <= 0 or self._ch <= 0: return
copy_w: int = self._cw
if copy_w > self.w: copy_w = self.w
copy_h: int = self._ch
if copy_h > self.h: copy_h = self.h
for py in range(copy_h):
ty: int = self.y + py
if ty < 0 or ty >= fh: continue
for px in range(copy_w):
tx: int = self.x + px
if tx < 0 or tx >= fw: continue
fb[ty * fw + tx] = self._fb[py * self._cw + px]
def handle_event(self, evt: window.InputEvent*) -> int:
return 0
def set_fb(self, fb: uint32*, cw: int, ch: int):
self._fb = fb
self._cw = cw
self._ch = ch
def get_fb(self) -> uint32*:
return self._fb
def get_w(self) -> int:
return self._cw
def get_h(self) -> int:
return self._ch
virtual class Text(Widget):
lines: [TEXT_MAX_LINES][80]char
line_count: int
input_buf: [80]char
input_len: int
cursor_pos: int
bg_color: uint32
text_color: uint32
prompt_color: uint32
cursor_color: uint32
border_color: uint32
on_command: rawptr
history: [16][80]char
history_count: int
history_idx: int
scroll_offset: int
max_lines: int
yscrollbar: rawptr
def __init__(self, master: Misc*, w: int, h: int):
self.x = 0
self.y = 0
self.w = w
self.h = h
self.visible = 1
self._misc_setup(master)
self.widget_type = WIDGET_TEXT
self.focused = 0
self.hovered = 0
self.line_count = 0
self.input_len = 0
self.cursor_pos = 0
self.bg_color = uint32(0x0C0C0C)
self.text_color = uint32(0xCCCCCC)
self.prompt_color = uint32(0x00FF00)
self.cursor_color = uint32(0x00FF00)
self.border_color = uint32(0x333333)
self.on_command = None
self.history_count = 0
self.history_idx = 0
self.scroll_offset = 0
self.max_lines = TEXT_MAX_LINES
self.yscrollbar = None
for i in range(TEXT_MAX_LINES):
for j in range(79 + 1):
self.lines[i][j] = 0
for i in range(80):
self.input_buf[i] = 0
_register_widget(master, Widget*(self))
def draw(self, fb: uint32*, fw: int, fh: int):
self._update_scrollbar()
_fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.bg_color)
_fill_rect(fb, fw, fh, self.x, self.y + self.h - 2, self.w, 2, self.border_color)
visible: int = self._visible_lines()
for i in range(visible):
line_idx: int = self.scroll_offset + i
if line_idx < self.line_count:
_draw_text(fb, fw, fh, self.x + 4, self.y + i * CHAR_H, &self.lines[line_idx][0], self.text_color)
prompt_y: int = self.y + self.h - CHAR_H - 4
_draw_text(fb, fw, fh, self.x + 4, prompt_y, "> ", self.prompt_color)
if self.input_len > 0:
_draw_text(fb, fw, fh, self.x + 4 + 2 * CHAR_W, prompt_y, &self.input_buf[0], uint32(0xFFFFFF))
cx_pos: int = self.x + 4 + (2 + self.cursor_pos) * CHAR_W
if cx_pos < self.x + self.w - 4:
_fill_rect(fb, fw, fh, cx_pos, prompt_y, CHAR_W // 2, CHAR_H, self.cursor_color)
def handle_event(self, evt: window.InputEvent*) -> int:
etype: uint32 = evt.type
ex: int = evt.x
ey: int = evt.y
if etype == window.EVENT_TYPE_CLICK:
if self.contains(ex, ey):
if not self.focused:
self.focused = 1
tk: Tk* = self.tk
tk.focused_widget = Widget*(self)
prompt_y: int = self.y + self.h - CHAR_H - 4
if ey >= prompt_y and ey < prompt_y + CHAR_H:
rel_x: int = ex - self.x - 4 - 2 * CHAR_W
if rel_x < 0: rel_x = 0
new_pos: int = (rel_x + CHAR_W / 2) / CHAR_W
if new_pos > self.input_len: new_pos = self.input_len
self.cursor_pos = new_pos
return 1
else:
if self.focused:
self.focused = 0
tk2: Tk* = self.tk
tk2.focused_widget = None
return 1
elif etype == window.EVENT_TYPE_KEY:
if self.focused:
ebtn2: uint8 = evt.button
if ebtn2 == window.KEY_STATE_RELEASE:
return 0
ch: int = ex
if ch == 8:
if self.cursor_pos > 0:
i: int = self.cursor_pos
while i < self.input_len:
self.input_buf[i - 1] = self.input_buf[i]
i += 1
self.input_len -= 1
self.cursor_pos -= 1
self.input_buf[self.input_len] = 0
return 1
elif ch == 13 or ch == 10:
self._exec_input()
self.input_len = 0
self.cursor_pos = 0
for i in range(80):
self.input_buf[i] = 0
return 1
elif ch == window.VK_LEFT:
if self.cursor_pos > 0:
self.cursor_pos -= 1
return 1
elif ch == window.VK_RIGHT:
if self.cursor_pos < self.input_len:
self.cursor_pos += 1
return 1
elif ch == window.VK_UP:
if self.history_count > 0:
if self.history_idx < self.history_count:
self.history_idx += 1
hi: int = self.history_count - self.history_idx
if hi < 0: hi = 0
for j in range(80):
self.input_buf[j] = self.history[hi][j]
self.input_len = 0
while self.input_len < 79 and self.input_buf[self.input_len] != 0:
self.input_len += 1
self.cursor_pos = self.input_len
return 1
elif ch == window.VK_DOWN:
if self.history_count > 0:
if self.history_idx > 0:
self.history_idx -= 1
if self.history_idx == 0:
self.input_len = 0
self.cursor_pos = 0
for j2 in range(80):
self.input_buf[j2] = 0
else:
hi2: int = self.history_count - self.history_idx
for j3 in range(80):
self.input_buf[j3] = self.history[hi2][j3]
self.input_len = 0
while self.input_len < 79 and self.input_buf[self.input_len] != 0:
self.input_len += 1
self.cursor_pos = self.input_len
return 1
elif ch == window.VK_HOME:
self.cursor_pos = 0
return 1
elif ch == window.VK_END:
self.cursor_pos = self.input_len
return 1
elif ch == window.VK_DELETE:
if self.cursor_pos < self.input_len:
i: int = self.cursor_pos
while i < self.input_len - 1:
self.input_buf[i] = self.input_buf[i + 1]
i += 1
self.input_len -= 1
self.input_buf[self.input_len] = 0
return 1
elif ch == window.VK_PRIOR:
if self.scroll_offset > 0:
vis: int = self._visible_lines()
self.scroll_offset -= vis
if self.scroll_offset < 0: self.scroll_offset = 0
self._update_scrollbar()
return 1
elif ch == window.VK_NEXT:
vis2: int = self._visible_lines()
max_off: int = self.line_count - vis2
if max_off < 0: max_off = 0
if self.scroll_offset < max_off:
self.scroll_offset += vis2
if self.scroll_offset > max_off: self.scroll_offset = max_off
self._update_scrollbar()
return 1
elif ch >= 32 and ch < 127:
if self.input_len < 79:
i: int = self.input_len
while i > self.cursor_pos:
self.input_buf[i] = self.input_buf[i - 1]
i -= 1
self.input_buf[self.cursor_pos] = char(ch)
self.input_len += 1
self.cursor_pos += 1
self.input_buf[self.input_len] = 0
return 1
return 0
def _exec_input(self):
self.insert("> ")
if self.input_len > 0:
last: int = self.line_count - 1
j: int = 0
while j < self.input_len and j < 77:
self.lines[last][2 + j] = self.input_buf[j]
j += 1
self.lines[last][2 + j] = 0
if self.history_count < 16:
for k in range(80):
self.history[self.history_count][k] = self.input_buf[k]
self.history_count += 1
else:
for i in range(15):
for k2 in range(80):
self.history[i][k2] = self.history[i + 1][k2]
for k3 in range(80):
self.history[15][k3] = self.input_buf[k3]
self.history_idx = 0
cmd: rawptr = self.on_command
if cmd is not None:
# 同 Button.handle_event:on_command 是闭包 {fn, env},必须 rdi = env 后 call fn。
__asm__(f"""push r12
push r13
mov r13, {asminp(cmd, ASM_DESCR.REG_ANY)}
mov rdi, qword ptr [r13 + 8]
mov r12, rsp
and rsp, -16
call qword ptr [r13]
mov rsp, r12
pop r13
pop r12""",
op=[ASM_DESCR.CLOBBER_MEMORY,
ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX,
ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI,
ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
else:
self.insert("(no callback)")
def insert(self, text: str):
start: int = 0
pos: int = 0
while True:
ch: char = *(text + pos)
if ch == 0:
if pos > start:
self._insert_substring(text, start, pos)
break
if ch == 10:
self._insert_substring(text, start, pos)
start = pos + 1
pos += 1
def insert_ptr(self, text: const str):
start: int = 0
pos: int = 0
while True:
ch: char = *(text + pos)
if ch == 0:
if pos > start:
self._insert_substring(text, start, pos)
break
if ch == 10:
self._insert_substring(text, start, pos)
start = pos + 1
pos += 1
def _insert_substring(self, text: const str, start: int, end: int):
if self.line_count >= self.max_lines:
for i in range(self.max_lines - 1):
for j in range(79 + 1):
self.lines[i][j] = self.lines[i + 1][j]
self.line_count -= 1
if self.scroll_offset > 0:
self.scroll_offset -= 1
idx: int = self.line_count
j: int = 0
k: int = start
while k < end and j < 79:
ch: char = *(text + k)
if ch == 9:
tab_stop: int = 8 - (j % 8)
while tab_stop > 0 and j < 79:
self.lines[idx][j] = 32
j += 1
tab_stop -= 1
elif ch == 13:
pass
else:
self.lines[idx][j] = ch
j += 1
k += 1
self.lines[idx][j] = 0
self.line_count += 1
vis: int = self._visible_lines()
if self.line_count > vis:
self.scroll_offset = self.line_count - vis
else:
self.scroll_offset = 0
self._update_scrollbar()
def delete(self, start: int, end: int):
self.line_count = 0
self.scroll_offset = 0
self._update_scrollbar()
def get(self) -> const str:
return &self.input_buf[0]
def config(self, text_color: uint32 = None, bg_color: uint32 = None, prompt_color: uint32 = None, cursor_color: uint32 = None, border_color: uint32 = None):
if text_color is not None:
self.text_color = text_color
if bg_color is not None:
self.bg_color = bg_color
if prompt_color is not None:
self.prompt_color = prompt_color
if cursor_color is not None:
self.cursor_color = cursor_color
if border_color is not None:
self.border_color = border_color
def _visible_lines(self) -> int:
return (self.h - CHAR_H - 4) / CHAR_H
def _update_scrollbar(self):
vis: int = self._visible_lines()
if self.line_count <= vis:
self.scroll_offset = 0
else:
max_off: int = self.line_count - vis
if self.scroll_offset > max_off:
self.scroll_offset = max_off
if self.yscrollbar is not None:
sb: Scrollbar* = Scrollbar*(self.yscrollbar)
if self.line_count <= vis:
sb.set(0, 1000)
else:
first: int = self.scroll_offset * 1000 / self.line_count
last: int = (self.scroll_offset + vis) * 1000 / self.line_count
sb.set(first, last)
# ---- Tk:主窗口类 = Wm(窗口管理器协议,无字段)+ Misc(公共状态/方法)----
virtual class Tk(Wm, Misc):
win_id: int
buf: uint32*
bw: int
bh: int
style: int
bg_color: uint32
widgets: [MAX_WIDGETS]Widget*
widget_count: int
focused_widget: Widget*
_win_x: int
_win_y: int
_title: [32]char
resizable_w: int
resizable_h: int
_grid_rows: int
_grid_cols: int
_bb: uint32* # 后备缓冲:整帧画完再一次性拷进共享 arena,避免合成器读到半成品帧
_bb_px: int # 后备缓冲容量(像素数),窗口变大时才重新分配
_ansi_state: int # ESC 序列解析状态:0=普通 1=已收 ESC 2=已收 ESC[
_ansi_param: int # CSI 数值参数(如 ESC[5~ 的 5)
def __init__(self):
global _g_layout_init
# Misc 的公共状态:主窗口没有父容器,tk 指向自身(控件经 _misc_setup 继承它)
self.master = None
self.tk = Tk*(self)
self.widget_count = 0
self.focused_widget = None
self.win_id = -1
self._win_x = 0
self._win_y = 0
self.style = 0
self.bg_color = uint32(0xFF231919)
self.resizable_w = 1
self.resizable_h = 1
self._grid_rows = 0
self._grid_cols = 0
if _g_layout_init == 0:
for i in range(MAX_WIDGETS):
_g_wl_type[i] = 0
_g_wl_side[i] = 0
_g_wl_fill[i] = 0
_g_wl_expand[i] = 0
_g_wl_padx[i] = 0
_g_wl_pady[i] = 0
_g_wl_row[i] = 0
_g_wl_col[i] = 0
_g_wl_rowspan[i] = 0
_g_wl_colspan[i] = 1
_g_wl_sticky[i] = 0
_g_layout_init = 1
self._bb = None
self._bb_px = 0
self._ansi_state = 0
self._ansi_param = 0
for j in range(32):
self._title[j] = 0
def pack(self, idx: int, side: int, fill: int, expand: int, padx: int, pady: int):
_g_wl_type[idx] = 2
_g_wl_side[idx] = side
_g_wl_fill[idx] = fill
_g_wl_expand[idx] = expand
_g_wl_padx[idx] = padx
_g_wl_pady[idx] = pady
def grid(self, idx: int, row: int, col: int, rowspan: int, colspan: int, sticky: int):
_g_wl_type[idx] = 3
_g_wl_row[idx] = row
_g_wl_col[idx] = col
_g_wl_rowspan[idx] = rowspan
_g_wl_colspan[idx] = colspan
_g_wl_sticky[idx] = sticky
# ---- Wm 协议的落地实现(Tk 持有窗口状态,覆写 Wm 声明的协议面)----
def title(self, text: str):
i: int = 0
while i < 31:
ch: char = text[i]
if ch == 0: break
self._title[i] = ch
i += 1
self._title[i] = 0
if self.win_id >= 0:
window.set_title(self.win_id, &self._title[0])
def set_style(self, s: int):
self.style = s
# 【诊断·临时】确认 app 侧 set_style 是否被调用、拿到的值是否非零
if s == 0:
devfs.serial_puts("[vpui] set_style 0\n")
else:
devfs.serial_puts("[vpui] set_style NZ\n")
def resizable(self, rw: int, rh: int):
self.resizable_w = rw
self.resizable_h = rh
if self.win_id >= 0:
window.set_resizable(self.win_id, rw, rh)
def geometry(self, x: int, y: int, w: int, h: int):
self._win_x = x
self._win_y = y
self.bw = w
self.bh = h - TITLE_H - 2
if self.win_id < 0:
# 【诊断·临时】建窗时 self.style 是否已是期望值
if self.style == 0:
devfs.serial_puts("[vpui] geometry style=0\n")
else:
devfs.serial_puts("[vpui] geometry style!=0\n")
self.win_id = window.create_window(x, y, w, h, &self._title[0], self.style)
self.buf = window.get_winbuf(self.win_id)
window.set_resizable(self.win_id, self.resizable_w, self.resizable_h)
else:
window.set_geometry(self.win_id, x, y, w, h)
self.buf = window.get_winbuf(self.win_id)
def setup(self, x: int, y: int, w: int, h: int, title: const str, style: int):
self._win_x = x
self._win_y = y
self.win_id = window.create_window(x, y, w, h, title, style)
self.buf = window.get_winbuf(self.win_id)
self.bw = w
self.bh = h - TITLE_H - 2
self.style = style
self.bg_color = uint32(0xFF231919)
def redraw(self):
self._do_layout()
self.buf = window.get_winbuf(self.win_id)
sz: int = window.get_win_size(self.win_id)
if sz != 0:
self.bw = sz >> 16
self.bh = sz & 0xFFFF
if not self.buf: return
if self.bw <= 0 or self.bh <= 0: return
# 双缓冲:整帧先画在**进程本地**的后备缓冲里,画完一次性拷进共享 arena,再 flush。
# 为什么必须这么做:arena 正是 explorer 合成时读的那块内存(render_window_rows
# 直接从它逐行 blit 上屏)。若直接往 arena 上画,合成器会在"整窗已填成背景色、
# 控件还没画"的中途采样 ⇒ 窗口闪烁;光标移动让 explorer 重画窗口所在行时同样撞上。
px: int = self.bw * self.bh
if self._bb_px < px:
if self._bb: stdlib.free(rawptr(self._bb))
self._bb = uint32*(stdlib.malloc(uint64(px * 4)))
self._bb_px = px
tgt: uint32* = self._bb
if not tgt:
# 分配失败不静默:打日志后**退回直写 arena**(画面仍能出,只是回到有撕裂的形态)。
devfs.serial_puts("[vpui] backbuffer alloc FAILED, drawing straight into arena\n")
tgt = self.buf
_fill_rect(tgt, self.bw, self.bh, 0, 0, self.bw, self.bh, self.bg_color)
for i in range(self.widget_count):
w: Widget* = self.widgets[i]
if w.visible:
w.draw(tgt, self.bw, self.bh)
if tgt != self.buf:
string.memcpy(self.buf, tgt, uint64(px * 4))
window.flush(self.win_id)
def _do_layout(self):
self._grid_rows = 0
self._grid_cols = 0
cav_x: int = 0
cav_y: int = 0
cav_w: int = self.bw
cav_h: int = self.bh
for i in range(self.widget_count):
w: Widget* = self.widgets[i]
lt: int = _g_wl_type[i]
if lt == 2:
side: int = _g_wl_side[i]
fill: int = _g_wl_fill[i]
expand: int = _g_wl_expand[i]
px: int = _g_wl_padx[i]
py: int = _g_wl_pady[i]
ww: int = w.w
wh: int = w.h
if side == PACK_TOP:
if fill == FILL_X or fill == FILL_BOTH:
ww = cav_w - 2 * px
if expand:
if fill == FILL_Y or fill == FILL_BOTH:
wh = cav_h - 2 * py
w.x = cav_x + px
w.y = cav_y + py
w.w = ww
w.h = wh
cav_y += wh + 2 * py
cav_h -= wh + 2 * py
elif side == PACK_BOTTOM:
if fill == FILL_X or fill == FILL_BOTH:
ww = cav_w - 2 * px
if expand:
if fill == FILL_Y or fill == FILL_BOTH:
wh = cav_h - 2 * py
w.x = cav_x + px
w.y = cav_y + cav_h - wh - py
w.w = ww
w.h = wh
cav_h -= wh + 2 * py
elif side == PACK_LEFT:
if fill == FILL_Y or fill == FILL_BOTH:
wh = cav_h - 2 * py
if expand:
if fill == FILL_X or fill == FILL_BOTH:
ww = cav_w - 2 * px
w.x = cav_x + px
w.y = cav_y + py
w.w = ww
w.h = wh
cav_x += ww + 2 * px
cav_w -= ww + 2 * px
elif side == PACK_RIGHT:
if fill == FILL_Y or fill == FILL_BOTH:
wh = cav_h - 2 * py
if expand:
if fill == FILL_X or fill == FILL_BOTH:
ww = cav_w - 2 * px
w.x = cav_x + cav_w - ww - px
w.y = cav_y + py
w.w = ww
w.h = wh
cav_w -= ww + 2 * px
_container_offset(self, w)
elif lt == 3:
r: int = _g_wl_row[i] + _g_wl_rowspan[i]
c: int = _g_wl_col[i] + _g_wl_colspan[i]
if r > self._grid_rows: self._grid_rows = r
if c > self._grid_cols: self._grid_cols = c
for i in range(self.widget_count):
w: Widget* = self.widgets[i]
if _g_wl_type[i] == 3:
if self._grid_rows <= 0: self._grid_rows = 1
if self._grid_cols <= 0: self._grid_cols = 1
cell_w: int = self.bw / self._grid_cols
cell_h: int = self.bh / self._grid_rows
wx: int = _g_wl_col[i] * cell_w
wy: int = _g_wl_row[i] * cell_h
ww: int = _g_wl_colspan[i] * cell_w
wh: int = _g_wl_rowspan[i] * cell_h
sticky: int = _g_wl_sticky[i]
if sticky == 0:
ox: int = (ww - w.w) / 2
oy: int = (wh - w.h) / 2
w.x = wx + ox
w.y = wy + oy
else:
w.x = wx
w.y = wy
if (sticky & 1) != 0:
w.w = ww
if (sticky & 2) != 0:
w.h = wh
_container_offset(self, w)
def update(self):
self.redraw()
def winfo_exists(self) -> int:
if self.win_id < 0: return 0
return window.is_active(self.win_id)
# ---- Misc 查询的覆写:主窗口用自己的窗口状态回答 winfo_* ----
def winfo_ismapped(self) -> int:
if self.win_id < 0: return 0
return window.is_active(self.win_id)
def winfo_x(self) -> int:
return self._win_x
def winfo_y(self) -> int:
return self._win_y
def winfo_width(self) -> int:
return self.bw
def winfo_height(self) -> int:
return self.bh
# 无显式焦点控件时,按注册顺序取第一个可见文本控件(Text/Entry)
def _first_text_widget(self) -> Widget*:
for i in range(self.widget_count):
w: Widget* = self.widgets[i]
if w.visible != 0:
if w.widget_type == WIDGET_TEXT or w.widget_type == WIDGET_ENTRY:
return w
return None
# 逐字节投递的按键流 -> ANSI CSI 状态机;返回 0=本字节被吞掉,1=可分发
def _fold_ansi_key(self, evt: window.InputEvent*) -> int:
ch: int = evt.x
if self._ansi_state == 0:
if ch == 27:
self._ansi_state = 1
self._ansi_param = 0
return 0
return 1
if self._ansi_state == 1:
if ch == 91:
self._ansi_state = 2
return 0
self._ansi_state = 0
evt.x = 27
return 1
if ch >= 48 and ch <= 57:
self._ansi_param = self._ansi_param * 10 + (ch - 48)
return 0
self._ansi_state = 0
vk: int = 0
if ch == 65: vk = window.VK_UP
elif ch == 66: vk = window.VK_DOWN
elif ch == 67: vk = window.VK_RIGHT
elif ch == 68: vk = window.VK_LEFT
elif ch == 72: vk = window.VK_HOME
elif ch == 70: vk = window.VK_END
elif ch == 126:
if self._ansi_param == 3: vk = window.VK_DELETE
elif self._ansi_param == 5: vk = window.VK_PRIOR
elif self._ansi_param == 6: vk = window.VK_NEXT
if vk == 0: return 0
evt.x = vk
return 1
def mainloop(self):
self.redraw()
evt: window.InputEvent
while window.is_active(self.win_id):
result: int = window.poll_event(&evt)
if result != 0:
if evt.win_id == self.win_id:
need_redraw: int = 0
etype: uint32 = evt.type
if etype == window.EVENT_TYPE_PING:
window.flush(self.win_id)
elif etype == window.EVENT_TYPE_RESIZE:
self.buf = window.get_winbuf(self.win_id)
sz: int = window.get_win_size(self.win_id)
self.bw = sz >> 16
self.bh = sz & 0xFFFF
need_redraw = 1
elif etype == window.EVENT_TYPE_KEY or etype == window.EVENT_TYPE_KEY_RELEASE:
folded: int = 1
if etype == window.EVENT_TYPE_KEY:
folded = self._fold_ansi_key(&evt)
if folded != 0:
fw: Widget* = self.focused_widget
if fw is None:
fw = self._first_text_widget()
if fw is not None:
fw.focused = 1
self.focused_widget = fw
if fw is not None:
handled: int = fw.dispatch_event(&evt)
if handled: need_redraw = 1
else:
if etype == window.EVENT_TYPE_HOVER:
i: int = self.widget_count - 1
while i >= 0:
w: Widget* = self.widgets[i]
if w.visible:
handled2: int = w.dispatch_event(&evt)
if handled2:
need_redraw = 1
break
i -= 1
else:
for i in range(self.widget_count):
w: Widget* = self.widgets[i]
if w.visible:
handled2: int = w.dispatch_event(&evt)
if handled2: need_redraw = 1
if need_redraw:
self.redraw()
else:
process.yield_cpu()