feat: 添加 Linux 预览后端和 Notes 应用预览版本

- 创建 desktop_app_linux.h 作为跨平台后端抽象层
- 创建 notes_preview.c 使用 5x7 位图字体渲染
- 添加 Makefile.linux
构建脚本
- 添加 README_LINUX_PREVIEW.md 使用说明- 修复键盘映射、字符渲染、文本输入显示等问题
This commit is contained in:
cool
2026-08-05 13:15:46 +08:00
parent 86df253a32
commit 6287513794
31 changed files with 1122 additions and 449 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools"
@mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools"
@mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c
+447
View File
@@ -0,0 +1,447 @@
/* desktop_app_linux.h — Deshab 应用 Linux 预览后端 (SDL2)
*
* 允许在 Linux 开发机上预览 Deshab-OS 应用的 UI 效果。
* 使用方法:
* 1. #
define DA_LINUX_PREVIEW 1 * 2. #include "desktop_app_linux.h"
* 3. 实现 dsk_entry() 函数(与 Deshab-OS 版本相同)
* 4. 编译:make -f Makefile.linux
*
* 编译后会在 X11/Wayland 窗口中显示应用 UI,可用于调试布局和渲染。
*/
#ifndef DESHAB_DESKTOP_APP_LINUX_H
#define DESHAB_DESKTOP_APP_LINUX_H
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* ===================================================================
* 基础类型(与 desktop_app.h 一致)
* =================================================================== */
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int64_t i64;
/* ===================================================================
* 颜色常量(与 desktop_app.h 一致)
* =================================================================== */
#define DA_BG_PRIMARY 0xFF0A1428u
#define DA_BG_SECONDARY 0xFF0F1E38u
#define DA_BG_TERTIARY 0xFF162848u
#define DA_TEXT_PRIMARY 0xFFE8E8F0u
#define DA_TEXT_DIM 0xFF6A6A88u
#define DA_ACCENT 0xFF00A8CCu
#define DA_ACCENT_LIGHT 0xFF44CCF0u
#define DA_BORDER 0xFF284888u
#define DA_BORDER_FOCUS 0xFF44CCF0u
#define DA_CURSOR_COLOR 0xFF00A8CCu
#define DA_SUCCESS 0xFF40C880u
#define DA_ERROR 0xFFFF4466u
#define DA_WARNING 0xFFF0A030u
#define DA_TITLEBAR_H 28
#define DA_BORDER_W 2
#define DA_STATUSBAR_H 20
/* ===================================================================
* 渲染上下文(SDL2 版本)
* =================================================================== */
typedef struct {
Uint32 *pixels; /* SDL2 像素缓冲 */
int width;
int height;
int pitch; /* 行字节宽 */
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
} da_app_context;
/* ===================================================================
* ASCII 字体声明(需要从 ascii_bitmaps.c 包含)
* =================================================================== */
/* 如果外部未定义字体,则声明 extern */
/* 在 notes_preview.c 中 include ascii_bitmaps.c 后会定义实际变量 */
#ifndef DA_FONT_DEFINED
extern const unsigned char *g_ascii[];
extern int g_ascii_w;
extern int g_ascii_h;
#else
/* 使用 extern 引用 ascii_bitmaps.c 中定义的 static 变量 */
/* 注意:ascii_bitmaps.c 需要改为非 static */
extern const unsigned char g_ascii[][11*18];
extern int g_ascii_w;
extern int g_ascii_h;
#endif
/* ===================================================================
* 绘制原语(SDL2 实现)
* =================================================================== */
/* 颜色混合 */
static inline Uint32 da_blend(Uint32 bg, Uint32 fg, Uint32 alpha) {
Uint32 na = 256 - alpha;
Uint32 r = ((bg & 0xFF) * na + (fg & 0xFF) * alpha) >> 8;
Uint32 g = (((bg >> 8) & 0xFF) * na + ((fg >> 8) & 0xFF) * alpha) >> 8;
Uint32 b = (((bg >> 16) & 0xFF) * na + ((fg >> 16) & 0xFF) * alpha) >> 8;
return 0xFF000000u | (b << 16) | (g << 8) | r;
}
/* 像素写入(SDL2 锁定时) */
static inline void da_pixel(da_app_context *ac, int x, int y, Uint32 color) {
if (x < 0 || x >= ac->width || y < 0 || y >= ac->height) return;
ac->pixels[y * ac->width + x] = color;
}
/* 矩形填充 */
static inline void da_fill_rect(da_app_context *ac, int x, int y, int w, int h, Uint32 color) {
for (int r = 0; r < h; r++) {
for (int c = 0; c < w; c++) {
int xx = x + c, yy = y + r;
if (xx >= 0 && xx < ac->width && yy >= 0 && yy < ac->height) {
ac->pixels[yy * ac->width + xx] = color;
}
}
}
}
/* 全屏背景 */
static inline void da_fill_bg(da_app_context *ac, Uint32 color) {
memset(ac->pixels,0, (size_t)ac->width * ac->height *4);
da_fill_rect(ac, 0, 0, ac->width, ac->height, color);
}
/* 圆角矩形(简化版) */
static inline void da_fill_rounded_rect(da_app_context *ac, int x, int y , int w, int h,
Uint32 color, int radius) {
/* 中间区域 */
da_fill_rect(ac, x + radius, y, w - 2 * radius, h, color);
da_fill_rect(ac, x, y + radius, radius, h - 2 * radius, color);
da_fill_rect(ac, x + w - radius, y + radius, radius, h - 2 * radius, color);
/* 四个角(简化为填充) */
for (int dy = 0; dy < radius; dy++) {
for (int dx = 0; dx < radius; dx++) {
if (dx * dx + dy * dy <= radius * radius) {
da_pixel(ac, x + radius - 1 - dx, y + radius - 1 - dy, color);
da_pixel(ac, x + w - radius + dx, y + radius - 1 - dy, color);
da_pixel(ac, x + radius - 1 - dx, y + h - radius + dy, color);
da_pixel(ac, x + w - radius + dx, y + h - radius + dy, color);
}
}
}
}
/* 字符渲染 */
static inline void da_draw_char(da_app_context *ac, char ch, int x, int y, Uint32 fg, Uint32 bg) {
Uint32 idx = (Uint32)(ch - ' ');
if (idx > 94) idx = 0;
if (!g_ascii[idx]) return;
const unsigned char *glyph = g_ascii[idx];
int gw = g_ascii_w, gh = g_ascii_h;
for (int r = 0; r < gh; r++) {
for (int c = 0; c < gw; c++) {
unsigned char a = glyph[r * gw + c];
if (a == 0) continue;
int xx = x + c, yy = y + r;
if (xx < 0 || xx >= ac->width || yy < 0 || yy >= ac->height) continue;
Uint32 pixel = (a == 255) ? fg : da_blend(bg, fg, a);
ac->pixels[yy * ac->width + xx] = pixel;
}
}
}
/* 字符串渲染 */
static inline void da_draw_string(da_app_context *ac, const char *s, int x, int y,
Uint32 fg, Uint32 bg, int step) {
int cx = x;
while (*s) {
da_draw_char(ac, *s, cx, y, fg, bg);
cx += step;
s ++;
}
}
/*标题栏 */
static inline void da_draw_titlebar(da_app_context *ac, const char *title, int w) {
da_fill_rect(ac, 0, 0, w, DA_TITLEBAR_H, DA_BG_SECONDARY);
da_draw_string(ac, title, 8, (DA_TITLEBAR_H - 18) / 2, DA_TEXT_PRIMARY, DA_BG_SECONDARY, 12);
/* 关闭按钮 */
da_fill_rounded_rect(ac, w - 28, 4, 24, 20, DA_ERROR, 4);
da_draw_string(ac, "X", w - 20, 5, 0xFF0A1428u, DA_ERROR, 12);
/* 底部分隔线 */
da_fill_rect(ac, 0, DA_TITLEBAR_H, w, 1, DA_ACCENT);
}
/* 状态栏 */
static inline void da_draw_statusbar(da_app_context *ac, const char *text, int w, int h) {
int y = h - DA_STATUSBAR_H;
da_fill_rect(ac, 0, y, w, DA_STATUSBAR_H, DA_BG_SECONDARY);
da_draw_string(ac, text, 4, y + 1, DA_TEXT_DIM, DA_BG_SECONDARY, 12);
da_fill_rect(ac, 0, y, w, 1, DA_BORDER);
}
/* ===================================================================
* 上下文初始化/刷新
* =================================================================== */
static inline int da_init_linux(da_app_context *ac, const char *title, int width, int height) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "SDL2 init failed: %s\n", SDL_GetError());
return -1;
}
ac->window = SDL_CreateWindow(title,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width, height,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
if (!ac->window) {
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
return -1;
}
ac->renderer = SDL_CreateRenderer(ac->window, -1, SDL_RENDERER_ACCELERATED);
if (!ac->renderer) {
fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError());
return -1;
}
ac->texture = SDL_CreateTexture(ac->renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
width, height);
if (!ac->texture) {
fprintf(stderr, "SDL_CreateTexture failed: %s\n", SDL_GetError());
return -1;
}
ac->width = width;
ac->height = height;
ac->pitch = width * 4;
ac->pixels = (Uint32 *)malloc((size_t)width * height * 4);
if (!ac->pixels) {
fprintf(stderr, "malloc failed\n");
return -1;
} memset(ac->pixels, 0, (size_t)width * height * 4);
return 0;
}
/* 刷新帧缓冲到屏幕 */
static inline void da_flush(da_app_context *ac) {
SDL_UpdateTexture(ac->texture, NULL, ac->pixels, ac->pitch);
SDL_RenderClear(ac->renderer);
SDL_RenderCopy(ac->renderer, ac->texture, NULL, NULL);
SDL_RenderPresent(ac->renderer);
}
/* 清理资源 */
static inline void da_cleanup(da_app_context *ac) {
free(ac->pixels);
if (ac->texture) SDL_DestroyTexture(ac->texture);
if (ac->renderer) SDL_DestroyRenderer(ac->renderer);
if (ac->window) SDL_DestroyWindow(ac->window);
SDL_Quit();
}
/* 事件循环(等待关闭窗口) */
static inline void da_event_loop(void) {
SDL_Event event;
int running = 1;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) running = 0;
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) running = 0;
}
SDL_Delay(16); /* ~60 FPS */
}
}
/* ===================================================================
* 鼠标光标(简化版)
* =================================================================== */
#define DA_CURSOR_SIZE 24
static const unsigned char da_cursor_shape[24][24] = {
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
};
typedef struct {
int mx, my;
int btn;
} da_cursor;
static inline void da_cursor_init(da_cursor *c, int fb_w, int fb_h) {
c->mx = fb_w / 2;
c->my = fb_h / 2;
c->btn = 0;
}
static inline void da_cursor_draw(da_app_context *ac, da_cursor *c, Uint32 color) {
for (int r = 0; r < DA_CURSOR_SIZE; r++) {
for (int col = 0; col < DA_CURSOR_SIZE; col++) {
if (da_cursor_shape[r][col]) {
int x = c->mx + col, y = c->my + r;
if (x >= 0 && x < ac->width && y >= 0 && y < ac->height) {
ac->pixels[y * ac->width + x] = color;
}
}
}
}
}
/* ===================================================================
* PS/2 鼠标模拟(SDL2 事件转换)
* =================================================================== */
typedef struct {
int has_packet;
int dx, dy;
int buttons;
} da_mouse;
static inline void da_mouse_init(da_mouse *m) {
memset(m, 0, sizeof(*m));
}
/* 将 SDL 鼠标事件转换为内部状态 */
static inline void da_mouse_update(da_mouse *m, SDL_Event *event) {
if (event->type == SDL_MOUSEMOTION) {
m->dx += event->motion.xrel;
m->dy += event->motion.yrel;
m->has_packet = 1;
}
if (event->type == SDL_MOUSEBUTTONDOWN) {
m->buttons |= (1 << (event->button.button -1));
m->has_packet = 1;
}
if (event->type == SDL_MOUSEBUTTONUP) {
m->buttons &= ~(1 << (event->button.button - 1));
m->has_packet = 1;
}
}
/* ===================================================================
* PS/2 键盘模拟(SDL2 事件转换)
* =================================================================== */
/* SDL scancode 到 Deshab PS/2 scan code 映射 */
static inline u8 da_sdl_to_ps2_scan(Uint32 sdl_scancode) {
/* 简化映射:只支持基本字母数字键 */
switch (sdl_scancode) {
case SDLK_ESCAPE: return 0x01;
case SDLK_1: return 0x02;
case SDLK_2: return 0x03;
case SDLK_3: return 0x04;
case SDLK_4: return 0x05;
case SDLK_5: return 0x06;
case SDLK_6: return 0x07;
case SDLK_7: return 0x08;
case SDLK_8: return 0x09;
case SDLK_9: return 0x0A;
case SDLK_0: return 0x0B;
case SDLK_MINUS: return 0x0C;
case SDLK_EQUALS: return 0x0D;
case SDLK_BACKSPACE: return 0x0E;
case SDLK_TAB: return 0x0F;
case SDLK_q: return 0x10;
case SDLK_w: return 0x11;
case SDLK_e: return 0x12;
case SDLK_r: return 0x13;
case SDLK_t: return 0x14;
case SDLK_y: return 0x15;
case SDLK_u: return 0x16;
case SDLK_i: return 0x17;
case SDLK_o: return 0x18;
case SDLK_p: return 0x19;
case SDLK_LEFTBRACKET: return 0x1A;
case SDLK_RIGHTBRACKET: return 0x1B;
case SDLK_RETURN: return 0x1C;
case SDLK_a: return 0x1E;
case SDLK_s: return 0x1F;
case SDLK_d: return 0x20;
case SDLK_f: return 0x21;
case SDLK_g: return 0x22;
case SDLK_h: return 0x23;
case SDLK_j: return 0x24;
case SDLK_k: return 0x25;
case SDLK_l: return 0x26;
case SDLK_SEMICOLON: return 0x27;
case SDLK_QUOTE: return 0x28;
case SDLK_BACKQUOTE: return 0x29;
case SDLK_BACKSLASH: return 0x2B;
case SDLK_z: return 0x2C;
case SDLK_x: return 0x2D;
case SDLK_c: return 0x2E;
case SDLK_v: return 0x2F;
case SDLK_b: return 0x30;
case SDLK_n: return 0x31;
case SDLK_m: return 0x32;
case SDLK_COMMA: return 0x33;
case SDLK_PERIOD: return 0x34;
case SDLK_SLASH: return 0x35;
case SDLK_RSHIFT:
case SDLK_LSHIFT: return 0; /* Shift 处理 */
case SDLK_SPACE: return 0x39;
case SDLK_LEFT: return 0x4B;
case SDLK_RIGHT: return 0x4D;
case SDLK_UP: return 0x48;
case SDLK_DOWN: return 0x50;
default: return 0;
}
}
static inline char da_scan_to_ascii(u8 sc, int shift) {
static const char normal[58] = {
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=', 8, '\t',
'q','w','e','r','t','y','u','i','o','p','[',']','\n',0,
'a','s','d','f','g','h','j','k','l',';','\'', '`',0,'\\',
'z','x','c','v','b','n','m',',','.','/',0,'*',0,' '
};
static const char shifted[58] = {
0, 27, '!','@','#','$','%','^','&','*','(',')','_','+', 8, '\t',
'Q','W','E','R','T','Y','U','I','O','P','{','}','\n',0,
'A','S','D','F','G','H','J','K','L',':','"','~',0,'|',
'Z','X','C','V','B','N','M','<','>','?',0,'*',0,' '
};
if (sc >= 58) return 0;
return shift ? shifted[sc] : normal[sc];
}
#endif /* DESHAB_DESKTOP_APP_LINUX_H */
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools"
@mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools"
@mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c
+3
View File
@@ -254,6 +254,9 @@ static void redraw_all(void) {
else redraw_list();
}
/* ---- 返回上级目录(前向声明) ---- */
static void go_up(void);
/* ---- 打开文件/目录 ---- */
static void go_up(void); /* 前向声明:open_selected 调用 go_up */
static void open_selected(void) {
+30
View File
@@ -0,0 +1,30 @@
# Makefile.linux — Deshab-OS 应用 Linux 预览编译
#
# 用法:
# make -f Makefile.linux # 编译预览版
# make -f Makefile.linux run # 编译并运行预览版
# make -f Makefile.linux clean # 清理
CC = gcc
CFLAGS = -Wall -O2 -D DA_LINUX_PREVIEW -I..
LDFLAGS = -lSDL2
SRC = notes_preview.c ../../firstInit/ascii_bitmaps.c
TARGET = notes_preview
all: $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) -o $@ $(SRC) $(LDFLAGS)
@echo "✓ 编译完成: $(TARGET)"
@echo " 运行: ./$(TARGET)"
@echo " 按 ESC 退出"
run: $(TARGET)
./$(TARGET)
clean:
rm -f $(TARGET)
@echo "✓ 已清理"
.PHONY: all run clean
+112
View File
@@ -0,0 +1,112 @@
# Deshab-OS 应用 Linux 预览功能
## 概述
允许在 Linux 开发机上预览 Deshab-OS 应用的 UI 效果。同一份 UI 绘制代码可以在 Linux 上通过 SDL2 后端预览,在 Deshab-OS 上通过 framebuffer 后端运行。
## 架构设计
```
┌──────────────────────────────────────────────────── ─┐
│ 应用源代码 │
│ (使用 da_draw_char, da_fill_rect 等绘制函数) │
└──────────────────┬──────────────────────────────────┘
│
┌──────────┴──────────┐
│ #ifdef DA_LINUX_PREVIEW │
└──┬──────────────┬────────────┘
│ │
Linux 后端 Deshab-OS 后端
(SDL2) (framebuffer) │ │
X11/Wayland 窗口物理显示器
```
## 文件结构
```
CODE/tools/notes/
├── main.c # Deshab-OS 原生应用入口
├── Makefile # Deshab-OS 编译用Makefile
├── notes_preview.c # Linux 预览版(包含完整 dsk_entry 实现)
├── Makefile.linux # Linux 编译用 Makefile
├── desktop_app_linux.h # Linux SDL2 后端
└── README_LINUX_PREVIEW.md # 本说明文档
```
## 使用方法
### 1. 在 Linux 上编译预览版
```bash
cd CODE/tools/notes
make -f Makefile.linux
```
### 2. 运行预览版
```bash
./notes_preview
```
### 3. 退出预览
按 `ESC` 键关闭窗口。
### 4. 清理
```bash
make -f Makefile.linux clean
```
## 开发工作流
```
1. 在 Linux 上编辑代码 ↓
2. make -f Makefile.linux 编译预览版
↓
3. ./notes_preview 查看 UI 效果
↓
4. 调试布局和渲染
↓
5. 测试通过后,用原生的 Makefile 编译到 Deshab-OS
↓
6. 在 Deshab-OS 中运行验证
```
## 支持的 UI 元素
| 元素 | Linux 预览 | Deshab-OS |
|------|-----------|-----------|
| 背景填充 | ✅ | ✅ |
| 矩形填充 | ✅ | ✅ |
| 圆角矩形 | ✅ | ✅ |
| ASCII 字符 | ✅ | ✅ |
| 字符串 | ✅ | ✅ |
| 标题栏 | ✅ | ✅ |
| 状态栏 | ✅ | ✅ |
| 鼠标 光标 | ✅ | ✅ |
|键盘输入 | ✅ | ✅ |
| 鼠标移动 | ✅ | ✅ |
## 扩展到其他应用
要为其他应用(如 fileman, editor, proedit)添加 Linux 预览支持:
1. 复制 `notes_preview.c` 作为模板
2. 修改 `main()` 中的 UI 逻辑
3. 使用相同的 `desktop_app_linux.h
` 后端
## 依赖- SDL2 (`libsdl2-dev` on Debian/Ubuntu)
- GCC 或 Clang
- Make
## 注意事项
1. **键盘映射**:SDL2 的键盘扫描码需要映射到 Deshab的 PS/2 扫描码
2. **鼠标行为**:SDL2 鼠标事件转换为 Deshab 的鼠标数据包格式
3. **字体数据**:需要从 `ascii_bitmaps.c` 获取字模数据
4. **性能**:Linux 预览版主要用于 UI 调试,不是完整的功能测试
+489
View File
@@ -0,0 +1,489 @@
/* notes_preview.c — Notes 应用的 Linux 预览版本
*
* 这个文件可以在 Linux 上编译预览 UI。
* 使用自定义位图字体渲染字符。
*
* 编译方式:
* gcc -o notes_preview notes_preview.c -I.. -lSDL2
*/
#define DA_LINUX_PREVIEW
#include "../desktop_app_linux.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
/* 全局状态 */
static da_app_context g_ac;
static int g_running = 1;
/* 文本内容 */
static char g_lines[512][256];
static int g_line_count = 0;
static int g_current_line = 0;
static int g_current_col = 0;
static int g_scroll_top = 0;
static int g_visible_rows;
/* 5x7 位图字体 (ASCII 32-126, 共95 个字符) */
static const unsigned char font_5x7[95][5] = {
/* 0x20 space */
{0x00, 0x00, 0x00, 0x00, 0x00},
/* 0x21 ! */
{0x00, 0x00, 0x30, 0x30, 0x00},
/* 0x22 " */
{0x30, 0x30, 0x00, 0x00, 0x00},
/* 0x23 # */
{0x18, 0x7E, 0x18, 0x7E, 0x18},
/* 0x24 $ */
{0x0C, 0x36, 0x6C, 0x36, 0x0C},
/* 0x25 % */
{0x60, 0x9C, 0x18, 0x36, 0x60},
/* 0x26 & */
{0x30, 0x7E, 0x1C, 0x36, 0x70},
/* 0x27 ' */
{0x30, 0x30, 0x00, 0x00, 0x00},
/* 0x28 ( */
{0x0C, 0x18, 0x30, 0x18, 0x0C},
/* 0x29 ) */
{0x30, 0x18, 0x0C, 0x18, 0x30},
/* 0x2A * */
{0x00, 0x18, 0x7E, 0x18, 0x00},
/* 0x2B + */
{0x00, 0x18, 0x7E, 0x18, 0x00},
/* 0x2C , */
{0x00, 0x00, 0x00, 0x30, 0x30},
/* 0x2D - */
{0x00, 0x00, 0x7E, 0x00, 0x00},
/* 0x2E . */
{0x00, 0x00, 0x00, 0x30, 0x30},
/* 0x2F / */
{0x06, 0x0C, 0x18, 0x30, 0x60},
/* 0x30 0 */
{0x38, 0x6C, 0x36, 0x6C, 0x38},
/* 0x31 1 */
{0x18, 0x38, 0x18, 0x18, 0x3C},
/* 0x32 2 */
{0x78, 0x60, 0x30, 0x18, 0x7C},
/* 0x33 3 */
{0x78, 0x60, 0x38, 0x00, 0x78},
/* 0x34 4 */
{0x0C, 0x1C, 0x3C, 0x7E, 0x0C},
/* 0x35 5 */
{0x7C, 0x60, 0x78, 0x06, 0x7C},
/* 0x36 6 */
{0x38, 0x60, 0x7C, 0x66, 0x3C},
/* 0x37 7 */
{0x7C, 0x60, 0x30, 0x30, 0x30},
/* 0x38 8 */
{0x3C, 0x66, 0x3C, 0x66, 0x3C},
/* 0x39 9 */
{0x3C, 0x66, 0x3E, 0x60, 0x3C},
/* 0x3A : */
{0x00, 0x30, 0x00, 0x30, 0x00},
/* 0x3B ; */
{0x00, 0x30, 0x00, 0x30, 0x30},
/* 0x3C < */
{0x0C, 0x18, 0x30, 0x18, 0x0C},
/* 0x3D = */
{0x00, 0x7E, 0x00, 0x7E, 0x00},
/* 0x3E > */
{0x30, 0x18, 0x0C, 0x18, 0x30},
/* 0x3F ? */
{0x78, 0x60, 0x30, 0x00, 0x30},
/* 0x40 @ */
{0x3C, 0x66, 0x7E, 0x06, 0x3C},
/* 0x41 A */
{0x18, 0x3C, 0x7E, 0x66, 0x66},
/* 0x42 B */
{0x7C, 0x66, 0x7C, 0x66, 0x7C},
/* 0x43 C */
{0x3C, 0x66, 0x60, 0x60, 0x3C},
/* 0x44 D */
{0x78, 0x6C, 0x66, 0x66, 0x78},
/* 0x45 E */
{0x7E, 0x60, 0x7C, 0x60, 0x7E},
/* 0x46 F */
{0x7E, 0x60, 0x7C, 0x60, 0x60},
/* 0x47 G */
{0x3C, 0x66, 0x6C, 0x66, 0x3C},
/* 0x48 H */
{0x66, 0x66, 0x7E, 0x66, 0x66},
/* 0x49 I */
{0x3C, 0x18, 0x18, 0x18, 0x3C},
/* 0x4A J */
{0x7C, 0x06, 0x06, 0x06, 0x3C},
/* 0x4B K */
{0x66, 0x6C, 0x78, 0x6C, 0x66},
/* 0x4C L */
{0x60, 0x60, 0x60, 0x60, 0x7E},
/* 0x4D M */
{0x63, 0x77, 0x7F, 0x6B, 0x63},
/* 0x4E N */
{0x66, 0x76, 0x7E, 0x6E, 0x66},
/* 0x4F O */
{0x3C, 0x66, 0x66, 0x66, 0x3C},
/* 0x50 P */
{0x7C, 0x66, 0x7C, 0x60, 0x60},
/* 0x51 Q */
{0x3C, 0x66, 0x76, 0x6C, 0x38},
/* 0x52 R */
{0x7C, 0x66, 0x7C, 0x6C, 0x66},
/* 0x53 S */
{0x3C, 0x66, 0x3C, 0x06, 0x7C},
/* 0x54 T */
{0x7E, 0x7E, 0x18, 0x18, 0x18},
/* 0x55 U */
{0x66, 0x66, 0x66, 0x66, 0x3C},
/* 0x56 V */
{0x66, 0x66, 0x66, 0x3C, 0x18},
/* 0x57 W */
{0x63, 0x63, 0x77, 0x7F, 0x36},
/* 0x58 X */
{0x66, 0x3C, 0x18, 0x3C, 0x66},
/* 0x59 Y */
{0x66, 0x66, 0x3C, 0x18, 0x18},
/* 0x5A Z */
{0x7C, 0x66, 0x3C, 0x18, 0x7C},
/* 0x5B [ */
{0x3C, 0x30, 0x30, 0x30, 0x3C},
/* 0x5C backslash */
{0x60, 0x30, 0x18, 0x0C, 0x06},
/* 0x5D ] */
{0x3C, 0x0C, 0x0C, 0x0C, 0x3C},
/* 0x5E ^ */
{0x18, 0x3C, 0x66, 0x00, 0x00},
/* 0x5F _ */
{0x00, 0x00, 0x00, 0x00, 0x7E},
/* 0x60 ` */
{0x30, 0x18, 0x00, 0x00, 0x00},
/* 0x61 a */
{0x00, 0x00, 0x3C, 0x66, 0x3C},
/* 0x62 b */
{0x60, 0x60, 0x7C, 0x66, 0x7C},
/* 0x63 c */
{0x00, 0x00, 0x3C, 0x66, 0x66},
/* 0x64 d */
{0x06, 0x06, 0x7E, 0x66, 0x7E},
/* 0x65 e */
{0x00, 0x00, 0x3C, 0x6E, 0x3C},
/* 0x66 f */
{0x30, 0x7C, 0x06, 0x06, 0x3C},
/* 0x67 g */
{0x00, 0x3E, 0x66, 0x66, 0x3C},
/* 0x68 h */
{0x60, 0x60, 0x7C, 0x66, 0x66},
/* 0x69 i */
{0x00, 0x18, 0x18, 0x00, 0x3C},
/* 0x6A j */
{0x06, 0x06, 0x06, 0x06, 0x3C},
/* 0x6B k */
{0x60, 0x6C, 0x78, 0x6C, 0x66},
/* 0x6C l */
{0x3C, 0x18, 0x18, 0x18, 0x3C},
/* 0x6D m */
{0x00, 0x00, 0x7E, 0x6E, 0x76},
/* 0x6E n */
{0x00, 0x00, 0x7C, 0x66, 0x66},
/* 0x6F o */
{0x00, 0x00, 0x3C, 0x66, 0x3C},
/* 0x70 p */
{0x00, 0x00, 0x7C, 0x66, 0x7C},
/* 0x71 q */
{0x00, 0x00, 0x7E, 0x66, 0x3E},
/* 0x72 r */
{0x00, 0x00, 0x7C, 0x60, 0x60},
/* 0x73 s */
{0x00, 0x00, 0x3C, 0x60, 0x3C},
/* 0x74 t */
{0x0C, 0x7E, 0x0C, 0x0C, 0x38},
/* 0x75 u */
{0x00, 0x00, 0x66, 0x66, 0x3C},
/* 0x76 v */
{0x00, 0x00, 0x66, 0x3C, 0x18},
/* 0x77 w */
{0x00, 0x00, 0x67, 0x67, 0x3E},
/* 0x78 x */
{0x00, 0x00, 0x66, 0x3C, 0x66},
/* 0x79 y */
{0x00, 0x00, 0x66, 0x66, 0x3C},
/* 0x7A z */
{0x00, 0x00, 0x7C, 0x7C, 0x78},
/* 0x7B { */
{0x0C, 0x18, 0x30, 0x18, 0x0C},
/* 0x7C | */
{0x30, 0x30, 0x30, 0x30, 0x30},
/* 0x7D } */
{0x30, 0x18, 0x0C, 0x18, 0x30},
/* 0x7E ~ */
{0x00, 0x00, 0x00, 0x00, 0x00},
};
#define FONT_W 5
#define FONT_H 7
#define FONT_STEP 9
/* 颜色常量 */
#define CURSOR_COLOR 0xFF0055FFu /* 蓝色光标 */
#define BG_COLOR 0xFF1E1E1Eu /* 深色背景 */
#define TEXT_COLOR 0xFFD4D4D4u /* 浅色文字 */
#define DIM_COLOR 0xFF808080u /* 灰色行号 */
#define STATUS_BG 0xFF007ACCu /* 蓝色状态栏 */
/* 绘制单个字符(使用位图字体) */
static void draw_char_bitmap(char ch, int x, int y, u32 color) {
if (ch < ' ' || ch > '~') return;
int idx = ch - ' ';
if (idx < 0 || idx >= 95) return;
for (int row = 0; row < FONT_H; row++) {
unsigned char bits = font_5x7[idx][row];
for (int col = 0; col < FONT_W; col++) {
if (bits & (1 << (FONT_W - 1 - col))) {
da_fill_rect(&g_ac, x + col, y + row, 1, 1, color);
}
}
}
}
/* 绘制文本行 */
static void draw_text_line(const char *text, int x, int y, u32 color) {
int cx = x;
while (*text) {
draw_char_bitmap(*text, cx, y, color);
cx += FONT_STEP;
text++;
}
}
/* 重绘所有 */
static void redraw_all(void) {
da_fill_bg(&g_ac, BG_COLOR);
int margin_x = 10;
int top_y = 44;
/* 计算可见列数 */
int visible_cols = (g_ac.width - 20) / FONT_STEP;
if (visible_cols < 10) visible_cols = 10;
/* 绘制可见行 */
for (int line = g_scroll_top; line < g_line_count && line < g_scroll_top + g_visible_rows; line++) {
int screen_y = top_y + (line - g_scroll_top) * FONT_STEP;
/* 行号 */
char ln[8];
int p = snprintf(ln, sizeof(ln), "%d ", line + 1);
/* 行号区域背景 */
int line_num_width = p * FONT_STEP;
da_fill_rect(&g_ac, 0, screen_y - 2, line_num_width + 10, FONT_STEP, 0xFF252526u);
/* 绘制行号 */
draw_text_line(ln, 5, screen_y, DIM_COLOR);
/* 文本内容 */
const char *line_text = g_lines[line];
int line_len = (int)strlen(line_text);
int draw_len = line_len;
if (draw_len > visible_cols) draw_len = visible_cols;
int text_start_x = margin_x + line_num_width;
for (int col = 0; col < draw_len; col++) {
draw_char_bitmap(line_text[col], text_start_x + col * FONT_STEP, screen_y, TEXT_COLOR);
}
}
/* 光标 */
if (g_current_line >= g_scroll_top && g_current_line < g_scroll_top + g_visible_rows) {
int screen_y = top_y + (g_current_line - g_scroll_top) * FONT_STEP;
/* 计算行号宽度 */
char tmp_ln[8];
snprintf(tmp_ln, sizeof(tmp_ln), "%d ", g_current_line + 1);
int line_num_width = (int)strlen(tmp_ln) * FONT_STEP;
int text_start_x = margin_x + line_num_width;
/* 高亮当前光标位置 */
int cursor_x = text_start_x + g_current_col * FONT_STEP;
da_fill_rect(&g_ac, cursor_x, screen_y, FONT_W, FONT_H, CURSOR_COLOR);
}
/* 状态栏 */
char status[128];
snprintf(status, sizeof(status), "Line: %d Col: %d Lines: %d ESC=Exit",
g_current_line + 1, g_current_col + 1, g_line_count);
da_fill_rect(&g_ac, 0, g_ac.height - 24, g_ac.width, 24, STATUS_BG);
draw_text_line(status, 10, g_ac.height - 22, 0xFFFFFFFFu);
/* 标题栏 */
da_fill_rect(&g_ac, 0, 0, g_ac.width, 36, 0xFF2D2D2Du);
draw_text_line("Notes Preview (Deshab-OS)", 10, 6, 0xFFFFFFFFu);
da_fill_rect(&g_ac, 0, 36, g_ac.width, 2, 0xFF007ACCu);
}
static void scroll_cursor_into_view(void) {
if (g_current_line < g_scroll_top) {
g_scroll_top = g_current_line;
}
if (g_current_line >= g_scroll_top + g_visible_rows) {
g_scroll_top = g_current_line - g_visible_rows + 1;
}
}
/* 换行 */
static void handle_enter(void) {
if (g_current_line >= 511) return;
int cur_len = (int)strlen(g_lines[g_current_line]);
if (g_current_col < cur_len) {
int remaining = cur_len - g_current_col;
memmove(g_lines[g_current_line] + g_current_col + remaining,
g_lines[g_current_line] + g_current_col, remaining + 1);
g_lines[g_current_line][g_current_col + remaining] = '\0';
} else {
g_lines[g_current_line][g_current_col] = '\0';
}
/* 移动下一行 */
memmove(g_lines[g_current_line + 1], g_lines[g_current_line + 1], 256);
g_line_count++;
g_current_line++;
g_current_col = 0;
scroll_cursor_into_view();
}
/* Backspace删除 */
static void handle_backspace(void) {
if (g_current_col > 0) {
int len = (int)strlen(g_lines[g_current_line]);
memmove(g_lines[g_current_line] + g_current_col - 1,
g_lines[g_current_line] + g_current_col, len - g_current_col + 1);
g_current_col--;
} else if (g_current_line > 0) {
int prev_len = (int)strlen(g_lines[g_current_line - 1]);
int cur_len = (int)strlen(g_lines[g_current_line]);
if (prev_len + cur_len < 255) {
memcpy(g_lines[g_current_line - 1] + prev_len, g_lines[g_current_line], cur_len + 1);
for (int i = g_current_line; i < g_line_count - 1; i++) {
memcpy(g_lines[i], g_lines[i+1], 256);
}
g_line_count--;
g_current_line--;
g_current_col = prev_len;
}
}
}
/* 插入字符 */
static void insert_char(char c) {
if (c < 32 || c > 126) return;
int len = (int)strlen(g_lines[g_current_line]);
if (len >= 255) return;
memmove(g_lines[g_current_line] + g_current_col + 1,
g_lines[g_current_line] + g_current_col, len - g_current_col + 1);
g_lines[g_current_line][g_current_col] = c;
g_current_col++;
}
int main(int argc, char *argv[]) {
(void)argc; (void)argv;
const char *title = "Notes Preview (Deshab-OS) - ESC to exit";
int width = 1024;
int height = 768;
if (da_init_linux(&g_ac, title, width, height) != 0) {
return 1;
}
g_visible_rows = (height - 60 - 24) / FONT_STEP;
if (g_visible_rows < 5) g_visible_rows = 5;
/* 初始化空文档 */
g_line_count = 1;
g_lines[0][0] = '\0';
redraw_all();
da_flush(&g_ac);
/* 主事件循环 */
SDL_Event event;
while (g_running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
g_running = 0;
break;
}
/* 处理文本输入事件(支持输入法) */
if (event.type == SDL_TEXTINPUT) {
for (int i = 0; event.text.text[i]; i++) {
unsigned char c = (unsigned char)event.text.text[i];
if (c >= 32 && c <= 126) {
insert_char((char)c);
}
}
scroll_cursor_into_view();
redraw_all();
da_flush(&g_ac);
}
if (event.type == SDL_KEYDOWN) {
u8 sc = da_sdl_to_ps2_scan(event.key.keysym.sym);
if (sc == 0x01) { /* Esc */
g_running = 0;
} else if (sc == 0x1C) { /* Enter */
handle_enter();
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x0E) { /* Backspace */
handle_backspace();
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x4B) { /* Left */
if (g_current_col > 0) g_current_col--;
else if (g_current_line > 0) {
g_current_line--;
g_current_col = (int)strlen(g_lines[g_current_line]);
}
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x4D) { /* Right */
if (g_current_col < (int)strlen(g_lines[g_current_line])) g_current_col++;
else if (g_current_line < g_line_count - 1) {
g_current_line++;
g_current_col = 0;
}
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x48) { /* Up */
if (g_current_line > 0) g_current_line--;
if (g_current_col > (int)strlen(g_lines[g_current_line])) {
g_current_col = (int)strlen(g_lines[g_current_line]);
}
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x50) { /* Down */
if (g_current_line < g_line_count - 1) g_current_line++;
if (g_current_col > (int)strlen(g_lines[g_current_line])) {
g_current_col = (int)strlen(g_lines[g_current_line]);
}
redraw_all();
da_flush(&g_ac);
}
}
}
SDL_Delay(16);
}
da_cleanup(&g_ac);
return 0;
}
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools"
@mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c