云计算百科
云计算领域专业知识百科平台

手机端C语言简谱编辑器SDL软件代码QZQ

#define TSF_IMPLEMENTATION
#include "tsf.h"

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <stdbool.h>

// ============== 类型定义 ==============

typedef struct Color {
Uint8 r, g, b, a;
} Color;

typedef struct Theme {
Color bg;
Color fg;
Color widget_bg;
Color widget_border;
Color button_bg;
Color button_hover;
Color button_active;
Color accent;
Color accent_hover;
Color accent_active;
Color selection;
Color disabled;
Color menu_bg;
Color menu_hover;
Color menu_border;
} Theme;

// ============== 全局变量声明 ==============

TTF_Font* g_font = NULL;
SDL_Renderer* g_renderer = NULL;
SDL_Window* g_window = NULL;
Theme g_theme;

// ============== 工具函数 ==============

void renderText(SDL_Renderer* renderer, const char* text, int x, int y, Color color) {
if (!g_font || !text) return;
SDL_Surface* surface = TTF_RenderUTF8_Blended(g_font, text, (SDL_Color) {
color.r, color.g, color.b, color.a
});
if (!surface) return;
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (!texture) {
SDL_FreeSurface(surface);
return;
}
SDL_Rect rect = {x, y, surface->w, surface->h};
SDL_RenderCopy(renderer, texture, NULL, &rect);
SDL_DestroyTexture(texture);
SDL_FreeSurface(surface);
}

int getTextWidth(const char* text) {
if (!g_font || !text) return 0;
int w, h;
TTF_SizeUTF8(g_font, text, &w, &h);
return w;
}

// ============== 剪贴板操作 ==============

char* getClipboardText(void) {
char* clipText = SDL_GetClipboardText();
if (clipText) {
char* result = strdup(clipText);
SDL_free(clipText);
return result;
}
return NULL;
}

bool setClipboardText(const char* text) {
if (!text || text[0] == '\\0') return false;
return SDL_SetClipboardText(text) == 0;
}

// ============== 前向声明 ==============

typedef struct Widget Widget;
typedef struct Frame Frame;
typedef struct Entry Entry;
typedef struct TextWidget TextWidget;
typedef struct ContextMenu ContextMenu;

// ============== 回调函数类型 ==============

typedef void (*VoidCallback)(void);
typedef void (*BoolCallback)(bool);
typedef void (*WidgetCallback)(Widget*);

// ============== 控件结构体 ==============

struct Widget {
int x, y, w, h;
bool visible;
bool enabled;
bool hover;
bool pressed;
bool focus;
char* text;
VoidCallback callback;
BoolCallback focusCallback;

void (*draw)(Widget*);
bool (*handleEvent)(Widget*, const SDL_Event*);
void (*onMouseMove)(Widget*, int, int);
void (*setFocus)(Widget*, bool);
char* (*getText)(Widget*);
void (*setText)(Widget*, const char*);
void (*selectAll)(Widget*);
void (*copySelection)(Widget*);
void (*cutSelection)(Widget*);
void (*pasteFromClipboard)(Widget*);
void (*deleteSelection)(Widget*);
bool (*hasSelection)(Widget*);
char* (*getSelectedText)(Widget*);
bool (*isPointInside)(Widget*, int, int);
void (*destroy)(Widget*);
};

// 标签控件
struct Label {
Widget base;
};

// 按钮控件
struct Button {
Widget base;
};

// 文本输入框
struct Entry {
Widget base;
char* value;
int cursorPos;
int selectionStart;
int selectionEnd;
bool hasSel;
};

// 多行文本框
struct TextWidget {
Widget base;
char* value;
int cursorPos;
int selectionStart;
int selectionEnd;
bool hasSel;
int scrollOffset;
char** lines;
int lineCount;
};

// 容器
struct Frame {
Widget base;
Widget** children;
int childCount;
int childCapacity;
};

// ============== 控件函数声明 ==============

// Label
Widget* Label_create(int x, int y, int w, int h, const char* text);
void Label_draw(Widget* self);
bool Label_handleEvent(Widget* self, const SDL_Event* event);

// Button
Widget* Button_create(int x, int y, int w, int h, const char* text, VoidCallback cb);
void Button_draw(Widget* self);
bool Button_handleEvent(Widget* self, const SDL_Event* event);

// Entry
Widget* Entry_create(int x, int y, int w, int h, const char* text);
void Entry_draw(Widget* self);
bool Entry_handleEvent(Widget* self, const SDL_Event* event);
void Entry_setText(Widget* self, const char* newText);
char* Entry_getText(Widget* self);
void Entry_selectAll(Widget* self);
void Entry_copySelection(Widget* self);
void Entry_cutSelection(Widget* self);
void Entry_pasteFromClipboard(Widget* self);
void Entry_deleteSelection(Widget* self);
bool Entry_hasSelection(Widget* self);
char* Entry_getSelectedText(Widget* self);
void Entry_destroy(Widget* self);

// TextWidget
Widget* TextWidget_create(int x, int y, int w, int h, const char* text);
void TextWidget_draw(Widget* self);
bool TextWidget_handleEvent(Widget* self, const SDL_Event* event);
void TextWidget_setText(Widget* self, const char* newText);
char* TextWidget_getText(Widget* self);
void TextWidget_selectAll(Widget* self);
void TextWidget_copySelection(Widget* self);
void TextWidget_cutSelection(Widget* self);
void TextWidget_pasteFromClipboard(Widget* self);
void TextWidget_deleteSelection(Widget* self);
bool TextWidget_hasSelection(Widget* self);
char* TextWidget_getSelectedText(Widget* self);
void TextWidget_updateLines(TextWidget* tw);
void TextWidget_destroy(Widget* self);

// Frame
Widget* Frame_create(int x, int y, int w, int h);
void Frame_draw(Widget* self);
bool Frame_handleEvent(Widget* self, const SDL_Event* event);
void Frame_onMouseMove(Widget* self, int mx, int my);
void Frame_setFocus(Widget* self, bool f);
void Frame_addChild(Widget* self, Widget* child);
void Frame_clearFocus(Widget* self);
Widget* Frame_getFocusedWidget(Widget* self);
Widget* Frame_getWidgetAt(Widget* self, int px, int py);
void Frame_destroy(Widget* self);

// ============== 通用Widget函数 ==============

void Widget_init(Widget* w, int x, int y, int wd, int ht, const char* text) {
w->x = x;
w->y = y;
w->w = wd;
w->h = ht;
w->visible = true;
w->enabled = true;
w->hover = false;
w->pressed = false;
w->focus = false;
w->text = text ? strdup(text) : NULL;
w->callback = NULL;
w->focusCallback = NULL;
}

void Widget_setFocus(Widget* self, bool f) {
if (self->focus != f) {
self->focus = f;
if (self->focusCallback) self->focusCallback(f);
}
}

bool Widget_isPointInside(Widget* self, int px, int py) {
return (px >= self->x && px <= self->x + self->w &&
py >= self->y && py <= self->y + self->h);
}

void Widget_onMouseMove(Widget* self, int mx, int my) {
self->hover = Widget_isPointInside(self, mx, my);
}

void Widget_destroy_base(Widget* self) {
if (self->text) free(self->text);
}

// ============== Label实现 ==============

Widget* Label_create(int x, int y, int w, int h, const char* text) {
Widget* self = (Widget*)calloc(1, sizeof(Widget));
Widget_init(self, x, y, w, h, text);
self->draw = Label_draw;
self->handleEvent = Label_handleEvent;
self->onMouseMove = Widget_onMouseMove;
self->setFocus = Widget_setFocus;
self->getText = NULL;
self->setText = NULL;
self->selectAll = NULL;
self->copySelection = NULL;
self->cutSelection = NULL;
self->pasteFromClipboard = NULL;
self->deleteSelection = NULL;
self->hasSelection = NULL;
self->getSelectedText = NULL;
self->isPointInside = Widget_isPointInside;
self->destroy = Widget_destroy_base;
return self;
}

void Label_draw(Widget* self) {
if (!self->visible || !self->text) return;
Color color = self->enabled ? g_theme.fg : g_theme.disabled;
int tw = getTextWidth(self->text);
int th = 20;
int tx = self->x + (self->w – tw) / 2;
int ty = self->y + (self->h – th) / 2;
renderText(g_renderer, self->text, tx, ty, color);
}

bool Label_handleEvent(Widget* self, const SDL_Event* event) {
return false;
}

// ============== Button实现 ==============

Widget* Button_create(int x, int y, int w, int h, const char* text, VoidCallback cb) {
Widget* self = (Widget*)calloc(1, sizeof(Widget));
Widget_init(self, x, y, w, h, text);
self->draw = Button_draw;
self->handleEvent = Button_handleEvent;
self->onMouseMove = Widget_onMouseMove;
self->setFocus = Widget_setFocus;
self->isPointInside = Widget_isPointInside;
self->destroy = Widget_destroy_base;
self->callback = cb;
return self;
}

