mirror of
https://github.com/JannisHeydemann/BoredOS.git
synced 2026-06-15 10:23:10 +00:00
Compare commits
33 Commits
Full_V1.80
...
26.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6d512b0f2 | ||
|
|
0b7a134282 | ||
|
|
91b67bd8d5 | ||
|
|
e60f232812 | ||
|
|
3169ec51cb | ||
|
|
beb2c724ff | ||
|
|
bf3c2cb578 | ||
|
|
823e9c0ce7 | ||
|
|
0ddb1e7610 | ||
|
|
32a6bb4d72 | ||
|
|
d8e680604c | ||
|
|
2e28f860cb | ||
|
|
9634ebb086 | ||
|
|
d7d97b5a97 | ||
|
|
4a3752583c | ||
|
|
9de8ee143c | ||
|
|
8d5fa53d3e | ||
|
|
ad8db32305 | ||
|
|
92928e55fb | ||
|
|
31eb7afdc6 | ||
|
|
ad9fac3e28 | ||
|
|
70cd296d19 | ||
|
|
b7020152c1 | ||
|
|
63749b8734 | ||
|
|
4e8ea5acd2 | ||
|
|
5c199e028a | ||
|
|
ec2a9d1883 | ||
|
|
4c46650c64 | ||
|
|
1ee2fcad9e | ||
|
|
5c29ac1473 | ||
|
|
81743261bf | ||
|
|
4eeb907342 | ||
|
|
e527f63af7 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
110
Makefile
110
Makefile
@@ -132,58 +132,56 @@ $(KERNEL_ELF): $(OBJ_FILES)
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
|
||||
$(MAKE) -C $(SRC_DIR)/userland
|
||||
|
||||
$(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||
$(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
|
||||
rm -rf $(BUILD_DIR)/initrd
|
||||
mkdir -p $(BUILD_DIR)/initrd/bin
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/images/Wallpapers
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/images/gif
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/DOOM
|
||||
mkdir -p $(BUILD_DIR)/initrd/docs
|
||||
|
||||
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/bin/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/images/wallpapers/*; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/images/Wallpapers/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/images/gif/*.gif; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/images/gif/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; fi \
|
||||
done
|
||||
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi
|
||||
@for f in $$(find docs -name '*.md' 2>/dev/null); do \
|
||||
if [ -f "$$f" ]; then \
|
||||
dir=$$(dirname "$$f"); \
|
||||
mkdir -p $(BUILD_DIR)/initrd/"$$dir"; \
|
||||
cp "$$f" $(BUILD_DIR)/initrd/"$$dir"/; \
|
||||
fi \
|
||||
done
|
||||
@if [ -f README.md ]; then cp README.md $(BUILD_DIR)/initrd/; fi
|
||||
@if [ -f LICENSE ]; then cp LICENSE $(BUILD_DIR)/initrd/; fi
|
||||
|
||||
cd $(BUILD_DIR)/initrd && COPYFILE_DISABLE=1 tar --exclude="._*" -cf ../initrd.tar *
|
||||
|
||||
$(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup
|
||||
rm -rf $(ISO_DIR)
|
||||
mkdir -p $(ISO_DIR)
|
||||
mkdir -p $(ISO_DIR)/EFI/BOOT
|
||||
|
||||
cp $(KERNEL_ELF) $(ISO_DIR)/
|
||||
cp limine.conf $(ISO_DIR)/
|
||||
mkdir -p $(ISO_DIR)/bin
|
||||
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/bin/; \
|
||||
echo " module_path: boot():/bin/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
@if [ -f README.md ]; then cp README.md $(ISO_DIR)/; fi
|
||||
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then \
|
||||
mkdir -p $(ISO_DIR)/Library/DOOM; \
|
||||
cp $(SRC_DIR)/userland/games/doom/doom1.wad $(ISO_DIR)/Library/DOOM/; \
|
||||
echo " module_path: boot():/Library/DOOM/doom1.wad" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
cp $(BUILD_DIR)/initrd.tar $(ISO_DIR)/
|
||||
echo " module_path: boot():/initrd.tar" >> $(ISO_DIR)/limine.conf
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/images/Wallpapers
|
||||
@for f in $(SRC_DIR)/images/wallpapers/*; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/images/Wallpapers/; \
|
||||
echo " module_path: boot():/Library/images/Wallpapers/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
@if [ -f splash.jpg ]; then cp splash.jpg $(ISO_DIR)/; fi
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/images/gif
|
||||
@for f in $(SRC_DIR)/images/gif/*.gif; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/images/gif/; \
|
||||
echo " module_path: boot():/Library/images/gif/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
mkdir -p $(ISO_DIR)/docs
|
||||
@for f in $$(find docs -name '*.md'); do \
|
||||
if [ -f "$$f" ]; then \
|
||||
dir=$$(dirname "$$f"); \
|
||||
mkdir -p $(ISO_DIR)/"$$dir"; \
|
||||
cp "$$f" $(ISO_DIR)/"$$dir"/; \
|
||||
echo " module_path: boot():/$$f" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
cp limine/limine-bios.sys $(ISO_DIR)/
|
||||
cp limine/limine-bios-cd.bin $(ISO_DIR)/
|
||||
cp limine/limine-uefi-cd.bin $(ISO_DIR)/
|
||||
@@ -191,34 +189,6 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||
cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/
|
||||
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/Fonts
|
||||
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/Fonts/; \
|
||||
echo " module_path: boot():/Library/Fonts/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/Fonts/Emoji
|
||||
@for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/Fonts/Emoji/; \
|
||||
echo " module_path: boot():/Library/Fonts/Emoji/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
@if [ -f README.md ]; then \
|
||||
cp README.md $(ISO_DIR)/; \
|
||||
echo " module_path: boot():/README.md" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
|
||||
@if [ -f LICENSE ]; then \
|
||||
cp LICENSE $(ISO_DIR)/; \
|
||||
echo " module_path: boot():/LICENSE" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
|
||||
$(XORRISO) -as mkisofs -R -J -b limine-bios-cd.bin \
|
||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||
--efi-boot limine-uefi-cd.bin \
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
---
|
||||
|
||||
BoredOS is a functional x86_64 operating system featuring a custom Desktop Environment (DE), a dedicated Window Manager (BoredWM), and a FAT32 filesystem. It balances low-level kernel exploration with a surprisingly capable userspace.
|
||||
BoredOS is a x86_64 operating system featuring a custom Desktop Environment (DE), a dedicated Window Manager (BoredWM), and a FAT32 filesystem. It balances low-level kernel exploration with a surprisingly capable userspace.
|
||||
|
||||

|
||||
> [!NOTE]
|
||||
|
||||
@@ -38,11 +38,11 @@ The WM acts as the central hub for input routing.
|
||||
|
||||
With the introduction of Symmetric Multi-Processing (SMP), the Window Manager (WM) was redesigned to ensure stability and high performance across multiple cores.
|
||||
|
||||
1. **Global GUI Lock (`wm_lock`)**: To prevent race conditions when multiple cores attempt to create windows, move cursors, or update pixels, the WM utilizes a central spinlock. All `GUI_CMD` system calls are protected by this lock.
|
||||
2. **Deferred Rendering**: Previously, the desktop was repainted inside the timer interrupt. On multi-core systems, this caused severe "core starvation" as all other CPUs would spin waiting for the GUI lock during the long draw cycle.
|
||||
3. **Kernel Loop Integration**: Final screen composition (`wm_paint`) is now deferred to the main kernel idle loop on the Bootstrap Processor (BSP). This allows application cores to continue processing logic while the GUI asynchronously flips the framebuffer.
|
||||
1. **Granular Window Locks**: Each `Window` object possesses its own `spinlock_t lock;`. User applications concurrently draw directly into their own window buffers without stalling the rest of the system. The global `wm_lock` is reserved strictly for altering global structures like window z-order or syncing buffers to the screen compositing layer.
|
||||
2. **Per-CPU Rendering State**: To facilitate simultaneous GUI system calls across all CPU cores, the low-level rendering context (`g_render_target` array) is isolated per-CPU using the core ID. This allows completely lockless multi-core pixel rasterization, drastically reducing rendering bottlenecks.
|
||||
3. **Deferred Compositing**: Final screen composition (`wm_paint`) is scheduled to the main kernel idle loop on the Bootstrap Processor (BSP). This enables application cores to continue processing logic seamlessly while the GUI asynchronously handles flipping the physical framebuffer.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Because rendering is now asynchronous to the timer, application performance is significantly higher as they are no longer bottlenecked by interrupt-context drawing.
|
||||
> Because application rendering (rasterizing geometry into a window's backbuffer) is SMP-safe and lock-free across cores, GUI performance scales linearly with the number of CPUs active.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
sed -i '' -e 's/asm volatile("pushfq; pop %0; cli" : "=r"(rflags));/rflags = wm_lock_acquire();/g' src/sys/syscall.c
|
||||
sed -i '' -e 's/asm volatile("push %0; popfq" : : "r"(rflags));/wm_lock_release(rflags);/g' src/sys/syscall.c
|
||||
sed -i '' -e 's/asm volatile("pushfq; pop %0; cli" : "=r"(rflags));/rflags = wm_lock_acquire();/g' src/wm/wm.c
|
||||
sed -i '' -e 's/asm volatile("push %0; popfq" : : "r"(rflags));/wm_lock_release(rflags);/g' src/wm/wm.c
|
||||
sed -i '' -e 's/uint64_t rflags;/uint64_t rflags;/g' src/sys/syscall.c
|
||||
echo "Done"
|
||||
@@ -45,7 +45,7 @@ isr%2_wrapper:
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; Save SSE/FPU state
|
||||
; Save SSE/FPU state (fxsave requires 16-byte alignment)
|
||||
sub rsp, 512
|
||||
fxsave [rsp]
|
||||
|
||||
@@ -164,7 +164,7 @@ exception_common:
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; Save SSE/FPU state
|
||||
; Save SSE/FPU state (fxsave requires 16-byte alignment)
|
||||
sub rsp, 512
|
||||
fxsave [rsp]
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "wm.h"
|
||||
#include "io.h"
|
||||
#include "fat32.h"
|
||||
#include "tar.h"
|
||||
#include "memory_manager.h"
|
||||
#include "platform.h"
|
||||
#include "wallpaper.h"
|
||||
@@ -210,6 +211,15 @@ void kmain(void) {
|
||||
if (fs_starts_with(clean_path, "boot():")) clean_path += 7;
|
||||
else if (fs_starts_with(clean_path, "boot:///")) clean_path += 8;
|
||||
|
||||
int len = 0;
|
||||
while(clean_path[len]) len++;
|
||||
|
||||
if (len >= 4 && clean_path[len-4] == '.' && clean_path[len-3] == 't' && clean_path[len-2] == 'a' && clean_path[len-1] == 'r') {
|
||||
serial_write("[DEBUG] Parsing TAR initrd: ");
|
||||
serial_write(clean_path);
|
||||
serial_write("\n");
|
||||
tar_parse(mod->address, mod->size);
|
||||
} else {
|
||||
char dir_path[256];
|
||||
int last_slash = -1;
|
||||
for (int j = 0; clean_path[j]; j++) {
|
||||
@@ -228,6 +238,7 @@ void kmain(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize fonts now that FAT32 and modules are loaded
|
||||
uint64_t current_rsp;
|
||||
|
||||
@@ -13,10 +13,10 @@ void get_os_info(os_info_t *info) {
|
||||
for (size_t i = 0; i < sizeof(os_info_t); i++) p[i] = 0;
|
||||
|
||||
const char *os_name = "BoredOS";
|
||||
const char *os_version = "1.72";
|
||||
const char *os_codename = "Retrowave";
|
||||
const char *os_version = "26.4";
|
||||
const char *os_codename = "Geometry";
|
||||
const char *kernel_name = "Boredkernel";
|
||||
const char *kernel_version = "3.1.2";
|
||||
const char *kernel_version = "3.2.3";
|
||||
const char *build_date = __DATE__;
|
||||
const char *build_time = __TIME__;
|
||||
const char *build_arch = "x86_64";
|
||||
|
||||
@@ -232,6 +232,16 @@ static uint32_t ramfs_allocate_cluster(void) {
|
||||
return cluster;
|
||||
}
|
||||
|
||||
static int ramfs_count_files_in_dir(const char *normalized_path) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < MAX_FILES; i++) {
|
||||
if (files[i].used && fs_strcmp(files[i].parent_path, normalized_path) == 0) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static bool check_desktop_limit(const char *normalized_path) {
|
||||
if (desktop_file_limit < 0) return true;
|
||||
if (fs_strlen(normalized_path) > 9 &&
|
||||
@@ -245,10 +255,8 @@ static bool check_desktop_limit(const char *normalized_path) {
|
||||
if (*p == '/') return true;
|
||||
p++;
|
||||
}
|
||||
FAT32_FileInfo *info = (FAT32_FileInfo*)kmalloc(256 * sizeof(FAT32_FileInfo));
|
||||
if (!info) return true;
|
||||
int count = fat32_list_directory("/Desktop", info, 256);
|
||||
kfree(info);
|
||||
|
||||
int count = ramfs_count_files_in_dir("/Desktop");
|
||||
if (count >= desktop_file_limit) return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
128
src/fs/tar.c
Normal file
128
src/fs/tar.c
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||
#include "tar.h"
|
||||
#include "fat32.h"
|
||||
|
||||
// The standard TAR header block is 512 bytes.
|
||||
struct tar_header {
|
||||
char filename[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char chksum[8];
|
||||
char typeflag;
|
||||
char linkname[100];
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char prefix[155];
|
||||
char pad[12];
|
||||
} __attribute__((packed));
|
||||
|
||||
// Helper: parse tar octal field representation
|
||||
static uint64_t tar_parse_octal(const char *str, int size) {
|
||||
uint64_t result = 0;
|
||||
while (size-- > 0) {
|
||||
if (*str >= '0' && *str <= '7') {
|
||||
result = (result << 3) + (*str - '0');
|
||||
}
|
||||
str++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper: Make directories sequentially for nested paths
|
||||
static void tar_mkdir_recursive(const char *path) {
|
||||
char temp[256];
|
||||
int i = 0;
|
||||
if (path[0] == '/') {
|
||||
temp[0] = '/';
|
||||
i = 1;
|
||||
}
|
||||
while (path[i] && i < 255) {
|
||||
temp[i] = path[i];
|
||||
if (path[i] == '/') {
|
||||
temp[i] = '\0';
|
||||
fat32_mkdir(temp);
|
||||
temp[i] = '/';
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i > 0 && temp[i - 1] != '/') {
|
||||
temp[i] = '\0';
|
||||
fat32_mkdir(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void tar_parse(void *archive, uint64_t archive_size) {
|
||||
uint8_t *ptr = (uint8_t *)archive;
|
||||
uint8_t *end = ptr + archive_size;
|
||||
|
||||
while (ptr + 512 <= end) {
|
||||
struct tar_header *header = (struct tar_header *)ptr;
|
||||
|
||||
// End of archive is marked by empty blocks
|
||||
if (header->filename[0] == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t file_size = tar_parse_octal(header->size, 11);
|
||||
|
||||
char full_path[256];
|
||||
// Ensure path starts with a '/' for VFS consistency
|
||||
if (header->filename[0] != '/') {
|
||||
full_path[0] = '/';
|
||||
int j = 0;
|
||||
while (header->filename[j] && j < 254) {
|
||||
full_path[j + 1] = header->filename[j];
|
||||
j++;
|
||||
}
|
||||
full_path[j + 1] = '\0';
|
||||
} else {
|
||||
int j = 0;
|
||||
while (header->filename[j] && j < 255) {
|
||||
full_path[j] = header->filename[j];
|
||||
j++;
|
||||
}
|
||||
full_path[j] = '\0';
|
||||
}
|
||||
|
||||
if (header->typeflag == '5') {
|
||||
// It's a directory
|
||||
tar_mkdir_recursive(full_path);
|
||||
} else if (header->typeflag == '0' || header->typeflag == '\0') {
|
||||
// It's a normal file
|
||||
// First ensure the parent directory exists
|
||||
char parent_path[256];
|
||||
int last_slash = -1;
|
||||
for (int j = 0; full_path[j]; j++) {
|
||||
parent_path[j] = full_path[j];
|
||||
if (full_path[j] == '/') {
|
||||
last_slash = j;
|
||||
}
|
||||
}
|
||||
if (last_slash > 0) {
|
||||
parent_path[last_slash] = '\0';
|
||||
tar_mkdir_recursive(parent_path);
|
||||
}
|
||||
|
||||
// Extract the file data block directly into the VFS
|
||||
FAT32_FileHandle *fh = fat32_open(full_path, "w");
|
||||
if (fh && fh->valid) {
|
||||
fat32_write(fh, ptr + 512, file_size);
|
||||
fat32_close(fh);
|
||||
}
|
||||
}
|
||||
|
||||
// Advance pointer to the next file header
|
||||
// Header block (512) + File data (padded to 512-byte multiples)
|
||||
uint64_t data_blocks = (file_size + 511) / 512;
|
||||
ptr += 512 + (data_blocks * 512);
|
||||
}
|
||||
}
|
||||
13
src/fs/tar.h
Normal file
13
src/fs/tar.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||
#ifndef TAR_H
|
||||
#define TAR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Parse a TAR archive located in memory and extract its contents into the current filesystem (fatal32 RAM disk).
|
||||
void tar_parse(void *archive, uint64_t archive_size);
|
||||
|
||||
#endif
|
||||
@@ -224,7 +224,7 @@ int network_tcp_connect(const ipv4_address_t *ip, uint16_t port) {
|
||||
|
||||
uint32_t start = sys_now();
|
||||
asm volatile("sti");
|
||||
while (sys_now() - start < 5000) { // 5 second timeout
|
||||
while (sys_now() - start < 15000) { // 15 second timeout
|
||||
network_poll_internal();
|
||||
if (tcp_connect_done) { asm volatile("cli"); network_processing = 0; return 0; }
|
||||
if (tcp_connect_error) { asm volatile("cli"); network_processing = 0; return -1; }
|
||||
@@ -253,7 +253,7 @@ int network_tcp_recv(void *buf, size_t max_len) {
|
||||
if (tcp_closed) { network_processing = 0; return 0; } // End of stream
|
||||
uint32_t start = sys_now();
|
||||
asm volatile("sti");
|
||||
while (sys_now() - start < 5000) { // 5 second timeout
|
||||
while (sys_now() - start < 30000) { // 30 second timeout
|
||||
network_poll_internal();
|
||||
if (tcp_recv_queue) break;
|
||||
if (tcp_closed) break;
|
||||
@@ -281,7 +281,10 @@ int network_tcp_recv_nb(void *buf, size_t max_len) {
|
||||
if (network_processing) return -1;
|
||||
network_processing = 1;
|
||||
|
||||
network_poll_internal();
|
||||
|
||||
if (!tcp_recv_queue) {
|
||||
if (tcp_closed) { network_processing = 0; return -2; }
|
||||
network_processing = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ int process_count = 0;
|
||||
static process_t* current_process[MAX_CPUS_SCHED] = {0}; // Per-CPU
|
||||
static uint32_t next_pid = 0;
|
||||
static void *free_kernel_stack_later = NULL;
|
||||
static uint64_t free_pml4_later = 0;
|
||||
static spinlock_t runqueue_lock = SPINLOCK_INIT;
|
||||
static uint32_t next_cpu_assign = 1; // Round-robin CPU assignment (start from CPU 1)
|
||||
|
||||
@@ -379,6 +380,11 @@ uint64_t process_schedule(uint64_t current_rsp) {
|
||||
kfree(free_kernel_stack_later);
|
||||
free_kernel_stack_later = NULL;
|
||||
}
|
||||
if (free_pml4_later) {
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
paging_destroy_user_pml4_phys(free_pml4_later);
|
||||
free_pml4_later = 0;
|
||||
}
|
||||
|
||||
uint32_t my_cpu = smp_this_cpu_id();
|
||||
process_t *cur = current_process[my_cpu];
|
||||
@@ -469,6 +475,9 @@ static void process_cleanup_inner(process_t *proc) {
|
||||
extern void cmd_process_finished(void);
|
||||
cmd_process_finished();
|
||||
|
||||
extern void network_cleanup(void);
|
||||
network_cleanup();
|
||||
|
||||
extern void network_cleanup_pcb(void *pcb);
|
||||
// TODO: We need per-process PCB tracking to call this safely
|
||||
// For now, let's NOT call global network_cleanup
|
||||
@@ -521,9 +530,8 @@ void process_terminate(process_t *to_delete) {
|
||||
to_delete->cpu_affinity = 0xFFFFFFFF;
|
||||
|
||||
if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc);
|
||||
if (to_delete->kernel_stack_alloc) {
|
||||
kfree(to_delete->kernel_stack_alloc);
|
||||
}
|
||||
// Defer kernel stack until we switch away from it
|
||||
to_delete->kernel_stack_alloc = NULL;
|
||||
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
if (to_delete->pml4_phys && to_delete->is_user) {
|
||||
@@ -586,6 +594,8 @@ uint64_t process_terminate_current(void) {
|
||||
// Mark slot as free
|
||||
to_delete->pid = 0xFFFFFFFF;
|
||||
to_delete->cpu_affinity = 0xFFFFFFFF;
|
||||
to_delete->ui_window = NULL;
|
||||
to_delete->is_terminal_proc = false;
|
||||
|
||||
// 4. Load context for the NEXT process
|
||||
if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) {
|
||||
|
||||
@@ -9,17 +9,16 @@
|
||||
#include "platform.h"
|
||||
#include "paging.h"
|
||||
#include "process.h"
|
||||
#include "work_queue.h"
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
extern void serial_write_num(uint32_t n);
|
||||
extern void serial_write_hex(uint64_t n);
|
||||
|
||||
// --- Dynamically allocated per-CPU state ---
|
||||
static cpu_state_t *cpu_states = NULL; // Array[cpu_count]
|
||||
static cpu_state_t *cpu_states = NULL;
|
||||
static uint32_t total_cpus = 0;
|
||||
static uint32_t bsp_lapic_id = 0;
|
||||
|
||||
// Get LAPIC ID via CPUID leaf 0x01 (works on all x86_64)
|
||||
static uint32_t read_lapic_id(void) {
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1));
|
||||
@@ -44,49 +43,37 @@ cpu_state_t *smp_get_cpu(uint32_t cpu_id) {
|
||||
return &cpu_states[cpu_id];
|
||||
}
|
||||
|
||||
// --- AP Entry Point ---
|
||||
// Called by Limine on each Application Processor.
|
||||
// The limine_smp_info* is passed as a parameter.
|
||||
static void ap_entry(struct limine_smp_info *info) {
|
||||
// 1. Figure out which CPU we are
|
||||
uint32_t my_id = (uint32_t)(info->extra_argument);
|
||||
|
||||
// 2. Enable FPU/SSE on this core (same as BSP does in platform_init)
|
||||
uint64_t cr0;
|
||||
asm volatile("mov %%cr0, %0" : "=r"(cr0));
|
||||
cr0 &= ~(1ULL << 2); // Clear EM
|
||||
cr0 |= (1ULL << 1); // Set MP
|
||||
cr0 |= (1ULL << 5); // Set NE
|
||||
cr0 &= ~(1ULL << 2);
|
||||
cr0 |= (1ULL << 1);
|
||||
cr0 |= (1ULL << 5);
|
||||
asm volatile("mov %0, %%cr0" : : "r"(cr0));
|
||||
|
||||
uint64_t cr4;
|
||||
asm volatile("mov %%cr4, %0" : "=r"(cr4));
|
||||
cr4 |= (1ULL << 9); // OSFXSR
|
||||
cr4 |= (1ULL << 10); // OSXMMEXCPT
|
||||
cr4 |= (1ULL << 9);
|
||||
cr4 |= (1ULL << 10);
|
||||
asm volatile("mov %0, %%cr4" : : "r"(cr4));
|
||||
asm volatile("fninit");
|
||||
|
||||
// 3. Load the shared GDT and properly reload all segments (including CS=0x08)
|
||||
extern struct gdt_ptr gdtr;
|
||||
extern void gdt_flush(uint64_t);
|
||||
gdt_flush((uint64_t)&gdtr);
|
||||
|
||||
// 4. Load per-CPU TSS
|
||||
gdt_load_ap_tss(my_id);
|
||||
|
||||
// 5. Load the shared IDT
|
||||
extern void idt_load(void);
|
||||
idt_load();
|
||||
|
||||
// 6. Load the kernel page tables (same CR3 as BSP — shared kernel space)
|
||||
uint64_t kernel_cr3 = paging_get_pml4_phys();
|
||||
asm volatile("mov %0, %%cr3" : : "r"(kernel_cr3));
|
||||
|
||||
// 7. Enable LAPIC on this core so it can receive IPIs
|
||||
extern void lapic_enable(void);
|
||||
lapic_enable();
|
||||
|
||||
// 8. Mark ourselves as online
|
||||
cpu_states[my_id].online = true;
|
||||
|
||||
serial_write("[SMP] AP ");
|
||||
@@ -95,25 +82,17 @@ static void ap_entry(struct limine_smp_info *info) {
|
||||
serial_write_num(cpu_states[my_id].lapic_id);
|
||||
serial_write(")\n");
|
||||
|
||||
// 9. Initialize the current_process pointer for this CPU
|
||||
// Create a dedicated idle task for this AP (PID 0 is reserved for the BSP)
|
||||
process_t *ap_idle = process_create(NULL, false); // Idle process
|
||||
process_t *ap_idle = process_create(NULL, false);
|
||||
ap_idle->cpu_affinity = my_id;
|
||||
process_set_current_for_cpu(my_id, ap_idle);
|
||||
|
||||
// 10. Enable interrupts and enter idle halt loop.
|
||||
// APs will be woken by scheduling IPIs from BSP (vector 0x41).
|
||||
// The IPI handler does context switching for this CPU's processes.
|
||||
asm volatile("sti");
|
||||
|
||||
// Idle loop — APs halt and wait for IPI
|
||||
for (;;) { asm volatile("hlt"); }
|
||||
work_queue_drain_loop();
|
||||
}
|
||||
|
||||
// --- SMP Initialization ---
|
||||
uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||
if (!smp_resp || smp_resp->cpu_count <= 1) {
|
||||
// Single CPU system — just set up the BSP entry
|
||||
total_cpus = 1;
|
||||
cpu_states = (cpu_state_t *)kmalloc(sizeof(cpu_state_t));
|
||||
if (!cpu_states) return 1;
|
||||
@@ -135,7 +114,6 @@ uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||
serial_write_num(bsp_lapic_id);
|
||||
serial_write("\n");
|
||||
|
||||
// Allocate per-CPU state array
|
||||
cpu_states = (cpu_state_t *)kmalloc(total_cpus * sizeof(cpu_state_t));
|
||||
if (!cpu_states) {
|
||||
serial_write("[SMP] ERROR: Failed to allocate CPU state array!\n");
|
||||
@@ -145,10 +123,8 @@ uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||
extern void mem_memset(void *, int, size_t);
|
||||
mem_memset(cpu_states, 0, total_cpus * sizeof(cpu_state_t));
|
||||
|
||||
// Initialize per-CPU GDT/TSS entries for all CPUs
|
||||
gdt_init_ap_tss(total_cpus);
|
||||
|
||||
// Fill in CPU state and start APs
|
||||
uint32_t bsp_index = 0;
|
||||
for (uint32_t i = 0; i < total_cpus; i++) {
|
||||
struct limine_smp_info *cpu = smp_resp->cpus[i];
|
||||
@@ -156,7 +132,6 @@ uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||
cpu_states[i].lapic_id = cpu->lapic_id;
|
||||
|
||||
if (cpu->lapic_id == bsp_lapic_id) {
|
||||
// This is the BSP — already running
|
||||
cpu_states[i].online = true;
|
||||
bsp_index = i;
|
||||
serial_write("[SMP] BSP CPU ");
|
||||
@@ -165,7 +140,6 @@ uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||
serial_write_num(cpu->lapic_id);
|
||||
serial_write(") online\n");
|
||||
} else {
|
||||
// Allocate a kernel stack for this AP
|
||||
void *ap_stack = kmalloc_aligned(65536, 65536);
|
||||
if (!ap_stack) {
|
||||
serial_write("[SMP] ERROR: Failed to allocate AP stack!\n");
|
||||
@@ -175,27 +149,18 @@ uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||
cpu_states[i].kernel_stack_alloc = ap_stack;
|
||||
cpu_states[i].online = false;
|
||||
|
||||
// Set extra_argument so the AP knows its index
|
||||
cpu->extra_argument = i;
|
||||
|
||||
// Tell Limine to start this AP. Limine sets up the AP's stack
|
||||
// from extra_argument's stack, but we need the goto_address.
|
||||
// Limine will jump to ap_entry with the AP's limine_smp_info*.
|
||||
// Important: Limine creates a temporary stack for the AP, and the
|
||||
// goto_address is where the AP starts executing.
|
||||
|
||||
serial_write("[SMP] Starting AP ");
|
||||
serial_write_num(i);
|
||||
serial_write(" (LAPIC ");
|
||||
serial_write_num(cpu->lapic_id);
|
||||
serial_write(")...\n");
|
||||
|
||||
// This atomic write triggers the AP to start executing at ap_entry
|
||||
__atomic_store_n(&cpu->goto_address, ap_entry, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all APs to come online (with timeout)
|
||||
volatile uint32_t timeout = 10000000;
|
||||
uint32_t online_count = 0;
|
||||
while (timeout-- > 0) {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "wm.h"
|
||||
#include "fat32.h"
|
||||
#include "paging.h"
|
||||
#include "work_queue.h"
|
||||
#include "smp.h"
|
||||
#include "platform.h"
|
||||
#include "io.h"
|
||||
#include "pci.h"
|
||||
@@ -19,6 +21,8 @@
|
||||
#include "font_manager.h"
|
||||
#include "graphics.h"
|
||||
|
||||
extern bool ps2_ctrl_pressed;
|
||||
|
||||
// Read MSR
|
||||
static inline uint64_t rdmsr(uint32_t msr) {
|
||||
uint32_t low, high;
|
||||
@@ -34,55 +38,90 @@ static inline void wrmsr(uint32_t msr, uint64_t value) {
|
||||
}
|
||||
|
||||
extern void isr128_wrapper(void);
|
||||
extern void* kmalloc(size_t size);
|
||||
extern void kfree(void* ptr);
|
||||
|
||||
typedef struct {
|
||||
void (*fn)(void *);
|
||||
void *arg;
|
||||
uint64_t pml4_phys;
|
||||
volatile int *completion_counter;
|
||||
} smp_user_task_t;
|
||||
|
||||
static void smp_user_wrapper(void *arg) {
|
||||
smp_user_task_t *task = (smp_user_task_t *)arg;
|
||||
if (!task) return;
|
||||
|
||||
uint64_t old_cr3;
|
||||
asm volatile("mov %%cr3, %0" : "=r"(old_cr3));
|
||||
|
||||
// Switch to user address space if necessary
|
||||
bool switch_cr3 = (task->pml4_phys != 0 && task->pml4_phys != old_cr3);
|
||||
if (switch_cr3) {
|
||||
asm volatile("mov %0, %%cr3" :: "r"(task->pml4_phys) : "memory");
|
||||
}
|
||||
|
||||
if (task->fn) {
|
||||
task->fn(task->arg);
|
||||
}
|
||||
|
||||
if (switch_cr3) {
|
||||
asm volatile("mov %0, %%cr3" :: "r"(old_cr3) : "memory");
|
||||
}
|
||||
|
||||
if (task->completion_counter) {
|
||||
__sync_fetch_and_add(task->completion_counter, -1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void syscall_init(void) {
|
||||
// SMP-Safe System Calls using int 0x80 (configured in idt.c)
|
||||
}
|
||||
|
||||
static void user_window_close(Window *win) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_CLOSE };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_paint(Window *win) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_PAINT };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_click(Window *win, int x, int y) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_CLICK, .arg1 = x, .arg2 = y };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_right_click(Window *win, int x, int y) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_RIGHT_CLICK, .arg1 = x, .arg2 = y };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_mouse_down(Window *win, int x, int y) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_DOWN, .arg1 = x, .arg2 = y };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_mouse_up(Window *win, int x, int y) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_UP, .arg1 = x, .arg2 = y };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_mouse_move(Window *win, int x, int y, uint8_t buttons) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_MOVE, .arg1 = x, .arg2 = y, .arg3 = buttons };
|
||||
process_push_gui_event(proc, &ev);
|
||||
@@ -90,24 +129,24 @@ static void user_window_mouse_move(Window *win, int x, int y, uint8_t buttons) {
|
||||
|
||||
// Helper function for WM to send mouse events
|
||||
void syscall_send_mouse_move_event(Window *win, int x, int y, uint8_t buttons) {
|
||||
if (!win || !win->data) return;
|
||||
if (!win) return;
|
||||
user_window_mouse_move(win, x, y, buttons);
|
||||
}
|
||||
|
||||
void syscall_send_mouse_down_event(Window *win, int x, int y) {
|
||||
if (!win || !win->data) return;
|
||||
if (!win) return;
|
||||
user_window_mouse_down(win, x, y);
|
||||
}
|
||||
|
||||
void syscall_send_mouse_up_event(Window *win, int x, int y) {
|
||||
if (!win || !win->data) return;
|
||||
if (!win) return;
|
||||
user_window_mouse_up(win, x, y);
|
||||
}
|
||||
|
||||
static void user_window_key(Window *win, char c, bool pressed) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = pressed ? GUI_EVENT_KEY : GUI_EVENT_KEYUP, .arg1 = (int)c };
|
||||
gui_event_t ev = { .type = pressed ? GUI_EVENT_KEY : GUI_EVENT_KEYUP, .arg1 = (int)c, .arg3 = (int)ps2_ctrl_pressed };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
@@ -119,15 +158,18 @@ static void user_window_resize(Window *win, int w, int h) {
|
||||
extern void kfree(void* ptr);
|
||||
extern void serial_write(const char *str);
|
||||
|
||||
|
||||
if (win->pixels) kfree(win->pixels);
|
||||
if (win->comp_pixels) kfree(win->comp_pixels);
|
||||
|
||||
win->pixels = (uint32_t *)kmalloc(w * h * sizeof(uint32_t));
|
||||
win->comp_pixels = (uint32_t *)kmalloc(w * h * sizeof(uint32_t));
|
||||
|
||||
win->w = w;
|
||||
win->h = h;
|
||||
|
||||
if (win->pixels) {
|
||||
for (int i = 0; i < w * h; i++) win->pixels[i] = 0;
|
||||
extern void mem_memset(void *dest, int val, size_t len);
|
||||
mem_memset(win->pixels, 0, w * h * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +257,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
win->cursor_pos = 0;
|
||||
win->data = proc;
|
||||
win->font = NULL;
|
||||
win->lock = SPINLOCK_INIT;
|
||||
|
||||
serial_write("Kernel: Dims initialized.\n");
|
||||
|
||||
@@ -269,7 +312,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
extern void graphics_set_render_target(uint32_t *buffer, int w, int h);
|
||||
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
bool use_wm_lock = (win->pixels == NULL);
|
||||
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||
|
||||
if (win->pixels) {
|
||||
// Strict user-to-window relative clamping
|
||||
@@ -289,7 +334,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
draw_rect(win->x + params[0], win->y + params[1], params[2], params[3], color);
|
||||
}
|
||||
|
||||
wm_lock_release(rflags);
|
||||
if (use_wm_lock) wm_lock_release(rflags);
|
||||
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||
}
|
||||
} else if (cmd == GUI_CMD_DRAW_ROUNDED_RECT_FILLED) {
|
||||
Window *win = (Window *)arg2;
|
||||
@@ -303,7 +349,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
extern void graphics_set_render_target(uint32_t *buffer, int w, int h);
|
||||
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
bool use_wm_lock = (win->pixels == NULL);
|
||||
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||
|
||||
if (win->pixels) {
|
||||
int rx = (int)params[0]; int ry = (int)params[1];
|
||||
@@ -321,7 +369,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
}
|
||||
}
|
||||
|
||||
wm_lock_release(rflags);
|
||||
if (use_wm_lock) wm_lock_release(rflags);
|
||||
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||
}
|
||||
} else if (cmd == GUI_CMD_DRAW_STRING) {
|
||||
Window *win = (Window *)arg2;
|
||||
@@ -344,7 +393,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
kernel_str[i] = 0;
|
||||
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
bool use_wm_lock = (win->pixels == NULL);
|
||||
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||
|
||||
ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf();
|
||||
|
||||
@@ -380,7 +431,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
}
|
||||
}
|
||||
|
||||
wm_lock_release(rflags);
|
||||
if (use_wm_lock) wm_lock_release(rflags);
|
||||
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||
}
|
||||
} else if (cmd == 10) { // GUI_CMD_DRAW_STRING_BITMAP
|
||||
Window *win = (Window *)arg2;
|
||||
@@ -403,7 +455,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
kernel_str[i] = 0;
|
||||
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
bool use_wm_lock = (win->pixels == NULL);
|
||||
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||
|
||||
if (win->pixels) {
|
||||
if (ux >= -100 && ux < win->w && uy >= -100 && uy < (win->h - 20)) {
|
||||
@@ -415,7 +469,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
draw_string_bitmap(win->x + ux, win->y + uy, kernel_str, color);
|
||||
}
|
||||
|
||||
wm_lock_release(rflags);
|
||||
if (use_wm_lock) wm_lock_release(rflags);
|
||||
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||
}
|
||||
} else if (cmd == 11) { // GUI_CMD_DRAW_STRING_SCALED
|
||||
Window *win = (Window *)arg2;
|
||||
@@ -442,7 +497,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
kernel_str[i] = 0;
|
||||
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
bool use_wm_lock = (win->pixels == NULL);
|
||||
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||
|
||||
ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf();
|
||||
|
||||
@@ -478,7 +535,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
}
|
||||
}
|
||||
|
||||
wm_lock_release(rflags);
|
||||
if (use_wm_lock) wm_lock_release(rflags);
|
||||
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||
}
|
||||
} else if (cmd == 18) { // GUI_CMD_DRAW_STRING_SCALED_SLOPED
|
||||
Window *win = (Window *)arg2;
|
||||
@@ -515,7 +573,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
kernel_str[i] = 0;
|
||||
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
bool use_wm_lock = (win->pixels == NULL);
|
||||
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||
|
||||
ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf();
|
||||
|
||||
@@ -553,7 +613,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
}
|
||||
}
|
||||
|
||||
wm_lock_release(rflags);
|
||||
if (use_wm_lock) wm_lock_release(rflags);
|
||||
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||
}
|
||||
} else if (cmd == GUI_CMD_DRAW_IMAGE) {
|
||||
Window *win = (Window *)arg2;
|
||||
@@ -564,14 +625,17 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
for (int i = 0; i < 4; i++) params[i] = u_params[i];
|
||||
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
bool use_wm_lock = (win->pixels == NULL);
|
||||
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||
|
||||
if (win->pixels) {
|
||||
int rx = (int)params[0]; int ry = (int)params[1];
|
||||
int rw = (int)params[2]; int rh = (int)params[3];
|
||||
|
||||
int src_w = rw;
|
||||
int src_x_offset = 0;
|
||||
int src_y_offset = 0;
|
||||
|
||||
if (rx < 0) { src_x_offset = -rx; rw += rx; rx = 0; }
|
||||
if (ry < 0) { src_y_offset = -ry; rh += ry; ry = 0; }
|
||||
if (rx + rw > win->w) rw = win->w - rx;
|
||||
@@ -580,15 +644,13 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
if (rw > 0 && rh > 0) {
|
||||
for (int y = 0; y < rh; y++) {
|
||||
uint32_t *dest = &win->pixels[(ry + y) * win->w + rx];
|
||||
uint32_t *src = &image_data[(src_y_offset + y) * (int)params[2] + src_x_offset];
|
||||
uint32_t *src = &image_data[(src_y_offset + y) * src_w + src_x_offset];
|
||||
for (int x = 0; x < rw; x++) {
|
||||
uint32_t s = src[x];
|
||||
uint8_t alpha = (s >> 24) & 0xFF;
|
||||
if (alpha == 0xFF) {
|
||||
dest[x] = s;
|
||||
} else if (alpha == 0) {
|
||||
// Skip
|
||||
} else {
|
||||
} else if (alpha > 0) {
|
||||
uint32_t d = dest[x];
|
||||
uint32_t rb = ((s & 0xFF00FF) * alpha + (d & 0xFF00FF) * (255 - alpha)) >> 8;
|
||||
uint32_t g = ((s & 0x00FF00) * alpha + (d & 0x00FF00) * (255 - alpha)) >> 8;
|
||||
@@ -599,7 +661,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
}
|
||||
}
|
||||
|
||||
wm_lock_release(rflags);
|
||||
if (use_wm_lock) wm_lock_release(rflags);
|
||||
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||
}
|
||||
} else if (cmd == GUI_CMD_MARK_DIRTY) {
|
||||
uint64_t rflags = wm_lock_acquire();
|
||||
@@ -611,8 +674,10 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
|
||||
// Dual-buffer commit: copy pixels to comp_pixels
|
||||
if (win->pixels && win->comp_pixels) {
|
||||
uint64_t win_rflags = spinlock_acquire_irqsave(&win->lock);
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
mem_memcpy(win->comp_pixels, win->pixels, (size_t)win->w * (win->h - 20) * 4);
|
||||
spinlock_release_irqrestore(&win->lock, win_rflags);
|
||||
}
|
||||
wm_mark_dirty(win->x + (int)params[0], win->y + (int)params[1], (int)params[2], (int)params[3]);
|
||||
}
|
||||
@@ -1251,6 +1316,36 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
extern void get_os_info(os_info_t *info);
|
||||
get_os_info(info);
|
||||
return 0;
|
||||
} else if (cmd == SYSTEM_CMD_PARALLEL_RUN) {
|
||||
void (*user_fn)(void*) = (void (*)(void*))arg2;
|
||||
void **args = (void **)arg3;
|
||||
int count = (int)arg4;
|
||||
|
||||
if (count <= 0) return 0;
|
||||
if (count > 64) count = 64;
|
||||
|
||||
volatile int completion_counter = count;
|
||||
uint64_t current_pml4 = proc->pml4_phys;
|
||||
|
||||
smp_user_task_t tasks[64];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
tasks[i].fn = user_fn;
|
||||
tasks[i].arg = args[i];
|
||||
tasks[i].pml4_phys = current_pml4;
|
||||
tasks[i].completion_counter = &completion_counter;
|
||||
|
||||
extern void work_queue_submit(void (*fn)(void*), void *arg);
|
||||
work_queue_submit(smp_user_wrapper, &tasks[i]);
|
||||
}
|
||||
|
||||
extern bool work_queue_drain_one(void);
|
||||
while (completion_counter > 0) {
|
||||
if (!work_queue_drain_one()) {
|
||||
asm volatile("pause");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ typedef struct {
|
||||
#define SYSTEM_CMD_SLEEP 46
|
||||
#define SYSTEM_CMD_SET_RESOLUTION 47
|
||||
#define SYSTEM_CMD_GET_OS_INFO 49
|
||||
#define SYSTEM_CMD_PARALLEL_RUN 50
|
||||
|
||||
void syscall_init(void);
|
||||
uint64_t syscall_handler_c(registers_t *regs);
|
||||
|
||||
@@ -11,7 +11,7 @@ LDFLAGS = -m elf_x86_64 -nostdlib -static -no-pie -Ttext=0x40000000 --no-dynamic
|
||||
BIN_DIR = bin
|
||||
|
||||
LIBC_SOURCES = $(wildcard libc/*.c)
|
||||
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o
|
||||
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o
|
||||
|
||||
VPATH = cli gui sys games libc net
|
||||
vpath %.c cli gui sys games libc net
|
||||
@@ -34,6 +34,9 @@ $(BIN_DIR)/crt0.o: crt0.asm
|
||||
$(BIN_DIR)/%.o: libc/%.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BIN_DIR)/libwidget.o: ../wm/libwidget.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BIN_DIR)/stb_image.o: stb_image.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{95A126D2-CC94-4840-BF05-80305041B5FB}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>doomgeneric</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<DisableSpecificWarnings>4146;4996</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<DisableSpecificWarnings>4146;4996</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<DisableSpecificWarnings>4146;4996</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<DisableSpecificWarnings>4146;4996</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="am_map.c" />
|
||||
<ClCompile Include="doomdef.c" />
|
||||
<ClCompile Include="doomgeneric.c" />
|
||||
<ClCompile Include="doomgeneric_win.c" />
|
||||
<ClCompile Include="doomstat.c" />
|
||||
<ClCompile Include="dstrings.c" />
|
||||
<ClCompile Include="dummy.c" />
|
||||
<ClCompile Include="d_event.c" />
|
||||
<ClCompile Include="d_items.c" />
|
||||
<ClCompile Include="d_iwad.c" />
|
||||
<ClCompile Include="d_loop.c" />
|
||||
<ClCompile Include="d_main.c" />
|
||||
<ClCompile Include="d_mode.c" />
|
||||
<ClCompile Include="d_net.c" />
|
||||
<ClCompile Include="f_finale.c" />
|
||||
<ClCompile Include="f_wipe.c" />
|
||||
<ClCompile Include="gusconf.c" />
|
||||
<ClCompile Include="g_game.c" />
|
||||
<ClCompile Include="hu_lib.c" />
|
||||
<ClCompile Include="hu_stuff.c" />
|
||||
<ClCompile Include="icon.c" />
|
||||
<ClCompile Include="info.c" />
|
||||
<ClCompile Include="i_cdmus.c" />
|
||||
<ClCompile Include="i_endoom.c" />
|
||||
<ClCompile Include="i_input.c" />
|
||||
<ClCompile Include="i_joystick.c" />
|
||||
<ClCompile Include="i_scale.c" />
|
||||
<ClCompile Include="i_sound.c" />
|
||||
<ClCompile Include="i_system.c" />
|
||||
<ClCompile Include="i_timer.c" />
|
||||
<ClCompile Include="i_video.c" />
|
||||
<ClCompile Include="memio.c" />
|
||||
<ClCompile Include="m_argv.c" />
|
||||
<ClCompile Include="m_bbox.c" />
|
||||
<ClCompile Include="m_cheat.c" />
|
||||
<ClCompile Include="m_config.c" />
|
||||
<ClCompile Include="m_controls.c" />
|
||||
<ClCompile Include="m_fixed.c" />
|
||||
<ClCompile Include="m_menu.c" />
|
||||
<ClCompile Include="m_misc.c" />
|
||||
<ClCompile Include="m_random.c" />
|
||||
<ClCompile Include="p_ceilng.c" />
|
||||
<ClCompile Include="p_doors.c" />
|
||||
<ClCompile Include="p_enemy.c" />
|
||||
<ClCompile Include="p_floor.c" />
|
||||
<ClCompile Include="p_inter.c" />
|
||||
<ClCompile Include="p_lights.c" />
|
||||
<ClCompile Include="p_map.c" />
|
||||
<ClCompile Include="p_maputl.c" />
|
||||
<ClCompile Include="p_mobj.c" />
|
||||
<ClCompile Include="p_plats.c" />
|
||||
<ClCompile Include="p_pspr.c" />
|
||||
<ClCompile Include="p_saveg.c" />
|
||||
<ClCompile Include="p_setup.c" />
|
||||
<ClCompile Include="p_sight.c" />
|
||||
<ClCompile Include="p_spec.c" />
|
||||
<ClCompile Include="p_switch.c" />
|
||||
<ClCompile Include="p_telept.c" />
|
||||
<ClCompile Include="p_tick.c" />
|
||||
<ClCompile Include="p_user.c" />
|
||||
<ClCompile Include="r_bsp.c" />
|
||||
<ClCompile Include="r_data.c" />
|
||||
<ClCompile Include="r_draw.c" />
|
||||
<ClCompile Include="r_main.c" />
|
||||
<ClCompile Include="r_plane.c" />
|
||||
<ClCompile Include="r_segs.c" />
|
||||
<ClCompile Include="r_sky.c" />
|
||||
<ClCompile Include="r_things.c" />
|
||||
<ClCompile Include="sha1.c" />
|
||||
<ClCompile Include="sounds.c" />
|
||||
<ClCompile Include="statdump.c" />
|
||||
<ClCompile Include="st_lib.c" />
|
||||
<ClCompile Include="st_stuff.c" />
|
||||
<ClCompile Include="s_sound.c" />
|
||||
<ClCompile Include="tables.c" />
|
||||
<ClCompile Include="v_video.c" />
|
||||
<ClCompile Include="wi_stuff.c" />
|
||||
<ClCompile Include="w_checksum.c" />
|
||||
<ClCompile Include="w_file.c" />
|
||||
<ClCompile Include="w_file_stdc.c" />
|
||||
<ClCompile Include="w_main.c" />
|
||||
<ClCompile Include="w_wad.c" />
|
||||
<ClCompile Include="z_zone.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="am_map.h" />
|
||||
<ClInclude Include="config.h" />
|
||||
<ClInclude Include="deh_main.h" />
|
||||
<ClInclude Include="deh_misc.h" />
|
||||
<ClInclude Include="deh_str.h" />
|
||||
<ClInclude Include="doom.h" />
|
||||
<ClInclude Include="doomdata.h" />
|
||||
<ClInclude Include="doomdef.h" />
|
||||
<ClInclude Include="doomfeatures.h" />
|
||||
<ClInclude Include="doomgeneric.h" />
|
||||
<ClInclude Include="doomkeys.h" />
|
||||
<ClInclude Include="doomstat.h" />
|
||||
<ClInclude Include="doomtype.h" />
|
||||
<ClInclude Include="dstrings.h" />
|
||||
<ClInclude Include="d_englsh.h" />
|
||||
<ClInclude Include="d_event.h" />
|
||||
<ClInclude Include="d_items.h" />
|
||||
<ClInclude Include="d_iwad.h" />
|
||||
<ClInclude Include="d_loop.h" />
|
||||
<ClInclude Include="d_main.h" />
|
||||
<ClInclude Include="d_mode.h" />
|
||||
<ClInclude Include="d_player.h" />
|
||||
<ClInclude Include="d_textur.h" />
|
||||
<ClInclude Include="d_think.h" />
|
||||
<ClInclude Include="d_ticcmd.h" />
|
||||
<ClInclude Include="f_finale.h" />
|
||||
<ClInclude Include="f_wipe.h" />
|
||||
<ClInclude Include="gusconf.h" />
|
||||
<ClInclude Include="g_game.h" />
|
||||
<ClInclude Include="hu_lib.h" />
|
||||
<ClInclude Include="hu_stuff.h" />
|
||||
<ClInclude Include="info.h" />
|
||||
<ClInclude Include="i_cdmus.h" />
|
||||
<ClInclude Include="i_endoom.h" />
|
||||
<ClInclude Include="i_joystick.h" />
|
||||
<ClInclude Include="i_scale.h" />
|
||||
<ClInclude Include="i_sound.h" />
|
||||
<ClInclude Include="i_swap.h" />
|
||||
<ClInclude Include="i_system.h" />
|
||||
<ClInclude Include="i_timer.h" />
|
||||
<ClInclude Include="i_video.h" />
|
||||
<ClInclude Include="memio.h" />
|
||||
<ClInclude Include="m_argv.h" />
|
||||
<ClInclude Include="m_bbox.h" />
|
||||
<ClInclude Include="m_cheat.h" />
|
||||
<ClInclude Include="m_config.h" />
|
||||
<ClInclude Include="m_controls.h" />
|
||||
<ClInclude Include="m_fixed.h" />
|
||||
<ClInclude Include="m_menu.h" />
|
||||
<ClInclude Include="m_misc.h" />
|
||||
<ClInclude Include="m_random.h" />
|
||||
<ClInclude Include="net_client.h" />
|
||||
<ClInclude Include="net_dedicated.h" />
|
||||
<ClInclude Include="net_defs.h" />
|
||||
<ClInclude Include="net_gui.h" />
|
||||
<ClInclude Include="net_io.h" />
|
||||
<ClInclude Include="net_loop.h" />
|
||||
<ClInclude Include="net_packet.h" />
|
||||
<ClInclude Include="net_query.h" />
|
||||
<ClInclude Include="net_sdl.h" />
|
||||
<ClInclude Include="net_server.h" />
|
||||
<ClInclude Include="p_inter.h" />
|
||||
<ClInclude Include="p_local.h" />
|
||||
<ClInclude Include="p_mobj.h" />
|
||||
<ClInclude Include="p_pspr.h" />
|
||||
<ClInclude Include="p_saveg.h" />
|
||||
<ClInclude Include="p_setup.h" />
|
||||
<ClInclude Include="p_spec.h" />
|
||||
<ClInclude Include="p_tick.h" />
|
||||
<ClInclude Include="r_bsp.h" />
|
||||
<ClInclude Include="r_data.h" />
|
||||
<ClInclude Include="r_defs.h" />
|
||||
<ClInclude Include="r_draw.h" />
|
||||
<ClInclude Include="r_local.h" />
|
||||
<ClInclude Include="r_main.h" />
|
||||
<ClInclude Include="r_plane.h" />
|
||||
<ClInclude Include="r_segs.h" />
|
||||
<ClInclude Include="r_sky.h" />
|
||||
<ClInclude Include="r_state.h" />
|
||||
<ClInclude Include="r_things.h" />
|
||||
<ClInclude Include="sha1.h" />
|
||||
<ClInclude Include="sounds.h" />
|
||||
<ClInclude Include="statdump.h" />
|
||||
<ClInclude Include="st_lib.h" />
|
||||
<ClInclude Include="st_stuff.h" />
|
||||
<ClInclude Include="s_sound.h" />
|
||||
<ClInclude Include="tables.h" />
|
||||
<ClInclude Include="v_patch.h" />
|
||||
<ClInclude Include="v_video.h" />
|
||||
<ClInclude Include="wi_stuff.h" />
|
||||
<ClInclude Include="w_checksum.h" />
|
||||
<ClInclude Include="w_file.h" />
|
||||
<ClInclude Include="w_main.h" />
|
||||
<ClInclude Include="w_merge.h" />
|
||||
<ClInclude Include="w_wad.h" />
|
||||
<ClInclude Include="z_zone.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,558 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="am_map.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="d_event.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="d_items.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="d_iwad.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="d_loop.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="d_main.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="d_mode.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="d_net.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="doomdef.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="doomstat.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dstrings.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dummy.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="f_finale.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="f_wipe.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="g_game.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gusconf.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hu_lib.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hu_stuff.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_cdmus.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_endoom.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_joystick.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_scale.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_sound.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_system.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_timer.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="icon.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="info.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_argv.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_bbox.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_cheat.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_config.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_controls.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_fixed.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_menu.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_misc.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="m_random.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="memio.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_ceilng.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_doors.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_enemy.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_floor.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_inter.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_lights.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_map.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_maputl.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_mobj.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_plats.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_pspr.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_saveg.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_setup.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_sight.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_spec.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_switch.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_telept.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_tick.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="p_user.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="r_bsp.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="r_data.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="r_draw.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="r_main.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="r_plane.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="r_segs.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="r_sky.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="r_things.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="s_sound.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="sha1.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="sounds.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="st_lib.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="st_stuff.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="statdump.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="tables.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="v_video.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="w_checksum.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="w_file.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="w_file_stdc.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="w_main.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="w_wad.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="wi_stuff.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="z_zone.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="doomgeneric.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="doomgeneric_win.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_input.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="i_video.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="am_map.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="config.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_englsh.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_event.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_items.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_iwad.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_loop.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_main.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_mode.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_player.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_textur.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_think.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="d_ticcmd.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="deh_main.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="deh_misc.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="deh_str.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="doom.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="doomdata.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="doomdef.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="doomfeatures.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="doomkeys.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="doomstat.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="doomtype.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="dstrings.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="f_finale.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="f_wipe.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="g_game.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gusconf.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="hu_lib.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="hu_stuff.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_cdmus.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_endoom.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_joystick.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_scale.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_sound.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_swap.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_system.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_timer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="i_video.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="info.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_argv.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_bbox.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_cheat.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_config.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_controls.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_fixed.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_menu.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_misc.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="m_random.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="memio.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_client.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_dedicated.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_defs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_gui.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_io.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_loop.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_packet.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_query.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_sdl.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="net_server.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="p_inter.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="p_local.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="p_mobj.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="p_pspr.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="p_saveg.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="p_setup.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="p_spec.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="p_tick.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_bsp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_data.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_defs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_draw.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_local.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_main.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_plane.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_segs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_sky.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_state.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="r_things.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="s_sound.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="sha1.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="sounds.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="st_lib.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="st_stuff.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="statdump.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="tables.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="v_patch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="v_video.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="w_checksum.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="w_file.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="w_main.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="w_merge.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="w_wad.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="wi_stuff.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="z_zone.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="doomgeneric.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -6,26 +6,13 @@
|
||||
static ui_window_t doom_win = 0;
|
||||
|
||||
void DG_Init(void) {
|
||||
doom_win = ui_window_create("Yes, it's DOOM.", (1920 - DOOMGENERIC_RESX) / 2, (1080 - DOOMGENERIC_RESY) / 2, DOOMGENERIC_RESX, DOOMGENERIC_RESY);
|
||||
doom_win = ui_window_create("DOOM", (1920 - DOOMGENERIC_RESX) / 2, (1080 - DOOMGENERIC_RESY) / 2, DOOMGENERIC_RESX, DOOMGENERIC_RESY);
|
||||
}
|
||||
|
||||
static uint32_t scaled_buffer[600 * 900]; // DOOMGENERIC_RESX * DOOMGENERIC_RESY
|
||||
|
||||
void DG_DrawFrame(void) {
|
||||
if (doom_win) {
|
||||
// Doom's internal rendering is always 320x200.
|
||||
// But doomgeneric seems to expect DOOMGENERIC_RESX x DOOMGENERIC_RESY.
|
||||
// Actually, if we set DOOMGENERIC_RESX = 600, doom builds its internal tables based on DOOMGENERIC_RESX.
|
||||
// Wait, Doom's standard resolution is 320x200. Let's find out what DG_ScreenBuffer dimensions are.
|
||||
// According to Doom source, SCREENWIDTH and SCREENHEIGHT define the buffer size.
|
||||
// So DG_ScreenBuffer is indeed DOOMGENERIC_RESX x DOOMGENERIC_RESY.
|
||||
// However, the issue shown in the image is that the *game itself* only drew a 320x200 or 640x400 block in the corner!
|
||||
|
||||
// So the image wasn't scaled by Doom. Let's let LibUI do the scaling!
|
||||
// We will tell Doom its resolution is 640x400 (which it knows how to handle correctly for the 16:10 aspect ratio),
|
||||
// but we'll scale it to 600x900 (or whatever window size) before sending it to ui_draw_image.
|
||||
|
||||
// Wait, ui_draw_image doesn't scale natively yet. Let's do nearest-neighbor scaling.
|
||||
int src_w = 640;
|
||||
int src_h = 400;
|
||||
int dst_w = DOOMGENERIC_RESX;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "libc/syscall.h"
|
||||
#include "libc/libui.h"
|
||||
#include "libc/stdlib.h"
|
||||
#include "../../wm/libwidget.h"
|
||||
#include <stddef.h>
|
||||
|
||||
#define COLOR_DARK_PANEL 0xFF202020
|
||||
@@ -90,8 +91,30 @@ static char open_filename[256] = "";
|
||||
static _Bool file_modified = 0;
|
||||
static int scroll_y = 0;
|
||||
|
||||
static _Bool is_dragging_scrollbar = 0;
|
||||
static int scrollbar_drag_offset_y = 0;
|
||||
static widget_scrollbar_t doc_scrollbar;
|
||||
|
||||
static void word_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
|
||||
ui_draw_rect((ui_window_t)user_data, x, y, w, h, color);
|
||||
}
|
||||
static void word_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
|
||||
ui_draw_rounded_rect_filled((ui_window_t)user_data, x, y, w, h, r, color);
|
||||
}
|
||||
static void word_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
|
||||
ui_draw_string((ui_window_t)user_data, x, y, str, color);
|
||||
}
|
||||
|
||||
static widget_context_t word_ctx = {
|
||||
.user_data = 0,
|
||||
.draw_rect = word_draw_rect,
|
||||
.draw_rounded_rect_filled = word_draw_rounded_rect_filled,
|
||||
.draw_string = word_draw_string,
|
||||
.mark_dirty = NULL
|
||||
};
|
||||
|
||||
static void word_on_scroll(void *user_data, int new_scroll_y) {
|
||||
(void)user_data;
|
||||
scroll_y = new_scroll_y;
|
||||
}
|
||||
|
||||
static _Bool is_in_selection(int p, int r, int c);
|
||||
|
||||
@@ -1301,20 +1324,13 @@ static void draw_document(ui_window_t win) {
|
||||
set_active_font(win, 0);
|
||||
|
||||
int content_h = current_page * (page_h + 20) + page_h + 20;
|
||||
if (content_h > win_h - 40) {
|
||||
int sb_x = win_w - 12;
|
||||
int sb_w = 12;
|
||||
int sb_h = win_h - 40;
|
||||
float ratio = (float)(win_h - 40) / (float)content_h;
|
||||
int thumb_h = (int)(sb_h * ratio);
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
int max_scroll = content_h - (win_h - 40);
|
||||
if (scroll_y > max_scroll) scroll_y = max_scroll;
|
||||
int thumb_y = 40 + (int)(((float)scroll_y / max_scroll) * (sb_h - thumb_h));
|
||||
|
||||
ui_draw_rect(win, sb_x, 40, sb_w, sb_h, 0xFF303030);
|
||||
ui_draw_rounded_rect_filled(win, sb_x+2, thumb_y+2, sb_w-4, thumb_h-4, 4, 0xFF606060);
|
||||
}
|
||||
doc_scrollbar.x = win_w - 12;
|
||||
doc_scrollbar.y = 40;
|
||||
doc_scrollbar.w = 12;
|
||||
doc_scrollbar.h = win_h - 40;
|
||||
doc_scrollbar.on_scroll = word_on_scroll;
|
||||
widget_scrollbar_update(&doc_scrollbar, content_h, scroll_y);
|
||||
widget_scrollbar_draw(&word_ctx, &doc_scrollbar);
|
||||
}
|
||||
|
||||
static void ensure_cursor_visible(ui_window_t win) {
|
||||
@@ -1645,32 +1661,8 @@ static void handle_click(ui_window_t win, int x, int y) {
|
||||
}
|
||||
}
|
||||
content_h = dummy_page * (page_h + 20) + page_h + 20;
|
||||
|
||||
int sb_x = win_w - 12;
|
||||
int sb_w = 12;
|
||||
int sb_h = win_h - 40;
|
||||
int thumb_y = 40;
|
||||
int thumb_h = 0;
|
||||
int max_scroll = 0;
|
||||
if (content_h > win_h - 40) {
|
||||
float ratio = (float)(win_h - 40) / (float)content_h;
|
||||
thumb_h = (int)(sb_h * ratio);
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
max_scroll = content_h - (win_h - 40);
|
||||
if (scroll_y > max_scroll) scroll_y = max_scroll;
|
||||
thumb_y = 40 + (int)(((float)scroll_y / max_scroll) * (sb_h - thumb_h));
|
||||
}
|
||||
|
||||
if (content_h > win_h - 40 && x >= sb_x && x < sb_x + sb_w) {
|
||||
if (y >= thumb_y && y < thumb_y + thumb_h) {
|
||||
is_dragging_scrollbar = 1;
|
||||
scrollbar_drag_offset_y = y - thumb_y;
|
||||
} else {
|
||||
if (y < thumb_y) scroll_y -= (win_h - 40);
|
||||
else scroll_y += (win_h - 40);
|
||||
if (scroll_y < 0) scroll_y = 0;
|
||||
if (scroll_y > max_scroll) scroll_y = max_scroll;
|
||||
}
|
||||
widget_scrollbar_update(&doc_scrollbar, content_h, scroll_y);
|
||||
if (widget_scrollbar_handle_mouse(&doc_scrollbar, x, y, true, NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1848,12 +1840,15 @@ int main(int argc, char **argv) {
|
||||
(void)argv;
|
||||
ui_window_t win = ui_window_create("BoredWord", 100, 100, win_w, win_h);
|
||||
if (!win) return 1;
|
||||
word_ctx.user_data = (void*)win;
|
||||
ui_window_set_resizable(win, 1);
|
||||
|
||||
load_fonts();
|
||||
set_active_font(win, 0);
|
||||
init_doc();
|
||||
|
||||
widget_scrollbar_init(&doc_scrollbar, win_w - 12, 40, 12, win_h - 40);
|
||||
|
||||
if (argc > 1) {
|
||||
load_file(win, argv[1]);
|
||||
}
|
||||
@@ -1892,61 +1887,12 @@ int main(int argc, char **argv) {
|
||||
needs_repaint = 1;
|
||||
} else if (ev.type == GUI_EVENT_MOUSE_UP) {
|
||||
is_dragging = 0;
|
||||
is_dragging_scrollbar = 0;
|
||||
widget_scrollbar_handle_mouse(&doc_scrollbar, ev.arg1, ev.arg2, false, NULL);
|
||||
needs_repaint = 1;
|
||||
} else if (ev.type == GUI_EVENT_MOUSE_MOVE) {
|
||||
if (is_dragging_scrollbar) {
|
||||
int pw, ph; get_page_size(&pw, &ph);
|
||||
int doc_view_w = win_w - 40;
|
||||
float scale = (float)doc_view_w / (float)pw; if (scale > 1.0f) scale = 1.0f;
|
||||
int page_w = (int)(pw * scale);
|
||||
int page_h = (int)(ph * scale);
|
||||
|
||||
int content_h = 0;
|
||||
int dummy_y = 10; int dummy_page = 0;
|
||||
for(int p=0; p<para_count; p++) {
|
||||
Paragraph *para = ¶graphs[p]; int start_run = 0; int start_char = 0;
|
||||
while(start_run < para->run_count) {
|
||||
int max_h = 16; int end_run = start_run; int end_char = start_char; int line_w = 0;
|
||||
int r_idx = start_run; int c_idx = start_char; int last_space_run = -1; int last_space_char = -1; int last_space_w = 0;
|
||||
while(r_idx < para->run_count) {
|
||||
TextRun *run = ¶->runs[r_idx]; set_active_font(win, run->font_idx);
|
||||
int fh = ui_get_font_height_scaled(run->font_size); if (fh > max_h) max_h = fh;
|
||||
while(c_idx < run->len) {
|
||||
char buf[2] = {run->text[c_idx], 0}; int cw = ui_get_string_width_scaled(buf, run->font_size);
|
||||
if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; }
|
||||
if (line_w + cw > page_w - 20) break;
|
||||
line_w += cw; c_idx++;
|
||||
}
|
||||
if (c_idx < run->len) break;
|
||||
r_idx++; c_idx = 0;
|
||||
}
|
||||
if (r_idx < para->run_count || (r_idx == para->run_count - 1 && c_idx < para->runs[r_idx].len)) {
|
||||
if (last_space_run != -1 && (last_space_run > start_run || last_space_char > start_char)) { end_run = last_space_run; end_char = last_space_char; line_w = last_space_w; }
|
||||
else { end_run = r_idx; end_char = c_idx; }
|
||||
} else { end_run = para->run_count; end_char = 0; }
|
||||
int line_h = (int)(max_h * para->spacing) + 4;
|
||||
if (dummy_y + line_h > dummy_page * (page_h + 20) + page_h - 10) { dummy_page++; dummy_y = dummy_page * (page_h + 20) + 10; }
|
||||
dummy_y += line_h; start_run = end_run; start_char = end_char;
|
||||
if (start_run < para->run_count && para->runs[start_run].text[start_char] == ' ') { start_char++; if (start_char >= para->runs[start_run].len) { start_char = 0; start_run++; } }
|
||||
}
|
||||
}
|
||||
content_h = dummy_page * (page_h + 20) + page_h + 20;
|
||||
|
||||
if (content_h > win_h - 40) {
|
||||
int sb_h = win_h - 40;
|
||||
float ratio = (float)(win_h - 40) / (float)content_h;
|
||||
int thumb_h = (int)(sb_h * ratio);
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
int max_scroll = content_h - (win_h - 40);
|
||||
|
||||
int new_thumb_y = ev.arg2 - scrollbar_drag_offset_y;
|
||||
if (new_thumb_y < 40) new_thumb_y = 40;
|
||||
if (new_thumb_y > 40 + sb_h - thumb_h) new_thumb_y = 40 + sb_h - thumb_h;
|
||||
|
||||
scroll_y = (int)(((float)(new_thumb_y - 40) / (sb_h - thumb_h)) * max_scroll);
|
||||
if (doc_scrollbar.is_dragging) {
|
||||
widget_scrollbar_handle_mouse(&doc_scrollbar, ev.arg1, ev.arg2, true, NULL);
|
||||
needs_repaint = 1;
|
||||
}
|
||||
} else if (is_dragging && ev.arg2 >= 40 && active_dialog == 0 && active_dropdown == 0) {
|
||||
handle_click(win, ev.arg1, ev.arg2);
|
||||
needs_repaint = 1;
|
||||
|
||||
@@ -113,6 +113,7 @@ typedef struct {
|
||||
float scale;
|
||||
int list_depth;
|
||||
int blockquote_depth;
|
||||
int attr_w;
|
||||
bool img_loading;
|
||||
bool img_failed;
|
||||
uint32_t **img_frames;
|
||||
@@ -137,6 +138,42 @@ static int scroll_y = 0;
|
||||
static int total_content_height = 0;
|
||||
static int focused_element = -1;
|
||||
|
||||
#include "../../wm/libwidget.h"
|
||||
|
||||
static void browser_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
|
||||
ui_draw_rect((ui_window_t)user_data, x, y, w, h, color);
|
||||
}
|
||||
static void browser_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
|
||||
ui_draw_rounded_rect_filled((ui_window_t)user_data, x, y, w, h, r, color);
|
||||
}
|
||||
static void browser_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
|
||||
ui_draw_string((ui_window_t)user_data, x, y, str, color);
|
||||
}
|
||||
static int browser_measure_string_width(void *user_data, const char *str) {
|
||||
(void)user_data;
|
||||
return (int)ui_get_string_width(str);
|
||||
}
|
||||
static void browser_mark_dirty(void *user_data, int x, int y, int w, int h) {
|
||||
ui_mark_dirty((ui_window_t)user_data, x, y, w, h);
|
||||
}
|
||||
|
||||
static widget_context_t browser_ctx = {
|
||||
.draw_rect = browser_draw_rect,
|
||||
.draw_rounded_rect_filled = browser_draw_rounded_rect_filled,
|
||||
.draw_string = browser_draw_string,
|
||||
.measure_string_width = browser_measure_string_width,
|
||||
.mark_dirty = browser_mark_dirty
|
||||
};
|
||||
|
||||
static widget_scrollbar_t browser_scrollbar;
|
||||
static void browser_on_scroll(void *user_data, int new_scroll_y) {
|
||||
(void)user_data;
|
||||
scroll_y = new_scroll_y;
|
||||
}
|
||||
static widget_textbox_t url_tb;
|
||||
static widget_button_t btn_back;
|
||||
static widget_button_t btn_home;
|
||||
|
||||
static void parse_html(const char *html);
|
||||
static void parse_html_incremental(const char *html, int safe_len);
|
||||
static void browser_reflow(void);
|
||||
@@ -219,6 +256,9 @@ static int parse_ip(const char* str, net_ipv4_address_t* ip) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char dns_cache_host[256] = "";
|
||||
static net_ipv4_address_t dns_cache_ip;
|
||||
|
||||
static int fetch_content(const char *url, char *dest_buf, int max_len, bool progressive) {
|
||||
const char* host_start = url;
|
||||
if (url[0] == 'h' && url[1] == 't' && url[2] == 't' && url[3] == 'p') {
|
||||
@@ -253,7 +293,13 @@ static int fetch_content(const char *url, char *dest_buf, int max_len, bool prog
|
||||
|
||||
net_ipv4_address_t ip;
|
||||
if (parse_ip(hostname, &ip) != 0) {
|
||||
if (str_iequals(hostname, dns_cache_host)) {
|
||||
ip = dns_cache_ip;
|
||||
} else {
|
||||
if (sys_dns_lookup(hostname, &ip) != 0) return 0;
|
||||
int k=0; while(hostname[k]) { dns_cache_host[k] = hostname[k]; k++; } dns_cache_host[k] = 0;
|
||||
dns_cache_ip = ip;
|
||||
}
|
||||
}
|
||||
|
||||
if (sys_tcp_connect(&ip, port) != 0) return 0;
|
||||
@@ -280,18 +326,100 @@ static int fetch_content(const char *url, char *dest_buf, int max_len, bool prog
|
||||
int total = 0;
|
||||
int last_render = 0;
|
||||
if (progressive) inc_parse_offset = 0;
|
||||
long long last_data_tick = sys_system(16, 0, 0, 0, 0);
|
||||
|
||||
while (1) {
|
||||
int len = sys_tcp_recv(dest_buf + total, max_len - 1 - total);
|
||||
if (len <= 0) break;
|
||||
int len = sys_tcp_recv_nb(dest_buf + total, max_len - 1 - total);
|
||||
if (len < 0 && len != -2) break;
|
||||
if (len == -2) break;
|
||||
|
||||
if (len == 0) {
|
||||
long long now = sys_system(16, 0, 0, 0, 0);
|
||||
if (now > last_data_tick + 1800) break; // 30 sec timeout
|
||||
|
||||
gui_event_t ev;
|
||||
bool scrolled = false;
|
||||
while (ui_get_event(win_browser, &ev)) {
|
||||
if (ev.type == 9) { // GUI_EVENT_MOUSE_WHEEL
|
||||
scroll_y += ev.arg1 * 20;
|
||||
scrolled = true;
|
||||
} else if (ev.type == 12) { // GUI_EVENT_CLOSE
|
||||
sys_exit(0);
|
||||
} else if (ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_MOUSE_UP || ev.type == GUI_EVENT_MOUSE_MOVE) {
|
||||
bool is_down = (ev.type == GUI_EVENT_MOUSE_DOWN || (ev.type == GUI_EVENT_MOUSE_MOVE && browser_scrollbar.is_dragging));
|
||||
if (widget_scrollbar_handle_mouse(&browser_scrollbar, ev.arg1, ev.arg2, is_down, &browser_ctx)) {
|
||||
scroll_y = browser_scrollbar.scroll_y;
|
||||
scrolled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scrolled) {
|
||||
int max_scroll = total_content_height - (win_h - URL_BAR_H);
|
||||
if (max_scroll < 0) max_scroll = 0;
|
||||
if (scroll_y > max_scroll) scroll_y = max_scroll;
|
||||
if (scroll_y < 0) scroll_y = 0;
|
||||
browser_reflow(); // Needs reflow in case of dimensions changing, but mostly just paint
|
||||
browser_paint();
|
||||
ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
|
||||
}
|
||||
sleep(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
last_data_tick = sys_system(16, 0, 0, 0, 0);
|
||||
total += len;
|
||||
if (total >= max_len - 1) break;
|
||||
|
||||
dest_buf[total] = 0;
|
||||
char *body = strstr(dest_buf, "\r\n\r\n");
|
||||
if (body) {
|
||||
char temp = body[0];
|
||||
body[0] = 0; // Null-terminate headers
|
||||
|
||||
int expected = -1;
|
||||
char *cl = str_istrstr(dest_buf, "Content-Length:");
|
||||
if (cl) {
|
||||
cl += 15;
|
||||
while (*cl == ' ') cl++;
|
||||
expected = 0;
|
||||
while (*cl >= '0' && *cl <= '9') {
|
||||
expected = expected * 10 + (*cl - '0');
|
||||
cl++;
|
||||
}
|
||||
}
|
||||
|
||||
int is_chunked = 0;
|
||||
char *te = str_istrstr(dest_buf, "Transfer-Encoding:");
|
||||
if (te && str_istrstr(te, "chunked")) {
|
||||
is_chunked = 1;
|
||||
}
|
||||
|
||||
body[0] = temp; // Restore body
|
||||
|
||||
body += 4;
|
||||
int body_len = total - (body - dest_buf);
|
||||
|
||||
if (expected != -1) {
|
||||
if (body_len >= expected) break;
|
||||
} else if (is_chunked) {
|
||||
if (total >= 5 && dest_buf[total-5] == '0' && dest_buf[total-4] == '\r' &&
|
||||
dest_buf[total-3] == '\n' && dest_buf[total-2] == '\r' && dest_buf[total-1] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (progressive && total - last_render > 32768) {
|
||||
dest_buf[total] = 0;
|
||||
char *body = strstr(dest_buf, "\r\n\r\n");
|
||||
if (body) {
|
||||
char temp = body[0];
|
||||
body[0] = 0;
|
||||
int is_chunked = strstr(dest_buf, "Transfer-Encoding: chunked") != NULL;
|
||||
body[0] = temp;
|
||||
|
||||
body += 4;
|
||||
if (!strstr(dest_buf, "Transfer-Encoding: chunked")) {
|
||||
if (!is_chunked) {
|
||||
int body_len = total - (body - dest_buf);
|
||||
int safe_len = body_len;
|
||||
while (safe_len > 0 && body[safe_len - 1] != '>') safe_len--;
|
||||
@@ -333,8 +461,13 @@ static void decode_image(unsigned char *data, int len, RenderElement *el) {
|
||||
|
||||
if (rgba && img_w_orig > 0 && img_h_orig > 0) {
|
||||
int fit_w = img_w_orig; int fit_h = img_h_orig;
|
||||
if (el->attr_w > 0) {
|
||||
fit_w = el->attr_w;
|
||||
fit_h = (img_h_orig * fit_w) / img_w_orig;
|
||||
} else {
|
||||
if (fit_w > win_w - 60) { fit_h = fit_h * (win_w - 60) / fit_w; fit_w = win_w - 60; }
|
||||
if (fit_h > 400) { fit_w = fit_w * 400 / fit_h; fit_h = 400; }
|
||||
}
|
||||
|
||||
if (frame_count > 1 && delays) {
|
||||
el->img_frames = malloc(frame_count * sizeof(uint32_t *));
|
||||
@@ -479,6 +612,7 @@ static int inc_list_type[16];
|
||||
static int inc_list_index[16];
|
||||
static int inc_center_depth = 0;
|
||||
static int inc_table_depth = 0;
|
||||
static int inc_table_float_depth = 0;
|
||||
static int inc_blockquote_depth = 0;
|
||||
static bool inc_is_bold = false;
|
||||
static bool inc_is_italic = false;
|
||||
@@ -521,7 +655,7 @@ static void flush_line(void) {
|
||||
if (el->tag == TAG_IMG && el->img_h + 10 > max_h) max_h = el->img_h + 10;
|
||||
if ((el->tag == TAG_INPUT || el->tag == TAG_BUTTON) && 20 + 10 > max_h) max_h = 20 + 10;
|
||||
if (el->tag == TAG_NONE) {
|
||||
int fh = ui_get_font_height_scaled(el->scale);
|
||||
int fh = el->h;
|
||||
if (fh + 4 > max_h) max_h = fh + 4;
|
||||
if (fh > max_baseline) max_baseline = fh;
|
||||
}
|
||||
@@ -531,7 +665,7 @@ static void flush_line(void) {
|
||||
RenderElement *el = &elements[line_elements[i]];
|
||||
el->x = offset_x;
|
||||
if (el->tag == TAG_NONE) {
|
||||
int fh = ui_get_font_height_scaled(el->scale);
|
||||
int fh = el->h;
|
||||
el->y = cur_line_y + (max_baseline - fh);
|
||||
} else {
|
||||
el->y = cur_line_y;
|
||||
@@ -550,9 +684,41 @@ static void browser_reflow(void) {
|
||||
line_element_count = 0;
|
||||
total_content_height = 0;
|
||||
|
||||
int float_right_bottom_y = 0;
|
||||
int float_start_idx = -1;
|
||||
int float_start_y = 0;
|
||||
|
||||
for (int i = 0; i < element_count; i++) {
|
||||
RenderElement *el = &elements[i];
|
||||
|
||||
if (el->tag == 8) {
|
||||
flush_line();
|
||||
float_start_idx = i;
|
||||
float_start_y = cur_line_y;
|
||||
continue;
|
||||
} else if (el->tag == 9) {
|
||||
if (float_start_idx != -1) {
|
||||
flush_line();
|
||||
float_right_bottom_y = cur_line_y;
|
||||
int float_offset = win_w - SCROLL_BAR_W - 320 - 10;
|
||||
|
||||
elements[float_start_idx].x = float_offset > 0 ? float_offset + 10 : 10;
|
||||
elements[float_start_idx].y = float_start_y;
|
||||
elements[float_start_idx].w = 320;
|
||||
elements[float_start_idx].h = cur_line_y - float_start_y;
|
||||
|
||||
if (float_offset > 0) {
|
||||
for (int j = float_start_idx; j < i; j++) {
|
||||
elements[j].x += float_offset;
|
||||
}
|
||||
}
|
||||
cur_line_y = float_start_y;
|
||||
cur_line_x = 10;
|
||||
float_start_idx = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el->tag == TAG_BR) {
|
||||
flush_line();
|
||||
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
||||
@@ -562,18 +728,24 @@ static void browser_reflow(void) {
|
||||
if (el->tag == TAG_HR) {
|
||||
flush_line();
|
||||
el->w = win_w - SCROLL_BAR_W - 40 - (el->blockquote_depth * 40);
|
||||
if (float_start_idx != -1) el->w = 300 - 20;
|
||||
else if (cur_line_y < float_right_bottom_y) el->w = win_w - 320 - SCROLL_BAR_W - 20;
|
||||
line_elements[line_element_count++] = i;
|
||||
flush_line();
|
||||
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
||||
continue;
|
||||
}
|
||||
|
||||
int max_x = win_w - SCROLL_BAR_W - 20 - (el->blockquote_depth * 40);
|
||||
if (float_start_idx != -1) max_x = 310;
|
||||
else if (cur_line_y < float_right_bottom_y) max_x = win_w - 320 - SCROLL_BAR_W - 20;
|
||||
|
||||
if (el->tag == TAG_NONE && el->content[0] == ' ' && el->content[1] == 0) {
|
||||
if (line_element_count == 0) continue;
|
||||
if (cur_line_x + el->w > win_w - SCROLL_BAR_W - 20 - (el->blockquote_depth * 40)) continue;
|
||||
if (cur_line_x + el->w > max_x) continue;
|
||||
}
|
||||
|
||||
if (cur_line_x + el->w > win_w - SCROLL_BAR_W - 20 - (el->blockquote_depth * 40)) {
|
||||
if (cur_line_x + el->w > max_x) {
|
||||
flush_line();
|
||||
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
||||
}
|
||||
@@ -714,7 +886,7 @@ static void parse_html(const char *html) {
|
||||
browser_clear();
|
||||
list_depth = 0;
|
||||
cur_line_y = 10; cur_line_x = 10; line_element_count = 0;
|
||||
int i = 0; int center_depth = 0; int table_depth = 0; int blockquote_depth = 0; bool is_bold = false; bool is_italic = false; bool is_underline = false;
|
||||
int i = 0; int center_depth = 0; int table_depth = 0; int table_float_depth = 0; int blockquote_depth = 0; bool is_bold = false; bool is_italic = false; bool is_underline = false;
|
||||
uint32_t current_color = COLOR_TEXT;
|
||||
char current_link[256] = "";
|
||||
float current_scale = 15.0f; float base_scale = 15.0f;
|
||||
@@ -758,7 +930,14 @@ static void parse_html(const char *html) {
|
||||
|
||||
if (tag_name[0] == '/') {
|
||||
if (str_iequals(tag_name+1, "center")) { emit_br(); if (center_depth > 0) center_depth--; }
|
||||
else if (str_iequals(tag_name+1, "table")) { emit_br(); if (table_depth > 0) table_depth--; table_col = 0; }
|
||||
else if (str_iequals(tag_name+1, "table")) {
|
||||
emit_br();
|
||||
if (table_depth > 0 && table_depth == table_float_depth) {
|
||||
table_float_depth = 0;
|
||||
if (element_count < MAX_ELEMENTS) { RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement)); el->tag = 9; }
|
||||
}
|
||||
if (table_depth > 0) table_depth--; table_col = 0;
|
||||
}
|
||||
else if (str_iequals(tag_name+1, "tr")) { emit_br(); table_col = 0; }
|
||||
else if (str_iequals(tag_name+1, "td") || str_iequals(tag_name+1, "th")) {
|
||||
table_col++;
|
||||
@@ -796,7 +975,7 @@ static void parse_html(const char *html) {
|
||||
current_form_id = 0; current_form_action[0] = 0;
|
||||
}
|
||||
else if (str_iequals(tag_name+1, "a")) current_link[0] = 0;
|
||||
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div") || str_iequals(tag_name+1, "address")) emit_br();
|
||||
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div") || str_iequals(tag_name+1, "address")) { emit_br(); }
|
||||
else if (str_iequals(tag_name+1, "pre") || str_iequals(tag_name+1, "xmp") || str_iequals(tag_name+1, "listing")) { emit_br(); is_pre = false; }
|
||||
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) {
|
||||
if (font_ptr > 0) {
|
||||
@@ -816,7 +995,17 @@ static void parse_html(const char *html) {
|
||||
}
|
||||
} else {
|
||||
if (str_iequals(tag_name, "center")) { emit_br(); center_depth++; }
|
||||
else if (str_iequals(tag_name, "table")) { emit_br(); table_depth++; table_col = 0; }
|
||||
else if (str_iequals(tag_name, "table")) {
|
||||
emit_br(); table_depth++; table_col = 0;
|
||||
if (str_istrstr(attr_buf, "align=\"right\"") && table_float_depth == 0) {
|
||||
table_float_depth = table_depth;
|
||||
if (element_count < MAX_ELEMENTS) {
|
||||
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement)); el->tag = 8;
|
||||
char *bg_str = str_istrstr(attr_buf, "bgcolor=\"");
|
||||
if (bg_str) el->color = parse_html_color(bg_str + 9);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (str_iequals(tag_name, "tr")) { emit_br(); table_col = 0; }
|
||||
else if (str_iequals(tag_name, "td") || str_iequals(tag_name, "th")) {
|
||||
if (table_col > 0) {
|
||||
@@ -920,7 +1109,9 @@ static void parse_html(const char *html) {
|
||||
}
|
||||
}
|
||||
else if (str_iequals(tag_name, "br")) emit_br();
|
||||
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) emit_br();
|
||||
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) {
|
||||
emit_br();
|
||||
}
|
||||
else if (str_iequals(tag_name, "pre")) { emit_br(); is_pre = true; current_scale = 14.0f; }
|
||||
else if (str_iequals(tag_name, "li")) {
|
||||
emit_br();
|
||||
@@ -983,6 +1174,8 @@ static void parse_html(const char *html) {
|
||||
RenderElement *el = &elements[element_count++];
|
||||
memset(el, 0, sizeof(RenderElement));
|
||||
el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
|
||||
char *width_str = str_istrstr(attr_buf, "width=\"");
|
||||
if (width_str) { int w = atoi(width_str + 7); if (w > 0) el->attr_w = w; }
|
||||
char *src = str_istrstr(attr_buf, "src=\"");
|
||||
if (src) {
|
||||
src += 5; int l = 0;
|
||||
@@ -995,6 +1188,8 @@ static void parse_html(const char *html) {
|
||||
RenderElement *el = &elements[element_count++];
|
||||
memset(el, 0, sizeof(RenderElement));
|
||||
el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
|
||||
char *size_str = str_istrstr(attr_buf, "size=\"");
|
||||
if (size_str) { int sz = atoi(size_str + 6); if (sz > 0) el->w = sz * 8; }
|
||||
char *val = str_istrstr(attr_buf, "value=\"");
|
||||
char *ph = str_istrstr(attr_buf, "placeholder=\"");
|
||||
char *type = str_istrstr(attr_buf, "type=\"");
|
||||
@@ -1145,7 +1340,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
browser_clear();
|
||||
list_depth = 0;
|
||||
cur_line_y = 10; cur_line_x = 10; line_element_count = 0;
|
||||
inc_center_depth = 0; inc_table_depth = 0; inc_blockquote_depth = 0;
|
||||
inc_center_depth = 0; inc_table_depth = 0; inc_table_float_depth = 0; inc_blockquote_depth = 0;
|
||||
inc_is_bold = false; inc_is_italic = false; inc_is_underline = false;
|
||||
inc_current_color = COLOR_TEXT; inc_current_link[0] = 0;
|
||||
inc_current_scale = 15.0f; inc_base_scale = 15.0f;
|
||||
@@ -1162,6 +1357,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
int i = inc_parse_offset;
|
||||
int center_depth = inc_center_depth;
|
||||
int table_depth = inc_table_depth;
|
||||
int table_float_depth = inc_table_float_depth;
|
||||
int blockquote_depth = inc_blockquote_depth;
|
||||
bool is_bold = inc_is_bold;
|
||||
bool is_italic = inc_is_italic;
|
||||
@@ -1207,7 +1403,14 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
|
||||
if (tag_name[0] == '/') {
|
||||
if (str_iequals(tag_name+1, "center")) { emit_br(); if (center_depth > 0) center_depth--; }
|
||||
else if (str_iequals(tag_name+1, "table")) { emit_br(); if (table_depth > 0) table_depth--; table_col = 0; }
|
||||
else if (str_iequals(tag_name+1, "table")) {
|
||||
emit_br();
|
||||
if (table_depth > 0 && table_depth == table_float_depth) {
|
||||
table_float_depth = 0;
|
||||
if (element_count < MAX_ELEMENTS) { RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement)); el->tag = 9; }
|
||||
}
|
||||
if (table_depth > 0) table_depth--; table_col = 0;
|
||||
}
|
||||
else if (str_iequals(tag_name+1, "tr")) { emit_br(); table_col = 0; }
|
||||
else if (str_iequals(tag_name+1, "td") || str_iequals(tag_name+1, "th")) {
|
||||
table_col++;
|
||||
@@ -1235,7 +1438,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
else if (tag_name[1] == 'h' && tag_name[2] >= '1' && tag_name[2] <= '6') { emit_br(); emit_br(); is_bold = false; is_italic = false; is_underline = false; base_scale = 15.0f; current_scale = 15.0f; }
|
||||
else if (str_iequals(tag_name+1, "form")) { emit_br(); current_form_id = 0; current_form_action[0] = 0; }
|
||||
else if (str_iequals(tag_name+1, "a")) current_link[0] = 0;
|
||||
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div")) emit_br();
|
||||
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div")) { emit_br(); }
|
||||
else if (str_iequals(tag_name+1, "pre") || str_iequals(tag_name+1, "xmp") || str_iequals(tag_name+1, "listing")) { emit_br(); is_pre = false; }
|
||||
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) {
|
||||
if (inc_font_ptr > 0) {
|
||||
@@ -1255,7 +1458,17 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
}
|
||||
} else {
|
||||
if (str_iequals(tag_name, "center")) { emit_br(); center_depth++; }
|
||||
else if (str_iequals(tag_name, "table")) { emit_br(); table_depth++; table_col = 0; }
|
||||
else if (str_iequals(tag_name, "table")) {
|
||||
emit_br(); table_depth++; table_col = 0;
|
||||
if (str_istrstr(attr_buf, "align=\"right\"") && table_float_depth == 0) {
|
||||
table_float_depth = table_depth;
|
||||
if (element_count < MAX_ELEMENTS) {
|
||||
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement)); el->tag = 8;
|
||||
char *bg_str = str_istrstr(attr_buf, "bgcolor=\"");
|
||||
if (bg_str) el->color = parse_html_color(bg_str + 9);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (str_iequals(tag_name, "tr")) { emit_br(); table_col = 0; }
|
||||
else if (str_iequals(tag_name, "td") || str_iequals(tag_name, "th")) {
|
||||
if (table_col > 0) {
|
||||
@@ -1314,10 +1527,13 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
}
|
||||
}
|
||||
else if (str_iequals(tag_name, "br")) emit_br();
|
||||
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) emit_br();
|
||||
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) {
|
||||
emit_br();
|
||||
}
|
||||
else if (str_iequals(tag_name, "pre")) { emit_br(); is_pre = true; current_scale = 14.0f; }
|
||||
else if (str_iequals(tag_name, "li")) {
|
||||
emit_br(); RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
|
||||
emit_br();
|
||||
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
|
||||
el->tag = TAG_NONE; if (list_depth > 0 && list_type[list_depth - 1] == 1) { char num[16]; itoa(list_index[list_depth - 1]++, num); int l=0; while(num[l]) { el->content[l] = num[l]; l++; } el->content[l++] = '.'; el->content[l++] = ' '; el->content[l] = 0; }
|
||||
else if (list_depth > 0 && list_type[list_depth - 1] == 2) { el->content[0] = ' '; el->content[1] = 0; } else { el->content[0] = (char)130; el->content[1] = ' '; el->content[2] = 0; }
|
||||
el->w = ui_get_string_width_scaled(el->content, current_scale); el->h = ui_get_font_height_scaled(current_scale); el->color = current_color; el->centered = EFF_CENTER; el->bold = is_bold; el->scale = current_scale; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth;
|
||||
@@ -1330,11 +1546,13 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
else if (str_iequals(tag_name, "hr")) { emit_br(); RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_HR; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth; el->h = 10; el->centered = true; emit_br(); }
|
||||
else if (str_iequals(tag_name, "img")) {
|
||||
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
|
||||
char *width_str = str_istrstr(attr_buf, "width=\""); if (width_str) { int w = atoi(width_str + 7); if (w > 0) el->attr_w = w; }
|
||||
char *src = str_istrstr(attr_buf, "src=\""); if (src) { src += 5; int l = 0; while(src[l] && src[l] != '"' && l < 255) { el->attr_value[l] = src[l]; l++; } el->attr_value[l] = 0; el->img_loading = true; }
|
||||
el->blockquote_depth = blockquote_depth;
|
||||
}
|
||||
else if (str_iequals(tag_name, "input")) {
|
||||
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
|
||||
char *size_str = str_istrstr(attr_buf, "size=\""); if (size_str) { int sz = atoi(size_str + 6); if (sz > 0) el->w = sz * 8; }
|
||||
char *val = str_istrstr(attr_buf, "value=\""); char *ph = str_istrstr(attr_buf, "placeholder=\""); char *type = str_istrstr(attr_buf, "type=\""); char *name = str_istrstr(attr_buf, "name=\"");
|
||||
el->form_id = current_form_id; el->input_cursor = 0; el->input_scroll = 0; int l; l = 0; while(current_form_action[l]) { el->form_action[l] = current_form_action[l]; l++; } el->form_action[l] = 0;
|
||||
if (name) { name += 6; l = 0; while(name[l] && name[l] != '"' && l < 63) { el->input_name[l] = name[l]; l++; } el->input_name[l] = 0; } else { l = 0; const char *dn = "q"; while(dn[l]) { el->input_name[l] = dn[l]; l++; } el->input_name[l] = 0; }
|
||||
@@ -1420,7 +1638,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
}
|
||||
emit_br();
|
||||
|
||||
inc_parse_offset = i; inc_center_depth = center_depth; inc_table_depth = table_depth; inc_blockquote_depth = blockquote_depth;
|
||||
inc_parse_offset = i; inc_center_depth = center_depth; inc_table_depth = table_depth; inc_table_float_depth = table_float_depth; inc_blockquote_depth = blockquote_depth;
|
||||
inc_is_bold = is_bold; inc_is_italic = is_italic; inc_is_underline = is_underline; inc_current_color = current_color;
|
||||
{ int k=0; while(current_link[k]) { inc_current_link[k] = current_link[k]; k++; } inc_current_link[k] = 0; }
|
||||
inc_current_scale = current_scale; inc_base_scale = base_scale; inc_is_space_pending = is_space_pending;
|
||||
@@ -1431,65 +1649,66 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
}
|
||||
|
||||
static void browser_paint(void) {
|
||||
browser_ctx.user_data = (void *)win_browser;
|
||||
ui_draw_rect(win_browser, 0, 0, win_w, win_h, COLOR_BG);
|
||||
|
||||
for (int i = 0; i < element_count; i++) {
|
||||
RenderElement *el = &elements[i];
|
||||
int draw_y = el->y - scroll_y + URL_BAR_H;
|
||||
if (draw_y < URL_BAR_H - 400 || draw_y > win_h) continue;
|
||||
int el_h = el->h;
|
||||
if (el->tag == TAG_IMG && el->img_h > el_h) el_h = el->img_h;
|
||||
if (draw_y + el_h < URL_BAR_H || draw_y > win_h) continue;
|
||||
|
||||
if (el->tag == 8) {
|
||||
if (el->color != 0 && el->color != COLOR_BG) {
|
||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h + 10, el->color);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el->tag == TAG_IMG) {
|
||||
uint32_t *pixels = el->img_pixels;
|
||||
if (el->img_frames) pixels = el->img_frames[el->img_current_frame];
|
||||
if (pixels) ui_draw_image(win_browser, el->x, draw_y, el->img_w, el->img_h, pixels);
|
||||
else ui_draw_rect(win_browser, el->x, draw_y, 100, 80, 0xFFCCCCCC);
|
||||
} else if (el->tag == TAG_INPUT) {
|
||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFFFFFFF);
|
||||
uint32_t border = (focused_element == i) ? 0xFF0000FF : 0xFF808080;
|
||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, 1, border);
|
||||
ui_draw_rect(win_browser, el->x, draw_y + el->h - 1, el->w, 1, border);
|
||||
ui_draw_rect(win_browser, el->x, draw_y, 1, el->h, border);
|
||||
ui_draw_rect(win_browser, el->x + el->w - 1, draw_y, 1, el->h, border);
|
||||
|
||||
char visible[64];
|
||||
browser_ctx.use_light_theme = true;
|
||||
char visible[128];
|
||||
int v_len = 0;
|
||||
int max_v = (el->w - 10) / 8;
|
||||
if (max_v > 63) max_v = 63;
|
||||
if (max_v > 127) max_v = 127;
|
||||
for (int k = el->input_scroll; el->attr_value[k] && v_len < max_v; k++) {
|
||||
visible[v_len++] = el->attr_value[k];
|
||||
}
|
||||
visible[v_len] = 0;
|
||||
ui_draw_string(win_browser, el->x + 5, draw_y + 2, visible, (focused_element == i) ? 0xFF000000 : 0xFF808080);
|
||||
|
||||
if (focused_element == i) {
|
||||
int cursor_pos = el->input_cursor - el->input_scroll;
|
||||
if (cursor_pos >= 0 && cursor_pos < max_v) {
|
||||
char sub[64];
|
||||
int k;
|
||||
for (k = 0; k < cursor_pos && visible[k]; k++) sub[k] = visible[k];
|
||||
sub[k] = 0;
|
||||
int cx = ui_get_string_width(sub);
|
||||
ui_draw_rect(win_browser, el->x + 5 + cx, draw_y + 16, 8, 2, 0xFF000000);
|
||||
}
|
||||
}
|
||||
widget_textbox_t tb;
|
||||
widget_textbox_init(&tb, el->x, draw_y, el->w, el->h, visible, max_v);
|
||||
tb.cursor_pos = el->input_cursor - el->input_scroll;
|
||||
if (tb.cursor_pos < 0) tb.cursor_pos = 0;
|
||||
tb.focused = (focused_element == i);
|
||||
widget_textbox_draw(&browser_ctx, &tb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
} else if (el->tag == TAG_BUTTON) {
|
||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFDDDDDD);
|
||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, 1, 0xFFFFFFFF);
|
||||
ui_draw_rect(win_browser, el->x, draw_y + el->h - 1, el->w, 1, 0xFF888888);
|
||||
ui_draw_rect(win_browser, el->x, draw_y, 1, el->h, 0xFFFFFFFF);
|
||||
ui_draw_rect(win_browser, el->x + el->w - 1, draw_y, 1, el->h, 0xFF888888);
|
||||
ui_draw_string(win_browser, el->x + 10, draw_y + 4, el->attr_value, 0xFF000000);
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_button_t btn;
|
||||
widget_button_init(&btn, el->x, draw_y, el->w, el->h, el->attr_value);
|
||||
widget_button_draw(&browser_ctx, &btn);
|
||||
browser_ctx.use_light_theme = false;
|
||||
} else if (el->tag == TAG_RADIO) {
|
||||
ui_draw_rounded_rect_filled(win_browser, el->x, draw_y, el->w, el->h, el->w/2, 0xFF808080);
|
||||
ui_draw_rounded_rect_filled(win_browser, el->x + 1, draw_y + 1, el->w - 2, el->h - 2, (el->w-2)/2, 0xFFFFFFFF);
|
||||
if (el->checked) {
|
||||
ui_draw_rounded_rect_filled(win_browser, el->x + 4, draw_y + 4, el->w - 8, el->h - 8, (el->w-8)/2, 0xFF000000);
|
||||
}
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, el->x, draw_y, el->w, el->h, "", true);
|
||||
cb.checked = el->checked;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
} else if (el->tag == TAG_CHECKBOX) {
|
||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFF808080);
|
||||
ui_draw_rect(win_browser, el->x + 1, draw_y + 1, el->w - 2, el->h - 2, 0xFFFFFFFF);
|
||||
if (el->checked) {
|
||||
ui_draw_rect(win_browser, el->x + 4, draw_y + 4, el->w - 8, el->h - 8, 0xFF000000);
|
||||
}
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, el->x, draw_y, el->w, el->h, "", false);
|
||||
cb.checked = el->checked;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
} else if (el->tag == TAG_HR) {
|
||||
ui_draw_rect(win_browser, el->x, draw_y + el->h / 2, el->w, 2, 0xFF888888);
|
||||
ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF);
|
||||
@@ -1504,47 +1723,37 @@ static void browser_paint(void) {
|
||||
ui_draw_string_scaled(win_browser, el->x + 1, draw_y - 1, el->content, el->color, el->scale);
|
||||
}
|
||||
if (el->underline) {
|
||||
int fh = ui_get_font_height_scaled(el->scale);
|
||||
int fh = el->h;
|
||||
ui_draw_rect(win_browser, el->x, draw_y + fh - 1, el->w, 1, el->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui_draw_rect(win_browser, 0, 0, win_w, URL_BAR_H, COLOR_URL_BAR);
|
||||
ui_draw_string(win_browser, 10, 8, url_input_buffer, COLOR_URL_TEXT);
|
||||
if (focused_element == -1) {
|
||||
char sub[512];
|
||||
int k;
|
||||
for (k = 0; k < url_cursor && url_input_buffer[k]; k++) sub[k] = url_input_buffer[k];
|
||||
sub[k] = 0;
|
||||
int cx = ui_get_string_width(sub);
|
||||
ui_draw_rect(win_browser, 10 + cx, 22, 8, 2, COLOR_URL_TEXT);
|
||||
}
|
||||
|
||||
widget_textbox_init(&url_tb, 10, 5, win_w - SCROLL_BAR_W - BTN_W*2 - BTN_PAD*2 - 20, 20, url_input_buffer, 511);
|
||||
url_tb.cursor_pos = url_cursor;
|
||||
url_tb.focused = (focused_element == -1);
|
||||
widget_textbox_draw(&browser_ctx, &url_tb);
|
||||
|
||||
// Back button
|
||||
int btn_y = (URL_BAR_H - BTN_H) / 2;
|
||||
uint32_t back_col = history_count > 0 ? 0xFF505050 : 0xFF404040;
|
||||
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, BTN_W, BTN_H, back_col);
|
||||
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, BTN_W, 1, 0xFF606060);
|
||||
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, 1, BTN_H, 0xFF606060);
|
||||
ui_draw_rect(win_browser, BACK_BTN_X, btn_y + BTN_H - 1, BTN_W, 1, 0xFF202020);
|
||||
ui_draw_rect(win_browser, BACK_BTN_X + BTN_W - 1, btn_y, 1, BTN_H, 0xFF202020);
|
||||
ui_draw_string(win_browser, BACK_BTN_X + 10, btn_y + 4, "<", history_count > 0 ? 0xFFFFFFFF : 0xFF808080);
|
||||
widget_button_init(&btn_back, BACK_BTN_X, btn_y, BTN_W, BTN_H, "<");
|
||||
widget_button_draw(&browser_ctx, &btn_back);
|
||||
|
||||
// Home button
|
||||
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, BTN_W, BTN_H, 0xFF505050);
|
||||
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, BTN_W, 1, 0xFF606060);
|
||||
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, 1, BTN_H, 0xFF606060);
|
||||
ui_draw_rect(win_browser, HOME_BTN_X, btn_y + BTN_H - 1, BTN_W, 1, 0xFF202020);
|
||||
ui_draw_rect(win_browser, HOME_BTN_X + BTN_W - 1, btn_y, 1, BTN_H, 0xFF202020);
|
||||
ui_draw_string(win_browser, HOME_BTN_X + 10, btn_y + 4, "H", 0xFFFFFFFF);
|
||||
widget_button_init(&btn_home, HOME_BTN_X, btn_y, BTN_W, BTN_H, "H");
|
||||
widget_button_draw(&browser_ctx, &btn_home);
|
||||
|
||||
// Scroll bar
|
||||
ui_draw_rect(win_browser, win_w - SCROLL_BAR_W, URL_BAR_H, SCROLL_BAR_W, win_h - URL_BAR_H, COLOR_SCROLL_BG);
|
||||
int thumb_h = (win_h - URL_BAR_H) * (win_h - URL_BAR_H) / (total_content_height > win_h ? total_content_height : win_h);
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
int thumb_y = URL_BAR_H + (scroll_y * (win_h - URL_BAR_H - thumb_h)) / (total_content_height > win_h - URL_BAR_H ? total_content_height - (win_h - URL_BAR_H) : 1);
|
||||
ui_draw_rect(win_browser, win_w - SCROLL_BAR_W + 2, thumb_y, SCROLL_BAR_W - 4, thumb_h, COLOR_SCROLL_BTN);
|
||||
int viewport_h = win_h - URL_BAR_H;
|
||||
browser_scrollbar.x = win_w - SCROLL_BAR_W;
|
||||
browser_scrollbar.y = URL_BAR_H;
|
||||
browser_scrollbar.w = SCROLL_BAR_W;
|
||||
browser_scrollbar.h = viewport_h;
|
||||
browser_scrollbar.on_scroll = browser_on_scroll;
|
||||
widget_scrollbar_update(&browser_scrollbar, total_content_height, scroll_y);
|
||||
widget_scrollbar_draw(&browser_ctx, &browser_scrollbar);
|
||||
}
|
||||
|
||||
static void navigate(const char *url) {
|
||||
@@ -1593,34 +1802,53 @@ int main(int argc, char **argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
else if (ev.type == GUI_EVENT_CLICK) {
|
||||
else if (ev.type == GUI_EVENT_CLICK || ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_MOUSE_UP || ev.type == GUI_EVENT_MOUSE_MOVE) {
|
||||
int mx = ev.arg1;
|
||||
if (mx >= win_w - SCROLL_BAR_W) {
|
||||
if (ev.arg2 < URL_BAR_H + (win_h - URL_BAR_H)/2) scroll_y -= 100;
|
||||
else scroll_y += 100;
|
||||
if (scroll_y < 0) scroll_y = 0;
|
||||
int my = ev.arg2;
|
||||
bool is_down = (ev.type == GUI_EVENT_MOUSE_DOWN || (ev.type == GUI_EVENT_MOUSE_MOVE && browser_scrollbar.is_dragging));
|
||||
bool is_click = (ev.type == GUI_EVENT_CLICK);
|
||||
|
||||
int old_scroll = scroll_y;
|
||||
bool was_dragging = browser_scrollbar.is_dragging;
|
||||
if (widget_scrollbar_handle_mouse(&browser_scrollbar, mx, my, is_down, &browser_ctx)) {
|
||||
if (scroll_y != old_scroll || browser_scrollbar.is_dragging || was_dragging) {
|
||||
needs_repaint = true;
|
||||
}
|
||||
if (ev.type == GUI_EVENT_MOUSE_MOVE) continue;
|
||||
if (is_down || is_click) continue;
|
||||
}
|
||||
|
||||
if (ev.type != GUI_EVENT_CLICK && ev.type != GUI_EVENT_MOUSE_DOWN) continue;
|
||||
|
||||
if (my < URL_BAR_H) {
|
||||
if (widget_textbox_handle_mouse(&url_tb, mx, my, is_click, NULL)) {
|
||||
focused_element = -1;
|
||||
needs_repaint = true;
|
||||
continue;
|
||||
}
|
||||
if (ev.arg2 < URL_BAR_H) {
|
||||
// Check back button
|
||||
if (mx >= BACK_BTN_X && mx < BACK_BTN_X + BTN_W && history_count > 0) {
|
||||
if (widget_button_handle_mouse(&btn_back, mx, my, is_down, is_click, NULL)) {
|
||||
if (is_click && history_count > 0) {
|
||||
history_count--;
|
||||
int j=0; while(history_stack[history_count][j]) { url_input_buffer[j] = history_stack[history_count][j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
|
||||
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
|
||||
}
|
||||
needs_repaint = true; continue;
|
||||
}
|
||||
// Check home button
|
||||
if (mx >= HOME_BTN_X && mx < HOME_BTN_X + BTN_W) {
|
||||
if (widget_button_handle_mouse(&btn_home, mx, my, is_down, is_click, NULL)) {
|
||||
if (is_click) {
|
||||
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
|
||||
const char *home = "http://find.boreddev.nl";
|
||||
int j=0; while(home[j]) { url_input_buffer[j] = home[j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
|
||||
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
|
||||
}
|
||||
needs_repaint = true; continue;
|
||||
}
|
||||
focused_element = -1; needs_repaint = true; continue;
|
||||
if (is_click) {
|
||||
focused_element = -1; needs_repaint = true;
|
||||
}
|
||||
int my = ev.arg2 - URL_BAR_H + scroll_y;
|
||||
continue;
|
||||
}
|
||||
my = ev.arg2 - URL_BAR_H + scroll_y;
|
||||
bool found = false;
|
||||
for (int i = 0; i < element_count; i++) {
|
||||
RenderElement *el = &elements[i];
|
||||
@@ -1637,15 +1865,38 @@ int main(int argc, char **argv) {
|
||||
if (el->tag == TAG_RADIO) {
|
||||
for (int k = 0; k < element_count; k++) {
|
||||
if (elements[k].tag == TAG_RADIO && elements[k].form_id == el->form_id && str_iequals(elements[k].input_name, el->input_name)) {
|
||||
if (elements[k].checked) {
|
||||
elements[k].checked = false;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, elements[k].x, elements[k].y - scroll_y + URL_BAR_H, elements[k].w, elements[k].h, "", true);
|
||||
cb.checked = false;
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||
}
|
||||
}
|
||||
}
|
||||
el->checked = true;
|
||||
needs_repaint = true; found = true; break;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, el->x, el->y - scroll_y + URL_BAR_H, el->w, el->h, "", true);
|
||||
cb.checked = true;
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||
found = true; break;
|
||||
}
|
||||
if (el->tag == TAG_CHECKBOX) {
|
||||
el->checked = !el->checked;
|
||||
needs_repaint = true; found = true; break;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, el->x, el->y - scroll_y + URL_BAR_H, el->w, el->h, "", false);
|
||||
cb.checked = el->checked;
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||
found = true; break;
|
||||
}
|
||||
if (el->tag == TAG_BUTTON) {
|
||||
int fid = el->form_id;
|
||||
@@ -1759,6 +2010,7 @@ int main(int argc, char **argv) {
|
||||
if (c == 13 || c == 10) {
|
||||
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
|
||||
navigate(url_input_buffer); scroll_y = 0;
|
||||
needs_repaint = true;
|
||||
}
|
||||
else if (c == 19) { if (url_cursor > 0) url_cursor--; }
|
||||
else if (c == 20) { int len = 0; while(url_input_buffer[len]) len++; if (url_cursor < len) url_cursor++; }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||
#include "syscall.h"
|
||||
#include "libui.h"
|
||||
#include "../../wm/libwidget.h"
|
||||
#include <stdbool.h>
|
||||
#include "stdlib.h"
|
||||
|
||||
@@ -25,6 +26,33 @@ static long long calc_decimal_divisor = 10;
|
||||
static char display_buffer[1024];
|
||||
static int display_buf_len = 0;
|
||||
|
||||
static widget_button_t buttons[20];
|
||||
static const char *labels[] = {
|
||||
"C", "sqr", "rt", "/",
|
||||
"7", "8", "9", "*",
|
||||
"4", "5", "6", "-",
|
||||
"1", "2", "3", "+",
|
||||
"0", ".", "BS", "="
|
||||
};
|
||||
|
||||
static void calc_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
|
||||
ui_draw_rect((ui_window_t)user_data, x, y, w, h, color);
|
||||
}
|
||||
static void calc_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
|
||||
ui_draw_rounded_rect_filled((ui_window_t)user_data, x, y, w, h, r, color);
|
||||
}
|
||||
static void calc_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
|
||||
ui_draw_string((ui_window_t)user_data, x, y, str, color);
|
||||
}
|
||||
|
||||
static widget_context_t calc_ctx = {
|
||||
.user_data = 0,
|
||||
.draw_rect = calc_draw_rect,
|
||||
.draw_rounded_rect_filled = calc_draw_rounded_rect_filled,
|
||||
.draw_string = calc_draw_string,
|
||||
.mark_dirty = NULL
|
||||
};
|
||||
|
||||
static long long isqrt(long long n) {
|
||||
if (n < 0) return -1;
|
||||
if (n == 0) return 0;
|
||||
@@ -97,27 +125,8 @@ static void calculator_paint(void) {
|
||||
int text_x = w - 15 - text_w;
|
||||
ui_draw_string(win_calculator, text_x, 18, display_buffer, COLOR_DARK_TEXT);
|
||||
|
||||
const char *labels[] = {
|
||||
"C", "sqr", "rt", "/",
|
||||
"7", "8", "9", "*",
|
||||
"4", "5", "6", "-",
|
||||
"1", "2", "3", "+",
|
||||
"0", ".", "BS", "="
|
||||
};
|
||||
|
||||
int bw = 35;
|
||||
int bh = 25;
|
||||
int gap = 5;
|
||||
int start_x = 10;
|
||||
int start_y = 40;
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
int r = i / 4;
|
||||
int c = i % 4;
|
||||
ui_draw_rounded_rect_filled(win_calculator, start_x + c*(bw+gap), start_y + r*(bh+gap), bw, bh, 4, COLOR_DARK_BORDER);
|
||||
int label_x = start_x + c*(bw+gap) + 5;
|
||||
int label_y = start_y + r*(bh+gap) + 9;
|
||||
ui_draw_string(win_calculator, label_x, label_y, labels[i], COLOR_DARK_TEXT);
|
||||
widget_button_draw(&calc_ctx, &buttons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,28 +144,15 @@ static void do_op(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void calculator_click(int x, int y) {
|
||||
int bw = 35;
|
||||
int bh = 25;
|
||||
int gap = 5;
|
||||
int start_x = 10;
|
||||
int start_y = 35; // Matches the hitboxes
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
int r = i / 4;
|
||||
int c = i % 4;
|
||||
int bx = start_x + c*(bw+gap);
|
||||
int by = start_y + r*(bh+gap);
|
||||
|
||||
if (x >= bx && x < bx + bw && y >= by && y < by + bh) {
|
||||
const char *labels[] = {
|
||||
static void handle_button_click(int idx) {
|
||||
const char *labels_map[] = {
|
||||
"C", "s", "r", "/",
|
||||
"7", "8", "9", "*",
|
||||
"4", "5", "6", "-",
|
||||
"1", "2", "3", "+",
|
||||
"0", ".", "B", "="
|
||||
};
|
||||
char lbl = labels[i][0];
|
||||
char lbl = labels_map[idx][0];
|
||||
|
||||
if (lbl >= '0' && lbl <= '9') {
|
||||
if (calc_new_entry || calc_error) {
|
||||
@@ -222,17 +218,18 @@ static void calculator_click(int x, int y) {
|
||||
}
|
||||
calc_op = lbl; calc_new_entry = true; calc_decimal_mode = false;
|
||||
}
|
||||
|
||||
update_display();
|
||||
calculator_paint();
|
||||
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
win_calculator = ui_window_create("Calculator", 200, 200, 180, 230);
|
||||
calc_ctx.user_data = (void *)win_calculator;
|
||||
|
||||
int bw = 35, bh = 25, gap = 5, start_x = 10, start_y = 40;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
int r = i / 4;
|
||||
int c = i % 4;
|
||||
widget_button_init(&buttons[i], start_x + c*(bw+gap), start_y + r*(bh+gap), bw, bh, labels[i]);
|
||||
}
|
||||
|
||||
calc_curr = 0;
|
||||
calc_acc = 0;
|
||||
@@ -249,8 +246,21 @@ int main(void) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
calculator_paint();
|
||||
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
calculator_click(ev.arg1, ev.arg2);
|
||||
} else if (ev.type == GUI_EVENT_CLICK || ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_MOUSE_UP) {
|
||||
bool needs_paint = false;
|
||||
for (int i=0; i<20; i++) {
|
||||
if (widget_button_handle_mouse(&buttons[i], ev.arg1, ev.arg2, ev.type == GUI_EVENT_MOUSE_DOWN, ev.type == GUI_EVENT_CLICK, NULL)) {
|
||||
needs_paint = true;
|
||||
if (ev.type == GUI_EVENT_CLICK) {
|
||||
handle_button_click(i);
|
||||
update_display();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needs_paint) {
|
||||
calculator_paint();
|
||||
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
|
||||
}
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
}
|
||||
|
||||
1610
src/userland/gui/grapher.c
Normal file
1610
src/userland/gui/grapher.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,9 +15,9 @@
|
||||
#define COLOR_BLACK 0xFF000000
|
||||
#define COLOR_WHITE 0xFFFFFFFF
|
||||
#define COLOR_RED 0xFFFF0000
|
||||
#define COLOR_APPLE_GREEN 0xFF4CD964
|
||||
#define COLOR_APPLE_BLUE 0xFF007AFF
|
||||
#define COLOR_APPLE_YELLOW 0xFFFFCC00
|
||||
#define COLOR_GREEN 0xFF4CD964
|
||||
#define COLOR_BLUE 0xFF007AFF
|
||||
#define COLOR_YELLOW 0xFFFFCC00
|
||||
|
||||
#define COLOR_DARK_BG 0xFF121212
|
||||
#define COLOR_DARK_PANEL 0xFF202020
|
||||
@@ -58,7 +58,7 @@ static void paint_paint(ui_window_t win) {
|
||||
ui_draw_rounded_rect_filled(win, canvas_x - 2, canvas_y - 2, CANVAS_W + 4, CANVAS_H + 4, 4, COLOR_DARK_BG);
|
||||
ui_draw_rounded_rect_filled(win, 10, 0, 40, 230, 6, COLOR_DARK_PANEL);
|
||||
|
||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE};
|
||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_WHITE};
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int cy = 10 + (i * 25);
|
||||
ui_draw_rounded_rect_filled(win, 15, cy, 30, 20, 3, colors[i]);
|
||||
@@ -202,7 +202,7 @@ static void paint_click(ui_window_t win, int x, int y) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int cy = 10 + (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};
|
||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_WHITE};
|
||||
current_color = colors[i];
|
||||
paint_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 380, 230);
|
||||
|
||||
@@ -9,16 +9,38 @@
|
||||
#include "stb_image_write.h"
|
||||
#include <string.h>
|
||||
|
||||
#define GUI_CMD_GET_SCREEN_SIZE 17
|
||||
#define GUI_CMD_GET_SCREENBUFFER 18
|
||||
#define GUI_CMD_SHOW_NOTIFICATION 19
|
||||
#define GUI_CMD_GET_DATETIME 20
|
||||
#define GUI_CMD_GET_SCREEN_SIZE 50
|
||||
#define GUI_CMD_GET_SCREENBUFFER 51
|
||||
#define GUI_CMD_SHOW_NOTIFICATION 52
|
||||
#define GUI_CMD_GET_DATETIME 53
|
||||
|
||||
#define PNG_WRITE_BUF_SIZE (8 * 1024 * 1024) // 8MB buffer to hold entire PNG
|
||||
static uint8_t *png_output_buf = NULL;
|
||||
static int png_output_idx = 0;
|
||||
|
||||
void png_write_func(void *context, void *data, int size) {
|
||||
int fd = *(int*)context;
|
||||
sys_write_fs(fd, data, size);
|
||||
(void)context;
|
||||
if (!png_output_buf) return;
|
||||
|
||||
if (png_output_idx + size < PNG_WRITE_BUF_SIZE) {
|
||||
memcpy(png_output_buf + png_output_idx, data, size);
|
||||
png_output_idx += size;
|
||||
}
|
||||
}
|
||||
|
||||
// Global filename for helper functions
|
||||
static char g_filename[128];
|
||||
|
||||
void append_num(int num, int digits) {
|
||||
int len = 0; while (g_filename[len]) len++;
|
||||
if (digits == 4) {
|
||||
g_filename[len++] = '0' + (num / 1000) % 10;
|
||||
g_filename[len++] = '0' + (num / 100) % 10;
|
||||
}
|
||||
g_filename[len++] = '0' + (num / 10) % 10;
|
||||
g_filename[len++] = '0' + (num % 10);
|
||||
g_filename[len] = '\0';
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
@@ -29,28 +51,25 @@ int main(int argc, char **argv) {
|
||||
syscall3(SYS_GUI, GUI_CMD_GET_SCREEN_SIZE, (uint64_t)&w, (uint64_t)&h);
|
||||
|
||||
if (w == 0 || h == 0 || w > 4096 || h > 4096) {
|
||||
printf("Failed to get screen size %d x %d\n", (int)w, (int)h);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. Allocate buffer for 0xAARRGGBB
|
||||
// 2. Allocate buffers
|
||||
uint32_t *pixels = (uint32_t *)malloc(w * h * sizeof(uint32_t));
|
||||
if (!pixels) {
|
||||
printf("Failed to allocate memory for %d x %d pixels\n", (int)w, (int)h);
|
||||
uint8_t *rgb_pixels = (uint8_t *)malloc(w * h * 3);
|
||||
png_output_buf = (uint8_t *)malloc(PNG_WRITE_BUF_SIZE);
|
||||
|
||||
if (!pixels || !rgb_pixels || !png_output_buf) {
|
||||
if (pixels) free(pixels);
|
||||
if (rgb_pixels) free(rgb_pixels);
|
||||
if (png_output_buf) free(png_output_buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 3. Request screenbuffer
|
||||
syscall2(SYS_GUI, GUI_CMD_GET_SCREENBUFFER, (uint64_t)pixels);
|
||||
|
||||
// 4. Convert 0xAARRGGBB to RGB for stb_image_write
|
||||
uint8_t *rgb_pixels = (uint8_t *)malloc(w * h * 3);
|
||||
if (!rgb_pixels) {
|
||||
printf("Failed to allocate RGB buffer\n");
|
||||
free(pixels);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 4. Convert 0xAARRGGBB to RGB
|
||||
for (int y = 0; y < (int)h; y++) {
|
||||
for (int x = 0; x < (int)w; x++) {
|
||||
uint32_t px = pixels[y * w + x];
|
||||
@@ -61,62 +80,50 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Get Datetime for filename
|
||||
// 5. Get Datetime and construct filename
|
||||
uint64_t dt[6] = {0};
|
||||
syscall2(SYS_GUI, GUI_CMD_GET_DATETIME, (uint64_t)dt);
|
||||
|
||||
char filename[128] = "A:/Desktop/screenshot-";
|
||||
strcpy(g_filename, "/Desktop/screenshot-");
|
||||
append_num((int)dt[0], 4); // Year
|
||||
append_num((int)dt[1], 2); // Month
|
||||
append_num((int)dt[2], 2); // Day
|
||||
|
||||
// Quick helper to append 4-digit and 2-digit numbers
|
||||
auto void append_num(int num, int digits);
|
||||
void append_num(int num, int digits) {
|
||||
int len = 0; while (filename[len]) len++;
|
||||
if (digits == 4) {
|
||||
filename[len++] = '0' + (num / 1000) % 10;
|
||||
filename[len++] = '0' + (num / 100) % 10;
|
||||
}
|
||||
filename[len++] = '0' + (num / 10) % 10;
|
||||
filename[len++] = '0' + (num % 10);
|
||||
filename[len] = '\0';
|
||||
}
|
||||
int len = strlen(g_filename);
|
||||
g_filename[len++] = '-'; g_filename[len] = '\0';
|
||||
|
||||
append_num((int)dt[0], 4);
|
||||
append_num((int)dt[1], 2);
|
||||
append_num((int)dt[2], 2);
|
||||
int len = 0; while (filename[len]) len++;
|
||||
filename[len++] = '-'; filename[len] = '\0';
|
||||
append_num((int)dt[3], 2);
|
||||
append_num((int)dt[4], 2);
|
||||
append_num((int)dt[5], 2);
|
||||
len = 0; while (filename[len]) len++;
|
||||
filename[len++] = '.'; filename[len++] = 'p'; filename[len++] = 'n'; filename[len++] = 'g'; filename[len] = '\0';
|
||||
append_num((int)dt[3], 2); // Hour
|
||||
append_num((int)dt[4], 2); // Min
|
||||
append_num((int)dt[5], 2); // Sec
|
||||
strcat(g_filename, ".png");
|
||||
|
||||
// 6. Write to PNG
|
||||
int fd = sys_open(filename, "w"); // Open file
|
||||
int res = 0;
|
||||
// 6. Generate PNG in memory
|
||||
png_output_idx = 0;
|
||||
int res = stbi_write_png_to_func(png_write_func, NULL, (int)w, (int)h, 3, rgb_pixels, (int)w * 3);
|
||||
|
||||
// 7. Perform a SINGLE write to the filesystem
|
||||
if (res && png_output_idx > 0) {
|
||||
int fd = sys_open(g_filename, "w");
|
||||
if (fd >= 0) {
|
||||
res = stbi_write_png_to_func(png_write_func, &fd, (int)w, (int)h, 3, rgb_pixels, (int)w * 3);
|
||||
sys_close(fd); // Close file
|
||||
sys_write_fs(fd, png_output_buf, png_output_idx);
|
||||
sys_close(fd);
|
||||
|
||||
// Show notification
|
||||
char notif[256] = "Saved ";
|
||||
strcat(notif, g_filename + 9); // Skip "/Desktop/"
|
||||
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)notif);
|
||||
} else {
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)"Failed to save screenshot");
|
||||
}
|
||||
|
||||
free(png_output_buf);
|
||||
free(rgb_pixels);
|
||||
free(pixels);
|
||||
|
||||
if (res) {
|
||||
char notif[256] = "Saved ";
|
||||
int nlen = 6;
|
||||
int flen = 0;
|
||||
while (filename[11 + flen]) {
|
||||
notif[nlen + flen] = filename[11 + flen];
|
||||
flen++;
|
||||
}
|
||||
notif[nlen + flen] = '\0';
|
||||
|
||||
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)notif);
|
||||
} else {
|
||||
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)"Failed to save screenshot");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return res ? 0 : 1;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,8 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define VIEWER_MAX_W 800
|
||||
#define VIEWER_MAX_H 600
|
||||
#define VIEWER_MAX_W 4096
|
||||
#define VIEWER_MAX_H 4096
|
||||
|
||||
static uint32_t *viewer_pixels = NULL;
|
||||
static uint32_t **viewer_frames = NULL;
|
||||
@@ -24,6 +24,8 @@ static int viewer_img_h = 0;
|
||||
static char viewer_title[64] = "Viewer";
|
||||
static bool viewer_has_image = false;
|
||||
static char viewer_file_path[256];
|
||||
static bool resize_pending = false;
|
||||
static uint64_t last_resize_tick = 0;
|
||||
|
||||
static int win_w = 500;
|
||||
static int win_h = 400;
|
||||
@@ -80,11 +82,13 @@ static void viewer_scale_rgba_to_argb(const unsigned char *rgba, int src_w, int
|
||||
}
|
||||
|
||||
static void viewer_paint(ui_window_t win) {
|
||||
int cx = 4;
|
||||
int cx = 0;
|
||||
int cy = 0;
|
||||
int cw = win_w - 8;
|
||||
int ch = win_h - 28;
|
||||
int cw = win_w;
|
||||
int ch = win_h - 20; // 20px header
|
||||
|
||||
// Clear background
|
||||
ui_draw_rect(win, 0, 0, win_w, win_h, 0xFF000000); // Black background
|
||||
|
||||
if (!viewer_has_image) {
|
||||
ui_draw_string(win, cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
|
||||
@@ -94,27 +98,24 @@ static void viewer_paint(ui_window_t win) {
|
||||
uint32_t *pixels = viewer_pixels;
|
||||
if (viewer_frames) pixels = viewer_frames[viewer_current_frame];
|
||||
|
||||
// Maintain aspect ratio while fitting to window
|
||||
int disp_w = viewer_img_w;
|
||||
int disp_h = viewer_img_h;
|
||||
|
||||
if (disp_w > cw - 8) {
|
||||
disp_h = disp_h * (cw - 8) / disp_w;
|
||||
disp_w = cw - 8;
|
||||
}
|
||||
if (disp_h > ch - 40) {
|
||||
disp_w = disp_w * (ch - 40) / disp_h;
|
||||
disp_h = ch - 40;
|
||||
}
|
||||
float sw = (float)cw / (float)viewer_img_w;
|
||||
float sh = (float)ch / (float)viewer_img_h;
|
||||
float scale = (sw < sh) ? sw : sh;
|
||||
|
||||
disp_w = (int)(viewer_img_w * scale);
|
||||
disp_h = (int)(viewer_img_h * scale);
|
||||
|
||||
int ox = cx + (cw - disp_w) / 2;
|
||||
int oy = cy + (ch - disp_h) / 2;
|
||||
|
||||
if (disp_w <= 0 || disp_h <= 0) return;
|
||||
|
||||
uint32_t *temp_buf = malloc(disp_w * disp_h * sizeof(uint32_t));
|
||||
if (temp_buf) {
|
||||
if (disp_w == viewer_img_w && disp_h == viewer_img_h) {
|
||||
// Fast path: 1:1
|
||||
for (int i = 0; i < disp_w * disp_h; i++) temp_buf[i] = pixels[i];
|
||||
} else {
|
||||
// Fixed-point 16.16
|
||||
uint32_t step_x = (viewer_img_w << 16) / disp_w;
|
||||
uint32_t step_y = (viewer_img_h << 16) / disp_h;
|
||||
@@ -134,7 +135,6 @@ static void viewer_paint(ui_window_t win) {
|
||||
}
|
||||
curr_y += step_y;
|
||||
}
|
||||
}
|
||||
ui_draw_image(win, ox, oy, disp_w, disp_h, temp_buf);
|
||||
free(temp_buf);
|
||||
}
|
||||
@@ -271,8 +271,13 @@ void viewer_open_file(const char *path) {
|
||||
viewer_title[ti] = 0;
|
||||
|
||||
win_w = fit_w + 16;
|
||||
if (win_w < 200) win_w = 200;
|
||||
win_h = fit_h + 34;
|
||||
|
||||
// Cap initial window size to 1024x768 (or image size if smaller)
|
||||
if (win_w > 1024) win_w = 1024;
|
||||
if (win_h > 768) win_h = 768;
|
||||
|
||||
if (win_w < 200) win_w = 200;
|
||||
if (win_h < 100) win_h = 100;
|
||||
}
|
||||
|
||||
@@ -284,18 +289,37 @@ int main(int argc, char **argv) {
|
||||
ui_window_t win = ui_window_create(viewer_title, 100, 50, win_w, win_h);
|
||||
if (!win) return 1;
|
||||
|
||||
ui_window_set_resizable(win, true);
|
||||
|
||||
gui_event_t ev;
|
||||
while (1) {
|
||||
if (ui_get_event(win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
viewer_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
||||
} else if (ev.type == GUI_EVENT_RESIZE) {
|
||||
win_w = ev.arg1;
|
||||
win_h = ev.arg2;
|
||||
resize_pending = true;
|
||||
last_resize_tick = sys_system(16, 0, 0, 0, 0);
|
||||
// Fast background clear during active resize
|
||||
ui_draw_rect(win, 0, 0, win_w, win_h, 0xFF000000);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
// No actions currently
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
}
|
||||
} else {
|
||||
if (resize_pending) {
|
||||
uint64_t now = sys_system(16, 0, 0, 0, 0);
|
||||
if (now > last_resize_tick + 10) {
|
||||
viewer_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
||||
resize_pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (viewer_has_image && viewer_frame_count > 1) {
|
||||
uint64_t now = sys_system(16, 0, 0, 0, 0);
|
||||
if (now >= viewer_next_frame_tick) {
|
||||
|
||||
@@ -255,3 +255,7 @@ int sys_get_os_info(os_info_t *info) {
|
||||
return (int)syscall5(SYS_SYSTEM, SYSTEM_CMD_GET_OS_INFO, (uint64_t)info, 0, 0, 0);
|
||||
}
|
||||
|
||||
void sys_parallel_run(void (*fn)(void*), void **args, int count) {
|
||||
syscall5(SYS_SYSTEM, SYSTEM_CMD_PARALLEL_RUN, (uint64_t)fn, (uint64_t)args, (uint64_t)count, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
#define SYSTEM_CMD_TCP_RECV_NB 42
|
||||
#define SYSTEM_CMD_YIELD 43
|
||||
#define SYSTEM_CMD_GET_OS_INFO 49
|
||||
#define SYSTEM_CMD_PARALLEL_RUN 50
|
||||
|
||||
// Internal assembly entry into Ring 0
|
||||
extern uint64_t syscall0(uint64_t sys_num);
|
||||
|
||||
@@ -374,7 +374,7 @@ static void cmd_init_config_defaults(void) {
|
||||
shell_config.default_text_color = 0xFFFFFFFF; // White
|
||||
shell_config.bg_color = 0xFF1E1E1E; // Dark background
|
||||
shell_config.cursor_color = 0xFFFFFFFF;
|
||||
shell_config.show_drive = true;
|
||||
shell_config.show_drive = false;
|
||||
shell_config.show_dir = true;
|
||||
shell_config.dir_color = 0xFF569CD6;
|
||||
shell_config.file_color = 0xFFFFFFFF;
|
||||
@@ -42,6 +42,28 @@ static int dropdown_menu_item_height = 25;
|
||||
#define FILE_CONTEXT_MENU_HEIGHT 50
|
||||
#define CONTEXT_MENU_ITEM_HEIGHT 25
|
||||
|
||||
static void wm_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
|
||||
(void)user_data; draw_rect(x, y, w, h, color);
|
||||
}
|
||||
static void wm_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
|
||||
(void)user_data; draw_rounded_rect_filled(x, y, w, h, r, color);
|
||||
}
|
||||
static void wm_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
|
||||
(void)user_data; draw_string(x, y, str, color);
|
||||
}
|
||||
static int wm_measure_string_width(void *user_data, const char *str) {
|
||||
(void)user_data;
|
||||
return font_manager_get_string_width(graphics_get_current_ttf(), str);
|
||||
}
|
||||
static widget_context_t wm_widget_ctx = {
|
||||
.user_data = NULL,
|
||||
.draw_rect = wm_draw_rect,
|
||||
.draw_rounded_rect_filled = wm_draw_rounded_rect_filled,
|
||||
.draw_string = wm_draw_string,
|
||||
.measure_string_width = wm_measure_string_width,
|
||||
.mark_dirty = NULL
|
||||
};
|
||||
|
||||
static char clipboard_path[FAT32_MAX_PATH] = "";
|
||||
static int clipboard_action = 0;
|
||||
#define FILE_CONTEXT_ITEMS 2
|
||||
@@ -386,7 +408,7 @@ bool explorer_delete_recursive(const char *path) {
|
||||
for (int k = i + 1; k < len; k++) filename[j++] = path[k];
|
||||
filename[j] = 0;
|
||||
|
||||
char drive_prefix[3] = "A:";
|
||||
char drive_prefix[3] = "/";
|
||||
if (path[0] && path[1] == ':') {
|
||||
drive_prefix[0] = path[0];
|
||||
}
|
||||
@@ -715,7 +737,7 @@ static void explorer_load_directory(Window *win, const char *path) {
|
||||
explorer_strcpy(state->items[temp_count].name, entries[i].name);
|
||||
state->items[temp_count].is_directory = entries[i].is_directory;
|
||||
state->items[temp_count].size = entries[i].size;
|
||||
state->items[temp_count].color = entries[i].is_directory ? COLOR_APPLE_BLUE : COLOR_APPLE_YELLOW;
|
||||
state->items[temp_count].color = entries[i].is_directory ? COLOR_BLUE : COLOR_YELLOW;
|
||||
temp_count++;
|
||||
}
|
||||
|
||||
@@ -788,15 +810,15 @@ static void explorer_open_target(const char *path) {
|
||||
if (explorer_str_ends_with(path, ".elf")) {
|
||||
process_create_elf(path, NULL);
|
||||
} else if (explorer_str_ends_with(path, ".pdf")) {
|
||||
process_create_elf("A:/bin/boredword.elf", path);
|
||||
process_create_elf("/bin/boredword.elf", path);
|
||||
} else if (explorer_is_markdown_file(path)) {
|
||||
process_create_elf("A:/bin/markdown.elf", path);
|
||||
process_create_elf("/bin/markdown.elf", path);
|
||||
} else if (explorer_str_ends_with(path, ".pnt")) {
|
||||
process_create_elf("A:/bin/paint.elf", path);
|
||||
process_create_elf("/bin/paint.elf", path);
|
||||
} else if (explorer_is_image_file(path)) {
|
||||
process_create_elf("A:/bin/viewer.elf", path);
|
||||
process_create_elf("/bin/viewer.elf", path);
|
||||
} else {
|
||||
process_create_elf("A:/bin/txtedit.elf", path);
|
||||
process_create_elf("/bin/txtedit.elf", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -898,7 +920,7 @@ static void explorer_paint(Window *win) {
|
||||
DirtyRect dirty = graphics_get_dirty_rect();
|
||||
|
||||
|
||||
graphics_set_clipping(offset_x, offset_y, win->w - 8, win->h - 28);
|
||||
graphics_push_clipping(offset_x, offset_y, win->w - 8, win->h - 28);
|
||||
|
||||
draw_rect(offset_x, offset_y, win->w - 8, win->h - 28, COLOR_DARK_BG);
|
||||
|
||||
@@ -950,20 +972,19 @@ static void explorer_paint(Window *win) {
|
||||
draw_string(path_x + 6 + path_label_w + 6, offset_y + 8, state->current_path, COLOR_DARK_TEXT);
|
||||
|
||||
int dropdown_btn_x = win->x + win->w - 90;
|
||||
draw_rounded_rect_filled(dropdown_btn_x, offset_y + 3, 35, 22, 5, COLOR_DARK_PANEL);
|
||||
draw_string(dropdown_btn_x + 10, offset_y + 8, "...", COLOR_DARK_TEXT);
|
||||
widget_button_init(&state->btn_dropdown, dropdown_btn_x, offset_y + 3, 35, 22, "...");
|
||||
widget_button_init(&state->btn_back, win->x + win->w - 40, offset_y + 3, 30, 22, "<");
|
||||
widget_button_init(&state->btn_up, win->x + win->w - 160, offset_y + 3, 30, 22, "^");
|
||||
widget_button_init(&state->btn_fwd, win->x + win->w - 125, offset_y + 3, 30, 22, "v");
|
||||
|
||||
draw_rounded_rect_filled(win->x + win->w - 40, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
|
||||
draw_string(win->x + win->w - 32, offset_y + 8, "<", COLOR_DARK_TEXT);
|
||||
|
||||
draw_rounded_rect_filled(win->x + win->w - 160, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
|
||||
draw_string(win->x + win->w - 150, offset_y + 8, "^", COLOR_DARK_TEXT);
|
||||
draw_rounded_rect_filled(win->x + win->w - 125, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
|
||||
draw_string(win->x + win->w - 115, offset_y + 8, "v", COLOR_DARK_TEXT);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_dropdown);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_back);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_up);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_fwd);
|
||||
|
||||
int content_start_y = offset_y + 30;
|
||||
|
||||
graphics_set_clipping(win->x + 4, content_start_y, win->w - 8, win->h - 54 - 4);
|
||||
graphics_push_clipping(win->x + 4, content_start_y, win->w - 8, win->h - 54 - 4);
|
||||
|
||||
for (int i = 0; i < state->item_count; i++) {
|
||||
int row = i / EXPLORER_COLS;
|
||||
@@ -989,11 +1010,8 @@ static void explorer_paint(Window *win) {
|
||||
}
|
||||
|
||||
|
||||
if (dirty.active) {
|
||||
graphics_set_clipping(dirty.x, dirty.y, dirty.w, dirty.h);
|
||||
} else {
|
||||
graphics_clear_clipping();
|
||||
}
|
||||
graphics_pop_clipping(); // Pop content clipping
|
||||
graphics_pop_clipping(); // Pop main window clipping
|
||||
|
||||
if (state->drive_menu_visible) {
|
||||
int menu_x = win->x + 4;
|
||||
@@ -1043,25 +1061,15 @@ static void explorer_paint(Window *win) {
|
||||
|
||||
draw_string(dlg_x + 10, dlg_y + 10, "Create New File", COLOR_WHITE);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG);
|
||||
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE);
|
||||
{ int max_w = 265;
|
||||
ttf_font_t *ttf_ = graphics_get_current_ttf();
|
||||
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
|
||||
int scroll_x = 0;
|
||||
if (total_w > max_w) scroll_x = total_w - max_w;
|
||||
char sub_[128]; int k_=0;
|
||||
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_];
|
||||
sub_[k_]=0;
|
||||
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
|
||||
if (cx_ < 0) cx_ = 0;
|
||||
if (cx_ > max_w) cx_ = max_w;
|
||||
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
|
||||
widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
|
||||
state->dialog_textbox.focused = true;
|
||||
state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
|
||||
widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 70, dlg_y + 72, "Create", COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
||||
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Create");
|
||||
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||
} else if (state->dialog_state == DIALOG_CREATE_FOLDER) {
|
||||
int dlg_x = win->x + win->w / 2 - 150;
|
||||
int dlg_y = win->y + win->h / 2 - 60;
|
||||
@@ -1070,25 +1078,15 @@ static void explorer_paint(Window *win) {
|
||||
|
||||
draw_string(dlg_x + 10, dlg_y + 10, "Create New Folder", COLOR_WHITE);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG);
|
||||
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE);
|
||||
{ int max_w = 265;
|
||||
ttf_font_t *ttf_ = graphics_get_current_ttf();
|
||||
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
|
||||
int scroll_x = 0;
|
||||
if (total_w > max_w) scroll_x = total_w - max_w;
|
||||
char sub_[128]; int k_=0;
|
||||
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_];
|
||||
sub_[k_]=0;
|
||||
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
|
||||
if (cx_ < 0) cx_ = 0;
|
||||
if (cx_ > max_w) cx_ = max_w;
|
||||
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
|
||||
widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
|
||||
state->dialog_textbox.focused = true;
|
||||
state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
|
||||
widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 70, dlg_y + 72, "Create", COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
||||
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Create");
|
||||
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||
} else if (state->dialog_state == DIALOG_DELETE_CONFIRM) {
|
||||
int dlg_x = win->x + win->w / 2 - 150;
|
||||
int dlg_y = win->y + win->h / 2 - 60;
|
||||
@@ -1107,8 +1105,14 @@ static void explorer_paint(Window *win) {
|
||||
}
|
||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, 0xFF8B2020);
|
||||
draw_string(dlg_x + 68, dlg_y + 72, "Delete", COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
||||
|
||||
// Use libwidget only for the cancel button, or do we want to use libwidget for the delete button too?
|
||||
// Let's use libwidget but the delete button needs red styling so maybe just keep it manual or make it secondary.
|
||||
// Actually wait, I will use libwidget for both and let the text dictate the action, we can't style individual buttons yet.
|
||||
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Delete");
|
||||
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
|
||||
int dlg_x = win->x + win->w / 2 - 150;
|
||||
int dlg_y = win->y + win->h / 2 - 60;
|
||||
@@ -1120,10 +1124,10 @@ static void explorer_paint(Window *win) {
|
||||
draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA);
|
||||
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 63, dlg_y + 77, "Replace", COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE);
|
||||
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Replace");
|
||||
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
|
||||
int dlg_x = win->x + win->w / 2 - 150;
|
||||
int dlg_y = win->y + win->h / 2 - 60;
|
||||
@@ -1150,10 +1154,10 @@ static void explorer_paint(Window *win) {
|
||||
draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", 0xFFAAAAAA);
|
||||
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 57, dlg_y + 77, "Overwrite", COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE);
|
||||
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Overwrite");
|
||||
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||
} else if (state->dialog_state == DIALOG_ERROR) {
|
||||
int dlg_x = win->x + win->w / 2 - 150;
|
||||
int dlg_y = win->y + win->h / 2 - 60;
|
||||
@@ -1163,32 +1167,23 @@ static void explorer_paint(Window *win) {
|
||||
draw_string(dlg_x + 10, dlg_y + 10, "Error", 0xFFFF6B6B);
|
||||
draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, 0xFFAAAAAA);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 110, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 138, dlg_y + 77, "OK", COLOR_WHITE);
|
||||
widget_button_init(&state->btn_primary, dlg_x + 110, dlg_y + 70, 80, 25, "OK");
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||
} else if (state->dialog_state == DIALOG_RENAME) {
|
||||
int dlg_x = win->x + win->w / 2 - 150;
|
||||
int dlg_y = win->y + win->h / 2 - 60;
|
||||
|
||||
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
|
||||
draw_string(dlg_x + 10, dlg_y + 10, "Rename", COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG);
|
||||
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE);
|
||||
{ int max_w = 265;
|
||||
ttf_font_t *ttf_ = graphics_get_current_ttf();
|
||||
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
|
||||
int scroll_x = 0;
|
||||
if (total_w > max_w) scroll_x = total_w - max_w;
|
||||
char sub_[128]; int k_=0;
|
||||
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_];
|
||||
sub_[k_]=0;
|
||||
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
|
||||
if (cx_ < 0) cx_ = 0;
|
||||
if (cx_ > max_w) cx_ = max_w;
|
||||
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
|
||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 68, dlg_y + 72, "Rename", COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
||||
widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
|
||||
state->dialog_textbox.focused = true;
|
||||
state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
|
||||
widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
|
||||
|
||||
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Rename");
|
||||
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||
}
|
||||
|
||||
if (state->file_context_menu_visible) {
|
||||
@@ -1220,6 +1215,9 @@ static void explorer_paint(Window *win) {
|
||||
}
|
||||
|
||||
|
||||
#define WIDGET_CLICKED(btn, cx, cy) ((cx) >= (btn)->x && (cx) < (btn)->x + (btn)->w && (cy) >= (btn)->y && (cy) < (btn)->y + (btn)->h)
|
||||
#define TEXTBOX_CLICKED(tb, cx, cy) ((cx) >= (tb)->x && (cx) < (tb)->x + (tb)->w && (cy) >= (tb)->y && (cy) < (tb)->y + (tb)->h)
|
||||
|
||||
static void explorer_handle_click(Window *win, int x, int y) {
|
||||
ExplorerState *state = (ExplorerState*)win->data;
|
||||
if (state->file_context_menu_visible) {
|
||||
@@ -1228,106 +1226,90 @@ static void explorer_handle_click(Window *win, int x, int y) {
|
||||
}
|
||||
|
||||
if (state->dialog_state == DIALOG_CREATE_FILE || state->dialog_state == DIALOG_CREATE_FOLDER) {
|
||||
int dlg_x = win->w / 2 - 150;
|
||||
int dlg_y = win->h / 2 - 80;
|
||||
|
||||
if (x >= dlg_x + 50 && x < dlg_x + 130 &&
|
||||
y >= dlg_y + 65 && y < dlg_y + 90) {
|
||||
if (state->dialog_state == DIALOG_CREATE_FILE) {
|
||||
dialog_confirm_create_file(win);
|
||||
} else {
|
||||
dialog_confirm_create_folder(win);
|
||||
}
|
||||
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_primary.pressed = false;
|
||||
if (state->dialog_state == DIALOG_CREATE_FILE) dialog_confirm_create_file(win);
|
||||
else dialog_confirm_create_folder(win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= dlg_x + 170 && x < dlg_x + 250 &&
|
||||
y >= dlg_y + 65 && y < dlg_y + 90) {
|
||||
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y)) {
|
||||
state->btn_secondary.pressed = false;
|
||||
dialog_close(win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= dlg_x + 10 && x < dlg_x + 290 &&
|
||||
y >= dlg_y + 35 && y < dlg_y + 55) {
|
||||
state->dialog_input_cursor = (x - dlg_x - 15) / 8;
|
||||
if (TEXTBOX_CLICKED(&state->dialog_textbox, win->x + x, win->y + y)) {
|
||||
state->dialog_input_cursor = (win->x + x - state->dialog_textbox.x - 5) / 8;
|
||||
if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) {
|
||||
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
||||
}
|
||||
if (state->dialog_input_cursor < 0) state->dialog_input_cursor = 0;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (state->dialog_state == DIALOG_DELETE_CONFIRM) {
|
||||
int dlg_x = win->w / 2 - 150;
|
||||
int dlg_y = win->h / 2 - 80;
|
||||
|
||||
if (x >= dlg_x + 50 && x < dlg_x + 130 &&
|
||||
y >= dlg_y + 65 && y < dlg_y + 90) {
|
||||
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_primary.pressed = false;
|
||||
dialog_confirm_delete(win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= dlg_x + 170 && x < dlg_x + 250 &&
|
||||
y >= dlg_y + 65 && y < dlg_y + 90) {
|
||||
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y)) {
|
||||
state->btn_secondary.pressed = false;
|
||||
dialog_close(win);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
|
||||
int dlg_x = win->w / 2 - 150;
|
||||
int dlg_y = win->h / 2 - 80;
|
||||
|
||||
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
||||
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_primary.pressed = false;
|
||||
dialog_confirm_replace(win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
||||
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_secondary.pressed = false;
|
||||
dialog_close(win);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
|
||||
int dlg_x = win->w / 2 - 150;
|
||||
int dlg_y = win->h / 2 - 80;
|
||||
|
||||
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
||||
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_primary.pressed = false;
|
||||
dialog_confirm_replace_move(win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
||||
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_secondary.pressed = false;
|
||||
dialog_close(win);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (state->dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) {
|
||||
int dlg_x = win->w / 2 - 150;
|
||||
int dlg_y = win->h / 2 - 80;
|
||||
|
||||
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
||||
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_primary.pressed = false;
|
||||
dialog_force_create_file(win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
||||
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_secondary.pressed = false;
|
||||
dialog_close(win);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (state->dialog_state == DIALOG_ERROR) {
|
||||
int dlg_x = win->w / 2 - 150;
|
||||
int dlg_y = win->h / 2 - 80;
|
||||
|
||||
if (x >= dlg_x + 110 && x < dlg_x + 190 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
||||
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_primary.pressed = false;
|
||||
dialog_close(win);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (state->dialog_state == DIALOG_RENAME) {
|
||||
int dlg_x = win->w / 2 - 150;
|
||||
int dlg_y = win->h / 2 - 80;
|
||||
|
||||
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 65 && y < dlg_y + 90) {
|
||||
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_primary.pressed = false;
|
||||
char new_path[FAT32_MAX_PATH];
|
||||
explorer_strcpy(new_path, state->current_path);
|
||||
if (new_path[explorer_strlen(new_path)-1] != '/') explorer_strcat(new_path, "/");
|
||||
@@ -1338,16 +1320,17 @@ static void explorer_handle_click(Window *win, int x, int y) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 65 && y < dlg_y + 90) {
|
||||
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
|
||||
state->btn_secondary.pressed = false;
|
||||
dialog_close(win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= dlg_x + 10 && x < dlg_x + 290 && y >= dlg_y + 35 && y < dlg_y + 55) {
|
||||
state->dialog_input_cursor = (x - dlg_x - 15) / 8;
|
||||
if (TEXTBOX_CLICKED(&state->dialog_textbox, win->x + x, win->y + y + 20)) {
|
||||
state->dialog_input_cursor = (win->x + x - state->dialog_textbox.x - 5) / 8;
|
||||
if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) {
|
||||
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
||||
}
|
||||
if (state->dialog_input_cursor < 0) state->dialog_input_cursor = 0;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
@@ -1420,27 +1403,27 @@ static void explorer_handle_click(Window *win, int x, int y) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= win->w - 90 && x < win->w - 55 &&
|
||||
y >= button_y && y < button_y + 22) {
|
||||
if (WIDGET_CLICKED(&state->btn_dropdown, win->x + x, win->y + y + 20)) {
|
||||
state->btn_dropdown.pressed = false;
|
||||
dropdown_menu_toggle(win);
|
||||
state->drive_menu_visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= win->w - 40 && x < win->w - 10 &&
|
||||
y >= button_y && y < button_y + 22) {
|
||||
if (WIDGET_CLICKED(&state->btn_back, win->x + x, win->y + y + 20)) {
|
||||
state->btn_back.pressed = false;
|
||||
explorer_navigate_to(win, "..");
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= win->w - 160 && x < win->w - 130 &&
|
||||
y >= button_y && y < button_y + 22) {
|
||||
if (WIDGET_CLICKED(&state->btn_up, win->x + x, win->y + y + 20)) {
|
||||
state->btn_up.pressed = false;
|
||||
if (state->explorer_scroll_row > 0) state->explorer_scroll_row--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= win->w - 125 && x < win->w - 95 &&
|
||||
y >= button_y && y < button_y + 22) {
|
||||
if (WIDGET_CLICKED(&state->btn_fwd, win->x + x, win->y + y + 20)) {
|
||||
state->btn_fwd.pressed = false;
|
||||
int total_rows = (state->item_count + EXPLORER_COLS - 1) / EXPLORER_COLS;
|
||||
if (total_rows == 0) total_rows = 1;
|
||||
if (state->explorer_scroll_row < total_rows - (EXPLORER_ROWS - 1)) state->explorer_scroll_row++;
|
||||
@@ -1716,7 +1699,7 @@ static void explorer_handle_file_context_menu_click(Window *win, int x, int y) {
|
||||
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
||||
explorer_strcpy(state->dialog_target_path, full_path);
|
||||
} else if (clicked_action == 110) { // Open with Text Editor
|
||||
process_create_elf("A:/bin/txtedit.elf", full_path);
|
||||
process_create_elf("/bin/txtedit.elf", full_path);
|
||||
} else if (clicked_action == ACTION_RESTORE) {
|
||||
explorer_restore_file(win, state->file_context_menu_item);
|
||||
} else if (clicked_action == ACTION_CREATE_SHORTCUT) {
|
||||
@@ -1900,12 +1883,12 @@ Window* explorer_create_window(const char *path) {
|
||||
state->explorer_scroll_row = 0;
|
||||
state->dialog_state = DIALOG_NONE;
|
||||
|
||||
if (explorer_strcmp(path, "/") == 0) explorer_load_directory(win, "A:/");
|
||||
if (explorer_strcmp(path, "/") == 0) explorer_load_directory(win, "/");
|
||||
else explorer_load_directory(win, path);
|
||||
|
||||
explorer_wins[explorer_win_count++] = win;
|
||||
wm_add_window(win);
|
||||
wm_bring_to_front(win);
|
||||
wm_add_window_locked(win);
|
||||
// wm_add_window_locked already calls wm_bring_to_front_locked!
|
||||
|
||||
return win;
|
||||
}
|
||||
@@ -1937,11 +1920,11 @@ void explorer_init(void) {
|
||||
state->dialog_state = DIALOG_NONE;
|
||||
|
||||
explorer_wins[explorer_win_count++] = &win_explorer;
|
||||
explorer_load_directory(&win_explorer, "A:/");
|
||||
explorer_load_directory(&win_explorer, "/");
|
||||
}
|
||||
void explorer_reset(void) {
|
||||
ExplorerState *state = (ExplorerState*)win_explorer.data;
|
||||
explorer_load_directory(&win_explorer, "A:/");
|
||||
explorer_load_directory(&win_explorer, "/");
|
||||
state->explorer_scroll_row = 0;
|
||||
win_explorer.focused = false;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "wm.h"
|
||||
#include "fat32.h"
|
||||
#include <stddef.h>
|
||||
#include "libwidget.h"
|
||||
|
||||
// External windows references (for opening other apps)
|
||||
extern Window win_explorer;
|
||||
@@ -55,6 +56,16 @@ typedef struct {
|
||||
int file_context_menu_y;
|
||||
int file_context_menu_item;
|
||||
|
||||
// GUI widgets
|
||||
widget_button_t btn_primary;
|
||||
widget_button_t btn_secondary;
|
||||
widget_button_t btn_dropdown;
|
||||
widget_button_t btn_back;
|
||||
widget_button_t btn_up;
|
||||
widget_button_t btn_fwd;
|
||||
|
||||
widget_textbox_t dialog_textbox;
|
||||
|
||||
} ExplorerState;
|
||||
|
||||
void explorer_init(void);
|
||||
|
||||
@@ -71,12 +71,13 @@ static int loaded_font_count = 0;
|
||||
|
||||
#define FONT_CACHE_SIZE 2048
|
||||
typedef struct {
|
||||
char c;
|
||||
float pixel_height;
|
||||
uint32_t codepoint;
|
||||
float scale;
|
||||
void *font;
|
||||
int w, h, xoff, yoff;
|
||||
unsigned char *bitmap;
|
||||
} font_cache_entry_t;
|
||||
|
||||
static font_cache_entry_t font_cache[FONT_CACHE_SIZE] = {0};
|
||||
|
||||
bool font_manager_init(void) {
|
||||
return true;
|
||||
@@ -200,11 +201,17 @@ void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, uint32_t co
|
||||
if (!font) font = default_font;
|
||||
if (!font) return;
|
||||
|
||||
stbtt_fontinfo *info = (stbtt_fontinfo *)font->info;
|
||||
uint32_t hash = (codepoint * 31 + (uint32_t)scale * 73) % FONT_CACHE_SIZE;
|
||||
font_cache_entry_t *entry = &font_cache[hash];
|
||||
|
||||
unsigned char *bitmap = NULL;
|
||||
int w, h, xoff, yoff;
|
||||
|
||||
if (entry->bitmap && entry->codepoint == codepoint && entry->scale == scale && entry->font == font) {
|
||||
bitmap = entry->bitmap;
|
||||
w = entry->w; h = entry->h; xoff = entry->xoff; yoff = entry->yoff;
|
||||
} else {
|
||||
stbtt_fontinfo *info = (stbtt_fontinfo *)font->info;
|
||||
float real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||
|
||||
if (stbtt_FindGlyphIndex(info, codepoint) == 0 && fallback_font) {
|
||||
@@ -214,6 +221,14 @@ void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, uint32_t co
|
||||
|
||||
bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff);
|
||||
|
||||
if (entry->bitmap) stbtt_FreeBitmap(entry->bitmap, NULL);
|
||||
entry->codepoint = codepoint;
|
||||
entry->scale = scale;
|
||||
entry->font = font;
|
||||
entry->w = w; entry->h = h; entry->xoff = xoff; entry->yoff = yoff;
|
||||
entry->bitmap = bitmap;
|
||||
}
|
||||
|
||||
if (bitmap) {
|
||||
for (int row = 0; row < h; row++) {
|
||||
for (int col = 0; col < w; col++) {
|
||||
@@ -226,7 +241,6 @@ void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, uint32_t co
|
||||
}
|
||||
}
|
||||
}
|
||||
stbtt_FreeBitmap(bitmap, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,11 +248,17 @@ void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t co
|
||||
if (!font) font = default_font;
|
||||
if (!font) return;
|
||||
|
||||
stbtt_fontinfo *info = (stbtt_fontinfo *)font->info;
|
||||
uint32_t hash = (codepoint * 31 + (uint32_t)scale * 73) % FONT_CACHE_SIZE;
|
||||
font_cache_entry_t *entry = &font_cache[hash];
|
||||
|
||||
unsigned char *bitmap = NULL;
|
||||
int w, h, xoff, yoff;
|
||||
|
||||
if (entry->bitmap && entry->codepoint == codepoint && entry->scale == scale && entry->font == font) {
|
||||
bitmap = entry->bitmap;
|
||||
w = entry->w; h = entry->h; xoff = entry->xoff; yoff = entry->yoff;
|
||||
} else {
|
||||
stbtt_fontinfo *info = (stbtt_fontinfo *)font->info;
|
||||
float real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||
|
||||
if (stbtt_FindGlyphIndex(info, codepoint) == 0 && fallback_font) {
|
||||
@@ -248,6 +268,14 @@ void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t co
|
||||
|
||||
bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff);
|
||||
|
||||
if (entry->bitmap) stbtt_FreeBitmap(entry->bitmap, NULL);
|
||||
entry->codepoint = codepoint;
|
||||
entry->scale = scale;
|
||||
entry->font = font;
|
||||
entry->w = w; entry->h = h; entry->xoff = xoff; entry->yoff = yoff;
|
||||
entry->bitmap = bitmap;
|
||||
}
|
||||
|
||||
if (bitmap) {
|
||||
for (int row = 0; row < h; row++) {
|
||||
int slant_offset = (int)((h - row) * slope);
|
||||
@@ -262,7 +290,6 @@ void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t co
|
||||
}
|
||||
}
|
||||
}
|
||||
stbtt_FreeBitmap(bitmap, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,19 @@ static DirtyRect g_dirty = {0, 0, 0, 0, false};
|
||||
#define MAX_FB_HEIGHT 2048
|
||||
static uint32_t g_back_buffer[MAX_FB_WIDTH * MAX_FB_HEIGHT] __attribute__((aligned(4096)));
|
||||
|
||||
static int g_clip_x = 0, g_clip_y = 0, g_clip_w = 0, g_clip_h = 0;
|
||||
static bool g_clip_enabled = false;
|
||||
#define MAX_RENDER_CPUS 32
|
||||
#define CLIP_STACK_DEPTH 8
|
||||
static int g_clip_stack_x[MAX_RENDER_CPUS][CLIP_STACK_DEPTH];
|
||||
static int g_clip_stack_y[MAX_RENDER_CPUS][CLIP_STACK_DEPTH];
|
||||
static int g_clip_stack_w[MAX_RENDER_CPUS][CLIP_STACK_DEPTH];
|
||||
static int g_clip_stack_h[MAX_RENDER_CPUS][CLIP_STACK_DEPTH];
|
||||
static int g_clip_stack_ptr[MAX_RENDER_CPUS] = {0};
|
||||
static bool g_clip_enabled[MAX_RENDER_CPUS] = {false};
|
||||
|
||||
static uint32_t *g_render_target = NULL;
|
||||
static int g_rt_width = 0;
|
||||
static int g_rt_height = 0;
|
||||
extern uint32_t smp_this_cpu_id(void);
|
||||
static uint32_t *g_render_target[MAX_RENDER_CPUS] = {0};
|
||||
static int g_rt_width[MAX_RENDER_CPUS] = {0};
|
||||
static int g_rt_height[MAX_RENDER_CPUS] = {0};
|
||||
|
||||
static ttf_font_t *g_current_ttf = NULL;
|
||||
|
||||
@@ -176,15 +183,19 @@ void graphics_clear_dirty_no_lock(void) {
|
||||
}
|
||||
|
||||
void graphics_set_render_target(uint32_t *buffer, int w, int h) {
|
||||
g_render_target = buffer;
|
||||
g_rt_width = w;
|
||||
g_rt_height = h;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
if (cpu < MAX_RENDER_CPUS) {
|
||||
g_render_target[cpu] = buffer;
|
||||
g_rt_width[cpu] = w;
|
||||
g_rt_height[cpu] = h;
|
||||
}
|
||||
}
|
||||
|
||||
void put_pixel(int x, int y, uint32_t color) {
|
||||
if (g_render_target) {
|
||||
if (x >= 0 && x < g_rt_width && y >= 0 && y < g_rt_height) {
|
||||
g_render_target[y * g_rt_width + x] = color;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
if (cpu < MAX_RENDER_CPUS && g_render_target[cpu]) {
|
||||
if (x >= 0 && x < g_rt_width[cpu] && y >= 0 && y < g_rt_height[cpu]) {
|
||||
g_render_target[cpu][y * g_rt_width[cpu] + x] = color;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -192,9 +203,10 @@ void put_pixel(int x, int y, uint32_t color) {
|
||||
if (!g_fb) return;
|
||||
if (x < 0 || x >= (int)g_fb->width || y < 0 || y >= (int)g_fb->height) return;
|
||||
|
||||
if (g_clip_enabled) {
|
||||
if (x < g_clip_x || x >= g_clip_x + g_clip_w ||
|
||||
y < g_clip_y || y >= g_clip_y + g_clip_h) {
|
||||
if (g_clip_enabled[cpu]) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
if (x < g_clip_stack_x[cpu][ptr] || x >= g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr] ||
|
||||
y < g_clip_stack_y[cpu][ptr] || y >= g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -204,9 +216,10 @@ void put_pixel(int x, int y, uint32_t color) {
|
||||
}
|
||||
|
||||
uint32_t graphics_get_pixel(int x, int y) {
|
||||
if (g_render_target) {
|
||||
if (x >= 0 && x < g_rt_width && y >= 0 && y < g_rt_height) {
|
||||
return g_render_target[y * g_rt_width + x];
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
if (cpu < MAX_RENDER_CPUS && g_render_target[cpu]) {
|
||||
if (x >= 0 && x < g_rt_width[cpu] && y >= 0 && y < g_rt_height[cpu]) {
|
||||
return g_render_target[cpu][y * g_rt_width[cpu] + x];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -220,15 +233,16 @@ uint32_t graphics_get_pixel(int x, int y) {
|
||||
void draw_rect(int x, int y, int w, int h, uint32_t color) {
|
||||
int x1 = x, y1 = y, x2 = x + w, y2 = y + h;
|
||||
|
||||
if (g_render_target) {
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
if (cpu < MAX_RENDER_CPUS && g_render_target[cpu]) {
|
||||
if (x1 < 0) x1 = 0;
|
||||
if (y1 < 0) y1 = 0;
|
||||
if (x2 > g_rt_width) x2 = g_rt_width;
|
||||
if (y2 > g_rt_height) y2 = g_rt_height;
|
||||
if (x2 > g_rt_width[cpu]) x2 = g_rt_width[cpu];
|
||||
if (y2 > g_rt_height[cpu]) y2 = g_rt_height[cpu];
|
||||
if (x1 >= x2 || y1 >= y2) return;
|
||||
|
||||
for (int i = y1; i < y2; i++) {
|
||||
uint32_t *row = &g_render_target[i * g_rt_width + x1];
|
||||
uint32_t *row = &g_render_target[cpu][i * g_rt_width[cpu] + x1];
|
||||
int len = x2 - x1;
|
||||
for (int j = 0; j < len; j++) {
|
||||
row[j] = color;
|
||||
@@ -239,11 +253,12 @@ void draw_rect(int x, int y, int w, int h, uint32_t color) {
|
||||
|
||||
if (!g_fb) return;
|
||||
|
||||
if (g_clip_enabled) {
|
||||
if (x1 < g_clip_x) x1 = g_clip_x;
|
||||
if (y1 < g_clip_y) y1 = g_clip_y;
|
||||
if (x2 > g_clip_x + g_clip_w) x2 = g_clip_x + g_clip_w;
|
||||
if (y2 > g_clip_y + g_clip_h) y2 = g_clip_y + g_clip_h;
|
||||
if (g_clip_enabled[cpu]) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
if (x1 < g_clip_stack_x[cpu][ptr]) x1 = g_clip_stack_x[cpu][ptr];
|
||||
if (y1 < g_clip_stack_y[cpu][ptr]) y1 = g_clip_stack_y[cpu][ptr];
|
||||
if (x2 > g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr]) x2 = g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr];
|
||||
if (y2 > g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) y2 = g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr];
|
||||
}
|
||||
|
||||
if (x1 < 0) x1 = 0;
|
||||
@@ -279,28 +294,39 @@ static int isqrt(int n) {
|
||||
void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color) {
|
||||
if (radius > w / 2) radius = w / 2;
|
||||
if (radius > h / 2) radius = h / 2;
|
||||
if (radius < 1) radius = 1;
|
||||
if (radius < 1) {
|
||||
// Draw a simple rect outline if no radius
|
||||
draw_rect(x, y, w, 1, color);
|
||||
draw_rect(x, y + h - 1, w, 1, color);
|
||||
draw_rect(x, y + 1, 1, h - 2, color);
|
||||
draw_rect(x + w - 1, y + 1, 1, h - 2, color);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw top and bottom edges
|
||||
// Draw top and bottom straight edges
|
||||
draw_rect(x + radius, y, w - 2*radius, 1, color);
|
||||
draw_rect(x + radius, y + h - 1, w - 2*radius, 1, color);
|
||||
|
||||
// Draw left and right edges
|
||||
// Draw left and right straight edges
|
||||
draw_rect(x, y + radius, 1, h - 2*radius, color);
|
||||
draw_rect(x + w - 1, y + radius, 1, h - 2*radius, color);
|
||||
|
||||
// Draw corner circles using integer approximation
|
||||
for (int i = 0; i < radius; i++) {
|
||||
int j = isqrt(radius*radius - i*i);
|
||||
// Draw four corner arcs
|
||||
for (int dy = 0; dy < radius; dy++) {
|
||||
int y_dist = radius - 1 - dy;
|
||||
int dx = isqrt(radius*radius - y_dist*y_dist);
|
||||
int next_dx = (dy < radius - 1) ? isqrt(radius*radius - (y_dist - 1)*(y_dist - 1)) : radius;
|
||||
|
||||
// Top-left corner
|
||||
put_pixel(x + radius - i - 1, y + radius - j, color);
|
||||
// Top-right corner
|
||||
put_pixel(x + w - radius + i, y + radius - j, color);
|
||||
// Bottom-left corner
|
||||
put_pixel(x + radius - i - 1, y + h - radius + j - 1, color);
|
||||
// Bottom-right corner
|
||||
put_pixel(x + w - radius + i, y + h - radius + j - 1, color);
|
||||
for (int i = dx; i < next_dx && i <= radius; i++) {
|
||||
// Top-left
|
||||
put_pixel(x + radius - 1 - i, y + dy, color);
|
||||
// Top-right
|
||||
put_pixel(x + w - radius + i, y + dy, color);
|
||||
// Bottom-left
|
||||
put_pixel(x + radius - 1 - i, y + h - 1 - dy, color);
|
||||
// Bottom-right
|
||||
put_pixel(x + w - radius + i, y + h - 1 - dy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,25 +334,21 @@ void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color) {
|
||||
void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t color) {
|
||||
if (radius > w / 2) radius = w / 2;
|
||||
if (radius > h / 2) radius = h / 2;
|
||||
if (radius < 1) radius = 1;
|
||||
if (radius < 1) {
|
||||
draw_rect(x, y, w, h, color);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw main rectangle body (center part without corners)
|
||||
draw_rect(x + radius, y, w - 2*radius, h, color);
|
||||
draw_rect(x, y + radius, radius, h - 2*radius, color);
|
||||
draw_rect(x + w - radius, y + radius, radius, h - 2*radius, color);
|
||||
// Draw main rectangle body
|
||||
draw_rect(x, y + radius, w, h - 2*radius, color);
|
||||
|
||||
// Draw rounded top and bottom caps
|
||||
for (int dy = 0; dy < radius; dy++) {
|
||||
int dx_top = isqrt(radius*radius - (radius - dy) * (radius - dy));
|
||||
int y_dist = radius - 1 - dy;
|
||||
int dx = isqrt(radius*radius - y_dist*y_dist);
|
||||
|
||||
int dx_bottom = isqrt(radius*radius - dy*dy);
|
||||
|
||||
draw_rect(x + radius - dx_top, y + dy, dx_top, 1, color);
|
||||
|
||||
draw_rect(x + w - radius, y + dy, dx_top, 1, color);
|
||||
|
||||
draw_rect(x + radius - dx_bottom, y + h - radius + dy, dx_bottom, 1, color);
|
||||
|
||||
draw_rect(x + w - radius, y + h - radius + dy, dx_bottom, 1, color);
|
||||
draw_rect(x + radius - dx, y + dy, w - 2*radius + 2*dx, 1, color);
|
||||
draw_rect(x + radius - dx, y + h - 1 - dy, w - 2*radius + 2*dx, 1, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,16 +416,23 @@ void draw_rounded_rect_blurred(int x, int y, int w, int h, int radius, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
for (int c = 0; c < w; c++) {
|
||||
for (int r = 0; r < h; r++) {
|
||||
int g_y = y + r;
|
||||
int g_x = x + c;
|
||||
if (g_y < 0 || g_y >= sh) continue;
|
||||
|
||||
if (g_clip_enabled) {
|
||||
if (g_x < g_clip_x || g_x >= g_clip_x + g_clip_w ||
|
||||
g_y < g_clip_y || g_y >= g_clip_y + g_clip_h) {
|
||||
continue;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
if (g_clip_enabled[cpu]) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
if (g_y < g_clip_stack_y[cpu][ptr] || g_y >= g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) continue;
|
||||
}
|
||||
|
||||
for (int c = 0; c < w; c++) {
|
||||
int g_x = x + c;
|
||||
if (g_x < 0 || g_x >= sw) continue;
|
||||
|
||||
if (g_clip_enabled[cpu]) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
if (g_x < g_clip_stack_x[cpu][ptr] || g_x >= g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr]) continue;
|
||||
}
|
||||
|
||||
bool in_corner = false;
|
||||
@@ -461,9 +490,12 @@ void draw_char(int x, int y, char c, uint32_t color) {
|
||||
unsigned char uc = (unsigned char)c;
|
||||
if (uc > 127) return;
|
||||
|
||||
if (g_clip_enabled && !g_render_target) {
|
||||
if (x + 8 <= g_clip_x || x >= g_clip_x + g_clip_w ||
|
||||
y + 8 <= g_clip_y || y >= g_clip_y + g_clip_h) {
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
bool has_rt = (cpu < MAX_RENDER_CPUS && g_render_target[cpu]);
|
||||
if (g_clip_enabled[cpu] && !has_rt) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
if (x + 8 <= g_clip_stack_x[cpu][ptr] || x >= g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr] ||
|
||||
y + 8 <= g_clip_stack_y[cpu][ptr] || y >= g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -479,14 +511,16 @@ void draw_char(int x, int y, char c, uint32_t color) {
|
||||
}
|
||||
}
|
||||
|
||||
// Bitmap-only version for terminal — always uses 8x8 bitmap font regardless of TTF
|
||||
void draw_char_bitmap(int x, int y, char c, uint32_t color) {
|
||||
unsigned char uc = (unsigned char)c;
|
||||
if (uc > 127) return;
|
||||
|
||||
if (g_clip_enabled && !g_render_target) {
|
||||
if (x + 8 <= g_clip_x || x >= g_clip_x + g_clip_w ||
|
||||
y + 8 <= g_clip_y || y >= g_clip_y + g_clip_h) {
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
bool has_rt = (cpu < MAX_RENDER_CPUS && g_render_target[cpu]);
|
||||
if (g_clip_enabled[cpu] && !has_rt) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
if (x + 8 <= g_clip_stack_x[cpu][ptr] || x >= g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr] ||
|
||||
y + 8 <= g_clip_stack_y[cpu][ptr] || y >= g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -557,7 +591,6 @@ void draw_string_scaled(int x, int y, const char *s, uint32_t color, float scale
|
||||
int cur_x = x;
|
||||
|
||||
if (g_current_ttf) {
|
||||
// We let the font manager handle the stbtt scale internally to avoid bringing stb_truetype into graphics.c
|
||||
int baseline = y + font_manager_get_font_ascent_scaled(g_current_ttf, scale) - 2;
|
||||
int line_height = font_manager_get_font_line_height_scaled(g_current_ttf, scale);
|
||||
|
||||
@@ -623,9 +656,11 @@ void draw_desktop_background(void) {
|
||||
if (g_use_image && g_bg_image) {
|
||||
// Draw wallpaper image (stretch/scale to screen)
|
||||
int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height;
|
||||
if (g_clip_enabled) {
|
||||
x1 = g_clip_x; y1 = g_clip_y;
|
||||
x2 = g_clip_x + g_clip_w; y2 = g_clip_y + g_clip_h;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
if (g_clip_enabled[cpu]) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
x1 = g_clip_stack_x[cpu][ptr]; y1 = g_clip_stack_y[cpu][ptr];
|
||||
x2 = g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr]; y2 = g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr];
|
||||
}
|
||||
for (int y = y1; y < y2; y++) {
|
||||
int src_y = y * g_bg_image_h / (int)g_fb->height;
|
||||
@@ -640,9 +675,11 @@ void draw_desktop_background(void) {
|
||||
} else if (g_use_pattern) {
|
||||
// Optimized tiled pattern: only draw within the clipping/dirty rect
|
||||
int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height;
|
||||
if (g_clip_enabled) {
|
||||
x1 = g_clip_x; y1 = g_clip_y;
|
||||
x2 = g_clip_x + g_clip_w; y2 = g_clip_y + g_clip_h;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
if (g_clip_enabled[cpu]) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
x1 = g_clip_stack_x[cpu][ptr]; y1 = g_clip_stack_y[cpu][ptr];
|
||||
x2 = g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr]; y2 = g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr];
|
||||
}
|
||||
|
||||
for (int y = y1; y < y2; y++) {
|
||||
@@ -758,10 +795,9 @@ void graphics_flip_buffer(void) {
|
||||
uint32_t *src_row = &g_back_buffer[curr_y * g_fb->width + x];
|
||||
|
||||
if (g_fb->bpp == 32) {
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
uint32_t *dst_row = (uint32_t *)((uint8_t *)g_fb->address + curr_y * g_fb->pitch) + x;
|
||||
for (int j = 0; j < w; j++) {
|
||||
dst_row[j] = src_row[j];
|
||||
}
|
||||
mem_memcpy(dst_row, src_row, w * 4);
|
||||
} else if (g_fb->bpp == 16) {
|
||||
uint16_t *dst_row = (uint16_t *)((uint8_t *)g_fb->address + curr_y * g_fb->pitch) + x;
|
||||
for (int j = 0; j < w; j++) {
|
||||
@@ -794,12 +830,6 @@ void graphics_flip_buffer(void) {
|
||||
|
||||
int gray = (r * 77 + g * 150 + b * 29) >> 8;
|
||||
|
||||
// Boost contrast by 2x to separate the dark UI colors:
|
||||
// Background (~30) -> 60
|
||||
// Panel (~40) -> 80
|
||||
// With thresholds {0, 64, 128, 192}:
|
||||
// BG > 0 (1/4 white), Panel > 64 (2/4 white - checkerboard)
|
||||
// Text (~170) -> 255 (solid white)
|
||||
gray = gray * 2;
|
||||
if (gray > 255) gray = 255;
|
||||
|
||||
@@ -824,19 +854,49 @@ void graphics_flip_buffer(void) {
|
||||
void graphics_copy_screenbuffer(uint32_t *dest) {
|
||||
if (!g_fb || !dest) return;
|
||||
|
||||
uint64_t rflags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
||||
int sw = g_fb->width;
|
||||
int sh = g_fb->height;
|
||||
extern uint64_t wm_lock_acquire(void);
|
||||
extern void wm_lock_release(uint64_t);
|
||||
uint64_t rflags = wm_lock_acquire();
|
||||
|
||||
// Copy the internal back object to the dest directly
|
||||
int sw = (int)g_fb->width;
|
||||
int sh = (int)g_fb->height;
|
||||
|
||||
// Copy from the composition back buffer, applying color mode transformations if necessary
|
||||
for (int y = 0; y < sh; y++) {
|
||||
uint32_t *src_row = &g_back_buffer[y * sw];
|
||||
for (int x = 0; x < sw; x++) {
|
||||
dest[y * sw + x] = src_row[x];
|
||||
uint32_t px = src_row[x];
|
||||
|
||||
if (g_color_mode == 1) { // 8-bit Grayscale
|
||||
uint8_t r = (px >> 16) & 0xFF;
|
||||
uint8_t g = (px >> 8) & 0xFF;
|
||||
uint8_t b = px & 0xFF;
|
||||
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
||||
dest[y * sw + x] = 0xFF000000 | (gray << 16) | (gray << 8) | gray;
|
||||
} else if (g_color_mode == 2) { // 1-bit Monochrome (Dithered)
|
||||
static const uint8_t bayer2[2][2] = {
|
||||
{ 0, 128 },
|
||||
{192, 64 }
|
||||
};
|
||||
uint8_t r = (px >> 16) & 0xFF;
|
||||
uint8_t g = (px >> 8) & 0xFF;
|
||||
uint8_t b = px & 0xFF;
|
||||
int gray = (r * 77 + g * 150 + b * 29) >> 8;
|
||||
|
||||
// Boost contrast (matches graphics_flip_buffer logic)
|
||||
gray = gray * 2;
|
||||
if (gray > 255) gray = 255;
|
||||
|
||||
uint8_t threshold = bayer2[y & 1][x & 1];
|
||||
dest[y * sw + x] = (gray > threshold) ? 0xFFFFFFFF : 0xFF000000;
|
||||
} else {
|
||||
// 32-bit (Standard)
|
||||
dest[y * sw + x] = px;
|
||||
}
|
||||
}
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
}
|
||||
|
||||
wm_lock_release(rflags);
|
||||
}
|
||||
|
||||
void graphics_set_clipping(int x, int y, int w, int h) {
|
||||
@@ -849,33 +909,94 @@ void graphics_set_clipping(int x, int y, int w, int h) {
|
||||
if (w < 0) w = 0;
|
||||
if (h < 0) h = 0;
|
||||
|
||||
g_clip_x = x;
|
||||
g_clip_y = y;
|
||||
g_clip_w = w;
|
||||
g_clip_h = h;
|
||||
g_clip_enabled = true;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
g_clip_stack_x[cpu][0] = x;
|
||||
g_clip_stack_y[cpu][0] = y;
|
||||
g_clip_stack_w[cpu][0] = w;
|
||||
g_clip_stack_h[cpu][0] = h;
|
||||
g_clip_stack_ptr[cpu] = 0; // Reset to base
|
||||
g_clip_enabled[cpu] = true;
|
||||
}
|
||||
|
||||
void graphics_push_clipping(int x, int y, int w, int h) {
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
int cur_ptr = g_clip_stack_ptr[cpu];
|
||||
if (cur_ptr + 1 >= CLIP_STACK_DEPTH) return; // Stack overflow
|
||||
|
||||
// Intersect with current top
|
||||
int cx1 = g_clip_stack_x[cpu][cur_ptr];
|
||||
int cy1 = g_clip_stack_y[cpu][cur_ptr];
|
||||
int cx2 = cx1 + g_clip_stack_w[cpu][cur_ptr];
|
||||
int cy2 = cy1 + g_clip_stack_h[cpu][cur_ptr];
|
||||
|
||||
int nx1 = x;
|
||||
int ny1 = y;
|
||||
int nx2 = x + w;
|
||||
int ny2 = y + h;
|
||||
|
||||
if (nx1 < cx1) nx1 = cx1;
|
||||
if (ny1 < cy1) ny1 = cy1;
|
||||
if (nx2 > cx2) nx2 = cx2;
|
||||
if (ny2 > cy2) ny2 = cy2;
|
||||
|
||||
int nw = nx2 - nx1;
|
||||
int nh = ny2 - ny1;
|
||||
if (nw < 0) nw = 0;
|
||||
if (nh < 0) nh = 0;
|
||||
|
||||
g_clip_stack_ptr[cpu]++;
|
||||
g_clip_stack_x[cpu][cur_ptr + 1] = nx1;
|
||||
g_clip_stack_y[cpu][cur_ptr + 1] = ny1;
|
||||
g_clip_stack_w[cpu][cur_ptr + 1] = nw;
|
||||
g_clip_stack_h[cpu][cur_ptr + 1] = nh;
|
||||
g_clip_enabled[cpu] = true;
|
||||
}
|
||||
|
||||
void graphics_pop_clipping(void) {
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
if (g_clip_stack_ptr[cpu] > 0) {
|
||||
g_clip_stack_ptr[cpu]--;
|
||||
} else {
|
||||
g_clip_enabled[cpu] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void graphics_clear_clipping(void) {
|
||||
g_clip_enabled = false;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
g_clip_stack_ptr[cpu] = 0;
|
||||
g_clip_enabled[cpu] = false;
|
||||
}
|
||||
void graphics_blit_buffer(uint32_t *src, int dst_x, int dst_y, int w, int h) {
|
||||
if (!g_fb || !src) return;
|
||||
int sw = get_screen_width();
|
||||
int sh = get_screen_height();
|
||||
|
||||
for (int y = 0; y < h; y++) {
|
||||
int vy = dst_y + y;
|
||||
if (vy < 0 || vy >= sh) continue;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
int cx1 = 0, cy1 = 0, cx2 = sw, cy2 = sh;
|
||||
if (g_clip_enabled[cpu]) {
|
||||
int ptr = g_clip_stack_ptr[cpu];
|
||||
cx1 = g_clip_stack_x[cpu][ptr];
|
||||
cy1 = g_clip_stack_y[cpu][ptr];
|
||||
cx2 = cx1 + g_clip_stack_w[cpu][ptr];
|
||||
cy2 = cy1 + g_clip_stack_h[cpu][ptr];
|
||||
}
|
||||
|
||||
for (int x = 0; x < w; x++) {
|
||||
int vx = dst_x + x;
|
||||
if (vx < 0 || vx >= sw) continue;
|
||||
int x1 = dst_x, y1 = dst_y, x2 = dst_x + w, y2 = dst_y + h;
|
||||
if (x1 < cx1) x1 = cx1;
|
||||
if (y1 < cy1) y1 = cy1;
|
||||
if (x2 > cx2) x2 = cx2;
|
||||
if (y2 > cy2) y2 = cy2;
|
||||
|
||||
uint32_t pcol = src[y * w + x];
|
||||
if (x1 >= x2 || y1 >= y2) return;
|
||||
|
||||
for (int y = y1; y < y2; y++) {
|
||||
uint32_t *dst_row = &g_back_buffer[y * sw + x1];
|
||||
uint32_t *src_row = &src[(y - dst_y) * w + (x1 - dst_x)];
|
||||
int len = x2 - x1;
|
||||
for (int x = 0; x < len; x++) {
|
||||
uint32_t pcol = src_row[x];
|
||||
if ((pcol & 0xFF000000) != 0 || (pcol & 0xFFFFFF) != 0) {
|
||||
g_back_buffer[vy * sw + vx] = pcol;
|
||||
dst_row[x] = pcol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ void graphics_clear_back_buffer(uint32_t color);
|
||||
|
||||
// Clipping
|
||||
void graphics_set_clipping(int x, int y, int w, int h);
|
||||
void graphics_push_clipping(int x, int y, int w, int h);
|
||||
void graphics_pop_clipping(void);
|
||||
void graphics_clear_clipping(void);
|
||||
|
||||
// Font access (requires font_manager.h for ttf_font_t)
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
#define GUI_CMD_GET_STRING_WIDTH 8
|
||||
#define GUI_CMD_GET_FONT_HEIGHT 9
|
||||
#define GUI_CMD_WINDOW_SET_RESIZABLE 14
|
||||
#define GUI_CMD_GET_SCREEN_SIZE 17
|
||||
#define GUI_CMD_GET_SCREENBUFFER 18
|
||||
#define GUI_CMD_SHOW_NOTIFICATION 19
|
||||
#define GUI_CMD_GET_DATETIME 20
|
||||
// Remapped Screenshot API Commands to avoid collisions (Originals 17, 18, 19 conflicted with magic numbers)
|
||||
#define GUI_CMD_GET_SCREEN_SIZE 50
|
||||
#define GUI_CMD_GET_SCREENBUFFER 51
|
||||
#define GUI_CMD_SHOW_NOTIFICATION 52
|
||||
#define GUI_CMD_GET_DATETIME 53
|
||||
|
||||
#define GUI_EVENT_NONE 0
|
||||
#define GUI_EVENT_PAINT 1
|
||||
|
||||
455
src/wm/libwidget.c
Normal file
455
src/wm/libwidget.c
Normal file
@@ -0,0 +1,455 @@
|
||||
#include "libwidget.h"
|
||||
#include <stddef.h>
|
||||
|
||||
#define COLOR_GRAY 0xFFC0C0C0
|
||||
#define COLOR_LTGRAY 0xFFDFDFDF
|
||||
#define COLOR_DKGRAY 0xFF808080
|
||||
#define COLOR_WHITE 0xFFFFFFFF
|
||||
#define COLOR_BLACK 0xFF000000
|
||||
|
||||
#define COLOR_SCROLLBAR_BG 0xFF2A2A2A
|
||||
#define COLOR_SCROLLBAR_THUMB 0xFF505050
|
||||
#define COLOR_SCROLLBAR_THUMB_HOVER 0xFF707070
|
||||
#define COLOR_SCROLLBAR_THUMB_DRAG 0xFF909090
|
||||
|
||||
static size_t string_len(const char *str) {
|
||||
size_t l = 0;
|
||||
while(str && str[l]) l++;
|
||||
return l;
|
||||
}
|
||||
|
||||
#define MAC_BTN_BORDER 0xFF4A4A4C
|
||||
#define MAC_BTN_BG_NORMAL 0xFF353537
|
||||
#define MAC_BTN_BG_HOVER 0xFF454547
|
||||
#define MAC_BTN_BG_PRESSED 0xFF555557
|
||||
|
||||
// --- Button Implementation ---
|
||||
|
||||
void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const char *text) {
|
||||
btn->x = x;
|
||||
btn->y = y;
|
||||
btn->w = w;
|
||||
btn->h = h;
|
||||
btn->text = text;
|
||||
btn->pressed = false;
|
||||
btn->hovered = false;
|
||||
btn->on_click = NULL;
|
||||
}
|
||||
|
||||
void widget_button_draw(widget_context_t *ctx, widget_button_t *btn) {
|
||||
uint32_t border_color = MAC_BTN_BORDER;
|
||||
uint32_t normal_bg = MAC_BTN_BG_NORMAL;
|
||||
uint32_t hover_bg = MAC_BTN_BG_HOVER;
|
||||
uint32_t pressed_bg = MAC_BTN_BG_PRESSED;
|
||||
uint32_t text_color = COLOR_WHITE;
|
||||
|
||||
if (ctx->use_light_theme) {
|
||||
border_color = 0xFFB0B0B0;
|
||||
normal_bg = 0xFFEAEAEA;
|
||||
hover_bg = 0xFFD0D0D0;
|
||||
pressed_bg = 0xFFB0B0B0;
|
||||
text_color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
uint32_t bg_color = normal_bg;
|
||||
if (btn->pressed) {
|
||||
bg_color = pressed_bg;
|
||||
} else if (btn->hovered) {
|
||||
bg_color = hover_bg;
|
||||
}
|
||||
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, btn->x, btn->y, btn->w, btn->h, 6, border_color);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, btn->x + 1, btn->y + 1, btn->w - 2, btn->h - 2, 5, bg_color);
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, btn->x, btn->y, btn->w, btn->h, border_color);
|
||||
ctx->draw_rect(ctx->user_data, btn->x + 1, btn->y + 1, btn->w - 2, btn->h - 2, bg_color);
|
||||
}
|
||||
|
||||
if (btn->text && ctx->draw_string) {
|
||||
int len = string_len(btn->text);
|
||||
int tx = btn->x + (btn->w - (len * 8)) / 2;
|
||||
int ty = btn->y + (btn->h - 8) / 2;
|
||||
ctx->draw_string(ctx->user_data, tx, ty, btn->text, text_color);
|
||||
}
|
||||
}
|
||||
|
||||
bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data) {
|
||||
bool in_bounds = (mx >= btn->x && mx < btn->x + btn->w && my >= btn->y && my < btn->y + btn->h);
|
||||
|
||||
btn->hovered = in_bounds;
|
||||
|
||||
if (mouse_clicked && in_bounds) {
|
||||
btn->pressed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mouse_down && btn->pressed) {
|
||||
btn->pressed = false;
|
||||
if (in_bounds && btn->on_click) {
|
||||
btn->on_click(user_data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_bounds;
|
||||
}
|
||||
|
||||
// --- Scrollbar Implementation ---
|
||||
|
||||
void widget_scrollbar_init(widget_scrollbar_t *sb, int x, int y, int w, int h) {
|
||||
sb->x = x;
|
||||
sb->y = y;
|
||||
sb->w = w;
|
||||
sb->h = h;
|
||||
sb->content_height = h;
|
||||
sb->scroll_y = 0;
|
||||
sb->is_dragging = false;
|
||||
sb->on_scroll = NULL;
|
||||
}
|
||||
|
||||
void widget_scrollbar_update(widget_scrollbar_t *sb, int content_height, int scroll_y) {
|
||||
sb->content_height = content_height;
|
||||
sb->scroll_y = scroll_y;
|
||||
}
|
||||
|
||||
void widget_scrollbar_draw(widget_context_t *ctx, widget_scrollbar_t *sb) {
|
||||
// Only draw scrollbar if content is larger than view
|
||||
if (sb->content_height > sb->h) {
|
||||
// Draw the track background
|
||||
uint32_t track_color = ctx->use_light_theme ? 0xFFE0E0E0 : 0xFF2A2A2A;
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, sb->x, sb->y, sb->w, sb->h, 4, track_color);
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, sb->x, sb->y, sb->w, sb->h, track_color);
|
||||
}
|
||||
|
||||
int thumb_h = (sb->h * sb->h) / sb->content_height;
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
|
||||
int max_scroll = sb->content_height - sb->h;
|
||||
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
|
||||
if (sb->scroll_y < 0) sb->scroll_y = 0;
|
||||
|
||||
int thumb_y = sb->y + (sb->scroll_y * (sb->h - thumb_h)) / max_scroll;
|
||||
|
||||
uint32_t color = 0xFF888888; // Subtle gray thumb for mac style
|
||||
if (sb->is_dragging) color = 0xFF666666;
|
||||
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
// Pill shaped thumb with margin
|
||||
int margin = 2;
|
||||
int radius = (sb->w - margin*2) / 2;
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, sb->x + margin, thumb_y + margin, sb->w - margin*2, thumb_h - margin*2, radius, color);
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, sb->x, thumb_y, sb->w, thumb_h, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data) {
|
||||
if (sb->content_height <= sb->h) return false;
|
||||
|
||||
int thumb_h = (sb->h * sb->h) / sb->content_height;
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
|
||||
int max_scroll = sb->content_height - sb->h;
|
||||
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
|
||||
if (sb->scroll_y < 0) sb->scroll_y = 0;
|
||||
|
||||
int thumb_y = sb->y + (sb->scroll_y * (sb->h - thumb_h)) / max_scroll;
|
||||
|
||||
bool in_thumb = (mx >= sb->x && mx < sb->x + sb->w && my >= thumb_y && my < thumb_y + thumb_h);
|
||||
bool in_track = (mx >= sb->x && mx < sb->x + sb->w && my >= sb->y && my < sb->y + sb->h);
|
||||
|
||||
if (mouse_down && !sb->is_dragging) {
|
||||
if (in_thumb) {
|
||||
sb->is_dragging = true;
|
||||
sb->drag_start_my = my;
|
||||
sb->drag_start_scroll_y = sb->scroll_y;
|
||||
return true;
|
||||
} else if (in_track) {
|
||||
// Page scroll
|
||||
if (my < thumb_y) {
|
||||
sb->scroll_y -= sb->h;
|
||||
} else {
|
||||
sb->scroll_y += sb->h;
|
||||
}
|
||||
if (sb->scroll_y < 0) sb->scroll_y = 0;
|
||||
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
|
||||
if (sb->on_scroll) sb->on_scroll(user_data, sb->scroll_y);
|
||||
return true;
|
||||
}
|
||||
} else if (!mouse_down) {
|
||||
sb->is_dragging = false;
|
||||
}
|
||||
|
||||
if (sb->is_dragging && mouse_down) {
|
||||
int dy = my - sb->drag_start_my;
|
||||
int track_h = sb->h - thumb_h;
|
||||
if (track_h > 0) {
|
||||
float ratio = (float)max_scroll / (float)track_h;
|
||||
int new_scroll = sb->drag_start_scroll_y + (int)(dy * ratio);
|
||||
|
||||
if (new_scroll < 0) new_scroll = 0;
|
||||
if (new_scroll > max_scroll) new_scroll = max_scroll;
|
||||
|
||||
if (new_scroll != sb->scroll_y) {
|
||||
sb->scroll_y = new_scroll;
|
||||
if (sb->on_scroll) sb->on_scroll(user_data, sb->scroll_y);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_track || sb->is_dragging;
|
||||
}
|
||||
|
||||
// --- TextBox Implementation ---
|
||||
void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len) {
|
||||
tb->x = x; tb->y = y; tb->w = w; tb->h = h;
|
||||
tb->text = buffer;
|
||||
tb->max_len = max_len;
|
||||
tb->cursor_pos = string_len(buffer);
|
||||
tb->focused = false;
|
||||
tb->on_change = NULL;
|
||||
}
|
||||
|
||||
void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
|
||||
uint32_t border_color = MAC_BTN_BORDER;
|
||||
uint32_t bg_color = COLOR_BLACK;
|
||||
uint32_t text_color = COLOR_WHITE;
|
||||
|
||||
if (ctx->use_light_theme) {
|
||||
border_color = 0xFFA0A0A0;
|
||||
bg_color = 0xFFFFFFFF;
|
||||
text_color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
// Background and border
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, tb->x, tb->y, tb->w, tb->h, 4, border_color);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, tb->x + 1, tb->y + 1, tb->w - 2, tb->h - 2, 3, bg_color); // background
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, tb->x, tb->y, tb->w, tb->h, border_color);
|
||||
ctx->draw_rect(ctx->user_data, tb->x + 1, tb->y + 1, tb->w - 2, tb->h - 2, bg_color);
|
||||
}
|
||||
|
||||
if (ctx->draw_string && tb->text) {
|
||||
int max_w = tb->w - 15;
|
||||
int scroll_x = 0;
|
||||
int text_w = 0;
|
||||
|
||||
if (ctx->measure_string_width) {
|
||||
text_w = ctx->measure_string_width(ctx->user_data, tb->text);
|
||||
} else {
|
||||
text_w = string_len(tb->text) * 8;
|
||||
}
|
||||
|
||||
if (text_w > max_w) scroll_x = text_w - max_w;
|
||||
|
||||
// Very basic simple drawing, without proper clipping since context lacks it
|
||||
ctx->draw_string(ctx->user_data, tb->x + 5, tb->y + (tb->h - 8) / 2, tb->text, text_color);
|
||||
|
||||
if (tb->focused && ctx->draw_rect) {
|
||||
int cx = 0;
|
||||
if (ctx->measure_string_width) {
|
||||
// measure up to cursor
|
||||
char tmp[256];
|
||||
int k = 0;
|
||||
for (k = 0; k < tb->cursor_pos && tb->text[k]; k++) {
|
||||
tmp[k] = tb->text[k];
|
||||
}
|
||||
tmp[k] = 0;
|
||||
cx = ctx->measure_string_width(ctx->user_data, tmp);
|
||||
} else {
|
||||
cx = tb->cursor_pos * 8;
|
||||
}
|
||||
|
||||
if (cx > max_w) cx = max_w; // clamped to visible end
|
||||
|
||||
ctx->draw_rect(ctx->user_data, tb->x + 5 + cx, tb->y + (tb->h - 12) / 2, 2, 12, text_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data) {
|
||||
bool in_bounds = (mx >= tb->x && mx < tb->x + tb->w && my >= tb->y && my < tb->y + tb->h);
|
||||
if (mouse_clicked) {
|
||||
tb->focused = in_bounds;
|
||||
if (in_bounds && tb->text) {
|
||||
int rel_x = mx - (tb->x + 5);
|
||||
if (rel_x < 0) rel_x = 0;
|
||||
// Rough estimation for fixed-width font 8px chars
|
||||
int new_pos = rel_x / 8;
|
||||
int len = string_len(tb->text);
|
||||
if (new_pos > len) new_pos = len;
|
||||
tb->cursor_pos = new_pos;
|
||||
}
|
||||
}
|
||||
return in_bounds;
|
||||
}
|
||||
|
||||
bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data) {
|
||||
if (!tb->focused || !tb->text) return false;
|
||||
|
||||
int len = string_len(tb->text);
|
||||
if (c == 19) { // LEFT
|
||||
if (tb->cursor_pos > 0) tb->cursor_pos--;
|
||||
return true;
|
||||
} else if (c == 20) { // RIGHT
|
||||
if (tb->cursor_pos < len) tb->cursor_pos++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c == '\b') { // backspace
|
||||
if (tb->cursor_pos > 0) {
|
||||
for (int i = tb->cursor_pos - 1; i < len; i++) {
|
||||
tb->text[i] = tb->text[i + 1];
|
||||
}
|
||||
tb->cursor_pos--;
|
||||
if (tb->on_change) tb->on_change(user_data);
|
||||
}
|
||||
} else if (c >= 32 && c < 127) {
|
||||
if (len < tb->max_len - 1) {
|
||||
for (int i = len; i >= tb->cursor_pos; i--) {
|
||||
tb->text[i + 1] = tb->text[i];
|
||||
}
|
||||
tb->text[tb->cursor_pos] = c;
|
||||
tb->cursor_pos++;
|
||||
if (tb->on_change) tb->on_change(user_data);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Dropdown Implementation ---
|
||||
void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, const char **items, int count) {
|
||||
dd->x = x; dd->y = y; dd->w = w; dd->h = h;
|
||||
dd->items = items;
|
||||
dd->item_count = count;
|
||||
dd->selected_idx = 0;
|
||||
dd->is_open = false;
|
||||
dd->on_select = NULL;
|
||||
}
|
||||
|
||||
void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd) {
|
||||
uint32_t border_color = MAC_BTN_BORDER;
|
||||
uint32_t bg_color = MAC_BTN_BG_NORMAL;
|
||||
uint32_t text_color = COLOR_WHITE;
|
||||
|
||||
if (ctx->use_light_theme) {
|
||||
border_color = 0xFFB0B0B0;
|
||||
bg_color = 0xFFE0E0E0;
|
||||
text_color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, dd->x, dd->y, dd->w, dd->h, 4, border_color);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, dd->x + 1, dd->y + 1, dd->w - 2, dd->h - 2, 3, bg_color);
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, dd->x, dd->y, dd->w, dd->h, border_color);
|
||||
ctx->draw_rect(ctx->user_data, dd->x + 1, dd->y + 1, dd->w - 2, dd->h - 2, bg_color);
|
||||
}
|
||||
|
||||
if (ctx->draw_string && dd->items && dd->item_count > 0 && dd->selected_idx >= 0 && dd->selected_idx < dd->item_count) {
|
||||
ctx->draw_string(ctx->user_data, dd->x + 5, dd->y + (dd->h - 8) / 2, dd->items[dd->selected_idx], text_color);
|
||||
ctx->draw_string(ctx->user_data, dd->x + dd->w - 12, dd->y + (dd->h - 8) / 2, "v", text_color);
|
||||
}
|
||||
|
||||
if (dd->is_open && ctx->draw_rect && dd->items) {
|
||||
int menu_h = dd->item_count * dd->h;
|
||||
ctx->draw_rect(ctx->user_data, dd->x, dd->y + dd->h, dd->w, menu_h, border_color);
|
||||
ctx->draw_rect(ctx->user_data, dd->x + 1, dd->y + dd->h + 1, dd->w - 2, menu_h - 2, bg_color);
|
||||
for (int i = 0; i < dd->item_count; i++) {
|
||||
if (ctx->draw_string) {
|
||||
ctx->draw_string(ctx->user_data, dd->x + 5, dd->y + dd->h + i * dd->h + (dd->h - 8)/2, dd->items[i], text_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool widget_dropdown_handle_mouse(widget_dropdown_t *dd, int mx, int my, bool mouse_clicked, void *user_data) {
|
||||
if (!mouse_clicked) return false;
|
||||
|
||||
if (dd->is_open) {
|
||||
int menu_h = dd->item_count * dd->h;
|
||||
if (mx >= dd->x && mx < dd->x + dd->w && my >= dd->y + dd->h && my < dd->y + dd->h + menu_h) {
|
||||
int clicked_idx = (my - (dd->y + dd->h)) / dd->h;
|
||||
if (clicked_idx >= 0 && clicked_idx < dd->item_count) {
|
||||
dd->selected_idx = clicked_idx;
|
||||
dd->is_open = false;
|
||||
if (dd->on_select) dd->on_select(user_data, clicked_idx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
dd->is_open = false;
|
||||
}
|
||||
|
||||
if (mx >= dd->x && mx < dd->x + dd->w && my >= dd->y && my < dd->y + dd->h) {
|
||||
dd->is_open = !dd->is_open;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Checkbox / Radio Implementation ---
|
||||
void widget_checkbox_init(widget_checkbox_t *cb, int x, int y, int w, int h, const char *text, bool is_radio) {
|
||||
cb->x = x; cb->y = y; cb->w = w; cb->h = h;
|
||||
cb->text = text;
|
||||
cb->checked = false;
|
||||
cb->is_radio = is_radio;
|
||||
cb->on_toggle = NULL;
|
||||
}
|
||||
|
||||
void widget_checkbox_draw(widget_context_t *ctx, widget_checkbox_t *cb) {
|
||||
int box_size = 14;
|
||||
int box_y = cb->y + (cb->h - box_size) / 2;
|
||||
|
||||
uint32_t border_color = MAC_BTN_BORDER;
|
||||
uint32_t bg_color = MAC_BTN_BG_NORMAL;
|
||||
uint32_t inner_color = COLOR_WHITE;
|
||||
uint32_t text_color = COLOR_WHITE;
|
||||
|
||||
if (ctx->use_light_theme) {
|
||||
border_color = 0xFF909090;
|
||||
bg_color = 0xFFFFFFFF;
|
||||
inner_color = 0xFF404040;
|
||||
text_color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
int radius = cb->is_radio ? box_size / 2 : 3;
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x, box_y, box_size, box_size, radius, border_color);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x + 1, box_y + 1, box_size - 2, box_size - 2, radius - 1, bg_color);
|
||||
|
||||
if (cb->checked) {
|
||||
int inner = box_size - 6;
|
||||
int inner_rad = cb->is_radio ? inner / 2 : 2;
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, inner_rad, inner_color);
|
||||
}
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, cb->x, box_y, box_size, box_size, border_color);
|
||||
ctx->draw_rect(ctx->user_data, cb->x + 1, box_y + 1, box_size - 2, box_size - 2, bg_color);
|
||||
if (cb->checked) {
|
||||
int inner = box_size - 6;
|
||||
ctx->draw_rect(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, inner_color);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->draw_string && cb->text) {
|
||||
ctx->draw_string(ctx->user_data, cb->x + box_size + 8, cb->y + (cb->h - 8) / 2, cb->text, text_color);
|
||||
}
|
||||
}
|
||||
|
||||
bool widget_checkbox_handle_mouse(widget_checkbox_t *cb, int mx, int my, bool mouse_clicked, void *user_data) {
|
||||
if (!mouse_clicked) return false;
|
||||
|
||||
if (mx >= cb->x && mx < cb->x + cb->w && my >= cb->y && my < cb->y + cb->h) {
|
||||
cb->checked = !cb->checked;
|
||||
if (cb->on_toggle) cb->on_toggle(user_data, cb->checked);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
93
src/wm/libwidget.h
Normal file
93
src/wm/libwidget.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef LIBWIDGET_H
|
||||
#define LIBWIDGET_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Widget Context for abstract drawing backend
|
||||
typedef struct {
|
||||
void *user_data;
|
||||
void (*draw_rect)(void *user_data, int x, int y, int w, int h, uint32_t color);
|
||||
void (*draw_rounded_rect_filled)(void *user_data, int x, int y, int w, int h, int r, uint32_t color);
|
||||
void (*draw_string)(void *user_data, int x, int y, const char *str, uint32_t color);
|
||||
int (*measure_string_width)(void *user_data, const char *str);
|
||||
void (*mark_dirty)(void *user_data, int x, int y, int w, int h);
|
||||
bool use_light_theme;
|
||||
} widget_context_t;
|
||||
|
||||
// --- Button ---
|
||||
typedef struct {
|
||||
int x, y, w, h;
|
||||
const char *text;
|
||||
bool pressed;
|
||||
bool hovered;
|
||||
void (*on_click)(void *user_data);
|
||||
} widget_button_t;
|
||||
|
||||
void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const char *text);
|
||||
void widget_button_draw(widget_context_t *ctx, widget_button_t *btn);
|
||||
// Returns true if event was consumed
|
||||
bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data);
|
||||
|
||||
|
||||
// --- Scrollbar ---
|
||||
typedef struct {
|
||||
int x, y, w, h;
|
||||
int content_height;
|
||||
int scroll_y;
|
||||
bool is_dragging;
|
||||
int drag_start_my;
|
||||
int drag_start_scroll_y;
|
||||
void (*on_scroll)(void *user_data, int new_scroll_y);
|
||||
} widget_scrollbar_t;
|
||||
|
||||
void widget_scrollbar_init(widget_scrollbar_t *sb, int x, int y, int w, int h);
|
||||
void widget_scrollbar_update(widget_scrollbar_t *sb, int content_height, int scroll_y);
|
||||
void widget_scrollbar_draw(widget_context_t *ctx, widget_scrollbar_t *sb);
|
||||
// Returns true if event was consumed
|
||||
bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data);
|
||||
|
||||
|
||||
// --- TextBox ---
|
||||
typedef struct {
|
||||
int x, y, w, h;
|
||||
char *text;
|
||||
int max_len;
|
||||
int cursor_pos;
|
||||
bool focused;
|
||||
void (*on_change)(void *user_data);
|
||||
} widget_textbox_t;
|
||||
|
||||
void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len);
|
||||
void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb);
|
||||
bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data);
|
||||
bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data);
|
||||
|
||||
// --- Dropdown ---
|
||||
typedef struct {
|
||||
int x, y, w, h;
|
||||
const char **items;
|
||||
int item_count;
|
||||
int selected_idx;
|
||||
bool is_open;
|
||||
void (*on_select)(void *user_data, int new_idx);
|
||||
} widget_dropdown_t;
|
||||
|
||||
void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, const char **items, int count);
|
||||
void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd);
|
||||
bool widget_dropdown_handle_mouse(widget_dropdown_t *dd, int mx, int my, bool mouse_clicked, void *user_data);
|
||||
|
||||
// --- Checkbox / Radio ---
|
||||
typedef struct {
|
||||
int x, y, w, h;
|
||||
const char *text;
|
||||
bool checked;
|
||||
bool is_radio;
|
||||
void (*on_toggle)(void *user_data, bool new_state);
|
||||
} widget_checkbox_t;
|
||||
|
||||
void widget_checkbox_init(widget_checkbox_t *cb, int x, int y, int w, int h, const char *text, bool is_radio);
|
||||
void widget_checkbox_draw(widget_context_t *ctx, widget_checkbox_t *cb);
|
||||
bool widget_checkbox_handle_mouse(widget_checkbox_t *cb, int mx, int my, bool mouse_clicked, void *user_data);
|
||||
|
||||
#endif
|
||||
460
src/wm/wm.c
460
src/wm/wm.c
@@ -17,6 +17,8 @@
|
||||
#include "userland/stb_image.h"
|
||||
#include "memory_manager.h"
|
||||
#include "disk.h"
|
||||
#include "../sys/work_queue.h"
|
||||
#include "../sys/smp.h"
|
||||
|
||||
|
||||
// Hello developer,
|
||||
@@ -81,7 +83,7 @@ void (*wm_custom_paint_hook)(void) = NULL;
|
||||
// Notification state
|
||||
static char notif_text[256] = {0};
|
||||
static int notif_timer = 0;
|
||||
static int notif_x_offset = 300; // Starts offscreen
|
||||
static int notif_x_offset = 420; // Starts offscreen
|
||||
static bool notif_active = false;
|
||||
extern bool ps2_ctrl_pressed;
|
||||
|
||||
@@ -98,6 +100,14 @@ static int drag_offset_y = 0;
|
||||
bool is_dragging_file = false;
|
||||
static char drag_file_path[FAT32_MAX_PATH];
|
||||
static int drag_icon_type = 0;
|
||||
|
||||
typedef struct {
|
||||
int y_start;
|
||||
int y_end;
|
||||
DirtyRect dirty;
|
||||
volatile int *completion_counter;
|
||||
int pass;
|
||||
} wm_strip_job_t;
|
||||
static int drag_start_x = 0;
|
||||
static int drag_start_y = 0;
|
||||
static int drag_icon_orig_x = 0;
|
||||
@@ -111,10 +121,8 @@ static int window_count = 0;
|
||||
// Redraw system
|
||||
static bool force_redraw = true;
|
||||
static uint32_t timer_ticks = 0;
|
||||
static int desktop_refresh_timer = 0;
|
||||
|
||||
// Cursor state
|
||||
static bool cursor_visible = true;
|
||||
static int last_cursor_x = 400;
|
||||
static int last_cursor_y = 300;
|
||||
|
||||
@@ -1110,27 +1118,6 @@ static void draw_dock_clock(int x, int y) {
|
||||
draw_rect(cx, cy - 1, 10, 2, 0xFF333333);
|
||||
}
|
||||
|
||||
static void draw_dock_editor(int x, int y) {
|
||||
draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFF0A1628);
|
||||
draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFF1565C0);
|
||||
draw_rounded_rect_filled(x + 1, y + 24, 46, 23, 9, 0xFF0D47A1);
|
||||
draw_rect(x + 5, y + 8, 9, 32, 0xFF1A237E);
|
||||
draw_filled_circle(x + 10, y + 14, 2, 0xFF7986CB);
|
||||
draw_filled_circle(x + 10, y + 22, 2, 0xFF7986CB);
|
||||
draw_filled_circle(x + 10, y + 30, 2, 0xFF7986CB);
|
||||
draw_rect(x + 15, y + 8, 28, 32, 0xFF1B2B3C);
|
||||
draw_rect(x + 15, y + 8, 14, 5, 0xFF1B2B3C);
|
||||
draw_rect(x + 15, y + 8, 14, 1, 0xFF569CD6);
|
||||
draw_rect(x + 18, y + 13, 9, 2, 0xFF569CD6);
|
||||
draw_rect(x + 29, y + 13, 8, 2, 0xFF4EC9B0);
|
||||
draw_rect(x + 18, y + 18, 5, 2, 0xFFCE9178);
|
||||
draw_rect(x + 25, y + 18, 7, 2, 0xFFCE9178);
|
||||
draw_rect(x + 21, y + 23, 7, 2, 0xFF9CDCFE);
|
||||
draw_rect(x + 30, y + 23, 5, 2, 0xFFD4D4D4);
|
||||
draw_rect(x + 18, y + 28, 16, 2, 0xFF6A9955);
|
||||
draw_rect(x + 18, y + 33, 10, 2, 0xFFD4D4D4);
|
||||
draw_rect(x + 30, y + 33, 6, 2, 0xFF569CD6);
|
||||
}
|
||||
|
||||
void draw_window(Window *win) {
|
||||
if (!win->visible) return;
|
||||
@@ -1240,6 +1227,7 @@ static void erase_cursor(int x, int y) {
|
||||
}
|
||||
|
||||
// --- Clock ---
|
||||
|
||||
static uint8_t rtc_read(uint8_t reg) {
|
||||
outb(0x70, reg);
|
||||
return inb(0x71);
|
||||
@@ -1274,60 +1262,43 @@ static void draw_clock(int x, int y) {
|
||||
}
|
||||
|
||||
// --- Main Paint Function ---
|
||||
void wm_paint(void) {
|
||||
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;
|
||||
}
|
||||
|
||||
static Window *sorted_windows_cache[32];
|
||||
static int sorted_window_count_cache = 0;
|
||||
|
||||
static void wm_paint_region(int y_start, int y_end, DirtyRect dirty, int pass) {
|
||||
int sw = get_screen_width();
|
||||
int sh = get_screen_height();
|
||||
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
|
||||
wm_mark_dirty(last_cursor_x, last_cursor_y, 12, 12);
|
||||
wm_mark_dirty(mx, my, 12, 12);
|
||||
|
||||
DirtyRect dirty = graphics_get_dirty_rect();
|
||||
|
||||
int cx = 0, cy = y_start, cw = sw, ch = y_end - y_start;
|
||||
if (dirty.active) {
|
||||
int d_h = 60;
|
||||
int d_y = sh - d_h - 6;
|
||||
int d_item_sz = 48;
|
||||
int d_space = 10;
|
||||
int d_tw = 10 * (d_item_sz + d_space);
|
||||
int d_bg_x = (sw - d_tw) / 2 - 12;
|
||||
int d_bg_w = d_tw + 24;
|
||||
|
||||
if (!(dirty.x >= d_bg_x + d_bg_w || dirty.x + dirty.w <= d_bg_x ||
|
||||
dirty.y >= d_y + d_h || dirty.y + dirty.h <= d_y)) {
|
||||
graphics_mark_dirty(d_bg_x - 10, d_y - 10, d_bg_w + 20, d_h + 20);
|
||||
dirty = graphics_get_dirty_rect();
|
||||
}
|
||||
graphics_set_clipping(dirty.x, dirty.y, dirty.w, dirty.h);
|
||||
} else {
|
||||
graphics_clear_clipping();
|
||||
if (cx < dirty.x) { cw -= (dirty.x - cx); cx = dirty.x; }
|
||||
if (cy < dirty.y) { ch -= (dirty.y - cy); cy = dirty.y; }
|
||||
if (cx + cw > dirty.x + dirty.w) cw = dirty.x + dirty.w - cx;
|
||||
if (cy + ch > dirty.y + dirty.h) ch = dirty.y + dirty.h - cy;
|
||||
}
|
||||
|
||||
// 1. Desktop Background (respects wallpaper color/pattern)
|
||||
if (cw <= 0 || ch <= 0) return;
|
||||
|
||||
graphics_set_clipping(cx, cy, cw, ch);
|
||||
|
||||
if (pass == 1) {
|
||||
draw_desktop_background();
|
||||
|
||||
// Draw Desktop Icons
|
||||
for (int i = 0; i < desktop_icon_count; i++) {
|
||||
DesktopIcon *icon = &desktop_icons[i];
|
||||
if (dirty.active) {
|
||||
if (icon->x + 80 <= dirty.x || icon->x >= dirty.x + dirty.w ||
|
||||
icon->y + 80 <= dirty.y || icon->y >= dirty.y + dirty.h) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (icon->y + 85 <= cy || icon->y >= cy + ch) continue;
|
||||
if (dirty.active && (icon->x + 85 <= dirty.x || icon->x >= dirty.x + dirty.w)) continue;
|
||||
|
||||
if (icon->type == 1) draw_folder_icon(icon->x, icon->y, icon->name);
|
||||
else if (icon->type == 2) {
|
||||
// App icon - strip .shortcut for display
|
||||
char label[64];
|
||||
int len = 0;
|
||||
char label[64]; int len = 0;
|
||||
while(icon->name[len] && len < 63) { label[len] = icon->name[len]; len++; }
|
||||
label[len] = 0;
|
||||
if (len > 9 && str_ends_with(label, ".shortcut")) {
|
||||
label[len-9] = 0;
|
||||
}
|
||||
|
||||
if (len > 9 && str_ends_with(label, ".shortcut")) label[len-9] = 0;
|
||||
if (str_starts_with(icon->name, "Notepad")) draw_notepad_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Calculator")) draw_calculator_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Terminal")) draw_terminal_icon(icon->x, icon->y, label);
|
||||
@@ -1343,8 +1314,7 @@ void wm_paint(void) {
|
||||
if (str_ends_with(icon->name, ".elf")) draw_elf_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 (is_image_file(icon->name)) {
|
||||
char full_path[128] = "/Desktop/";
|
||||
int p=9; int n=0; while(icon->name[n] && p < 127) full_path[p++] = icon->name[n++]; full_path[p]=0;
|
||||
char full_path[128] = "/Desktop/"; int p=9; int n=0; while(icon->name[n] && p < 127) full_path[p++] = icon->name[n++]; full_path[p]=0;
|
||||
draw_image_icon(icon->x, icon->y, full_path);
|
||||
draw_icon_label(icon->x, icon->y, icon->name);
|
||||
}
|
||||
@@ -1353,196 +1323,202 @@ void wm_paint(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Windows - sort by z-index and draw
|
||||
int local_window_count = window_count;
|
||||
Window *sorted_windows[32];
|
||||
for (int i = 0; i < local_window_count; i++) {
|
||||
sorted_windows[i] = all_windows[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < local_window_count - 1; i++) {
|
||||
for (int j = 0; j < local_window_count - i - 1; j++) {
|
||||
if (sorted_windows[j] && sorted_windows[j + 1] &&
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < local_window_count; i++) {
|
||||
Window *win = sorted_windows[i];
|
||||
for (int i = 0; i < sorted_window_count_cache; i++) {
|
||||
Window *win = sorted_windows_cache[i];
|
||||
if (!win || !win->visible) continue;
|
||||
|
||||
if (dirty.active && !win->focused) {
|
||||
if (win->x + win->w <= dirty.x || win->x >= dirty.x + dirty.w ||
|
||||
win->y + win->h <= dirty.y || win->y >= dirty.y + dirty.h) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (win->y + win->h <= cy || win->y >= cy + ch) continue;
|
||||
if (dirty.active && !win->focused && (win->x + win->w <= dirty.x || win->x >= dirty.x + dirty.w)) continue;
|
||||
draw_window(win);
|
||||
}
|
||||
|
||||
} else if (pass == 2) {
|
||||
if (0 < cy + ch && 30 > cy) {
|
||||
draw_rect(0, 0, sw, 30, COLOR_TOPBAR_BG);
|
||||
draw_boredos_logo(8, 8, 1);
|
||||
draw_clock(sw - 80, 12);
|
||||
}
|
||||
|
||||
if (start_menu_open) {
|
||||
int menu_h = 85;
|
||||
draw_rounded_rect_filled(8, 40, 160, menu_h, 8, COLOR_DARK_PANEL);
|
||||
if (start_menu_open && 40 < cy + ch && 125 > cy) {
|
||||
draw_rounded_rect_filled(8, 40, 160, 85, 8, COLOR_DARK_PANEL);
|
||||
draw_string(20, 48, "About BoredOS", COLOR_DARK_TEXT);
|
||||
draw_string(20, 68, "Settings", COLOR_DARK_TEXT);
|
||||
draw_string(20, 88, "Shutdown", COLOR_DARK_TEXT);
|
||||
draw_string(20, 108, "Restart", COLOR_DARK_TEXT);
|
||||
}
|
||||
|
||||
int dock_h = 60;
|
||||
int dock_y = sh - dock_h - 6;
|
||||
int dock_item_size = 48;
|
||||
int dock_spacing = 10;
|
||||
int total_dock_width = 11 * (dock_item_size + dock_spacing);
|
||||
int dock_bg_x = (sw - total_dock_width) / 2 - 12;
|
||||
int dock_bg_w = total_dock_width + 24;
|
||||
int dock_h = 60, dock_y = sh - dock_h - 6;
|
||||
if (dock_y < cy + ch && dock_y + dock_h > cy) {
|
||||
int d_item_sz = 48, d_space = 10, d_total_w = 11 * (d_item_sz + d_space);
|
||||
int d_bg_x = (sw - d_total_w) / 2 - 12, d_bg_w = d_total_w + 24;
|
||||
draw_rounded_rect_blurred(d_bg_x, dock_y, d_bg_w, dock_h, 18, COLOR_DOCK_BG, 5, 140);
|
||||
int dx = (sw - d_total_w) / 2, dy = dock_y + 6;
|
||||
draw_dock_files(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_settings(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_notepad(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_calculator(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_terminal(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_minesweeper(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_paint(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_browser(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_taskman(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_clock(dx, dy); dx += d_item_sz+d_space;
|
||||
draw_dock_word(dx, dy);
|
||||
}
|
||||
|
||||
// Draw blurred dock background with reduced radius and tint
|
||||
draw_rounded_rect_blurred(dock_bg_x, dock_y, dock_bg_w, dock_h, 18, COLOR_DOCK_BG, 5, 140);
|
||||
|
||||
int dock_x = (sw - total_dock_width) / 2;
|
||||
int dock_item_y = dock_y + 6;
|
||||
|
||||
draw_dock_files(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_settings(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_notepad(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_calculator(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_terminal(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_minesweeper(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_paint(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_browser(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_taskman(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_clock(dock_x, dock_item_y);
|
||||
dock_x += dock_item_size + dock_spacing;
|
||||
draw_dock_word(dock_x, dock_item_y);
|
||||
|
||||
// Desktop Context Menu (with rounded corners)
|
||||
if (desktop_menu_visible) {
|
||||
int menu_w = 140;
|
||||
int d_mw = 140, d_mh = (desktop_menu_target_icon != -1) ? 125 : 75;
|
||||
if (desktop_menu_y < cy + ch && desktop_menu_y + d_mh > cy) {
|
||||
draw_rounded_rect_filled(desktop_menu_x, desktop_menu_y, d_mw, d_mh, 8, COLOR_DARK_PANEL);
|
||||
int item_h = 25;
|
||||
int menu_h = (desktop_menu_target_icon != -1) ? 125 : 75;
|
||||
|
||||
draw_rounded_rect_filled(desktop_menu_x, desktop_menu_y, menu_w, menu_h, 8, COLOR_DARK_PANEL);
|
||||
|
||||
if (desktop_menu_target_icon != -1) {
|
||||
bool can_paste = explorer_clipboard_has_content();
|
||||
bool cp = explorer_clipboard_has_content();
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5, "Cut", COLOR_WHITE);
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5 + item_h, "Copy", COLOR_WHITE);
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5 + item_h * 2, "Paste", can_paste ? COLOR_WHITE : COLOR_DKGRAY);
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5 + item_h * 2, "Paste", cp ? COLOR_WHITE : COLOR_DKGRAY);
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5 + item_h * 3, "Delete", COLOR_TRAFFIC_RED);
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5 + item_h * 4, "Rename", COLOR_WHITE);
|
||||
} else {
|
||||
bool can_paste = explorer_clipboard_has_content();
|
||||
bool cp = explorer_clipboard_has_content();
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5, "New File", COLOR_WHITE);
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5 + item_h, "New Folder", COLOR_WHITE);
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5 + item_h * 2, "Paste", can_paste ? COLOR_WHITE : COLOR_DKGRAY);
|
||||
draw_string(desktop_menu_x + 10, desktop_menu_y + 5 + item_h * 2, "Paste", cp ? COLOR_WHITE : COLOR_DKGRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop Dialogs (dark mode)
|
||||
if (desktop_dialog_state != 0) {
|
||||
int dlg_w = 300; int dlg_h = 110;
|
||||
int dlg_x = (sw - dlg_w) / 2;
|
||||
int dlg_y = (sh - dlg_h) / 2;
|
||||
|
||||
draw_rounded_rect_filled(dlg_x, dlg_y, dlg_w, dlg_h, 8, COLOR_DARK_PANEL);
|
||||
|
||||
const char *title = "Rename";
|
||||
const char *btn_text = "Rename";
|
||||
if (desktop_dialog_state == 1) { title = "Create New File"; btn_text = "Create"; }
|
||||
else if (desktop_dialog_state == 2) { title = "Create New Folder"; btn_text = "Create"; }
|
||||
|
||||
draw_string(dlg_x + 10, dlg_y + 10, title, COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG);
|
||||
draw_string(dlg_x + 15, dlg_y + 40, desktop_dialog_input, COLOR_WHITE);
|
||||
// Cursor
|
||||
char sub[64];
|
||||
int k;
|
||||
for (k = 0; k < desktop_dialog_cursor && desktop_dialog_input[k]; k++) sub[k] = desktop_dialog_input[k];
|
||||
sub[k] = 0;
|
||||
int cx = font_manager_get_string_width(graphics_get_current_ttf(), sub);
|
||||
draw_rect(dlg_x + 15 + cx, dlg_y + 39, 2, 12, COLOR_WHITE);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 4, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 70, dlg_y + 72, btn_text, COLOR_WHITE);
|
||||
|
||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 4, COLOR_DARK_BORDER);
|
||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
||||
int dlg_w = 300, dlg_h = 110, dg_x = (sw - dlg_w)/2, dg_y = (sh - dlg_h)/2;
|
||||
if (dg_y < cy + ch && dg_y + dlg_h > cy) {
|
||||
draw_rounded_rect_filled(dg_x, dg_y, dlg_w, dlg_h, 8, COLOR_DARK_PANEL);
|
||||
const char *title = (desktop_dialog_state == 1) ? "Create New File" : (desktop_dialog_state == 2 ? "Create New Folder" : "Rename");
|
||||
const char *btn = (desktop_dialog_state == 0) ? "Rename" : "Create";
|
||||
draw_string(dg_x + 10, dg_y + 10, title, COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dg_x + 10, dg_y + 35, 280, 20, 4, COLOR_DARK_BG);
|
||||
draw_string(dg_x + 15, dg_y + 40, desktop_dialog_input, COLOR_WHITE);
|
||||
char temp_sub[64]; int k; for (k=0; k<desktop_dialog_cursor&&desktop_dialog_input[k]; k++) temp_sub[k]=desktop_dialog_input[k]; temp_sub[k]=0;
|
||||
draw_rect(dg_x + 15 + font_manager_get_string_width(graphics_get_current_ttf(), temp_sub), dg_y + 39, 2, 12, COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dg_x + 50, dg_y + 65, 80, 25, 4, COLOR_DARK_BORDER); draw_string(dg_x + 70, dg_y + 72, btn, COLOR_WHITE);
|
||||
draw_rounded_rect_filled(dg_x + 170, dg_y + 65, 80, 25, 4, COLOR_DARK_BORDER); draw_string(dg_x + 185, dg_y + 72, "Cancel", COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Message Box (dark mode)
|
||||
if (msg_box_visible) {
|
||||
int mw = 320;
|
||||
int mh = 100;
|
||||
int mx = (sw - mw) / 2;
|
||||
int my = (sh - mh) / 2;
|
||||
|
||||
draw_rounded_rect_filled(mx, my, mw, mh, 8, COLOR_DARK_PANEL);
|
||||
draw_string(mx + 15, my + 10, msg_box_title, COLOR_DARK_TEXT);
|
||||
draw_string(mx + 10, my + 40, msg_box_text, COLOR_DARK_TEXT);
|
||||
draw_rounded_rect_filled(mx + mw/2 - 30, my + 70, 60, 20, 4, COLOR_DARK_BORDER);
|
||||
int mw = 320, mh = 100, m_x = (sw - mw)/2, m_y = (sh - mh)/2;
|
||||
if (m_y < cy + ch && m_y + mh > cy) {
|
||||
draw_rounded_rect_filled(m_x, m_y, mw, mh, 8, COLOR_DARK_PANEL);
|
||||
draw_string(m_x + 15, m_y + 10, msg_box_title, COLOR_DARK_TEXT);
|
||||
draw_string(m_x + 10, m_y + 40, msg_box_text, COLOR_DARK_TEXT);
|
||||
draw_rounded_rect_filled(m_x + mw/2 - 30, m_y + 70, 60, 20, 4, COLOR_DARK_BORDER);
|
||||
draw_string(m_x + mw/2 - 10, m_y + 75, "OK", COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Notification (dark mode)
|
||||
if (notif_active) {
|
||||
int nx = sw - 280 + notif_x_offset;
|
||||
int ny = 40;
|
||||
int nw = 260;
|
||||
int nh = 50;
|
||||
|
||||
int nx = sw - 400 + notif_x_offset, ny = 40, nw = 380, nh = 50;
|
||||
if (ny < cy + ch && ny + nh > cy) {
|
||||
draw_rounded_rect_filled(nx, ny, nw, nh, 8, COLOR_DARK_PANEL);
|
||||
draw_string(nx + 15, ny + 10, "Screenshot", COLOR_DARK_TEXT);
|
||||
draw_string(nx + 15, ny + 30, notif_text, COLOR_DKGRAY);
|
||||
}
|
||||
|
||||
// Custom Overlay (VM Graphics)
|
||||
if (wm_custom_paint_hook) {
|
||||
wm_custom_paint_hook();
|
||||
}
|
||||
|
||||
// Draw Dragged Icon
|
||||
if (wm_custom_paint_hook) wm_custom_paint_hook();
|
||||
|
||||
if (is_dragging_file) {
|
||||
if (mx - 20 < cx + cw && mx + 20 > cx && my - 20 < cy + ch && my + 20 > cy) {
|
||||
if (drag_icon_type == 1) draw_folder_icon(mx - 20, my - 20, "Moving...");
|
||||
else if (drag_icon_type == 2) draw_icon(mx - 20, my - 20, "Moving...");
|
||||
else draw_document_icon(mx - 20, my - 20, "Moving...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Mouse cursor (draw last so it's on top)
|
||||
static void wm_strip_worker_job(void *arg) {
|
||||
wm_strip_job_t *job = (wm_strip_job_t *)arg;
|
||||
wm_paint_region(job->y_start, job->y_end, job->dirty, job->pass);
|
||||
__atomic_sub_fetch(job->completion_counter, 1, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
void wm_paint(void) {
|
||||
int sw = get_screen_width();
|
||||
int sh = get_screen_height();
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
wm_mark_dirty(last_cursor_x, last_cursor_y, 12, 12);
|
||||
wm_mark_dirty(mx, my, 12, 12);
|
||||
|
||||
DirtyRect dirty = graphics_get_dirty_rect();
|
||||
if (dirty.active) {
|
||||
int d_h = 60, d_y = sh - d_h - 6, d_total_w = 11 * (48 + 10);
|
||||
int d_bg_x = (sw - d_total_w) / 2 - 12, d_bg_w = d_total_w + 24;
|
||||
if (!(dirty.x >= d_bg_x + d_bg_w || dirty.x + dirty.w <= d_bg_x ||
|
||||
dirty.y >= d_y + d_h || dirty.y + dirty.h <= d_y)) {
|
||||
graphics_mark_dirty(d_bg_x - 10, d_y - 10, d_bg_w + 20, d_h + 20);
|
||||
dirty = graphics_get_dirty_rect();
|
||||
}
|
||||
}
|
||||
|
||||
sorted_window_count_cache = window_count;
|
||||
if (sorted_window_count_cache > 32) sorted_window_count_cache = 32;
|
||||
for (int i = 0; i < sorted_window_count_cache; i++) sorted_windows_cache[i] = all_windows[i];
|
||||
for (int i = 0; i < sorted_window_count_cache - 1; i++) {
|
||||
for (int j = 0; j < sorted_window_count_cache - i - 1; j++) {
|
||||
if (sorted_windows_cache[j] && sorted_windows_cache[j+1] &&
|
||||
sorted_windows_cache[j]->z_index > sorted_windows_cache[j+1]->z_index) {
|
||||
Window *tmp = sorted_windows_cache[j];
|
||||
sorted_windows_cache[j] = sorted_windows_cache[j+1];
|
||||
sorted_windows_cache[j+1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memory barrier to ensure APs see the sorted window list correctly
|
||||
asm volatile("" ::: "memory");
|
||||
|
||||
uint32_t cpu_count = smp_cpu_count();
|
||||
if (cpu_count > 32) cpu_count = 32;
|
||||
if (cpu_count < 1) cpu_count = 1;
|
||||
|
||||
volatile int completion_counter = (int)cpu_count;
|
||||
wm_strip_job_t jobs[32];
|
||||
int rows_per_strip = sh / cpu_count;
|
||||
|
||||
// PASS 1: BACKGROUND & WINDOWS
|
||||
for (uint32_t i = 0; i < cpu_count; i++) {
|
||||
jobs[i].y_start = i * rows_per_strip;
|
||||
jobs[i].y_end = (i == cpu_count - 1) ? sh : (i + 1) * rows_per_strip;
|
||||
jobs[i].dirty = dirty;
|
||||
jobs[i].completion_counter = &completion_counter;
|
||||
jobs[i].pass = 1;
|
||||
if (i < cpu_count - 1) work_queue_submit(wm_strip_worker_job, &jobs[i]);
|
||||
}
|
||||
wm_paint_region(jobs[cpu_count-1].y_start, jobs[cpu_count-1].y_end, dirty, 1);
|
||||
__atomic_sub_fetch(&completion_counter, 1, __ATOMIC_SEQ_CST);
|
||||
while (completion_counter > 0) {
|
||||
if (!work_queue_drain_one()) asm volatile("pause");
|
||||
}
|
||||
|
||||
// PASS 2: UI OVERLAY (Dock, start menu, menus etc)
|
||||
completion_counter = (int)cpu_count;
|
||||
for (uint32_t i = 0; i < cpu_count; i++) {
|
||||
jobs[i].pass = 2;
|
||||
if (i < cpu_count - 1) work_queue_submit(wm_strip_worker_job, &jobs[i]);
|
||||
}
|
||||
wm_paint_region(jobs[cpu_count-1].y_start, jobs[cpu_count-1].y_end, dirty, 2);
|
||||
__atomic_sub_fetch(&completion_counter, 1, __ATOMIC_SEQ_CST);
|
||||
while (completion_counter > 0) {
|
||||
if (!work_queue_drain_one()) asm volatile("pause");
|
||||
}
|
||||
|
||||
graphics_clear_clipping();
|
||||
draw_cursor(mx, my);
|
||||
last_cursor_x = mx;
|
||||
last_cursor_y = my;
|
||||
|
||||
// Flip the buffer - display the rendered frame atomically
|
||||
graphics_flip_buffer();
|
||||
graphics_clear_dirty_no_lock();
|
||||
|
||||
// Restore IRQs
|
||||
wm_lock_release(rflags);
|
||||
}
|
||||
|
||||
// --- 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_bring_to_front_locked(Window *win) {
|
||||
for (int i = 0; i < window_count; i++) {
|
||||
all_windows[i]->focused = false;
|
||||
@@ -1566,28 +1542,36 @@ void wm_bring_to_front(Window *win) {
|
||||
wm_lock_release(rflags);
|
||||
}
|
||||
|
||||
void wm_add_window(Window *win) {
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
void wm_add_window_locked(Window *win) {
|
||||
if (window_count < 32) {
|
||||
all_windows[window_count++] = win;
|
||||
wm_bring_to_front_locked(win); // Ensure newly added windows are on top
|
||||
}
|
||||
}
|
||||
|
||||
void wm_add_window(Window *win) {
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
wm_add_window_locked(win);
|
||||
wm_lock_release(rflags);
|
||||
}
|
||||
|
||||
Window* wm_find_window_by_title_locked(const char *title) {
|
||||
if (!title) return NULL;
|
||||
for (int i = 0; i < window_count; i++) {
|
||||
if (all_windows[i] && all_windows[i]->title && str_eq(all_windows[i]->title, title)) {
|
||||
return all_windows[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Window* wm_find_window_by_title(const char *title) {
|
||||
if (!title) return NULL;
|
||||
uint64_t rflags;
|
||||
rflags = wm_lock_acquire();
|
||||
for (int i = 0; i < window_count; i++) {
|
||||
if (all_windows[i] && all_windows[i]->title && str_eq(all_windows[i]->title, title)) {
|
||||
uint64_t rflags = wm_lock_acquire();
|
||||
Window *win = wm_find_window_by_title_locked(title);
|
||||
wm_lock_release(rflags);
|
||||
return all_windows[i];
|
||||
}
|
||||
}
|
||||
wm_lock_release(rflags);
|
||||
return NULL;
|
||||
return win;
|
||||
}
|
||||
|
||||
void wm_remove_window(Window *win) {
|
||||
@@ -1717,7 +1701,8 @@ void wm_handle_click(int x, int y) {
|
||||
if (desktop_snap_to_grid) {
|
||||
int col = (desktop_icons[new_idx].x - 20 + 40) / 80;
|
||||
int row = (desktop_icons[new_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[new_idx].x = 20 + col * 80;
|
||||
desktop_icons[new_idx].y = 20 + row * 80;
|
||||
}
|
||||
@@ -1811,7 +1796,7 @@ void wm_handle_click(int x, int y) {
|
||||
if (item == 0) { // About
|
||||
process_create_elf("/bin/about.elf", NULL);
|
||||
} else if (item == 1) { // Settings
|
||||
Window *existing = wm_find_window_by_title("Settings");
|
||||
Window *existing = wm_find_window_by_title_locked("Settings");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/settings.elf", NULL);
|
||||
} else if (item == 2) { // Shutdown
|
||||
@@ -1946,7 +1931,9 @@ void wm_handle_right_click(int x, int y) {
|
||||
}
|
||||
|
||||
force_redraw = true;
|
||||
}void wm_handle_mouse(int dx, int dy, uint8_t buttons, int dz) {
|
||||
}
|
||||
|
||||
static void wm_handle_mouse_internal(int dx, int dy, uint8_t buttons, int dz) {
|
||||
int sw = get_screen_width();
|
||||
int sh = get_screen_height();
|
||||
|
||||
@@ -2153,53 +2140,53 @@ void wm_handle_right_click(int x, int y) {
|
||||
if (str_starts_with(start_menu_pending_app, "Files")) {
|
||||
explorer_open_directory("/");
|
||||
} else if (str_starts_with(start_menu_pending_app, "Notepad")) {
|
||||
Window *existing = wm_find_window_by_title("Notepad");
|
||||
Window *existing = wm_find_window_by_title_locked("Notepad");
|
||||
if (existing) {
|
||||
wm_bring_to_front_locked(existing);
|
||||
} else {
|
||||
process_create_elf("/bin/notepad.elf", NULL);
|
||||
}
|
||||
} else if (str_starts_with(start_menu_pending_app, "Editor")) {
|
||||
Window *existing = wm_find_window_by_title("Txtedit");
|
||||
Window *existing = wm_find_window_by_title_locked("Txtedit");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/txtedit.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Word Processor")) {
|
||||
Window *existing = wm_find_window_by_title("Word Processor");
|
||||
Window *existing = wm_find_window_by_title_locked("Word Processor");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/boredword.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Terminal")) {
|
||||
cmd_reset(); wm_bring_to_front_locked(&win_cmd);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Calculator")) {
|
||||
Window *existing = wm_find_window_by_title("Calculator");
|
||||
Window *existing = wm_find_window_by_title_locked("Calculator");
|
||||
if (existing) {
|
||||
wm_bring_to_front_locked(existing);
|
||||
} else {
|
||||
process_create_elf("/bin/calculator.elf", NULL);
|
||||
}
|
||||
} else if (str_starts_with(start_menu_pending_app, "Minesweeper")) {
|
||||
Window *existing = wm_find_window_by_title("Minesweeper");
|
||||
Window *existing = wm_find_window_by_title_locked("Minesweeper");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/minesweeper.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Settings")) {
|
||||
Window *existing = wm_find_window_by_title("Settings");
|
||||
Window *existing = wm_find_window_by_title_locked("Settings");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/settings.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Paint")) {
|
||||
Window *existing = wm_find_window_by_title("Paint");
|
||||
Window *existing = wm_find_window_by_title_locked("Paint");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/paint.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Clock")) {
|
||||
Window *existing = wm_find_window_by_title("Clock");
|
||||
Window *existing = wm_find_window_by_title_locked("Clock");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/clock.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Browser")) {
|
||||
Window *existing = wm_find_window_by_title("Web Browser");
|
||||
Window *existing = wm_find_window_by_title_locked("Web Browser");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/browser.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "About")) {
|
||||
process_create_elf("/bin/about.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Task Manager")) {
|
||||
Window *existing = wm_find_window_by_title("Task Manager");
|
||||
Window *existing = wm_find_window_by_title_locked("Task Manager");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/taskman.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Shutdown")) {
|
||||
@@ -2404,7 +2391,8 @@ void wm_handle_right_click(int x, int y) {
|
||||
if (desktop_snap_to_grid) {
|
||||
int col = (desktop_icons[i].x - 20 + 40) / 80;
|
||||
int row = (desktop_icons[i].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[i].x = 20 + col * 80;
|
||||
desktop_icons[i].y = 20 + row * 80;
|
||||
}
|
||||
@@ -2575,6 +2563,12 @@ void wm_handle_right_click(int x, int y) {
|
||||
prev_left = left;
|
||||
}
|
||||
|
||||
void wm_handle_mouse(int dx, int dy, uint8_t buttons, int dz) {
|
||||
uint64_t rflags = wm_lock_acquire();
|
||||
wm_handle_mouse_internal(dx, dy, buttons, dz);
|
||||
wm_lock_release(rflags);
|
||||
}
|
||||
|
||||
// Input Queue
|
||||
#define INPUT_QUEUE_SIZE 128
|
||||
typedef struct {
|
||||
@@ -2663,7 +2657,7 @@ void wm_show_notification(const char *msg) {
|
||||
notif_text[i] = 0;
|
||||
|
||||
notif_timer = 180; // ~3 seconds at 60Hz
|
||||
notif_x_offset = 300;
|
||||
notif_x_offset = 420;
|
||||
notif_active = true;
|
||||
force_redraw = true;
|
||||
}
|
||||
@@ -2795,19 +2789,19 @@ void wm_timer_tick(void) {
|
||||
notif_timer--;
|
||||
// Slide in
|
||||
if (notif_timer > 165 && notif_x_offset > 0) { // First 15 ticks (1/4 sec) slide in
|
||||
notif_x_offset -= 20;
|
||||
notif_x_offset -= 28; // Slightly faster slide for larger distance
|
||||
if (notif_x_offset < 0) notif_x_offset = 0;
|
||||
}
|
||||
// Slide out
|
||||
else if (notif_timer < 15 && notif_x_offset < 300) { // Last 15 ticks slide out
|
||||
notif_x_offset += 20;
|
||||
else if (notif_timer < 15 && notif_x_offset < 420) { // Last 15 ticks slide out
|
||||
notif_x_offset += 28;
|
||||
}
|
||||
} else {
|
||||
notif_active = false;
|
||||
}
|
||||
|
||||
int sw = get_screen_width();
|
||||
wm_mark_dirty(sw - 280, 40, 275, 60);
|
||||
wm_mark_dirty(sw - 420, 40, 415, 60);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
15
src/wm/wm.h
15
src/wm/wm.h
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "../sys/spinlock.h"
|
||||
|
||||
uint64_t wm_lock_acquire(void);
|
||||
void wm_lock_release(uint64_t flags);
|
||||
@@ -18,14 +19,13 @@ void wm_lock_release(uint64_t flags);
|
||||
#define COLOR_BLUE 0xFF000080
|
||||
#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
|
||||
#define COLOR_APPLE_YELLOW 0xFFFFFF00
|
||||
#define COLOR_APPLE_GREEN 0xFF00FF00
|
||||
#define COLOR_APPLE_BLUE 0xFF0000FF
|
||||
#define COLOR_RED 0xFFFF0000
|
||||
#define COLOR_ORANGE 0xFFFF7F00
|
||||
#define COLOR_YELLOW 0xFFFFFF00
|
||||
#define COLOR_GREEN 0xFF00FF00
|
||||
#define COLOR_BLUE 0xFF0000FF
|
||||
#define COLOR_APPLE_INDIGO 0xFF4B0082
|
||||
#define COLOR_APPLE_VIOLET 0xFF9400D3
|
||||
|
||||
@@ -56,6 +56,7 @@ struct Window {
|
||||
uint32_t *pixels;
|
||||
uint32_t *comp_pixels;
|
||||
void *font;
|
||||
spinlock_t lock;
|
||||
|
||||
// Callbacks
|
||||
void (*paint)(Window *win);
|
||||
@@ -74,8 +75,10 @@ void wm_handle_click(int x, int y);
|
||||
void wm_handle_right_click(int x, int y);
|
||||
void wm_process_input(void);
|
||||
void wm_process_deferred_thumbs(void);
|
||||
void wm_add_window_locked(Window *win);
|
||||
void wm_add_window(Window *win);
|
||||
void wm_remove_window(Window *win);
|
||||
void wm_bring_to_front_locked(Window *win);
|
||||
void wm_bring_to_front(Window *win);
|
||||
Window* wm_find_window_by_title(const char *title);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user