Context2D* umami_min_max_close =
    @image_file_to_context2d("M:/Media/Themes/Umami/Window/min_max_close.png");
Context2D* umami_min_max_close_pressed = Image.FileToContext2D(
    "M:/Media/Themes/Umami/Window/min_max_close_pressed.png");
Context2D* umami_title_bar_button = NewContext2D(16, 14);
Context2D* umami_default_icon =
    @image_file_to_context2d("M:/Media/Themes/Umami/Icon/default.png");
Context2D* umami_corner_resize =
    @image_file_to_context2d("M:/Media/Themes/Umami/Window/corner_resize.png");

Context2D* umami_widget_checkbox =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/checkbox.png");
Context2D* umami_widget_checkbox_active =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/checkbox_active.png");
Context2D* umami_widget_checkbox_checked =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/checkbox_checked.png");
Context2D* umami_widget_checkbox_checked_active = @image_file_to_context2d(
    "M:/Media/Themes/Umami/Widget/checkbox_checked_active.png");

Context2D* umami_widget_radio =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/radio.png");
Context2D* umami_widget_radio_active =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/radio_active.png");
Context2D* umami_widget_radio_selected =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/radio_selected.png");

Context2D* umami_widget_horz_slider =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/horz_slider.png");
Context2D* umami_widget_vert_slider =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/vert_slider.png");

Context2D* umami_widget_sb_up_button =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/sb_up_button.png");
Context2D* umami_widget_sb_down_button =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/sb_down_button.png");
Context2D* umami_widget_sb_left_button =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/sb_left_button.png");
Context2D* umami_widget_sb_right_button =
    @image_file_to_context2d("M:/Media/Themes/Umami/Widget/sb_right_button.png");

U0 @umami_button_repaint(Window* win, ButtonWidget* widget, I64 x, I64 y)
{
    Context2D* ctx = win->render_ctx;
    Context2D tmpctx;
    I64 text_offset;
    I64 text_width;
    if (!widget->color)
        widget->color = Color(0, 0, 0);
    if (!widget->font)
        widget->font = Compositor.theme.font.sans;
    if (widget->image) {
        widget->width = Max(widget->image->width + 8, widget->width);
        widget->height = Max(widget->image->height + 8, widget->height);
    }
    I64 color = widget->color;
    color.u8[3] = win->opacity;
    ctx->fill_rect(x + 1, y + 1, widget->width - 2, widget->height - 2,
        Color(232, 232, 232, win->opacity));
    Line2D(win->render_ctx, x + 1, y, x + widget->width - 1, y,
        Color(96, 96, 96, win->opacity));
    Line2D(win->render_ctx, x + 1, y + widget->height - 1, x + widget->width - 1,
        y + widget->height - 1, Color(96, 96, 96, win->opacity));
    Line2D(win->render_ctx, x, y + 1, x, y + widget->height - 2,
        Color(96, 96, 96, win->opacity));
    Line2D(win->render_ctx, x + widget->width - 1, y + 1, x + widget->width - 1,
        y + widget->height - 2, Color(96, 96, 96, win->opacity));
    if (widget == win->mouse_down_widget && Mouse.left &&
        @widget_is_hovered(win->x + x, win->y + y, widget)) {

        if (widget->image) {
            MemCpy(&tmpctx, widget->image, sizeof(Context2D));
            tmpctx.opacity = win->opacity;
            ctx->blot(x + 5, y + 5, &tmpctx);
        }

        Line2D(win->render_ctx, x + 1, y + 1, x + widget->width - 2, y + 1,
            Color(152, 152, 152, win->opacity));
        Line2D(win->render_ctx, x + 1, y + 1, x + 1, y + widget->height - 3,
            Color(152, 152, 152, win->opacity));
        Line2D(win->render_ctx, x + 2, y + 2, x + widget->width - 3, y + 2,
            Color(216, 216, 216, win->opacity));
        Line2D(win->render_ctx, x + 2, y + 2, x + 2, y + widget->height - 4,
            Color(216, 216, 216, win->opacity));
        Line2D(win->render_ctx, x + 2, y + widget->height - 3,
            x + widget->width - 2, y + widget->height - 3, Color(255, 255, 255, win->opacity));
        Line2D(win->render_ctx, x + 3, y + widget->height - 4,
            x + widget->width - 2, y + widget->height - 4, Color(255, 255, 255, win->opacity));
        Line2D(win->render_ctx, x + widget->width - 3, y + 2, x + widget->width - 3,
            y + widget->height - 3, Color(255, 255, 255, win->opacity));
        Line2D(win->render_ctx, x + widget->width - 4, y + 3, x + widget->width - 4,
            y + widget->height - 3, Color(255, 255, 255, win->opacity));
    } else {

        if (widget->image) {
            MemCpy(&tmpctx, widget->image, sizeof(Context2D));
            tmpctx.opacity = win->opacity;
            ctx->blot(x + 4, y + 4, &tmpctx);
        }

        Line2D(win->render_ctx, x + 2, y + 2, x + widget->width - 2, y + 2,
            Color(255, 255, 255, win->opacity));
        Line2D(win->render_ctx, x + 2, y + 3, x + widget->width - 3, y + 3,
            Color(255, 255, 255, win->opacity));
        Line2D(win->render_ctx, x + 2, y + 2, x + 2, y + widget->height - 3,
            Color(255, 255, 255, win->opacity));
        Line2D(win->render_ctx, x + 3, y + 2, x + 3, y + widget->height - 4,
            Color(255, 255, 255, win->opacity));
        Line2D(win->render_ctx, x + 3, y + widget->height - 3,
            x + widget->width - 3, y + widget->height - 3, Color(216, 216, 216, win->opacity));
        Line2D(win->render_ctx, x + widget->width - 3, y + 3, x + widget->width - 3,
            y + widget->height - 3, Color(216, 216, 216, win->opacity));
        Line2D(win->render_ctx, x + 2, y + widget->height - 2,
            x + widget->width - 2, y + widget->height - 2, Color(152, 152, 152, win->opacity));
        Line2D(win->render_ctx, x + widget->width - 2, y + 2, x + widget->width - 2,
            y + widget->height - 2, Color(152, 152, 152, win->opacity));
    }
    if (StrLen(&widget->text) && win->opacity > 0) {
        text_offset = T(widget == win->mouse_down_widget && Mouse.left &&
                @widget_is_hovered(win->x + x, win->y + y, widget),
            1, 0);
        text_width = PutS2D(NULL, widget->font, 0, 0, Color(0, 0, 0), , &widget->text);
        PutS2D(win->render_ctx, widget->font,
            x + text_offset + (widget->width / 2) - (text_width / 2),
            y + text_offset + (widget->height / 2) - (widget->font->line_height / 2),
            color, , &widget->text);
    }
}

U0 @umami_checkbox_repaint(Window* win, CheckBoxWidget* widget, I64 x, I64 y)
{
    Context2D tmpctx;
    Context2D* ctx = win->render_ctx;
    if (widget == win->mouse_down_widget &&
        @widget_is_hovered(win->x + x, win->y + y, widget)) {
        MemCpy(&tmpctx, T(widget->checked, umami_widget_checkbox_checked_active, umami_widget_checkbox_active), sizeof(Context2D));
        tmpctx.opacity = win->opacity;
        ctx->blot(x, y, &tmpctx);
        return;
    }
    if (widget == win->mouse_down_widget->echo && widget != win->mouse_down_widget &&
        @widget_is_hovered(win->x + x, win->y + y, win->mouse_down_widget)) {
        MemCpy(&tmpctx, T(widget->checked, umami_widget_checkbox_checked_active, umami_widget_checkbox_active), sizeof(Context2D));
        tmpctx.opacity = win->opacity;
        ctx->blot(x, y, &tmpctx);
        return;
    }
    MemCpy(&tmpctx,
        T(widget->checked, umami_widget_checkbox_checked, umami_widget_checkbox), sizeof(Context2D));
    tmpctx.opacity = win->opacity;
    ctx->blot(x, y, &tmpctx);
}

U0 @umami_terminal_calculate_size(Window* win, TerminalWidget* widget)
{
    widget->size.cols = RoundI64(win->width, 8) / 8;
    widget->size.rows = (RoundI64(win->height, 16) / 16) - 2;
}

U32 @umami_terminal_256_color_lookup(I64 index)
{
    U32* color_table = TERMINAL_COLOR_TABLE;
    return color_table[index];
}

U0 @umami_terminal_set_256_color_foreground(Window* win, TerminalWidget* widget,
    I64 argc, U8** argv)
{
    if (argc != 3 && !StrCmp(argv[1], "5"))
        return;
    widget->color.foreground = @umami_terminal_256_color_lookup(Str2I64(argv[2]));
}