void Button_draw(Widget* self) {
if (!self->visible || !self->enabled) return;

Color bgColor = g_theme.button_bg;
if (self->pressed) bgColor = g_theme.button_active;
else if (self->hover) bgColor = g_theme.button_hover;

SDL_SetRenderDrawColor(g_renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);
SDL_Rect rect = {self->x, self->y, self->w, self->h};
SDL_RenderFillRect(g_renderer, &rect);

SDL_SetRenderDrawColor(g_renderer, g_theme.widget_border.r, g_theme.widget_border.g,
g_theme.widget_border.b, 255);
SDL_RenderDrawRect(g_renderer, &rect);

if (self->text) {
int tw = getTextWidth(self->text);
int th = 20;
int tx = self->x + (self->w – tw) / 2;
int ty = self->y + (self->h – th) / 2;
renderText(g_renderer, self->text, tx, ty, g_theme.fg);
}
}

bool Button_handleEvent(Widget* self, const SDL_Event* event) {
if (!self->visible || !self->enabled) return false;

if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_LEFT) {
if (Widget_isPointInside(self, event->button.x, event->button.y)) {
self->pressed = true;
return true;
}
}
if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_LEFT) {
if (self->pressed) {
self->pressed = false;
if (Widget_isPointInside(self, event->button.x, event->button.y)) {
if (self->callback) self->callback();
return true;
}
}
}
return false;
}

// ============== Entry实现 ==============

Widget* Entry_create(int x, int y, int w, int h, const char* text) {
Entry* self = (Entry*)calloc(1, sizeof(Entry));
Widget_init((Widget*)self, x, y, w, h, text);
self->value = text ? strdup(text) : strdup("");
self->cursorPos = self->value ? strlen(self->value) : 0;
self->selectionStart = -1;
self->selectionEnd = -1;
self->hasSel = false;

Widget* base = (Widget*)self;
base->draw = Entry_draw;
base->handleEvent = Entry_handleEvent;
base->onMouseMove = Widget_onMouseMove;
base->setFocus = Widget_setFocus;
base->getText = Entry_getText;
base->setText = Entry_setText;
base->selectAll = Entry_selectAll;
base->copySelection = Entry_copySelection;
base->cutSelection = Entry_cutSelection;
base->pasteFromClipboard = Entry_pasteFromClipboard;
base->deleteSelection = Entry_deleteSelection;
base->hasSelection = Entry_hasSelection;
base->getSelectedText = Entry_getSelectedText;
base->isPointInside = Widget_isPointInside;
base->destroy = Entry_destroy;
return (Widget*)self;
}

void Entry_draw(Widget* base) {
if (!base->visible) return;
Entry* self = (Entry*)base;

Color bgColor = base->enabled ? g_theme.widget_bg : g_theme.disabled;
SDL_SetRenderDrawColor(g_renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);
SDL_Rect rect = {base->x, base->y, base->w, base->h};
SDL_RenderFillRect(g_renderer, &rect);

Color borderColor = base->focus ? g_theme.accent : g_theme.widget_border;
SDL_SetRenderDrawColor(g_renderer, borderColor.r, borderColor.g, borderColor.b, 255);
SDL_RenderDrawRect(g_renderer, &rect);

int textX = base->x + 5;
int textY = base->y + (base->h – 20) / 2;
char* value = self->value ? self->value : "";

if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;

int len = strlen(value);
if (start > len) start = len;
if (end > len) end = len;

char* before = strndup(value, start);
char* selected = strndup(value + start, end – start);
char* after = strdup(value + end);

int beforeW = before ? getTextWidth(before) : 0;
int selW = selected ? getTextWidth(selected) : 0;

SDL_SetRenderDrawColor(g_renderer, g_theme.selection.r, g_theme.selection.g,
g_theme.selection.b, g_theme.selection.a);
SDL_Rect selRect = {textX + beforeW, textY – 2, selW, 24};
SDL_RenderFillRect(g_renderer, &selRect);

Color white = {255, 255, 255, 255};
if (before) renderText(g_renderer, before, textX, textY, g_theme.fg);
if (selected) renderText(g_renderer, selected, textX + beforeW, textY, white);
if (after) renderText(g_renderer, after, textX + beforeW + selW, textY, g_theme.fg);

free(before);
free(selected);
free(after);
} else {
renderText(g_renderer, value, textX, textY, g_theme.fg);
}

if (base->focus && base->enabled) {
Uint32 now = SDL_GetTicks();
if ((now / 500) % 2 == 0) {
char* prefix = self->value ? strndup(self->value, self->cursorPos) : NULL;
int cursorX = textX + (prefix ? getTextWidth(prefix) : 0);
SDL_SetRenderDrawColor(g_renderer, 0, 0, 0, 255);
SDL_RenderDrawLine(g_renderer, cursorX, textY + 2, cursorX, textY + 22);
free(prefix);
}
}
}

bool Entry_handleEvent(Widget* base, const SDL_Event* event) {
if (!base->visible || !base->enabled) return false;
Entry* self = (Entry*)base;

if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_LEFT) {
if (Widget_isPointInside(base, event->button.x, event->button.y)) {
Widget_setFocus(base, true);
int textX = base->x + 5;
int pos = 0;
char* value = self->value ? self->value : "";
int len = strlen(value);
for (int i = 0; i <= len; i++) {
char* prefix = strndup(value, i);
int px = textX + (prefix ? getTextWidth(prefix) : 0);
free(prefix);
if (event->button.x < px + 5) {
pos = i;
break;
}
if (i == len) pos = i;
}
self->cursorPos = pos;
return true;
} else {
if (base->focus) Widget_setFocus(base, false);
self->hasSel = false;
}
}

if (event->type == SDL_TEXTINPUT && base->focus) {
if (self->hasSel) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = strlen(self->value);
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + strlen(event->text.text) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, event->text.text);
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start + strlen(event->text.text);
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
} else {
int len = strlen(self->value);
char* newVal = (char*)malloc(len + strlen(event->text.text) + 1);
strncpy(newVal, self->value, self->cursorPos);
newVal[self->cursorPos] = '\\0';
strcat(newVal, event->text.text);
strcat(newVal, self->value + self->cursorPos);
free(self->value);
self->value = newVal;
self->cursorPos += strlen(event->text.text);
}
if (base->callback) base->callback();
return true;
}

if (event->type == SDL_KEYDOWN && base->focus) {
bool shift = event->key.keysym.mod & KMOD_SHIFT;
int len = self->value ? strlen(self->value) : 0;

if (event->key.keysym.sym == SDLK_LEFT) {
if (shift) {
if (!self->hasSel) {
self->selectionStart = self->cursorPos;
self->selectionEnd = self->cursorPos;
self->hasSel = true;
}
if (self->cursorPos > 0) {
self->cursorPos–;
self->selectionEnd = self->cursorPos;
}
} else {
if (self->cursorPos > 0) self->cursorPos–;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
}
return true;
}
if (event->key.keysym.sym == SDLK_RIGHT) {
if (shift) {
if (!self->hasSel) {
self->selectionStart = self->cursorPos;
self->selectionEnd = self->cursorPos;
self->hasSel = true;
}
if (self->cursorPos < len) {
self->cursorPos++;
self->selectionEnd = self->cursorPos;
}
} else {
if (self->cursorPos < len) self->cursorPos++;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
}
return true;
}
if (event->key.keysym.sym == SDLK_HOME) {
self->cursorPos = 0;
if (shift) {
if (!self->hasSel) {
self->selectionStart = 0;
self->selectionEnd = 0;
self->hasSel = true;
}
self->selectionEnd = 0;
}
return true;
}
if (event->key.keysym.sym == SDLK_END) {
self->cursorPos = len;
if (shift) {
if (!self->hasSel) {
self->selectionStart = self->cursorPos;
self->selectionEnd = self->cursorPos;
self->hasSel = true;
}
self->selectionEnd = self->cursorPos;
}
return true;
}
if (event->key.keysym.sym == SDLK_BACKSPACE) {
if (self->hasSel) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
} else if (self->cursorPos > 0) {
char* newVal = (char*)malloc(len);
strncpy(newVal, self->value, self->cursorPos – 1);
newVal[self->cursorPos – 1] = '\\0';
strcat(newVal, self->value + self->cursorPos);
free(self->value);
self->value = newVal;
self->cursorPos–;
}
if (base->callback) base->callback();
return true;
}
if (event->key.keysym.sym == SDLK_DELETE) {
if (self->hasSel) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
} else if (self->cursorPos < len) {
char* newVal = (char*)malloc(len);
strncpy(newVal, self->value, self->cursorPos);
newVal[self->cursorPos] = '\\0';
strcat(newVal, self->value + self->cursorPos + 1);
free(self->value);
self->value = newVal;
}
if (base->callback) base->callback();
return true;
}
}
return false;
}

char* Entry_getText(Widget* base) {
Entry* self = (Entry*)base;
return self->value ? strdup(self->value) : strdup("");
}

void Entry_setText(Widget* base, const char* newText) {
Entry* self = (Entry*)base;
if (self->value) free(self->value);
self->value = newText ? strdup(newText) : strdup("");
self->cursorPos = self->value ? strlen(self->value) : 0;
if (base->callback) base->callback();
}

void Entry_selectAll(Widget* base) {
Entry* self = (Entry*)base;
self->selectionStart = 0;
self->selectionEnd = self->value ? strlen(self->value) : 0;
self->hasSel = true;
}

