/* * Sovereign IDE — Application Shell * Top-level window + child panel HWNDs + layout engine. * This replaces window.c as the orchestrator. */ #include "shell.h" #include "application.h" #include "../../ui/layout.h" #include "../../ui/project_tree.h" #include "../../ui/status_bar.h" #include "../../ui/output_panel.h" #include "../../editor/editor_view.h" #include "../../editor/document.h" #include "../../terminal/terminal_view.h" #include "../../git/repository.h" #include "../../build/cmake_runner.h" #include "../../lsp/client.h" #include "../../core/events.h" #include #include #define TIMER_CARET 1 #define TIMER_PTY 2 #define TIMER_GIT 3 /* Child window IDs */ #define IDC_SIDEBAR 101 #define IDC_EDITOR 102 #define IDC_BOTTOM 103 #define IDC_STATUSBAR 104 #define SHELL_CLASS L"SovShell" #define PANEL_CLASS L"SovPanel" #define SHELL_TITLE L"Sovereign IDE" static struct { HWND frame; HWND wnd_sidebar; HWND wnd_editor; HWND wnd_bottom; /* container only — no D2D on this one */ HWND wnd_output; /* OutputPanel lives here */ HWND wnd_terminal; /* TerminalView lives here */ HWND wnd_status; Layout layout; ProjectTree *tree; EditorView *editor; OutputPanel *output; TerminalView *terminal; StatusBar *status; GitRepo git; CmakeRunner *builder; LspClient *lsp; int lsp_version; Document doc; int client_w; int client_h; bool terminal_focused; } g_shell; /* ----------------------------------------------------------- * Build runner callbacks * ----------------------------------------------------------- */ static void on_build_line(const char *text, bool is_err, void *ctx) { (void)ctx; (void)is_err; if (!g_shell.output) return; /* Convert UTF-8 line to wide */ wchar_t wbuf[2048]; MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf, 2048); output_panel_append(g_shell.output, wbuf); output_panel_append(g_shell.output, L"\n"); /* Show output panel while building */ g_shell.terminal_focused = false; ShowWindow(g_shell.wnd_output, SW_SHOW); ShowWindow(g_shell.wnd_terminal, SW_HIDE); } static void on_build_done(int exit_code, void *ctx) { (void)ctx; if (!g_shell.output) return; wchar_t msg[64]; if (exit_code == 0) swprintf_s(msg, 64, L"✔ Build succeeded\n"); else swprintf_s(msg, 64, L"✘ Build failed (exit %d)\n", exit_code); output_panel_append(g_shell.output, msg); if (g_shell.status) status_bar_set_message(g_shell.status, exit_code == 0 ? L"Build OK" : L"Build FAILED"); InvalidateRect(g_shell.wnd_bottom, NULL, FALSE); InvalidateRect(g_shell.wnd_status, NULL, FALSE); } /* ----------------------------------------------------------- * LSP diagnostics callback * ----------------------------------------------------------- */ static void on_lsp_diag(const LspDiagList *list, void *ctx) { (void)ctx; if (!g_shell.editor) return; /* Only apply diags that match the current open file */ if (g_shell.doc.has_path && _wcsicmp(list->uri, g_shell.doc.path) == 0) { editor_view_set_diagnostics(g_shell.editor, list); } } /* ----------------------------------------------------------- * File open callback (from project tree click) * ----------------------------------------------------------- */ static void on_open_file(const wchar_t *path, void *ctx) { (void)ctx; document_destroy(&g_shell.doc); if (document_open(&g_shell.doc, path) == SOV_OK) { editor_view_set_document(g_shell.editor, &g_shell.doc); editor_view_set_diagnostics(g_shell.editor, NULL); /* Notify LSP of the new file */ if (g_shell.lsp) { char utf8[1 << 20]; size_t blen = buffer_length(g_shell.doc.buffer); if (blen > sizeof(utf8) - 1) blen = sizeof(utf8) - 1; buffer_read(g_shell.doc.buffer, 0, utf8, blen); utf8[blen] = '\0'; g_shell.lsp_version = 1; lsp_client_open(g_shell.lsp, path, utf8, blen); } /* extract filename for status bar */ const wchar_t *name = wcsrchr(path, L'\\'); status_bar_set_file(g_shell.status, name ? name + 1 : path, false); output_panel_append(g_shell.output, L"Opened: "); output_panel_append(g_shell.output, path); output_panel_append(g_shell.output, L"\n"); } else { output_panel_append(g_shell.output, L"Failed to open file\n"); } InvalidateRect(g_shell.wnd_editor, NULL, FALSE); } /* ----------------------------------------------------------- * Reposition all child windows from layout * ----------------------------------------------------------- */ static void apply_layout(void) { PanelRect rects[PANEL_COUNT]; layout_compute(&g_shell.layout, g_shell.client_w, g_shell.client_h, rects); SetWindowPos(g_shell.wnd_sidebar, NULL, rects[PANEL_SIDEBAR].x, rects[PANEL_SIDEBAR].y, rects[PANEL_SIDEBAR].w, rects[PANEL_SIDEBAR].h, SWP_NOZORDER | SWP_NOACTIVATE); SetWindowPos(g_shell.wnd_editor, NULL, rects[PANEL_EDITOR].x, rects[PANEL_EDITOR].y, rects[PANEL_EDITOR].w, rects[PANEL_EDITOR].h, SWP_NOZORDER | SWP_NOACTIVATE); SetWindowPos(g_shell.wnd_bottom, NULL, rects[PANEL_BOTTOM].x, rects[PANEL_BOTTOM].y, rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h, SWP_NOZORDER | SWP_NOACTIVATE); SetWindowPos(g_shell.wnd_status, NULL, rects[PANEL_STATUSBAR].x, rects[PANEL_STATUSBAR].y, rects[PANEL_STATUSBAR].w, rects[PANEL_STATUSBAR].h, SWP_NOZORDER | SWP_NOACTIVATE); if (g_shell.editor) editor_view_resize(g_shell.editor, (uint32_t)rects[PANEL_EDITOR].w, (uint32_t)rects[PANEL_EDITOR].h); if (g_shell.tree) project_tree_resize(g_shell.tree, rects[PANEL_SIDEBAR].w, rects[PANEL_SIDEBAR].h); /* Resize output+terminal sub-windows to fill wnd_bottom */ if (g_shell.wnd_output) SetWindowPos(g_shell.wnd_output, NULL, 0, 0, rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h, SWP_NOZORDER | SWP_NOACTIVATE); if (g_shell.wnd_terminal) SetWindowPos(g_shell.wnd_terminal, NULL, 0, 0, rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h, SWP_NOZORDER | SWP_NOACTIVATE); if (g_shell.output) output_panel_resize(g_shell.output, rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h); if (g_shell.terminal) terminal_view_resize(g_shell.terminal, rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h); if (g_shell.status) status_bar_resize(g_shell.status, rects[PANEL_STATUSBAR].w, rects[PANEL_STATUSBAR].h); } /* ----------------------------------------------------------- * Panel child window proc (sidebar / editor / bottom / status) * ----------------------------------------------------------- */ static LRESULT CALLBACK panel_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_PAINT: { PAINTSTRUCT ps; BeginPaint(hwnd, &ps); if (hwnd == g_shell.wnd_sidebar && g_shell.tree) project_tree_paint(g_shell.tree); else if (hwnd == g_shell.wnd_editor && g_shell.editor) { RECT rc; GetClientRect(hwnd, &rc); editor_view_paint(g_shell.editor, rc); } else if (hwnd == g_shell.wnd_output && g_shell.output) output_panel_paint(g_shell.output); else if (hwnd == g_shell.wnd_terminal && g_shell.terminal) terminal_view_paint(g_shell.terminal); else if (hwnd == g_shell.wnd_status && g_shell.status) status_bar_paint(g_shell.status); EndPaint(hwnd, &ps); return 0; } case WM_LBUTTONDOWN: { int x = (int)(short)LOWORD(lp), y = (int)(short)HIWORD(lp); if (hwnd == g_shell.wnd_sidebar && g_shell.tree) project_tree_mouse_down(g_shell.tree, x, y); else if (hwnd == g_shell.wnd_editor && g_shell.editor) { SetFocus(hwnd); g_shell.terminal_focused = false; editor_view_mouse_down(g_shell.editor, x, y); } else if (hwnd == g_shell.wnd_terminal || hwnd == g_shell.wnd_output) { SetFocus(hwnd); g_shell.terminal_focused = (hwnd == g_shell.wnd_terminal); } return 0; } case WM_MOUSEMOVE: { if (hwnd == g_shell.wnd_editor && g_shell.editor) { bool btn = (wp & MK_LBUTTON) != 0; editor_view_mouse_move(g_shell.editor, (int)(short)LOWORD(lp), (int)(short)HIWORD(lp), btn); } return 0; } case WM_KEYDOWN: case WM_SYSKEYDOWN: { if (hwnd == g_shell.wnd_terminal && g_shell.terminal) { UINT mods = 0; if (GetKeyState(VK_CONTROL) & 0x8000) mods |= 2; terminal_view_key_down(g_shell.terminal, (UINT)wp, mods); return 0; } if (hwnd == g_shell.wnd_editor && g_shell.editor) { UINT mods = 0; if (GetKeyState(VK_SHIFT) & 0x8000) mods |= 1; if (GetKeyState(VK_CONTROL) & 0x8000) mods |= 2; if (GetKeyState(VK_MENU) & 0x8000) mods |= 4; editor_view_key_down(g_shell.editor, (UINT)wp, mods); /* Update status bar line/col after navigation */ if (g_shell.status && g_shell.doc.buffer) { size_t off = editor_view_caret_offset(g_shell.editor); char tmp[1]; /* count lines up to offset */ size_t line = 1, col = 1; for (size_t i = 0; i < off; i++) { size_t n = buffer_read(g_shell.doc.buffer, i, tmp, 1); if (n && tmp[0] == '\n') { line++; col = 1; } else col++; } status_bar_set_pos(g_shell.status, (int)line, (int)col); status_bar_set_file(g_shell.status, g_shell.doc.has_path ? wcsrchr(g_shell.doc.path, L'\\') + 1 : L"untitled", document_is_dirty(&g_shell.doc)); } } else if (hwnd == g_shell.wnd_sidebar && g_shell.tree) { project_tree_key_down(g_shell.tree, (UINT)wp); } return 0; } case WM_CHAR: { if (hwnd == g_shell.wnd_terminal && g_shell.terminal) { terminal_view_char(g_shell.terminal, (wchar_t)wp); } else if (hwnd == g_shell.wnd_editor && g_shell.editor) { editor_view_char(g_shell.editor, (wchar_t)wp); /* Notify LSP of change */ if (g_shell.lsp && g_shell.doc.has_path && g_shell.doc.buffer) { char utf8[1 << 20]; size_t blen = buffer_length(g_shell.doc.buffer); if (blen < sizeof(utf8)) { buffer_read(g_shell.doc.buffer, 0, utf8, blen); utf8[blen] = '\0'; lsp_client_change(g_shell.lsp, g_shell.doc.path, utf8, blen, ++g_shell.lsp_version); } } } return 0; } case WM_MOUSEWHEEL: { if (hwnd == g_shell.wnd_bottom && g_shell.output) output_panel_scroll(g_shell.output, GET_WHEEL_DELTA_WPARAM(wp) / 40); return 0; } case WM_TIMER: if (wp == TIMER_PTY && hwnd == g_shell.wnd_terminal) { terminal_view_tick(g_shell.terminal); InvalidateRect(g_shell.wnd_terminal, NULL, FALSE); } else { InvalidateRect(hwnd, NULL, FALSE); } return 0; } return DefWindowProcW(hwnd, msg, wp, lp); } /* ----------------------------------------------------------- * Frame window proc (top-level) * ----------------------------------------------------------- */ static LRESULT CALLBACK shell_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: { layout_init(&g_shell.layout); /* Measure initial client area so D2D render targets get real dimensions */ RECT cr; GetClientRect(hwnd, &cr); g_shell.client_w = cr.right - cr.left; g_shell.client_h = cr.bottom - cr.top; if (g_shell.client_w < 1) g_shell.client_w = 1280; if (g_shell.client_h < 1) g_shell.client_h = 800; /* Compute layout so child panels get non-zero sizes */ PanelRect rects[PANEL_COUNT]; layout_compute(&g_shell.layout, g_shell.client_w, g_shell.client_h, rects); /* Create child panels at their real positions */ HINSTANCE hi = app_get_hinstance(); g_shell.wnd_sidebar = CreateWindowExW(0, PANEL_CLASS, NULL, WS_CHILD | WS_VISIBLE, rects[PANEL_SIDEBAR].x, rects[PANEL_SIDEBAR].y, rects[PANEL_SIDEBAR].w, rects[PANEL_SIDEBAR].h, hwnd, (HMENU)IDC_SIDEBAR, hi, NULL); g_shell.wnd_editor = CreateWindowExW(0, PANEL_CLASS, NULL, WS_CHILD | WS_VISIBLE, rects[PANEL_EDITOR].x, rects[PANEL_EDITOR].y, rects[PANEL_EDITOR].w, rects[PANEL_EDITOR].h, hwnd, (HMENU)IDC_EDITOR, hi, NULL); g_shell.wnd_bottom = CreateWindowExW(0, PANEL_CLASS, NULL, WS_CHILD | WS_VISIBLE, rects[PANEL_BOTTOM].x, rects[PANEL_BOTTOM].y, rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h, hwnd, (HMENU)IDC_BOTTOM, hi, NULL); g_shell.wnd_status = CreateWindowExW(0, PANEL_CLASS, NULL, WS_CHILD | WS_VISIBLE, rects[PANEL_STATUSBAR].x, rects[PANEL_STATUSBAR].y, rects[PANEL_STATUSBAR].w, rects[PANEL_STATUSBAR].h, hwnd, (HMENU)IDC_STATUSBAR, hi, NULL); /* Output and terminal need separate HWNDs (each owns one D2D RT) */ int bw = rects[PANEL_BOTTOM].w; int bh = rects[PANEL_BOTTOM].h; g_shell.wnd_output = CreateWindowExW(0, PANEL_CLASS, NULL, WS_CHILD | WS_VISIBLE, 0, 0, bw, bh, g_shell.wnd_bottom, NULL, hi, NULL); g_shell.wnd_terminal = CreateWindowExW(0, PANEL_CLASS, NULL, WS_CHILD, 0, 0, bw, bh, g_shell.wnd_bottom, NULL, hi, NULL); /* Init subsystems — each on its own HWND */ #define DBGINIT(call, name) do { \ SovResult _r = (call); \ if (_r != SOV_OK) MessageBoxW(NULL, L##name L" FAILED", L"Init Error", MB_OK); \ } while(0) project_tree_create(&g_shell.tree, g_shell.wnd_sidebar); editor_view_create(&g_shell.editor, g_shell.wnd_editor); output_panel_create(&g_shell.output, g_shell.wnd_output); terminal_view_create(&g_shell.terminal, g_shell.wnd_terminal); status_bar_create(&g_shell.status, g_shell.wnd_status); #undef DBGINIT g_shell.terminal_focused = false; /* default: show output panel */ /* wnd_output visible, wnd_terminal hidden until user toggles */ ShowWindow(g_shell.wnd_output, SW_SHOW); ShowWindow(g_shell.wnd_terminal, SW_HIDE); /* Wire callbacks */ if (g_shell.tree) { project_tree_set_callback(g_shell.tree, on_open_file, NULL); } /* Default document */ document_new(&g_shell.doc); buffer_insert(g_shell.doc.buffer, 0, "#include \n\nint main(void) {\n printf(\"Sovereign IDE\\n\");\n return 0;\n}\n", 88); buffer_mark_clean(g_shell.doc.buffer); if (g_shell.editor) editor_view_set_document(g_shell.editor, &g_shell.doc); /* Open current working directory in project tree */ wchar_t cwd[MAX_PATH]; GetCurrentDirectoryW(MAX_PATH, cwd); if (g_shell.tree) project_tree_open(g_shell.tree, cwd); if (g_shell.status) { status_bar_set_file(g_shell.status, L"main.c", false); status_bar_set_pos(g_shell.status, 1, 1); status_bar_set_branch(g_shell.status, L"master"); status_bar_set_message(g_shell.status, L"GCC 13.2"); } /* Output welcome */ if (g_shell.output) { output_panel_append(g_shell.output, L"Sovereign IDE ready.\n"); output_panel_append(g_shell.output, L"Project: "); output_panel_append(g_shell.output, cwd); output_panel_append(g_shell.output, L"\n"); } /* Git branch detection */ if (git_repo_open(&g_shell.git, cwd) == SOV_OK && g_shell.status) { wchar_t branch_disp[136]; swprintf_s(branch_disp, 136, L"%s%s", g_shell.git.detached ? L"detached:" : L"", g_shell.git.branch); status_bar_set_branch(g_shell.status, branch_disp); } /* CMake build runner */ wchar_t build_dir[MAX_PATH]; swprintf_s(build_dir, MAX_PATH, L"%s\\build", cwd); cmake_runner_create(&g_shell.builder, build_dir, on_build_line, on_build_done, NULL); /* Start LSP server (clangd must be on PATH) */ g_shell.lsp = NULL; g_shell.lsp_version = 1; if (lsp_client_start(&g_shell.lsp, L"clangd", on_lsp_diag, NULL) == SOV_OK) { lsp_client_initialize(g_shell.lsp, cwd); output_panel_append(g_shell.output, L"LSP: clangd started.\n"); } else { output_panel_append(g_shell.output, L"LSP: clangd not found — install LLVM.\n"); g_shell.lsp = NULL; } SetTimer(g_shell.wnd_editor, TIMER_CARET, 530, NULL); SetTimer(g_shell.wnd_terminal, TIMER_PTY, 16, NULL); /* ~60fps PTY poll */ SetTimer(hwnd, TIMER_GIT, 3000, NULL); /* git refresh every 3s */ return 0; } case WM_SIZE: { g_shell.client_w = (int)(short)LOWORD(lp); g_shell.client_h = (int)(short)HIWORD(lp); apply_layout(); return 0; } case WM_MOUSEMOVE: { int mx = (int)(short)LOWORD(lp); int my = (int)(short)HIWORD(lp); if (g_shell.layout.dragging) { layout_update_drag(&g_shell.layout, mx, my); apply_layout(); return 0; } int spl = layout_hit_splitter(&g_shell.layout, g_shell.client_w, g_shell.client_h, mx, my); if (spl == 0 || spl == 1) SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_SIZEWE)); else if (spl == 2) SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_SIZENS)); else SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); return 0; } case WM_LBUTTONDOWN: { int mx = (int)(short)LOWORD(lp); int my = (int)(short)HIWORD(lp); int spl = layout_hit_splitter(&g_shell.layout, g_shell.client_w, g_shell.client_h, mx, my); if (spl >= 0) { layout_begin_drag(&g_shell.layout, spl, mx, my); SetCapture(hwnd); } return 0; } case WM_LBUTTONUP: layout_end_drag(&g_shell.layout); ReleaseCapture(); return 0; case WM_COMMAND: { switch (LOWORD(wp)) { case 1001: /* File > Open Folder */ { /* Use SHBrowseForFolder for folder selection */ BROWSEINFOW bi = {0}; bi.hwndOwner = hwnd; bi.lpszTitle = L"Open Project Folder"; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; LPITEMIDLIST pidl = SHBrowseForFolderW(&bi); if (pidl) { wchar_t path[MAX_PATH]; SHGetPathFromIDListW(pidl, path); CoTaskMemFree(pidl); if (g_shell.tree) project_tree_open(g_shell.tree, path); output_panel_append(g_shell.output, L"Opened folder: "); output_panel_append(g_shell.output, path); output_panel_append(g_shell.output, L"\n"); } break; } case 1002: /* File > Open File */ { OPENFILENAMEW ofn = {0}; wchar_t file[MAX_PATH] = {0}; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hwnd; ofn.lpstrFile = file; ofn.nMaxFile = MAX_PATH; ofn.lpstrFilter = L"C/C++ Files\0*.c;*.cpp;*.h;*.hpp\0All Files\0*.*\0"; ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; if (GetOpenFileNameW(&ofn)) { on_open_file(file, NULL); } break; } case 1003: /* File > Save */ if (g_shell.doc.has_path) { document_save(&g_shell.doc); output_panel_append(g_shell.output, L"Saved.\n"); } break; case 1010: /* View > Toggle Sidebar */ g_shell.layout.sidebar_visible = !g_shell.layout.sidebar_visible; apply_layout(); break; case 1011: /* View > Toggle Bottom */ g_shell.layout.bottom_visible = !g_shell.layout.bottom_visible; apply_layout(); break; case 1020: /* Run > Build (Ctrl+B) */ if (g_shell.builder && !cmake_runner_is_running(g_shell.builder)) { output_panel_clear(g_shell.output); output_panel_append(g_shell.output, L"Building...\n"); g_shell.terminal_focused = false; ShowWindow(g_shell.wnd_output, SW_SHOW); ShowWindow(g_shell.wnd_terminal, SW_HIDE); if (cmake_runner_build(g_shell.builder) != SOV_OK) output_panel_append(g_shell.output, L"cmake not found — is it on PATH?\n"); } break; case 1021: /* View > Toggle Terminal/Output */ g_shell.terminal_focused = !g_shell.terminal_focused; ShowWindow(g_shell.wnd_output, g_shell.terminal_focused ? SW_HIDE : SW_SHOW); ShowWindow(g_shell.wnd_terminal, g_shell.terminal_focused ? SW_SHOW : SW_HIDE); break; case 9999: /* File > Exit */ PostMessageW(hwnd, WM_CLOSE, 0, 0); break; } return 0; } case WM_TIMER: { if (wp == TIMER_GIT) { if (git_repo_refresh(&g_shell.git) == SOV_OK && g_shell.status) { wchar_t branch_disp[136]; swprintf_s(branch_disp, 136, L"%s%s", g_shell.git.detached ? L"detached:" : L"", g_shell.git.branch); status_bar_set_branch(g_shell.status, branch_disp); InvalidateRect(g_shell.wnd_status, NULL, FALSE); } if (g_shell.builder) cmake_runner_tick(g_shell.builder); if (g_shell.lsp) lsp_client_tick(g_shell.lsp); } return 0; } case WM_DPICHANGED: { RECT *r = (RECT *)lp; SetWindowPos(hwnd, NULL, r->left, r->top, r->right - r->left, r->bottom - r->top, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_CLOSE: KillTimer(hwnd, TIMER_GIT); KillTimer(g_shell.wnd_editor, TIMER_CARET); KillTimer(g_shell.wnd_terminal, TIMER_PTY); if (g_shell.lsp) { lsp_client_shutdown(g_shell.lsp); g_shell.lsp = NULL; } if (g_shell.builder) { cmake_runner_destroy(g_shell.builder); g_shell.builder = NULL; } if (g_shell.editor) { editor_view_destroy(g_shell.editor); g_shell.editor = NULL; } if (g_shell.tree) { project_tree_destroy(g_shell.tree); g_shell.tree = NULL; } if (g_shell.terminal) { terminal_view_destroy(g_shell.terminal); g_shell.terminal = NULL; } if (g_shell.output) { output_panel_destroy(g_shell.output); g_shell.output = NULL; } if (g_shell.status) { status_bar_destroy(g_shell.status); g_shell.status = NULL; } document_destroy(&g_shell.doc); DestroyWindow(hwnd); return 0; case WM_DESTROY: app_request_quit(); PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wp, lp); } /* Build the native menu */ static void build_menu(HWND hwnd) { HMENU bar = CreateMenu(); HMENU file = CreatePopupMenu(); HMENU view = CreatePopupMenu(); HMENU run = CreatePopupMenu(); AppendMenuW(file, MF_STRING, 1001, L"Open &Folder…\tCtrl+Shift+O"); AppendMenuW(file, MF_STRING, 1002, L"Open &File…\tCtrl+O"); AppendMenuW(file, MF_SEPARATOR, 0, NULL); AppendMenuW(file, MF_STRING, 1003, L"&Save\tCtrl+S"); AppendMenuW(file, MF_SEPARATOR, 0, NULL); AppendMenuW(file, MF_STRING, 9999, L"E&xit"); AppendMenuW(view, MF_STRING, 1010, L"Toggle &Sidebar\tCtrl+\\"); AppendMenuW(view, MF_STRING, 1011, L"Toggle &Output\tCtrl+J"); AppendMenuW(view, MF_STRING, 1021, L"Toggle &Terminal\tCtrl+`"); AppendMenuW(run, MF_STRING, 1020, L"&Build\tCtrl+B"); AppendMenuW(bar, MF_POPUP, (UINT_PTR)file, L"&File"); AppendMenuW(bar, MF_POPUP, (UINT_PTR)view, L"&View"); AppendMenuW(bar, MF_POPUP, (UINT_PTR)run, L"&Run"); SetMenu(hwnd, bar); } SovResult shell_create(HINSTANCE hinstance, int width, int height) { /* Register panel class */ WNDCLASSEXW pc = {0}; pc.cbSize = sizeof(pc); pc.style = CS_HREDRAW | CS_VREDRAW; pc.lpfnWndProc = panel_proc; pc.hInstance = hinstance; pc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); pc.lpszClassName = PANEL_CLASS; RegisterClassExW(&pc); /* Register shell class */ WNDCLASSEXW sc = {0}; sc.cbSize = sizeof(sc); sc.style = CS_HREDRAW | CS_VREDRAW; sc.lpfnWndProc = shell_proc; sc.hInstance = hinstance; sc.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW); sc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); sc.lpszClassName = SHELL_CLASS; RegisterClassExW(&sc); g_shell.frame = CreateWindowExW( 0, SHELL_CLASS, SHELL_TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hinstance, NULL ); if (!g_shell.frame) return SOV_ERR_IO; build_menu(g_shell.frame); ShowWindow(g_shell.frame, SW_SHOW); UpdateWindow(g_shell.frame); return SOV_OK; }