I added a simple paint app (and sweden by c418 in bios beep sounds lmao)
This commit is contained in:
Chris 2026-02-08 14:47:41 +01:00
parent 071620b71e
commit e2cf01bb4a
28 changed files with 604 additions and 57 deletions

View file

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/cli_apps/sweden.o Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/paint.o Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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");
}

View file

@ -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();

View file

@ -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

View file

@ -11,6 +11,7 @@
#include "minesweeper.h"
#include "control_panel.h"
#include "about.h"
#include "paint.h"
#include <stdbool.h>
#include <stddef.h>
@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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

193
src/kernel/paint.c Normal file
View file

@ -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;
}
}
}

14
src/kernel/paint.h Normal file
View file

@ -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

View file

@ -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,12 +950,50 @@ 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)) {
start_menu_open = !start_menu_open;
@ -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;

View file

@ -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;