void Entry_copySelection(Widget* base) {
Entry* self = (Entry*)base;
if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* selected = strndup(self->value + start, end – start);
if (selected) {
setClipboardText(selected);
free(selected);
}
}
}

void Entry_cutSelection(Widget* base) {
Entry* self = (Entry*)base;
if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* selected = strndup(self->value + start, end – start);
if (selected) {
if (setClipboardText(selected)) {
char* newVal = (char*)malloc(len – (end – start) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
if (base->callback) base->callback();
}
free(selected);
}
}
}

void Entry_pasteFromClipboard(Widget* base) {
Entry* self = (Entry*)base;
char* clipText = getClipboardText();
if (clipText) {
if (self->hasSel) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + strlen(clipText) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, clipText);
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start + strlen(clipText);
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
} else {
int len = self->value ? strlen(self->value) : 0;
char* newVal = (char*)malloc(len + strlen(clipText) + 1);
strncpy(newVal, self->value, self->cursorPos);
newVal[self->cursorPos] = '\\0';
strcat(newVal, clipText);
strcat(newVal, self->value + self->cursorPos);
free(self->value);
self->value = newVal;
self->cursorPos += strlen(clipText);
}
if (base->callback) base->callback();
free(clipText);
}
}

void Entry_deleteSelection(Widget* base) {
Entry* self = (Entry*)base;
if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
if (base->callback) base->callback();
}
}

bool Entry_hasSelection(Widget* base) {
Entry* self = (Entry*)base;
return self->hasSel;
}

char* Entry_getSelectedText(Widget* base) {
Entry* self = (Entry*)base;
if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
return strndup(self->value + start, end – start);
}
return strdup("");
}

void Entry_destroy(Widget* base) {
Entry* self = (Entry*)base;
if (self->value) free(self->value);
Widget_destroy_base(base);
free(self);
}

// ============== TextWidget实现 ==============

Widget* TextWidget_create(int x, int y, int w, int h, const char* text) {
TextWidget* self = (TextWidget*)calloc(1, sizeof(TextWidget));
Widget_init((Widget*)self, x, y, w, h, text);
self->value = text ? strdup(text) : strdup("");
self->cursorPos = self->value ? strlen(self->value) : 0;
self->selectionStart = -1;
self->selectionEnd = -1;
self->hasSel = false;
self->scrollOffset = 0;
self->lines = NULL;
self->lineCount = 0;
TextWidget_updateLines(self);

Widget* base = (Widget*)self;
base->draw = TextWidget_draw;
base->handleEvent = TextWidget_handleEvent;
base->onMouseMove = Widget_onMouseMove;
base->setFocus = Widget_setFocus;
base->getText = TextWidget_getText;
base->setText = TextWidget_setText;
base->selectAll = TextWidget_selectAll;
base->copySelection = TextWidget_copySelection;
base->cutSelection = TextWidget_cutSelection;
base->pasteFromClipboard = TextWidget_pasteFromClipboard;
base->deleteSelection = TextWidget_deleteSelection;
base->hasSelection = TextWidget_hasSelection;
base->getSelectedText = TextWidget_getSelectedText;
base->isPointInside = Widget_isPointInside;
base->destroy = TextWidget_destroy;
return (Widget*)self;
}

void TextWidget_updateLines(TextWidget* self) {
if (self->lines) {
for (int i = 0; i < self->lineCount; i++) {
free(self->lines[i]);
}
free(self->lines);
self->lines = NULL;
self->lineCount = 0;
}

if (!self->value) {
self->lines = malloc(sizeof(char*));
self->lines[0] = strdup("");
self->lineCount = 1;
return;
}

int count = 0;
char* temp = strdup(self->value);
char* token = strtok(temp, "\\n");
while (token) {
count++;
token = strtok(NULL, "\\n");
}
free(temp);

if (count == 0) {
count = 1;
}

self->lines = malloc(sizeof(char*) * count);
self->lineCount = 0;

temp = strdup(self->value);
token = strtok(temp, "\\n");
while (token) {
self->lines[self->lineCount++] = strdup(token);
token = strtok(NULL, "\\n");
}
free(temp);

if (self->lineCount == 0) {
self->lines = realloc(self->lines, sizeof(char*));
self->lines[0] = strdup("");
self->lineCount = 1;
}
}

void TextWidget_draw(Widget* base) {
if (!base->visible) return;
TextWidget* self = (TextWidget*)base;

Color bgColor = base->enabled ? g_theme.widget_bg : g_theme.disabled;
SDL_SetRenderDrawColor(g_renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);
SDL_Rect rect = {base->x, base->y, base->w, base->h};
SDL_RenderFillRect(g_renderer, &rect);

Color borderColor = base->focus ? g_theme.accent : g_theme.widget_border;
SDL_SetRenderDrawColor(g_renderer, borderColor.r, borderColor.g, borderColor.b, 255);
SDL_RenderDrawRect(g_renderer, &rect);

SDL_Rect clip = {base->x + 2, base->y + 2, base->w – 4, base->h – 4};
SDL_RenderSetClipRect(g_renderer, &clip);

int lineHeight = 24;
int textX = base->x + 5;
int textY = base->y + 5 – self->scrollOffset;

char* value = self->value ? self->value : "";
int globalOffset = 0;
int len = strlen(value);

for (int i = 0; i < self->lineCount; i++) {
if (textY >= base->y + base->h) break;
if (textY + lineHeight >= base->y) {
char* line = self->lines[i];
int lineStart = globalOffset;
int lineEnd = globalOffset + strlen(line);

if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int selStart = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int selEnd = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;

if (selStart < lineEnd && selEnd > lineStart) {
int localStart = selStart > lineStart ? selStart – lineStart : 0;
int localEnd = selEnd < lineEnd ? selEnd – lineStart : strlen(line);

char* before = strndup(line, localStart);
char* selected = strndup(line + localStart, localEnd – localStart);
char* after = strdup(line + localEnd);

int beforeW = before ? getTextWidth(before) : 0;
int selW = selected ? getTextWidth(selected) : 0;

SDL_SetRenderDrawColor(g_renderer, g_theme.selection.r,
g_theme.selection.g, g_theme.selection.b,
g_theme.selection.a);
SDL_Rect selRect = {textX + beforeW, textY – 2, selW, 22};
SDL_RenderFillRect(g_renderer, &selRect);

Color white = {255, 255, 255, 255};
if (before) renderText(g_renderer, before, textX, textY, g_theme.fg);
if (selected) renderText(g_renderer, selected, textX + beforeW, textY, white);
if (after) renderText(g_renderer, after, textX + beforeW + selW, textY, g_theme.fg);

free(before);
free(selected);
free(after);
} else {
renderText(g_renderer, line, textX, textY, g_theme.fg);
}
} else {
renderText(g_renderer, line, textX, textY, g_theme.fg);
}

if (base->focus && base->enabled && self->cursorPos >= lineStart && self->cursorPos <= lineEnd) {
Uint32 now = SDL_GetTicks();
if ((now / 500) % 2 == 0) {
char* prefix = strndup(value + lineStart, self->cursorPos – lineStart);
int cursorX = textX + (prefix ? getTextWidth(prefix) : 0);
SDL_SetRenderDrawColor(g_renderer, 0, 0, 0, 255);
SDL_RenderDrawLine(g_renderer, cursorX, textY + 2, cursorX, textY + 22);
free(prefix);
}
}
}
globalOffset += strlen(self->lines[i]) + 1;
textY += lineHeight;
}

SDL_RenderSetClipRect(g_renderer, NULL);
}

bool TextWidget_handleEvent(Widget* base, const SDL_Event* event) {
if (!base->visible || !base->enabled) return false;
TextWidget* self = (TextWidget*)base;

if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_LEFT) {
if (Widget_isPointInside(base, event->button.x, event->button.y)) {
Widget_setFocus(base, true);
int lineHeight = 24;
int lineIndex = (event->button.y – base->y – 5 + self->scrollOffset) / lineHeight;
if (lineIndex >= 0 && lineIndex < self->lineCount) {
int globalPos = 0;
for (int i = 0; i < lineIndex; i++) {
globalPos += strlen(self->lines[i]) + 1;
}
char* line = self->lines[lineIndex];
int textX = base->x + 5;
int pos = 0;
int len = strlen(line);
for (int i = 0; i <= len; i++) {
char* prefix = strndup(line, i);
int px = textX + (prefix ? getTextWidth(prefix) : 0);
free(prefix);
if (event->button.x < px + 5) {
pos = i;
break;
}
if (i == len) pos = i;
}
self->cursorPos = globalPos + pos;
} else {
self->cursorPos = self->value ? strlen(self->value) : 0;
}
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
return true;
} else {
if (base->focus) Widget_setFocus(base, false);
self->hasSel = false;
}
}

if (event->type == SDL_TEXTINPUT && base->focus) {
if (self->hasSel) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + strlen(event->text.text) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, event->text.text);
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start + strlen(event->text.text);
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
} else {
int len = self->value ? strlen(self->value) : 0;
char* newVal = (char*)malloc(len + strlen(event->text.text) + 1);
strncpy(newVal, self->value, self->cursorPos);
newVal[self->cursorPos] = '\\0';
strcat(newVal, event->text.text);
strcat(newVal, self->value + self->cursorPos);
free(self->value);
self->value = newVal;
self->cursorPos += strlen(event->text.text);
}
TextWidget_updateLines(self);
int totalHeight = self->lineCount * 24;
if (totalHeight > base->h – 10) {
self->scrollOffset = totalHeight – (base->h – 10);
}
if (base->callback) base->callback();
return true;
}