U0 @umami_terminal_set_256_color_background(Window* win, TerminalWidget* widget,
    I64 argc, U8** argv)
{
    if (argc != 3 && !StrCmp(argv[1], "5"))
        return;
    widget->color.background = @umami_terminal_256_color_lookup(Str2I64(argv[2]));
    widget->color.background.u8[3] = win->opacity;
}

U0 @umami_terminal_cleanup_strings(I64 argc, U8** argv)
{
    I64 i;
    for (i = 0; i < argc - 1; i++)
        Free(argv[i]);
}

I64 @umami_ansi_16_color_table[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };

U0 @umami_terminal_set_16_color_fg_value(TerminalWidget* widget)
{
    I64 val = widget->last_fg_color_set;
    if (val > 15)
        return;
    widget->color.foreground = Color(T(widget->attr.bold,
                                         gr_palette_std[8 + @umami_ansi_16_color_table[val]].r & 0xFF,
                                         gr_palette_std[@umami_ansi_16_color_table[val]].r & 0xFF),
        T(widget->attr.bold,
            gr_palette_std[8 + @umami_ansi_16_color_table[val]].g & 0xFF,
            gr_palette_std[@umami_ansi_16_color_table[val]].g & 0xFF),
        T(widget->attr.bold,
            gr_palette_std[8 + @umami_ansi_16_color_table[val]].b & 0xFF,
            gr_palette_std[@umami_ansi_16_color_table[val]].b & 0xFF));
}

U0 @umami_terminal_ctrl_seq_sgr(Window* win, TerminalWidget* widget)
{
    I64 argc;
    I64 i;
    I64 val;
    U8** argv = String.Split(&widget->consumed_chars, ';', &argc);
    if (!argc && StrLen(&widget->consumed_chars)) {
        argc = 1;
        argv[0] = &widget->consumed_chars;
    }
    if (!argc && !StrLen(&widget->consumed_chars)) {
        widget->color.background = Color(0, 0, 0);
        widget->color.foreground = Color(217, 217, 217);
        return;
    }
    for (i = 0; i < argc; i++) {
        val = Str2I64(argv[i]);
        switch (val) {
        case 0:
            widget->color.background = Color(0, 0, 0);
            widget->color.foreground = Color(217, 217, 217);
            break;
        case 1:
            widget->attr.bold = TRUE;
            if (widget->last_fg_color_set < 16)
                @umami_terminal_set_16_color_fg_value(widget);
            break;
        case 2:
            widget->attr.bold = FALSE;
            if (widget->last_fg_color_set < 16)
                @umami_terminal_set_16_color_fg_value(widget);
            break;
        // 16 colors
        case 30...37:
            widget->last_fg_color_set = val - 30;
            @umami_terminal_set_16_color_fg_value(widget);
            break;
        case 40...47:
            widget->color.background = Color(gr_palette_std[@umami_ansi_16_color_table[(val - 40)]].r & 0xFF,
                gr_palette_std[@umami_ansi_16_color_table[(val - 40)]].g & 0xFF,
                gr_palette_std[@umami_ansi_16_color_table[(val - 40)]].b & 0xFF,
                win->opacity);
            break;
        // 256 colors
        case 38:
            widget->last_fg_color_set = 256;
            @umami_terminal_set_256_color_foreground(win, widget, argc, argv);
            i += 2;
            break;
        case 48:
            @umami_terminal_set_256_color_background(win, widget, argc, argv);
            i += 2;
            break;
        case 39:
            // FIXME: Set default foreground color
            widget->color.foreground = Color(217, 217, 217);
            break;
        case 49:
            // FIXME: Set default background color
            widget->color.background = Color(0, 0, 0);
            break;
        default:
            System.Log("Unrecognized attribute in ctrl_seq_sgr: %d", val);
            break;
        }
    }
    // if (argc)
    //  @umami_terminal_cleanup_strings(argc, argv);
}

U0 @umami_terminal_set_col(Window* win, TerminalWidget* widget, I64 row,
    I64 col, U8 char)
{
    widget->row[row + widget->scroll.y].col[col + widget->scroll.x].foreground = widget->color.foreground;
    widget->row[row + widget->scroll.y].col[col + widget->scroll.x].background = widget->color.background;
    widget->row[row + widget->scroll.y].col[col + widget->scroll.x].char = char;
}

U0 @umami_terminal_set_row(Window* win, TerminalWidget* widget, I64 row,
    U8 char)
{
    I64 col;
    for (col = 0; col < TERMINAL_MAX_COLS - 1; col++)
        @umami_terminal_set_col(win, widget, row, col, char);
}

U0 @umami_terminal_set_rows(Window* win, TerminalWidget* widget, U8 char)
{
    I64 row;
    for (row = 0; row < widget->size.rows - 1; row++)
        @umami_terminal_set_row(win, widget, row, char);
}

U0 @umami_terminal_repaint_col(Window* win, TerminalWidget* widget, I64 row,
    I64 col)
{
    U8 char[2];
    char[1] = NULL;
    U32 col_bg_color;
    char[0] = widget->row[row + widget->scroll.y].col[col + widget->scroll.x].char;
    col_bg_color = widget->row[row + widget->scroll.y]
                       .col[col + widget->scroll.x]
                       .background;
    col_bg_color.u8[3] = win->opacity;
    widget->backing_store->fill_rect(2 + (col * 8), 2 + (row * 16), 8, 16,
        col_bg_color);
    PutS2D(widget->backing_store, Compositor.theme.font.monospace, 2 + (col * 8),
        2 + (row * 16),
        widget->row[row + widget->scroll.y]
            .col[col + widget->scroll.x]
            .foreground,
        , &char);
}

U0 @umami_terminal_repaint_row(Window* win, TerminalWidget* widget, I64 row)
{
    I64 col;
    for (col = 0; col < TERMINAL_MAX_COLS - 1; col++)
        @umami_terminal_repaint_col(win, widget, row, col);
}

U0 @umami_terminal_repaint_rows(Window* win, TerminalWidget* widget)
{
    I64 row;
    for (row = 0; row < widget->size.rows - 1; row++)
        @umami_terminal_repaint_row(win, widget, row);
}

U0 @umami_terminal_ctrl_seq_cursor_move(Window* win, TerminalWidget* widget,
    I64 dir)
{
    I64 pos = Str2I64(&widget->consumed_chars);
    I64 prev_row = widget->cursor.y;
    switch (dir) {
    case 'A':
        widget->cursor.y = Max(0, widget->cursor.y - pos);
        break;
    case 'B':
        widget->cursor.y = Min(widget->cursor.y + pos, widget->size.rows - 1);
        break;
    case 'C':
        widget->cursor.x = Min(widget->cursor.x + pos, widget->size.cols - 1);
        break;
    case 'D':
        widget->cursor.x = Max(0, widget->cursor.x - pos);
        break;
    };
    @umami_terminal_repaint_row(win, widget, prev_row);
}

U0 @umami_terminal_ctrl_seq_cursor_pos(Window* win, TerminalWidget* widget)
{
    I64 argc;
    I64 i;
    I64 val;
    U8** argv = String.Split(&widget->consumed_chars, ';', &argc);
    if (!argc) {
        @umami_terminal_repaint_col(win, widget, widget->cursor.y,
            widget->cursor.x);
        widget->cursor.x = 0;
        widget->cursor.y = 0;
        return;
    }
    if (argc == 2) {
        @umami_terminal_repaint_col(win, widget, widget->cursor.y,
            widget->cursor.x);
        widget->cursor.y = Str2I64(argv[0]) - 1;
        widget->cursor.x = Str2I64(argv[1]) - 1;
        widget->cursor.y = Min(widget->size.rows - 2, widget->cursor.y);
        widget->cursor.x = Min(widget->size.cols - 2, widget->cursor.x);
        widget->cursor.y = Max(0, widget->cursor.y);
        widget->cursor.x = Max(0, widget->cursor.x);
        //@umami_terminal_cleanup_strings(argc, argv);
        return;
    }
}

U0 @umami_terminal_ctrl_seq_erase_screen(Window* win, TerminalWidget* widget)
{
    I64 i;
    if (!StrLen(&widget->consumed_chars)) {
        // TODO: clear from cursor to end of the screen
        return;
    }
    I64 val = Str2I64(&widget->consumed_chars);
    switch (val) {
    case 0:
        // TODO: clear from cursor to end of the screen
        break;
    case 1:
        // TODO: clear from cursor to beginning of the screen
        break;
    case 2:
        widget->scroll.x = 0;
        widget->scroll.y = 0;
        widget->max.x = 0;
        widget->max.y = 0;
        for (i = 0; i < 2000; i++)
            @umami_terminal_set_row(win, widget, i, NULL);
        @umami_terminal_repaint_rows(win, widget);
        break;
    default:
        System.Log("Unrecognized attribute in ctrl_seq_erase_screen: %d", val);
        break;
    };
}

