mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
Update 1.03
New feature(s): - Markdown reader (Automatically opens when opening .MD files, right click a .MD file to edit. Bug fixes: -N/A
This commit is contained in:
parent
a852075701
commit
d9fc8fbeda
22 changed files with 904 additions and 132 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# Brew OS 1.01 Alpha
|
||||
# Brew OS 1.03 Pre-Alpha
|
||||
|
||||
## Brewkernel is now BrewOS!
|
||||
Brewkernel will from now on be deprecated as it's core became too messy. I have built a less bloated kernel and wrote a DE above it, which is why it is now an OS instead of a kernel (in my opinion).
|
||||
|
|
|
|||
BIN
brewos.iso
BIN
brewos.iso
Binary file not shown.
BIN
build/about.o
BIN
build/about.o
Binary file not shown.
BIN
build/brewos.elf
BIN
build/brewos.elf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/cmd.o
BIN
build/cmd.o
Binary file not shown.
BIN
build/editor.o
BIN
build/editor.o
Binary file not shown.
BIN
build/explorer.o
BIN
build/explorer.o
Binary file not shown.
BIN
build/markdown.o
Normal file
BIN
build/markdown.o
Normal file
Binary file not shown.
BIN
build/wm.o
BIN
build/wm.o
Binary file not shown.
Binary file not shown.
|
|
@ -35,7 +35,8 @@ static void about_paint(Window *win) {
|
|||
|
||||
// Version info
|
||||
draw_string(offset_x, offset_y + 105, "BrewOS", COLOR_BLACK);
|
||||
draw_string(offset_x, offset_y + 120, "Version 1.0", COLOR_BLACK);
|
||||
draw_string(offset_x, offset_y + 120, "BrewOS Version 1.03", COLOR_BLACK);
|
||||
draw_string(offset_x, offset_y + 135, "Kernel Version 2.0.3", COLOR_BLACK);
|
||||
|
||||
// Copyright
|
||||
draw_string(offset_x, offset_y + 150, "(C) 2026 boreddevnl.", COLOR_BLACK);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
void cli_cmd_brewver(char *args) {
|
||||
(void)args;
|
||||
cli_write("BrewOS v1.02 Alpha\n");
|
||||
cli_write("BrewOS Kernel V2.0.2 Pre-Alpha\n");
|
||||
cli_write("BrewOS v1.03 Alpha\n");
|
||||
cli_write("BrewOS Kernel V2.0.3 Pre-Alpha\n");
|
||||
}
|
||||
|
|
|
|||
159
src/kernel/cmd.c
159
src/kernel/cmd.c
|
|
@ -624,86 +624,95 @@ static void create_test_files(void) {
|
|||
FAT32_FileHandle *fh = fat32_open("README.md", "w");
|
||||
if (fh) {
|
||||
const char *content =
|
||||
"BREW OS 1.01 ALPHA\n"
|
||||
"==================\n\n"
|
||||
"BREWKERNEL IS NOW BREWOS!\n\n"
|
||||
"Brewkernel will from now on be deprecated as its core became too messy.\n"
|
||||
"I have built a less bloated kernel and wrote a DE above it, which is why\n"
|
||||
"it is now an OS instead of a kernel.\n\n"
|
||||
"# Brew OS 1.01 Alpha\n\n"
|
||||
"## Brewkernel is now BrewOS!\n"
|
||||
"Brewkernel will from now on be deprecated as it's core became too messy. I have built a less bloated kernel and wrote a DE above it, which is why it is now an OS instead of a kernel (in my opinion).\n\n"
|
||||
"Brew Kernel is a simple x86_64 hobbyist operating system.\n"
|
||||
"It features a DE (and WM), a FAT32 filesystem, customizable UI and much much more!\n"
|
||||
"ramdisk-like filesystem.\n\n"
|
||||
"FEATURES\n"
|
||||
"--------\n"
|
||||
"* Brew WM (Window Manager)\n"
|
||||
"* FAT32 Filesystem\n"
|
||||
"* 64-bit long mode support\n"
|
||||
"* Multiboot2 compliant\n"
|
||||
"* Text editor and file explorer\n"
|
||||
"* IDT (Interrupt Descriptor Table)\n"
|
||||
"* Ability to run on actual x86_64 hardware\n"
|
||||
"* Command-line interface (CLI)\n\n"
|
||||
"PREREQUISITES\n"
|
||||
"-------------\n"
|
||||
"It features a DE (and WM), a FAT32 filesystem, customizable UI and much much more!\n\n"
|
||||
"## Features\n"
|
||||
"- Brew WM\n"
|
||||
"- Fat 32 FS\n"
|
||||
"- 64-bit long mode support\n"
|
||||
"- Multiboot2 compliant\n"
|
||||
"- Text editor\n"
|
||||
"- IDT\n"
|
||||
"- Ability to run on actual x86_64 hardware\n"
|
||||
"- CLI\n\n"
|
||||
"## Prerequisites\n\n"
|
||||
"To build BrewOS, you'll need the following tools installed:\n\n"
|
||||
"* x86_64 ELF Toolchain (x86_64-elf-gcc, x86_64-elf-ld)\n"
|
||||
"* NASM (Netwide Assembler)\n"
|
||||
"* xorriso (for creating bootable ISO images)\n"
|
||||
"* QEMU (optional, for testing in emulator)\n\n"
|
||||
"On macOS, install via Homebrew:\n"
|
||||
" brew install x86_64-elf-binutils x86_64-elf-gcc nasm xorriso qemu\n\n"
|
||||
"BUILDING\n"
|
||||
"--------\n"
|
||||
"Simply run 'make' from the project root:\n\n"
|
||||
" make\n\n"
|
||||
"- **x86_64 ELF Toolchain**: `x86_64-elf-gcc`, `x86_64-elf-ld`\n"
|
||||
"- **NASM**: Netwide Assembler for compiling assembly code\n"
|
||||
"- **xorriso**: For creating bootable ISO images\n"
|
||||
"- **QEMU** (optional): For testing the kernel in an emulator\n\n"
|
||||
"On macOS, you can install these using Homebrew:\n"
|
||||
"```sh\n"
|
||||
"brew install x86_64-elf-binutils x86_64-elf-gcc nasm xorriso qemu\n"
|
||||
"```\n\n"
|
||||
"## Building\n\n"
|
||||
"Simply run `make` from the project root:\n\n"
|
||||
"```sh\n"
|
||||
"make\n"
|
||||
"```\n\n"
|
||||
"This will:\n"
|
||||
"1. Download Limine v7.0.0 bootloader files (if not present)\n"
|
||||
"2. Compile all kernel C sources and assembly files\n"
|
||||
"3. Link the kernel ELF binary\n"
|
||||
"4. Generate a bootable ISO image (brewos.iso)\n\n"
|
||||
"Build output:\n"
|
||||
"* Compiled object files: build/\n"
|
||||
"* ISO root filesystem: iso_root/\n"
|
||||
"* Final ISO image: brewos.iso\n\n"
|
||||
"RUNNING\n"
|
||||
"-------\n"
|
||||
"QEMU EMULATION:\n"
|
||||
"Run in QEMU with:\n"
|
||||
" make run\n\n"
|
||||
"1. Compile all kernel C sources and assembly files\n"
|
||||
"2. Link the kernel ELF binary\n"
|
||||
"3. Generate a bootable ISO image (`brewos.iso`)\n\n"
|
||||
"The build output is organized as follows:\n"
|
||||
"- Compiled object files: `build/`\n"
|
||||
"- ISO root filesystem: `iso_root/`\n"
|
||||
"- Final ISO image: `brewos.iso`\n\n"
|
||||
"## Running\n\n"
|
||||
"### QEMU Emulation\n\n"
|
||||
"Run the kernel in QEMU:\n\n"
|
||||
"```sh\n"
|
||||
"make run\n"
|
||||
"```\n\n"
|
||||
"Or manually:\n"
|
||||
" qemu-system-x86_64 -m 2G -serial stdio -cdrom brewos.iso -boot d\n\n"
|
||||
"RUNNING ON REAL HARDWARE:\n"
|
||||
"WARNING: This is at YOUR OWN RISK. This software comes with ZERO warranty\n"
|
||||
"and may break your system.\n\n"
|
||||
"1. Create bootable USB using Balena Etcher to flash brewos.iso\n"
|
||||
"2. Enable legacy (BIOS) boot in your system BIOS/UEFI settings\n"
|
||||
"3. Disable Secure Boot if needed\n"
|
||||
"4. Insert USB drive and select it in boot menu during startup\n\n"
|
||||
"Tested Hardware:\n"
|
||||
"* HP EliteDesk 705 G4 DM (AMD Ryzen 5 PRO 2400G, Radeon Vega)\n"
|
||||
"* Lenovo ThinkPad A475 20KL002VMH (AMD Pro A12-8830B, Radeon R7)\n\n"
|
||||
"PROJECT STRUCTURE\n"
|
||||
"-----------------\n"
|
||||
"* src/kernel/ - Main kernel implementation\n"
|
||||
" - boot.asm - Boot assembly code\n"
|
||||
" - main.c - Kernel entry point\n"
|
||||
" - *.c / *.h - Core kernel modules\n"
|
||||
" - cli_apps/ - Command-line applications\n"
|
||||
" - wallpaper.ppm - Default desktop wallpaper\n"
|
||||
"* build/ - Compiled object files (generated during build)\n"
|
||||
"* iso_root/ - ISO filesystem layout (generated during build)\n"
|
||||
"* limine/ - Limine bootloader files (downloaded automatically)\n"
|
||||
"* linker.ld - Linker script for x86_64 ELF\n"
|
||||
"* limine.cfg - Limine bootloader configuration\n"
|
||||
"* Makefile - Build configuration and targets\n\n"
|
||||
"LICENSE\n"
|
||||
"-------\n"
|
||||
"```sh\n"
|
||||
"qemu-system-x86_64 -m 2G -serial stdio -cdrom brewos.iso -boot d\n"
|
||||
"```\n\n"
|
||||
"### Running on Real Hardware\n\n"
|
||||
"*Warning: This is at YOUR OWN RISK. This software comes with ZERO warranty and may break your system.*\n\n"
|
||||
"1. **Create bootable USB**: Use [Balena Etcher](https://www.balena.io/etcher/) to flash `brewos.iso` to a USB drive\n\n"
|
||||
"2. **Prepare the system**:\n"
|
||||
" - Enable legacy (BIOS) boot in your system BIOS/UEFI settings\n"
|
||||
" - Disable Secure Boot if needed\n\n"
|
||||
"3. **Boot**: Insert the USB drive and select it in the boot menu during startup\n\n"
|
||||
"4. **Tested Hardware**:\n"
|
||||
" - HP EliteDesk 705 G4 DM (AMD Ryzen 5 PRO 2400G, Radeon Vega)\n"
|
||||
" - Lenovo ThinkPad A475 20KL002VMH (AMD Pro A12-8830B, Radeon R7)\n\n"
|
||||
"## Project Structure\n\n"
|
||||
"- `src/kernel/` - Main kernel implementation\n"
|
||||
" - `boot.asm` - Boot assembly code\n"
|
||||
" - `main.c` - Kernel entry point\n"
|
||||
" - `*.c / *.h` - Core kernel modules (graphics, interrupts, filesystem, etc.)\n"
|
||||
" - `cli_apps/` - Command-line applications\n"
|
||||
" - `wallpaper.ppm` - Default desktop wallpaper\n"
|
||||
"- `build/` - Compiled object files (generated during build)\n"
|
||||
"- `iso_root/` - ISO filesystem layout (generated during build)\n"
|
||||
"- `limine/` - Limine bootloader files (downloaded automatically)\n"
|
||||
"- `linker.ld` - Linker script for x86_64 ELF\n"
|
||||
"- `limine.cfg` - Limine bootloader configuration\n"
|
||||
"- `Makefile` - Build configuration and targets\n\n"
|
||||
"## License\n\n"
|
||||
"Copyright (C) 2024-2026 boreddevnl\n\n"
|
||||
"This program is free software: you can redistribute it and/or modify\n"
|
||||
"it under the terms of the GNU General Public License as published by\n"
|
||||
"the Free Software Foundation, either version 3 of the License, or\n"
|
||||
"(at your option) any later version.\n\n"
|
||||
"For full license details, see the LICENSE file in the repository.\n";
|
||||
"This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\n"
|
||||
"NOTICE\n"
|
||||
"------\n\n"
|
||||
"This product includes software developed by Chris (\"boreddevnl\") as part of the BrewKernel project.\n\n"
|
||||
"Copyright (C) 2024–2026 Chris / boreddevnl (previously boreddevhq)\n\n"
|
||||
"All source files in this repository contain copyright and license\n"
|
||||
"headers that must be preserved in redistributions and derivative works.\n\n"
|
||||
"If you distribute or modify this project (in whole or in part),\n"
|
||||
"you MUST:\n\n"
|
||||
" - Retain all copyright and license headers at the top of each file.\n"
|
||||
" - Include this NOTICE file along with any redistributions or\n"
|
||||
" derivative works.\n"
|
||||
" - Provide clear attribution to the original author in documentation\n"
|
||||
" or credits where appropriate.\n\n"
|
||||
"The above attribution requirements are informational and intended to\n"
|
||||
"ensure proper credit is given. They do not alter or supersede the\n"
|
||||
"terms of the GNU General Public License (GPL), which governs this work.\n";
|
||||
fat32_write(fh, (void *)content, cmd_strlen(content));
|
||||
fat32_close(fh);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,10 @@ static void editor_ensure_cursor_visible(void);
|
|||
|
||||
static void editor_clear_all(void) {
|
||||
for (int i = 0; i < EDITOR_MAX_LINES; i++) {
|
||||
lines[i].content[0] = 0;
|
||||
// Zero out entire buffer to prevent ghost text from previous file
|
||||
for (int j = 0; j < EDITOR_MAX_LINE_LEN; j++) {
|
||||
lines[i].content[j] = 0;
|
||||
}
|
||||
lines[i].length = 0;
|
||||
}
|
||||
line_count = 1;
|
||||
|
|
@ -264,17 +267,26 @@ static void editor_paint(Window *win) {
|
|||
// Fill editor background
|
||||
draw_rect(offset_x, offset_y + 30, content_width, content_height - 55, COLOR_WHITE);
|
||||
|
||||
// Draw line numbers and content
|
||||
// Calculate available width for text (accounting for line numbers)
|
||||
int text_start_x = offset_x + 40;
|
||||
int available_width = content_width - 40;
|
||||
int max_chars_per_line = available_width / EDITOR_CHAR_WIDTH;
|
||||
if (max_chars_per_line < 1) max_chars_per_line = 1;
|
||||
|
||||
// Draw line numbers and content with word wrapping
|
||||
int display_line = 0;
|
||||
int visible_lines = (content_height - 55) / EDITOR_LINE_HEIGHT;
|
||||
int max_line = scroll_top + visible_lines;
|
||||
if (max_line > line_count) max_line = line_count;
|
||||
int max_display_lines = visible_lines;
|
||||
|
||||
for (int i = scroll_top; i < max_line; i++) {
|
||||
int display_y = offset_y + 35 + (i - scroll_top) * EDITOR_LINE_HEIGHT;
|
||||
int line_idx = scroll_top;
|
||||
while (line_idx < line_count && display_line < max_display_lines) {
|
||||
int display_y = offset_y + 35 + display_line * EDITOR_LINE_HEIGHT;
|
||||
|
||||
// Only draw line number for first wrapped line of this editor line
|
||||
if (display_line == 0 || line_idx < line_count) {
|
||||
// Draw line number
|
||||
char line_num_str[16];
|
||||
int temp = i + 1;
|
||||
int temp = line_idx + 1;
|
||||
int str_len = 0;
|
||||
if (temp == 0) {
|
||||
line_num_str[0] = '0';
|
||||
|
|
@ -293,15 +305,66 @@ static void editor_paint(Window *win) {
|
|||
}
|
||||
line_num_str[str_len] = 0;
|
||||
draw_string(offset_x + 4, display_y, line_num_str, COLOR_DKGRAY);
|
||||
|
||||
// Draw line content
|
||||
draw_string(offset_x + 40, display_y, lines[i].content, COLOR_BLACK);
|
||||
|
||||
// Draw cursor if on this line
|
||||
if (i == cursor_line) {
|
||||
int cursor_x = offset_x + 40 + (cursor_col * EDITOR_CHAR_WIDTH);
|
||||
draw_rect(cursor_x, display_y, 2, 10, COLOR_BLACK);
|
||||
}
|
||||
|
||||
// Word-based text wrapping for this line
|
||||
const char *text = lines[line_idx].content;
|
||||
int text_len = lines[line_idx].length;
|
||||
int char_idx = 0;
|
||||
int local_display_line = 0;
|
||||
|
||||
while (char_idx < text_len && display_line < max_display_lines) {
|
||||
int current_display_y = offset_y + 35 + display_line * EDITOR_LINE_HEIGHT;
|
||||
|
||||
// Extract segment (up to max_chars_per_line)
|
||||
char segment[256];
|
||||
int segment_len = 0;
|
||||
int segment_start = char_idx;
|
||||
|
||||
while (char_idx < text_len && segment_len < max_chars_per_line) {
|
||||
segment[segment_len++] = text[char_idx++];
|
||||
}
|
||||
segment[segment_len] = 0;
|
||||
|
||||
// Word-based wrapping: find last space if we didn't reach end
|
||||
if (char_idx < text_len && segment_len > 0) {
|
||||
int last_space = -1;
|
||||
for (int i = segment_len - 1; i >= 0; i--) {
|
||||
if (segment[i] == ' ') {
|
||||
last_space = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_space > 0) {
|
||||
segment_len = last_space;
|
||||
segment[segment_len] = 0;
|
||||
char_idx = segment_start + last_space + 1;
|
||||
// Skip additional spaces
|
||||
while (char_idx < text_len && text[char_idx] == ' ') {
|
||||
char_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the text segment
|
||||
if (segment_len > 0) {
|
||||
draw_string(text_start_x, current_display_y, segment, COLOR_BLACK);
|
||||
}
|
||||
|
||||
// Draw cursor if on this line and wrapped segment
|
||||
if (line_idx == cursor_line && cursor_col >= segment_start && cursor_col < segment_start + segment_len) {
|
||||
int cursor_x = text_start_x + ((cursor_col - segment_start) * EDITOR_CHAR_WIDTH);
|
||||
draw_rect(cursor_x, current_display_y, 2, 10, COLOR_BLACK);
|
||||
}
|
||||
|
||||
display_line++;
|
||||
local_display_line++;
|
||||
|
||||
if (char_idx >= text_len) break;
|
||||
}
|
||||
|
||||
line_idx++;
|
||||
}
|
||||
|
||||
// Draw status bar at bottom
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "fat32.h"
|
||||
#include "wm.h"
|
||||
#include "editor.h"
|
||||
#include "markdown.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
|
@ -57,6 +58,15 @@ static int dropdown_menu_item_height = 25;
|
|||
#define DROPDOWN_MENU_WIDTH 120
|
||||
#define DROPDOWN_MENU_ITEMS 3
|
||||
|
||||
// File context menu state
|
||||
static bool file_context_menu_visible = false;
|
||||
static int file_context_menu_x = 0;
|
||||
static int file_context_menu_y = 0;
|
||||
static int file_context_menu_item = -1; // Which item is being right-clicked
|
||||
#define FILE_CONTEXT_MENU_WIDTH 140
|
||||
#define FILE_CONTEXT_MENU_HEIGHT 50
|
||||
#define FILE_CONTEXT_ITEMS 2 // "Open with Text Editor" and "Open with Markdown Viewer"
|
||||
|
||||
// === Helper Functions ===
|
||||
|
||||
static size_t explorer_strlen(const char *str);
|
||||
|
|
@ -64,6 +74,8 @@ static void explorer_strcpy(char *dest, const char *src);
|
|||
static int explorer_strcmp(const char *s1, const char *s2);
|
||||
static void explorer_strcat(char *dest, const char *src);
|
||||
static void explorer_load_directory(const char *path);
|
||||
static void explorer_handle_right_click(Window *win, int x, int y);
|
||||
static void explorer_handle_file_context_menu_click(Window *win, int x, int y);
|
||||
|
||||
static size_t explorer_strlen(const char *str) {
|
||||
size_t len = 0;
|
||||
|
|
@ -89,6 +101,28 @@ static void explorer_strcat(char *dest, const char *src) {
|
|||
explorer_strcpy(dest, src);
|
||||
}
|
||||
|
||||
// Get file extension (e.g., "md" from "file.md")
|
||||
static const char* explorer_get_extension(const char *filename) {
|
||||
const char *dot = filename;
|
||||
const char *ext = "";
|
||||
|
||||
// Find the last dot
|
||||
while (*dot) {
|
||||
if (*dot == '.') {
|
||||
ext = dot + 1;
|
||||
}
|
||||
dot++;
|
||||
}
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
// Check if file is markdown
|
||||
static bool explorer_is_markdown_file(const char *filename) {
|
||||
const char *ext = explorer_get_extension(filename);
|
||||
return explorer_strcmp(ext, "md") == 0;
|
||||
}
|
||||
|
||||
// === Dialog and File Operations ===
|
||||
|
||||
static void dialog_open_create_file(void) {
|
||||
|
|
@ -417,12 +451,40 @@ static void explorer_paint(Window *win) {
|
|||
draw_button(dlg_x + 50, dlg_y + 65, 80, 25, "Delete", false);
|
||||
draw_button(dlg_x + 170, dlg_y + 65, 80, 25, "Cancel", false);
|
||||
}
|
||||
|
||||
// Draw file context menu if visible
|
||||
if (file_context_menu_visible && file_context_menu_item >= 0) {
|
||||
// Convert window-relative coordinates to screen coordinates for drawing
|
||||
int menu_screen_x = win->x + file_context_menu_x;
|
||||
int menu_screen_y = win->y + file_context_menu_y;
|
||||
|
||||
// Draw menu background
|
||||
draw_rect(menu_screen_x, menu_screen_y, FILE_CONTEXT_MENU_WIDTH, FILE_CONTEXT_MENU_HEIGHT, COLOR_LTGRAY);
|
||||
draw_bevel_rect(menu_screen_x, menu_screen_y, FILE_CONTEXT_MENU_WIDTH, FILE_CONTEXT_MENU_HEIGHT, true);
|
||||
|
||||
// Draw menu items
|
||||
int item_height = FILE_CONTEXT_MENU_HEIGHT / FILE_CONTEXT_ITEMS;
|
||||
|
||||
// Item 1: "Open with Text Editor"
|
||||
draw_string(menu_screen_x + 5, menu_screen_y + 5, "Open w/ Editor", COLOR_BLACK);
|
||||
|
||||
// Item 2: "Open with Markdown Viewer" (only show if file is .md)
|
||||
if (explorer_is_markdown_file(items[file_context_menu_item].name)) {
|
||||
draw_string(menu_screen_x + 5, menu_screen_y + item_height + 5, "Open w/ Markdown", COLOR_BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Mouse Handler ===
|
||||
|
||||
static void explorer_handle_click(Window *win, int x, int y) {
|
||||
// Handle dialog clicks first
|
||||
// Handle file context menu clicks first
|
||||
if (file_context_menu_visible) {
|
||||
explorer_handle_file_context_menu_click(win, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle dialog clicks
|
||||
if (dialog_state == DIALOG_CREATE_FILE || dialog_state == DIALOG_CREATE_FOLDER) {
|
||||
int dlg_x = win->w / 2 - 150;
|
||||
int dlg_y = win->h / 2 - 60;
|
||||
|
|
@ -549,7 +611,7 @@ static void explorer_handle_click(Window *win, int x, int y) {
|
|||
if (items[i].is_directory) {
|
||||
explorer_navigate_to(items[i].name);
|
||||
} else {
|
||||
// Open file in editor
|
||||
// Open file - check type
|
||||
char full_path[256];
|
||||
explorer_strcpy(full_path, current_path);
|
||||
if (full_path[explorer_strlen(full_path) - 1] != '/') {
|
||||
|
|
@ -557,20 +619,33 @@ static void explorer_handle_click(Window *win, int x, int y) {
|
|||
}
|
||||
explorer_strcat(full_path, items[i].name);
|
||||
|
||||
// Open in editor and bring to front
|
||||
win_editor.visible = true;
|
||||
win_editor.focused = true;
|
||||
// Check if markdown file
|
||||
if (explorer_is_markdown_file(items[i].name)) {
|
||||
// Open with markdown viewer
|
||||
win_markdown.visible = true;
|
||||
win_markdown.focused = true;
|
||||
int max_z = 0;
|
||||
for (int j = 0; j < 5; j++) { // window_count is 5
|
||||
// Need to find max z_index - check all windows
|
||||
if (win_explorer.z_index > max_z) max_z = win_explorer.z_index;
|
||||
if (win_cmd.z_index > max_z) max_z = win_cmd.z_index;
|
||||
if (win_notepad.z_index > max_z) max_z = win_notepad.z_index;
|
||||
if (win_calculator.z_index > max_z) max_z = win_calculator.z_index;
|
||||
}
|
||||
if (win_editor.z_index > max_z) max_z = win_editor.z_index;
|
||||
win_markdown.z_index = max_z + 1;
|
||||
markdown_open_file(full_path);
|
||||
} else {
|
||||
// Open with text editor
|
||||
win_editor.visible = true;
|
||||
win_editor.focused = true;
|
||||
int max_z = 0;
|
||||
if (win_explorer.z_index > max_z) max_z = win_explorer.z_index;
|
||||
if (win_cmd.z_index > max_z) max_z = win_cmd.z_index;
|
||||
if (win_notepad.z_index > max_z) max_z = win_notepad.z_index;
|
||||
if (win_calculator.z_index > max_z) max_z = win_calculator.z_index;
|
||||
if (win_markdown.z_index > max_z) max_z = win_markdown.z_index;
|
||||
win_editor.z_index = max_z + 1;
|
||||
editor_open_file(full_path);
|
||||
}
|
||||
}
|
||||
last_clicked_item = -1;
|
||||
} else {
|
||||
// Single-click - select
|
||||
|
|
@ -678,6 +753,100 @@ static void explorer_handle_key(Window *win, char c) {
|
|||
}
|
||||
}
|
||||
|
||||
// === Right-Click Handler ===
|
||||
|
||||
static void explorer_handle_right_click(Window *win, int x, int y) {
|
||||
// File items start at y=64 relative to window
|
||||
int content_start_y = 64;
|
||||
int offset_x = 4;
|
||||
|
||||
for (int i = 0; i < item_count; i++) {
|
||||
int row = i / EXPLORER_COLS;
|
||||
int col = i % EXPLORER_COLS;
|
||||
|
||||
int item_x = offset_x + 10 + (col * (EXPLORER_ITEM_WIDTH + EXPLORER_PADDING));
|
||||
int item_y = content_start_y + (row * (EXPLORER_ITEM_HEIGHT + EXPLORER_PADDING));
|
||||
|
||||
if (x >= item_x && x < item_x + EXPLORER_ITEM_WIDTH &&
|
||||
y >= item_y && y < item_y + EXPLORER_ITEM_HEIGHT) {
|
||||
|
||||
// Right-click on a file item
|
||||
if (!items[i].is_directory) {
|
||||
// Show context menu
|
||||
file_context_menu_visible = true;
|
||||
file_context_menu_item = i;
|
||||
file_context_menu_x = x;
|
||||
file_context_menu_y = y;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close menu if clicking elsewhere
|
||||
file_context_menu_visible = false;
|
||||
file_context_menu_item = -1;
|
||||
}
|
||||
|
||||
static void explorer_handle_file_context_menu_click(Window *win, int x, int y) {
|
||||
(void)win; // Suppress unused warning - we use absolute coordinates instead
|
||||
|
||||
if (!file_context_menu_visible || file_context_menu_item < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust coordinates to be relative to context menu
|
||||
int relative_x = x - file_context_menu_x;
|
||||
int relative_y = y - file_context_menu_y;
|
||||
|
||||
if (relative_x < 0 || relative_x > FILE_CONTEXT_MENU_WIDTH ||
|
||||
relative_y < 0 || relative_y > FILE_CONTEXT_MENU_HEIGHT) {
|
||||
// Clicked outside menu - close it
|
||||
file_context_menu_visible = false;
|
||||
file_context_menu_item = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
int item_height = FILE_CONTEXT_MENU_HEIGHT / FILE_CONTEXT_ITEMS;
|
||||
int clicked_item = relative_y / item_height;
|
||||
|
||||
// Build full path
|
||||
char full_path[256];
|
||||
explorer_strcpy(full_path, current_path);
|
||||
if (full_path[explorer_strlen(full_path) - 1] != '/') {
|
||||
explorer_strcat(full_path, "/");
|
||||
}
|
||||
explorer_strcat(full_path, items[file_context_menu_item].name);
|
||||
|
||||
if (clicked_item == 0) {
|
||||
// "Open with Text Editor"
|
||||
win_editor.visible = true;
|
||||
win_editor.focused = true;
|
||||
int max_z = 0;
|
||||
if (win_explorer.z_index > max_z) max_z = win_explorer.z_index;
|
||||
if (win_cmd.z_index > max_z) max_z = win_cmd.z_index;
|
||||
if (win_notepad.z_index > max_z) max_z = win_notepad.z_index;
|
||||
if (win_calculator.z_index > max_z) max_z = win_calculator.z_index;
|
||||
if (win_markdown.z_index > max_z) max_z = win_markdown.z_index;
|
||||
win_editor.z_index = max_z + 1;
|
||||
editor_open_file(full_path);
|
||||
} else if (clicked_item == 1 && explorer_is_markdown_file(items[file_context_menu_item].name)) {
|
||||
// "Open with Markdown Viewer"
|
||||
win_markdown.visible = true;
|
||||
win_markdown.focused = true;
|
||||
int max_z = 0;
|
||||
if (win_explorer.z_index > max_z) max_z = win_explorer.z_index;
|
||||
if (win_cmd.z_index > max_z) max_z = win_cmd.z_index;
|
||||
if (win_notepad.z_index > max_z) max_z = win_notepad.z_index;
|
||||
if (win_calculator.z_index > max_z) max_z = win_calculator.z_index;
|
||||
if (win_editor.z_index > max_z) max_z = win_editor.z_index;
|
||||
win_markdown.z_index = max_z + 1;
|
||||
markdown_open_file(full_path);
|
||||
}
|
||||
|
||||
file_context_menu_visible = false;
|
||||
file_context_menu_item = -1;
|
||||
}
|
||||
|
||||
// === Initialization ===
|
||||
|
||||
void explorer_init(void) {
|
||||
|
|
@ -692,7 +861,7 @@ void explorer_init(void) {
|
|||
win_explorer.paint = explorer_paint;
|
||||
win_explorer.handle_key = explorer_handle_key;
|
||||
win_explorer.handle_click = explorer_handle_click;
|
||||
win_explorer.handle_right_click = NULL;
|
||||
win_explorer.handle_right_click = explorer_handle_right_click;
|
||||
|
||||
explorer_load_directory("/");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ extern Window win_editor;
|
|||
extern Window win_cmd;
|
||||
extern Window win_notepad;
|
||||
extern Window win_calculator;
|
||||
extern Window win_markdown;
|
||||
|
||||
void explorer_init(void);
|
||||
void explorer_reset(void);
|
||||
|
|
|
|||
512
src/kernel/markdown.c
Normal file
512
src/kernel/markdown.c
Normal file
|
|
@ -0,0 +1,512 @@
|
|||
#include "markdown.h"
|
||||
#include "graphics.h"
|
||||
#include "fat32.h"
|
||||
#include "wm.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// === Markdown Viewer State ===
|
||||
Window win_markdown;
|
||||
|
||||
#define MD_MAX_CONTENT 16384
|
||||
#define MD_MAX_LINES 256
|
||||
#define MD_CHAR_WIDTH 8
|
||||
#define MD_LINE_HEIGHT 16
|
||||
#define MD_CONTENT_Y 40
|
||||
#define MD_PADDING_X 12
|
||||
#define MD_CONTENT_WIDTH 400
|
||||
|
||||
typedef enum {
|
||||
MD_LINE_NORMAL,
|
||||
MD_LINE_HEADING1,
|
||||
MD_LINE_HEADING2,
|
||||
MD_LINE_HEADING3,
|
||||
MD_LINE_BOLD,
|
||||
MD_LINE_ITALIC,
|
||||
MD_LINE_LIST,
|
||||
MD_LINE_BLOCKQUOTE,
|
||||
MD_LINE_CODE
|
||||
} MDLineType;
|
||||
|
||||
typedef struct {
|
||||
char content[256];
|
||||
int length;
|
||||
MDLineType type;
|
||||
int indent_level;
|
||||
} MDLine;
|
||||
|
||||
static MDLine lines[MD_MAX_LINES];
|
||||
static int line_count = 0;
|
||||
static int scroll_top = 0;
|
||||
static char open_filename[256] = "";
|
||||
|
||||
// === Helper Functions ===
|
||||
|
||||
static size_t md_strlen(const char *str) {
|
||||
size_t len = 0;
|
||||
while (str[len]) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
static void md_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
static int md_strncpy(char *dest, const char *src, int n) {
|
||||
int i = 0;
|
||||
while (i < n && src[i]) {
|
||||
dest[i] = src[i];
|
||||
i++;
|
||||
}
|
||||
dest[i] = 0;
|
||||
return i;
|
||||
}
|
||||
|
||||
static int md_strcmp(const char *s1, const char *s2) {
|
||||
(void)s1; // Suppress unused warning
|
||||
(void)s2; // Suppress unused warning
|
||||
// Reserved for future use
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if string starts with pattern
|
||||
static bool md_starts_with(const char *str, const char *pattern) {
|
||||
(void)str; // Suppress unused warning
|
||||
(void)pattern; // Suppress unused warning
|
||||
// Reserved for future use
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse markdown line and extract formatted text
|
||||
static void md_parse_line(const char *raw_line, char *output, MDLineType *type, int *indent) {
|
||||
int i = 0;
|
||||
int out_idx = 0;
|
||||
*indent = 0;
|
||||
*type = MD_LINE_NORMAL;
|
||||
|
||||
// Skip leading whitespace and count indentation
|
||||
while (raw_line[i] == ' ' || raw_line[i] == '\t') {
|
||||
if (raw_line[i] == '\t') *indent += 2;
|
||||
else *indent += 1;
|
||||
i++;
|
||||
}
|
||||
|
||||
// Detect line type
|
||||
if (raw_line[i] == '#') {
|
||||
// Heading
|
||||
int hash_count = 0;
|
||||
while (raw_line[i] == '#') {
|
||||
hash_count++;
|
||||
i++;
|
||||
}
|
||||
// Skip space after hashes
|
||||
if (raw_line[i] == ' ') i++;
|
||||
|
||||
if (hash_count == 1) *type = MD_LINE_HEADING1;
|
||||
else if (hash_count == 2) *type = MD_LINE_HEADING2;
|
||||
else if (hash_count <= 6) *type = MD_LINE_HEADING3;
|
||||
} else if (raw_line[i] == '-' || raw_line[i] == '*') {
|
||||
// Could be list or horizontal rule
|
||||
if ((raw_line[i] == '-' || raw_line[i] == '*') && (raw_line[i+1] == ' ' || raw_line[i+1] == '\t')) {
|
||||
*type = MD_LINE_LIST;
|
||||
i += 2; // Skip '- ' or '* '
|
||||
while (raw_line[i] == ' ' || raw_line[i] == '\t') i++; // Skip extra spaces
|
||||
}
|
||||
} else if (raw_line[i] == '>') {
|
||||
// Blockquote
|
||||
*type = MD_LINE_BLOCKQUOTE;
|
||||
i++;
|
||||
if (raw_line[i] == ' ') i++;
|
||||
} else if (raw_line[i] == '`') {
|
||||
// Code block
|
||||
*type = MD_LINE_CODE;
|
||||
i++;
|
||||
}
|
||||
|
||||
// Parse inline formatting and copy content
|
||||
while (raw_line[i] && out_idx < 255) {
|
||||
// Handle bold **text**
|
||||
if (raw_line[i] == '*' && raw_line[i+1] == '*') {
|
||||
i += 2;
|
||||
while (raw_line[i] && !(raw_line[i] == '*' && raw_line[i+1] == '*') && out_idx < 255) {
|
||||
output[out_idx++] = raw_line[i++];
|
||||
}
|
||||
if (raw_line[i] == '*' && raw_line[i+1] == '*') i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle italic *text* or _text_
|
||||
if ((raw_line[i] == '*' || raw_line[i] == '_') && out_idx > 0 && raw_line[i-1] != '\\') {
|
||||
char delim = raw_line[i];
|
||||
i++;
|
||||
while (raw_line[i] && raw_line[i] != delim && out_idx < 255) {
|
||||
output[out_idx++] = raw_line[i++];
|
||||
}
|
||||
if (raw_line[i] == delim) i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle inline code `code`
|
||||
if (raw_line[i] == '`') {
|
||||
i++;
|
||||
while (raw_line[i] && raw_line[i] != '`' && out_idx < 255) {
|
||||
output[out_idx++] = raw_line[i++];
|
||||
}
|
||||
if (raw_line[i] == '`') i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle links [text](url) - keep only text
|
||||
if (raw_line[i] == '[') {
|
||||
i++;
|
||||
while (raw_line[i] && raw_line[i] != ']' && out_idx < 255) {
|
||||
output[out_idx++] = raw_line[i++];
|
||||
}
|
||||
if (raw_line[i] == ']') i++;
|
||||
// Skip (url)
|
||||
if (raw_line[i] == '(') {
|
||||
while (raw_line[i] && raw_line[i] != ')') i++;
|
||||
if (raw_line[i] == ')') i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
output[out_idx++] = raw_line[i++];
|
||||
}
|
||||
|
||||
output[out_idx] = 0;
|
||||
}
|
||||
|
||||
// Clear all markdown lines
|
||||
static void md_clear_all(void) {
|
||||
for (int i = 0; i < MD_MAX_LINES; i++) {
|
||||
lines[i].content[0] = 0;
|
||||
lines[i].length = 0;
|
||||
lines[i].type = MD_LINE_NORMAL;
|
||||
lines[i].indent_level = 0;
|
||||
}
|
||||
line_count = 0;
|
||||
scroll_top = 0;
|
||||
open_filename[0] = 0;
|
||||
}
|
||||
|
||||
// Load and parse markdown file
|
||||
void markdown_open_file(const char *filename) {
|
||||
md_clear_all();
|
||||
md_strcpy(open_filename, filename);
|
||||
|
||||
FAT32_FileHandle *fh = fat32_open(filename, "r");
|
||||
if (!fh) {
|
||||
// File not found
|
||||
return;
|
||||
}
|
||||
|
||||
// Read file content
|
||||
char buffer[MD_MAX_CONTENT];
|
||||
int bytes_read = fat32_read(fh, buffer, sizeof(buffer) - 1);
|
||||
fat32_close(fh);
|
||||
|
||||
if (bytes_read <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer[bytes_read] = 0;
|
||||
|
||||
// Parse into markdown lines
|
||||
int line = 0;
|
||||
int col = 0;
|
||||
char raw_line[256] = "";
|
||||
|
||||
for (int i = 0; i < bytes_read && line < MD_MAX_LINES; i++) {
|
||||
char ch = buffer[i];
|
||||
|
||||
if (ch == '\n') {
|
||||
raw_line[col] = 0;
|
||||
|
||||
// Parse the raw line
|
||||
char parsed_content[256];
|
||||
MDLineType type;
|
||||
int indent;
|
||||
md_parse_line(raw_line, parsed_content, &type, &indent);
|
||||
|
||||
// Store parsed line
|
||||
md_strcpy(lines[line].content, parsed_content);
|
||||
lines[line].length = md_strlen(parsed_content);
|
||||
lines[line].type = type;
|
||||
lines[line].indent_level = indent;
|
||||
|
||||
line++;
|
||||
col = 0;
|
||||
raw_line[0] = 0;
|
||||
} else if (col < 255) {
|
||||
raw_line[col++] = ch;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle last line if no trailing newline
|
||||
if (col > 0 && line < MD_MAX_LINES) {
|
||||
raw_line[col] = 0;
|
||||
char parsed_content[256];
|
||||
MDLineType type;
|
||||
int indent;
|
||||
md_parse_line(raw_line, parsed_content, &type, &indent);
|
||||
|
||||
md_strcpy(lines[line].content, parsed_content);
|
||||
lines[line].length = md_strlen(parsed_content);
|
||||
lines[line].type = type;
|
||||
lines[line].indent_level = indent;
|
||||
line++;
|
||||
}
|
||||
|
||||
line_count = line;
|
||||
}
|
||||
|
||||
// === Paint Function ===
|
||||
|
||||
// Helper to draw text with emphasis (bold effect by overlaying)
|
||||
static void md_draw_text_bold(int x, int y, const char *text, uint32_t color) {
|
||||
draw_string(x, y, text, color);
|
||||
draw_string(x + 1, y, text, color);
|
||||
}
|
||||
|
||||
static void md_paint(Window *win) {
|
||||
int offset_x = win->x + 4;
|
||||
int offset_y = win->y + 24;
|
||||
int content_width = win->w - 8;
|
||||
int content_height = win->h - 28;
|
||||
|
||||
// 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 + 50, offset_y + 4, open_filename, COLOR_BLACK);
|
||||
|
||||
// Draw scroll buttons on top right
|
||||
int btn_x_up = offset_x + content_width - 50;
|
||||
int btn_y = offset_y + 2;
|
||||
draw_button(btn_x_up, btn_y, 20, 16, "^", false);
|
||||
draw_button(btn_x_up + 24, btn_y, 20, 16, "v", false);
|
||||
|
||||
// Content area - starts below filename bar
|
||||
int content_start_y = offset_y + 24;
|
||||
int content_start_x = offset_x + 4;
|
||||
int usable_content_width = content_width - 8 - 20; // Reserved space for scroll button
|
||||
int usable_content_height = content_height - 28;
|
||||
int max_display_lines = usable_content_height / MD_LINE_HEIGHT;
|
||||
|
||||
// Draw content background
|
||||
draw_rect(offset_x, content_start_y, content_width - 20, usable_content_height, COLOR_WHITE);
|
||||
|
||||
|
||||
|
||||
int display_line = 0;
|
||||
int i = scroll_top;
|
||||
|
||||
while (i < line_count && display_line < max_display_lines) {
|
||||
MDLine *line = &lines[i];
|
||||
|
||||
// Determine spacing and text properties based on heading level
|
||||
int line_height = MD_LINE_HEIGHT;
|
||||
int extra_spacing = 0;
|
||||
uint32_t text_color = COLOR_BLACK;
|
||||
bool use_bold = false;
|
||||
|
||||
switch (line->type) {
|
||||
case MD_LINE_HEADING1:
|
||||
line_height = MD_LINE_HEIGHT * 2; // Double height
|
||||
text_color = 0xFF004080; // Dark blue
|
||||
use_bold = true;
|
||||
extra_spacing = 4;
|
||||
break;
|
||||
case MD_LINE_HEADING2:
|
||||
line_height = MD_LINE_HEIGHT + 6; // 1.5x height
|
||||
text_color = 0xFF1060A0; // Medium blue
|
||||
use_bold = true;
|
||||
extra_spacing = 2;
|
||||
break;
|
||||
case MD_LINE_HEADING3:
|
||||
line_height = MD_LINE_HEIGHT + 2; // Slightly larger
|
||||
text_color = 0xFF2080C0; // Light blue
|
||||
use_bold = false;
|
||||
break;
|
||||
case MD_LINE_BLOCKQUOTE:
|
||||
text_color = 0xFF808080; // Gray
|
||||
break;
|
||||
case MD_LINE_CODE:
|
||||
text_color = 0xFF800000; // Dark red
|
||||
break;
|
||||
default:
|
||||
text_color = COLOR_BLACK;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this heading will fit on the screen
|
||||
if (display_line + (line_height / MD_LINE_HEIGHT) > max_display_lines) {
|
||||
break; // Stop rendering if heading won't fit
|
||||
}
|
||||
|
||||
// Adjust X position based on indentation
|
||||
int x_offset = content_start_x + (line->indent_level * 4);
|
||||
int available_width = usable_content_width - (line->indent_level * 4);
|
||||
int max_chars_per_line = available_width / MD_CHAR_WIDTH;
|
||||
|
||||
if (max_chars_per_line < 1) max_chars_per_line = 1;
|
||||
|
||||
// Handle line wrapping (word-based)
|
||||
const char *text = line->content;
|
||||
int text_len = line->length;
|
||||
int char_idx = 0;
|
||||
int local_display_line = 0;
|
||||
int wrapped_line_count = 0;
|
||||
|
||||
while (char_idx < text_len) {
|
||||
int line_y = content_start_y + display_line * MD_LINE_HEIGHT + (local_display_line * MD_LINE_HEIGHT);
|
||||
|
||||
// Extract line segment - copy up to max_chars_per_line characters
|
||||
char line_segment[256];
|
||||
int segment_len = 0;
|
||||
int segment_start = char_idx;
|
||||
|
||||
// Copy characters up to max_chars_per_line OR until end of string
|
||||
while (char_idx < text_len && segment_len < max_chars_per_line) {
|
||||
line_segment[segment_len++] = text[char_idx++];
|
||||
}
|
||||
line_segment[segment_len] = 0;
|
||||
|
||||
// Word-based wrapping: if we didn't reach end of string, find last space
|
||||
if (char_idx < text_len && segment_len > 0) {
|
||||
// Look for the last space in the segment
|
||||
int last_space = -1;
|
||||
for (int i = segment_len - 1; i >= 0; i--) {
|
||||
if (line_segment[i] == ' ') {
|
||||
last_space = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a space, break there
|
||||
if (last_space > 0) {
|
||||
segment_len = last_space;
|
||||
line_segment[segment_len] = 0;
|
||||
// Backtrack char_idx to position after the space
|
||||
char_idx = segment_start + last_space + 1;
|
||||
// Skip any additional spaces at the start of next line
|
||||
while (char_idx < text_len && text[char_idx] == ' ') {
|
||||
char_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw special elements for first wrapped line of this markdown line
|
||||
if (local_display_line == 0) {
|
||||
switch (line->type) {
|
||||
case MD_LINE_LIST:
|
||||
// Draw bullet point
|
||||
draw_rect(x_offset, line_y + MD_LINE_HEIGHT/2 - 1, 2, 2, COLOR_BLACK);
|
||||
x_offset += 12;
|
||||
// Redraw segment without leading space
|
||||
if (segment_len > 0 && line_segment[0] == ' ') {
|
||||
for (int j = 0; j < segment_len - 1; j++) {
|
||||
line_segment[j] = line_segment[j + 1];
|
||||
}
|
||||
segment_len--;
|
||||
}
|
||||
break;
|
||||
case MD_LINE_BLOCKQUOTE:
|
||||
// Draw left border
|
||||
draw_rect(x_offset - 4, line_y, 2, line_height, 0xFF404080);
|
||||
break;
|
||||
case MD_LINE_CODE:
|
||||
// Draw background for code
|
||||
draw_rect(x_offset - 2, line_y, (max_chars_per_line * MD_CHAR_WIDTH) + 4, line_height, 0xFFF0F0F0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the text segment with appropriate styling
|
||||
if (segment_len > 0) {
|
||||
if (use_bold) {
|
||||
md_draw_text_bold(x_offset, line_y + extra_spacing, line_segment, text_color);
|
||||
} else {
|
||||
draw_string(x_offset, line_y, line_segment, text_color);
|
||||
}
|
||||
}
|
||||
|
||||
local_display_line++;
|
||||
wrapped_line_count++;
|
||||
|
||||
if (char_idx >= text_len) break;
|
||||
}
|
||||
|
||||
// Move display line forward by the actual number of wrapped lines created
|
||||
// Each wrapped line uses one MD_LINE_HEIGHT worth of space
|
||||
display_line += wrapped_line_count;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// === Input Handling ===
|
||||
|
||||
static void md_handle_key(Window *win, char c) {
|
||||
(void)win; // Suppress unused warning
|
||||
|
||||
// Handle scrolling with arrow keys and W/S
|
||||
// 17 = UP arrow, 18 = DOWN arrow (from ps2 keyboard mapping)
|
||||
if (c == 'w' || c == 'W' || c == 17) { // Page up or UP arrow
|
||||
scroll_top -= 3;
|
||||
if (scroll_top < 0) scroll_top = 0;
|
||||
} else if (c == 's' || c == 'S' || c == 18) { // Page down or DOWN arrow
|
||||
scroll_top += 3;
|
||||
int max_scroll = line_count - 10;
|
||||
if (scroll_top > max_scroll) scroll_top = max_scroll;
|
||||
if (scroll_top < 0) scroll_top = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void md_handle_click(Window *win, int x, int y) {
|
||||
// x and y are relative to window origin
|
||||
int content_width = win->w - 8;
|
||||
|
||||
// Top right up button: 4 + content_width - 50, 24 + 2, 20x16
|
||||
int btn_x_up = 4 + content_width - 50;
|
||||
int btn_y = 24 + 2;
|
||||
if (x >= btn_x_up && x < btn_x_up + 20 && y >= btn_y && y < btn_y + 16) {
|
||||
// Scroll up
|
||||
scroll_top -= 3;
|
||||
if (scroll_top < 0) scroll_top = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Top right down button: 4 + content_width - 50 + 24, 24 + 2, 20x16
|
||||
int btn_x_down_top = 4 + content_width - 50 + 24;
|
||||
if (x >= btn_x_down_top && x < btn_x_down_top + 20 && y >= btn_y && y < btn_y + 16) {
|
||||
// Scroll down
|
||||
scroll_top += 3;
|
||||
int max_scroll = line_count - 10;
|
||||
if (scroll_top > max_scroll) scroll_top = max_scroll;
|
||||
if (scroll_top < 0) scroll_top = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// === Initialization ===
|
||||
|
||||
void markdown_init(void) {
|
||||
win_markdown.title = "Markdown Viewer";
|
||||
win_markdown.x = 150;
|
||||
win_markdown.y = 180;
|
||||
win_markdown.w = 600;
|
||||
win_markdown.h = 400;
|
||||
win_markdown.visible = false;
|
||||
win_markdown.focused = false;
|
||||
win_markdown.z_index = 0;
|
||||
win_markdown.paint = md_paint;
|
||||
win_markdown.handle_key = md_handle_key;
|
||||
win_markdown.handle_click = md_handle_click;
|
||||
win_markdown.handle_right_click = NULL;
|
||||
|
||||
md_clear_all();
|
||||
}
|
||||
11
src/kernel/markdown.h
Normal file
11
src/kernel/markdown.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef MARKDOWN_H
|
||||
#define MARKDOWN_H
|
||||
|
||||
#include "wm.h"
|
||||
|
||||
extern Window win_markdown;
|
||||
|
||||
void markdown_init(void);
|
||||
void markdown_open_file(const char *filename);
|
||||
|
||||
#endif
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include "cli_apps/cli_utils.h"
|
||||
#include "explorer.h"
|
||||
#include "editor.h"
|
||||
#include "markdown.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "notepad.h"
|
||||
|
|
@ -25,7 +26,7 @@ static int drag_offset_x = 0;
|
|||
static int drag_offset_y = 0;
|
||||
|
||||
// Windows array for z-order management
|
||||
static Window *all_windows[8];
|
||||
static Window *all_windows[9];
|
||||
static int window_count = 0;
|
||||
|
||||
// Redraw system
|
||||
|
|
@ -121,7 +122,7 @@ void draw_folder_icon(int x, int y, const char *label) {
|
|||
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, 15, COLOR_APPLE_YELLOW);
|
||||
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);
|
||||
|
|
@ -678,6 +679,7 @@ void wm_handle_key(char c) {
|
|||
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_markdown.focused && win_markdown.visible) target = &win_markdown;
|
||||
else if (win_control_panel.focused && win_control_panel.visible) target = &win_control_panel;
|
||||
|
||||
if (!target) return;
|
||||
|
|
@ -704,6 +706,7 @@ void wm_init(void) {
|
|||
calculator_init();
|
||||
explorer_init();
|
||||
editor_init();
|
||||
markdown_init();
|
||||
control_panel_init();
|
||||
about_init();
|
||||
minesweeper_init();
|
||||
|
|
@ -714,9 +717,10 @@ void wm_init(void) {
|
|||
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;
|
||||
win_markdown.z_index = 5;
|
||||
win_control_panel.z_index = 6;
|
||||
win_about.z_index = 7;
|
||||
win_minesweeper.z_index = 8;
|
||||
|
||||
// Register windows in array
|
||||
all_windows[0] = &win_notepad;
|
||||
|
|
@ -724,10 +728,11 @@ void wm_init(void) {
|
|||
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;
|
||||
all_windows[5] = &win_markdown;
|
||||
all_windows[6] = &win_control_panel;
|
||||
all_windows[7] = &win_about;
|
||||
all_windows[8] = &win_minesweeper;
|
||||
window_count = 9;
|
||||
|
||||
// Only show Explorer and Notepad on desktop (Explorer on top)
|
||||
win_explorer.visible = false;
|
||||
|
|
@ -742,6 +747,7 @@ void wm_init(void) {
|
|||
win_cmd.visible = false;
|
||||
win_calculator.visible = false;
|
||||
win_editor.visible = false;
|
||||
win_markdown.visible = false;
|
||||
win_control_panel.visible = false;
|
||||
win_about.visible = false;
|
||||
win_minesweeper.visible = false;
|
||||
|
|
|
|||
Loading…
Reference in a new issue