diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index 8b958cf..8bdc219 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -4,6 +4,7 @@ import vpsdk.fs as fs import vpsdk.process as process import vpsdk.syscall as syscall import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表 +import vpsdk.fonttools.fonttools as fonttools from stdint import * import string @@ -60,6 +61,25 @@ define SHADOW_TOP = 4 define SHADOW_BOTTOM = 14 define SHADOW_ALPHA = 100 +_ttf_font: fonttools.Font* = None +_ttf_atlas: uint8* = None +_ttf_state: int = 0 + +def _ensure_ttf(): + global _ttf_font, _ttf_atlas, _ttf_state + if _ttf_state != 0: return + f: fonttools.Font* = fonttools.load("/FONTS/FONT.TTF") + if f is None: + _ttf_state = -1 + return + a: uint8* = fonttools.build_ascii_atlas(f, vpui.CHAR_W, vpui.CHAR_H) + if a is None: + _ttf_state = -1 + return + _ttf_font = f + _ttf_atlas = a + _ttf_state = 1 + # 光标 16×16 位图模板(取自 desktop.vp 的历史实现):0 = 透明、1 = 黑边、2 = 白心。 # 同一历史文件里的 _CURSOR_IBEAM / _CURSOR_RESIZE_* 待接 WinSlot.cursor 后再启用。 _CURSOR_ARROW: [256]uint8 = [ @@ -724,6 +744,37 @@ class DeskServer: # ---------------- 文本(8x16 点阵,字体表复用 vpui.font)---------------- def draw_char(self, x: int, y: int, ch: char, col: uint32): ci: int = int(uint8(ch)) + _ensure_ttf() + if _ttf_state == 1 and ci < 128: + fy: int = 0 + while fy < vpui.CHAR_H: + ty: int = y + fy + if ty >= 0 and ty < self.fb_h: + base: int = ty * self.fb_w + fx: int = 0 + while fx < vpui.CHAR_W: + a: int = int(_ttf_atlas[(ci * vpui.CHAR_H + fy) * vpui.CHAR_W + fx]) + if a != 0: + px: int = x + fx + if px >= 0 and px < self.fb_w: + idx: int = base + px + if a >= 255: + self.back[idx] = col + else: + dst: uint32 = self.back[idx] + dr: int = int(dst & 0xFF) + dg: int = int((dst >> 8) & 0xFF) + db: int = int((dst >> 16) & 0xFF) + sr: int = int(col & 0xFF) + sg: int = int((col >> 8) & 0xFF) + sb: int = int((col >> 16) & 0xFF) + r: int = (sr * a + dr * (255 - a)) / 255 + g: int = (sg * a + dg * (255 - a)) / 255 + b: int = (sb * a + db * (255 - a)) / 255 + self.back[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) + fx += 1 + fy += 1 + return if ci >= 128: ci = ci & 127 fy: int = 0 while fy < vpui.CHAR_H: @@ -1648,6 +1699,67 @@ class DeskServer: self.evt.d = d return desk.ring_push(self.shm + desk.desk_evt_ring_off(pid), self.evt) + def draw_text_request(self, win: int, x: int, y: int, color: uint32, text: char*): + s: desk.WinSlot* = self.slot(win) + if s.used == 0: return + _ensure_ttf() + buf: uint32* = uint32*(self.shm + uint64(s.fb_off)) + bw: int = int(s.fb_w) + bh: int = int(s.fb_h) + cx: int = x + i: int = 0 + while text[i] != 0: + ci: int = int(uint8(text[i])) + if _ttf_state == 1 and ci < 128: + fy: int = 0 + while fy < vpui.CHAR_H: + ty: int = y + fy + if ty >= 0 and ty < bh: + base: int = ty * bw + fx: int = 0 + while fx < vpui.CHAR_W: + a: int = int(_ttf_atlas[(ci * vpui.CHAR_H + fy) * vpui.CHAR_W + fx]) + if a != 0: + px: int = cx + fx + if px >= 0 and px < bw: + idx: int = base + px + if a >= 255: + buf[idx] = color + else: + dst: uint32 = buf[idx] + dr: int = int(dst & 0xFF) + dg: int = int((dst >> 8) & 0xFF) + db: int = int((dst >> 16) & 0xFF) + sr: int = int(color & 0xFF) + sg: int = int((color >> 8) & 0xFF) + sb: int = int((color >> 16) & 0xFF) + r: int = (sr * a + dr * (255 - a)) / 255 + g: int = (sg * a + dg * (255 - a)) / 255 + b: int = (sb * a + db * (255 - a)) / 255 + buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) + fx += 1 + fy += 1 + else: + c2: int = ci & 127 + fy = 0 + while fy < vpui.CHAR_H: + ty = y + fy + if ty >= 0 and ty < bh: + row: uint8 = vpui.font[c2][fy] + if row != uint8(0): + base = ty * bw + fx = 0 + while fx < vpui.CHAR_W: + if row & (0x80 >> fx): + px = cx + fx + if px >= 0 and px < bw: + buf[base + px] = color + fx += 1 + fy += 1 + cx += vpui.CHAR_W + i += 1 + self.mark(s.uy, s.h) + # ---------------- 请求处理 ---------------- def handle_request(self, m: desk.DeskMsg): if m.type == uint32(desk.DESK_REQ_CREATE): @@ -1665,6 +1777,9 @@ class DeskServer: s: desk.WinSlot* = self.slot(win) if s.used == 0: return ty: uint32 = m.type + if ty == uint32(desk.DESK_REQ_TEXT): + self.draw_text_request(win, m.a, m.b, uint32(m.c), &m.title[0]) + return if ty == uint32(desk.DESK_REQ_DESTROY): self._close_window(win) return diff --git a/build.py b/build.py index 11b7514..247f79b 100644 --- a/build.py +++ b/build.py @@ -95,9 +95,6 @@ def CopyFonts(): src = "NotoSansSC/NotoSansSC-6.ttf" if os.path.exists(src): shutil.copy(src, "./disk/fonts/font.ttf") - atlas = "vpsdk/vpui_font.atl" - if os.path.exists(atlas): - shutil.copy(atlas, "./disk/fonts/vpui.atl") def BuildLibs(): diff --git a/vpsdk/desk.vp b/vpsdk/desk.vp index 959c757..d5431ad 100644 --- a/vpsdk/desk.vp +++ b/vpsdk/desk.vp @@ -54,6 +54,7 @@ define DESK_REQ_TITLE = 5 # title 在数据区 define DESK_REQ_FLUSH = 6 # 像素已写,请求合成该窗口 define DESK_REQ_ACTIVATE = 7 # 置顶并聚焦 define DESK_REQ_CURSOR = 8 # a=cursor type(app 请求窗口内光标形状) +define DESK_REQ_TEXT = 9 # a=x b=y c=color,title=文字(explorer 托管绘制) # 消息类型:事件(explorer → app) define DESK_EVT_CREATED = 101 # a=win_id(失败为 -1) define DESK_EVT_CLOSE = 102 # 用户点了关闭按钮,app 应当退出 diff --git a/vpsdk/fonttools/fonttools.vp b/vpsdk/fonttools/fonttools.vp index eb0fd74..2608b20 100644 --- a/vpsdk/fonttools/fonttools.vp +++ b/vpsdk/fonttools/fonttools.vp @@ -500,6 +500,51 @@ def draw_text(font: Font*, fb: uint32*, fw: int, fh: int, x: int, baseline: int, i += int(adv) return int(pen) +_ascii_tmp: [128 * 128]uint32 + +def build_ascii_atlas(font: Font*, cw: int, ch: int) -> uint8*: + """Render ASCII 0x20..0x7e into an alpha atlas of cw x ch cells.""" + if font is None or font.loaded == 0: return None + if cw <= 0 or ch <= 0 or cw > 128 or ch > 128: return None + atlas: uint8* = uint8*(stdlib.calloc(1, uint64(128 * cw * ch))) + if atlas is None: return None + ci: int = 32 + while ci < 127: + gid: int = font.glyph_index(uint32(ci)) + if gid != 0: + r: uint64 = font.glyph_range(gid) + if r != 0: + base: uint8* = font.data + uint64(font.glyf_off) + (r >> 32) + xmin: int = _i16(base, 2) + ymin: int = _i16(base, 4) + xmax: int = _i16(base, 6) + ymax: int = _i16(base, 8) + bw: int = xmax - xmin + bh: int = ymax - ymin + if bw > 0 and bh > 0: + a: float = float(cw - 2) / float(bw) + d: float = float(ch - 4) / float(bh) + tx: float = 1.0 - float(xmin) * a + ty: float = -2.0 - float(ymax) * d + string.memset(&_ascii_tmp, 0, uint64(cw * ch * 4)) + global _edge_count, _min_x, _max_x, _min_y, _max_y + _edge_count = 0 + _min_x = 1.0e30 + _max_x = -1.0e30 + _min_y = 1.0e30 + _max_y = -1.0e30 + _add_glyph(font, gid, a, 0.0, 0.0, d, tx, ty, 0.0, 0.0, 1.0, 0) + _fill_edges(&_ascii_tmp[0], cw, ch, uint32(0x00FFFFFF)) + py: int = 0 + while py < ch: + px: int = 0 + while px < cw: + atlas[(ci * ch + py) * cw + px] = uint8(_ascii_tmp[py * cw + px] & 0xFF) + px += 1 + py += 1 + ci += 1 + return atlas + def load(path: const str) -> Font*: fd: int = fs.open(path, fs.O_RDONLY) if fd < 0: return None diff --git a/vpsdk/vpui.vp b/vpsdk/vpui.vp index 557807a..5f8efaa 100644 --- a/vpsdk/vpui.vp +++ b/vpsdk/vpui.vp @@ -2,7 +2,7 @@ import vpsdk.window as window import vpsdk.process as process import vpsdk.devfs as devfs import vpsdk.stdlib as stdlib -import vpsdk.fs as fs +import vpsdk.fonttools.fonttools as fonttools import string define TITLE_H = 32 @@ -10,6 +10,11 @@ define MAX_WIDGETS = 24 define CHAR_W = 8 define CHAR_H = 16 +define TEXT_MODE_DIRECT = 0 +define TEXT_MODE_MANAGED = 1 +define TEXT_QUEUE_MAX = 256 +define TEXT_CHUNK = 28 + define WIDGET_LABEL = 1 define WIDGET_BUTTON = 2 define WIDGET_ENTRY = 3 @@ -248,70 +253,109 @@ font: [256][16]uint8 = [ [0x00,0x00,0x70,0x13,0x24,0x26,0x24,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], ] -_ttf_font: uint8* = None -_ttf_state: int = 0 +class TextCmd: + x: int + y: int + color: uint32 + s: [32]char -def _ensure_font(): - global _ttf_font, _ttf_state +_text_mode: int = TEXT_MODE_MANAGED +_ttf_font: fonttools.Font* = None +_ttf_atlas: uint8* = None +_ttf_state: int = 0 +_text_queue: [TEXT_QUEUE_MAX]TextCmd +_text_qn: int = 0 + +def set_text_mode(mode: int): + global _text_mode + _text_mode = mode + +def _ensure_ttf(): + global _ttf_font, _ttf_atlas, _ttf_state if _ttf_state != 0: return - fd: int = fs.open("/FONTS/VPUI.ATL", fs.O_RDONLY) - if fd < 0: + f: fonttools.Font* = fonttools.load("/FONTS/FONT.TTF") + if f is None: _ttf_state = -1 return - buf: uint8* = uint8*(stdlib.malloc(uint64(16384))) - if buf is None: - fs.close(fd) + a: uint8* = fonttools.build_ascii_atlas(f, CHAR_W, CHAR_H) + if a is None: _ttf_state = -1 return - got: uint64 = 0 - while got < uint64(16384): - n: int = fs.read(fd, rawptr(buf + got), uint32(16384) - uint32(got)) - if n <= 0: break - got += uint64(n) - fs.close(fd) - if got != uint64(16384): - stdlib.free(buf) - _ttf_state = -1 - return - _ttf_font = buf + _ttf_font = f + _ttf_atlas = a _ttf_state = 1 +def _queue_text(x: int, y: int, s: const str, color: uint32): + global _text_qn + if _text_mode != TEXT_MODE_MANAGED: return + cx: int = x + cy: int = y + i: int = 0 + while s[i] != 0: + if s[i] == 10: + cx = x + cy += CHAR_H + i += 1 + continue + if _text_qn >= TEXT_QUEUE_MAX: return + cmd: TextCmd* = &_text_queue[_text_qn] + cmd.x = cx + cmd.y = cy + cmd.color = color + n: int = 0 + while n < TEXT_CHUNK and s[i] != 0 and s[i] != 10: + cmd.s[n] = s[i] + n += 1 + i += 1 + cmd.s[n] = 0 + _text_qn += 1 + cx += n * CHAR_W + +def _flush_text(win_id: int): + global _text_qn + if _text_qn == 0: return + for i in range(_text_qn): + cmd: TextCmd* = &_text_queue[i] + window.text(win_id, cmd.x, cmd.y, cmd.color, &cmd.s[0]) + _text_qn = 0 + 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: int = int(uint8(ch)) - _ensure_font() - if _ttf_state == 1 and ci < 128: - fy: int = 0 - while fy < CHAR_H: - py: int = y + fy - if py >= 0 and py < bh: - fx: int = 0 - while fx < CHAR_W: - a: int = int(_ttf_font[(ci * 16 + fy) * 8 + fx]) - if a != 0: - px: int = x + fx - if px >= 0 and px < bw: - idx: int = py * bw + px - if a >= 255: - buf[idx] = color - else: - dst: uint32 = buf[idx] - dr: int = int(dst & 0xFF) - dg: int = int((dst >> 8) & 0xFF) - db: int = int((dst >> 16) & 0xFF) - sr: int = int(color & 0xFF) - sg: int = int((color >> 8) & 0xFF) - sb: int = int((color >> 16) & 0xFF) - r: int = (sr * a + dr * (255 - a)) / 255 - g: int = (sg * a + dg * (255 - a)) / 255 - b: int = (sb * a + db * (255 - a)) / 255 - buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) - fx += 1 - fy += 1 - return + if _text_mode == TEXT_MODE_DIRECT: + _ensure_ttf() + if _ttf_state == 1 and ci < 128: + fy: int = 0 + while fy < CHAR_H: + py: int = y + fy + if py >= 0 and py < bh: + fx: int = 0 + while fx < CHAR_W: + a: int = int(_ttf_atlas[(ci * CHAR_H + fy) * CHAR_W + fx]) + if a != 0: + px: int = x + fx + if px >= 0 and px < bw: + idx: int = py * bw + px + if a >= 255: + buf[idx] = color + else: + dst: uint32 = buf[idx] + dr: int = int(dst & 0xFF) + dg: int = int((dst >> 8) & 0xFF) + db: int = int((dst >> 16) & 0xFF) + sr: int = int(color & 0xFF) + sg: int = int((color >> 8) & 0xFF) + sb: int = int((color >> 16) & 0xFF) + r: int = (sr * a + dr * (255 - a)) / 255 + g: int = (sg * a + dg * (255 - a)) / 255 + b: int = (sb * a + db * (255 - a)) / 255 + buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) + fx += 1 + fy += 1 + return if ci >= 128: ci = 0 - fy = 0 + fy: int = 0 while fy < CHAR_H: if y + fy < 0 or y + fy >= bh: fy += 1 @@ -330,6 +374,9 @@ def _draw_char(buf: uint32*, bw: int, bh: int, x: int, y: int, ch: char, color: 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 + if _text_mode == TEXT_MODE_MANAGED: + _queue_text(x, y, s, color) + return cx: int = x cy: int = y i: int = 0 @@ -337,11 +384,11 @@ def _draw_text(buf: uint32*, bw: int, bh: int, x: int, y: int, s: const str, col ch: char = s[i] if ch == 0: break if ch == 10: - cy += 16 + cy += CHAR_H cx = x else: _draw_char(buf, bw, bh, cx, cy, ch, color) - cx += 8 + cx += CHAR_W i += 1 # ============================================================================ @@ -1741,6 +1788,7 @@ virtual class Tk(Wm, Misc): self.bg_color = uint32(0xFF231919) def redraw(self): + global _text_qn # 窗口槽已回收(用户点了关闭):立即返回,不再触碰共享 arena / flush。 if self.win_id < 0: return if window.is_active(self.win_id) == 0: return @@ -1753,6 +1801,7 @@ virtual class Tk(Wm, Misc): if self.bw <= 0 or self.bh <= 0: return # 先刷新尺寸再布局:窗口 resize 后若沿用上一帧的 bw/bh,布局会滞后一帧。 self._do_layout() + _text_qn = 0 # 双缓冲:整帧先画在**进程本地**的后备缓冲里,画完一次性拷进共享 arena,再 flush。 # 为什么必须这么做:arena 正是 explorer 合成时读的那块内存(render_window_rows # 直接从它逐行 blit 上屏)。若直接往 arena 上画,合成器会在"整窗已填成背景色、 @@ -1772,6 +1821,7 @@ virtual class Tk(Wm, Misc): w.draw(tgt, self.bw, self.bh) if tgt != self.buf: string.memcpy(self.buf, tgt, uint64(px * 4)) + _flush_text(self.win_id) window.flush(self.win_id) def _do_layout(self): diff --git a/vpsdk/vpui_font.atl b/vpsdk/vpui_font.atl deleted file mode 100644 index 29b17cd..0000000 Binary files a/vpsdk/vpui_font.atl and /dev/null differ diff --git a/vpsdk/window.vp b/vpsdk/window.vp index d83c857..a24aa8f 100644 --- a/vpsdk/window.vp +++ b/vpsdk/window.vp @@ -191,6 +191,14 @@ def flush(win_id: int): if s.used == 0: return _send(uint32(desk.DESK_REQ_FLUSH), win_id, 0, 0, 0, 0, None) +def text(win_id: int, x: int, y: int, color: uint32, s: const str) -> int: + if _connect() != 0: return -1 + for _ in range(10000): + if _send(uint32(desk.DESK_REQ_TEXT), win_id, x, y, int(color), 0, s) == 0: + return 0 + process.yield_cpu() + return -1 + def is_active(win_id: int) -> int: if _connect() != 0: return 0 if win_id < 0 or win_id >= desk.DESK_MAX_WIN: return 0