U0 @umami_terminal_ctrl_seq_erase_line(Window* win, TerminalWidget* widget)
{
    if (!StrLen(&widget->consumed_chars)) {
        // TODO: clear from cursor to end of the line
        return;
    }
    I64 val = Str2I64(&widget->consumed_chars);
    switch (val) {
    case 0:
        // TODO: clear from cursor to end of the line
        break;
    case 1:
        // TODO: clear from cursor to beginning of the line
        break;
    case 2:
        @umami_terminal_set_row(win, widget, widget->cursor.y, ' ');
        @umami_terminal_repaint_rows(win, widget);
        break;
    default:
        System.Log("Unrecognized attribute in ctrl_seq_erase_line: %d", val);
        break;
    };
}

U0 @umami_terminal_send_response_string(Window* win, TerminalWidget* widget,
    U8* string)
{
    if (!string || !widget->output)
        return;
    I64 i;
    for (i = 0; i < StrLen(string); i++)
        FifoU8Ins(widget->output, string[i]);
}

U0 @umami_terminal_ctrl_seq_set_func(Window* win, TerminalWidget* widget,
    Bool bool)
{
    if (!StrCmp(&widget->consumed_chars, "?25")) {
        @umami_terminal_repaint_col(win, widget, widget->cursor.y,
            widget->cursor.x);
        widget->cursor.hidden = T(bool, FALSE, TRUE);
    }
}

U0 @umami_terminal_ctrl_seq_dev_attr_report(Window* win,
    TerminalWidget* widget)
{
    if (!widget->output)
        return;
    if (StrFind("=", &widget->consumed_chars)) {
        // Tertiary device attributes
        @umami_terminal_send_response_string(win, widget, "\x1bP!|7E565445\x1b\\");
        return;
    }
    if (StrFind(">", &widget->consumed_chars)) {
        // Secondary device attributes
        @umami_terminal_send_response_string(win, widget, "\x1b[>65;6201;1c");
        return;
    }
    // Primary device attributes
    @umami_terminal_send_response_string(win, widget, "\x1b[?65;1;9c");
}

U0 @umami_terminal_ctrl_seq_ecp_report(Window* win, TerminalWidget* widget)
{
    if (!widget->output)
        return;
    U8 resp_str[32];
    StrPrint(&resp_str, "\x1b[%d;%dR", widget->cursor.y + 1,
        widget->cursor.x + 1);
    if (!StrCmp(&widget->consumed_chars, "5"))
        @umami_terminal_send_response_string(win, widget, "\x1b[0n");
    if (!StrCmp(&widget->consumed_chars, "6"))
        @umami_terminal_send_response_string(win, widget, &resp_str);
}

U0 @umami_terminal_ctrl_seq_req_tparm(Window* win, TerminalWidget* widget)
{
    // FIXME: allow unsolicited DECREPTPARMs
    if (!StrCmp(&widget->consumed_chars, "1")) {
        @umami_terminal_send_response_string(win, widget,
            "\x1b[3;1;1;120;120;1;0x");
        return;
    }
    @umami_terminal_send_response_string(win, widget, "\x1b[2;1;1;120;120;1;0x");
}

U0 @umami_terminal_os_cmd(Window* win, TerminalWidget* widget)
{
    I64 argc;
    I64 i;
    I64 val;
    U8** argv = String.Split(&widget->consumed_chars, ';', &argc);
    if (!argc)
        return;
    val = Str2I64(argv[0]);
    switch (val) {
    case 0:
        // Test set window title
        StrPrint(win->title, "Terminal - %s", argv[1]);
        break;
    default:
        break;
    }
    //@umami_terminal_cleanup_strings(argc, argv);
}

U0 @umami_terminal_consume_char(Window* win, TerminalWidget* widget)
{
    U8 char[2];
    char[1] = NULL;
    FifoU8Rem(widget->input, &char);

    switch (widget->state) {
    case TERMINAL_STATE_CONSUME_BEGIN:
        StrCpy(&widget->consumed_chars, "");
        switch (char[0]) {
        case '7': // DEC Save Cursor
            MemCpy(&widget->stored.attr, &widget->attr,
                sizeof(@terminal_widget_attr));
            MemCpy(&widget->stored.color, &widget->color,
                sizeof(@terminal_widget_color));
            MemCpy(&widget->stored.cursor, &widget->cursor,
                sizeof(@terminal_widget_cursor));
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case '8': // Dec Restore Cursor
            MemCpy(&widget->attr, &widget->stored.attr,
                sizeof(@terminal_widget_attr));
            MemCpy(&widget->color, &widget->stored.color,
                sizeof(@terminal_widget_color));
            MemCpy(&widget->cursor, &widget->stored.cursor,
                sizeof(@terminal_widget_cursor));
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case '[':
            widget->state = TERMINAL_STATE_CONSUME_CTRL_SEQ;
            break;
        case ']':
            widget->state = TERMINAL_STATE_CONSUME_OS_CMD;
            break;
        default:
            System.Log(Fs, "Undefined C1 control code: %c", char[0]);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        };
        break;
    case TERMINAL_STATE_CONSUME_CTRL_SEQ:
        switch (char[0]) {
        case '0' ... '9':
        case ';':
        case '?':
        case '>':
        case '=':
            String.Append(&widget->consumed_chars, &char);
            break;
        case 'A' ... 'D':
            @umami_terminal_ctrl_seq_cursor_move(win, widget, char[0]);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'H':
            @umami_terminal_ctrl_seq_cursor_pos(win, widget);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'J':
            @umami_terminal_ctrl_seq_erase_screen(win, widget);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'K':
            @umami_terminal_ctrl_seq_erase_line(win, widget);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'c':
            @umami_terminal_ctrl_seq_dev_attr_report(win, widget);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'h':
            @umami_terminal_ctrl_seq_set_func(win, widget, 1);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'l':
            @umami_terminal_ctrl_seq_set_func(win, widget, 0);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'm':
            @umami_terminal_ctrl_seq_sgr(win, widget);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'n':
            @umami_terminal_ctrl_seq_ecp_report(win, widget);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 't':
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        case 'x':
            @umami_terminal_ctrl_seq_req_tparm(win, widget);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        default:
            System.Log(Fs,
                "Invalid or unimplemented character in control sequence: '%c'",
                char[0]);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        }
        break;
    case TERMINAL_STATE_CONSUME_OS_CMD:
        switch (char[0]) {
        case 7:
        case '\\':
            @umami_terminal_os_cmd(win, widget);
            widget->state = TERMINAL_STATE_CONSUME_END;
            break;
        default:
            String.Append(&widget->consumed_chars, &char);
            break;
        };
        break;
    };
}

U0 @umami_terminal_output_char(Window* win, TerminalWidget* widget)
{
    if (widget->scroll.y != widget->max.y) {
        widget->scroll.y = widget->max.y;
        @umami_terminal_repaint_rows(win, widget);
    } else {
        widget->scroll.y = widget->max.y;
    }
    U8 char[2];
    char[1] = NULL;
    FifoU8Rem(widget->input, &char);
    switch (char[0]) {
    case 27:
        StrCpy(&widget->consumed_chars, "");
        widget->state = TERMINAL_STATE_CONSUME_BEGIN;
        return;
        break;
    case 7:
        // Audio.Beep();
        break;
    case 8:
        widget->row[widget->cursor.y + widget->scroll.y]
            .col[widget->cursor.x + widget->scroll.x]
            .char
            = ' ';
        widget->cursor.x--;
        if (widget->cursor.x < 0) {
            widget->cursor.y--;
            widget->cursor.x = widget->size.cols - 2;
        }
        break;
    case 10:
        widget->cursor.y++;
        widget->cursor.x = 0;
        break;
    case 13:
        widget->cursor.x = 0;
        break;
    case 32...127:
        widget->row[widget->cursor.y + widget->scroll.y]
            .col[widget->cursor.x + widget->scroll.x]
            .foreground
            = widget->color.foreground;
        widget->row[widget->cursor.y + widget->scroll.y]
            .col[widget->cursor.x + widget->scroll.x]
            .background
            = widget->color.background;
        widget->row[widget->cursor.y + widget->scroll.y]
            .col[widget->cursor.x + widget->scroll.x]
            .char
            = char[0];
        widget->cursor.x++;
        break;
    default:
        widget->row[widget->cursor.y + widget->scroll.y]
            .col[widget->cursor.x + widget->scroll.x]
            .foreground
            = widget->color.foreground;
        widget->row[widget->cursor.y + widget->scroll.y]
            .col[widget->cursor.x + widget->scroll.x]
            .background
            = widget->color.background;
        widget->row[widget->cursor.y + widget->scroll.y]
            .col[widget->cursor.x + widget->scroll.x]
            .char
            = '?';
        widget->cursor.x++;
        break;
    };
    if (widget->cursor.x >= widget->size.cols - 1) {
        widget->cursor.y++;
        widget->cursor.x = 0;
    }
    if (widget->cursor.y >= widget->size.rows - 1) {
        widget->cursor.y--;
        widget->max.y++;
        widget->scroll.y = widget->max.y;
        @umami_terminal_set_row(win, widget, widget->cursor.y, NULL);
        @umami_terminal_repaint_rows(win, widget);
    }
    @umami_terminal_set_col(win, widget, widget->cursor.y, widget->cursor.x, ' ');
}