if (event->type == SDL_KEYDOWN && base->focus) {
bool shift = event->key.keysym.mod & KMOD_SHIFT;
int len = self->value ? strlen(self->value) : 0;

if (event->key.keysym.sym == SDLK_LEFT) {
if (shift) {
if (!self->hasSel) {
self->selectionStart = self->cursorPos;
self->selectionEnd = self->cursorPos;
self->hasSel = true;
}
if (self->cursorPos > 0) {
self->cursorPos–;
self->selectionEnd = self->cursorPos;
}
} else {
if (self->cursorPos > 0) self->cursorPos–;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
}
return true;
}
if (event->key.keysym.sym == SDLK_RIGHT) {
if (shift) {
if (!self->hasSel) {
self->selectionStart = self->cursorPos;
self->selectionEnd = self->cursorPos;
self->hasSel = true;
}
if (self->cursorPos < len) {
self->cursorPos++;
self->selectionEnd = self->cursorPos;
}
} else {
if (self->cursorPos < len) self->cursorPos++;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
}
return true;
}
if (event->key.keysym.sym == SDLK_UP) {
int lineIndex = 0;
int pos = 0;
for (int i = 0; i < self->lineCount; i++) {
int lineLen = strlen(self->lines[i]);
if (self->cursorPos >= pos && self->cursorPos <= pos + lineLen) {
lineIndex = i;
break;
}
pos += lineLen + 1;
}
if (lineIndex > 0) {
int targetPos = pos – strlen(self->lines[lineIndex-1]) – 1;
int offset = self->cursorPos – pos;
int lineLen = strlen(self->lines[lineIndex-1]);
if (offset > lineLen) offset = lineLen;
self->cursorPos = targetPos + offset;
}
return true;
}
if (event->key.keysym.sym == SDLK_DOWN) {
int lineIndex = 0;
int pos = 0;
for (int i = 0; i < self->lineCount; i++) {
int lineLen = strlen(self->lines[i]);
if (self->cursorPos >= pos && self->cursorPos <= pos + lineLen) {
lineIndex = i;
break;
}
pos += lineLen + 1;
}
if (lineIndex < self->lineCount – 1) {
int targetPos = pos + strlen(self->lines[lineIndex]) + 1;
int offset = self->cursorPos – pos;
int lineLen = strlen(self->lines[lineIndex+1]);
if (offset > lineLen) offset = lineLen;
self->cursorPos = targetPos + offset;
}
return true;
}
if (event->key.keysym.sym == SDLK_HOME) {
self->cursorPos = 0;
if (shift) {
if (!self->hasSel) {
self->selectionStart = 0;
self->selectionEnd = 0;
self->hasSel = true;
}
self->selectionEnd = 0;
}
return true;
}
if (event->key.keysym.sym == SDLK_END) {
self->cursorPos = len;
if (shift) {
if (!self->hasSel) {
self->selectionStart = self->cursorPos;
self->selectionEnd = self->cursorPos;
self->hasSel = true;
}
self->selectionEnd = self->cursorPos;
}
return true;
}
if (event->key.keysym.sym == SDLK_BACKSPACE) {
if (self->hasSel) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
} else if (self->cursorPos > 0) {
char* newVal = (char*)malloc(len);
strncpy(newVal, self->value, self->cursorPos – 1);
newVal[self->cursorPos – 1] = '\\0';
strcat(newVal, self->value + self->cursorPos);
free(self->value);
self->value = newVal;
self->cursorPos–;
}
TextWidget_updateLines(self);
if (base->callback) base->callback();
return true;
}
if (event->key.keysym.sym == SDLK_DELETE) {
if (self->hasSel) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
} else if (self->cursorPos < len) {
char* newVal = (char*)malloc(len);
strncpy(newVal, self->value, self->cursorPos);
newVal[self->cursorPos] = '\\0';
strcat(newVal, self->value + self->cursorPos + 1);
free(self->value);
self->value = newVal;
}
TextWidget_updateLines(self);
if (base->callback) base->callback();
return true;
}
if (event->key.keysym.sym == SDLK_RETURN || event->key.keysym.sym == SDLK_KP_ENTER) {
char* newVal = (char*)malloc(len + 2);
strncpy(newVal, self->value, self->cursorPos);
newVal[self->cursorPos] = '\\0';
strcat(newVal, "\\n");
strcat(newVal, self->value + self->cursorPos);
free(self->value);
self->value = newVal;
self->cursorPos++;
TextWidget_updateLines(self);
if (base->callback) base->callback();
return true;
}
}
return false;
}

char* TextWidget_getText(Widget* base) {
TextWidget* self = (TextWidget*)base;
return self->value ? strdup(self->value) : strdup("");
}

void TextWidget_setText(Widget* base, const char* newText) {
TextWidget* self = (TextWidget*)base;
if (self->value) free(self->value);
self->value = newText ? strdup(newText) : strdup("");
self->cursorPos = self->value ? strlen(self->value) : 0;
TextWidget_updateLines(self);
if (base->callback) base->callback();
}

void TextWidget_selectAll(Widget* base) {
TextWidget* self = (TextWidget*)base;
self->selectionStart = 0;
self->selectionEnd = self->value ? strlen(self->value) : 0;
self->hasSel = true;
}

void TextWidget_copySelection(Widget* base) {
TextWidget* self = (TextWidget*)base;
if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* selected = strndup(self->value + start, end – start);
if (selected) {
setClipboardText(selected);
free(selected);
}
}
}

void TextWidget_cutSelection(Widget* base) {
TextWidget* self = (TextWidget*)base;
if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* selected = strndup(self->value + start, end – start);
if (selected) {
if (setClipboardText(selected)) {
char* newVal = (char*)malloc(len – (end – start) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
TextWidget_updateLines(self);
if (base->callback) base->callback();
}
free(selected);
}
}
}

void TextWidget_pasteFromClipboard(Widget* base) {
TextWidget* self = (TextWidget*)base;
char* clipText = getClipboardText();
if (clipText) {
if (self->hasSel) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + strlen(clipText) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, clipText);
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start + strlen(clipText);
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
} else {
int len = self->value ? strlen(self->value) : 0;
char* newVal = (char*)malloc(len + strlen(clipText) + 1);
strncpy(newVal, self->value, self->cursorPos);
newVal[self->cursorPos] = '\\0';
strcat(newVal, clipText);
strcat(newVal, self->value + self->cursorPos);
free(self->value);
self->value = newVal;
self->cursorPos += strlen(clipText);
}
TextWidget_updateLines(self);
if (base->callback) base->callback();
free(clipText);
}
}

void TextWidget_deleteSelection(Widget* base) {
TextWidget* self = (TextWidget*)base;
if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
char* newVal = (char*)malloc(len – (end – start) + 1);
strncpy(newVal, self->value, start);
newVal[start] = '\\0';
strcat(newVal, self->value + end);
free(self->value);
self->value = newVal;
self->cursorPos = start;
self->hasSel = false;
self->selectionStart = self->selectionEnd = -1;
TextWidget_updateLines(self);
if (base->callback) base->callback();
}
}

bool TextWidget_hasSelection(Widget* base) {
TextWidget* self = (TextWidget*)base;
return self->hasSel;
}

char* TextWidget_getSelectedText(Widget* base) {
TextWidget* self = (TextWidget*)base;
if (self->hasSel && self->selectionStart >= 0 && self->selectionEnd >= 0) {
int start = self->selectionStart < self->selectionEnd ? self->selectionStart : self->selectionEnd;
int end = self->selectionStart > self->selectionEnd ? self->selectionStart : self->selectionEnd;
int len = self->value ? strlen(self->value) : 0;
if (start > len) start = len;
if (end > len) end = len;
return strndup(self->value + start, end – start);
}
return strdup("");
}

void TextWidget_destroy(Widget* base) {
TextWidget* self = (TextWidget*)base;
if (self->value) free(self->value);
if (self->lines) {
for (int i = 0; i < self->lineCount; i++) {
free(self->lines[i]);
}
free(self->lines);
}
Widget_destroy_base(base);
free(self);
}

// ============== Frame实现 ==============

Widget* Frame_create(int x, int y, int w, int h) {
Frame* self = (Frame*)calloc(1, sizeof(Frame));
Widget_init((Widget*)self, x, y, w, h, NULL);
self->children = NULL;
self->childCount = 0;
self->childCapacity = 0;

Widget* base = (Widget*)self;
base->draw = Frame_draw;
base->handleEvent = Frame_handleEvent;
base->onMouseMove = Frame_onMouseMove;
base->setFocus = Frame_setFocus;
base->isPointInside = Widget_isPointInside;
base->destroy = Frame_destroy;
return (Widget*)self;
}

