boredos_mirror/src/kernel/wm.c
2026-02-04 20:51:17 +01:00

746 lines
23 KiB
C

#include "wm.h"
#include "graphics.h"
#include "io.h"
#include "cmd.h"
#include "calculator.h"
#include "cli_apps/cli_utils.h"
#include "explorer.h"
#include "editor.h"
#include <stdbool.h>
#include <stddef.h>
#include "notepad.h"
#include "control_panel.h"
#include "about.h"
#include "minesweeper.h"
// --- State ---
static int mx = 400, my = 300; // Mouse Pos
static int prev_mx = 400, prev_my = 300; // Previous mouse position
static bool start_menu_open = false;
// Dragging State
static bool is_dragging = false;
static Window *drag_window = NULL;
static int drag_offset_x = 0;
static int drag_offset_y = 0;
// Windows array for z-order management
static Window *all_windows[8];
static int window_count = 0;
// Redraw system
static bool force_redraw = true; // Force full redraw on next tick
static uint32_t timer_ticks = 0;
// Cursor state
static bool cursor_visible = true;
static int last_cursor_x = 400;
static int last_cursor_y = 300;
// --- Drawing Helpers ---
// Draw a bevelled box (Win 3.1 style)
void draw_bevel_rect(int x, int y, int w, int h, bool sunken) {
draw_rect(x, y, w, h, COLOR_GRAY);
uint32_t top_left = sunken ? COLOR_DKGRAY : COLOR_WHITE;
uint32_t bot_right = sunken ? COLOR_WHITE : COLOR_DKGRAY;
// Top
draw_rect(x, y, w, 1, top_left);
// Left
draw_rect(x, y, 1, h, top_left);
// Bottom
draw_rect(x, y + h - 1, w, 1, bot_right);
// Right
draw_rect(x + w - 1, y, 1, h, bot_right);
}
void draw_button(int x, int y, int w, int h, const char *text, bool pressed) {
draw_bevel_rect(x, y, w, h, pressed);
// Center Text
int len = 0; while(text[len]) len++;
int tx = x + (w - (len * 8)) / 2;
int ty = y + (h - 8) / 2;
if (pressed) { tx++; ty++; }
draw_string(tx, ty, text, COLOR_BLACK);
}
void draw_icon(int x, int y, const char *label) {
// Simple "File" Icon
draw_rect(x + 10, y, 20, 25, COLOR_WHITE);
draw_rect(x + 10, y, 20, 1, COLOR_BLACK);
draw_rect(x + 10, y, 1, 25, COLOR_BLACK);
draw_rect(x + 30, y, 1, 25, COLOR_BLACK);
draw_rect(x + 10, y + 25, 21, 1, COLOR_BLACK);
// Label
draw_string(x, y + 30, label, COLOR_WHITE);
}
void draw_folder_icon(int x, int y, const char *label) {
// Folder icon (yellow folder)
// Folder tab
draw_rect(x + 5, y, 15, 6, COLOR_LTGRAY);
draw_rect(x + 5, y, 15, 1, COLOR_BLACK);
draw_rect(x + 5, y, 1, 6, COLOR_BLACK);
draw_rect(x + 19, y, 1, 6, COLOR_BLACK);
// Folder body
draw_rect(x + 5, y + 6, 25, 15, COLOR_LTGRAY);
draw_rect(x + 5, y + 6, 25, 1, COLOR_BLACK);
draw_rect(x + 5, y + 6, 1, 15, COLOR_BLACK);
draw_rect(x + 29, y + 6, 1, 15, COLOR_BLACK);
draw_rect(x + 5, y + 20, 25, 1, COLOR_BLACK);
// Label
draw_string(x, y + 30, label, COLOR_WHITE);
}
void draw_document_icon(int x, int y, const char *label) {
// Document icon (white paper with lines)
draw_rect(x + 10, y, 20, 25, COLOR_WHITE);
draw_rect(x + 10, y, 20, 1, COLOR_BLACK);
draw_rect(x + 10, y, 1, 25, COLOR_BLACK);
draw_rect(x + 30, y, 1, 25, COLOR_BLACK);
draw_rect(x + 10, y + 25, 21, 1, COLOR_BLACK);
// Lines on document
draw_rect(x + 14, y + 8, 12, 1, COLOR_BLACK);
draw_rect(x + 14, y + 12, 12, 1, COLOR_BLACK);
draw_rect(x + 14, y + 16, 12, 1, COLOR_BLACK);
// Label
draw_string(x, y + 30, label, COLOR_WHITE);
}
void draw_window(Window *win) {
if (!win->visible) return;
// Main Body
draw_bevel_rect(win->x, win->y, win->w, win->h, false);
// Title Bar
uint32_t title_color = win->focused ? COLOR_BLUE : COLOR_DKGRAY;
draw_rect(win->x + 3, win->y + 3, win->w - 6, 18, title_color);
draw_string(win->x + 8, win->y + 8, win->title, COLOR_WHITE);
// Close Button (X)
draw_button(win->x + win->w - 20, win->y + 5, 14, 14, "X", false);
// Client Area
draw_rect(win->x + 4, win->y + 24, win->w - 8, win->h - 28, COLOR_WHITE);
if (win->paint) {
win->paint(win);
}
}
// Draw Mouse Cursor (Simple Arrow)
void draw_cursor(int x, int y) {
// 0 = Transparent (skip), 1 = Black, 2 = White
static const uint8_t cursor_bitmap[10][10] = {
{1,1,0,0,0,0,0,0,0,0},
{1,2,1,0,0,0,0,0,0,0},
{1,2,2,1,0,0,0,0,0,0},
{1,2,2,2,1,0,0,0,0,0},
{1,2,2,2,2,1,0,0,0,0},
{1,2,2,2,2,2,1,0,0,0},
{1,2,2,1,1,1,1,0,0,0},
{1,1,1,0,1,2,1,0,0,0},
{0,0,0,0,0,1,2,1,0,0},
{0,0,0,0,0,0,1,0,0,0}
};
for (int r = 0; r < 10; r++) {
for (int c = 0; c < 10; c++) {
uint8_t p = cursor_bitmap[r][c];
if (p == 1) put_pixel(x + c, y + r, COLOR_BLACK);
else if (p == 2) put_pixel(x + c, y + r, COLOR_WHITE);
}
}
}
// Erase cursor by redrawing the background in that area
static void erase_cursor(int x, int y) {
int sw = get_screen_width();
int sh = get_screen_height();
// Clamp to screen
int x1 = x < 0 ? 0 : x;
int y1 = y < 0 ? 0 : y;
int x2 = x + 10 > sw ? sw : x + 10;
int y2 = y + 10 > sh ? sh : y + 10;
int w = x2 - x1;
int h = y2 - y1;
// Check what's underneath the cursor and redraw it
if (y1 < sh - 28) {
// Desktop or window area - draw teal background
draw_rect(x1, y1, w, h, COLOR_TEAL);
} else {
// Taskbar area - draw gray background
draw_rect(x1, y1, w, h, COLOR_GRAY);
}
}
// --- Clock ---
static uint8_t rtc_read(uint8_t reg) {
outb(0x70, reg);
return inb(0x71);
}
static void draw_clock(int x, int y) {
// Wait for update in progress
while (rtc_read(0x0A) & 0x80);
uint8_t s = rtc_read(0x00);
uint8_t m = rtc_read(0x02);
uint8_t h = rtc_read(0x04);
uint8_t b = rtc_read(0x0B);
if (!(b & 0x04)) {
s = (s & 0x0F) + ((s >> 4) * 10);
m = (m & 0x0F) + ((m >> 4) * 10);
h = (h & 0x0F) + ((h >> 4) * 10);
}
char buf[9];
buf[0] = '0' + (h / 10);
buf[1] = '0' + (h % 10);
buf[2] = ':';
buf[3] = '0' + (m / 10);
buf[4] = '0' + (m % 10);
buf[5] = ':';
buf[6] = '0' + (s / 10);
buf[7] = '0' + (s % 10);
buf[8] = 0;
draw_string(x, y, buf, COLOR_BLACK);
}
// --- Main Paint Function ---
void wm_paint(void) {
int sw = get_screen_width();
int sh = get_screen_height();
// First, erase the old cursor (before redrawing anything)
if (cursor_visible) {
erase_cursor(last_cursor_x, last_cursor_y);
}
// 1. Desktop
draw_desktop_background();
draw_folder_icon(20, 20, "Explorer");
draw_document_icon(20, 80, "Notepad");
// 3. Windows - sort by z-index and draw
// Simple bubble sort by z-index (5 windows max)
Window *sorted_windows[6];
for (int i = 0; i < window_count; i++) {
sorted_windows[i] = all_windows[i];
}
for (int i = 0; i < window_count - 1; i++) {
for (int j = 0; j < window_count - i - 1; j++) {
if (sorted_windows[j]->z_index > sorted_windows[j + 1]->z_index) {
Window *temp = sorted_windows[j];
sorted_windows[j] = sorted_windows[j + 1];
sorted_windows[j + 1] = temp;
}
}
}
// Draw windows in z-order (lowest first)
for (int i = 0; i < window_count; i++) {
draw_window(sorted_windows[i]);
}
// 4. Taskbar
draw_rect(0, sh - 28, sw, 28, COLOR_GRAY);
draw_rect(0, sh - 28, sw, 2, COLOR_WHITE); // Top highlight
// 5. Start Button
draw_button(2, sh - 26, 60, 24, "Start", start_menu_open);
// Clock
draw_clock(sw - 80, sh - 20);
// 6. Start Menu (if open)
if (start_menu_open) {
int menu_h = 230;
int menu_y = sh - 28 - menu_h;
draw_bevel_rect(0, menu_y, 120, menu_h, false);
// Items
draw_string(8, menu_y + 8, "Explorer", COLOR_BLACK);
draw_string(8, menu_y + 28, "Notepad", COLOR_BLACK);
draw_string(8, menu_y + 48, "Editor", COLOR_BLACK);
draw_string(8, menu_y + 68, "CMD", COLOR_BLACK);
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);
// Separator line
draw_rect(5, menu_y + 165, 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);
}
// 7. Mouse cursor (draw last so it's on top)
draw_cursor(mx, my);
last_cursor_x = mx;
last_cursor_y = my;
// Flip the buffer - display the rendered frame atomically
graphics_flip_buffer();
}
// --- Input Handling ---
bool rect_contains(int x, int y, int w, int h, int px, int py) {
return px >= x && px < x + w && py >= y && py < y + h;
}
void wm_handle_click(int x, int y) {
int sh = get_screen_height();
// Check Start Button
if (rect_contains(2, sh - 26, 60, 24, x, y)) {
start_menu_open = !start_menu_open;
force_redraw = true;
return;
}
// Check Start Menu Items
if (start_menu_open) {
int menu_h = 230;
int menu_y = sh - 28 - menu_h;
if (rect_contains(0, menu_y, 120, menu_h, x, y)) {
// Clear focus from all windows first
for (int i = 0; i < window_count; i++) {
all_windows[i]->focused = false;
}
// Find which item was clicked
if (y < menu_y + 25) { // Explorer
win_explorer.visible = true;
win_explorer.focused = true;
// Bring to front
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_explorer.z_index = max_z + 1;
start_menu_open = false;
} else if (y < menu_y + 45) { // Notepad
win_notepad.visible = true;
win_notepad.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_notepad.z_index = max_z + 1;
start_menu_open = false;
} else if (y < menu_y + 65) { // Editor
win_editor.visible = true;
win_editor.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_editor.z_index = max_z + 1;
start_menu_open = false;
} else if (y < menu_y + 85) { // CMD
win_cmd.visible = true;
win_cmd.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_cmd.z_index = max_z + 1;
start_menu_open = false;
} else if (y < menu_y + 105) { // Calculator
win_calculator.visible = true;
win_calculator.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_calculator.z_index = max_z + 1;
start_menu_open = false;
} else if (y < menu_y + 125) { // Minesweeper
win_minesweeper.visible = true;
win_minesweeper.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_minesweeper.z_index = max_z + 1;
start_menu_open = false;
} else if (y < menu_y + 145) { // Control Panel
win_control_panel.visible = true;
win_control_panel.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_control_panel.z_index = max_z + 1;
start_menu_open = false;
} else if (y < menu_y + 165) { // About BrewOS
win_about.visible = true;
win_about.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_about.z_index = max_z + 1;
start_menu_open = false;
} else if (y < menu_y + 185) { // Shutdown
cli_cmd_shutdown(NULL);
start_menu_open = false;
} else { // Restart
cli_cmd_reboot(NULL);
start_menu_open = false;
}
force_redraw = true;
return;
}
}
// Find topmost window at click location
Window *topmost = NULL;
int topmost_z = -1;
for (int i = 0; i < window_count; i++) {
Window *win = all_windows[i];
if (win->visible && rect_contains(win->x, win->y, win->w, win->h, x, y)) {
if (win->z_index > topmost_z) {
topmost = win;
topmost_z = win->z_index;
}
}
}
// If a window was clicked
if (topmost != NULL) {
// Clear focus from all windows
for (int i = 0; i < window_count; i++) {
all_windows[i]->focused = false;
}
// Bring it to front
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
topmost->z_index = max_z + 1;
topmost->focused = true;
// Check close button
if (rect_contains(topmost->x + topmost->w - 20, topmost->y + 5, 14, 14, x, y)) {
topmost->visible = false;
// Reset window state on close
if (topmost == &win_explorer) {
explorer_reset();
} else if (topmost == &win_notepad) {
notepad_reset();
} else if (topmost == &win_control_panel) {
control_panel_reset();
}
} else if (y < topmost->y + 24) {
// Dragging the title bar
is_dragging = true;
drag_window = topmost;
drag_offset_x = x - topmost->x;
drag_offset_y = y - topmost->y;
} else {
// Content click
if (topmost->handle_click) {
topmost->handle_click(topmost, x - topmost->x, y - topmost->y);
}
}
} else {
// No window clicked - check desktop icons
// Clear focus from all windows first
for (int i = 0; i < window_count; i++) {
all_windows[i]->focused = false;
}
if (rect_contains(20, 20, 40, 40, x, y)) {
// Explorer icon
win_explorer.visible = true;
win_explorer.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_explorer.z_index = max_z + 1;
} else if (rect_contains(20, 80, 40, 40, x, y)) {
// Notepad icon
win_notepad.visible = true;
win_notepad.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_notepad.z_index = max_z + 1;
} else if (rect_contains(20, 140, 40, 40, x, y)) {
win_calculator.visible = true;
win_calculator.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_calculator.z_index = max_z + 1;
} else if (rect_contains(20, 200, 40, 40, x, y)) {
win_explorer.visible = true;
win_explorer.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_explorer.z_index = max_z + 1;
} else if (rect_contains(20, 260, 40, 40, x, y)) {
win_editor.visible = true;
win_editor.focused = true;
int max_z = 0;
for (int i = 0; i < window_count; i++) {
if (all_windows[i]->z_index > max_z) {
max_z = all_windows[i]->z_index;
}
}
win_editor.z_index = max_z + 1;
}
}
// Close start menu if clicked elsewhere
if (start_menu_open) {
start_menu_open = false;
}
force_redraw = true;
}
// Handle right click (context menu or special actions)
void wm_handle_right_click(int x, int y) {
int sh = get_screen_height();
// Find topmost window at click location
Window *topmost = NULL;
int topmost_z = -1;
for (int i = 0; i < window_count; i++) {
Window *win = all_windows[i];
if (win->visible && rect_contains(win->x, win->y, win->w, win->h, x, y)) {
if (win->z_index > topmost_z) {
topmost = win;
topmost_z = win->z_index;
}
}
}
// If a window was clicked
if (topmost != NULL) {
// Don't process close button or title bar for right click
if (y >= topmost->y + 24) {
// Content right click
if (topmost->handle_right_click) {
topmost->handle_right_click(topmost, x - topmost->x, y - topmost->y);
}
}
}
force_redraw = true;
}void wm_handle_mouse(int dx, int dy, uint8_t buttons) {
int sw = get_screen_width();
int sh = get_screen_height();
prev_mx = mx;
prev_my = my;
mx += dx;
my += dy;
if (mx < 0) mx = 0;
if (my < 0) my = 0;
if (mx >= sw) mx = sw - 1;
if (my >= sh) my = sh - 1;
static bool prev_left = false;
static bool prev_right = false;
bool left = buttons & 0x01;
bool right = buttons & 0x02;
if (left && !prev_left) {
wm_handle_click(mx, my);
} else if (right && !prev_right) {
wm_handle_right_click(mx, my);
} else if (left && is_dragging && drag_window) {
drag_window->x = mx - drag_offset_x;
drag_window->y = my - drag_offset_y;
// Mark for full redraw since window moved
force_redraw = true;
} else if (left && !is_dragging && (dx != 0 || dy != 0)) {
// Mouse is moving while left button is held, but window isn't being dragged
force_redraw = true;
} else if (!left && is_dragging) {
is_dragging = false;
drag_window = NULL;
// Mark for full redraw since dragging ended
force_redraw = true;
}
prev_left = left;
prev_right = right;
if (prev_mx != mx || prev_my != my) {
// Cursor moved - just mark dirty cursor areas
wm_mark_dirty(prev_mx, prev_my, 10, 10);
wm_mark_dirty(mx, my, 10, 10);
}
prev_left = left;
}
void wm_handle_key(char c) {
Window *target = NULL;
if (win_notepad.focused && win_notepad.visible) target = &win_notepad;
else if (win_cmd.focused && win_cmd.visible) target = &win_cmd;
else if (win_calculator.focused && win_calculator.visible) target = &win_calculator;
else if (win_explorer.focused && win_explorer.visible) target = &win_explorer;
else if (win_editor.focused && win_editor.visible) target = &win_editor;
else if (win_control_panel.focused && win_control_panel.visible) target = &win_control_panel;
if (!target) return;
if (target->handle_key) {
target->handle_key(target, c);
}
// Mark window as needing redraw on next timer tick
wm_mark_dirty(target->x, target->y, target->w, target->h);
}
void wm_mark_dirty(int x, int y, int w, int h) {
graphics_mark_dirty(x, y, w, h);
}
void wm_refresh(void) {
force_redraw = true;
}
void wm_init(void) {
notepad_init();
cmd_init();
calculator_init();
explorer_init();
editor_init();
control_panel_init();
about_init();
minesweeper_init();
// Initialize z-indices
win_notepad.z_index = 0;
win_cmd.z_index = 1;
win_calculator.z_index = 2;
win_explorer.z_index = 3;
win_editor.z_index = 4;
win_control_panel.z_index = 5;
win_about.z_index = 6;
win_minesweeper.z_index = 7;
// Register windows in array
all_windows[0] = &win_notepad;
all_windows[1] = &win_cmd;
all_windows[2] = &win_calculator;
all_windows[3] = &win_explorer;
all_windows[4] = &win_editor;
all_windows[5] = &win_control_panel;
all_windows[6] = &win_about;
all_windows[7] = &win_minesweeper;
window_count = 8;
// Only show Explorer and Notepad on desktop (Explorer on top)
win_explorer.visible = false;
win_explorer.focused = false;
win_explorer.z_index = 10;
win_notepad.visible = false;
win_notepad.focused = false;
win_notepad.z_index = 9;
// Rest are hidden initially
win_cmd.visible = false;
win_calculator.visible = false;
win_editor.visible = false;
win_control_panel.visible = false;
win_about.visible = false;
win_minesweeper.visible = false;
force_redraw = true;
}
// Called by timer interrupt ~60Hz
void wm_timer_tick(void) {
timer_ticks++;
// Only redraw if there are dirty areas (clock updates at most every second, cursor rarely moves in timer only)
// Most of the time, nothing changes between ticks
static uint8_t last_second = 0xFF;
outb(0x70, 0x00);
uint8_t current_sec = inb(0x71);
if (current_sec != last_second) {
last_second = current_sec;
int sw = get_screen_width();
int sh = get_screen_height();
// Mark clock area + a bit of buffer
wm_mark_dirty(sw - 90, sh - 30, 90, 20);
}
// If force_redraw is set, do a full redraw
if (force_redraw) {
graphics_mark_screen_dirty();
force_redraw = false;
}
// Perform redraw if there are dirty areas
DirtyRect dirty = graphics_get_dirty_rect();
if (dirty.active) {
wm_paint();
graphics_clear_dirty();
}
}