U0 @umami_terminal_repaint(Window* win, TerminalWidget* widget, I64 x, I64 y)
{

    I64 current_col = NULL;
    I64 current_row = NULL;

    @umami_terminal_calculate_size(win, widget);

    if (widget->refresh) {
        @umami_terminal_repaint_rows(win, widget);
        widget->refresh = FALSE;
    }

    while (FifoU8Cnt(widget->input)) {
        current_col = widget->cursor.x;
        current_row = widget->cursor.y;
        switch (widget->state) {
        case TERMINAL_STATE_OUTPUT:
            @umami_terminal_output_char(win, widget);
            break;
        case TERMINAL_STATE_CONSUME_BEGIN:
        case TERMINAL_STATE_CONSUME_CTRL_SEQ:
        case TERMINAL_STATE_CONSUME_OS_CMD:
            @umami_terminal_consume_char(win, widget);
            break;
        }
        if (widget->state == TERMINAL_STATE_OUTPUT)
            @umami_terminal_repaint_col(win, widget, current_row, current_col);
        if (widget->state == TERMINAL_STATE_CONSUME_END)
            widget->state = TERMINAL_STATE_OUTPUT;
    }
    if (widget->state == TERMINAL_STATE_OUTPUT && !widget->cursor.hidden && widget->scroll.y == widget->max.y)
        widget->backing_store->fill_rect(2 + (widget->cursor.x * 8),
            2 + (widget->cursor.y * 16), 8, 16, widget->color.cursor);
}

I64 @umami_input_get_text_width(BitmapFontTextInputWidget* widget)
{
    I64 x;
    I64 y;
    I64 text_width = PutS2D(NULL, widget->font, 0, 0, Color(0, 0, 0), -1, &widget->text);

    Context2D* ctx = NewContext2D(text_width, widget->font->line_height);
    ctx->fill(0);
    PutS2D(ctx, widget->font, 0, 0, Color(128, 255, 255), -1, &widget->text);

    for (x = ctx->width - 1; x > -1; x--) {
        for (y = 0; y < ctx->height - 1; y++) {
            if (ctx->fb[(ctx->width * y) + x]) {
                DelContext2D(ctx);
                return x + 2;
            }
        }
    }
    DelContext2D(ctx);
    return x;
}

U0 @umami_input_draw_cursor(Window* win, BitmapFontTextInputWidget* widget,
    I64 index, I64 x, I64 y, I64 pos)
{
    if (widget->selected_region_start != -1 && widget->selected_region_end != -1)
        return;
    if (widget == win->focused_widget) {
        if (Blink && index == widget->cursor_index) {
            VLine2D(win->render_ctx, x + 2 + pos, y + 2,
                y + 2 + widget->font->line_height, Color(0, 0, 0));
        }
    }
}

U0 @umami_input_repaint(Window* win, I64 event,
    BitmapFontTextInputWidget* widget, I64 x, I64 y)
{
    Context2D* ctx = win->render_ctx;
    if (!widget->font)
        widget->font = Compositor.theme.font.sans;
    U8* text;
    U8 ch[2];
    I64 index;
    I64 text_width;
    I64 text_x_pos;
    ch[1] = NULL;
    widget->height = widget->font->line_height + 6;

    HLine2D(win->render_ctx, x, y, x + widget->width, Color(156, 156, 156, win->opacity));
    VLine2D(win->render_ctx, x, y, y + widget->height, Color(156, 156, 156, win->opacity));
    HLine2D(win->render_ctx, x + 1, y + widget->height, x + widget->width,
        Color(190, 190, 190, win->opacity));
    VLine2D(win->render_ctx, x + widget->width, y + 1, y + widget->height,
        Color(190, 190, 190, win->opacity));
    ctx->fill_rect(x + 1, y + 1, widget->width - 1, widget->height - 1,
        Color(255, 255, 255, win->opacity));

    if (widget == win->focused_widget) {
        HLine2D(win->render_ctx, x + 1, y + 1, x + widget->width - 1,
            Color(0, 0, 0, win->opacity));
        VLine2D(win->render_ctx, x + 1, y + 1, y + widget->height - 2,
            Color(0, 0, 0, win->opacity));
        HLine2D(win->render_ctx, x + 1, y + widget->height - 1,
            x + widget->width - 1, Color(0, 0, 0, win->opacity));
        VLine2D(win->render_ctx, x + widget->width - 1, y + 1,
            y + widget->height - 1, Color(0, 0, 0, win->opacity));
    } else {
        widget->in_drag = FALSE;
    }

    if (!Mouse.left) {
        widget->in_drag = FALSE;
        widget->mouse_drag_index = -1;
    }

    Context2D* text_input_ctx = NewContext2D(widget->width - 4, widget->height);
    text_input_ctx->opacity = win->opacity;

    if (widget->is_password) {
        StrCpy(widget->password, widget->text);
        for (text_x_pos = 0; text_x_pos < StrLen(widget->password); text_x_pos++)
            widget->password[text_x_pos] = '*';
    }
    text = T(widget->is_password, &widget->password, &widget->text);
    index = 0;
    text_width;
    text_x_pos = 0;
    while (*text) {
        ch[0] = *text;
        text_width = PutS2D(NULL, widget->font, 0, 0, Color(0, 0, 0), -1, &ch) + 2;

        if (event == CPZ_MSG_WIN_LEFT_BTN_DOWN && !widget->in_drag && Mouse.x >= win->x + widget->x + -widget->x_offset + text_x_pos && Mouse.x <= win->x + widget->x + -widget->x_offset + text_x_pos + text_width) {
            widget->cursor_index = index;
            widget->mouse_drag_index = widget->cursor_index - 1;
            widget->mouse_drag_origin_x = Mouse.x - win->x - widget->x + widget->x_offset;
        }

        if (widget->in_drag) {
            if (Mouse.x - win->x - widget->x < widget->mouse_drag_origin_x && Mouse.x >= win->x + widget->x + -widget->x_offset + text_x_pos && Mouse.x <= win->x + widget->x + -widget->x_offset + text_x_pos + text_width) {
                widget->selected_region_start = index;
                widget->selected_region_end = widget->mouse_drag_index;
            }
            if (Mouse.x - win->x - widget->x >= widget->mouse_drag_origin_x && Mouse.x >= win->x + widget->x + -widget->x_offset + text_x_pos + text_width) {
                widget->selected_region_start = widget->mouse_drag_index + 1;
                widget->selected_region_end = index;
            }
        }

        if (index >= widget->selected_region_start && index <= widget->selected_region_end) {
            text_input_ctx->fill_rect(-widget->x_offset + text_x_pos, 0, text_width,
                widget->font->line_height + 3, Compositor.theme.color.hilight);
        }
        if (index >= widget->selected_region_start && index <= widget->selected_region_end)
            PutS2D(text_input_ctx, widget->font, -widget->x_offset + text_x_pos, 2,
                Color(255, 255, 255), -1, &ch);
        else
            PutS2D(text_input_ctx, widget->font, -widget->x_offset + text_x_pos, 2,
                widget->color, -1, &ch);

        @umami_input_draw_cursor(win, widget, index, x, y,
            -widget->x_offset + text_x_pos);
        text_x_pos += text_width;

        text++;
        index++;
    }

    if (event == CPZ_MSG_WIN_LEFT_BTN_DOWN && !widget->in_drag && Mouse.x > win->x + widget->x + -widget->x_offset + text_x_pos && widget == win->focused_widget) {
        widget->cursor_index = StrLen(&widget->text);
        widget->mouse_drag_index = widget->cursor_index;
        widget->mouse_drag_origin_x = Mouse.x - win->x - widget->x;
    }

    if (event == CPZ_MSG_WIN_LEFT_BTN_DOWN && widget == win->focused_widget) {
        @widget_input_clear_selected_region(widget);
        widget->in_drag = TRUE;
    }

    if (-widget->x_offset + text_x_pos < text_input_ctx->width)
        @umami_input_draw_cursor(win, widget, index, x, y,
            -widget->x_offset + text_x_pos);

    ctx->blot(x + 3, y + 2, text_input_ctx);
    DelContext2D(text_input_ctx);
}

