diff --git a/Makefile b/Makefile index 6e52cab..1090ea2 100644 --- a/Makefile +++ b/Makefile @@ -119,4 +119,4 @@ run: $(ISO_IMAGE) qemu-system-x86_64 -m 2G -serial stdio -cdrom $(ISO_IMAGE) -boot d \ -audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \ -netdev user,id=net0,hostfwd=udp::12345-:12345 -device e1000,netdev=net0 \ - -vga std -global VGA.xres=1920 -global VGA.yres=1080 + -vga std -global VGA.xres=1280 -global VGA.yres=800 diff --git a/brewos.iso b/brewos.iso index 1075e4b..98dfed3 100644 Binary files a/brewos.iso and b/brewos.iso differ diff --git a/build/brewos.elf b/build/brewos.elf index 5966207..baa994f 100755 Binary files a/build/brewos.elf and b/build/brewos.elf differ diff --git a/build/cli_apps/fs_commands.o b/build/cli_apps/fs_commands.o index 435ce84..7b3bc60 100644 Binary files a/build/cli_apps/fs_commands.o and b/build/cli_apps/fs_commands.o differ diff --git a/build/cli_apps/letithappen.o b/build/cli_apps/letithappen.o new file mode 100644 index 0000000..a23d4c3 Binary files /dev/null and b/build/cli_apps/letithappen.o differ diff --git a/build/cli_apps/sweden.o b/build/cli_apps/sweden.o new file mode 100644 index 0000000..cceabce Binary files /dev/null and b/build/cli_apps/sweden.o differ diff --git a/build/cmd.o b/build/cmd.o index 7617195..25ac2f0 100644 Binary files a/build/cmd.o and b/build/cmd.o differ diff --git a/build/editor.o b/build/editor.o index 7a6633f..e988bbf 100644 Binary files a/build/editor.o and b/build/editor.o differ diff --git a/build/explorer.o b/build/explorer.o index c85a9f9..ffb7db8 100644 Binary files a/build/explorer.o and b/build/explorer.o differ diff --git a/build/fat32.o b/build/fat32.o index ba9f85f..d0f30ef 100644 Binary files a/build/fat32.o and b/build/fat32.o differ diff --git a/build/markdown.o b/build/markdown.o index 0728afa..0b87531 100644 Binary files a/build/markdown.o and b/build/markdown.o differ diff --git a/build/paint.o b/build/paint.o new file mode 100644 index 0000000..1777a27 Binary files /dev/null and b/build/paint.o differ diff --git a/build/wm.o b/build/wm.o index fb1ede3..943ae1b 100644 Binary files a/build/wm.o and b/build/wm.o differ diff --git a/iso_root/brewos.elf b/iso_root/brewos.elf index 5966207..baa994f 100755 Binary files a/iso_root/brewos.elf and b/iso_root/brewos.elf differ diff --git a/iso_root/limine.cfg b/iso_root/limine.cfg index 9a3e839..66ca4be 100644 --- a/iso_root/limine.cfg +++ b/iso_root/limine.cfg @@ -9,6 +9,5 @@ TIMEOUT=3 # Path to the kernel to boot. boot:/// represents the partition on which limine.cfg is located. KERNEL_PATH=boot:///brewos.elf - # Set screen resolution to 1920x1080 - FRAMEBUFFER_WIDTH=1920 - FRAMEBUFFER_HEIGHT=1080 + #FRAMEBUFFER_WIDTH=1280 + #FRAMEBUFFER_HEIGHT=720 diff --git a/limine.cfg b/limine.cfg index 9a3e839..66ca4be 100644 --- a/limine.cfg +++ b/limine.cfg @@ -9,6 +9,5 @@ TIMEOUT=3 # Path to the kernel to boot. boot:/// represents the partition on which limine.cfg is located. KERNEL_PATH=boot:///brewos.elf - # Set screen resolution to 1920x1080 - FRAMEBUFFER_WIDTH=1920 - FRAMEBUFFER_HEIGHT=1080 + #FRAMEBUFFER_WIDTH=1280 + #FRAMEBUFFER_HEIGHT=720 diff --git a/src/kernel/cli_apps/cli_apps.h b/src/kernel/cli_apps/cli_apps.h index 25cae41..cf05359 100644 --- a/src/kernel/cli_apps/cli_apps.h +++ b/src/kernel/cli_apps/cli_apps.h @@ -53,4 +53,7 @@ void cli_cmd_pcilist(char *args); // Compiler void cli_cmd_cc(char *args); +// Music +void cli_cmd_minecraft(char *args); + #endif diff --git a/src/kernel/cli_apps/sweden.c b/src/kernel/cli_apps/sweden.c new file mode 100644 index 0000000..bc22ee8 --- /dev/null +++ b/src/kernel/cli_apps/sweden.c @@ -0,0 +1,60 @@ +#include "cli_utils.h" +#include "io.h" + +void play_note(int freq, int duration_ms) { + if (freq == 0) { + outb(0x61, inb(0x61) & 0xFC); + } else { + int div = 1193180 / freq; + outb(0x43, 0xB6); + outb(0x42, div & 0xFF); + outb(0x42, (div >> 8) & 0xFF); + outb(0x61, inb(0x61) | 0x03); + } + + + cli_delay(duration_ms * 300000); + + outb(0x61, inb(0x61) & 0xFC); + cli_delay(2000000); +} + +void cli_cmd_minecraft(char *args) { + (void)args; + cli_write("Playing: Sweden - C418 (What a masterpiece)\n"); + + int melody[] = { + 196, 330, 294, 0, // G3, E4, D4, rest + 196, 262, 247, 220, 196, 0, // G3, C4, B3, A3, G3, rest + + 196, 330, 294, 392, 330, 0, // G3, E4, D4, G4, E4, rest + + 440, 330, 294, 0, // A4, E4, D4, rest + 262, 247, 220, 196, 147, 0, // C4, B3, A3, G3, D3, rest + + 196, 330, 294, 0, // Return to G3, E4, D4 + 196, 262, 247, 220, 196 // Final resolution + }; + + int rhythm[] = { + 1000, 1000, 2000, 500, + 1000, 1000, 1000, 1000, 2000, 500, + + 1000, 1000, 1000, 1000, 2000, 500, + + 1000, 1000, 2000, 500, + 1000, 1000, 1000, 1000, 2000, 500, + + 1000, 1000, 2000, 500, + 1000, 1000, 1000, 1000, 3000 + }; + + int song_length = sizeof(melody) / sizeof(melody[0]); + + for (int i = 0; i < song_length; i++) { + play_note(melody[i], rhythm[i]); + } + + outb(0x61, inb(0x61) & 0xFC); + cli_write("Composition finished.\n"); +} \ No newline at end of file diff --git a/src/kernel/cmd.c b/src/kernel/cmd.c index 2ce3310..cb180d9 100644 --- a/src/kernel/cmd.c +++ b/src/kernel/cmd.c @@ -465,6 +465,8 @@ static const CommandEntry commands[] = { {"compc", cli_cmd_cc}, {"CC", cli_cmd_cc}, {"cc", cli_cmd_cc}, + {"sweden", cli_cmd_minecraft}, + {"SWEDEN", cli_cmd_minecraft}, {NULL, NULL} }; @@ -949,6 +951,7 @@ static void create_test_files(void) { fh = fat32_open("Desktop/Terminal.shortcut", "w"); if(fh) fat32_close(fh); fh = fat32_open("Desktop/About.shortcut", "w"); if(fh) fat32_close(fh); fh = fat32_open("Desktop/Recycle Bin.shortcut", "w"); if(fh) fat32_close(fh); + fh = fat32_open("Desktop/Paint.shortcut", "w"); if(fh) fat32_close(fh); // Always try to write README to ensure content exists fh = fat32_open("README.md", "w"); @@ -1057,6 +1060,9 @@ static void create_test_files(void) { fat32_write(fh, (void *)content, cmd_strlen(content)); fat32_close(fh); } + + + write_license_file(); fh = fat32_open("Documents/notes.txt", "w"); @@ -1127,9 +1133,21 @@ static void create_test_files(void) { } fat32_close(fh); } + + fh = fat32_open("Apps/DOOM.c", "w"); + if (fh) { + const char *content = + "int main(){\n" + " puts(\"To DOOM, or not to DOOM.\\n\");\n" + " puts(\"-Me\\n\");\n" + "}\n"; + fat32_write(fh, (void *)content, cmd_strlen(content)); + fat32_close(fh); + } } + void cmd_init(void) { fat32_init(); // Init FAT32 filesystem create_test_files(); diff --git a/src/kernel/editor.c b/src/kernel/editor.c index aa4f5e0..c28b837 100644 --- a/src/kernel/editor.c +++ b/src/kernel/editor.c @@ -253,7 +253,7 @@ static void editor_paint(Window *win) { // Draw filename and save button area at top of content draw_rect(offset_x, offset_y, content_width, 25, COLOR_GRAY); - draw_string(offset_x + 10, offset_y + 5, "File: ", COLOR_BLACK); + draw_string(offset_x + 10, offset_y + 5, "File", COLOR_BLACK); draw_string(offset_x + 55, offset_y + 5, open_filename, COLOR_BLACK); // Draw save button diff --git a/src/kernel/explorer.c b/src/kernel/explorer.c index fb68050..aba32e5 100644 --- a/src/kernel/explorer.c +++ b/src/kernel/explorer.c @@ -11,6 +11,7 @@ #include "minesweeper.h" #include "control_panel.h" #include "about.h" +#include "paint.h" #include #include @@ -33,6 +34,7 @@ Window win_explorer; #define DIALOG_REPLACE_MOVE_CONFIRM 5 #define DIALOG_CREATE_REPLACE_CONFIRM 6 #define DIALOG_INPUT_MAX 256 +#define DIALOG_RENAME 8 #define ACTION_RESTORE 108 #define DIALOG_ERROR 7 #define ACTION_CREATE_SHORTCUT 107 @@ -606,6 +608,7 @@ static int explorer_build_context_menu(ExplorerContextItem *items_out) { } items_out[count++] = (ExplorerContextItem){"Delete", 106, true, COLOR_RED}; + items_out[count++] = (ExplorerContextItem){"Rename", 111, true, COLOR_BLACK}; items_out[count++] = (ExplorerContextItem){"Create Shortcut", ACTION_CREATE_SHORTCUT, true, COLOR_BLACK}; if (is_dir) { @@ -794,6 +797,8 @@ static void explorer_open_target(const char *path) { win_markdown.visible = true; win_markdown.focused = true; win_markdown.z_index = max_z + 1; markdown_open_file(path); + } else if (explorer_str_ends_with(path, ".pnt")) { + paint_load(path); } else { win_editor.visible = true; win_editor.focused = true; win_editor.z_index = max_z + 1; @@ -905,6 +910,8 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, c else if (explorer_strcmp(filename, "Recycle Bin.shortcut") == 0) draw_recycle_bin_icon(x + 5, y + 5, ""); else if (explorer_strcmp(filename, "RecycleBin") == 0) draw_recycle_bin_icon(x + 5, y + 5, ""); else draw_icon(x + 5, y + 5, ""); + } else if (explorer_str_ends_with(filename, ".pnt")) { + draw_paint_icon(x - 15, y + 10, ""); } else { // Document icon - larger draw_rect(x + 12, y + 10, 20, 25, COLOR_WHITE); @@ -912,10 +919,15 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, c draw_rect(x + 12, y + 10, 2, 25, COLOR_BLACK); draw_rect(x + 30, y + 10, 2, 25, COLOR_BLACK); draw_rect(x + 12, y + 33, 20, 2, COLOR_BLACK); - // Lines on document - draw_rect(x + 15, y + 18, 14, 1, COLOR_DKGRAY); - draw_rect(x + 15, y + 23, 14, 1, COLOR_DKGRAY); - draw_rect(x + 15, y + 28, 14, 1, COLOR_DKGRAY); + + if (explorer_str_ends_with(filename, ".md")) { + draw_string(x + 14, y + 12, "MD", COLOR_BLACK); + } else { + // Lines on document + draw_rect(x + 15, y + 18, 14, 1, COLOR_DKGRAY); + draw_rect(x + 15, y + 23, 14, 1, COLOR_DKGRAY); + draw_rect(x + 15, y + 28, 14, 1, COLOR_DKGRAY); + } } } @@ -931,7 +943,7 @@ static void explorer_paint(Window *win) { // Draw path bar int path_height = 30; draw_bevel_rect(offset_x + 4, offset_y + 4, win->w - 16, path_height, true); - draw_string(offset_x + 10, offset_y + 10, "Path: ", COLOR_BLACK); + draw_string(offset_x + 10, offset_y + 10, "Path", COLOR_BLACK); draw_string(offset_x + 50, offset_y + 10, current_path, COLOR_BLACK); // Draw dropdown menu button (right-aligned, before back button) @@ -1010,7 +1022,7 @@ static void explorer_paint(Window *win) { // Input field draw_bevel_rect(dlg_x + 10, dlg_y + 35, 280, 20, false); draw_string(dlg_x + 15, dlg_y + 40, dialog_input, COLOR_BLACK); - draw_string(dlg_x + 15 + dialog_input_cursor * 8, dlg_y + 40, "|", COLOR_BLACK); + draw_rect(dlg_x + 15 + dialog_input_cursor * 8, dlg_y + 39, 2, 12, COLOR_BLACK); // Buttons draw_button(dlg_x + 50, dlg_y + 65, 80, 25, "Create", false); @@ -1029,7 +1041,7 @@ static void explorer_paint(Window *win) { // Input field draw_bevel_rect(dlg_x + 10, dlg_y + 35, 280, 20, false); draw_string(dlg_x + 15, dlg_y + 40, dialog_input, COLOR_BLACK); - draw_string(dlg_x + 15 + dialog_input_cursor * 8, dlg_y + 40, "|", COLOR_BLACK); + draw_rect(dlg_x + 15 + dialog_input_cursor * 8, dlg_y + 39, 2, 12, COLOR_BLACK); // Buttons draw_button(dlg_x + 50, dlg_y + 65, 80, 25, "Create", false); @@ -1123,6 +1135,21 @@ static void explorer_paint(Window *win) { // OK Button draw_button(dlg_x + 110, dlg_y + 70, 80, 25, "OK", false); + } else if (dialog_state == DIALOG_RENAME) { + int dlg_x = win->x + win->w / 2 - 150; + int dlg_y = win->y + win->h / 2 - 60; + + draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY); + draw_bevel_rect(dlg_x, dlg_y, 300, 110, true); + + draw_string(dlg_x + 10, dlg_y + 10, "Rename", COLOR_BLACK); + + draw_bevel_rect(dlg_x + 10, dlg_y + 35, 280, 20, false); + draw_string(dlg_x + 15, dlg_y + 40, dialog_input, COLOR_BLACK); + draw_rect(dlg_x + 15 + dialog_input_cursor * 8, dlg_y + 39, 2, 12, COLOR_BLACK); + + draw_button(dlg_x + 50, dlg_y + 65, 80, 25, "Rename", false); + draw_button(dlg_x + 170, dlg_y + 65, 80, 25, "Cancel", false); } // Draw context menu if visible @@ -1262,6 +1289,25 @@ static void explorer_handle_click(Window *win, int x, int y) { dialog_close(); return; } + } else if (dialog_state == DIALOG_RENAME) { + int dlg_x = win->w / 2 - 150; + int dlg_y = win->h / 2 - 60; + + if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 65 && y < dlg_y + 90) { + char new_path[256]; + explorer_strcpy(new_path, current_path); + if (new_path[explorer_strlen(new_path)-1] != '/') explorer_strcat(new_path, "/"); + explorer_strcat(new_path, dialog_input); + + if (fat32_rename(dialog_target_path, new_path)) explorer_refresh(); + dialog_close(); + return; + } + + if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 65 && y < dlg_y + 90) { + dialog_close(); + return; + } } // Handle dropdown menu clicks @@ -1377,17 +1423,27 @@ static void explorer_handle_key(Window *win, char c) { (void)win; // Handle dialog input - if (dialog_state == DIALOG_CREATE_FILE || dialog_state == DIALOG_CREATE_FOLDER) { + if (dialog_state == DIALOG_CREATE_FILE || dialog_state == DIALOG_CREATE_FOLDER || dialog_state == DIALOG_RENAME) { if (c == 27) { // ESC - close dialog dialog_close(); return; } else if (c == '\n') { // ENTER - confirm if (dialog_state == DIALOG_CREATE_FILE) { dialog_confirm_create_file(); - } else { + } else if (dialog_state == DIALOG_CREATE_FOLDER) { dialog_confirm_create_folder(); + } else if (dialog_state == DIALOG_RENAME) { + char new_path[256]; + explorer_strcpy(new_path, current_path); + if (new_path[explorer_strlen(new_path)-1] != '/') explorer_strcat(new_path, "/"); + explorer_strcat(new_path, dialog_input); + if (fat32_rename(dialog_target_path, new_path)) explorer_refresh(); + dialog_close(); } - return; + } else if (c == 19) { // LEFT arrow + if (dialog_input_cursor > 0) dialog_input_cursor--; + } else if (c == 20) { // RIGHT arrow + if (dialog_input_cursor < (int)explorer_strlen(dialog_input)) dialog_input_cursor++; } else if (c == 8 || c == 127) { // BACKSPACE if (dialog_input_cursor > 0) { dialog_input_cursor--; @@ -1396,7 +1452,6 @@ static void explorer_handle_key(Window *win, char c) { dialog_input[i] = dialog_input[i + 1]; } } - return; } else if (c >= 32 && c < 127) { // Printable character int len = explorer_strlen(dialog_input); if (len < DIALOG_INPUT_MAX - 1) { @@ -1407,8 +1462,8 @@ static void explorer_handle_key(Window *win, char c) { dialog_input[dialog_input_cursor] = c; dialog_input_cursor++; } - return; } + wm_mark_dirty(win->x, win->y, win->w, win->h); return; } @@ -1616,6 +1671,11 @@ static void explorer_handle_file_context_menu_click(Window *win, int x, int y) { explorer_clipboard_copy(full_path); } else if (clicked_action == 106) { // Delete dialog_open_delete_confirm(file_context_menu_item); + } else if (clicked_action == 111) { // Rename + dialog_state = DIALOG_RENAME; + explorer_strcpy(dialog_input, items[file_context_menu_item].name); + dialog_input_cursor = explorer_strlen(dialog_input); + explorer_strcpy(dialog_target_path, full_path); } else if (clicked_action == 110) { // Open with Text Editor win_editor.visible = true; win_editor.focused = true; int max_z = 0; diff --git a/src/kernel/fat32.c b/src/kernel/fat32.c index 5bee13f..96f0f7f 100644 --- a/src/kernel/fat32.c +++ b/src/kernel/fat32.c @@ -64,6 +64,13 @@ static bool fs_ends_with(const char *str, const char *suffix) { return fs_strcmp(str + str_len - suffix_len, suffix) == 0; } +static bool fs_starts_with(const char *str, const char *prefix) { + while (*prefix) { + if (*prefix++ != *str++) return false; + } + return true; +} + // Extract filename from path static void extract_filename(const char *path, char *filename) { int len = fs_strlen(path); @@ -526,6 +533,48 @@ bool fat32_exists(const char *path) { return find_file(path) != NULL; } +bool fat32_rename(const char *old_path, const char *new_path) { + FileEntry *entry = find_file(old_path); + if (!entry) return false; + if (find_file(new_path)) return false; // Destination exists + + int old_len = fs_strlen(old_path); + + // Update the entry itself and all children + for (int i = 0; i < MAX_FILES; i++) { + if (!files[i].used) continue; + + // 1. Update full_path if it matches or is a child + if (fs_strcmp(files[i].full_path, old_path) == 0) { + // This is the target + fs_strcpy(files[i].full_path, new_path); + extract_filename(new_path, files[i].filename); + extract_parent_path(new_path, files[i].parent_path); + } else if (fs_strlen(files[i].full_path) > old_len && + fs_starts_with(files[i].full_path, old_path) && + files[i].full_path[old_len] == '/') { + // This is a child path + char suffix[FAT32_MAX_PATH]; + fs_strcpy(suffix, files[i].full_path + old_len); + fs_strcpy(files[i].full_path, new_path); + fs_strcat(files[i].full_path, suffix); + } + + // 2. Update parent_path if it matches or is a child of the renamed folder + if (fs_strcmp(files[i].parent_path, old_path) == 0) { + fs_strcpy(files[i].parent_path, new_path); + } else if (fs_strlen(files[i].parent_path) > old_len && + fs_starts_with(files[i].parent_path, old_path) && + files[i].parent_path[old_len] == '/') { + char suffix[FAT32_MAX_PATH]; + fs_strcpy(suffix, files[i].parent_path + old_len); + fs_strcpy(files[i].parent_path, new_path); + fs_strcat(files[i].parent_path, suffix); + } + } + return true; +} + bool fat32_is_directory(const char *path) { FileEntry *entry = find_file(path); return entry && (entry->attributes & ATTR_DIRECTORY); diff --git a/src/kernel/fat32.h b/src/kernel/fat32.h index 87909d6..17d353d 100644 --- a/src/kernel/fat32.h +++ b/src/kernel/fat32.h @@ -114,6 +114,7 @@ bool fat32_mkdir(const char *path); bool fat32_rmdir(const char *path); bool fat32_delete(const char *path); bool fat32_exists(const char *path); +bool fat32_rename(const char *old_path, const char *new_path); bool fat32_is_directory(const char *path); // Listing diff --git a/src/kernel/markdown.c b/src/kernel/markdown.c index 7731610..19fbd55 100644 --- a/src/kernel/markdown.c +++ b/src/kernel/markdown.c @@ -276,7 +276,7 @@ static void md_paint(Window *win) { // Draw filename bar below title draw_rect(offset_x, offset_y, content_width, 20, COLOR_GRAY); - draw_string(offset_x + 4, offset_y + 4, "File: ", COLOR_BLACK); + draw_string(offset_x + 4, offset_y + 4, "File", COLOR_BLACK); draw_string(offset_x + 50, offset_y + 4, open_filename, COLOR_BLACK); // Draw scroll buttons on top right diff --git a/src/kernel/paint.c b/src/kernel/paint.c new file mode 100644 index 0000000..fa8427d --- /dev/null +++ b/src/kernel/paint.c @@ -0,0 +1,193 @@ +#include "paint.h" +#include "graphics.h" +#include "wm.h" +#include "memory_manager.h" +#include "fat32.h" + +#define CANVAS_W 300 +#define CANVAS_H 200 +#define PAINT_MAGIC 0x544E5042 // 'BPNT' + +Window win_paint; +static uint32_t *canvas_buffer = NULL; +static uint32_t current_color = COLOR_BLACK; +static int last_mx = -1; +static int last_my = -1; + +static void paint_paint(Window *win) { + // Background + draw_rect(win->x + 4, win->y + 24, win->w - 8, win->h - 28, COLOR_LTGRAY); + + // Toolbar area + draw_rect(win->x + 10, win->y + 30, 40, win->h - 40, COLOR_GRAY); + draw_bevel_rect(win->x + 10, win->y + 30, 40, win->h - 40, true); + + // Color Palette + uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE}; + for (int i = 0; i < 6; i++) { + int cy = win->y + 40 + (i * 25); + draw_rect(win->x + 15, cy, 30, 20, colors[i]); + draw_rect(win->x + 15, cy, 30, 1, COLOR_BLACK); + draw_rect(win->x + 15, cy, 1, 20, COLOR_BLACK); + draw_rect(win->x + 44, cy, 1, 20, COLOR_BLACK); + draw_rect(win->x + 15, cy + 19, 30, 1, COLOR_BLACK); + + // Highlight selected color + if (current_color == colors[i]) { + draw_rect(win->x + 13, cy - 2, 34, 2, COLOR_WHITE); + draw_rect(win->x + 13, cy + 20, 34, 2, COLOR_WHITE); + } + } + + // Toolbar Buttons + draw_button(win->x + 12, win->y + win->h - 65, 36, 20, "CLR", false); + draw_button(win->x + 12, win->y + win->h - 40, 36, 20, "SAV", false); + + // Canvas Area + int canvas_x = win->x + 60; + int canvas_y = win->y + 30; + draw_bevel_rect(canvas_x - 2, canvas_y - 2, CANVAS_W + 4, CANVAS_H + 4, true); + + if (canvas_buffer) { + for (int y = 0; y < CANVAS_H; y++) { + for (int x = 0; x < CANVAS_W; x++) { + uint32_t color = canvas_buffer[y * CANVAS_W + x]; + put_pixel(canvas_x + x, canvas_y + y, color); + } + } + } +} + +static void paint_put_brush(int cx, int cy) { + if (!canvas_buffer) return; + for (int dy = 0; dy < 2; dy++) { + for (int dx = 0; dx < 2; dx++) { + int px = cx + dx; + int py = cy + dy; + if (px >= 0 && px < CANVAS_W && py >= 0 && py < CANVAS_H) { + canvas_buffer[py * CANVAS_W + px] = current_color; + } + } + } + wm_mark_dirty(win_paint.x + 60 + cx, win_paint.y + 30 + cy, 2, 2); +} + +void paint_handle_mouse(int x, int y) { + int cx = x - 60; + int cy = y - 30; + + if (cx < 0 || cx >= CANVAS_W || cy < 0 || cy >= CANVAS_H) { + last_mx = -1; + return; + } + + if (last_mx == -1) { + paint_put_brush(cx, cy); + } else { + // Bresenham's line algorithm to fill gaps + int x0 = last_mx, y0 = last_my; + int x1 = cx, y1 = cy; + int dx = (x1 - x0 > 0) ? (x1 - x0) : (x0 - x1); + int dy = (y1 - y0 > 0) ? (y1 - y0) : (y0 - y1); + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + int err = dx - dy; + + while (1) { + paint_put_brush(x0, y0); + if (x0 == x1 && y0 == y1) break; + int e2 = 2 * err; + if (e2 > -dy) { err -= dy; x0 += sx; } + if (e2 < dx) { err += dx; y0 += sy; } + } + } + last_mx = cx; + last_my = cy; +} + +void paint_reset_last_pos(void) { + last_mx = -1; + last_my = -1; +} + +static void paint_save(const char *path) { + FAT32_FileHandle *fh = fat32_open(path, "w"); + if (fh) { + uint32_t header[3] = {PAINT_MAGIC, CANVAS_W, CANVAS_H}; + fat32_write(fh, header, sizeof(header)); + fat32_write(fh, canvas_buffer, CANVAS_W * CANVAS_H * sizeof(uint32_t)); + fat32_close(fh); + wm_show_message("Paint", "Image saved to Desktop."); + } +} + +void paint_load(const char *path) { + FAT32_FileHandle *fh = fat32_open(path, "r"); + if (fh) { + uint32_t header[3]; + if (fat32_read(fh, header, sizeof(header)) == sizeof(header)) { + if (header[0] == PAINT_MAGIC) { + fat32_read(fh, canvas_buffer, CANVAS_W * CANVAS_H * sizeof(uint32_t)); + win_paint.visible = true; + win_paint.focused = true; + } + } + fat32_close(fh); + } +} + +static void paint_click(Window *win, int x, int y) { + (void)win; + // Check Buttons + if (x >= 12 && x < 48) { + if (y >= win->h - 65 && y < win->h - 45) { + paint_reset(); + return; + } + if (y >= win->h - 40 && y < win->h - 20) { + paint_save("/Desktop/drawing.pnt"); + return; + } + } + + // Check Palette + if (x >= 15 && x < 45) { + for (int i = 0; i < 6; i++) { + int cy = 40 + (i * 25); + if (y >= cy && y < cy + 20) { + uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE}; + current_color = colors[i]; + return; + } + } + } + paint_handle_mouse(x, y); +} + +void paint_init(void) { + win_paint.title = "Paint"; + win_paint.x = 150; + win_paint.y = 100; + win_paint.w = 380; + win_paint.h = 260; + win_paint.visible = false; + win_paint.focused = false; + win_paint.z_index = 0; + win_paint.paint = paint_paint; + win_paint.handle_click = paint_click; + win_paint.handle_right_click = NULL; + win_paint.handle_key = NULL; + + if (!canvas_buffer) { + canvas_buffer = (uint32_t*)kmalloc(CANVAS_W * CANVAS_H * sizeof(uint32_t)); + paint_reset(); + } +} + +void paint_reset(void) { + if (canvas_buffer) { + for (int i = 0; i < CANVAS_W * CANVAS_H; i++) { + canvas_buffer[i] = COLOR_WHITE; + } + } +} \ No newline at end of file diff --git a/src/kernel/paint.h b/src/kernel/paint.h new file mode 100644 index 0000000..d54e390 --- /dev/null +++ b/src/kernel/paint.h @@ -0,0 +1,14 @@ +#ifndef PAINT_H +#define PAINT_H + +#include "wm.h" + +extern Window win_paint; + +void paint_init(void); +void paint_reset(void); +void paint_handle_mouse(int x, int y); +void paint_load(const char *path); +void paint_reset_last_pos(void); + +#endif \ No newline at end of file diff --git a/src/kernel/wm.c b/src/kernel/wm.c index c2162c9..6ed00b3 100644 --- a/src/kernel/wm.c +++ b/src/kernel/wm.c @@ -15,6 +15,7 @@ #include "minesweeper.h" #include "fat32.h" #include "memory_manager.h" +#include "paint.h" // --- State --- static int mx = 400, my = 300; // Mouse Pos @@ -29,6 +30,12 @@ static int desktop_menu_x = 0; static int desktop_menu_y = 0; static int desktop_menu_target_icon = -1; // -1 for background +// Desktop Dialog State +static int desktop_dialog_state = 0; // 0=None, 8=Rename +static char desktop_dialog_input[64]; +static int desktop_dialog_cursor = 0; +static int desktop_dialog_target = -1; + // Message Box static bool msg_box_visible = false; static char msg_box_title[64]; @@ -53,7 +60,7 @@ static int drag_icon_orig_x = 0; static int drag_icon_orig_y = 0; // Windows array for z-order management -static Window *all_windows[9]; +static Window *all_windows[10]; static int window_count = 0; // Redraw system @@ -81,8 +88,8 @@ static int desktop_icon_count = 0; // Desktop Settings bool desktop_snap_to_grid = true; bool desktop_auto_align = true; -int desktop_max_rows_per_col = 13; -int desktop_max_cols = 23; +int desktop_max_rows_per_col = 9; +int desktop_max_cols = 15; // Helper to check if string ends with suffix static bool str_ends_with(const char *str, const char *suffix) { @@ -561,6 +568,24 @@ void draw_recycle_bin_icon(int x, int y, const char *label) { draw_icon_label(x, y, label); } +void draw_paint_icon(int x, int y, const char *label) { + // Paint Palette Icon + draw_rect(x + 27, y + 2, 26, 20, COLOR_WHITE); + draw_rect(x + 27, y + 2, 26, 1, COLOR_BLACK); + draw_rect(x + 27, y + 2, 1, 20, COLOR_BLACK); + draw_rect(x + 52, y + 2, 1, 20, COLOR_BLACK); + draw_rect(x + 27, y + 22, 26, 1, COLOR_BLACK); + + // Color dots + draw_rect(x + 30, y + 5, 4, 4, COLOR_RED); + draw_rect(x + 38, y + 5, 4, 4, COLOR_APPLE_GREEN); + draw_rect(x + 46, y + 5, 4, 4, COLOR_APPLE_BLUE); + draw_rect(x + 30, y + 13, 4, 4, COLOR_APPLE_YELLOW); + draw_rect(x + 38, y + 13, 4, 4, COLOR_PURPLE); + + draw_icon_label(x, y, label); +} + void draw_window(Window *win) { if (!win->visible) return; @@ -702,8 +727,16 @@ void wm_paint(void) { else if (str_starts_with(icon->name, "About")) draw_about_icon(icon->x, icon->y, label); else if (str_starts_with(icon->name, "Recycle Bin")) draw_recycle_bin_icon(icon->x, icon->y, label); else if (str_starts_with(icon->name, "Explorer")) draw_folder_icon(icon->x, icon->y, label); + else if (str_starts_with(icon->name, "Paint")) draw_paint_icon(icon->x, icon->y, label); else draw_icon(icon->x, icon->y, label); - } else draw_document_icon(icon->x, icon->y, icon->name); + } else { + if (str_ends_with(icon->name, ".pnt")) draw_paint_icon(icon->x, icon->y, icon->name); + else if (str_ends_with(icon->name, ".md")) { + draw_document_icon(icon->x, icon->y, icon->name); + draw_string(icon->x + 31, icon->y + 2, "MD", COLOR_BLACK); + } + else draw_document_icon(icon->x, icon->y, icon->name); + } } // 3. Windows - sort by z-index and draw @@ -744,7 +777,7 @@ void wm_paint(void) { // 6. Start Menu (if open) if (start_menu_open) { - int menu_h = 230; + int menu_h = 250; int menu_y = sh - 28 - menu_h; draw_bevel_rect(0, menu_y, 120, menu_h, false); @@ -756,20 +789,21 @@ void wm_paint(void) { draw_string(8, menu_y + 88, "Calculator", COLOR_BLACK); draw_string(8, menu_y + 108, "Minesweeper", COLOR_BLACK); draw_string(8, menu_y + 128, "Control Panel", COLOR_BLACK); - draw_string(8, menu_y + 148, "About BrewOS", COLOR_BLACK); + draw_string(8, menu_y + 148, "Paint", COLOR_BLACK); + draw_string(8, menu_y + 168, "About BrewOS", COLOR_BLACK); // Separator line - draw_rect(5, menu_y + 165, 110, 1, COLOR_BLACK); + draw_rect(5, menu_y + 185, 110, 1, COLOR_BLACK); // Power options at bottom - draw_string(8, menu_y + 175, "Shutdown", COLOR_BLACK); - draw_string(8, menu_y + 195, "Restart", COLOR_BLACK); + draw_string(8, menu_y + 195, "Shutdown", COLOR_BLACK); + draw_string(8, menu_y + 215, "Restart", COLOR_BLACK); } // Desktop Context Menu if (desktop_menu_visible) { int menu_w = 140; - int menu_h = 100; // 4 items * 25 + int menu_h = 125; // 5 items * 25 draw_rect(desktop_menu_x, desktop_menu_y, menu_w, menu_h, COLOR_LTGRAY); draw_bevel_rect(desktop_menu_x, desktop_menu_y, menu_w, menu_h, true); @@ -788,6 +822,26 @@ void wm_paint(void) { draw_string(desktop_menu_x + 5, desktop_menu_y + 5 + item_h, "Copy", can_cut_copy ? COLOR_BLACK : COLOR_DKGRAY); draw_string(desktop_menu_x + 5, desktop_menu_y + 5 + item_h * 2, "Paste", can_paste ? COLOR_BLACK : COLOR_DKGRAY); draw_string(desktop_menu_x + 5, desktop_menu_y + 5 + item_h * 3, "Delete", can_cut_copy ? COLOR_RED : COLOR_DKGRAY); + draw_string(desktop_menu_x + 5, desktop_menu_y + 5 + item_h * 4, "Rename", can_cut_copy ? COLOR_BLACK : COLOR_DKGRAY); + } + + // Desktop Rename Dialog + if (desktop_dialog_state == 8) { + int dlg_w = 300; int dlg_h = 110; + int dlg_x = (sw - dlg_w) / 2; + int dlg_y = (sh - dlg_h) / 2; + + draw_rect(dlg_x - 5, dlg_y - 5, dlg_w + 10, dlg_h + 10, COLOR_LTGRAY); + draw_bevel_rect(dlg_x, dlg_y, dlg_w, dlg_h, true); + + draw_string(dlg_x + 10, dlg_y + 10, "Rename", COLOR_BLACK); + draw_bevel_rect(dlg_x + 10, dlg_y + 35, 280, 20, false); + draw_string(dlg_x + 15, dlg_y + 40, desktop_dialog_input, COLOR_BLACK); + // Cursor + draw_rect(dlg_x + 15 + desktop_dialog_cursor * 8, dlg_y + 39, 2, 12, COLOR_BLACK); + + draw_button(dlg_x + 50, dlg_y + 65, 80, 25, "Rename", false); + draw_button(dlg_x + 170, dlg_y + 65, 80, 25, "Cancel", false); } // Message Box @@ -853,7 +907,7 @@ void wm_handle_click(int x, int y) { // Handle Desktop Context Menu Click if (desktop_menu_visible) { int menu_w = 140; - int menu_h = 100; + int menu_h = 125; if (rect_contains(desktop_menu_x, desktop_menu_y, menu_w, menu_h, x, y)) { int rel_y = y - desktop_menu_y; int item = rel_y / 25; @@ -896,11 +950,49 @@ void wm_handle_click(int x, int y) { explorer_delete_recursive(path); refresh_desktop_icons(); } + else if (item == 4 && desktop_menu_target_icon != -1) { // Rename + desktop_dialog_state = 8; + desktop_dialog_target = desktop_menu_target_icon; + int k=0; while(desktop_icons[desktop_dialog_target].name[k]) { + desktop_dialog_input[k] = desktop_icons[desktop_dialog_target].name[k]; + k++; + } + desktop_dialog_input[k] = 0; + desktop_dialog_cursor = k; + } } desktop_menu_visible = false; force_redraw = true; return; } + + // Handle Desktop Dialog Clicks + if (desktop_dialog_state == 8) { + int dlg_x = (sw - 300) / 2; int dlg_y = (sh - 110) / 2; + if (rect_contains(dlg_x + 50, dlg_y + 65, 80, 25, x, y)) { // Rename + char old_path[128] = "/Desktop/"; + char new_path[128] = "/Desktop/"; + int p=9; int n=0; while(desktop_icons[desktop_dialog_target].name[n]) old_path[p++] = desktop_icons[desktop_dialog_target].name[n++]; old_path[p]=0; + p=9; n=0; while(desktop_dialog_input[n]) new_path[p++] = desktop_dialog_input[n++]; new_path[p]=0; + + if (fat32_rename(old_path, new_path)) refresh_desktop_icons(); + desktop_dialog_state = 0; + force_redraw = true; + return; + } + if (rect_contains(dlg_x + 170, dlg_y + 65, 80, 25, x, y)) { // Cancel + desktop_dialog_state = 0; + force_redraw = true; + return; + } + if (rect_contains(dlg_x + 10, dlg_y + 35, 280, 20, x, y)) { + desktop_dialog_cursor = (x - dlg_x - 15) / 8; + int len = 0; while(desktop_dialog_input[len]) len++; + if (desktop_dialog_cursor > len) desktop_dialog_cursor = len; + force_redraw = true; + return; + } + } // Check Start Button if (rect_contains(2, sh - 26, 90, 24, x, y)) { @@ -953,6 +1045,8 @@ void wm_handle_click(int x, int y) { notepad_reset(); } else if (topmost == &win_control_panel) { control_panel_reset(); + } else if (topmost == &win_paint) { + paint_reset(); } } else if (y < topmost->y + 24) { // Dragging the title bar @@ -1061,9 +1155,14 @@ void wm_handle_right_click(int x, int y) { // Mouse Down drag_start_x = mx; drag_start_y = my; + + if (win_paint.focused && win_paint.visible) { + paint_reset_last_pos(); + } + // Check Start Menu for potential drag if (start_menu_open) { - int menu_h = 230; + int menu_h = 250; int menu_y = sh - 28 - menu_h; if (rect_contains(0, menu_y, 120, menu_h, mx, my)) { if (my < menu_y + 25) start_menu_pending_app = "Explorer"; @@ -1073,8 +1172,9 @@ void wm_handle_right_click(int x, int y) { else if (my < menu_y + 105) start_menu_pending_app = "Calculator"; else if (my < menu_y + 125) start_menu_pending_app = "Minesweeper"; else if (my < menu_y + 145) start_menu_pending_app = "Control Panel"; - else if (my < menu_y + 165) start_menu_pending_app = "About"; - else if (my < menu_y + 185) start_menu_pending_app = "Shutdown"; + else if (my < menu_y + 165) start_menu_pending_app = "Paint"; + else if (my < menu_y + 185) start_menu_pending_app = "About"; + else if (my < menu_y + 205) start_menu_pending_app = "Shutdown"; else start_menu_pending_app = "Restart"; } else { wm_handle_click(mx, my); @@ -1084,6 +1184,11 @@ void wm_handle_right_click(int x, int y) { } } else if (right && !prev_right) { wm_handle_right_click(mx, my); + } else if (left && win_paint.focused && win_paint.visible && !is_dragging) { + int rel_x = mx - win_paint.x; + int rel_y = my - win_paint.y; + paint_handle_mouse(rel_x, rel_y); + force_redraw = true; } else if (left && is_dragging && drag_window) { drag_window->x = mx - drag_offset_x; drag_window->y = my - drag_offset_y; @@ -1164,6 +1269,8 @@ void wm_handle_right_click(int x, int y) { win_minesweeper.visible = true; win_minesweeper.focused = true; } else if (str_starts_with(start_menu_pending_app, "Control Panel")) { win_control_panel.visible = true; win_control_panel.focused = true; + } else if (str_starts_with(start_menu_pending_app, "Paint")) { + win_paint.visible = true; win_paint.focused = true; } else if (str_starts_with(start_menu_pending_app, "About")) { win_about.visible = true; win_about.focused = true; } else if (str_starts_with(start_menu_pending_app, "Shutdown")) { @@ -1184,6 +1291,7 @@ void wm_handle_right_click(int x, int y) { if (win_calculator.visible && win_calculator.focused) win_calculator.z_index = max_z + 1; if (win_minesweeper.visible && win_minesweeper.focused) win_minesweeper.z_index = max_z + 1; if (win_control_panel.visible && win_control_panel.focused) win_control_panel.z_index = max_z + 1; + if (win_paint.visible && win_paint.focused) win_paint.z_index = max_z + 1; if (win_about.visible && win_about.focused) win_about.z_index = max_z + 1; start_menu_open = false; @@ -1216,6 +1324,8 @@ void wm_handle_right_click(int x, int y) { explorer_reset(); } else if (str_ends_with(icon->name, "Recycle Bin.shortcut")) { explorer_open_directory("/RecycleBin"); + } else if (str_ends_with(icon->name, "Paint.shortcut")) { + win_paint.visible = true; win_paint.focused = true; } // Generic Shortcut Handling @@ -1241,34 +1351,40 @@ void wm_handle_right_click(int x, int y) { } } } - // Bring to front - int max_z = 0; - for (int w = 0; w < window_count; w++) { - if (all_windows[w]->z_index > max_z) max_z = all_windows[w]->z_index; - } - if (win_notepad.visible && win_notepad.focused) win_notepad.z_index = max_z + 1; - if (win_calculator.visible && win_calculator.focused) win_calculator.z_index = max_z + 1; - if (win_minesweeper.visible && win_minesweeper.focused) win_minesweeper.z_index = max_z + 1; - if (win_control_panel.visible && win_control_panel.focused) win_control_panel.z_index = max_z + 1; - if (win_cmd.visible && win_cmd.focused) win_cmd.z_index = max_z + 1; - if (win_about.visible && win_about.focused) win_about.z_index = max_z + 1; - if (win_explorer.visible && win_explorer.focused) win_explorer.z_index = max_z + 1; } else if (icon->type == 1) { // Folder char path[128] = "/Desktop/"; int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; explorer_open_directory(path); - - int max_z = 0; - for (int w = 0; w < window_count; w++) { - if (all_windows[w]->z_index > max_z) max_z = all_windows[w]->z_index; - } - win_explorer.z_index = max_z + 1; } else { // File - win_editor.visible = true; win_editor.focused = true; char path[128] = "/Desktop/"; int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; - editor_open_file(path); + + if (str_ends_with(icon->name, ".pnt")) { + paint_load(path); + } else if (str_ends_with(icon->name, ".md")) { + win_markdown.visible = true; win_markdown.focused = true; + markdown_open_file(path); + } else { + win_editor.visible = true; win_editor.focused = true; + editor_open_file(path); + } } + + // Bring launched window to front + int max_z = 0; + for (int w = 0; w < window_count; w++) { + if (all_windows[w]->z_index > max_z) max_z = all_windows[w]->z_index; + } + if (win_notepad.visible && win_notepad.focused) win_notepad.z_index = max_z + 1; + if (win_calculator.visible && win_calculator.focused) win_calculator.z_index = max_z + 1; + if (win_minesweeper.visible && win_minesweeper.focused) win_minesweeper.z_index = max_z + 1; + if (win_control_panel.visible && win_control_panel.focused) win_control_panel.z_index = max_z + 1; + if (win_cmd.visible && win_cmd.focused) win_cmd.z_index = max_z + 1; + if (win_paint.visible && win_paint.focused) win_paint.z_index = max_z + 1; + if (win_about.visible && win_about.focused) win_about.z_index = max_z + 1; + if (win_explorer.visible && win_explorer.focused) win_explorer.z_index = max_z + 1; + if (win_editor.visible && win_editor.focused) win_editor.z_index = max_z + 1; + if (win_markdown.visible && win_markdown.focused) win_markdown.z_index = max_z + 1; } pending_desktop_icon_click = -1; } @@ -1425,7 +1541,8 @@ void wm_handle_right_click(int x, int y) { if (desktop_snap_to_grid) { int col = (desktop_icons[dragged_idx].x - 20 + 40) / 80; int row = (desktop_icons[dragged_idx].y - 20 + 40) / 80; - if (col < 0) col = 0; if (row < 0) row = 0; + if (col < 0) col = 0; + if (row < 0) row = 0; desktop_icons[dragged_idx].x = 20 + col * 80; desktop_icons[dragged_idx].y = 20 + row * 80; } @@ -1480,6 +1597,35 @@ static volatile int key_head = 0; static volatile int key_tail = 0; static void wm_dispatch_key(char c) { + if (desktop_dialog_state == 8) { + int len = 0; while(desktop_dialog_input[len]) len++; + if (c == '\n') { + char old_path[128] = "/Desktop/"; + char new_path[128] = "/Desktop/"; + int p=9; int n=0; while(desktop_icons[desktop_dialog_target].name[n]) old_path[p++] = desktop_icons[desktop_dialog_target].name[n++]; old_path[p]=0; + p=9; n=0; while(desktop_dialog_input[n]) new_path[p++] = desktop_dialog_input[n++]; new_path[p]=0; + if (fat32_rename(old_path, new_path)) refresh_desktop_icons(); + desktop_dialog_state = 0; + } else if (c == 27) { + desktop_dialog_state = 0; + } else if (c == '\b' || c == 127) { + if (desktop_dialog_cursor > 0) { + for(int i = desktop_dialog_cursor - 1; i < len; i++) desktop_dialog_input[i] = desktop_dialog_input[i+1]; + desktop_dialog_cursor--; + } + } else if (c == 19) { // Left + if (desktop_dialog_cursor > 0) desktop_dialog_cursor--; + } else if (c == 20) { // Right + if (desktop_dialog_cursor < len) desktop_dialog_cursor++; + } else if (c >= 32 && c <= 126 && len < 63) { + for(int i = len; i >= desktop_dialog_cursor; i--) desktop_dialog_input[i+1] = desktop_dialog_input[i]; + desktop_dialog_input[desktop_dialog_cursor] = c; + desktop_dialog_cursor++; + } + force_redraw = true; + return; + } + Window *target = NULL; if (win_notepad.focused && win_notepad.visible) target = &win_notepad; else if (win_cmd.focused && win_cmd.visible) target = &win_cmd; @@ -1533,6 +1679,7 @@ void wm_init(void) { control_panel_init(); about_init(); minesweeper_init(); + paint_init(); refresh_desktop_icons(); @@ -1546,6 +1693,7 @@ void wm_init(void) { win_control_panel.z_index = 6; win_about.z_index = 7; win_minesweeper.z_index = 8; + win_paint.z_index = 9; // Register windows in array all_windows[0] = &win_notepad; @@ -1557,7 +1705,8 @@ void wm_init(void) { all_windows[6] = &win_control_panel; all_windows[7] = &win_about; all_windows[8] = &win_minesweeper; - window_count = 9; + all_windows[9] = &win_paint; + window_count = 10; // Only show Explorer and Notepad on desktop (Explorer on top) win_explorer.visible = false; diff --git a/src/kernel/wm.h b/src/kernel/wm.h index 059f1e0..e22ab0e 100644 --- a/src/kernel/wm.h +++ b/src/kernel/wm.h @@ -13,6 +13,7 @@ #define COLOR_LTGRAY 0xFFDFDFDF #define COLOR_DKGRAY 0xFF808080 #define COLOR_RED 0xFFFF0000 +#define COLOR_PURPLE 0xFF800080 #define COLOR_COFFEE 0xFF6B4423 #define COLOR_APPLE_RED 0xFFFF0000 #define COLOR_APPLE_ORANGE 0xFFFF7F00 @@ -72,6 +73,7 @@ void draw_minesweeper_icon(int x, int y, const char *label); void draw_control_panel_icon(int x, int y, const char *label); void draw_about_icon(int x, int y, const char *label); void draw_recycle_bin_icon(int x, int y, const char *label); +void draw_paint_icon(int x, int y, const char *label); // Desktop Settings extern bool desktop_snap_to_grid;