2039 lines
86 KiB
Plaintext
2039 lines
86 KiB
Plaintext
import vpsdk.window as window
|
||
import vpsdk.process as process
|
||
import vpsdk.devfs as devfs
|
||
import vpsdk.stdlib as stdlib
|
||
import viperlib
|
||
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
|
||
|
||
# 【诊断·临时】本进程首次 redraw 是否已发生(0 = 还没画过)
|
||
_redraw_logged: int = 0
|
||
# 【诊断·临时】本进程首次 redraw 是否已走到 flush(0 = 还没到)
|
||
_flush_logged: int = 0
|
||
# 【诊断·临时】FBCanvas 首次绘制是否已发生(0 = 还没画过)
|
||
_canvas_dbg: int = 0
|
||
|
||
_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
|
||
# Label 没有显式 w/h 参数:自然尺寸必须由文本推出。否则 pack 拿到的请求尺寸是
|
||
# 0×0 ⇒ 不占布局空间,且 PACK_BOTTOM/非 fill 时会被贴到可视区域之外(文字被裁掉)。
|
||
self._fit_natural()
|
||
_register_widget(master, Widget*(self))
|
||
|
||
# 按当前 text 计算自然尺寸:宽取最长行,高取行数(多行 Label 也成立)。
|
||
def _fit_natural(self):
|
||
line_chars: int = 0
|
||
max_chars: int = 0
|
||
lines: int = 1
|
||
i: int = 0
|
||
while i < 64:
|
||
ch: char = self.text[i]
|
||
if ch == 0: break
|
||
if ch == 10:
|
||
if line_chars > max_chars: max_chars = line_chars
|
||
line_chars = 0
|
||
lines += 1
|
||
else:
|
||
line_chars += 1
|
||
i += 1
|
||
if line_chars > max_chars: max_chars = line_chars
|
||
self.w = max_chars * CHAR_W
|
||
self.h = lines * CHAR_H
|
||
|
||
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
|
||
self._fit_natural()
|
||
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
|
||
return 1
|
||
else:
|
||
if self.hovered:
|
||
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):
|
||
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
|
||
# 【诊断·临时】首次绘制:确认源缓冲指针 / 尺寸 / 首像素是否已被 Scene3D 写进来
|
||
# (源就是 app 的全局帧缓冲;px0 仍等于清屏色说明渲染线程没画或没跑)。
|
||
global _canvas_dbg
|
||
if _canvas_dbg == 0:
|
||
_canvas_dbg = 1
|
||
_cb: [176]char
|
||
viperlib.snprintf(&_cb, 176, "[vpui] canvas draw src=0x%lx cw=%d ch=%d w=%d h=%d px0=0x%x\n",
|
||
uint64(self._fb), self._cw, self._ch, self.w, self.h, self._fb[0])
|
||
devfs.serial_puts(&_cb)
|
||
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
|
||
prompt: [80]char
|
||
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.prompt[0] = '>'
|
||
self.prompt[1] = ' '
|
||
self.prompt[2] = 0
|
||
self.bg_color = uint32(0x0C0C0C)
|
||
self.text_color = uint32(0xCCCCCC)
|
||
self.prompt_color = uint32(0xCCCCCC)
|
||
self.cursor_color = uint32(0xCCCCCC)
|
||
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)
|
||
# 输入行不是一个固定在底部的控件,而是紧跟在最后一条输出之后的一行;
|
||
# 输出增多时它随之下移,超出可视区时由 scroll_offset 把它保持在最后一行。
|
||
prompt_row: int = self.line_count - self.scroll_offset
|
||
if prompt_row >= 0 and prompt_row < visible:
|
||
prompt_y: int = self.y + prompt_row * CHAR_H
|
||
plen: int = int(string.strlen(&self.prompt[0]))
|
||
_draw_text(fb, fw, fh, self.x + 4, prompt_y, &self.prompt[0], self.prompt_color)
|
||
if self.input_len > 0:
|
||
_draw_text(fb, fw, fh, self.x + 4 + plen * CHAR_W, prompt_y,
|
||
&self.input_buf[0], self.text_color)
|
||
cx_pos: int = self.x + 4 + (plen + 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_row: int = self.line_count - self.scroll_offset
|
||
prompt_y: int = self.y + prompt_row * CHAR_H
|
||
if prompt_row >= 0 and prompt_row < self._visible_lines() and ey >= prompt_y and ey < prompt_y + CHAR_H:
|
||
plen: int = int(string.strlen(&self.prompt[0]))
|
||
rel_x: int = ex - self.x - 4 - plen * 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_WHEEL:
|
||
if self.contains(ex, ey):
|
||
w: int = int(evt.button)
|
||
if w >= 128: w = w - 256
|
||
vis: int = self._visible_lines()
|
||
self.scroll_offset = self.scroll_offset - w * 3
|
||
max_off: int = self.line_count + 1 - vis
|
||
if max_off < 0: max_off = 0
|
||
if self.scroll_offset < 0: self.scroll_offset = 0
|
||
if self.scroll_offset > max_off: self.scroll_offset = max_off
|
||
self._update_scrollbar()
|
||
return 1
|
||
return 0
|
||
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 + 1 - 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(&self.prompt[0])
|
||
plen: int = int(string.strlen(&self.prompt[0]))
|
||
if self.input_len > 0:
|
||
last: int = self.line_count - 1
|
||
j: int = 0
|
||
while j < self.input_len and plen + j < 79:
|
||
self.lines[last][plen + j] = self.input_buf[j]
|
||
j += 1
|
||
self.lines[last][plen + 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
|
||
before_lines: int = self.line_count
|
||
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)")
|
||
|
||
# 本命令确实产生了输出时,在日志与下一个输入行之间留一个空行。
|
||
# 这样提示符不会紧贴最后一行日志(用实际写入的行数判断,clear 不会被补空行)。
|
||
if self.line_count > before_lines:
|
||
self.insert("\n")
|
||
|
||
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()
|
||
total: int = self.line_count + 1 # 输出行 + 输入行
|
||
if total > vis:
|
||
self.scroll_offset = total - 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 set_prompt(self, text: const str):
|
||
i: int = 0
|
||
while i < 79:
|
||
ch: char = *(text + i)
|
||
self.prompt[i] = ch
|
||
if ch == 0: break
|
||
i += 1
|
||
self.prompt[i] = 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 - 2) / CHAR_H
|
||
|
||
def _update_scrollbar(self):
|
||
vis: int = self._visible_lines()
|
||
total: int = self.line_count + 1 # 输出行 + 输入行
|
||
if total <= vis:
|
||
self.scroll_offset = 0
|
||
else:
|
||
max_off: int = total - vis
|
||
if self.scroll_offset > max_off:
|
||
self.scroll_offset = max_off
|
||
if self.yscrollbar is not None:
|
||
sb: Scrollbar* = Scrollbar*(self.yscrollbar)
|
||
if total <= vis:
|
||
sb.set(0, 1000)
|
||
else:
|
||
first: int = self.scroll_offset * 1000 / total
|
||
last: int = (self.scroll_offset + vis) * 1000 / total
|
||
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*
|
||
hovered_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.hovered_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):
|
||
# 窗口槽已回收(用户点了关闭):立即返回,不再触碰共享 arena / flush。
|
||
if self.win_id < 0: return
|
||
if window.is_active(self.win_id) == 0: return
|
||
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
|
||
# 先刷新尺寸再布局:窗口 resize 后若沿用上一帧的 bw/bh,布局会滞后一帧。
|
||
self._do_layout()
|
||
# 【诊断·临时】每个进程第一次真正走到 redraw 时打一行:证明"app 侧开始绘制了",
|
||
# 并给出它拿到的缓冲地址/尺寸(与 explorer 侧 shm0/flush 对照)。
|
||
# `_first` 只在本帧为 1:下面的 st1..st4 分段打点也只打这一次,用于定位
|
||
# "redraw 进去了但没走到 flush" 到底停在哪一段(malloc / 填底 / 控件 / 拷回 arena)。
|
||
global _redraw_logged
|
||
_first: int = 0
|
||
if _redraw_logged == 0:
|
||
_redraw_logged = 1
|
||
_first = 1
|
||
_rlb: [160]char
|
||
viperlib.snprintf(&_rlb, 160, "[vpui] redraw win=%d bw=%d bh=%d buf=0x%lx\n",
|
||
self.win_id, self.bw, self.bh, uint64(self.buf))
|
||
devfs.serial_puts(&_rlb)
|
||
# 双缓冲:整帧先画在**进程本地**的后备缓冲里,画完一次性拷进共享 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
|
||
# 【诊断·临时】st1:后备缓冲分配结果(指针 0 = 失败)
|
||
if _first != 0:
|
||
_bbp: uint64 = uint64(0)
|
||
if self._bb is not None: _bbp = uint64(self._bb)
|
||
_s1b: [112]char
|
||
viperlib.snprintf(&_s1b, 112, "[vpui] st1 bb win=%d px=%d ptr=0x%lx\n",
|
||
self.win_id, px, _bbp)
|
||
devfs.serial_puts(&_s1b)
|
||
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)
|
||
# 【诊断·临时】st2:整窗底色已铺完,即将进入控件绘制
|
||
if _first != 0:
|
||
_s2b: [96]char
|
||
viperlib.snprintf(&_s2b, 96, "[vpui] st2 fill win=%d widgets=%d\n",
|
||
self.win_id, self.widget_count)
|
||
devfs.serial_puts(&_s2b)
|
||
for i in range(self.widget_count):
|
||
w: Widget* = self.widgets[i]
|
||
# 【诊断·临时】st3:逐个控件打点 —— 卡死时最后一个编号就是元凶
|
||
if _first != 0:
|
||
_s3b: [112]char
|
||
viperlib.snprintf(&_s3b, 112, "[vpui] st3 w%d type=%d vis=%d\n",
|
||
i, w.widget_type, w.visible)
|
||
devfs.serial_puts(&_s3b)
|
||
if w.visible:
|
||
w.draw(tgt, self.bw, self.bh)
|
||
if tgt != self.buf:
|
||
string.memcpy(self.buf, tgt, uint64(px * 4))
|
||
# 【诊断·临时】st4:整帧已拷进共享 arena,即将 flush
|
||
if _first != 0:
|
||
devfs.serial_puts("[vpui] st4 copied\n")
|
||
window.flush(self.win_id)
|
||
# 【诊断·临时】redraw 走到"已请求合成"这一步:与前面的"进入 redraw"配对,
|
||
# 可区分"绘制中途没跑完"与"跑完了但合成器没收到"。
|
||
global _flush_logged
|
||
if _flush_logged == 0:
|
||
_flush_logged = 1
|
||
_flb: [96]char
|
||
viperlib.snprintf(&_flb, 96, "[vpui] flushed win=%d\n", self.win_id)
|
||
devfs.serial_puts(&_flb)
|
||
|
||
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
|
||
# 交叉轴上未 fill 时按 Tk 默认 anchor=center 居中;沿 pack 方向仍贴腔体边缘 +
|
||
# padding(与 tkinter 一致)。此前一律贴左上,导致不 fill 的控件全部挤在左边缘。
|
||
if side == PACK_TOP:
|
||
if fill == FILL_X or fill == FILL_BOTH:
|
||
ww = cav_w - 2 * px
|
||
w.x = cav_x + px
|
||
else:
|
||
w.x = cav_x + px + (cav_w - 2 * px - ww) / 2
|
||
if expand:
|
||
if fill == FILL_Y or fill == FILL_BOTH:
|
||
wh = cav_h - 2 * py
|
||
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
|
||
w.x = cav_x + px
|
||
else:
|
||
w.x = cav_x + px + (cav_w - 2 * px - ww) / 2
|
||
if expand:
|
||
if fill == FILL_Y or fill == FILL_BOTH:
|
||
wh = cav_h - 2 * py
|
||
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
|
||
w.y = cav_y + py
|
||
else:
|
||
w.y = cav_y + py + (cav_h - 2 * py - wh) / 2
|
||
if expand:
|
||
if fill == FILL_X or fill == FILL_BOTH:
|
||
ww = cav_w - 2 * px
|
||
w.x = cav_x + px
|
||
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
|
||
w.y = cav_y + py
|
||
else:
|
||
w.y = cav_y + py + (cav_h - 2 * py - wh) / 2
|
||
if expand:
|
||
if fill == FILL_X or fill == FILL_BOTH:
|
||
ww = cav_w - 2 * px
|
||
w.x = cav_x + cav_w - ww - px
|
||
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
|
||
# 孤立 ESC:只丢弃 ESC,当前字节照常分发 —— 否则会连带吞掉下一个键入字符。
|
||
self._ansi_state = 0
|
||
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
|
||
|
||
# 指针下的最上层控件决定光标形状: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
|
||
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:
|
||
self._update_cursor_for_hover(evt.x, evt.y)
|
||
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()
|