U0 @umami_label_repaint(Window* win, BitmapFontTextLabelWidget* widget, I64 x,
    I64 y)
{
    I64 color = widget->color;
    color.u8[3] = win->opacity;
    if (!widget->font)
        widget->font = Compositor.theme.font.sans;
    if (widget->text && win->opacity > 0)
        PutS2D(win->render_ctx, widget->font, x, y, color, -1,
            widget->text);
}

U0 @umami_list_view_repaint(Window* win, ListViewWidget* widget, I64 x, I64 y)
{
}

U0 @umami_menu_item_repaint(Window* win, MenuItemWidget* widget, I64 x, I64 y)
{
    Context2D* ctx = win->render_ctx;
    U32 text_color = Color(0, 0, 0);
    I64 text_x = 8;
    I64 text_y = 4 + (widget->height / 2) - (16 / 2);
    I64 icon_x = 4;
    I64 icon_y = 0;
    if (widget->icon) {
        icon_y = 4 + (widget->height / 2) - (widget->icon->height / 2) - 3;
    }
    if (@widget_is_hovered(win->x + x, win->y + y, widget)) {
        ctx->fill_rect(x, y, widget->width, widget->height,
            Compositor.theme.color.hilight);
        text_color = Color(255, 255, 255);
        if (widget->submenu) {
            Gui.Window.Show(widget->submenu);
            Gui.Window.Refresh(widget->submenu);
            if (Compositor.GetWindowByZIndex(Compositor.max_z_index - 3) != widget->submenu) {
                Gui.Window.SetZIndex(widget->submenu, Compositor.max_z_index - 3);
            }
        }
    }
    if (!(@widget_is_hovered(win->x + x, win->y + y, widget))) {
        if (widget->submenu) {
            Gui.Window.Hide(widget->submenu);
        }
    }
    if (!widget->font)
        widget->font = Compositor.theme.font.menu;
    if (widget->icon) {
        ctx->blot(x + icon_x, y + icon_y, widget->icon);
        text_x += 20;
    }
    if (widget->text)
        PutS2D(win->render_ctx, widget->font, x + text_x, y + text_y,
            T(widget->color, widget->color, text_color), -1, widget->text);
}

U0 @umami_radio_repaint(Window* win, RadioButtonWidget* widget, I64 x, I64 y)
{
    Context2D* ctx = win->render_ctx;
    Context2D tmpctx;
    if (widget == win->mouse_down_widget &&
        @widget_is_hovered(win->x + x, win->y + y, widget)) {
        MemCpy(&tmpctx,
            T(widget->selected, umami_widget_radio_selected,
                umami_widget_radio_active),
            sizeof(Context2D));
        tmpctx.opacity = win->opacity;
        ctx->blot(x, y, &tmpctx);
        return;
    }
    if (widget == win->mouse_down_widget->echo && widget != win->mouse_down_widget && !widget->selected &&
        @widget_is_hovered(win->x + x, win->y + y, win->mouse_down_widget)) {
        MemCpy(&tmpctx, T(win->mouse_down_widget(RadioButtonWidget*)->selected, umami_widget_radio_selected, umami_widget_radio_active), sizeof(Context2D));
        tmpctx.opacity = win->opacity;
        ctx->blot(x, y, &tmpctx);
        return;
    }
    MemCpy(&tmpctx, T(widget->selected, umami_widget_radio_selected, umami_widget_radio), sizeof(Context2D));
    tmpctx.opacity = win->opacity;
    ctx->blot(x, y, &tmpctx);
}

U0 @umami_radio_select(Window* win, RadioButtonWidget* widget)
{
    @window_widgets_list* widgets_list;
    widgets_list = win->widget;
    while (widgets_list) {
        if (widgets_list->widget && widgets_list->widget->type == WIDGET_TYPE_RADIO)
            if (widgets_list->widget(RadioButtonWidget*)->group == widget->group)
                widgets_list->widget(RadioButtonWidget*)->selected = T(widgets_list->widget == widget, TRUE, FALSE);
        widgets_list = widgets_list->next;
    }
}

Bool @umami_horz_slider_bar_is_hovered(I64 x, I64 y, Widget* widget)
{
    no_warn y;
    if (Mouse.x > x - 7 && Mouse.x < x + widget->width + 7)
        return TRUE;
    return FALSE;
}

Bool @umami_horz_slider_is_hovered(Window* win, HorizontalSliderWidget* widget,
    I64 x, I64 y, I64 scroll_pos)
{
    if (@widget_is_hovered(win->x + x, win->y + y - 24, widget) && Mouse.x > -4 + (win->x + widget->x - 7) + scroll_pos && Mouse.x < (win->x + widget->x - 7) + scroll_pos + 16)
        return TRUE;
    return FALSE;
}

U0 @umami_horz_slider_repaint(Window* win, I64 event,
    HorizontalSliderWidget* widget, I64 x, I64 y)
{
    Context2D* ctx = win->render_ctx;
    Context2D tmpctx;
    I64 scroll = widget->scroll;
    widget->height = 12;
    if (widget->in_drag &&
        @umami_horz_slider_bar_is_hovered(win->x + x, win->y + y - 24, widget)) {
        if (widget->origin.mouse_x != Mouse.x) {
            widget->scroll += (Mouse.x - widget->origin.mouse_x);
            widget->origin.mouse_x = Mouse.x;
        }
    }

    ctx->fill_rect(x, y + 5, widget->width, 4, Color(156, 156, 156, win->opacity));
    if (@umami_horz_slider_is_hovered(win, widget, x, y, widget->scroll) && event == CPZ_MSG_WIN_LEFT_BTN_DOWN) {
        win->mouse_down_widget = widget;
        widget->origin.mouse_x = Mouse.x;
        widget->in_drag = TRUE;
    } else {
        if (@widget_is_hovered(win->x + x, win->y + y, widget) && event == CPZ_MSG_WIN_LEFT_BTN_DOWN) {
            widget->scroll = (Mouse.x - win->x - x + 2);
            widget->origin.mouse_x = Mouse.x;
            widget->in_drag = TRUE;
        }
    }

    if (@widget_is_hovered(win->x + x, win->y + y, widget) && event == CPZ_MSG_WIN_MOUSE_WHEEL) {
        widget->scroll -= Compositor.mouse.z;
    }

    widget->scroll = Max(0, widget->scroll);
    widget->scroll = Min(widget->width - 1, widget->scroll);
    widget->value = ToI64((widget->max * 1.0) / (widget->width * 1.0) * (widget->scroll * 1.0));

    if (!Mouse.left)
        widget->in_drag = FALSE;

    HLine2D(win->render_ctx, x, y + 1, x + widget->width, Color(190, 190, 190, win->opacity));
    HLine2D(win->render_ctx, x + 1, y + 4, x + widget->width, Color(0, 0, 0, win->opacity));
    VLine2D(win->render_ctx, x + 1, y + 4, y + 8, Color(0, 0, 0, win->opacity));
    VLine2D(win->render_ctx, x, y + 4, y + 9, Color(190, 190, 190, win->opacity));
    HLine2D(win->render_ctx, x, y + 9, x + widget->width, Color(255, 255, 255, win->opacity));
    VLine2D(win->render_ctx, x + widget->width, y + 4, y + 9,
        Color(255, 255, 255, win->opacity));

    VLine2D(win->render_ctx, x, y + 11, y + 16, Color(190, 190, 190, win->opacity));
    VLine2D(win->render_ctx, x + 1, y + 11, y + 16, Color(255, 255, 255, win->opacity));

    VLine2D(win->render_ctx, x + (widget->width / 2) - 1, y + 11, y + 16,
        Color(190, 190, 190, win->opacity));
    VLine2D(win->render_ctx, x + (widget->width / 2), y + 11, y + 16,
        Color(255, 255, 255, win->opacity));

    VLine2D(win->render_ctx, x + widget->width - 1, y + 11, y + 16,
        Color(190, 190, 190, win->opacity));
    VLine2D(win->render_ctx, x + widget->width, y + 11, y + 16,
        Color(255, 255, 255, win->opacity));

    MemCpy(&tmpctx, umami_widget_horz_slider, sizeof(Context2D));
    tmpctx.opacity = win->opacity;
    ctx->blot((x - 7) + widget->scroll, y, &tmpctx);

    if (widget->scroll != scroll)
        widget->change = TRUE;
}

Bool @umami_vert_slider_bar_is_hovered(I64 x, I64 y, Widget* widget)
{
    no_warn x;
    if (Mouse.y > y - 7 && Mouse.y < y + widget->height + 7)
        return TRUE;
    return FALSE;
}