void Frame_addChild(Widget* base, Widget* child) {
Frame* self = (Frame*)base;
if (self->childCount >= self->childCapacity) {
self->childCapacity = self->childCapacity == 0 ? 4 : self->childCapacity * 2;
self->children = realloc(self->children, sizeof(Widget*) * self->childCapacity);
}
self->children[self->childCount++] = child;
}

void Frame_draw(Widget* base) {
if (!base->visible) return;
Frame* self = (Frame*)base;
for (int i = 0; i < self->childCount; i++) {
if (self->children[i]->draw) {
self->children[i]->draw(self->children[i]);
}
}
}

bool Frame_handleEvent(Widget* base, const SDL_Event* event) {
Frame* self = (Frame*)base;
for (int i = 0; i < self->childCount; i++) {
if (self->children[i]->handleEvent &&
self->children[i]->handleEvent(self->children[i], event)) {
return true;
}
}
return false;
}

void Frame_onMouseMove(Widget* base, int mx, int my) {
Frame* self = (Frame*)base;
base->hover = Widget_isPointInside(base, mx, my);
for (int i = 0; i < self->childCount; i++) {
if (self->children[i]->onMouseMove) {
self->children[i]->onMouseMove(self->children[i], mx, my);
}
}
}

void Frame_setFocus(Widget* base, bool f) {
// Frame本身不获取焦点
}

void Frame_clearFocus(Widget* base) {
Frame* self = (Frame*)base;
for (int i = 0; i < self->childCount; i++) {
Widget* child = self->children[i];
if (child->setFocus) {
child->setFocus(child, false);
}
if (child->draw == Frame_draw) {
Frame_clearFocus(child);
}
}
}

Widget* Frame_getFocusedWidget(Widget* base) {
Frame* self = (Frame*)base;
for (int i = 0; i < self->childCount; i++) {
Widget* child = self->children[i];
if (child->focus) return child;
if (child->draw == Frame_draw) {
Widget* w = Frame_getFocusedWidget(child);
if (w) return w;
}
}
return NULL;
}

Widget* Frame_getWidgetAt(Widget* base, int px, int py) {
Frame* self = (Frame*)base;
for (int i = 0; i < self->childCount; i++) {
Widget* child = self->children[i];
if (Widget_isPointInside(child, px, py)) {
if (child->draw == Frame_draw) {
Widget* w = Frame_getWidgetAt(child, px, py);
if (w) return w;
}
return child;
}
}
return NULL;
}

void Frame_destroy(Widget* base) {
Frame* self = (Frame*)base;
for (int i = 0; i < self->childCount; i++) {
if (self->children[i]->destroy) {
self->children[i]->destroy(self->children[i]);
}
}
free(self->children);
Widget_destroy_base(base);
free(self);
}

// ============== 右键菜单 ==============

struct ContextMenu {
bool visible;
int x, y;
int width;
int itemHeight;
int selectedIndex;
char** items;
VoidCallback* callbacks;
int itemCount;
int itemCapacity;
Widget* targetWidget;
};

ContextMenu* ContextMenu_create(void) {
ContextMenu* menu = (ContextMenu*)calloc(1, sizeof(ContextMenu));
menu->visible = false;
menu->x = 0;
menu->y = 0;
menu->width = 220;
menu->itemHeight = 32;
menu->selectedIndex = -1;
menu->items = NULL;
menu->callbacks = NULL;
menu->itemCount = 0;
menu->itemCapacity = 0;
menu->targetWidget = NULL;
return menu;
}

void ContextMenu_addItem(ContextMenu* menu, const char* text, VoidCallback callback) {
if (menu->itemCount >= menu->itemCapacity) {
menu->itemCapacity = menu->itemCapacity == 0 ? 4 : menu->itemCapacity * 2;
menu->items = realloc(menu->items, sizeof(char*) * menu->itemCapacity);
menu->callbacks = realloc(menu->callbacks, sizeof(VoidCallback) * menu->itemCapacity);
}
menu->items[menu->itemCount] = strdup(text);
menu->callbacks[menu->itemCount] = callback;
menu->itemCount++;
}

void ContextMenu_show(ContextMenu* menu, int mx, int my, Widget* widget) {
menu->visible = true;
menu->x = mx;
menu->y = my;
menu->targetWidget = widget;
menu->selectedIndex = -1;

int screenW, screenH;
SDL_GetWindowSize(g_window, &screenW, &screenH);
if (menu->x + menu->width > screenW) menu->x = screenW – menu->width – 10;
if (menu->y + menu->itemCount * menu->itemHeight + 10 > screenH) {
menu->y = screenH – menu->itemCount * menu->itemHeight – 10;
}
if (menu->x < 10) menu->x = 10;
if (menu->y < 10) menu->y = 10;
}

void ContextMenu_hide(ContextMenu* menu) {
menu->visible = false;
menu->targetWidget = NULL;
menu->selectedIndex = -1;
}

bool ContextMenu_isVisible(ContextMenu* menu) {
return menu->visible;
}

bool ContextMenu_handleEvent(ContextMenu* menu, const SDL_Event* event) {
if (!menu->visible) return false;

if (event->type == SDL_MOUSEMOTION) {
int mx = event->motion.x, my = event->motion.y;
menu->selectedIndex = -1;
if (mx >= menu->x && mx <= menu->x + menu->width &&
my >= menu->y && my <= menu->y + menu->itemCount * menu->itemHeight) {
int index = (my – menu->y) / menu->itemHeight;
if (index >= 0 && index < menu->itemCount) {
menu->selectedIndex = index;
}
}
return true;
}

if (event->type == SDL_MOUSEBUTTONDOWN) {
int mx = event->button.x, my = event->button.y;

if (!(mx >= menu->x && mx <= menu->x + menu->width &&
my >= menu->y && my <= menu->y + menu->itemCount * menu->itemHeight)) {
ContextMenu_hide(menu);
return true;
}

if (event->button.button == SDL_BUTTON_LEFT) {
int index = (my – menu->y) / menu->itemHeight;
if (index >= 0 && index < menu->itemCount) {
if (menu->callbacks[index]) menu->callbacks[index]();
ContextMenu_hide(menu);
return true;
}
}
}

if (event->type == SDL_KEYDOWN) {
if (event->key.keysym.sym == SDLK_ESCAPE) {
ContextMenu_hide(menu);
return true;
}
}

return false;
}

void ContextMenu_draw(ContextMenu* menu) {
if (!menu->visible) return;

int totalHeight = menu->itemCount * menu->itemHeight + 10;

SDL_Rect rect = {menu->x, menu->y, menu->width, totalHeight};
SDL_SetRenderDrawColor(g_renderer, g_theme.menu_bg.r, g_theme.menu_bg.g,
g_theme.menu_bg.b, g_theme.menu_bg.a);
SDL_RenderFillRect(g_renderer, &rect);

SDL_SetRenderDrawColor(g_renderer, g_theme.menu_border.r, g_theme.menu_border.g,
g_theme.menu_border.b, 255);
SDL_RenderDrawRect(g_renderer, &rect);

Color normalColor = {220, 220, 220, 255};
Color hoverColor = {255, 255, 255, 255};
Color disabledColor = {150, 150, 150, 255};

Widget* target = menu->targetWidget;
bool hasSel = target && target->hasSelection ? target->hasSelection(target) : false;
char* text = target && target->getText ? target->getText(target) : NULL;
bool hasText = text && strlen(text) > 0;
if (text) free(text);

for (int i = 0; i < menu->itemCount; i++) {
int itemY = menu->y + 5 + i * menu->itemHeight;
SDL_Rect itemRect = {menu->x + 5, itemY, menu->width – 10, menu->itemHeight – 5};

bool isTextOp = (i == 0 || i == 2 || i == 4);
bool isSelectOp = (i == 3);
bool isPasteOp = (i == 1);
bool isBatchOp = (i >= 5);

bool enabled = true;
if (isTextOp && !hasSel) enabled = false;
if (isSelectOp) enabled = true;
if (isPasteOp) enabled = (target != NULL);
if (isBatchOp) enabled = (target != NULL && hasText);

if (menu->selectedIndex == i && enabled) {
SDL_SetRenderDrawColor(g_renderer, g_theme.menu_hover.r, g_theme.menu_hover.g,
g_theme.menu_hover.b, g_theme.menu_hover.a);
SDL_RenderFillRect(g_renderer, &itemRect);
}

Color color = enabled ? normalColor : disabledColor;
renderText(g_renderer, menu->items[i], menu->x + 15, itemY + 5, color);
}
}

void ContextMenu_destroy(ContextMenu* menu) {
for (int i = 0; i < menu->itemCount; i++) {
free(menu->items[i]);
}
free(menu->items);
free(menu->callbacks);
free(menu);
}

// ============== 全局变量 ==============

tsf* g_tsf = NULL;
const int SAMPLE_RATE = 44100;

int currentChannel = 0;
int currentTempo = 120;
char currentKey[32] = "C大调";
bool isPlaying = false;

// 控件
Widget* g_mainFrame = NULL;
Widget* g_sheetText = NULL;
Widget* g_channelEntry = NULL;
Widget* g_tempoEntry = NULL;
Widget* g_keyEntry = NULL;
Widget* g_statusLabel = NULL;

// 右键菜单
ContextMenu* g_contextMenu = NULL;

