{"id":85349,"date":"2026-07-27T13:00:54","date_gmt":"2026-07-27T05:00:54","guid":{"rendered":"https:\/\/www.wsisp.com\/helps\/85349.html"},"modified":"2026-07-27T13:00:54","modified_gmt":"2026-07-27T05:00:54","slug":"%e6%89%8b%e6%9c%ba%e7%ab%afc%e8%af%ad%e8%a8%80%e7%ae%80%e8%b0%b1%e7%bc%96%e8%be%91%e5%99%a8sdl%e8%bd%af%e4%bb%b6%e4%bb%a3%e7%a0%81qzq","status":"publish","type":"post","link":"https:\/\/www.wsisp.com\/helps\/85349.html","title":{"rendered":"\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ"},"content":{"rendered":"<p>#define TSF_IMPLEMENTATION<br \/>\n#include &#034;tsf.h&#034;<\/p>\n<p>#include &lt;SDL2\/SDL.h&gt;<br \/>\n#include &lt;SDL2\/SDL_ttf.h&gt;<br \/>\n#include &lt;stdio.h&gt;<br \/>\n#include &lt;stdlib.h&gt;<br \/>\n#include &lt;string.h&gt;<br \/>\n#include &lt;math.h&gt;<br \/>\n#include &lt;ctype.h&gt;<br \/>\n#include &lt;stdbool.h&gt;<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u7c7b\u578b\u5b9a\u4e49 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>typedef struct Color {<br \/>\n    Uint8 r, g, b, a;<br \/>\n} Color;<\/p>\n<p>typedef struct Theme {<br \/>\n    Color bg;<br \/>\n    Color fg;<br \/>\n    Color widget_bg;<br \/>\n    Color widget_border;<br \/>\n    Color button_bg;<br \/>\n    Color button_hover;<br \/>\n    Color button_active;<br \/>\n    Color accent;<br \/>\n    Color accent_hover;<br \/>\n    Color accent_active;<br \/>\n    Color selection;<br \/>\n    Color disabled;<br \/>\n    Color menu_bg;<br \/>\n    Color menu_hover;<br \/>\n    Color menu_border;<br \/>\n} Theme;<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u5168\u5c40\u53d8\u91cf\u58f0\u660e &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>TTF_Font* g_font &#061; NULL;<br \/>\nSDL_Renderer* g_renderer &#061; NULL;<br \/>\nSDL_Window* g_window &#061; NULL;<br \/>\nTheme g_theme;<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u5de5\u5177\u51fd\u6570 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>void renderText(SDL_Renderer* renderer, const char* text, int x, int y, Color color) {<br \/>\n    if (!g_font || !text) return;<br \/>\n    SDL_Surface* surface &#061; TTF_RenderUTF8_Blended(g_font, text, (SDL_Color) {<br \/>\n        color.r, color.g, color.b, color.a<br \/>\n    });<br \/>\n    if (!surface) return;<br \/>\n    SDL_Texture* texture &#061; SDL_CreateTextureFromSurface(renderer, surface);<br \/>\n    if (!texture) {<br \/>\n        SDL_FreeSurface(surface);<br \/>\n        return;<br \/>\n    }<br \/>\n    SDL_Rect rect &#061; {x, y, surface-&gt;w, surface-&gt;h};<br \/>\n    SDL_RenderCopy(renderer, texture, NULL, &amp;rect);<br \/>\n    SDL_DestroyTexture(texture);<br \/>\n    SDL_FreeSurface(surface);<br \/>\n}<\/p>\n<p>int getTextWidth(const char* text) {<br \/>\n    if (!g_font || !text) return 0;<br \/>\n    int w, h;<br \/>\n    TTF_SizeUTF8(g_font, text, &amp;w, &amp;h);<br \/>\n    return w;<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u526a\u8d34\u677f\u64cd\u4f5c &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>char* getClipboardText(void) {<br \/>\n    char* clipText &#061; SDL_GetClipboardText();<br \/>\n    if (clipText) {<br \/>\n        char* result &#061; strdup(clipText);<br \/>\n        SDL_free(clipText);<br \/>\n        return result;<br \/>\n    }<br \/>\n    return NULL;<br \/>\n}<\/p>\n<p>bool setClipboardText(const char* text) {<br \/>\n    if (!text || text[0] &#061;&#061; &#039;\\\\0&#039;) return false;<br \/>\n    return SDL_SetClipboardText(text) &#061;&#061; 0;<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u524d\u5411\u58f0\u660e &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>typedef struct Widget Widget;<br \/>\ntypedef struct Frame Frame;<br \/>\ntypedef struct Entry Entry;<br \/>\ntypedef struct TextWidget TextWidget;<br \/>\ntypedef struct ContextMenu ContextMenu;<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u56de\u8c03\u51fd\u6570\u7c7b\u578b &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>typedef void (*VoidCallback)(void);<br \/>\ntypedef void (*BoolCallback)(bool);<br \/>\ntypedef void (*WidgetCallback)(Widget*);<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u63a7\u4ef6\u7ed3\u6784\u4f53 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>struct Widget {<br \/>\n    int x, y, w, h;<br \/>\n    bool visible;<br \/>\n    bool enabled;<br \/>\n    bool hover;<br \/>\n    bool pressed;<br \/>\n    bool focus;<br \/>\n    char* text;<br \/>\n    VoidCallback callback;<br \/>\n    BoolCallback focusCallback;<\/p>\n<p>    void (*draw)(Widget*);<br \/>\n    bool (*handleEvent)(Widget*, const SDL_Event*);<br \/>\n    void (*onMouseMove)(Widget*, int, int);<br \/>\n    void (*setFocus)(Widget*, bool);<br \/>\n    char* (*getText)(Widget*);<br \/>\n    void (*setText)(Widget*, const char*);<br \/>\n    void (*selectAll)(Widget*);<br \/>\n    void (*copySelection)(Widget*);<br \/>\n    void (*cutSelection)(Widget*);<br \/>\n    void (*pasteFromClipboard)(Widget*);<br \/>\n    void (*deleteSelection)(Widget*);<br \/>\n    bool (*hasSelection)(Widget*);<br \/>\n    char* (*getSelectedText)(Widget*);<br \/>\n    bool (*isPointInside)(Widget*, int, int);<br \/>\n    void (*destroy)(Widget*);<br \/>\n};<\/p>\n<p>\/\/ \u6807\u7b7e\u63a7\u4ef6<br \/>\nstruct Label {<br \/>\n    Widget base;<br \/>\n};<\/p>\n<p>\/\/ \u6309\u94ae\u63a7\u4ef6<br \/>\nstruct Button {<br \/>\n    Widget base;<br \/>\n};<\/p>\n<p>\/\/ \u6587\u672c\u8f93\u5165\u6846<br \/>\nstruct Entry {<br \/>\n    Widget base;<br \/>\n    char* value;<br \/>\n    int cursorPos;<br \/>\n    int selectionStart;<br \/>\n    int selectionEnd;<br \/>\n    bool hasSel;<br \/>\n};<\/p>\n<p>\/\/ \u591a\u884c\u6587\u672c\u6846<br \/>\nstruct TextWidget {<br \/>\n    Widget base;<br \/>\n    char* value;<br \/>\n    int cursorPos;<br \/>\n    int selectionStart;<br \/>\n    int selectionEnd;<br \/>\n    bool hasSel;<br \/>\n    int scrollOffset;<br \/>\n    char** lines;<br \/>\n    int lineCount;<br \/>\n};<\/p>\n<p>\/\/ \u5bb9\u5668<br \/>\nstruct Frame {<br \/>\n    Widget base;<br \/>\n    Widget** children;<br \/>\n    int childCount;<br \/>\n    int childCapacity;<br \/>\n};<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u63a7\u4ef6\u51fd\u6570\u58f0\u660e &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>\/\/ Label<br \/>\nWidget* Label_create(int x, int y, int w, int h, const char* text);<br \/>\nvoid Label_draw(Widget* self);<br \/>\nbool Label_handleEvent(Widget* self, const SDL_Event* event);<\/p>\n<p>\/\/ Button<br \/>\nWidget* Button_create(int x, int y, int w, int h, const char* text, VoidCallback cb);<br \/>\nvoid Button_draw(Widget* self);<br \/>\nbool Button_handleEvent(Widget* self, const SDL_Event* event);<\/p>\n<p>\/\/ Entry<br \/>\nWidget* Entry_create(int x, int y, int w, int h, const char* text);<br \/>\nvoid Entry_draw(Widget* self);<br \/>\nbool Entry_handleEvent(Widget* self, const SDL_Event* event);<br \/>\nvoid Entry_setText(Widget* self, const char* newText);<br \/>\nchar* Entry_getText(Widget* self);<br \/>\nvoid Entry_selectAll(Widget* self);<br \/>\nvoid Entry_copySelection(Widget* self);<br \/>\nvoid Entry_cutSelection(Widget* self);<br \/>\nvoid Entry_pasteFromClipboard(Widget* self);<br \/>\nvoid Entry_deleteSelection(Widget* self);<br \/>\nbool Entry_hasSelection(Widget* self);<br \/>\nchar* Entry_getSelectedText(Widget* self);<br \/>\nvoid Entry_destroy(Widget* self);<\/p>\n<p>\/\/ TextWidget<br \/>\nWidget* TextWidget_create(int x, int y, int w, int h, const char* text);<br \/>\nvoid TextWidget_draw(Widget* self);<br \/>\nbool TextWidget_handleEvent(Widget* self, const SDL_Event* event);<br \/>\nvoid TextWidget_setText(Widget* self, const char* newText);<br \/>\nchar* TextWidget_getText(Widget* self);<br \/>\nvoid TextWidget_selectAll(Widget* self);<br \/>\nvoid TextWidget_copySelection(Widget* self);<br \/>\nvoid TextWidget_cutSelection(Widget* self);<br \/>\nvoid TextWidget_pasteFromClipboard(Widget* self);<br \/>\nvoid TextWidget_deleteSelection(Widget* self);<br \/>\nbool TextWidget_hasSelection(Widget* self);<br \/>\nchar* TextWidget_getSelectedText(Widget* self);<br \/>\nvoid TextWidget_updateLines(TextWidget* tw);<br \/>\nvoid TextWidget_destroy(Widget* self);<\/p>\n<p>\/\/ Frame<br \/>\nWidget* Frame_create(int x, int y, int w, int h);<br \/>\nvoid Frame_draw(Widget* self);<br \/>\nbool Frame_handleEvent(Widget* self, const SDL_Event* event);<br \/>\nvoid Frame_onMouseMove(Widget* self, int mx, int my);<br \/>\nvoid Frame_setFocus(Widget* self, bool f);<br \/>\nvoid Frame_addChild(Widget* self, Widget* child);<br \/>\nvoid Frame_clearFocus(Widget* self);<br \/>\nWidget* Frame_getFocusedWidget(Widget* self);<br \/>\nWidget* Frame_getWidgetAt(Widget* self, int px, int py);<br \/>\nvoid Frame_destroy(Widget* self);<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u901a\u7528Widget\u51fd\u6570 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>void Widget_init(Widget* w, int x, int y, int wd, int ht, const char* text) {<br \/>\n    w-&gt;x &#061; x;<br \/>\n    w-&gt;y &#061; y;<br \/>\n    w-&gt;w &#061; wd;<br \/>\n    w-&gt;h &#061; ht;<br \/>\n    w-&gt;visible &#061; true;<br \/>\n    w-&gt;enabled &#061; true;<br \/>\n    w-&gt;hover &#061; false;<br \/>\n    w-&gt;pressed &#061; false;<br \/>\n    w-&gt;focus &#061; false;<br \/>\n    w-&gt;text &#061; text ? strdup(text) : NULL;<br \/>\n    w-&gt;callback &#061; NULL;<br \/>\n    w-&gt;focusCallback &#061; NULL;<br \/>\n}<\/p>\n<p>void Widget_setFocus(Widget* self, bool f) {<br \/>\n    if (self-&gt;focus !&#061; f) {<br \/>\n        self-&gt;focus &#061; f;<br \/>\n        if (self-&gt;focusCallback) self-&gt;focusCallback(f);<br \/>\n    }<br \/>\n}<\/p>\n<p>bool Widget_isPointInside(Widget* self, int px, int py) {<br \/>\n    return (px &gt;&#061; self-&gt;x &amp;&amp; px &lt;&#061; self-&gt;x &#043; self-&gt;w &amp;&amp;<br \/>\n            py &gt;&#061; self-&gt;y &amp;&amp; py &lt;&#061; self-&gt;y &#043; self-&gt;h);<br \/>\n}<\/p>\n<p>void Widget_onMouseMove(Widget* self, int mx, int my) {<br \/>\n    self-&gt;hover &#061; Widget_isPointInside(self, mx, my);<br \/>\n}<\/p>\n<p>void Widget_destroy_base(Widget* self) {<br \/>\n    if (self-&gt;text) free(self-&gt;text);<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; Label\u5b9e\u73b0 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>Widget* Label_create(int x, int y, int w, int h, const char* text) {<br \/>\n    Widget* self &#061; (Widget*)calloc(1, sizeof(Widget));<br \/>\n    Widget_init(self, x, y, w, h, text);<br \/>\n    self-&gt;draw &#061; Label_draw;<br \/>\n    self-&gt;handleEvent &#061; Label_handleEvent;<br \/>\n    self-&gt;onMouseMove &#061; Widget_onMouseMove;<br \/>\n    self-&gt;setFocus &#061; Widget_setFocus;<br \/>\n    self-&gt;getText &#061; NULL;<br \/>\n    self-&gt;setText &#061; NULL;<br \/>\n    self-&gt;selectAll &#061; NULL;<br \/>\n    self-&gt;copySelection &#061; NULL;<br \/>\n    self-&gt;cutSelection &#061; NULL;<br \/>\n    self-&gt;pasteFromClipboard &#061; NULL;<br \/>\n    self-&gt;deleteSelection &#061; NULL;<br \/>\n    self-&gt;hasSelection &#061; NULL;<br \/>\n    self-&gt;getSelectedText &#061; NULL;<br \/>\n    self-&gt;isPointInside &#061; Widget_isPointInside;<br \/>\n    self-&gt;destroy &#061; Widget_destroy_base;<br \/>\n    return self;<br \/>\n}<\/p>\n<p>void Label_draw(Widget* self) {<br \/>\n    if (!self-&gt;visible || !self-&gt;text) return;<br \/>\n    Color color &#061; self-&gt;enabled ? g_theme.fg : g_theme.disabled;<br \/>\n    int tw &#061; getTextWidth(self-&gt;text);<br \/>\n    int th &#061; 20;<br \/>\n    int tx &#061; self-&gt;x &#043; (self-&gt;w &#8211; tw) \/ 2;<br \/>\n    int ty &#061; self-&gt;y &#043; (self-&gt;h &#8211; th) \/ 2;<br \/>\n    renderText(g_renderer, self-&gt;text, tx, ty, color);<br \/>\n}<\/p>\n<p>bool Label_handleEvent(Widget* self, const SDL_Event* event) {<br \/>\n    return false;<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; Button\u5b9e\u73b0 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>Widget* Button_create(int x, int y, int w, int h, const char* text, VoidCallback cb) {<br \/>\n    Widget* self &#061; (Widget*)calloc(1, sizeof(Widget));<br \/>\n    Widget_init(self, x, y, w, h, text);<br \/>\n    self-&gt;draw &#061; Button_draw;<br \/>\n    self-&gt;handleEvent &#061; Button_handleEvent;<br \/>\n    self-&gt;onMouseMove &#061; Widget_onMouseMove;<br \/>\n    self-&gt;setFocus &#061; Widget_setFocus;<br \/>\n    self-&gt;isPointInside &#061; Widget_isPointInside;<br \/>\n    self-&gt;destroy &#061; Widget_destroy_base;<br \/>\n    self-&gt;callback &#061; cb;<br \/>\n    return self;<br \/>\n}<\/p>\n<p>void Button_draw(Widget* self) {<br \/>\n    if (!self-&gt;visible || !self-&gt;enabled) return;<\/p>\n<p>    Color bgColor &#061; g_theme.button_bg;<br \/>\n    if (self-&gt;pressed) bgColor &#061; g_theme.button_active;<br \/>\n    else if (self-&gt;hover) bgColor &#061; g_theme.button_hover;<\/p>\n<p>    SDL_SetRenderDrawColor(g_renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);<br \/>\n    SDL_Rect rect &#061; {self-&gt;x, self-&gt;y, self-&gt;w, self-&gt;h};<br \/>\n    SDL_RenderFillRect(g_renderer, &amp;rect);<\/p>\n<p>    SDL_SetRenderDrawColor(g_renderer, g_theme.widget_border.r, g_theme.widget_border.g,<br \/>\n                           g_theme.widget_border.b, 255);<br \/>\n    SDL_RenderDrawRect(g_renderer, &amp;rect);<\/p>\n<p>    if (self-&gt;text) {<br \/>\n        int tw &#061; getTextWidth(self-&gt;text);<br \/>\n        int th &#061; 20;<br \/>\n        int tx &#061; self-&gt;x &#043; (self-&gt;w &#8211; tw) \/ 2;<br \/>\n        int ty &#061; self-&gt;y &#043; (self-&gt;h &#8211; th) \/ 2;<br \/>\n        renderText(g_renderer, self-&gt;text, tx, ty, g_theme.fg);<br \/>\n    }<br \/>\n}<\/p>\n<p>bool Button_handleEvent(Widget* self, const SDL_Event* event) {<br \/>\n    if (!self-&gt;visible || !self-&gt;enabled) return false;<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_MOUSEBUTTONDOWN &amp;&amp; event-&gt;button.button &#061;&#061; SDL_BUTTON_LEFT) {<br \/>\n        if (Widget_isPointInside(self, event-&gt;button.x, event-&gt;button.y)) {<br \/>\n            self-&gt;pressed &#061; true;<br \/>\n            return true;<br \/>\n        }<br \/>\n    }<br \/>\n    if (event-&gt;type &#061;&#061; SDL_MOUSEBUTTONUP &amp;&amp; event-&gt;button.button &#061;&#061; SDL_BUTTON_LEFT) {<br \/>\n        if (self-&gt;pressed) {<br \/>\n            self-&gt;pressed &#061; false;<br \/>\n            if (Widget_isPointInside(self, event-&gt;button.x, event-&gt;button.y)) {<br \/>\n                if (self-&gt;callback) self-&gt;callback();<br \/>\n                return true;<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n    return false;<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; Entry\u5b9e\u73b0 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>Widget* Entry_create(int x, int y, int w, int h, const char* text) {<br \/>\n    Entry* self &#061; (Entry*)calloc(1, sizeof(Entry));<br \/>\n    Widget_init((Widget*)self, x, y, w, h, text);<br \/>\n    self-&gt;value &#061; text ? strdup(text) : strdup(&#034;&#034;);<br \/>\n    self-&gt;cursorPos &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n    self-&gt;selectionStart &#061; -1;<br \/>\n    self-&gt;selectionEnd &#061; -1;<br \/>\n    self-&gt;hasSel &#061; false;<\/p>\n<p>    Widget* base &#061; (Widget*)self;<br \/>\n    base-&gt;draw &#061; Entry_draw;<br \/>\n    base-&gt;handleEvent &#061; Entry_handleEvent;<br \/>\n    base-&gt;onMouseMove &#061; Widget_onMouseMove;<br \/>\n    base-&gt;setFocus &#061; Widget_setFocus;<br \/>\n    base-&gt;getText &#061; Entry_getText;<br \/>\n    base-&gt;setText &#061; Entry_setText;<br \/>\n    base-&gt;selectAll &#061; Entry_selectAll;<br \/>\n    base-&gt;copySelection &#061; Entry_copySelection;<br \/>\n    base-&gt;cutSelection &#061; Entry_cutSelection;<br \/>\n    base-&gt;pasteFromClipboard &#061; Entry_pasteFromClipboard;<br \/>\n    base-&gt;deleteSelection &#061; Entry_deleteSelection;<br \/>\n    base-&gt;hasSelection &#061; Entry_hasSelection;<br \/>\n    base-&gt;getSelectedText &#061; Entry_getSelectedText;<br \/>\n    base-&gt;isPointInside &#061; Widget_isPointInside;<br \/>\n    base-&gt;destroy &#061; Entry_destroy;<br \/>\n    return (Widget*)self;<br \/>\n}<\/p>\n<p>void Entry_draw(Widget* base) {<br \/>\n    if (!base-&gt;visible) return;<br \/>\n    Entry* self &#061; (Entry*)base;<\/p>\n<p>    Color bgColor &#061; base-&gt;enabled ? g_theme.widget_bg : g_theme.disabled;<br \/>\n    SDL_SetRenderDrawColor(g_renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);<br \/>\n    SDL_Rect rect &#061; {base-&gt;x, base-&gt;y, base-&gt;w, base-&gt;h};<br \/>\n    SDL_RenderFillRect(g_renderer, &amp;rect);<\/p>\n<p>    Color borderColor &#061; base-&gt;focus ? g_theme.accent : g_theme.widget_border;<br \/>\n    SDL_SetRenderDrawColor(g_renderer, borderColor.r, borderColor.g, borderColor.b, 255);<br \/>\n    SDL_RenderDrawRect(g_renderer, &amp;rect);<\/p>\n<p>    int textX &#061; base-&gt;x &#043; 5;<br \/>\n    int textY &#061; base-&gt;y &#043; (base-&gt;h &#8211; 20) \/ 2;<br \/>\n    char* value &#061; self-&gt;value ? self-&gt;value : &#034;&#034;;<\/p>\n<p>    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<\/p>\n<p>        int len &#061; strlen(value);<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<\/p>\n<p>        char* before &#061; strndup(value, start);<br \/>\n        char* selected &#061; strndup(value &#043; start, end &#8211; start);<br \/>\n        char* after &#061; strdup(value &#043; end);<\/p>\n<p>        int beforeW &#061; before ? getTextWidth(before) : 0;<br \/>\n        int selW &#061; selected ? getTextWidth(selected) : 0;<\/p>\n<p>        SDL_SetRenderDrawColor(g_renderer, g_theme.selection.r, g_theme.selection.g,<br \/>\n                               g_theme.selection.b, g_theme.selection.a);<br \/>\n        SDL_Rect selRect &#061; {textX &#043; beforeW, textY &#8211; 2, selW, 24};<br \/>\n        SDL_RenderFillRect(g_renderer, &amp;selRect);<\/p>\n<p>        Color white &#061; {255, 255, 255, 255};<br \/>\n        if (before) renderText(g_renderer, before, textX, textY, g_theme.fg);<br \/>\n        if (selected) renderText(g_renderer, selected, textX &#043; beforeW, textY, white);<br \/>\n        if (after) renderText(g_renderer, after, textX &#043; beforeW &#043; selW, textY, g_theme.fg);<\/p>\n<p>        free(before);<br \/>\n        free(selected);<br \/>\n        free(after);<br \/>\n    } else {<br \/>\n        renderText(g_renderer, value, textX, textY, g_theme.fg);<br \/>\n    }<\/p>\n<p>    if (base-&gt;focus &amp;&amp; base-&gt;enabled) {<br \/>\n        Uint32 now &#061; SDL_GetTicks();<br \/>\n        if ((now \/ 500) % 2 &#061;&#061; 0) {<br \/>\n            char* prefix &#061; self-&gt;value ? strndup(self-&gt;value, self-&gt;cursorPos) : NULL;<br \/>\n            int cursorX &#061; textX &#043; (prefix ? getTextWidth(prefix) : 0);<br \/>\n            SDL_SetRenderDrawColor(g_renderer, 0, 0, 0, 255);<br \/>\n            SDL_RenderDrawLine(g_renderer, cursorX, textY &#043; 2, cursorX, textY &#043; 22);<br \/>\n            free(prefix);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>bool Entry_handleEvent(Widget* base, const SDL_Event* event) {<br \/>\n    if (!base-&gt;visible || !base-&gt;enabled) return false;<br \/>\n    Entry* self &#061; (Entry*)base;<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_MOUSEBUTTONDOWN &amp;&amp; event-&gt;button.button &#061;&#061; SDL_BUTTON_LEFT) {<br \/>\n        if (Widget_isPointInside(base, event-&gt;button.x, event-&gt;button.y)) {<br \/>\n            Widget_setFocus(base, true);<br \/>\n            int textX &#061; base-&gt;x &#043; 5;<br \/>\n            int pos &#061; 0;<br \/>\n            char* value &#061; self-&gt;value ? self-&gt;value : &#034;&#034;;<br \/>\n            int len &#061; strlen(value);<br \/>\n            for (int i &#061; 0; i &lt;&#061; len; i&#043;&#043;) {<br \/>\n                char* prefix &#061; strndup(value, i);<br \/>\n                int px &#061; textX &#043; (prefix ? getTextWidth(prefix) : 0);<br \/>\n                free(prefix);<br \/>\n                if (event-&gt;button.x &lt; px &#043; 5) {<br \/>\n                    pos &#061; i;<br \/>\n                    break;<br \/>\n                }<br \/>\n                if (i &#061;&#061; len) pos &#061; i;<br \/>\n            }<br \/>\n            self-&gt;cursorPos &#061; pos;<br \/>\n            return true;<br \/>\n        } else {<br \/>\n            if (base-&gt;focus) Widget_setFocus(base, false);<br \/>\n            self-&gt;hasSel &#061; false;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_TEXTINPUT &amp;&amp; base-&gt;focus) {<br \/>\n        if (self-&gt;hasSel) {<br \/>\n            int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n            int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n            int len &#061; strlen(self-&gt;value);<br \/>\n            if (start &gt; len) start &#061; len;<br \/>\n            if (end &gt; len) end &#061; len;<br \/>\n            char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; strlen(event-&gt;text.text) &#043; 1);<br \/>\n            strncpy(newVal, self-&gt;value, start);<br \/>\n            newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, event-&gt;text.text);<br \/>\n            strcat(newVal, self-&gt;value &#043; end);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos &#061; start &#043; strlen(event-&gt;text.text);<br \/>\n            self-&gt;hasSel &#061; false;<br \/>\n            self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n        } else {<br \/>\n            int len &#061; strlen(self-&gt;value);<br \/>\n            char* newVal &#061; (char*)malloc(len &#043; strlen(event-&gt;text.text) &#043; 1);<br \/>\n            strncpy(newVal, self-&gt;value, self-&gt;cursorPos);<br \/>\n            newVal[self-&gt;cursorPos] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, event-&gt;text.text);<br \/>\n            strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos &#043;&#061; strlen(event-&gt;text.text);<br \/>\n        }<br \/>\n        if (base-&gt;callback) base-&gt;callback();<br \/>\n        return true;<br \/>\n    }<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_KEYDOWN &amp;&amp; base-&gt;focus) {<br \/>\n        bool shift &#061; event-&gt;key.keysym.mod &amp; KMOD_SHIFT;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<\/p>\n<p>        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_LEFT) {<br \/>\n            if (shift) {<br \/>\n                if (!self-&gt;hasSel) {<br \/>\n                    self-&gt;selectionStart &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;hasSel &#061; true;<br \/>\n                }<br \/>\n                if (self-&gt;cursorPos &gt; 0) {<br \/>\n                    self-&gt;cursorPos&#8211;;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                }<br \/>\n            } else {<br \/>\n                if (self-&gt;cursorPos &gt; 0) self-&gt;cursorPos&#8211;;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_RIGHT) {<br \/>\n            if (shift) {<br \/>\n                if (!self-&gt;hasSel) {<br \/>\n                    self-&gt;selectionStart &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;hasSel &#061; true;<br \/>\n                }<br \/>\n                if (self-&gt;cursorPos &lt; len) {<br \/>\n                    self-&gt;cursorPos&#043;&#043;;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                }<br \/>\n            } else {<br \/>\n                if (self-&gt;cursorPos &lt; len) self-&gt;cursorPos&#043;&#043;;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_HOME) {<br \/>\n            self-&gt;cursorPos &#061; 0;<br \/>\n            if (shift) {<br \/>\n                if (!self-&gt;hasSel) {<br \/>\n                    self-&gt;selectionStart &#061; 0;<br \/>\n                    self-&gt;selectionEnd &#061; 0;<br \/>\n                    self-&gt;hasSel &#061; true;<br \/>\n                }<br \/>\n                self-&gt;selectionEnd &#061; 0;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_END) {<br \/>\n            self-&gt;cursorPos &#061; len;<br \/>\n            if (shift) {<br \/>\n                if (!self-&gt;hasSel) {<br \/>\n                    self-&gt;selectionStart &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;hasSel &#061; true;<br \/>\n                }<br \/>\n                self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_BACKSPACE) {<br \/>\n            if (self-&gt;hasSel) {<br \/>\n                int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                if (start &gt; len) start &#061; len;<br \/>\n                if (end &gt; len) end &#061; len;<br \/>\n                char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; 1);<br \/>\n                strncpy(newVal, self-&gt;value, start);<br \/>\n                newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; end);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n                self-&gt;cursorPos &#061; start;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            } else if (self-&gt;cursorPos &gt; 0) {<br \/>\n                char* newVal &#061; (char*)malloc(len);<br \/>\n                strncpy(newVal, self-&gt;value, self-&gt;cursorPos &#8211; 1);<br \/>\n                newVal[self-&gt;cursorPos &#8211; 1] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n                self-&gt;cursorPos&#8211;;<br \/>\n            }<br \/>\n            if (base-&gt;callback) base-&gt;callback();<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_DELETE) {<br \/>\n            if (self-&gt;hasSel) {<br \/>\n                int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                if (start &gt; len) start &#061; len;<br \/>\n                if (end &gt; len) end &#061; len;<br \/>\n                char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; 1);<br \/>\n                strncpy(newVal, self-&gt;value, start);<br \/>\n                newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; end);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n                self-&gt;cursorPos &#061; start;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            } else if (self-&gt;cursorPos &lt; len) {<br \/>\n                char* newVal &#061; (char*)malloc(len);<br \/>\n                strncpy(newVal, self-&gt;value, self-&gt;cursorPos);<br \/>\n                newVal[self-&gt;cursorPos] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos &#043; 1);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n            }<br \/>\n            if (base-&gt;callback) base-&gt;callback();<br \/>\n            return true;<br \/>\n        }<br \/>\n    }<br \/>\n    return false;<br \/>\n}<\/p>\n<p>char* Entry_getText(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    return self-&gt;value ? strdup(self-&gt;value) : strdup(&#034;&#034;);<br \/>\n}<\/p>\n<p>void Entry_setText(Widget* base, const char* newText) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    if (self-&gt;value) free(self-&gt;value);<br \/>\n    self-&gt;value &#061; newText ? strdup(newText) : strdup(&#034;&#034;);<br \/>\n    self-&gt;cursorPos &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n    if (base-&gt;callback) base-&gt;callback();<br \/>\n}<\/p>\n<p>void Entry_selectAll(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    self-&gt;selectionStart &#061; 0;<br \/>\n    self-&gt;selectionEnd &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n    self-&gt;hasSel &#061; true;<br \/>\n}<\/p>\n<p>void Entry_copySelection(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<br \/>\n        char* selected &#061; strndup(self-&gt;value &#043; start, end &#8211; start);<br \/>\n        if (selected) {<br \/>\n            setClipboardText(selected);<br \/>\n            free(selected);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>void Entry_cutSelection(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<br \/>\n        char* selected &#061; strndup(self-&gt;value &#043; start, end &#8211; start);<br \/>\n        if (selected) {<br \/>\n            if (setClipboardText(selected)) {<br \/>\n                char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; 1);<br \/>\n                strncpy(newVal, self-&gt;value, start);<br \/>\n                newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; end);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n                self-&gt;cursorPos &#061; start;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n                if (base-&gt;callback) base-&gt;callback();<br \/>\n            }<br \/>\n            free(selected);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>void Entry_pasteFromClipboard(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    char* clipText &#061; getClipboardText();<br \/>\n    if (clipText) {<br \/>\n        if (self-&gt;hasSel) {<br \/>\n            int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n            int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n            int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n            if (start &gt; len) start &#061; len;<br \/>\n            if (end &gt; len) end &#061; len;<br \/>\n            char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; strlen(clipText) &#043; 1);<br \/>\n            strncpy(newVal, self-&gt;value, start);<br \/>\n            newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, clipText);<br \/>\n            strcat(newVal, self-&gt;value &#043; end);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos &#061; start &#043; strlen(clipText);<br \/>\n            self-&gt;hasSel &#061; false;<br \/>\n            self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n        } else {<br \/>\n            int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n            char* newVal &#061; (char*)malloc(len &#043; strlen(clipText) &#043; 1);<br \/>\n            strncpy(newVal, self-&gt;value, self-&gt;cursorPos);<br \/>\n            newVal[self-&gt;cursorPos] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, clipText);<br \/>\n            strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos &#043;&#061; strlen(clipText);<br \/>\n        }<br \/>\n        if (base-&gt;callback) base-&gt;callback();<br \/>\n        free(clipText);<br \/>\n    }<br \/>\n}<\/p>\n<p>void Entry_deleteSelection(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<br \/>\n        char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; 1);<br \/>\n        strncpy(newVal, self-&gt;value, start);<br \/>\n        newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n        strcat(newVal, self-&gt;value &#043; end);<br \/>\n        free(self-&gt;value);<br \/>\n        self-&gt;value &#061; newVal;<br \/>\n        self-&gt;cursorPos &#061; start;<br \/>\n        self-&gt;hasSel &#061; false;<br \/>\n        self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n        if (base-&gt;callback) base-&gt;callback();<br \/>\n    }<br \/>\n}<\/p>\n<p>bool Entry_hasSelection(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    return self-&gt;hasSel;<br \/>\n}<\/p>\n<p>char* Entry_getSelectedText(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<br \/>\n        return strndup(self-&gt;value &#043; start, end &#8211; start);<br \/>\n    }<br \/>\n    return strdup(&#034;&#034;);<br \/>\n}<\/p>\n<p>void Entry_destroy(Widget* base) {<br \/>\n    Entry* self &#061; (Entry*)base;<br \/>\n    if (self-&gt;value) free(self-&gt;value);<br \/>\n    Widget_destroy_base(base);<br \/>\n    free(self);<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; TextWidget\u5b9e\u73b0 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>Widget* TextWidget_create(int x, int y, int w, int h, const char* text) {<br \/>\n    TextWidget* self &#061; (TextWidget*)calloc(1, sizeof(TextWidget));<br \/>\n    Widget_init((Widget*)self, x, y, w, h, text);<br \/>\n    self-&gt;value &#061; text ? strdup(text) : strdup(&#034;&#034;);<br \/>\n    self-&gt;cursorPos &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n    self-&gt;selectionStart &#061; -1;<br \/>\n    self-&gt;selectionEnd &#061; -1;<br \/>\n    self-&gt;hasSel &#061; false;<br \/>\n    self-&gt;scrollOffset &#061; 0;<br \/>\n    self-&gt;lines &#061; NULL;<br \/>\n    self-&gt;lineCount &#061; 0;<br \/>\n    TextWidget_updateLines(self);<\/p>\n<p>    Widget* base &#061; (Widget*)self;<br \/>\n    base-&gt;draw &#061; TextWidget_draw;<br \/>\n    base-&gt;handleEvent &#061; TextWidget_handleEvent;<br \/>\n    base-&gt;onMouseMove &#061; Widget_onMouseMove;<br \/>\n    base-&gt;setFocus &#061; Widget_setFocus;<br \/>\n    base-&gt;getText &#061; TextWidget_getText;<br \/>\n    base-&gt;setText &#061; TextWidget_setText;<br \/>\n    base-&gt;selectAll &#061; TextWidget_selectAll;<br \/>\n    base-&gt;copySelection &#061; TextWidget_copySelection;<br \/>\n    base-&gt;cutSelection &#061; TextWidget_cutSelection;<br \/>\n    base-&gt;pasteFromClipboard &#061; TextWidget_pasteFromClipboard;<br \/>\n    base-&gt;deleteSelection &#061; TextWidget_deleteSelection;<br \/>\n    base-&gt;hasSelection &#061; TextWidget_hasSelection;<br \/>\n    base-&gt;getSelectedText &#061; TextWidget_getSelectedText;<br \/>\n    base-&gt;isPointInside &#061; Widget_isPointInside;<br \/>\n    base-&gt;destroy &#061; TextWidget_destroy;<br \/>\n    return (Widget*)self;<br \/>\n}<\/p>\n<p>void TextWidget_updateLines(TextWidget* self) {<br \/>\n    if (self-&gt;lines) {<br \/>\n        for (int i &#061; 0; i &lt; self-&gt;lineCount; i&#043;&#043;) {<br \/>\n            free(self-&gt;lines[i]);<br \/>\n        }<br \/>\n        free(self-&gt;lines);<br \/>\n        self-&gt;lines &#061; NULL;<br \/>\n        self-&gt;lineCount &#061; 0;<br \/>\n    }<\/p>\n<p>    if (!self-&gt;value) {<br \/>\n        self-&gt;lines &#061; malloc(sizeof(char*));<br \/>\n        self-&gt;lines[0] &#061; strdup(&#034;&#034;);<br \/>\n        self-&gt;lineCount &#061; 1;<br \/>\n        return;<br \/>\n    }<\/p>\n<p>    int count &#061; 0;<br \/>\n    char* temp &#061; strdup(self-&gt;value);<br \/>\n    char* token &#061; strtok(temp, &#034;\\\\n&#034;);<br \/>\n    while (token) {<br \/>\n        count&#043;&#043;;<br \/>\n        token &#061; strtok(NULL, &#034;\\\\n&#034;);<br \/>\n    }<br \/>\n    free(temp);<\/p>\n<p>    if (count &#061;&#061; 0) {<br \/>\n        count &#061; 1;<br \/>\n    }<\/p>\n<p>    self-&gt;lines &#061; malloc(sizeof(char*) * count);<br \/>\n    self-&gt;lineCount &#061; 0;<\/p>\n<p>    temp &#061; strdup(self-&gt;value);<br \/>\n    token &#061; strtok(temp, &#034;\\\\n&#034;);<br \/>\n    while (token) {<br \/>\n        self-&gt;lines[self-&gt;lineCount&#043;&#043;] &#061; strdup(token);<br \/>\n        token &#061; strtok(NULL, &#034;\\\\n&#034;);<br \/>\n    }<br \/>\n    free(temp);<\/p>\n<p>    if (self-&gt;lineCount &#061;&#061; 0) {<br \/>\n        self-&gt;lines &#061; realloc(self-&gt;lines, sizeof(char*));<br \/>\n        self-&gt;lines[0] &#061; strdup(&#034;&#034;);<br \/>\n        self-&gt;lineCount &#061; 1;<br \/>\n    }<br \/>\n}<\/p>\n<p>void TextWidget_draw(Widget* base) {<br \/>\n    if (!base-&gt;visible) return;<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<\/p>\n<p>    Color bgColor &#061; base-&gt;enabled ? g_theme.widget_bg : g_theme.disabled;<br \/>\n    SDL_SetRenderDrawColor(g_renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);<br \/>\n    SDL_Rect rect &#061; {base-&gt;x, base-&gt;y, base-&gt;w, base-&gt;h};<br \/>\n    SDL_RenderFillRect(g_renderer, &amp;rect);<\/p>\n<p>    Color borderColor &#061; base-&gt;focus ? g_theme.accent : g_theme.widget_border;<br \/>\n    SDL_SetRenderDrawColor(g_renderer, borderColor.r, borderColor.g, borderColor.b, 255);<br \/>\n    SDL_RenderDrawRect(g_renderer, &amp;rect);<\/p>\n<p>    SDL_Rect clip &#061; {base-&gt;x &#043; 2, base-&gt;y &#043; 2, base-&gt;w &#8211; 4, base-&gt;h &#8211; 4};<br \/>\n    SDL_RenderSetClipRect(g_renderer, &amp;clip);<\/p>\n<p>    int lineHeight &#061; 24;<br \/>\n    int textX &#061; base-&gt;x &#043; 5;<br \/>\n    int textY &#061; base-&gt;y &#043; 5 &#8211; self-&gt;scrollOffset;<\/p>\n<p>    char* value &#061; self-&gt;value ? self-&gt;value : &#034;&#034;;<br \/>\n    int globalOffset &#061; 0;<br \/>\n    int len &#061; strlen(value);<\/p>\n<p>    for (int i &#061; 0; i &lt; self-&gt;lineCount; i&#043;&#043;) {<br \/>\n        if (textY &gt;&#061; base-&gt;y &#043; base-&gt;h) break;<br \/>\n        if (textY &#043; lineHeight &gt;&#061; base-&gt;y) {<br \/>\n            char* line &#061; self-&gt;lines[i];<br \/>\n            int lineStart &#061; globalOffset;<br \/>\n            int lineEnd &#061; globalOffset &#043; strlen(line);<\/p>\n<p>            if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n                int selStart &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                int selEnd &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<\/p>\n<p>                if (selStart &lt; lineEnd &amp;&amp; selEnd &gt; lineStart) {<br \/>\n                    int localStart &#061; selStart &gt; lineStart ? selStart &#8211; lineStart : 0;<br \/>\n                    int localEnd &#061; selEnd &lt; lineEnd ? selEnd &#8211; lineStart : strlen(line);<\/p>\n<p>                    char* before &#061; strndup(line, localStart);<br \/>\n                    char* selected &#061; strndup(line &#043; localStart, localEnd &#8211; localStart);<br \/>\n                    char* after &#061; strdup(line &#043; localEnd);<\/p>\n<p>                    int beforeW &#061; before ? getTextWidth(before) : 0;<br \/>\n                    int selW &#061; selected ? getTextWidth(selected) : 0;<\/p>\n<p>                    SDL_SetRenderDrawColor(g_renderer, g_theme.selection.r,<br \/>\n                                           g_theme.selection.g, g_theme.selection.b,<br \/>\n                                           g_theme.selection.a);<br \/>\n                    SDL_Rect selRect &#061; {textX &#043; beforeW, textY &#8211; 2, selW, 22};<br \/>\n                    SDL_RenderFillRect(g_renderer, &amp;selRect);<\/p>\n<p>                    Color white &#061; {255, 255, 255, 255};<br \/>\n                    if (before) renderText(g_renderer, before, textX, textY, g_theme.fg);<br \/>\n                    if (selected) renderText(g_renderer, selected, textX &#043; beforeW, textY, white);<br \/>\n                    if (after) renderText(g_renderer, after, textX &#043; beforeW &#043; selW, textY, g_theme.fg);<\/p>\n<p>                    free(before);<br \/>\n                    free(selected);<br \/>\n                    free(after);<br \/>\n                } else {<br \/>\n                    renderText(g_renderer, line, textX, textY, g_theme.fg);<br \/>\n                }<br \/>\n            } else {<br \/>\n                renderText(g_renderer, line, textX, textY, g_theme.fg);<br \/>\n            }<\/p>\n<p>            if (base-&gt;focus &amp;&amp; base-&gt;enabled &amp;&amp; self-&gt;cursorPos &gt;&#061; lineStart &amp;&amp; self-&gt;cursorPos &lt;&#061; lineEnd) {<br \/>\n                Uint32 now &#061; SDL_GetTicks();<br \/>\n                if ((now \/ 500) % 2 &#061;&#061; 0) {<br \/>\n                    char* prefix &#061; strndup(value &#043; lineStart, self-&gt;cursorPos &#8211; lineStart);<br \/>\n                    int cursorX &#061; textX &#043; (prefix ? getTextWidth(prefix) : 0);<br \/>\n                    SDL_SetRenderDrawColor(g_renderer, 0, 0, 0, 255);<br \/>\n                    SDL_RenderDrawLine(g_renderer, cursorX, textY &#043; 2, cursorX, textY &#043; 22);<br \/>\n                    free(prefix);<br \/>\n                }<br \/>\n            }<br \/>\n        }<br \/>\n        globalOffset &#043;&#061; strlen(self-&gt;lines[i]) &#043; 1;<br \/>\n        textY &#043;&#061; lineHeight;<br \/>\n    }<\/p>\n<p>    SDL_RenderSetClipRect(g_renderer, NULL);<br \/>\n}<\/p>\n<p>bool TextWidget_handleEvent(Widget* base, const SDL_Event* event) {<br \/>\n    if (!base-&gt;visible || !base-&gt;enabled) return false;<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_MOUSEBUTTONDOWN &amp;&amp; event-&gt;button.button &#061;&#061; SDL_BUTTON_LEFT) {<br \/>\n        if (Widget_isPointInside(base, event-&gt;button.x, event-&gt;button.y)) {<br \/>\n            Widget_setFocus(base, true);<br \/>\n            int lineHeight &#061; 24;<br \/>\n            int lineIndex &#061; (event-&gt;button.y &#8211; base-&gt;y &#8211; 5 &#043; self-&gt;scrollOffset) \/ lineHeight;<br \/>\n            if (lineIndex &gt;&#061; 0 &amp;&amp; lineIndex &lt; self-&gt;lineCount) {<br \/>\n                int globalPos &#061; 0;<br \/>\n                for (int i &#061; 0; i &lt; lineIndex; i&#043;&#043;) {<br \/>\n                    globalPos &#043;&#061; strlen(self-&gt;lines[i]) &#043; 1;<br \/>\n                }<br \/>\n                char* line &#061; self-&gt;lines[lineIndex];<br \/>\n                int textX &#061; base-&gt;x &#043; 5;<br \/>\n                int pos &#061; 0;<br \/>\n                int len &#061; strlen(line);<br \/>\n                for (int i &#061; 0; i &lt;&#061; len; i&#043;&#043;) {<br \/>\n                    char* prefix &#061; strndup(line, i);<br \/>\n                    int px &#061; textX &#043; (prefix ? getTextWidth(prefix) : 0);<br \/>\n                    free(prefix);<br \/>\n                    if (event-&gt;button.x &lt; px &#043; 5) {<br \/>\n                        pos &#061; i;<br \/>\n                        break;<br \/>\n                    }<br \/>\n                    if (i &#061;&#061; len) pos &#061; i;<br \/>\n                }<br \/>\n                self-&gt;cursorPos &#061; globalPos &#043; pos;<br \/>\n            } else {<br \/>\n                self-&gt;cursorPos &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n            }<br \/>\n            self-&gt;hasSel &#061; false;<br \/>\n            self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            return true;<br \/>\n        } else {<br \/>\n            if (base-&gt;focus) Widget_setFocus(base, false);<br \/>\n            self-&gt;hasSel &#061; false;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_TEXTINPUT &amp;&amp; base-&gt;focus) {<br \/>\n        if (self-&gt;hasSel) {<br \/>\n            int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n            int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n            int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n            if (start &gt; len) start &#061; len;<br \/>\n            if (end &gt; len) end &#061; len;<br \/>\n            char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; strlen(event-&gt;text.text) &#043; 1);<br \/>\n            strncpy(newVal, self-&gt;value, start);<br \/>\n            newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, event-&gt;text.text);<br \/>\n            strcat(newVal, self-&gt;value &#043; end);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos &#061; start &#043; strlen(event-&gt;text.text);<br \/>\n            self-&gt;hasSel &#061; false;<br \/>\n            self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n        } else {<br \/>\n            int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n            char* newVal &#061; (char*)malloc(len &#043; strlen(event-&gt;text.text) &#043; 1);<br \/>\n            strncpy(newVal, self-&gt;value, self-&gt;cursorPos);<br \/>\n            newVal[self-&gt;cursorPos] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, event-&gt;text.text);<br \/>\n            strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos &#043;&#061; strlen(event-&gt;text.text);<br \/>\n        }<br \/>\n        TextWidget_updateLines(self);<br \/>\n        int totalHeight &#061; self-&gt;lineCount * 24;<br \/>\n        if (totalHeight &gt; base-&gt;h &#8211; 10) {<br \/>\n            self-&gt;scrollOffset &#061; totalHeight &#8211; (base-&gt;h &#8211; 10);<br \/>\n        }<br \/>\n        if (base-&gt;callback) base-&gt;callback();<br \/>\n        return true;<br \/>\n    }<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_KEYDOWN &amp;&amp; base-&gt;focus) {<br \/>\n        bool shift &#061; event-&gt;key.keysym.mod &amp; KMOD_SHIFT;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<\/p>\n<p>        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_LEFT) {<br \/>\n            if (shift) {<br \/>\n                if (!self-&gt;hasSel) {<br \/>\n                    self-&gt;selectionStart &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;hasSel &#061; true;<br \/>\n                }<br \/>\n                if (self-&gt;cursorPos &gt; 0) {<br \/>\n                    self-&gt;cursorPos&#8211;;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                }<br \/>\n            } else {<br \/>\n                if (self-&gt;cursorPos &gt; 0) self-&gt;cursorPos&#8211;;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_RIGHT) {<br \/>\n            if (shift) {<br \/>\n                if (!self-&gt;hasSel) {<br \/>\n                    self-&gt;selectionStart &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;hasSel &#061; true;<br \/>\n                }<br \/>\n                if (self-&gt;cursorPos &lt; len) {<br \/>\n                    self-&gt;cursorPos&#043;&#043;;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                }<br \/>\n            } else {<br \/>\n                if (self-&gt;cursorPos &lt; len) self-&gt;cursorPos&#043;&#043;;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_UP) {<br \/>\n            int lineIndex &#061; 0;<br \/>\n            int pos &#061; 0;<br \/>\n            for (int i &#061; 0; i &lt; self-&gt;lineCount; i&#043;&#043;) {<br \/>\n                int lineLen &#061; strlen(self-&gt;lines[i]);<br \/>\n                if (self-&gt;cursorPos &gt;&#061; pos &amp;&amp; self-&gt;cursorPos &lt;&#061; pos &#043; lineLen) {<br \/>\n                    lineIndex &#061; i;<br \/>\n                    break;<br \/>\n                }<br \/>\n                pos &#043;&#061; lineLen &#043; 1;<br \/>\n            }<br \/>\n            if (lineIndex &gt; 0) {<br \/>\n                int targetPos &#061; pos &#8211; strlen(self-&gt;lines[lineIndex-1]) &#8211; 1;<br \/>\n                int offset &#061; self-&gt;cursorPos &#8211; pos;<br \/>\n                int lineLen &#061; strlen(self-&gt;lines[lineIndex-1]);<br \/>\n                if (offset &gt; lineLen) offset &#061; lineLen;<br \/>\n                self-&gt;cursorPos &#061; targetPos &#043; offset;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_DOWN) {<br \/>\n            int lineIndex &#061; 0;<br \/>\n            int pos &#061; 0;<br \/>\n            for (int i &#061; 0; i &lt; self-&gt;lineCount; i&#043;&#043;) {<br \/>\n                int lineLen &#061; strlen(self-&gt;lines[i]);<br \/>\n                if (self-&gt;cursorPos &gt;&#061; pos &amp;&amp; self-&gt;cursorPos &lt;&#061; pos &#043; lineLen) {<br \/>\n                    lineIndex &#061; i;<br \/>\n                    break;<br \/>\n                }<br \/>\n                pos &#043;&#061; lineLen &#043; 1;<br \/>\n            }<br \/>\n            if (lineIndex &lt; self-&gt;lineCount &#8211; 1) {<br \/>\n                int targetPos &#061; pos &#043; strlen(self-&gt;lines[lineIndex]) &#043; 1;<br \/>\n                int offset &#061; self-&gt;cursorPos &#8211; pos;<br \/>\n                int lineLen &#061; strlen(self-&gt;lines[lineIndex&#043;1]);<br \/>\n                if (offset &gt; lineLen) offset &#061; lineLen;<br \/>\n                self-&gt;cursorPos &#061; targetPos &#043; offset;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_HOME) {<br \/>\n            self-&gt;cursorPos &#061; 0;<br \/>\n            if (shift) {<br \/>\n                if (!self-&gt;hasSel) {<br \/>\n                    self-&gt;selectionStart &#061; 0;<br \/>\n                    self-&gt;selectionEnd &#061; 0;<br \/>\n                    self-&gt;hasSel &#061; true;<br \/>\n                }<br \/>\n                self-&gt;selectionEnd &#061; 0;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_END) {<br \/>\n            self-&gt;cursorPos &#061; len;<br \/>\n            if (shift) {<br \/>\n                if (!self-&gt;hasSel) {<br \/>\n                    self-&gt;selectionStart &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n                    self-&gt;hasSel &#061; true;<br \/>\n                }<br \/>\n                self-&gt;selectionEnd &#061; self-&gt;cursorPos;<br \/>\n            }<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_BACKSPACE) {<br \/>\n            if (self-&gt;hasSel) {<br \/>\n                int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                if (start &gt; len) start &#061; len;<br \/>\n                if (end &gt; len) end &#061; len;<br \/>\n                char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; 1);<br \/>\n                strncpy(newVal, self-&gt;value, start);<br \/>\n                newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; end);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n                self-&gt;cursorPos &#061; start;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            } else if (self-&gt;cursorPos &gt; 0) {<br \/>\n                char* newVal &#061; (char*)malloc(len);<br \/>\n                strncpy(newVal, self-&gt;value, self-&gt;cursorPos &#8211; 1);<br \/>\n                newVal[self-&gt;cursorPos &#8211; 1] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n                self-&gt;cursorPos&#8211;;<br \/>\n            }<br \/>\n            TextWidget_updateLines(self);<br \/>\n            if (base-&gt;callback) base-&gt;callback();<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_DELETE) {<br \/>\n            if (self-&gt;hasSel) {<br \/>\n                int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n                if (start &gt; len) start &#061; len;<br \/>\n                if (end &gt; len) end &#061; len;<br \/>\n                char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; 1);<br \/>\n                strncpy(newVal, self-&gt;value, start);<br \/>\n                newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; end);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n                self-&gt;cursorPos &#061; start;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n            } else if (self-&gt;cursorPos &lt; len) {<br \/>\n                char* newVal &#061; (char*)malloc(len);<br \/>\n                strncpy(newVal, self-&gt;value, self-&gt;cursorPos);<br \/>\n                newVal[self-&gt;cursorPos] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos &#043; 1);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n            }<br \/>\n            TextWidget_updateLines(self);<br \/>\n            if (base-&gt;callback) base-&gt;callback();<br \/>\n            return true;<br \/>\n        }<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_RETURN || event-&gt;key.keysym.sym &#061;&#061; SDLK_KP_ENTER) {<br \/>\n            char* newVal &#061; (char*)malloc(len &#043; 2);<br \/>\n            strncpy(newVal, self-&gt;value, self-&gt;cursorPos);<br \/>\n            newVal[self-&gt;cursorPos] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, &#034;\\\\n&#034;);<br \/>\n            strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos&#043;&#043;;<br \/>\n            TextWidget_updateLines(self);<br \/>\n            if (base-&gt;callback) base-&gt;callback();<br \/>\n            return true;<br \/>\n        }<br \/>\n    }<br \/>\n    return false;<br \/>\n}<\/p>\n<p>char* TextWidget_getText(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    return self-&gt;value ? strdup(self-&gt;value) : strdup(&#034;&#034;);<br \/>\n}<\/p>\n<p>void TextWidget_setText(Widget* base, const char* newText) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    if (self-&gt;value) free(self-&gt;value);<br \/>\n    self-&gt;value &#061; newText ? strdup(newText) : strdup(&#034;&#034;);<br \/>\n    self-&gt;cursorPos &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n    TextWidget_updateLines(self);<br \/>\n    if (base-&gt;callback) base-&gt;callback();<br \/>\n}<\/p>\n<p>void TextWidget_selectAll(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    self-&gt;selectionStart &#061; 0;<br \/>\n    self-&gt;selectionEnd &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n    self-&gt;hasSel &#061; true;<br \/>\n}<\/p>\n<p>void TextWidget_copySelection(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<br \/>\n        char* selected &#061; strndup(self-&gt;value &#043; start, end &#8211; start);<br \/>\n        if (selected) {<br \/>\n            setClipboardText(selected);<br \/>\n            free(selected);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>void TextWidget_cutSelection(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<br \/>\n        char* selected &#061; strndup(self-&gt;value &#043; start, end &#8211; start);<br \/>\n        if (selected) {<br \/>\n            if (setClipboardText(selected)) {<br \/>\n                char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; 1);<br \/>\n                strncpy(newVal, self-&gt;value, start);<br \/>\n                newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n                strcat(newVal, self-&gt;value &#043; end);<br \/>\n                free(self-&gt;value);<br \/>\n                self-&gt;value &#061; newVal;<br \/>\n                self-&gt;cursorPos &#061; start;<br \/>\n                self-&gt;hasSel &#061; false;<br \/>\n                self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n                TextWidget_updateLines(self);<br \/>\n                if (base-&gt;callback) base-&gt;callback();<br \/>\n            }<br \/>\n            free(selected);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>void TextWidget_pasteFromClipboard(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    char* clipText &#061; getClipboardText();<br \/>\n    if (clipText) {<br \/>\n        if (self-&gt;hasSel) {<br \/>\n            int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n            int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n            int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n            if (start &gt; len) start &#061; len;<br \/>\n            if (end &gt; len) end &#061; len;<br \/>\n            char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; strlen(clipText) &#043; 1);<br \/>\n            strncpy(newVal, self-&gt;value, start);<br \/>\n            newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, clipText);<br \/>\n            strcat(newVal, self-&gt;value &#043; end);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos &#061; start &#043; strlen(clipText);<br \/>\n            self-&gt;hasSel &#061; false;<br \/>\n            self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n        } else {<br \/>\n            int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n            char* newVal &#061; (char*)malloc(len &#043; strlen(clipText) &#043; 1);<br \/>\n            strncpy(newVal, self-&gt;value, self-&gt;cursorPos);<br \/>\n            newVal[self-&gt;cursorPos] &#061; &#039;\\\\0&#039;;<br \/>\n            strcat(newVal, clipText);<br \/>\n            strcat(newVal, self-&gt;value &#043; self-&gt;cursorPos);<br \/>\n            free(self-&gt;value);<br \/>\n            self-&gt;value &#061; newVal;<br \/>\n            self-&gt;cursorPos &#043;&#061; strlen(clipText);<br \/>\n        }<br \/>\n        TextWidget_updateLines(self);<br \/>\n        if (base-&gt;callback) base-&gt;callback();<br \/>\n        free(clipText);<br \/>\n    }<br \/>\n}<\/p>\n<p>void TextWidget_deleteSelection(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<br \/>\n        char* newVal &#061; (char*)malloc(len &#8211; (end &#8211; start) &#043; 1);<br \/>\n        strncpy(newVal, self-&gt;value, start);<br \/>\n        newVal[start] &#061; &#039;\\\\0&#039;;<br \/>\n        strcat(newVal, self-&gt;value &#043; end);<br \/>\n        free(self-&gt;value);<br \/>\n        self-&gt;value &#061; newVal;<br \/>\n        self-&gt;cursorPos &#061; start;<br \/>\n        self-&gt;hasSel &#061; false;<br \/>\n        self-&gt;selectionStart &#061; self-&gt;selectionEnd &#061; -1;<br \/>\n        TextWidget_updateLines(self);<br \/>\n        if (base-&gt;callback) base-&gt;callback();<br \/>\n    }<br \/>\n}<\/p>\n<p>bool TextWidget_hasSelection(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    return self-&gt;hasSel;<br \/>\n}<\/p>\n<p>char* TextWidget_getSelectedText(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    if (self-&gt;hasSel &amp;&amp; self-&gt;selectionStart &gt;&#061; 0 &amp;&amp; self-&gt;selectionEnd &gt;&#061; 0) {<br \/>\n        int start &#061; self-&gt;selectionStart &lt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int end &#061; self-&gt;selectionStart &gt; self-&gt;selectionEnd ? self-&gt;selectionStart : self-&gt;selectionEnd;<br \/>\n        int len &#061; self-&gt;value ? strlen(self-&gt;value) : 0;<br \/>\n        if (start &gt; len) start &#061; len;<br \/>\n        if (end &gt; len) end &#061; len;<br \/>\n        return strndup(self-&gt;value &#043; start, end &#8211; start);<br \/>\n    }<br \/>\n    return strdup(&#034;&#034;);<br \/>\n}<\/p>\n<p>void TextWidget_destroy(Widget* base) {<br \/>\n    TextWidget* self &#061; (TextWidget*)base;<br \/>\n    if (self-&gt;value) free(self-&gt;value);<br \/>\n    if (self-&gt;lines) {<br \/>\n        for (int i &#061; 0; i &lt; self-&gt;lineCount; i&#043;&#043;) {<br \/>\n            free(self-&gt;lines[i]);<br \/>\n        }<br \/>\n        free(self-&gt;lines);<br \/>\n    }<br \/>\n    Widget_destroy_base(base);<br \/>\n    free(self);<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; Frame\u5b9e\u73b0 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>Widget* Frame_create(int x, int y, int w, int h) {<br \/>\n    Frame* self &#061; (Frame*)calloc(1, sizeof(Frame));<br \/>\n    Widget_init((Widget*)self, x, y, w, h, NULL);<br \/>\n    self-&gt;children &#061; NULL;<br \/>\n    self-&gt;childCount &#061; 0;<br \/>\n    self-&gt;childCapacity &#061; 0;<\/p>\n<p>    Widget* base &#061; (Widget*)self;<br \/>\n    base-&gt;draw &#061; Frame_draw;<br \/>\n    base-&gt;handleEvent &#061; Frame_handleEvent;<br \/>\n    base-&gt;onMouseMove &#061; Frame_onMouseMove;<br \/>\n    base-&gt;setFocus &#061; Frame_setFocus;<br \/>\n    base-&gt;isPointInside &#061; Widget_isPointInside;<br \/>\n    base-&gt;destroy &#061; Frame_destroy;<br \/>\n    return (Widget*)self;<br \/>\n}<\/p>\n<p>void Frame_addChild(Widget* base, Widget* child) {<br \/>\n    Frame* self &#061; (Frame*)base;<br \/>\n    if (self-&gt;childCount &gt;&#061; self-&gt;childCapacity) {<br \/>\n        self-&gt;childCapacity &#061; self-&gt;childCapacity &#061;&#061; 0 ? 4 : self-&gt;childCapacity * 2;<br \/>\n        self-&gt;children &#061; realloc(self-&gt;children, sizeof(Widget*) * self-&gt;childCapacity);<br \/>\n    }<br \/>\n    self-&gt;children[self-&gt;childCount&#043;&#043;] &#061; child;<br \/>\n}<\/p>\n<p>void Frame_draw(Widget* base) {<br \/>\n    if (!base-&gt;visible) return;<br \/>\n    Frame* self &#061; (Frame*)base;<br \/>\n    for (int i &#061; 0; i &lt; self-&gt;childCount; i&#043;&#043;) {<br \/>\n        if (self-&gt;children[i]-&gt;draw) {<br \/>\n            self-&gt;children[i]-&gt;draw(self-&gt;children[i]);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>bool Frame_handleEvent(Widget* base, const SDL_Event* event) {<br \/>\n    Frame* self &#061; (Frame*)base;<br \/>\n    for (int i &#061; 0; i &lt; self-&gt;childCount; i&#043;&#043;) {<br \/>\n        if (self-&gt;children[i]-&gt;handleEvent &amp;&amp;<br \/>\n                self-&gt;children[i]-&gt;handleEvent(self-&gt;children[i], event)) {<br \/>\n            return true;<br \/>\n        }<br \/>\n    }<br \/>\n    return false;<br \/>\n}<\/p>\n<p>void Frame_onMouseMove(Widget* base, int mx, int my) {<br \/>\n    Frame* self &#061; (Frame*)base;<br \/>\n    base-&gt;hover &#061; Widget_isPointInside(base, mx, my);<br \/>\n    for (int i &#061; 0; i &lt; self-&gt;childCount; i&#043;&#043;) {<br \/>\n        if (self-&gt;children[i]-&gt;onMouseMove) {<br \/>\n            self-&gt;children[i]-&gt;onMouseMove(self-&gt;children[i], mx, my);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>void Frame_setFocus(Widget* base, bool f) {<br \/>\n    \/\/ Frame\u672c\u8eab\u4e0d\u83b7\u53d6\u7126\u70b9<br \/>\n}<\/p>\n<p>void Frame_clearFocus(Widget* base) {<br \/>\n    Frame* self &#061; (Frame*)base;<br \/>\n    for (int i &#061; 0; i &lt; self-&gt;childCount; i&#043;&#043;) {<br \/>\n        Widget* child &#061; self-&gt;children[i];<br \/>\n        if (child-&gt;setFocus) {<br \/>\n            child-&gt;setFocus(child, false);<br \/>\n        }<br \/>\n        if (child-&gt;draw &#061;&#061; Frame_draw) {<br \/>\n            Frame_clearFocus(child);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>Widget* Frame_getFocusedWidget(Widget* base) {<br \/>\n    Frame* self &#061; (Frame*)base;<br \/>\n    for (int i &#061; 0; i &lt; self-&gt;childCount; i&#043;&#043;) {<br \/>\n        Widget* child &#061; self-&gt;children[i];<br \/>\n        if (child-&gt;focus) return child;<br \/>\n        if (child-&gt;draw &#061;&#061; Frame_draw) {<br \/>\n            Widget* w &#061; Frame_getFocusedWidget(child);<br \/>\n            if (w) return w;<br \/>\n        }<br \/>\n    }<br \/>\n    return NULL;<br \/>\n}<\/p>\n<p>Widget* Frame_getWidgetAt(Widget* base, int px, int py) {<br \/>\n    Frame* self &#061; (Frame*)base;<br \/>\n    for (int i &#061; 0; i &lt; self-&gt;childCount; i&#043;&#043;) {<br \/>\n        Widget* child &#061; self-&gt;children[i];<br \/>\n        if (Widget_isPointInside(child, px, py)) {<br \/>\n            if (child-&gt;draw &#061;&#061; Frame_draw) {<br \/>\n                Widget* w &#061; Frame_getWidgetAt(child, px, py);<br \/>\n                if (w) return w;<br \/>\n            }<br \/>\n            return child;<br \/>\n        }<br \/>\n    }<br \/>\n    return NULL;<br \/>\n}<\/p>\n<p>void Frame_destroy(Widget* base) {<br \/>\n    Frame* self &#061; (Frame*)base;<br \/>\n    for (int i &#061; 0; i &lt; self-&gt;childCount; i&#043;&#043;) {<br \/>\n        if (self-&gt;children[i]-&gt;destroy) {<br \/>\n            self-&gt;children[i]-&gt;destroy(self-&gt;children[i]);<br \/>\n        }<br \/>\n    }<br \/>\n    free(self-&gt;children);<br \/>\n    Widget_destroy_base(base);<br \/>\n    free(self);<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u53f3\u952e\u83dc\u5355 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>struct ContextMenu {<br \/>\n    bool visible;<br \/>\n    int x, y;<br \/>\n    int width;<br \/>\n    int itemHeight;<br \/>\n    int selectedIndex;<br \/>\n    char** items;<br \/>\n    VoidCallback* callbacks;<br \/>\n    int itemCount;<br \/>\n    int itemCapacity;<br \/>\n    Widget* targetWidget;<br \/>\n};<\/p>\n<p>ContextMenu* ContextMenu_create(void) {<br \/>\n    ContextMenu* menu &#061; (ContextMenu*)calloc(1, sizeof(ContextMenu));<br \/>\n    menu-&gt;visible &#061; false;<br \/>\n    menu-&gt;x &#061; 0;<br \/>\n    menu-&gt;y &#061; 0;<br \/>\n    menu-&gt;width &#061; 220;<br \/>\n    menu-&gt;itemHeight &#061; 32;<br \/>\n    menu-&gt;selectedIndex &#061; -1;<br \/>\n    menu-&gt;items &#061; NULL;<br \/>\n    menu-&gt;callbacks &#061; NULL;<br \/>\n    menu-&gt;itemCount &#061; 0;<br \/>\n    menu-&gt;itemCapacity &#061; 0;<br \/>\n    menu-&gt;targetWidget &#061; NULL;<br \/>\n    return menu;<br \/>\n}<\/p>\n<p>void ContextMenu_addItem(ContextMenu* menu, const char* text, VoidCallback callback) {<br \/>\n    if (menu-&gt;itemCount &gt;&#061; menu-&gt;itemCapacity) {<br \/>\n        menu-&gt;itemCapacity &#061; menu-&gt;itemCapacity &#061;&#061; 0 ? 4 : menu-&gt;itemCapacity * 2;<br \/>\n        menu-&gt;items &#061; realloc(menu-&gt;items, sizeof(char*) * menu-&gt;itemCapacity);<br \/>\n        menu-&gt;callbacks &#061; realloc(menu-&gt;callbacks, sizeof(VoidCallback) * menu-&gt;itemCapacity);<br \/>\n    }<br \/>\n    menu-&gt;items[menu-&gt;itemCount] &#061; strdup(text);<br \/>\n    menu-&gt;callbacks[menu-&gt;itemCount] &#061; callback;<br \/>\n    menu-&gt;itemCount&#043;&#043;;<br \/>\n}<\/p>\n<p>void ContextMenu_show(ContextMenu* menu, int mx, int my, Widget* widget) {<br \/>\n    menu-&gt;visible &#061; true;<br \/>\n    menu-&gt;x &#061; mx;<br \/>\n    menu-&gt;y &#061; my;<br \/>\n    menu-&gt;targetWidget &#061; widget;<br \/>\n    menu-&gt;selectedIndex &#061; -1;<\/p>\n<p>    int screenW, screenH;<br \/>\n    SDL_GetWindowSize(g_window, &amp;screenW, &amp;screenH);<br \/>\n    if (menu-&gt;x &#043; menu-&gt;width &gt; screenW) menu-&gt;x &#061; screenW &#8211; menu-&gt;width &#8211; 10;<br \/>\n    if (menu-&gt;y &#043; menu-&gt;itemCount * menu-&gt;itemHeight &#043; 10 &gt; screenH) {<br \/>\n        menu-&gt;y &#061; screenH &#8211; menu-&gt;itemCount * menu-&gt;itemHeight &#8211; 10;<br \/>\n    }<br \/>\n    if (menu-&gt;x &lt; 10) menu-&gt;x &#061; 10;<br \/>\n    if (menu-&gt;y &lt; 10) menu-&gt;y &#061; 10;<br \/>\n}<\/p>\n<p>void ContextMenu_hide(ContextMenu* menu) {<br \/>\n    menu-&gt;visible &#061; false;<br \/>\n    menu-&gt;targetWidget &#061; NULL;<br \/>\n    menu-&gt;selectedIndex &#061; -1;<br \/>\n}<\/p>\n<p>bool ContextMenu_isVisible(ContextMenu* menu) {<br \/>\n    return menu-&gt;visible;<br \/>\n}<\/p>\n<p>bool ContextMenu_handleEvent(ContextMenu* menu, const SDL_Event* event) {<br \/>\n    if (!menu-&gt;visible) return false;<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_MOUSEMOTION) {<br \/>\n        int mx &#061; event-&gt;motion.x, my &#061; event-&gt;motion.y;<br \/>\n        menu-&gt;selectedIndex &#061; -1;<br \/>\n        if (mx &gt;&#061; menu-&gt;x &amp;&amp; mx &lt;&#061; menu-&gt;x &#043; menu-&gt;width &amp;&amp;<br \/>\n                my &gt;&#061; menu-&gt;y &amp;&amp; my &lt;&#061; menu-&gt;y &#043; menu-&gt;itemCount * menu-&gt;itemHeight) {<br \/>\n            int index &#061; (my &#8211; menu-&gt;y) \/ menu-&gt;itemHeight;<br \/>\n            if (index &gt;&#061; 0 &amp;&amp; index &lt; menu-&gt;itemCount) {<br \/>\n                menu-&gt;selectedIndex &#061; index;<br \/>\n            }<br \/>\n        }<br \/>\n        return true;<br \/>\n    }<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_MOUSEBUTTONDOWN) {<br \/>\n        int mx &#061; event-&gt;button.x, my &#061; event-&gt;button.y;<\/p>\n<p>        if (!(mx &gt;&#061; menu-&gt;x &amp;&amp; mx &lt;&#061; menu-&gt;x &#043; menu-&gt;width &amp;&amp;<br \/>\n                my &gt;&#061; menu-&gt;y &amp;&amp; my &lt;&#061; menu-&gt;y &#043; menu-&gt;itemCount * menu-&gt;itemHeight)) {<br \/>\n            ContextMenu_hide(menu);<br \/>\n            return true;<br \/>\n        }<\/p>\n<p>        if (event-&gt;button.button &#061;&#061; SDL_BUTTON_LEFT) {<br \/>\n            int index &#061; (my &#8211; menu-&gt;y) \/ menu-&gt;itemHeight;<br \/>\n            if (index &gt;&#061; 0 &amp;&amp; index &lt; menu-&gt;itemCount) {<br \/>\n                if (menu-&gt;callbacks[index]) menu-&gt;callbacks[index]();<br \/>\n                ContextMenu_hide(menu);<br \/>\n                return true;<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    if (event-&gt;type &#061;&#061; SDL_KEYDOWN) {<br \/>\n        if (event-&gt;key.keysym.sym &#061;&#061; SDLK_ESCAPE) {<br \/>\n            ContextMenu_hide(menu);<br \/>\n            return true;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    return false;<br \/>\n}<\/p>\n<p>void ContextMenu_draw(ContextMenu* menu) {<br \/>\n    if (!menu-&gt;visible) return;<\/p>\n<p>    int totalHeight &#061; menu-&gt;itemCount * menu-&gt;itemHeight &#043; 10;<\/p>\n<p>    SDL_Rect rect &#061; {menu-&gt;x, menu-&gt;y, menu-&gt;width, totalHeight};<br \/>\n    SDL_SetRenderDrawColor(g_renderer, g_theme.menu_bg.r, g_theme.menu_bg.g,<br \/>\n                           g_theme.menu_bg.b, g_theme.menu_bg.a);<br \/>\n    SDL_RenderFillRect(g_renderer, &amp;rect);<\/p>\n<p>    SDL_SetRenderDrawColor(g_renderer, g_theme.menu_border.r, g_theme.menu_border.g,<br \/>\n                           g_theme.menu_border.b, 255);<br \/>\n    SDL_RenderDrawRect(g_renderer, &amp;rect);<\/p>\n<p>    Color normalColor &#061; {220, 220, 220, 255};<br \/>\n    Color hoverColor &#061; {255, 255, 255, 255};<br \/>\n    Color disabledColor &#061; {150, 150, 150, 255};<\/p>\n<p>    Widget* target &#061; menu-&gt;targetWidget;<br \/>\n    bool hasSel &#061; target &amp;&amp; target-&gt;hasSelection ? target-&gt;hasSelection(target) : false;<br \/>\n    char* text &#061; target &amp;&amp; target-&gt;getText ? target-&gt;getText(target) : NULL;<br \/>\n    bool hasText &#061; text &amp;&amp; strlen(text) &gt; 0;<br \/>\n    if (text) free(text);<\/p>\n<p>    for (int i &#061; 0; i &lt; menu-&gt;itemCount; i&#043;&#043;) {<br \/>\n        int itemY &#061; menu-&gt;y &#043; 5 &#043; i * menu-&gt;itemHeight;<br \/>\n        SDL_Rect itemRect &#061; {menu-&gt;x &#043; 5, itemY, menu-&gt;width &#8211; 10, menu-&gt;itemHeight &#8211; 5};<\/p>\n<p>        bool isTextOp &#061; (i &#061;&#061; 0 || i &#061;&#061; 2 || i &#061;&#061; 4);<br \/>\n        bool isSelectOp &#061; (i &#061;&#061; 3);<br \/>\n        bool isPasteOp &#061; (i &#061;&#061; 1);<br \/>\n        bool isBatchOp &#061; (i &gt;&#061; 5);<\/p>\n<p>        bool enabled &#061; true;<br \/>\n        if (isTextOp &amp;&amp; !hasSel) enabled &#061; false;<br \/>\n        if (isSelectOp) enabled &#061; true;<br \/>\n        if (isPasteOp) enabled &#061; (target !&#061; NULL);<br \/>\n        if (isBatchOp) enabled &#061; (target !&#061; NULL &amp;&amp; hasText);<\/p>\n<p>        if (menu-&gt;selectedIndex &#061;&#061; i &amp;&amp; enabled) {<br \/>\n            SDL_SetRenderDrawColor(g_renderer, g_theme.menu_hover.r, g_theme.menu_hover.g,<br \/>\n                                   g_theme.menu_hover.b, g_theme.menu_hover.a);<br \/>\n            SDL_RenderFillRect(g_renderer, &amp;itemRect);<br \/>\n        }<\/p>\n<p>        Color color &#061; enabled ? normalColor : disabledColor;<br \/>\n        renderText(g_renderer, menu-&gt;items[i], menu-&gt;x &#043; 15, itemY &#043; 5, color);<br \/>\n    }<br \/>\n}<\/p>\n<p>void ContextMenu_destroy(ContextMenu* menu) {<br \/>\n    for (int i &#061; 0; i &lt; menu-&gt;itemCount; i&#043;&#043;) {<br \/>\n        free(menu-&gt;items[i]);<br \/>\n    }<br \/>\n    free(menu-&gt;items);<br \/>\n    free(menu-&gt;callbacks);<br \/>\n    free(menu);<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u5168\u5c40\u53d8\u91cf &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>tsf* g_tsf &#061; NULL;<br \/>\nconst int SAMPLE_RATE &#061; 44100;<\/p>\n<p>int currentChannel &#061; 0;<br \/>\nint currentTempo &#061; 120;<br \/>\nchar currentKey[32] &#061; &#034;C\u5927\u8c03&#034;;<br \/>\nbool isPlaying &#061; false;<\/p>\n<p>\/\/ \u63a7\u4ef6<br \/>\nWidget* g_mainFrame &#061; NULL;<br \/>\nWidget* g_sheetText &#061; NULL;<br \/>\nWidget* g_channelEntry &#061; NULL;<br \/>\nWidget* g_tempoEntry &#061; NULL;<br \/>\nWidget* g_keyEntry &#061; NULL;<br \/>\nWidget* g_statusLabel &#061; NULL;<\/p>\n<p>\/\/ \u53f3\u952e\u83dc\u5355<br \/>\nContextMenu* g_contextMenu &#061; NULL;<\/p>\n<p>\/\/ \u957f\u6309\u68c0\u6d4b<br \/>\nUint32 g_pressStartTime &#061; 0;<br \/>\nbool g_isPressed &#061; false;<br \/>\nint g_pressX &#061; 0, g_pressY &#061; 0;<br \/>\nWidget* g_pressedWidget &#061; NULL;<br \/>\nbool g_longPressTriggered &#061; false;<\/p>\n<p>\/\/ \u9ed8\u8ba4\u7b80\u8c31<br \/>\nconst char* DEFAULT_SHEET &#061; &#034;[0,120,C\u5927\u8c03]\\\\n1 1 5 5 6 6 5-\\\\n4 4 3 3 2 2 1-&#034;;<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u97f3\u8272\u548c\u8c03\u5f0f\u6620\u5c04 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>typedef struct {<br \/>\n    int id;<br \/>\n    const char* name;<br \/>\n} Instrument;<\/p>\n<p>Instrument instrumentList[] &#061; {<br \/>\n    {0, &#034;\u5927\u94a2\u7434&#034;}, {1, &#034;\u660e\u4eae\u94a2\u7434&#034;}, {2, &#034;\u7535\u94a2\u7434&#034;}, {3, &#034;\u9152\u5427\u94a2\u7434&#034;},<br \/>\n    {4, &#034;\u7535\u94a2\u74342&#034;}, {5, &#034;\u7535\u94a2\u74343&#034;}, {6, &#034;\u54c8\u666e\u897f\u79d1\u5fb7&#034;}, {7, &#034;\u53e4\u94a2\u7434&#034;},<br \/>\n    {8, &#034;\u94a2\u7247\u7434&#034;}, {9, &#034;\u949f\u7434&#034;}, {10, &#034;\u516b\u97f3\u76d2&#034;}, {11, &#034;\u98a4\u97f3\u7434&#034;},<br \/>\n    {12, &#034;\u9a6c\u6797\u5df4&#034;}, {13, &#034;\u6728\u7434&#034;}, {14, &#034;\u7ba1\u949f&#034;}, {15, &#034;\u7ad6\u7434&#034;},<br \/>\n    {16, &#034;\u624b\u98ce\u7434&#034;}, {17, &#034;\u53e3\u7434&#034;}, {18, &#034;\u73ed\u591a\u7ebd&#034;}, {19, &#034;\u5f26\u4e50\u5408\u594f1&#034;},<br \/>\n    {20, &#034;\u5f26\u4e50\u5408\u594f2&#034;}, {21, &#034;\u5408\u6210\u5f26\u4e501&#034;}, {22, &#034;\u5408\u6210\u5f26\u4e502&#034;}, {23, &#034;\u5531\u8bd7\u73ed&#034;},<br \/>\n    {24, &#034;\u4eba\u58f0\u5408\u5531&#034;}, {25, &#034;\u5408\u6210\u4eba\u58f0&#034;}, {26, &#034;\u5c0f\u53f7&#034;}, {27, &#034;\u957f\u53f7&#034;},<br \/>\n    {28, &#034;\u5927\u53f7&#034;}, {29, &#034;\u5c0f\u53f72&#034;}, {30, &#034;\u6cd5\u56fd\u53f7&#034;}, {31, &#034;\u94dc\u7ba1\u5408\u594f&#034;},<br \/>\n    {32, &#034;\u5408\u6210\u94dc\u7ba11&#034;}, {33, &#034;\u5408\u6210\u94dc\u7ba12&#034;}, {34, &#034;\u9ad8\u97f3\u8428\u514b\u65af&#034;}, {35, &#034;\u4e2d\u97f3\u8428\u514b\u65af&#034;},<br \/>\n    {36, &#034;\u6b21\u4e2d\u97f3\u8428\u514b\u65af&#034;}, {37, &#034;\u4e0a\u4f4e\u97f3\u8428\u514b\u65af&#034;}, {38, &#034;\u53cc\u7c27\u7ba1&#034;}, {39, &#034;\u82f1\u56fd\u7ba1&#034;},<br \/>\n    {40, &#034;\u5df4\u677e\u7ba1&#034;}, {41, &#034;\u5355\u7c27\u7ba1&#034;}, {42, &#034;\u77ed\u7b1b&#034;}, {43, &#034;\u957f\u7b1b&#034;},<br \/>\n    {44, &#034;\u7ad6\u7b1b&#034;}, {45, &#034;\u6392\u7bab&#034;}, {46, &#034;\u53e3\u54e8&#034;}, {47, &#034;\u9676\u7b1b&#034;}<br \/>\n};<\/p>\n<p>int instrumentCount &#061; sizeof(instrumentList) \/ sizeof(Instrument);<\/p>\n<p>typedef struct {<br \/>\n    const char* name;<br \/>\n    int doPitch;<br \/>\n    int scale[7];<br \/>\n} KeyInfo;<\/p>\n<p>KeyInfo keyMap[] &#061; {<br \/>\n    {&#034;C\u5927\u8c03&#034;, 60, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;G\u5927\u8c03&#034;, 67, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;D\u5927\u8c03&#034;, 62, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;A\u5927\u8c03&#034;, 69, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;E\u5927\u8c03&#034;, 64, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;F\u5927\u8c03&#034;, 65, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;Bb\u5927\u8c03&#034;, 70, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;Eb\u5927\u8c03&#034;, 63, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;Ab\u5927\u8c03&#034;, 68, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;Db\u5927\u8c03&#034;, 61, {0, 2, 4, 5, 7, 9, 11}},<br \/>\n    {&#034;Gb\u5927\u8c03&#034;, 66, {0, 2, 4, 5, 7, 9, 11}}<br \/>\n};<\/p>\n<p>int keyMapCount &#061; sizeof(keyMap) \/ sizeof(KeyInfo);<\/p>\n<p>const char* getInstrumentName(int id) {<br \/>\n    for (int i &#061; 0; i &lt; instrumentCount; i&#043;&#043;) {<br \/>\n        if (instrumentList[i].id &#061;&#061; id) return instrumentList[i].name;<br \/>\n    }<br \/>\n    return &#034;\u672a\u77e5&#034;;<br \/>\n}<\/p>\n<p>KeyInfo* findKeyInfo(const char* key) {<br \/>\n    for (int i &#061; 0; i &lt; keyMapCount; i&#043;&#043;) {<br \/>\n        if (strcmp(keyMap[i].name, key) &#061;&#061; 0) return (KeyInfo*)&amp;keyMap[i];<br \/>\n    }<br \/>\n    return (KeyInfo*)&amp;keyMap[0];<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u7b80\u8c31\u89e3\u6790\u548c\u64ad\u653e &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>typedef struct {<br \/>\n    int midiNote;<br \/>\n    float duration;<br \/>\n    bool isRest;<br \/>\n} Note;<\/p>\n<p>int parseNoteChar(char c, char nextChar, int* pos, const char* content, KeyInfo* keyInfo) {<br \/>\n    if (c &lt; &#039;1&#039; || c &gt; &#039;7&#039;) return -1;<br \/>\n    int noteIndex &#061; c &#8211; &#039;1&#039;;<br \/>\n    int midiNote &#061; keyInfo-&gt;doPitch &#043; keyInfo-&gt;scale[noteIndex];<br \/>\n    if (*pos &lt; (int)strlen(content) &amp;&amp; content[*pos] &#061;&#061; &#039;#&#039;) {<br \/>\n        midiNote&#043;&#043;;<br \/>\n        (*pos)&#043;&#043;;<br \/>\n    }<br \/>\n    while (*pos &lt; (int)strlen(content) &amp;&amp; content[*pos] &#061;&#061; &#039;.&#039;) {<br \/>\n        midiNote -&#061; 12;<br \/>\n        (*pos)&#043;&#043;;<br \/>\n    }<br \/>\n    while (*pos &lt; (int)strlen(content) &amp;&amp; content[*pos] &#061;&#061; &#039;\\\\&#039;&#039;) {<br \/>\n        midiNote &#043;&#061; 12;<br \/>\n        (*pos)&#043;&#043;;<br \/>\n    }<br \/>\n    return midiNote;<br \/>\n}<\/p>\n<p>Note* parseSheet(const char* sheet, int* outInstrument, int* outTempo, char* outKey) {<br \/>\n    Note* notes &#061; NULL;<br \/>\n    int noteCount &#061; 0;<br \/>\n    int noteCapacity &#061; 0;<\/p>\n<p>    char* content &#061; strdup(sheet);<br \/>\n    char* temp &#061; malloc(strlen(content) &#043; 1);<br \/>\n    int tempIdx &#061; 0;<br \/>\n    for (int i &#061; 0; content[i]; i&#043;&#043;) {<br \/>\n        if (content[i] !&#061; &#039; &#039; &amp;&amp; content[i] !&#061; &#039;\\\\n&#039;) {<br \/>\n            temp[tempIdx&#043;&#043;] &#061; content[i];<br \/>\n        }<br \/>\n    }<br \/>\n    temp[tempIdx] &#061; &#039;\\\\0&#039;;<br \/>\n    free(content);<br \/>\n    content &#061; temp;<\/p>\n<p>    char* start &#061; strchr(content, &#039;[&#039;);<br \/>\n    char* end &#061; strchr(content, &#039;]&#039;);<br \/>\n    if (start &amp;&amp; end) {<br \/>\n        *end &#061; &#039;\\\\0&#039;;<br \/>\n        char* config &#061; start &#043; 1;<br \/>\n        char* parts[3];<br \/>\n        int partCount &#061; 0;<br \/>\n        char* token &#061; strtok(config, &#034;,&#034;);<br \/>\n        while (token &amp;&amp; partCount &lt; 3) {<br \/>\n            parts[partCount&#043;&#043;] &#061; token;<br \/>\n            token &#061; strtok(NULL, &#034;,&#034;);<br \/>\n        }<br \/>\n        if (partCount &gt;&#061; 3) {<br \/>\n            *outInstrument &#061; atoi(parts[0]);<br \/>\n            *outTempo &#061; atoi(parts[1]);<br \/>\n            strcpy(outKey, parts[2]);<br \/>\n        }<br \/>\n        *end &#061; &#039;]&#039;;<br \/>\n        char* newContent &#061; strdup(end &#043; 1);<br \/>\n        free(content);<br \/>\n        content &#061; newContent;<br \/>\n    } else {<br \/>\n        strcpy(outKey, &#034;C\u5927\u8c03&#034;);<br \/>\n    }<\/p>\n<p>    KeyInfo* keyInfo &#061; findKeyInfo(outKey);<\/p>\n<p>    char* cleanContent &#061; malloc(strlen(content) &#043; 1);<br \/>\n    int cleanIdx &#061; 0;<br \/>\n    for (int i &#061; 0; content[i]; i&#043;&#043;) {<br \/>\n        if (content[i] !&#061; &#039;|&#039;) {<br \/>\n            cleanContent[cleanIdx&#043;&#043;] &#061; content[i];<br \/>\n        }<br \/>\n    }<br \/>\n    cleanContent[cleanIdx] &#061; &#039;\\\\0&#039;;<br \/>\n    free(content);<br \/>\n    content &#061; cleanContent;<\/p>\n<p>    int i &#061; 0;<br \/>\n    int len &#061; strlen(content);<br \/>\n    while (i &lt; len) {<br \/>\n        char c &#061; content[i];<\/p>\n<p>        if (c &#061;&#061; &#039;0&#039;) {<br \/>\n            Note note &#061; {-1, 1.0f, true};<br \/>\n            i&#043;&#043;;<br \/>\n            while (i &lt; len &amp;&amp; content[i] &#061;&#061; &#039;-&#039;) {<br \/>\n                note.duration &#043;&#061; 1.0f;<br \/>\n                i&#043;&#043;;<br \/>\n            }<br \/>\n            if (noteCount &gt;&#061; noteCapacity) {<br \/>\n                noteCapacity &#061; noteCapacity &#061;&#061; 0 ? 16 : noteCapacity * 2;<br \/>\n                notes &#061; realloc(notes, sizeof(Note) * noteCapacity);<br \/>\n            }<br \/>\n            notes[noteCount&#043;&#043;] &#061; note;<br \/>\n            continue;<br \/>\n        }<\/p>\n<p>        if (c &#061;&#061; &#039;[&#039;) {<br \/>\n            i&#043;&#043;;<br \/>\n            char* group &#061; malloc(len &#043; 1);<br \/>\n            int groupIdx &#061; 0;<br \/>\n            while (i &lt; len &amp;&amp; content[i] !&#061; &#039;]&#039;) {<br \/>\n                group[groupIdx&#043;&#043;] &#061; content[i&#043;&#043;];<br \/>\n            }<br \/>\n            group[groupIdx] &#061; &#039;\\\\0&#039;;<br \/>\n            if (i &lt; len) i&#043;&#043;;<br \/>\n            int noteCountInGroup &#061; 0;<br \/>\n            for (int j &#061; 0; group[j]; j&#043;&#043;) {<br \/>\n                if (group[j] &gt;&#061; &#039;1&#039; &amp;&amp; group[j] &lt;&#061; &#039;7&#039;) noteCountInGroup&#043;&#043;;<br \/>\n            }<br \/>\n            if (noteCountInGroup &#061;&#061; 0) {<br \/>\n                free(group);<br \/>\n                continue;<br \/>\n            }<br \/>\n            float perNoteDuration &#061; 1.0f \/ noteCountInGroup;<br \/>\n            int j &#061; 0;<br \/>\n            int groupLen &#061; strlen(group);<br \/>\n            while (j &lt; groupLen) {<br \/>\n                char gc &#061; group[j];<br \/>\n                if (gc &gt;&#061; &#039;1&#039; &amp;&amp; gc &lt;&#061; &#039;7&#039;) {<br \/>\n                    int pos &#061; j &#043; 1;<br \/>\n                    int midiNote &#061; parseNoteChar(gc, (pos &lt; groupLen) ? group[pos] : &#039;\\\\0&#039;, &amp;pos, group, keyInfo);<br \/>\n                    if (midiNote &#061;&#061; -1) {<br \/>\n                        j&#043;&#043;;<br \/>\n                        continue;<br \/>\n                    }<br \/>\n                    Note note &#061; {midiNote, perNoteDuration, false};<br \/>\n                    if (noteCount &gt;&#061; noteCapacity) {<br \/>\n                        noteCapacity &#061; noteCapacity &#061;&#061; 0 ? 16 : noteCapacity * 2;<br \/>\n                        notes &#061; realloc(notes, sizeof(Note) * noteCapacity);<br \/>\n                    }<br \/>\n                    notes[noteCount&#043;&#043;] &#061; note;<br \/>\n                    j &#061; pos;<br \/>\n                } else {<br \/>\n                    j&#043;&#043;;<br \/>\n                }<br \/>\n            }<br \/>\n            free(group);<br \/>\n            continue;<br \/>\n        }<\/p>\n<p>        if (c &#061;&#061; &#039;(&#039;) {<br \/>\n            i&#043;&#043;;<br \/>\n            char* group &#061; malloc(len &#043; 1);<br \/>\n            int groupIdx &#061; 0;<br \/>\n            while (i &lt; len &amp;&amp; content[i] !&#061; &#039;)&#039;) {<br \/>\n                group[groupIdx&#043;&#043;] &#061; content[i&#043;&#043;];<br \/>\n            }<br \/>\n            group[groupIdx] &#061; &#039;\\\\0&#039;;<br \/>\n            if (i &lt; len) i&#043;&#043;;<br \/>\n            int noteCountInGroup &#061; 0;<br \/>\n            for (int j &#061; 0; group[j]; j&#043;&#043;) {<br \/>\n                if (group[j] &gt;&#061; &#039;1&#039; &amp;&amp; group[j] &lt;&#061; &#039;7&#039;) noteCountInGroup&#043;&#043;;<br \/>\n            }<br \/>\n            if (noteCountInGroup &#061;&#061; 0) {<br \/>\n                free(group);<br \/>\n                continue;<br \/>\n            }<br \/>\n            float perNoteDuration &#061; 0.5f \/ noteCountInGroup;<br \/>\n            int j &#061; 0;<br \/>\n            int groupLen &#061; strlen(group);<br \/>\n            while (j &lt; groupLen) {<br \/>\n                char gc &#061; group[j];<br \/>\n                if (gc &gt;&#061; &#039;1&#039; &amp;&amp; gc &lt;&#061; &#039;7&#039;) {<br \/>\n                    int pos &#061; j &#043; 1;<br \/>\n                    int midiNote &#061; parseNoteChar(gc, (pos &lt; groupLen) ? group[pos] : &#039;\\\\0&#039;, &amp;pos, group, keyInfo);<br \/>\n                    if (midiNote &#061;&#061; -1) {<br \/>\n                        j&#043;&#043;;<br \/>\n                        continue;<br \/>\n                    }<br \/>\n                    Note note &#061; {midiNote, perNoteDuration, false};<br \/>\n                    if (noteCount &gt;&#061; noteCapacity) {<br \/>\n                        noteCapacity &#061; noteCapacity &#061;&#061; 0 ? 16 : noteCapacity * 2;<br \/>\n                        notes &#061; realloc(notes, sizeof(Note) * noteCapacity);<br \/>\n                    }<br \/>\n                    notes[noteCount&#043;&#043;] &#061; note;<br \/>\n                    j &#061; pos;<br \/>\n                } else {<br \/>\n                    j&#043;&#043;;<br \/>\n                }<br \/>\n            }<br \/>\n            free(group);<br \/>\n            continue;<br \/>\n        }<\/p>\n<p>        if (c &gt;&#061; &#039;1&#039; &amp;&amp; c &lt;&#061; &#039;7&#039;) {<br \/>\n            Note note &#061; {-1, 1.0f, false};<br \/>\n            char noteChar &#061; c;<br \/>\n            i&#043;&#043;;<br \/>\n            int pos &#061; i;<br \/>\n            int midiNote &#061; parseNoteChar(noteChar, (i &lt; len) ? content[i] : &#039;\\\\0&#039;, &amp;pos, content, keyInfo);<br \/>\n            if (midiNote &#061;&#061; -1) continue;<br \/>\n            note.midiNote &#061; midiNote;<br \/>\n            i &#061; pos;<br \/>\n            while (i &lt; len &amp;&amp; content[i] &#061;&#061; &#039;-&#039;) {<br \/>\n                note.duration &#043;&#061; 1.0f;<br \/>\n                i&#043;&#043;;<br \/>\n            }<br \/>\n            if (noteCount &gt;&#061; noteCapacity) {<br \/>\n                noteCapacity &#061; noteCapacity &#061;&#061; 0 ? 16 : noteCapacity * 2;<br \/>\n                notes &#061; realloc(notes, sizeof(Note) * noteCapacity);<br \/>\n            }<br \/>\n            notes[noteCount&#043;&#043;] &#061; note;<br \/>\n            continue;<br \/>\n        }<br \/>\n        i&#043;&#043;;<br \/>\n    }<\/p>\n<p>    free(content);<\/p>\n<p>    Note* result &#061; malloc(sizeof(Note) * (noteCount &#043; 1));<br \/>\n    for (int i &#061; 0; i &lt; noteCount; i&#043;&#043;) {<br \/>\n        result[i] &#061; notes[i];<br \/>\n    }<br \/>\n    result[noteCount].midiNote &#061; -1;<br \/>\n    result[noteCount].duration &#061; 0;<br \/>\n    result[noteCount].isRest &#061; false;<br \/>\n    free(notes);<br \/>\n    return result;<br \/>\n}<\/p>\n<p>void changeInstrument(int channel) {<br \/>\n    if (channel &lt; 0 || channel &gt; 127) return;<br \/>\n    currentChannel &#061; channel;<br \/>\n    printf(&#034;&#x1f3b5; \u5207\u6362\u97f3\u8272: %d &#8211; %s\\\\n&#034;, currentChannel, getInstrumentName(currentChannel));<br \/>\n}<\/p>\n<p>void audioCallback(void* userdata, Uint8* stream, int len) {<br \/>\n    if (!g_tsf) return;<br \/>\n    SDL_memset(stream, 0, len);<br \/>\n    int sampleCount &#061; len \/ sizeof(short) \/ 2;<br \/>\n    tsf_render_short(g_tsf, (short*)stream, sampleCount, 1);<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u952e\u76d8\u63a7\u5236 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>void showKeyboard(void) {<br \/>\n    SDL_StartTextInput();<br \/>\n    printf(&#034;\u2328\ufe0f \u8f6f\u952e\u76d8\u5df2\u8bf7\u6c42\u663e\u793a\\\\n&#034;);<br \/>\n}<\/p>\n<p>void hideKeyboard(void) {<br \/>\n    SDL_StopTextInput();<br \/>\n    printf(&#034;\u2328\ufe0f \u8f6f\u952e\u76d8\u5df2\u8bf7\u6c42\u9690\u85cf\\\\n&#034;);<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u64ad\u653e\u63a7\u5236 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>void playSheet(void) {<br \/>\n    if (!g_sheetText || !g_sheetText-&gt;getText) {<br \/>\n        printf(&#034;\u274c \u7b80\u8c31\u4e3a\u7a7a\\\\n&#034;);<br \/>\n        if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n            g_statusLabel-&gt;setText(g_statusLabel, &#034;\u274c \u7b80\u8c31\u4e3a\u7a7a&#034;);<br \/>\n        }<br \/>\n        return;<br \/>\n    }<\/p>\n<p>    char* sheetContent &#061; g_sheetText-&gt;getText(g_sheetText);<br \/>\n    if (!sheetContent || strlen(sheetContent) &#061;&#061; 0) {<br \/>\n        printf(&#034;\u274c \u7b80\u8c31\u4e3a\u7a7a\\\\n&#034;);<br \/>\n        if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n            g_statusLabel-&gt;setText(g_statusLabel, &#034;\u274c \u7b80\u8c31\u4e3a\u7a7a&#034;);<br \/>\n        }<br \/>\n        free(sheetContent);<br \/>\n        return;<br \/>\n    }<\/p>\n<p>    int instrument &#061; 0, tempo &#061; 120;<br \/>\n    char key[32] &#061; &#034;C\u5927\u8c03&#034;;<br \/>\n    Note* notes &#061; parseSheet(sheetContent, &amp;instrument, &amp;tempo, key);<br \/>\n    free(sheetContent);<\/p>\n<p>    if (!notes || notes[0].midiNote &#061;&#061; -1) {<br \/>\n        printf(&#034;\u274c \u7b80\u8c31\u89e3\u6790\u5931\u8d25\\\\n&#034;);<br \/>\n        if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n            g_statusLabel-&gt;setText(g_statusLabel, &#034;\u274c \u89e3\u6790\u5931\u8d25&#034;);<br \/>\n        }<br \/>\n        free(notes);<br \/>\n        return;<br \/>\n    }<\/p>\n<p>    changeInstrument(instrument);<br \/>\n    currentTempo &#061; tempo;<br \/>\n    strcpy(currentKey, key);<\/p>\n<p>    char statusText[256];<br \/>\n    int noteCount &#061; 0;<br \/>\n    while (notes[noteCount].midiNote !&#061; -1) noteCount&#043;&#043;;<br \/>\n    snprintf(statusText, sizeof(statusText), &#034;&#x1f534; \u6b63\u5728\u64ad\u653e&#8230; %d \u4e2a\u97f3\u7b26&#034;, noteCount);<br \/>\n    if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n        g_statusLabel-&gt;setText(g_statusLabel, statusText);<br \/>\n    }<\/p>\n<p>    isPlaying &#061; true;<br \/>\n    float beatDuration &#061; 60.0f \/ tempo;<\/p>\n<p>    for (int idx &#061; 0; notes[idx].midiNote !&#061; -1 &amp;&amp; isPlaying; idx&#043;&#043;) {<br \/>\n        Note note &#061; notes[idx];<br \/>\n        float duration &#061; note.duration * beatDuration;<br \/>\n        if (!note.isRest) {<br \/>\n            tsf_note_on(g_tsf, currentChannel, note.midiNote, 0.8f);<br \/>\n        }<br \/>\n        int waitMs &#061; (int)(duration * 1000);<br \/>\n        int elapsed &#061; 0;<br \/>\n        while (elapsed &lt; waitMs &amp;&amp; isPlaying) {<br \/>\n            SDL_Delay(10);<br \/>\n            elapsed &#043;&#061; 10;<br \/>\n            SDL_Event event;<br \/>\n            while (SDL_PollEvent(&amp;event)) {<br \/>\n                if (event.type &#061;&#061; SDL_QUIT ||<br \/>\n                        (event.type &#061;&#061; SDL_KEYDOWN &amp;&amp; event.key.keysym.sym &#061;&#061; SDLK_ESCAPE)) {<br \/>\n                    isPlaying &#061; false;<br \/>\n                    goto cleanup;<br \/>\n                }<br \/>\n            }<br \/>\n        }<br \/>\n        if (!note.isRest) {<br \/>\n            tsf_note_off(g_tsf, currentChannel, note.midiNote);<br \/>\n        }<br \/>\n    }<\/p>\n<p>cleanup:<br \/>\n    isPlaying &#061; false;<br \/>\n    if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n        g_statusLabel-&gt;setText(g_statusLabel, &#034;\u26aa \u5c31\u7eea&#034;);<br \/>\n    }<br \/>\n    printf(&#034;\u23f9 \u64ad\u653e\u7ed3\u675f\\\\n&#034;);<br \/>\n    free(notes);<br \/>\n}<\/p>\n<p>void stopPlay(void) {<br \/>\n    isPlaying &#061; false;<br \/>\n    if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n        g_statusLabel-&gt;setText(g_statusLabel, &#034;\u23f9 \u5df2\u505c\u6b62&#034;);<br \/>\n    }<br \/>\n    printf(&#034;\u23f9 \u505c\u6b62\u64ad\u653e\\\\n&#034;);<br \/>\n}<\/p>\n<p>void loadDefault(void) {<br \/>\n    if (g_sheetText &amp;&amp; g_sheetText-&gt;setText) {<br \/>\n        g_sheetText-&gt;setText(g_sheetText, DEFAULT_SHEET);<br \/>\n    }<br \/>\n    if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n        g_statusLabel-&gt;setText(g_statusLabel, &#034;&#x1f4cb; \u5df2\u52a0\u8f7d\u9ed8\u8ba4\u7b80\u8c31&#034;);<br \/>\n    }<br \/>\n    printf(&#034;&#x1f4cb; \u52a0\u8f7d\u9ed8\u8ba4\u7b80\u8c31\\\\n&#034;);<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u56de\u8c03\u51fd\u6570 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>void onChannelChanged(void) {<br \/>\n    if (g_channelEntry &amp;&amp; g_channelEntry-&gt;getText) {<br \/>\n        char* val &#061; g_channelEntry-&gt;getText(g_channelEntry);<br \/>\n        if (val) {<br \/>\n            int ch &#061; atoi(val);<br \/>\n            if (ch &gt;&#061; 0 &amp;&amp; ch &lt;&#061; 127) changeInstrument(ch);<br \/>\n            free(val);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>void onHideKeyboard(void) {<br \/>\n    hideKeyboard();<br \/>\n    if (g_mainFrame) {<br \/>\n        Frame_clearFocus(g_mainFrame);<br \/>\n    }<br \/>\n    if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n        g_statusLabel-&gt;setText(g_statusLabel, &#034;\u2328\ufe0f \u952e\u76d8\u5df2\u9690\u85cf&#034;);<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u83dc\u5355\u56de\u8c03\u51fd\u6570 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>void menuCopy(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;copySelection) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;copySelection(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>void menuPaste(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;pasteFromClipboard) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;pasteFromClipboard(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>void menuCut(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;cutSelection) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;cutSelection(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>void menuSelectAll(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;selectAll) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;selectAll(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>void menuDelete(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;deleteSelection) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;deleteSelection(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>void menuSelectAllCopy(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;selectAll &amp;&amp;<br \/>\n            g_contextMenu-&gt;targetWidget-&gt;copySelection) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;selectAll(g_contextMenu-&gt;targetWidget);<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;copySelection(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>void menuSelectAllPaste(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;selectAll &amp;&amp;<br \/>\n            g_contextMenu-&gt;targetWidget-&gt;pasteFromClipboard) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;selectAll(g_contextMenu-&gt;targetWidget);<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;pasteFromClipboard(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>void menuSelectAllDelete(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;selectAll &amp;&amp;<br \/>\n            g_contextMenu-&gt;targetWidget-&gt;deleteSelection) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;selectAll(g_contextMenu-&gt;targetWidget);<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;deleteSelection(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>void menuSelectAllCut(void) {<br \/>\n    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;targetWidget &amp;&amp; g_contextMenu-&gt;targetWidget-&gt;selectAll &amp;&amp;<br \/>\n            g_contextMenu-&gt;targetWidget-&gt;cutSelection) {<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;selectAll(g_contextMenu-&gt;targetWidget);<br \/>\n        g_contextMenu-&gt;targetWidget-&gt;cutSelection(g_contextMenu-&gt;targetWidget);<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; \u4e3b\u51fd\u6570 &#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<\/p>\n<p>int main(int argc, char* argv[]) {<br \/>\n    SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO);<br \/>\n    TTF_Init();<\/p>\n<p>    SDL_DisplayMode DM;<br \/>\n    SDL_GetCurrentDisplayMode(0, &amp;DM);<br \/>\n    int windowWidth &#061; 800;<br \/>\n    int windowHeight &#061; 650;<\/p>\n<p>    g_window &#061; SDL_CreateWindow(<br \/>\n                   &#034;&#x1f3b5; \u7b80\u8c31\u7f16\u8f91\u5668 &#8211; C\u8bed\u8a00\u7248&#034;,<br \/>\n                   SDL_WINDOWPOS_CENTERED,<br \/>\n                   SDL_WINDOWPOS_CENTERED,<br \/>\n                   windowWidth, windowHeight,<br \/>\n                   SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE<br \/>\n               );<\/p>\n<p>    if (!g_window) {<br \/>\n        printf(&#034;\u7a97\u53e3\u521b\u5efa\u5931\u8d25&#xff1a;%s\\\\n&#034;, SDL_GetError());<br \/>\n        return -1;<br \/>\n    }<\/p>\n<p>    g_renderer &#061; SDL_CreateRenderer(<br \/>\n                     g_window, -1,<br \/>\n                     SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC<br \/>\n                 );<\/p>\n<p>    \/\/ \u52a0\u8f7d\u5b57\u4f53<br \/>\n    g_font &#061; TTF_OpenFont(&#034;SimHei.ttf&#034;, 18);<br \/>\n    if (!g_font) {<br \/>\n        g_font &#061; TTF_OpenFont(&#034;\/system\/fonts\/NotoSansCJK-Regular.ttc&#034;, 18);<br \/>\n        if (!g_font) {<br \/>\n            g_font &#061; TTF_OpenFont(&#034;\/system\/fonts\/DroidSansFallback.ttf&#034;, 18);<br \/>\n        }<br \/>\n        if (!g_font) {<br \/>\n            printf(&#034;\u26a0\ufe0f \u5b57\u4f53\u52a0\u8f7d\u5931\u8d25\\\\n&#034;);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/\/ \u52a0\u8f7dSF2<br \/>\n    g_tsf &#061; tsf_load_filename(&#034;sndfnt.sf2&#034;);<br \/>\n    if (!g_tsf) {<br \/>\n        printf(&#034;\u274c SF2\u97f3\u8272\u6587\u4ef6\u52a0\u8f7d\u5931\u8d25&#xff01;\\\\n&#034;);<br \/>\n        SDL_Delay(3000);<br \/>\n        return -1;<br \/>\n    }<br \/>\n    tsf_set_output(g_tsf, TSF_STEREO_INTERLEAVED, SAMPLE_RATE, 0);<\/p>\n<p>    \/\/ \u521d\u59cb\u5316\u4e3b\u9898<br \/>\n    g_theme.bg &#061; (Color) {<br \/>\n        240, 240, 235, 255<br \/>\n    };<br \/>\n    g_theme.fg &#061; (Color) {<br \/>\n        50, 50, 50, 255<br \/>\n    };<br \/>\n    g_theme.widget_bg &#061; (Color) {<br \/>\n        255, 255, 255, 255<br \/>\n    };<br \/>\n    g_theme.widget_border &#061; (Color) {<br \/>\n        180, 180, 175, 255<br \/>\n    };<br \/>\n    g_theme.button_bg &#061; (Color) {<br \/>\n        220, 220, 215, 255<br \/>\n    };<br \/>\n    g_theme.button_hover &#061; (Color) {<br \/>\n        200, 200, 195, 255<br \/>\n    };<br \/>\n    g_theme.button_active &#061; (Color) {<br \/>\n        180, 180, 175, 255<br \/>\n    };<br \/>\n    g_theme.accent &#061; (Color) {<br \/>\n        70, 130, 180, 255<br \/>\n    };<br \/>\n    g_theme.accent_hover &#061; (Color) {<br \/>\n        90, 150, 200, 255<br \/>\n    };<br \/>\n    g_theme.accent_active &#061; (Color) {<br \/>\n        50, 110, 160, 255<br \/>\n    };<br \/>\n    g_theme.selection &#061; (Color) {<br \/>\n        180, 210, 240, 200<br \/>\n    };<br \/>\n    g_theme.disabled &#061; (Color) {<br \/>\n        200, 200, 200, 255<br \/>\n    };<br \/>\n    g_theme.menu_bg &#061; (Color) {<br \/>\n        50, 50, 55, 240<br \/>\n    };<br \/>\n    g_theme.menu_hover &#061; (Color) {<br \/>\n        70, 70, 80, 240<br \/>\n    };<br \/>\n    g_theme.menu_border &#061; (Color) {<br \/>\n        100, 100, 110, 255<br \/>\n    };<\/p>\n<p>    \/\/ \u521b\u5efa\u4e3b\u5bb9\u5668<br \/>\n    g_mainFrame &#061; Frame_create(0, 0, windowWidth, windowHeight);<\/p>\n<p>    \/\/ \u6807\u9898<br \/>\n    Widget* titleLabel &#061; Label_create(10, 10, 780, 30, &#034;&#x1f3b5; \u7b80\u8c31\u7f16\u8f91\u5668&#034;);<br \/>\n    Frame_addChild(g_mainFrame, titleLabel);<\/p>\n<p>    \/\/ \u63a7\u5236\u680f<br \/>\n    int controlY &#061; 45;<br \/>\n    int labelW &#061; 40, entryW &#061; 80, spacing &#061; 10;<\/p>\n<p>    Widget* channelLabel &#061; Label_create(10, controlY, labelW, 30, &#034;\u97f3\u8272:&#034;);<br \/>\n    Frame_addChild(g_mainFrame, channelLabel);<\/p>\n<p>    g_channelEntry &#061; Entry_create(55, controlY, entryW, 30, &#034;0&#034;);<br \/>\n    g_channelEntry-&gt;callback &#061; onChannelChanged;<br \/>\n    g_channelEntry-&gt;focusCallback &#061; showKeyboard;<br \/>\n    Frame_addChild(g_mainFrame, g_channelEntry);<\/p>\n<p>    Widget* tempoLabel &#061; Label_create(10 &#043; labelW &#043; entryW &#043; 20, controlY, labelW, 30, &#034;\u901f\u5ea6:&#034;);<br \/>\n    Frame_addChild(g_mainFrame, tempoLabel);<\/p>\n<p>    g_tempoEntry &#061; Entry_create(10 &#043; labelW &#043; entryW &#043; 20 &#043; labelW &#043; 5, controlY, entryW, 30, &#034;120&#034;);<br \/>\n    g_tempoEntry-&gt;focusCallback &#061; showKeyboard;<br \/>\n    Frame_addChild(g_mainFrame, g_tempoEntry);<\/p>\n<p>    Widget* keyLabel &#061; Label_create(10 &#043; (labelW &#043; entryW &#043; 20) * 2, controlY, labelW, 30, &#034;\u8c03\u5f0f:&#034;);<br \/>\n    Frame_addChild(g_mainFrame, keyLabel);<\/p>\n<p>    g_keyEntry &#061; Entry_create(10 &#043; (labelW &#043; entryW &#043; 20) * 2 &#043; labelW &#043; 5, controlY, 100, 30, &#034;C\u5927\u8c03&#034;);<br \/>\n    g_keyEntry-&gt;focusCallback &#061; showKeyboard;<br \/>\n    Frame_addChild(g_mainFrame, g_keyEntry);<\/p>\n<p>    \/\/ \u7b80\u8c31\u7f16\u8f91\u533a<br \/>\n    int textY &#061; controlY &#043; 40;<br \/>\n    int textH &#061; 300;<br \/>\n    g_sheetText &#061; TextWidget_create(10, textY, windowWidth &#8211; 20, textH, DEFAULT_SHEET);<br \/>\n    g_sheetText-&gt;focusCallback &#061; showKeyboard;<br \/>\n    Frame_addChild(g_mainFrame, g_sheetText);<\/p>\n<p>    \/\/ \u6309\u94ae\u680f<br \/>\n    int btnY &#061; textY &#043; textH &#043; 10;<br \/>\n    int btnH &#061; 40;<br \/>\n    int btnW &#061; 100;<br \/>\n    int btnSpacing &#061; 10;<br \/>\n    int btnX &#061; 10;<\/p>\n<p>    Widget* playBtn &#061; Button_create(btnX, btnY, btnW, btnH, &#034;\u25b6 \u64ad\u653e&#034;, playSheet);<br \/>\n    Frame_addChild(g_mainFrame, playBtn);<\/p>\n<p>    btnX &#043;&#061; btnW &#043; btnSpacing;<br \/>\n    Widget* stopBtn &#061; Button_create(btnX, btnY, btnW, btnH, &#034;\u23f9 \u505c\u6b62&#034;, stopPlay);<br \/>\n    Frame_addChild(g_mainFrame, stopBtn);<\/p>\n<p>    btnX &#043;&#061; btnW &#043; btnSpacing;<br \/>\n    Widget* defaultBtn &#061; Button_create(btnX, btnY, btnW, btnH, &#034;&#x1f4cb; \u9ed8\u8ba4&#034;, loadDefault);<br \/>\n    Frame_addChild(g_mainFrame, defaultBtn);<\/p>\n<p>    btnX &#043;&#061; btnW &#043; btnSpacing;<br \/>\n    Widget* hideKeyboardBtn &#061; Button_create(btnX, btnY, btnW, btnH, &#034;\u2328\ufe0f \u9690\u85cf&#034;, onHideKeyboard);<br \/>\n    Frame_addChild(g_mainFrame, hideKeyboardBtn);<\/p>\n<p>    \/\/ \u72b6\u6001\u680f<br \/>\n    int statusY &#061; btnY &#043; btnH &#043; 10;<br \/>\n    g_statusLabel &#061; Label_create(10, statusY, 780, 30, &#034;\u26aa \u5c31\u7eea&#034;);<br \/>\n    Frame_addChild(g_mainFrame, g_statusLabel);<\/p>\n<p>    \/\/ \u63d0\u793a\u4fe1\u606f<br \/>\n    int tipY &#061; statusY &#043; 30;<br \/>\n    Widget* tipLabel &#061; Label_create(10, tipY, 780, 25,<br \/>\n                                    &#034;&#x1f4a1; \u957f\u6309\u5f39\u51fa\u83dc\u5355 | \u70b9\u51fb\u8f93\u5165\u6846\u5f39\u51fa\u952e\u76d8 | \u8bed\u6cd5: 1-7 #(\u5347) .(\u4f4e\u516b\u5ea6) &#039;(\u9ad8\u516b\u5ea6) -(\u5ef6\u957f) 0(\u4f11\u6b62)&#034;);<br \/>\n    Frame_addChild(g_mainFrame, tipLabel);<\/p>\n<p>    \/\/ \u521d\u59cb\u5316\u97f3\u9891<br \/>\n    SDL_AudioSpec spec;<br \/>\n    spec.freq &#061; SAMPLE_RATE;<br \/>\n    spec.format &#061; AUDIO_S16SYS;<br \/>\n    spec.channels &#061; 2;<br \/>\n    spec.samples &#061; 1024;<br \/>\n    spec.callback &#061; audioCallback;<br \/>\n    spec.userdata &#061; NULL;<\/p>\n<p>    if (SDL_OpenAudio(&amp;spec, NULL) &lt; 0) {<br \/>\n        printf(&#034;\u274c \u97f3\u9891\u6253\u5f00\u5931\u8d25&#xff1a;%s\\\\n&#034;, SDL_GetError());<br \/>\n        tsf_close(g_tsf);<br \/>\n        return -1;<br \/>\n    }<br \/>\n    SDL_PauseAudio(0);<\/p>\n<p>    \/\/ \u521d\u59cb\u5316\u97f3\u8272<br \/>\n    changeInstrument(0);<\/p>\n<p>    \/\/ \u521b\u5efa\u53f3\u952e\u83dc\u5355<br \/>\n    g_contextMenu &#061; ContextMenu_create();<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;&#x1f4cb; \u590d\u5236&#034;, menuCopy);<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;&#x1f4ce; \u7c98\u8d34&#034;, menuPaste);<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;\u2702\ufe0f \u526a\u5207&#034;, menuCut);<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;&#x1f50d; \u5168\u9009&#034;, menuSelectAll);<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;&#x1f5d1; \u5220\u9664\u9009\u62e9&#034;, menuDelete);<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;&#x1f4cb; \u5168\u9009\u5e76\u590d\u5236&#034;, menuSelectAllCopy);<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;&#x1f4ce; \u5168\u9009\u5e76\u7c98\u8d34&#034;, menuSelectAllPaste);<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;&#x1f5d1; \u5168\u9009\u5e76\u5220\u9664&#034;, menuSelectAllDelete);<br \/>\n    ContextMenu_addItem(g_contextMenu, &#034;&#x1f4cb; \u5168\u9009\u5e76\u526a\u5207&#034;, menuSelectAllCut);<\/p>\n<p>    printf(&#034;&#x1f3b5; \u7b80\u8c31\u7f16\u8f91\u5668 v3.0 (C\u8bed\u8a00\u7248) \u5df2\u542f\u52a8&#xff01;\\\\n&#034;);<br \/>\n    printf(&#034;&#x1f4a1; \u957f\u6309\u7f16\u8f91\u533a\u5f39\u51fa\u83dc\u5355\\\\n&#034;);<\/p>\n<p>    hideKeyboard();<\/p>\n<p>    \/\/ \u4e3b\u5faa\u73af<br \/>\n    bool running &#061; true;<br \/>\n    SDL_Event event;<\/p>\n<p>    while (running) {<br \/>\n        while (SDL_PollEvent(&amp;event)) {<br \/>\n            if (event.type &#061;&#061; SDL_QUIT) {<br \/>\n                running &#061; false;<br \/>\n            }<\/p>\n<p>            if (event.type &#061;&#061; SDL_WINDOWEVENT &amp;&amp;<br \/>\n                    event.window.event &#061;&#061; SDL_WINDOWEVENT_RESIZED) {<br \/>\n                windowWidth &#061; event.window.data1;<br \/>\n                windowHeight &#061; event.window.data2;<br \/>\n            }<\/p>\n<p>            if (event.type &#061;&#061; SDL_MOUSEMOTION) {<br \/>\n                if (g_mainFrame &amp;&amp; g_mainFrame-&gt;onMouseMove) {<br \/>\n                    g_mainFrame-&gt;onMouseMove(g_mainFrame, event.motion.x, event.motion.y);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            \/\/ &#061;&#061;&#061;&#061;&#061;&#061; \u957f\u6309\u68c0\u6d4b &#061;&#061;&#061;&#061;&#061;&#061;<br \/>\n            if (event.type &#061;&#061; SDL_MOUSEBUTTONDOWN &amp;&amp; event.button.button &#061;&#061; SDL_BUTTON_LEFT) {<br \/>\n                g_isPressed &#061; true;<br \/>\n                g_pressStartTime &#061; SDL_GetTicks();<br \/>\n                g_pressX &#061; event.button.x;<br \/>\n                g_pressY &#061; event.button.y;<br \/>\n                g_longPressTriggered &#061; false;<br \/>\n                if (g_mainFrame &amp;&amp; g_mainFrame-&gt;isPointInside) {<br \/>\n                    g_pressedWidget &#061; Frame_getWidgetAt(g_mainFrame, g_pressX, g_pressY);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            if (event.type &#061;&#061; SDL_MOUSEBUTTONUP &amp;&amp; event.button.button &#061;&#061; SDL_BUTTON_LEFT) {<br \/>\n                g_isPressed &#061; false;<br \/>\n                g_pressedWidget &#061; NULL;<br \/>\n                g_longPressTriggered &#061; false;<br \/>\n            }<\/p>\n<p>            \/\/ \u68c0\u6d4b\u957f\u6309<br \/>\n            if (g_isPressed &amp;&amp; !g_longPressTriggered) {<br \/>\n                Uint32 pressDuration &#061; SDL_GetTicks() &#8211; g_pressStartTime;<br \/>\n                if (pressDuration &gt; 50) {<br \/>\n                    g_longPressTriggered &#061; true;<\/p>\n<p>                    if (g_pressedWidget) {<br \/>\n                        if (g_pressedWidget-&gt;draw &#061;&#061; Entry_draw ||<br \/>\n                                g_pressedWidget-&gt;draw &#061;&#061; TextWidget_draw) {<br \/>\n                            ContextMenu_show(g_contextMenu, g_pressX, g_pressY, g_pressedWidget);<br \/>\n                            printf(&#034;&#x1f4cb; \u957f\u6309\u5f39\u51fa\u83dc\u5355\\\\n&#034;);<br \/>\n                            if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n                                g_statusLabel-&gt;setText(g_statusLabel, &#034;&#x1f4cb; \u83dc\u5355\u5df2\u5f39\u51fa&#034;);<br \/>\n                            }<br \/>\n                        }<br \/>\n                    }<br \/>\n                }<br \/>\n            }<\/p>\n<p>            \/\/ \u5904\u7406\u53f3\u952e\u83dc\u5355\u4e8b\u4ef6<br \/>\n            if (g_contextMenu &amp;&amp; ContextMenu_handleEvent(g_contextMenu, &amp;event)) {<br \/>\n                continue;<br \/>\n            }<\/p>\n<p>            \/\/ \u5904\u7406\u63a7\u4ef6\u4e8b\u4ef6<br \/>\n            if (g_mainFrame &amp;&amp; g_mainFrame-&gt;handleEvent) {<br \/>\n                g_mainFrame-&gt;handleEvent(g_mainFrame, &amp;event);<br \/>\n            }<\/p>\n<p>            \/\/ \u5168\u5c40\u5feb\u6377\u952e<br \/>\n            if (event.type &#061;&#061; SDL_KEYDOWN) {<br \/>\n                if (event.key.keysym.sym &#061;&#061; SDLK_ESCAPE) {<br \/>\n                    if (g_contextMenu &amp;&amp; g_contextMenu-&gt;visible) {<br \/>\n                        ContextMenu_hide(g_contextMenu);<br \/>\n                    } else {<br \/>\n                        running &#061; false;<br \/>\n                    }<br \/>\n                }<br \/>\n                if ((event.key.keysym.mod &amp; KMOD_CTRL) &amp;&amp; event.key.keysym.sym &#061;&#061; SDLK_r) {<br \/>\n                    playSheet();<br \/>\n                }<br \/>\n                if (event.key.keysym.sym &#061;&#061; SDLK_F1) {<br \/>\n                    hideKeyboard();<br \/>\n                    if (g_mainFrame) Frame_clearFocus(g_mainFrame);<br \/>\n                    if (g_statusLabel &amp;&amp; g_statusLabel-&gt;setText) {<br \/>\n                        g_statusLabel-&gt;setText(g_statusLabel, &#034;\u2328\ufe0f \u952e\u76d8\u5df2\u9690\u85cf (F1)&#034;);<br \/>\n                    }<br \/>\n                }<br \/>\n                if ((event.key.keysym.mod &amp; KMOD_CTRL) &amp;&amp; event.key.keysym.sym &#061;&#061; SDLK_a) {<br \/>\n                    Widget* focused &#061; g_mainFrame ? Frame_getFocusedWidget(g_mainFrame) : NULL;<br \/>\n                    if (focused &amp;&amp; focused-&gt;selectAll) focused-&gt;selectAll(focused);<br \/>\n                }<br \/>\n                if ((event.key.keysym.mod &amp; KMOD_CTRL) &amp;&amp; event.key.keysym.sym &#061;&#061; SDLK_c) {<br \/>\n                    Widget* focused &#061; g_mainFrame ? Frame_getFocusedWidget(g_mainFrame) : NULL;<br \/>\n                    if (focused &amp;&amp; focused-&gt;copySelection) focused-&gt;copySelection(focused);<br \/>\n                }<br \/>\n                if ((event.key.keysym.mod &amp; KMOD_CTRL) &amp;&amp; event.key.keysym.sym &#061;&#061; SDLK_v) {<br \/>\n                    Widget* focused &#061; g_mainFrame ? Frame_getFocusedWidget(g_mainFrame) : NULL;<br \/>\n                    if (focused &amp;&amp; focused-&gt;pasteFromClipboard) focused-&gt;pasteFromClipboard(focused);<br \/>\n                }<br \/>\n                if ((event.key.keysym.mod &amp; KMOD_CTRL) &amp;&amp; event.key.keysym.sym &#061;&#061; SDLK_x) {<br \/>\n                    Widget* focused &#061; g_mainFrame ? Frame_getFocusedWidget(g_mainFrame) : NULL;<br \/>\n                    if (focused &amp;&amp; focused-&gt;cutSelection) focused-&gt;cutSelection(focused);<br \/>\n                }<br \/>\n            }<br \/>\n        }<\/p>\n<p>        SDL_SetRenderDrawColor(g_renderer, g_theme.bg.r, g_theme.bg.g, g_theme.bg.b, 255);<br \/>\n        SDL_RenderClear(g_renderer);<\/p>\n<p>        if (g_mainFrame &amp;&amp; g_mainFrame-&gt;draw) {<br \/>\n            g_mainFrame-&gt;draw(g_mainFrame);<br \/>\n        }<\/p>\n<p>        \/\/ \u7ed8\u5236\u53f3\u952e\u83dc\u5355<br \/>\n        if (g_contextMenu) ContextMenu_draw(g_contextMenu);<\/p>\n<p>        SDL_RenderPresent(g_renderer);<br \/>\n        SDL_Delay(16);<br \/>\n    }<\/p>\n<p>    \/\/ \u6e05\u7406<br \/>\n    isPlaying &#061; false;<br \/>\n    hideKeyboard();<br \/>\n    SDL_StopTextInput();<br \/>\n    if (g_font) TTF_CloseFont(g_font);<br \/>\n    SDL_CloseAudio();<br \/>\n    SDL_DestroyRenderer(g_renderer);<br \/>\n    SDL_DestroyWindow(g_window);<br \/>\n    TTF_Quit();<br \/>\n    SDL_Quit();<br \/>\n    if (g_tsf) tsf_close(g_tsf);<br \/>\n    if (g_contextMenu) ContextMenu_destroy(g_contextMenu);<br \/>\n    if (g_mainFrame &amp;&amp; g_mainFrame-&gt;destroy) g_mainFrame-&gt;destroy(g_mainFrame);<\/p>\n<p>    printf(&#034;&#x1f44b; \u7a0b\u5e8f\u9000\u51fa\\\\n&#034;);<br \/>\n    return 0;<br \/>\n}<\/p>\n","protected":false},"excerpt":{"rendered":"<p>#define TSF_IMPLEMENTATION<br \/>\n#include \\&#8221;tsf.h\\&#8221;#include<br \/>\n#include<br \/>\n#include<br \/>\n#include<br \/>\n#include<br \/>\n#include <math><br \/>\n#include<br \/>\n#include \/\/  \u7c7b\u578b\u5b9a\u4e49 typedef struct Color {Ui<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[174,190,427],"topic":[],"class_list":["post-85349","post","type-post","status-publish","format-standard","hentry","category-server","tag-c","tag-190","tag-427"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.wsisp.com\/helps\/85349.html\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"og:description\" content=\"#define TSF_IMPLEMENTATION #include &quot;tsf.h&quot;#include #include #include #include #include #include #include #include \/\/ \u7c7b\u578b\u5b9a\u4e49 typedef struct Color {Ui\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.wsisp.com\/helps\/85349.html\" \/>\n<meta property=\"og:site_name\" content=\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-27T05:00:54+00:00\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"51 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/85349.html\",\"url\":\"https:\/\/www.wsisp.com\/helps\/85349.html\",\"name\":\"\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"isPartOf\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\"},\"datePublished\":\"2026-07-27T05:00:54+00:00\",\"dateModified\":\"2026-07-27T05:00:54+00:00\",\"author\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/85349.html#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.wsisp.com\/helps\/85349.html\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/85349.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"https:\/\/www.wsisp.com\/helps\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\",\"url\":\"https:\/\/www.wsisp.com\/helps\/\",\"name\":\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"description\":\"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"contentUrl\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"caption\":\"admin\"},\"sameAs\":[\"http:\/\/wp.wsisp.com\"],\"url\":\"https:\/\/www.wsisp.com\/helps\/author\/admin\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.wsisp.com\/helps\/85349.html","og_locale":"zh_CN","og_type":"article","og_title":"\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","og_description":"#define TSF_IMPLEMENTATION #include \"tsf.h\"#include #include #include #include #include #include #include #include \/\/ \u7c7b\u578b\u5b9a\u4e49 typedef struct Color {Ui","og_url":"https:\/\/www.wsisp.com\/helps\/85349.html","og_site_name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","article_published_time":"2026-07-27T05:00:54+00:00","author":"admin","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"admin","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"51 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.wsisp.com\/helps\/85349.html","url":"https:\/\/www.wsisp.com\/helps\/85349.html","name":"\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","isPartOf":{"@id":"https:\/\/www.wsisp.com\/helps\/#website"},"datePublished":"2026-07-27T05:00:54+00:00","dateModified":"2026-07-27T05:00:54+00:00","author":{"@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41"},"breadcrumb":{"@id":"https:\/\/www.wsisp.com\/helps\/85349.html#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.wsisp.com\/helps\/85349.html"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.wsisp.com\/helps\/85349.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"https:\/\/www.wsisp.com\/helps"},{"@type":"ListItem","position":2,"name":"\u624b\u673a\u7aefC\u8bed\u8a00\u7b80\u8c31\u7f16\u8f91\u5668SDL\u8f6f\u4ef6\u4ee3\u7801QZQ"}]},{"@type":"WebSite","@id":"https:\/\/www.wsisp.com\/helps\/#website","url":"https:\/\/www.wsisp.com\/helps\/","name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","description":"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"zh-Hans"},{"@type":"Person","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41","name":"admin","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/","url":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","contentUrl":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","caption":"admin"},"sameAs":["http:\/\/wp.wsisp.com"],"url":"https:\/\/www.wsisp.com\/helps\/author\/admin"}]}},"_links":{"self":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/85349","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/comments?post=85349"}],"version-history":[{"count":0,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/85349\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media?parent=85349"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/categories?post=85349"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/tags?post=85349"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/topic?post=85349"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}