Bool @umami_vert_slider_is_hovered(Window* win, VerticalSliderWidget* widget,
    I64 x, I64 y, I64 scroll_pos)
{
    if (@widget_is_hovered(win->x + x - 24, win->y + y, widget) && Mouse.y > -4 + (win->y + widget->y - 7) + scroll_pos && Mouse.y < (win->y + widget->y - 7) + scroll_pos + 16)
        return TRUE;
    return FALSE;
}

U0 @umami_vert_slider_repaint(Window* win, I64 event,
    VerticalSliderWidget* widget, I64 x, I64 y)
{
    Context2D* ctx = win->render_ctx;
    Context2D tmpctx;
    I64 scroll = widget->scroll;
    widget->width = 12;
    if (widget->in_drag &&
        @umami_vert_slider_bar_is_hovered(win->x + x, win->y + y, widget)) {
        if (widget->origin.mouse_y != Mouse.y) {
            widget->scroll += (Mouse.y - widget->origin.mouse_y);
            widget->origin.mouse_y = Mouse.y;
        }
    }
    ctx->fill_rect(x + 5, y, 4, widget->height, Color(156, 156, 156, win->opacity));
    if (@umami_horz_slider_is_hovered(win, widget, x, y, widget->scroll) && event == CPZ_MSG_WIN_LEFT_BTN_DOWN) {
        win->mouse_down_widget = widget;
        widget->origin.mouse_y = Mouse.y;
        widget->in_drag = TRUE;
    } else {
        if (@widget_is_hovered(win->x + x, win->y + y, widget) && event == CPZ_MSG_WIN_LEFT_BTN_DOWN) {
            widget->scroll = (Mouse.y - win->y - y + 1);
            widget->origin.mouse_y = Mouse.y;
            widget->in_drag = TRUE;
        }
    }
    if (@widget_is_hovered(win->x + x, win->y + y, widget) && event == CPZ_MSG_WIN_MOUSE_WHEEL) {
        widget->scroll += Compositor.mouse.z;
    }
    widget->scroll = Max(0, widget->scroll);
    widget->scroll = Min(widget->height - 1, widget->scroll);
    widget->value = ToI64((widget->max * 1.0) / (widget->height * 1.0) * (widget->scroll * 1.0));
    if (!Mouse.left)
        widget->in_drag = FALSE;
    VLine2D(win->render_ctx, x + 1, y, y + widget->height, Color(190, 190, 190, win->opacity));
    VLine2D(win->render_ctx, x + 4, y + 1, y + widget->height, Color(0, 0, 0, win->opacity));
    HLine2D(win->render_ctx, x + 4, y, x + 9, Color(0, 0, 0, win->opacity));
    VLine2D(win->render_ctx, x + 9, y, y + widget->height, Color(255, 255, 255, win->opacity));
    HLine2D(win->render_ctx, x + 4, y + widget->height, x + 9,
        Color(255, 255, 255, win->opacity));
    HLine2D(win->render_ctx, x + 11, y, x + 16, Color(190, 190, 190, win->opacity));
    HLine2D(win->render_ctx, x + 11, y + 1, x + 16, Color(255, 255, 255, win->opacity));
    HLine2D(win->render_ctx, x + 11, y + (widget->height / 2) - 1, x + 16,
        Color(190, 190, 190, win->opacity));
    HLine2D(win->render_ctx, x + 11, y + (widget->height / 2), x + 16,
        Color(255, 255, 255, win->opacity));
    HLine2D(win->render_ctx, x + 11, y + widget->height - 1, x + 16,
        Color(190, 190, 190, win->opacity));
    HLine2D(win->render_ctx, x + 11, y + widget->height, x + 16,
        Color(255, 255, 255, win->opacity));

    MemCpy(&tmpctx, umami_widget_vert_slider, sizeof(Context2D));
    tmpctx.opacity = win->opacity;
    ctx->blot(x, (y - 7) + widget->scroll, &tmpctx);

    if (widget->scroll != scroll)
        widget->change = TRUE;
}

U0 @umami_vert_scroll_button_repaint(Window* win, I64 x, I64 y, I64 width,
    I64 height)
{
    I64 i;
    Context2D* ctx = win->render_ctx;

    ctx->fill_rect(x, y, width, height, Color(192, 192, 192));
    for (i = 0; i < height; i++) {
        Line2D(win->render_ctx, x, y, x, y + height - 1, Color(192, 192, 192));
        Line2D(win->render_ctx, x + 1, y, x + 1, y + height - 1,
            Color(255, 255, 255));
        Line2D(win->render_ctx, x + 14, y, x + width - 2, y + height - 1,
            Color(128, 128, 128));
        Line2D(win->render_ctx, x + 15, y, x + width - 1, y + height - 1,
            Color(64, 64, 64));
    }

    ctx->plot(x + 1, y, Color(192, 192, 192));
    ctx->plot(x + 14, y, Color(192, 192, 192));
    ctx->plot(x + 15, y, Color(64, 64, 64));

    Line2D(win->render_ctx, x + 1, y + 1, x + width - 2, y + 1,
        Color(255, 255, 255));
    ctx->plot(x + 14, y + 1, Color(128, 128, 128));
    ctx->plot(x + 15, y + 1, Color(64, 64, 64));

    Line2D(win->render_ctx, x + 1, y + height - 2, x + width - 2, y + height - 2,
        Color(128, 128, 128));

    Line2D(win->render_ctx, x, y + height - 1, x + width - 1, y + height - 1,
        Color(64, 64, 64));
}

U0 @umami_vert_scrollbar_repaint(Window* win, I64 event,
    VerticalScrollBarWidget* widget, I64 x,
    I64 y)
{
    I64 scroll = widget->scroll;
    I64 segment_length = widget->length;
    I64 i, j;
    Bool fill = TRUE;
    Context2D* ctx = win->render_ctx;

    if (widget->in_drag && Mouse.y > win->y + widget->y && Mouse.y < win->y + widget->y + widget->height) {
        if (widget->origin.mouse_y != Mouse.y) {
            widget->scroll += (Mouse.y - widget->origin.mouse_y);
            widget->origin.mouse_y = Mouse.y;
        }
    }
    if (@widget_is_hovered(win->x + x, win->y + y, widget) && event == CPZ_MSG_WIN_LEFT_BTN_DOWN) {
        if (Mouse.y > win->y + y + 16 && Mouse.y < win->y + y + widget->height - 16) {
            widget->scroll = (Mouse.y - win->y - y - (widget->length / 2));
            widget->origin.mouse_y = Mouse.y;
            widget->in_drag = TRUE;
        }
    }
    if (@widget_is_hovered(win->x + x, win->y + y, widget) && event == CPZ_MSG_WIN_MOUSE_WHEEL) {
        widget->scroll += Compositor.mouse.z;
    }
    widget->scroll = Max(16, widget->scroll);
    widget->scroll = Min(widget->height - 1, widget->scroll);
    while (widget->scroll + segment_length > widget->height - 16)
        widget->scroll--;
    widget->value = ToI64((widget->max * 1.0) / (widget->height * 1.0) * (widget->scroll * 1.0));
    if (!Mouse.left)
        widget->in_drag = FALSE;

    ctx->fill_rect(x, y, widget->width, widget->height,
        Color(127, 127, 127));

    for (i = 0; i < widget->width; i++) {
        for (j = 16; j < widget->height - 16; j++) {
            if (fill)
                ctx->plot(i + x, j + y, Color(255, 255, 255));
            fill = !fill;
        }
        fill = !fill;
    }

    @umami_vert_scroll_button_repaint(win, x, y, widget->width, 16);
    @umami_vert_scroll_button_repaint(win, x, y + widget->height - 16,
        widget->width, 16);

    ctx->blot(x, y, umami_widget_sb_up_button);
    ctx->blot(x, y + widget->height - 16,
        umami_widget_sb_down_button);

    @umami_vert_scroll_button_repaint(win, x, y + widget->scroll, widget->width,
        segment_length);

    if (widget->scroll != scroll)
        widget->change = TRUE;
}