// 长按检测
Uint32 g_pressStartTime = 0;
bool g_isPressed = false;
int g_pressX = 0, g_pressY = 0;
Widget* g_pressedWidget = NULL;
bool g_longPressTriggered = false;

// 默认简谱
const char* DEFAULT_SHEET = "[0,120,C大调]\\n1 1 5 5 6 6 5-\\n4 4 3 3 2 2 1-";

// ============== 音色和调式映射 ==============

typedef struct {
int id;
const char* name;
} Instrument;

Instrument instrumentList[] = {
{0, "大钢琴"}, {1, "明亮钢琴"}, {2, "电钢琴"}, {3, "酒吧钢琴"},
{4, "电钢琴2"}, {5, "电钢琴3"}, {6, "哈普西科德"}, {7, "古钢琴"},
{8, "钢片琴"}, {9, "钟琴"}, {10, "八音盒"}, {11, "颤音琴"},
{12, "马林巴"}, {13, "木琴"}, {14, "管钟"}, {15, "竖琴"},
{16, "手风琴"}, {17, "口琴"}, {18, "班多纽"}, {19, "弦乐合奏1"},
{20, "弦乐合奏2"}, {21, "合成弦乐1"}, {22, "合成弦乐2"}, {23, "唱诗班"},
{24, "人声合唱"}, {25, "合成人声"}, {26, "小号"}, {27, "长号"},
{28, "大号"}, {29, "小号2"}, {30, "法国号"}, {31, "铜管合奏"},
{32, "合成铜管1"}, {33, "合成铜管2"}, {34, "高音萨克斯"}, {35, "中音萨克斯"},
{36, "次中音萨克斯"}, {37, "上低音萨克斯"}, {38, "双簧管"}, {39, "英国管"},
{40, "巴松管"}, {41, "单簧管"}, {42, "短笛"}, {43, "长笛"},
{44, "竖笛"}, {45, "排箫"}, {46, "口哨"}, {47, "陶笛"}
};

int instrumentCount = sizeof(instrumentList) / sizeof(Instrument);

typedef struct {
const char* name;
int doPitch;
int scale[7];
} KeyInfo;

KeyInfo keyMap[] = {
{"C大调", 60, {0, 2, 4, 5, 7, 9, 11}},
{"G大调", 67, {0, 2, 4, 5, 7, 9, 11}},
{"D大调", 62, {0, 2, 4, 5, 7, 9, 11}},
{"A大调", 69, {0, 2, 4, 5, 7, 9, 11}},
{"E大调", 64, {0, 2, 4, 5, 7, 9, 11}},
{"F大调", 65, {0, 2, 4, 5, 7, 9, 11}},
{"Bb大调", 70, {0, 2, 4, 5, 7, 9, 11}},
{"Eb大调", 63, {0, 2, 4, 5, 7, 9, 11}},
{"Ab大调", 68, {0, 2, 4, 5, 7, 9, 11}},
{"Db大调", 61, {0, 2, 4, 5, 7, 9, 11}},
{"Gb大调", 66, {0, 2, 4, 5, 7, 9, 11}}
};

int keyMapCount = sizeof(keyMap) / sizeof(KeyInfo);

const char* getInstrumentName(int id) {
for (int i = 0; i < instrumentCount; i++) {
if (instrumentList[i].id == id) return instrumentList[i].name;
}
return "未知";
}

KeyInfo* findKeyInfo(const char* key) {
for (int i = 0; i < keyMapCount; i++) {
if (strcmp(keyMap[i].name, key) == 0) return (KeyInfo*)&keyMap[i];
}
return (KeyInfo*)&keyMap[0];
}

// ============== 简谱解析和播放 ==============

typedef struct {
int midiNote;
float duration;
bool isRest;
} Note;

int parseNoteChar(char c, char nextChar, int* pos, const char* content, KeyInfo* keyInfo) {
if (c < '1' || c > '7') return -1;
int noteIndex = c – '1';
int midiNote = keyInfo->doPitch + keyInfo->scale[noteIndex];
if (*pos < (int)strlen(content) && content[*pos] == '#') {
midiNote++;
(*pos)++;
}
while (*pos < (int)strlen(content) && content[*pos] == '.') {
midiNote -= 12;
(*pos)++;
}
while (*pos < (int)strlen(content) && content[*pos] == '\\'') {
midiNote += 12;
(*pos)++;
}
return midiNote;
}

Note* parseSheet(const char* sheet, int* outInstrument, int* outTempo, char* outKey) {
Note* notes = NULL;
int noteCount = 0;
int noteCapacity = 0;

char* content = strdup(sheet);
char* temp = malloc(strlen(content) + 1);
int tempIdx = 0;
for (int i = 0; content[i]; i++) {
if (content[i] != ' ' && content[i] != '\\n') {
temp[tempIdx++] = content[i];
}
}
temp[tempIdx] = '\\0';
free(content);
content = temp;

char* start = strchr(content, '[');
char* end = strchr(content, ']');
if (start && end) {
*end = '\\0';
char* config = start + 1;
char* parts[3];
int partCount = 0;
char* token = strtok(config, ",");
while (token && partCount < 3) {
parts[partCount++] = token;
token = strtok(NULL, ",");
}
if (partCount >= 3) {
*outInstrument = atoi(parts[0]);
*outTempo = atoi(parts[1]);
strcpy(outKey, parts[2]);
}
*end = ']';
char* newContent = strdup(end + 1);
free(content);
content = newContent;
} else {
strcpy(outKey, "C大调");
}

KeyInfo* keyInfo = findKeyInfo(outKey);

char* cleanContent = malloc(strlen(content) + 1);
int cleanIdx = 0;
for (int i = 0; content[i]; i++) {
if (content[i] != '|') {
cleanContent[cleanIdx++] = content[i];
}
}
cleanContent[cleanIdx] = '\\0';
free(content);
content = cleanContent;

int i = 0;
int len = strlen(content);
while (i < len) {
char c = content[i];

if (c == '0') {
Note note = {-1, 1.0f, true};
i++;
while (i < len && content[i] == '-') {
note.duration += 1.0f;
i++;
}
if (noteCount >= noteCapacity) {
noteCapacity = noteCapacity == 0 ? 16 : noteCapacity * 2;
notes = realloc(notes, sizeof(Note) * noteCapacity);
}
notes[noteCount++] = note;
continue;
}

if (c == '[') {
i++;
char* group = malloc(len + 1);
int groupIdx = 0;
while (i < len && content[i] != ']') {
group[groupIdx++] = content[i++];
}
group[groupIdx] = '\\0';
if (i < len) i++;
int noteCountInGroup = 0;
for (int j = 0; group[j]; j++) {
if (group[j] >= '1' && group[j] <= '7') noteCountInGroup++;
}
if (noteCountInGroup == 0) {
free(group);
continue;
}
float perNoteDuration = 1.0f / noteCountInGroup;
int j = 0;
int groupLen = strlen(group);
while (j < groupLen) {
char gc = group[j];
if (gc >= '1' && gc <= '7') {
int pos = j + 1;
int midiNote = parseNoteChar(gc, (pos < groupLen) ? group[pos] : '\\0', &pos, group, keyInfo);
if (midiNote == -1) {
j++;
continue;
}
Note note = {midiNote, perNoteDuration, false};
if (noteCount >= noteCapacity) {
noteCapacity = noteCapacity == 0 ? 16 : noteCapacity * 2;
notes = realloc(notes, sizeof(Note) * noteCapacity);
}
notes[noteCount++] = note;
j = pos;
} else {
j++;
}
}
free(group);
continue;
}

if (c == '(') {
i++;
char* group = malloc(len + 1);
int groupIdx = 0;
while (i < len && content[i] != ')') {
group[groupIdx++] = content[i++];
}
group[groupIdx] = '\\0';
if (i < len) i++;
int noteCountInGroup = 0;
for (int j = 0; group[j]; j++) {
if (group[j] >= '1' && group[j] <= '7') noteCountInGroup++;
}
if (noteCountInGroup == 0) {
free(group);
continue;
}
float perNoteDuration = 0.5f / noteCountInGroup;
int j = 0;
int groupLen = strlen(group);
while (j < groupLen) {
char gc = group[j];
if (gc >= '1' && gc <= '7') {
int pos = j + 1;
int midiNote = parseNoteChar(gc, (pos < groupLen) ? group[pos] : '\\0', &pos, group, keyInfo);
if (midiNote == -1) {
j++;
continue;
}
Note note = {midiNote, perNoteDuration, false};
if (noteCount >= noteCapacity) {
noteCapacity = noteCapacity == 0 ? 16 : noteCapacity * 2;
notes = realloc(notes, sizeof(Note) * noteCapacity);
}
notes[noteCount++] = note;
j = pos;
} else {
j++;
}
}
free(group);
continue;
}

if (c >= '1' && c <= '7') {
Note note = {-1, 1.0f, false};
char noteChar = c;
i++;
int pos = i;
int midiNote = parseNoteChar(noteChar, (i < len) ? content[i] : '\\0', &pos, content, keyInfo);
if (midiNote == -1) continue;
note.midiNote = midiNote;
i = pos;
while (i < len && content[i] == '-') {
note.duration += 1.0f;
i++;
}
if (noteCount >= noteCapacity) {
noteCapacity = noteCapacity == 0 ? 16 : noteCapacity * 2;
notes = realloc(notes, sizeof(Note) * noteCapacity);
}
notes[noteCount++] = note;
continue;
}
i++;
}

free(content);

Note* result = malloc(sizeof(Note) * (noteCount + 1));
for (int i = 0; i < noteCount; i++) {
result[i] = notes[i];
}
result[noteCount].midiNote = -1;
result[noteCount].duration = 0;
result[noteCount].isRest = false;
free(notes);
return result;
}