U0 @umami_set_focused_and_hovered_widget(Window* win, I64 event)
{
    I64 title_bar_height = 0;
    if (@gui_window_flag_is_set(win, WIN_FLAGS_TITLE_BAR)) {
        title_bar_height = 17;
    }
    I64 x, y;

    I64 origin_x = 4;
    I64 origin_y = 4;
    if (title_bar_height)
        origin_y = 7 + title_bar_height;

    @window_widgets_list* widgets_list;
    Widget* widget;
    Bool widget_is_hovered = FALSE;

    if (event == CPZ_MSG_WIN_LEFT_BTN_DOWN)
        win->focused_widget = NULL;

    widget_is_hovered = FALSE;
    widgets_list = win->widget;
    while (widgets_list) {
        if (widgets_list->widget) {
            widget = widgets_list->widget;
            x = win->x + origin_x + widget->x;
            y = win->y + origin_y + widget->y;

            if (@gui_window_is_hovered(win) && @widget_is_hovered(x, y, widget)) {
                win->hovered_widget = widget;
                widget_is_hovered = TRUE;
            }

            if (@widget_is_hovered(x, y, widget) && event == CPZ_MSG_WIN_LEFT_BTN_DOWN) {
                win->focused_widget = widget;
                win->mouse_down_widget = widget;
                break;
            }
        }
        widgets_list = widgets_list->next;
    }

    if (!widget_is_hovered)
        win->hovered_widget = NULL;

    if (!Mouse.left && win->mouse_down_widget) {
        widget = win->mouse_down_widget;
        x = win->x + origin_x + widget->x;
        y = win->y + origin_y + widget->y;
        if (@widget_is_hovered(x, y, widget) && widget->callback.clicked)
            widget->callback.clicked(widget);
        if (@widget_is_hovered(x, y, widget) && widget->echo) {
            if (widget->echo->callback.clicked)
                widget->echo->callback.clicked(widget->echo);
        }
        if (@widget_is_hovered(x, y, widget)) {
            switch (widget->type) {
            case WIDGET_TYPE_CHECKBOX:
                widget(CheckBoxWidget*)->checked = !widget(CheckBoxWidget*)->checked;
                break;
            case WIDGET_TYPE_RADIO:
                @umami_radio_select(win, widget);
                break;
            default:
                break;
            }
        }
        if (@widget_is_hovered(x, y, widget) && widget->echo) {
            switch (widget->echo->type) {
            case WIDGET_TYPE_CHECKBOX:
                widget->echo(CheckBoxWidget*)->checked = !widget->echo(CheckBoxWidget*)->checked;
                break;
            case WIDGET_TYPE_RADIO:
                @umami_radio_select(win, widget->echo);
                break;
            default:
                break;
            }
        }
        win->mouse_down_widget = NULL;
    }
}

U0 @umami_widgets_repaint(Window* win, I64 event, I64 origin_x, I64 origin_y)
{
    @window_widgets_list* widgets_list;
    Widget* widget;
    Context2D* ctx;
    // Bool widget_is_hovered;
    I64 x, y;
    F64 ttf_point_to_size;
    no_warn ctx;
    no_warn ttf_point_to_size;

    widgets_list = win->widget;
    while (widgets_list) {
        if (widgets_list->widget) {
            widget = widgets_list->widget;
            x = origin_x + widget->x;
            y = origin_y + widget->y;
            if (x > win->width - 12 || y > win->height - 12)
                goto @umami_skip_widget_repaint;
            switch (widget->type) {
            case NULL:
                break;
            case WIDGET_TYPE_BUTTON:
                @umami_button_repaint(win, widget, x, y);
                break;
            case WIDGET_TYPE_CHECKBOX:
                @umami_checkbox_repaint(win, widget, x, y);
                break;
            case WIDGET_TYPE_TERMINAL:
                @umami_terminal_repaint(win, widget, x, y);
                if (widget->backing_store)
                    CopyRect2D(win->render_ctx, x, y, widget->backing_store);
                break;
            case WIDGET_TYPE_CONTEXT2D:
                if (widget(Context2DWidget*)->ctx)
                    if (widget(Context2DWidget*)->fast_copy)
                        CopyRect2D(win->render_ctx, x, y, widget(Context2DWidget*)->ctx);
                    else
                        win->render_ctx->blot(x, y, widget(Context2DWidget*)->ctx);
                break;
            case WIDGET_TYPE_RADIO:
                @umami_radio_repaint(win, widget, x, y);
                break;
            case WIDGET_TYPE_INPUT:
                @umami_input_repaint(win, event, widget, x, y);
                break;
            case WIDGET_TYPE_LABEL:
                @umami_label_repaint(win, widget, x, y);
                break;
            case WIDGET_TYPE_LISTVIEW:
                @umami_list_view_repaint(win, widget, x, y);
                break;
            case WIDGET_TYPE_MENU_ITEM:
                @umami_menu_item_repaint(win, widget, x, y);
                break;
            case WIDGET_TYPE_HORZ_SLIDER:
                @umami_horz_slider_repaint(win, event, widget, x, y);
                break;
            case WIDGET_TYPE_VERT_SLIDER:
                @umami_vert_slider_repaint(win, event, widget, x, y);
                break;
            case WIDGET_TYPE_HORZ_SCROLLBAR:
                //@umami_horz_scrollbar_repaint(win, event, widget, x, y);
                break;
            case WIDGET_TYPE_VERT_SCROLLBAR:
                @umami_vert_scrollbar_repaint(win, event, widget, x, y);
                break;
            default:
                break;
            }
            if (widget->change) {
                if (widget->callback.change)
                    widget->callback.change(widget);
                if (widget->echo) {
                    if (widget->echo->callback.change)
                        widget->echo->callback.change(widget);
                }
                widget->change = FALSE;
            }
        }
        @umami_skip_widget_repaint : widgets_list = widgets_list->next;
    }
}