void changeInstrument(int channel) {
if (channel < 0 || channel > 127) return;
currentChannel = channel;
printf("🎵 切换音色: %d – %s\\n", currentChannel, getInstrumentName(currentChannel));
}

void audioCallback(void* userdata, Uint8* stream, int len) {
if (!g_tsf) return;
SDL_memset(stream, 0, len);
int sampleCount = len / sizeof(short) / 2;
tsf_render_short(g_tsf, (short*)stream, sampleCount, 1);
}

// ============== 键盘控制 ==============

void showKeyboard(void) {
SDL_StartTextInput();
printf("⌨️ 软键盘已请求显示\\n");
}

void hideKeyboard(void) {
SDL_StopTextInput();
printf("⌨️ 软键盘已请求隐藏\\n");
}

// ============== 播放控制 ==============

void playSheet(void) {
if (!g_sheetText || !g_sheetText->getText) {
printf("❌ 简谱为空\\n");
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "❌ 简谱为空");
}
return;
}

char* sheetContent = g_sheetText->getText(g_sheetText);
if (!sheetContent || strlen(sheetContent) == 0) {
printf("❌ 简谱为空\\n");
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "❌ 简谱为空");
}
free(sheetContent);
return;
}

int instrument = 0, tempo = 120;
char key[32] = "C大调";
Note* notes = parseSheet(sheetContent, &instrument, &tempo, key);
free(sheetContent);

if (!notes || notes[0].midiNote == -1) {
printf("❌ 简谱解析失败\\n");
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "❌ 解析失败");
}
free(notes);
return;
}

changeInstrument(instrument);
currentTempo = tempo;
strcpy(currentKey, key);

char statusText[256];
int noteCount = 0;
while (notes[noteCount].midiNote != -1) noteCount++;
snprintf(statusText, sizeof(statusText), "🔴 正在播放… %d 个音符", noteCount);
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, statusText);
}

isPlaying = true;
float beatDuration = 60.0f / tempo;

for (int idx = 0; notes[idx].midiNote != -1 && isPlaying; idx++) {
Note note = notes[idx];
float duration = note.duration * beatDuration;
if (!note.isRest) {
tsf_note_on(g_tsf, currentChannel, note.midiNote, 0.8f);
}
int waitMs = (int)(duration * 1000);
int elapsed = 0;
while (elapsed < waitMs && isPlaying) {
SDL_Delay(10);
elapsed += 10;
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT ||
(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
isPlaying = false;
goto cleanup;
}
}
}
if (!note.isRest) {
tsf_note_off(g_tsf, currentChannel, note.midiNote);
}
}

cleanup:
isPlaying = false;
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "⚪ 就绪");
}
printf("⏹ 播放结束\\n");
free(notes);
}

void stopPlay(void) {
isPlaying = false;
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "⏹ 已停止");
}
printf("⏹ 停止播放\\n");
}

void loadDefault(void) {
if (g_sheetText && g_sheetText->setText) {
g_sheetText->setText(g_sheetText, DEFAULT_SHEET);
}
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "📋 已加载默认简谱");
}
printf("📋 加载默认简谱\\n");
}

// ============== 回调函数 ==============

void onChannelChanged(void) {
if (g_channelEntry && g_channelEntry->getText) {
char* val = g_channelEntry->getText(g_channelEntry);
if (val) {
int ch = atoi(val);
if (ch >= 0 && ch <= 127) changeInstrument(ch);
free(val);
}
}
}

void onHideKeyboard(void) {
hideKeyboard();
if (g_mainFrame) {
Frame_clearFocus(g_mainFrame);
}
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "⌨️ 键盘已隐藏");
}
}

// ============== 菜单回调函数 ==============

void menuCopy(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->copySelection) {
g_contextMenu->targetWidget->copySelection(g_contextMenu->targetWidget);
}
}

void menuPaste(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->pasteFromClipboard) {
g_contextMenu->targetWidget->pasteFromClipboard(g_contextMenu->targetWidget);
}
}

void menuCut(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->cutSelection) {
g_contextMenu->targetWidget->cutSelection(g_contextMenu->targetWidget);
}
}

void menuSelectAll(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->selectAll) {
g_contextMenu->targetWidget->selectAll(g_contextMenu->targetWidget);
}
}

void menuDelete(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->deleteSelection) {
g_contextMenu->targetWidget->deleteSelection(g_contextMenu->targetWidget);
}
}

void menuSelectAllCopy(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->selectAll &&
g_contextMenu->targetWidget->copySelection) {
g_contextMenu->targetWidget->selectAll(g_contextMenu->targetWidget);
g_contextMenu->targetWidget->copySelection(g_contextMenu->targetWidget);
}
}

void menuSelectAllPaste(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->selectAll &&
g_contextMenu->targetWidget->pasteFromClipboard) {
g_contextMenu->targetWidget->selectAll(g_contextMenu->targetWidget);
g_contextMenu->targetWidget->pasteFromClipboard(g_contextMenu->targetWidget);
}
}

void menuSelectAllDelete(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->selectAll &&
g_contextMenu->targetWidget->deleteSelection) {
g_contextMenu->targetWidget->selectAll(g_contextMenu->targetWidget);
g_contextMenu->targetWidget->deleteSelection(g_contextMenu->targetWidget);
}
}

void menuSelectAllCut(void) {
if (g_contextMenu && g_contextMenu->targetWidget && g_contextMenu->targetWidget->selectAll &&
g_contextMenu->targetWidget->cutSelection) {
g_contextMenu->targetWidget->selectAll(g_contextMenu->targetWidget);
g_contextMenu->targetWidget->cutSelection(g_contextMenu->targetWidget);
}
}

// ============== 主函数 ==============

int main(int argc, char* argv[]) {
SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO);
TTF_Init();

SDL_DisplayMode DM;
SDL_GetCurrentDisplayMode(0, &DM);
int windowWidth = 800;
int windowHeight = 650;

g_window = SDL_CreateWindow(
"🎵 简谱编辑器 – C语言版",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
windowWidth, windowHeight,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
);

if (!g_window) {
printf("窗口创建失败:%s\\n", SDL_GetError());
return -1;
}

g_renderer = SDL_CreateRenderer(
g_window, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
);

// 加载字体
g_font = TTF_OpenFont("SimHei.ttf", 18);
if (!g_font) {
g_font = TTF_OpenFont("/system/fonts/NotoSansCJK-Regular.ttc", 18);
if (!g_font) {
g_font = TTF_OpenFont("/system/fonts/DroidSansFallback.ttf", 18);
}
if (!g_font) {
printf("⚠️ 字体加载失败\\n");
}
}

// 加载SF2
g_tsf = tsf_load_filename("sndfnt.sf2");
if (!g_tsf) {
printf("❌ SF2音色文件加载失败!\\n");
SDL_Delay(3000);
return -1;
}
tsf_set_output(g_tsf, TSF_STEREO_INTERLEAVED, SAMPLE_RATE, 0);

// 初始化主题
g_theme.bg = (Color) {
240, 240, 235, 255
};
g_theme.fg = (Color) {
50, 50, 50, 255
};
g_theme.widget_bg = (Color) {
255, 255, 255, 255
};
g_theme.widget_border = (Color) {
180, 180, 175, 255
};
g_theme.button_bg = (Color) {
220, 220, 215, 255
};
g_theme.button_hover = (Color) {
200, 200, 195, 255
};
g_theme.button_active = (Color) {
180, 180, 175, 255
};
g_theme.accent = (Color) {
70, 130, 180, 255
};
g_theme.accent_hover = (Color) {
90, 150, 200, 255
};
g_theme.accent_active = (Color) {
50, 110, 160, 255
};
g_theme.selection = (Color) {
180, 210, 240, 200
};
g_theme.disabled = (Color) {
200, 200, 200, 255
};
g_theme.menu_bg = (Color) {
50, 50, 55, 240
};
g_theme.menu_hover = (Color) {
70, 70, 80, 240
};
g_theme.menu_border = (Color) {
100, 100, 110, 255
};

// 创建主容器
g_mainFrame = Frame_create(0, 0, windowWidth, windowHeight);

// 标题
Widget* titleLabel = Label_create(10, 10, 780, 30, "🎵 简谱编辑器");
Frame_addChild(g_mainFrame, titleLabel);

// 控制栏
int controlY = 45;
int labelW = 40, entryW = 80, spacing = 10;

Widget* channelLabel = Label_create(10, controlY, labelW, 30, "音色:");
Frame_addChild(g_mainFrame, channelLabel);

g_channelEntry = Entry_create(55, controlY, entryW, 30, "0");
g_channelEntry->callback = onChannelChanged;
g_channelEntry->focusCallback = showKeyboard;
Frame_addChild(g_mainFrame, g_channelEntry);