U0 @umami_window_repaint(Window* win, I64 event)
{
    if (!win)
        return;

    win->repainting = TRUE;
    Context2D* ctx = win->render_ctx;
    Context2D tmpctx;

    win->render_ctx->width = win->width;
    win->render_ctx->height = win->height;
    if (!@gui_window_flag_is_set(win, WIN_FLAGS_NOFILL))
        MemSetU32(win->render_ctx->fb, Color(204, 204, 204, win->opacity),
            win->width * win->height);

    I64 title_bar_x;
    I64 title_bar_y;
    I64 title_bar_width;
    I64 title_bar_height = 0;
    I64 window_button_x;
    I64 origin_title_width;
    I64 pass;
    Bool repaint;
    U8 window_title[1024];

    if (@gui_window_flag_is_set(win, WIN_FLAGS_TITLE_BAR)) {
        title_bar_height = 17;
    }

    I64 widget_origin_x = 4;
    I64 widget_origin_y = 4;
    if (title_bar_height)
        widget_origin_y = 7 + title_bar_height;

    @umami_widgets_repaint(win, event, widget_origin_x, widget_origin_y);

    // Draw window margins
    if (!@gui_window_flag_is_set(win, WIN_FLAGS_NOFILL)) {
        ctx->fill_rect(0, 0, win->width, 4,
            Color(204, 204, 204, win->opacity));
        ctx->fill_rect(0, win->height - 4, win->width, 4,
            Color(204, 204, 204, win->opacity));
        ctx->fill_rect(0, 0, 4, win->height,
            Color(204, 204, 204, win->opacity));
        ctx->fill_rect(win->width - 5, 0, 4, win->height,
            Color(204, 204, 204, win->opacity));
    }

    // Draw window border
    Line2D(win->render_ctx, 0, 0, win->width - 1, 0,
        Color(255, 255, 255, win->opacity));
    Line2D(win->render_ctx, 0, 0, 0, win->height - 2,
        Color(255, 255, 255, win->opacity));
    Line2D(win->render_ctx, 1, win->height - 1, win->width - 1, win->height - 1,
        Color(156, 156, 156, win->opacity));
    Line2D(win->render_ctx, win->width - 1, 1, win->width - 1, win->height - 1,
        Color(156, 156, 156, win->opacity));

    title_bar_width = 0;

    if (@gui_window_flag_is_set(win, WIN_FLAGS_TITLE_BAR)) {
        title_bar_x = 4;
        title_bar_y = 4;
        title_bar_width = win->width - 8;

        if (@gui_window_flag_is_set(win, WIN_FLAGS_ICON)) {
            if (win->icon) {
                MemCpy(&tmpctx, win->icon, sizeof(Context2D));
            } else {
                MemCpy(&tmpctx, umami_default_icon, sizeof(Context2D));
            }
            tmpctx.opacity = win->opacity;
            ctx->blot(4, 4, &tmpctx);
            title_bar_x = 24;
            title_bar_width -= 20;
        }

        origin_title_width = title_bar_width;

        repaint = FALSE;

        for (pass = 0; pass < 2; pass++) {
            title_bar_width = origin_title_width;
            window_button_x = win->width - 20;
            if (@gui_window_flag_is_set(win, WIN_FLAGS_CLOSE_BUTTON)) {
                if (pass) {
                    if (win->mouse.x > window_button_x && win->mouse.x < window_button_x + 16 && win->mouse.y > 5 && win->mouse.y < 21 && win->button.close) {
                        CopyRect2D(umami_title_bar_button, -32, -1,
                            umami_min_max_close_pressed);
                        if (!Mouse.left && win->callback.close)
                            win->callback.close(win);
                    } else
                        CopyRect2D(umami_title_bar_button, -32, -1, umami_min_max_close);
                    MemCpy(&tmpctx, umami_title_bar_button, sizeof(Context2D));
                    tmpctx.opacity = win->opacity;
                    win->render_ctx->blot(window_button_x, 5, &tmpctx);
                }
                if (win->left_btn_down.x > window_button_x && win->left_btn_down.x < window_button_x + 16 && win->left_btn_down.y > 5 && win->left_btn_down.y < 21 && Mouse.left && !win->button.close) {
                    win->button.close = TRUE;
                    repaint = TRUE;
                }
                window_button_x -= 16;
                title_bar_width -= 17;
            }
            if (@gui_window_flag_is_set(win, WIN_FLAGS_MAX_BUTTON)) {
                if (pass) {
                    if (win->mouse.x > window_button_x && win->mouse.x < window_button_x + 16 && win->mouse.y > 5 && win->mouse.y < 21 && win->button.maximize) {
                        CopyRect2D(umami_title_bar_button, -16, -1,
                            umami_min_max_close_pressed);
                        if (!Mouse.left && win->callback.maximize)
                            win->callback.maximize(win);
                    }

                    else
                        CopyRect2D(umami_title_bar_button, -16, -1, umami_min_max_close);
                    MemCpy(&tmpctx, umami_title_bar_button, sizeof(Context2D));
                    tmpctx.opacity = win->opacity;
                    win->render_ctx->blot(window_button_x, 5, &tmpctx);
                }
                if (win->left_btn_down.x > window_button_x && win->left_btn_down.x < window_button_x + 16 && win->left_btn_down.y > 5 && win->left_btn_down.y < 21 && Mouse.left && !win->button.maximize) {
                    win->button.maximize = TRUE;
                    repaint = TRUE;
                }
                window_button_x -= 16;
                title_bar_width -= 17;
            }
            if (@gui_window_flag_is_set(win, WIN_FLAGS_MIN_BUTTON)) {
                if (pass) {
                    if (win->mouse.x > window_button_x && win->mouse.x < window_button_x + 16 && win->mouse.y > 5 && win->mouse.y < 21 && win->button.minimize) {
                        CopyRect2D(umami_title_bar_button, 0, -1,
                            umami_min_max_close_pressed);
                        if (!Mouse.left && win->callback.minimize)
                            win->callback.minimize(win);
                    } else
                        CopyRect2D(umami_title_bar_button, 0, -1, umami_min_max_close);
                    MemCpy(&tmpctx, umami_title_bar_button, sizeof(Context2D));
                    tmpctx.opacity = win->opacity;
                    win->render_ctx->blot(window_button_x, 5, &tmpctx);
                }
                if (win->left_btn_down.x > window_button_x && win->left_btn_down.x < window_button_x + 16 && win->left_btn_down.y > 5 && win->left_btn_down.y < 21 && Mouse.left && !win->button.minimize) {
                    win->button.minimize = TRUE;
                    repaint = TRUE;
                }
                window_button_x -= 16;
                title_bar_width -= 17;
            }
        }

        if (!Mouse.left) {
            if (win->button.close)
                repaint = TRUE;
            if (win->button.maximize)
                repaint = TRUE;
            if (win->button.minimize)
                repaint = TRUE;
            win->button.close = FALSE;
            win->button.maximize = FALSE;
            win->button.minimize = FALSE;
        }

        if (repaint) {
            @umami_window_repaint(win, event);
            return;
        }

        if (title_bar_width) {
            win->title_bar_x = title_bar_x;
            win->title_bar_width = title_bar_width;
        }

        StrCpy(&window_title, &win->title);
        if (win == Compositor.active_win) {
            HGradientRect2D(win->render_ctx, title_bar_x, title_bar_y,
                title_bar_width, title_bar_height,
                Color(10, 36, 106, win->opacity),
                Color(165, 201, 239, win->opacity));
            // Color(85, 18, 132, win->opacity),
            // Color(233, 15, 240, win->opacity));
            PutS2D(win->render_ctx, Compositor.theme.font.menu, title_bar_x + 4,
                title_bar_y + 4, Color(255, 255, 255, win->opacity),
                title_bar_width, &window_title);
        } else {
            ctx->fill_rect(title_bar_x, title_bar_y, title_bar_width,
                title_bar_height, Color(235, 235, 235, win->opacity));
            PutS2D(win->render_ctx, Compositor.theme.font.menu, title_bar_x + 4,
                title_bar_y + 4, Color(156, 156, 156, win->opacity),
                title_bar_width, &window_title);
        }

        Line2D(win->render_ctx, title_bar_x, title_bar_y,
            title_bar_x + title_bar_width - 1, title_bar_y,
            Color(156, 156, 156, win->opacity));
        Line2D(win->render_ctx, title_bar_x, title_bar_y, title_bar_x,
            title_bar_y + title_bar_height - 2,
            Color(156, 156, 156, win->opacity));

        Line2D(win->render_ctx, title_bar_x + title_bar_width - 1, title_bar_y,
            title_bar_x + title_bar_width - 1,
            title_bar_y + title_bar_height - 1,
            Color(255, 255, 255, win->opacity));
        Line2D(win->render_ctx, title_bar_x, title_bar_y + title_bar_height - 1,
            title_bar_x + title_bar_width - 1,
            title_bar_y + title_bar_height - 1,
            Color(255, 255, 255, win->opacity));

        if (@gui_window_flag_is_set(win, WIN_FLAGS_RESIZABLE)) {
            MemCpy(&tmpctx, umami_corner_resize, sizeof(Context2D));
            tmpctx.opacity = win->opacity;
            ctx->blot(win->width - 16, win->height - 16, &tmpctx);
        }
    }

    win->backing_store->width = win->render_ctx->width;
    win->backing_store->height = win->render_ctx->height;
    if (!@gui_window_flag_is_set(win, WIN_FLAGS_NOFILL))
        MemCpyU32(win->backing_store->fb, win->render_ctx->fb,
            win->render_ctx->width * win->render_ctx->height);
    if (@gui_window_flag_is_set(win, WIN_FLAGS_NOFILL))
        MemCpyU32(win->backing_store->fb, win->render_ctx->fb, (win->width) * 20);

    win->repainting = FALSE;
}

Compositor.theme.color.active_border = Color(0, 0, 0);
Compositor.theme.color.hilight = Color(8, 33, 107);

BitmapFont* @umami_theme_bitmapfont_001 = @bitmapfont_new_from_context2d(
    Image.FileToContext2D("M:/Media/Themes/Umami/BitmapFont/menu.png"),
    "Eight Bit Dragon",
    "`1234567890-=[]\;',./"
    "~!@#\d%^&*()_+{}|:\"<>?"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
@umami_theme_bitmapfont_001->line_height = 12;
BitmapFonts.Add(@umami_theme_bitmapfont_001);

BitmapFont* @umami_theme_bitmapfont_002 = @bitmapfont_new_from_context2d(
    Image.FileToContext2D("M:/Media/Themes/Umami/BitmapFont/sans.png"),
    "Nineteen Ninety Three",
    "`1234567890-=[]\;',./"
    "~!@#\d%^&*()_+{}|:\"<>?"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
@umami_theme_bitmapfont_002->line_height = 10;
BitmapFonts.Add(@umami_theme_bitmapfont_002);

BitmapFont* @umami_theme_bitmapfont_003 = @bitmapfont_new_from_context2d(
    Image.FileToContext2D("M:/Media/Themes/Umami/BitmapFont/ibm3161.png"),
    "IBM3161",
    " !\"#\d%&'()*+,-./"
    "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
    "abcdefghijklmnopqrstuvwxyz{|}~",
    7);
@umami_theme_bitmapfont_003->line_height = 16;
BitmapFonts.Add(@umami_theme_bitmapfont_003);

Compositor.theme.font.menu = BitmapFonts.GetByName("Eight Bit Dragon");
Compositor.theme.font.sans = BitmapFonts.GetByName("Nineteen Ninety Three");
Compositor.theme.font.monospace = BitmapFonts.GetByName("IBM3161");

Compositor.theme.window.min_width = 128;
Compositor.theme.window.min_height = 128;

Compositor.theme.window_repaint = &@umami_window_repaint;

Context2D** umami_pointer_wait_frames = CAlloc(sizeof(Context2D*) * 18);
Context2D* umami_pointer_wait_anim = Image.FileToContext2D("M:/Media/Themes/Umami/Pointer/wait.png");

U0 @umami_pointer_wait_anim_init()
{
    I64 i;
    for (i = 0; i < 18; i++) {
        umami_pointer_wait_frames[i] = NewContext2D(24, 24);
        umami_pointer_wait_frames[i]->blot(0, -(24 * i), umami_pointer_wait_anim);
    }
    DelContext2D(umami_pointer_wait_anim);
}

@umami_pointer_wait_anim_init;
Compositor.theme.pointer.wait = Animation2D.NewFromFrames(umami_pointer_wait_frames, 18, 50);