Widget* tempoLabel = Label_create(10 + labelW + entryW + 20, controlY, labelW, 30, "速度:");
Frame_addChild(g_mainFrame, tempoLabel);

g_tempoEntry = Entry_create(10 + labelW + entryW + 20 + labelW + 5, controlY, entryW, 30, "120");
g_tempoEntry->focusCallback = showKeyboard;
Frame_addChild(g_mainFrame, g_tempoEntry);

Widget* keyLabel = Label_create(10 + (labelW + entryW + 20) * 2, controlY, labelW, 30, "调式:");
Frame_addChild(g_mainFrame, keyLabel);

g_keyEntry = Entry_create(10 + (labelW + entryW + 20) * 2 + labelW + 5, controlY, 100, 30, "C大调");
g_keyEntry->focusCallback = showKeyboard;
Frame_addChild(g_mainFrame, g_keyEntry);

// 简谱编辑区
int textY = controlY + 40;
int textH = 300;
g_sheetText = TextWidget_create(10, textY, windowWidth – 20, textH, DEFAULT_SHEET);
g_sheetText->focusCallback = showKeyboard;
Frame_addChild(g_mainFrame, g_sheetText);

// 按钮栏
int btnY = textY + textH + 10;
int btnH = 40;
int btnW = 100;
int btnSpacing = 10;
int btnX = 10;

Widget* playBtn = Button_create(btnX, btnY, btnW, btnH, "▶ 播放", playSheet);
Frame_addChild(g_mainFrame, playBtn);

btnX += btnW + btnSpacing;
Widget* stopBtn = Button_create(btnX, btnY, btnW, btnH, "⏹ 停止", stopPlay);
Frame_addChild(g_mainFrame, stopBtn);

btnX += btnW + btnSpacing;
Widget* defaultBtn = Button_create(btnX, btnY, btnW, btnH, "📋 默认", loadDefault);
Frame_addChild(g_mainFrame, defaultBtn);

btnX += btnW + btnSpacing;
Widget* hideKeyboardBtn = Button_create(btnX, btnY, btnW, btnH, "⌨️ 隐藏", onHideKeyboard);
Frame_addChild(g_mainFrame, hideKeyboardBtn);

// 状态栏
int statusY = btnY + btnH + 10;
g_statusLabel = Label_create(10, statusY, 780, 30, "⚪ 就绪");
Frame_addChild(g_mainFrame, g_statusLabel);

// 提示信息
int tipY = statusY + 30;
Widget* tipLabel = Label_create(10, tipY, 780, 25,
"💡 长按弹出菜单 | 点击输入框弹出键盘 | 语法: 1-7 #(升) .(低八度) '(高八度) -(延长) 0(休止)");
Frame_addChild(g_mainFrame, tipLabel);

// 初始化音频
SDL_AudioSpec spec;
spec.freq = SAMPLE_RATE;
spec.format = AUDIO_S16SYS;
spec.channels = 2;
spec.samples = 1024;
spec.callback = audioCallback;
spec.userdata = NULL;

if (SDL_OpenAudio(&spec, NULL) < 0) {
printf("❌ 音频打开失败:%s\\n", SDL_GetError());
tsf_close(g_tsf);
return -1;
}
SDL_PauseAudio(0);

// 初始化音色
changeInstrument(0);

// 创建右键菜单
g_contextMenu = ContextMenu_create();
ContextMenu_addItem(g_contextMenu, "📋 复制", menuCopy);
ContextMenu_addItem(g_contextMenu, "📎 粘贴", menuPaste);
ContextMenu_addItem(g_contextMenu, "✂️ 剪切", menuCut);
ContextMenu_addItem(g_contextMenu, "🔍 全选", menuSelectAll);
ContextMenu_addItem(g_contextMenu, "🗑 删除选择", menuDelete);
ContextMenu_addItem(g_contextMenu, "📋 全选并复制", menuSelectAllCopy);
ContextMenu_addItem(g_contextMenu, "📎 全选并粘贴", menuSelectAllPaste);
ContextMenu_addItem(g_contextMenu, "🗑 全选并删除", menuSelectAllDelete);
ContextMenu_addItem(g_contextMenu, "📋 全选并剪切", menuSelectAllCut);

printf("🎵 简谱编辑器 v3.0 (C语言版) 已启动!\\n");
printf("💡 长按编辑区弹出菜单\\n");

hideKeyboard();

// 主循环
bool running = true;
SDL_Event event;

while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = false;
}

if (event.type == SDL_WINDOWEVENT &&
event.window.event == SDL_WINDOWEVENT_RESIZED) {
windowWidth = event.window.data1;
windowHeight = event.window.data2;
}

if (event.type == SDL_MOUSEMOTION) {
if (g_mainFrame && g_mainFrame->onMouseMove) {
g_mainFrame->onMouseMove(g_mainFrame, event.motion.x, event.motion.y);
}
}

// ====== 长按检测 ======
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
g_isPressed = true;
g_pressStartTime = SDL_GetTicks();
g_pressX = event.button.x;
g_pressY = event.button.y;
g_longPressTriggered = false;
if (g_mainFrame && g_mainFrame->isPointInside) {
g_pressedWidget = Frame_getWidgetAt(g_mainFrame, g_pressX, g_pressY);
}
}

if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) {
g_isPressed = false;
g_pressedWidget = NULL;
g_longPressTriggered = false;
}

// 检测长按
if (g_isPressed && !g_longPressTriggered) {
Uint32 pressDuration = SDL_GetTicks() – g_pressStartTime;
if (pressDuration > 50) {
g_longPressTriggered = true;

if (g_pressedWidget) {
if (g_pressedWidget->draw == Entry_draw ||
g_pressedWidget->draw == TextWidget_draw) {
ContextMenu_show(g_contextMenu, g_pressX, g_pressY, g_pressedWidget);
printf("📋 长按弹出菜单\\n");
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "📋 菜单已弹出");
}
}
}
}
}

// 处理右键菜单事件
if (g_contextMenu && ContextMenu_handleEvent(g_contextMenu, &event)) {
continue;
}

// 处理控件事件
if (g_mainFrame && g_mainFrame->handleEvent) {
g_mainFrame->handleEvent(g_mainFrame, &event);
}

// 全局快捷键
if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym == SDLK_ESCAPE) {
if (g_contextMenu && g_contextMenu->visible) {
ContextMenu_hide(g_contextMenu);
} else {
running = false;
}
}
if ((event.key.keysym.mod & KMOD_CTRL) && event.key.keysym.sym == SDLK_r) {
playSheet();
}
if (event.key.keysym.sym == SDLK_F1) {
hideKeyboard();
if (g_mainFrame) Frame_clearFocus(g_mainFrame);
if (g_statusLabel && g_statusLabel->setText) {
g_statusLabel->setText(g_statusLabel, "⌨️ 键盘已隐藏 (F1)");
}
}
if ((event.key.keysym.mod & KMOD_CTRL) && event.key.keysym.sym == SDLK_a) {
Widget* focused = g_mainFrame ? Frame_getFocusedWidget(g_mainFrame) : NULL;
if (focused && focused->selectAll) focused->selectAll(focused);
}
if ((event.key.keysym.mod & KMOD_CTRL) && event.key.keysym.sym == SDLK_c) {
Widget* focused = g_mainFrame ? Frame_getFocusedWidget(g_mainFrame) : NULL;
if (focused && focused->copySelection) focused->copySelection(focused);
}
if ((event.key.keysym.mod & KMOD_CTRL) && event.key.keysym.sym == SDLK_v) {
Widget* focused = g_mainFrame ? Frame_getFocusedWidget(g_mainFrame) : NULL;
if (focused && focused->pasteFromClipboard) focused->pasteFromClipboard(focused);
}
if ((event.key.keysym.mod & KMOD_CTRL) && event.key.keysym.sym == SDLK_x) {
Widget* focused = g_mainFrame ? Frame_getFocusedWidget(g_mainFrame) : NULL;
if (focused && focused->cutSelection) focused->cutSelection(focused);
}
}
}

SDL_SetRenderDrawColor(g_renderer, g_theme.bg.r, g_theme.bg.g, g_theme.bg.b, 255);
SDL_RenderClear(g_renderer);

if (g_mainFrame && g_mainFrame->draw) {
g_mainFrame->draw(g_mainFrame);
}

// 绘制右键菜单
if (g_contextMenu) ContextMenu_draw(g_contextMenu);

SDL_RenderPresent(g_renderer);
SDL_Delay(16);
}

// 清理
isPlaying = false;
hideKeyboard();
SDL_StopTextInput();
if (g_font) TTF_CloseFont(g_font);
SDL_CloseAudio();
SDL_DestroyRenderer(g_renderer);
SDL_DestroyWindow(g_window);
TTF_Quit();
SDL_Quit();
if (g_tsf) tsf_close(g_tsf);
if (g_contextMenu) ContextMenu_destroy(g_contextMenu);
if (g_mainFrame && g_mainFrame->destroy) g_mainFrame->destroy(g_mainFrame);

printf("👋 程序退出\\n");
return 0;
}

赞(0)
未经允许不得转载:网硕互联帮助中心 » 手机端C语言简谱编辑器SDL软件代码QZQ
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!