mirror of
https://github.com/JannisHeydemann/BoredOS.git
synced 2026-05-30 10:26:59 +00:00
Compare commits
46 Commits
Full_V1.80
...
26.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b77e8c48e | ||
|
|
1ce08c70b0 | ||
|
|
fca67f68a9 | ||
|
|
c330382436 | ||
|
|
f0c2963793 | ||
|
|
3b24bc882c | ||
|
|
2b44e59e9f | ||
|
|
7a2769e8e3 | ||
|
|
1a6e30b52e | ||
|
|
69847adee6 | ||
|
|
f402e5e4f0 | ||
|
|
684ed774ee | ||
|
|
9ed8eac3e5 | ||
|
|
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 \
|
||||
|
||||
22
README.md
22
README.md
@@ -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]
|
||||
@@ -19,9 +19,9 @@ BoredOS is a functional x86_64 operating system featuring a custom Desktop Envir
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Features
|
||||
## Features
|
||||
|
||||
### ⚙️ System Architecture
|
||||
### System Architecture
|
||||
* **64-bit Long Mode:** Fully utilizing the x86_64 architecture.
|
||||
* **Symmetric Multi-Processing (SMP):** Full support for multi-core CPUs via Limine SMP.
|
||||
* **LAPIC & IPI Scheduling:** Advanced interrupt handling and inter-processor communication for task distribution.
|
||||
@@ -31,12 +31,12 @@ BoredOS is a functional x86_64 operating system featuring a custom Desktop Envir
|
||||
* **Filesystem:** Full **FAT32** support for persistent and in-memory storage.
|
||||
* **Networking:** Includes the lwIP networking stack.
|
||||
|
||||
### 📺 Graphical User Interface
|
||||
### Graphical User Interface
|
||||
* **BoredWM:** A custom Window Manager with drag-and-drop, mouse-centered interaction.
|
||||
* **Customization:** Adjustable UI to suit your aesthetic.
|
||||
* **Media Support:** Built-in image decoding.
|
||||
|
||||
### 🛠️ Included Applications
|
||||
### Included Applications
|
||||
* **Productivity:** GUI Text Editor calculator, Markdown Viewer, a simple browser and BoredWord.
|
||||
* **Creativity:** A Paint application.
|
||||
* **Utilities:** Terminal, Task Manager, File Explorer, Clock and a (limited) C Compiler.
|
||||
@@ -55,11 +55,11 @@ Explore the internal workings of BoredOS via our comprehensive guides in the [`d
|
||||
|
||||
---
|
||||
|
||||
## ☕ Support the Journey
|
||||
## Support the Journey
|
||||
|
||||
If you find this project interesting or helpful, consider fueling the development with a coffee!
|
||||
|
||||
<a href="https://buymeacoffee.com/boreddevnl" target="_blank">
|
||||
<a href="https://buymeacoffee.com/boreddevhq" target="_blank">
|
||||
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50" style="border-radius: 8px;" />
|
||||
</a>
|
||||
|
||||
@@ -68,20 +68,20 @@ If you find this project interesting or helpful, consider fueling the developmen
|
||||
|
||||
## ⚠️ Project Disclaimer & Heritage
|
||||
|
||||
**BoredOS** is the successor to **BrewKernel**, a text-only project initiated in 2023.
|
||||
**BoredOS** is the successor to **BrewKernel**, a project initiated in 2023.
|
||||
|
||||
While BrewKernel served as the foundational learning ground for this OS, it has been officially **deprecated and archived**. It no longer receives updates, bug fixes, or pull request reviews. BoredOS represents a complete architectural reboot, applying years of lessons learned to create a cleaner, more modular, and more capable 64-bit system.
|
||||
While BrewKernel served as the foundational learning ground for this OS, it has been officially **deprecated and archived**. It no longer receives updates, bug fixes, or pull request reviews. BoredOS represents a complete architectural reboot, applying years of lessons learned to create a cleaner, more modular, and more capable system.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please ensure all issues, discussions, and contributions are directed to this repository. Legacy BrewKernel code is preserved for historical purposes only and is not compatible with BoredOS.
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ License
|
||||
## License
|
||||
|
||||
**Copyright (C) 2024-2026 boreddevnl**
|
||||
|
||||
Distributed under the **GNU General Public License v3**. See the `LICENSE` file for details.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This product includes software developed by Chris ("boreddevnl"). You must retain all copyright headers and include the original attribution in any redistributions or derivative works. See the `NOTICE` file for more details.
|
||||
> This product includes software developed by Chris ("boreddevnl"). You must retain all copyright headers and include the original attribution in any redistributions or derivative works. See the `NOTICE` file for more details.
|
||||
|
||||
@@ -27,7 +27,9 @@ Instructions for compiling the OS from source.
|
||||
The SDK and toolchain guides for creating your own `.elf` userland binaries.
|
||||
- [`SDK Reference`](appdev/sdk_reference.md): Explanation of the custom `libc` wrappers (`stdlib.h`, `string.h`) and system calls.
|
||||
- [`UI API`](appdev/ui_api.md): Drawing on the screen, creating windows, and polling the event loop using `libui.h`.
|
||||
- [`Widget API`](appdev/widget_api.md): High-level UI components like buttons, textboxes, and scrollbars using `libwidget.h`.
|
||||
- [`Custom Apps`](appdev/custom_apps.md): A step-by-step tutorial on writing a new graphical C application, editing the Makefile, and bundling it into the ISO.
|
||||
- [`Example Apps`](appdev/examples/README.md): A collection of sample C applications ranging from basic terminal output to advanced TCP networking.
|
||||
- [`Grapher`](appdev/grapher.md): Full reference for the built-in mathematical graphing application — equation syntax, keyboard controls, architecture, and configuration.
|
||||
|
||||
---
|
||||
|
||||
@@ -10,7 +10,7 @@ This guide explains how to write a new "Hello World" application locally, compil
|
||||
> [!TIP]
|
||||
> **Looking for working code?** Check out the [Examples Directory](examples/README.md) for full source code demonstrating basic CLI, Windows, Animations, and TCP Networking.
|
||||
|
||||
## 📝 Step 1: Write the C Source
|
||||
## Step 1: Write the C Source
|
||||
|
||||
Applications reside entirely in the `src/userland/` directory. Create a new file, for example, `src/userland/gui/hello.c`.
|
||||
|
||||
@@ -52,7 +52,7 @@ int main(void) {
|
||||
}
|
||||
```
|
||||
|
||||
## ⚙️ Step 2: Edit the Makefile
|
||||
## Step 2: Edit the Makefile
|
||||
|
||||
Now you need to tell the build system to compile `hello.c`. Fortunately, the `src/userland/Makefile` is designed to detect new C files largely automatically!
|
||||
|
||||
@@ -64,7 +64,7 @@ Now you need to tell the build system to compile `hello.c`. Fortunately, the `sr
|
||||
Since you placed the file in `gui/hello.c`, the wildcard logic will pick it up automatically.
|
||||
3. The Makefile will generate `bin/hello.elf` during the build phase.
|
||||
|
||||
## 📦 Step 3: Bundle it into the OS
|
||||
## Step 3: Bundle it into the OS
|
||||
|
||||
The main overarching `Makefile` (in the project root) takes binaries from `src/userland/bin/*.elf` and places them into the `iso_root/bin/` directory, while also adding them to `limine.conf` as loadable boot modules.
|
||||
|
||||
@@ -77,7 +77,7 @@ The main overarching `Makefile` (in the project root) takes binaries from `src/u
|
||||
make clean && make run
|
||||
```
|
||||
|
||||
## 🚀 Step 4: Run it inside BoredOS
|
||||
## Step 4: Run it inside BoredOS
|
||||
|
||||
1. When BoredOS boots, launch the **Terminal** application.
|
||||
2. The OS automatically maps built applications to standard shell commands. Simply type your application's filename (without the `.elf` extension).
|
||||
|
||||
345
docs/appdev/grapher.md
Normal file
345
docs/appdev/grapher.md
Normal file
@@ -0,0 +1,345 @@
|
||||
<div align="center">
|
||||
<h1>Grapher</h1>
|
||||
<p><em>An interactive mathematical expression plotter for BoredOS, supporting both 2D and 3D visualizations.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Grapher is a built-in GUI application that lets you type any mathematical equation and see it plotted in real time. It supports 2D explicit and implicit curves as well as full 3D surface visualization — including both explicit surfaces (`z = f(x, y)`) and implicit surfaces (`f(x, y, z) = c`).
|
||||
|
||||
> [!NOTE]
|
||||
> Grapher is located at `src/userland/gui/grapher.c`. It runs as a standard BoredOS GUI process and can be launched from the terminal or from the dock.
|
||||
|
||||
---
|
||||
|
||||
## Features at a Glance
|
||||
|
||||
| Feature | Details |
|
||||
|---|---|
|
||||
| **2D Explicit** | Plot `y = f(x)` curves |
|
||||
| **2D Implicit** | Plot any `f(x, y) = g(x, y)` contour via marching squares |
|
||||
| **3D Explicit** | Plot `z = f(x, y)` surfaces |
|
||||
| **3D Implicit** | Plot any `f(x, y, z) = c` surface |
|
||||
| **Rendering modes** | Wireframe and filled polygon modes |
|
||||
| **Height coloring** | Surfaces are colored by a blue→green→yellow→red gradient based on Z height |
|
||||
| **Phong-style shading** | Filled mode computes per-face normals and applies diffuse + ambient lighting |
|
||||
| **Parallel rendering** | Evaluation and projection are distributed across 4 worker threads via `sys_parallel_run` |
|
||||
| **Preset equations** | 7 built-in presets accessible from the toolbar |
|
||||
| **Auto-fit** | 2D view auto-fits the Y axis to the plotted curve on first plot |
|
||||
| **Atomic Color-Depth Buffer** | All 3D drawing uses a 64-bit atomic buffer to prevent depth/color race conditions |
|
||||
|
||||
---
|
||||
|
||||
## Launching Grapher
|
||||
|
||||
From the BoredOS terminal:
|
||||
```sh
|
||||
grapher
|
||||
```
|
||||
|
||||
Or click the **Grapher icon** in the system dock.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
### Toolbar Controls
|
||||
|
||||
| Control | Function |
|
||||
|---|---|
|
||||
| **Equation box** | Type your mathematical expression, then press **Enter** or **Plot** |
|
||||
| **Plot button** | Parse and render the current equation |
|
||||
| **Wire / Filled button** | Toggle wireframe vs. shaded polygon mode (3D only) |
|
||||
| **Presets button** | Open a dropdown of example equations |
|
||||
|
||||
### Status Bar Controls (3D mode)
|
||||
|
||||
| Control | Function |
|
||||
|---|---|
|
||||
| **`+` button** | Increase the 3D world range (zoom out in world space) |
|
||||
| **`-` button** | Decrease the 3D world range (zoom in in world space) |
|
||||
|
||||
---
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Action |
|
||||
|---|---|
|
||||
| **Enter** (in equation box) | Plot the equation |
|
||||
| **Ctrl + R** | Reset the view to defaults |
|
||||
| **F** | Toggle filled / wireframe rendering (3D mode) |
|
||||
| **Scroll wheel** | Zoom in/out (2D mode adjusts viewport; 3D mode adjusts camera zoom) |
|
||||
| **Right-click drag** | Rotate the 3D surface |
|
||||
|
||||
---
|
||||
|
||||
## Writing Equations
|
||||
|
||||
Grapher parses equations entered as plain text. It supports a subset of mathematical notation with automatic implicit multiplication.
|
||||
|
||||
### Supported Functions
|
||||
|
||||
| Syntax | Meaning |
|
||||
|---|---|
|
||||
| `sin(x)` | Sine |
|
||||
| `cos(x)` | Cosine |
|
||||
| `tan(x)` | Tangent |
|
||||
| `sqrt(x)` | Square root |
|
||||
| `abs(x)` | Absolute value |
|
||||
| `log(x)` | Natural logarithm (base *e*) |
|
||||
|
||||
### Supported Operators
|
||||
|
||||
| Operator | Meaning |
|
||||
|---|---|
|
||||
| `+` `-` `*` `/` | Arithmetic |
|
||||
| `^` | Exponentiation (right-associative) |
|
||||
| `(` `)` | Grouping |
|
||||
|
||||
### Special Values
|
||||
|
||||
| Token | Value |
|
||||
|---|---|
|
||||
| `pi` or `PI` | π ≈ 3.14159… |
|
||||
|
||||
### Implicit Multiplication
|
||||
|
||||
Adjacent tokens that would normally require a `*` are multiplied automatically:
|
||||
|
||||
```
|
||||
2x → 2 * x
|
||||
3sin(x) → 3 * sin(x)
|
||||
(x+1)(x) → (x+1) * x
|
||||
```
|
||||
|
||||
### How Equations Are Classified
|
||||
|
||||
Grapher looks at which variables appear in your equation to automatically choose the rendering mode:
|
||||
|
||||
| Equation form | Auto-detected as |
|
||||
|---|---|
|
||||
| `y = f(x)` or just `f(x)` | 2D explicit |
|
||||
| `f(x, y) = g(x, y)` | 2D implicit |
|
||||
| `z = f(x, y)` | 3D explicit |
|
||||
| `f(x, y, z) = c` | 3D implicit |
|
||||
|
||||
If you omit the `=` sign, Grapher treats the input as `y = <expression>` when no `y` or `z` is present, or as `<expression> = 0` otherwise.
|
||||
|
||||
---
|
||||
|
||||
## Example Equations
|
||||
|
||||
### 2D Examples
|
||||
|
||||
```
|
||||
y = sin(x)
|
||||
y = x^2
|
||||
y = cos(x)*x
|
||||
y = abs(x) - 2
|
||||
x^2 + y^2 = 25 ← circle (implicit)
|
||||
y = log(x)
|
||||
```
|
||||
|
||||
### 3D Explicit Examples
|
||||
|
||||
```
|
||||
z = sin(x)*cos(y)
|
||||
z = x^2 - y^2 ← saddle surface
|
||||
z = sqrt(25 - x^2 - y^2)
|
||||
```
|
||||
|
||||
### 3D Implicit Examples
|
||||
|
||||
```
|
||||
x^2 + y^2 + z^2 = 25 ← sphere
|
||||
x^2 + y^2 = 16 ← cylinder
|
||||
x^2 + y^2 - z^2 = 1 ← hyperboloid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Navigation Controls
|
||||
|
||||
### 2D Mode
|
||||
|
||||
| Input | Action |
|
||||
|---|---|
|
||||
| **Scroll up** | Zoom in |
|
||||
| **Scroll down** | Zoom out |
|
||||
| **Ctrl+R** | Reset to default view (`x: [-10, 10]`) |
|
||||
|
||||
### 3D Mode
|
||||
|
||||
| Input | Action |
|
||||
|---|---|
|
||||
| **Right-click drag** | Rotate the surface (orbit camera) |
|
||||
| **Scroll up** | Zoom camera in |
|
||||
| **Scroll down** | Zoom camera out |
|
||||
| **`+` / `-` buttons** | Increase / decrease world range |
|
||||
| **Ctrl+R** | Reset rotation and zoom |
|
||||
|
||||
> [!TIP]
|
||||
> In 3D mode, the surface auto-rotates slowly by default. This can be disabled by setting `#define ROTATE 0` in the source file.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Grapher is implemented as a single self-contained C file. Below is a high-level breakdown of its major components:
|
||||
|
||||
### Math Library
|
||||
|
||||
Grapher uses the BoredOS freestanding **`libc/math.h`** library, which provides all the math functions it needs without depending on a host standard library:
|
||||
|
||||
| Function | Description |
|
||||
|---|---|
|
||||
| `sin`, `cos`, `tan` | Trigonometry via Taylor series (8 terms, range-reduced to `[-π, π]`) |
|
||||
| `sqrt` | Newton-Raphson iteration (25 steps) |
|
||||
| `log` | Natural logarithm via Padé-style series |
|
||||
| `log2`, `log10` | Derived from `log` |
|
||||
| `exp` | Range-reduced Taylor series for `e^x` |
|
||||
| `pow` | Integer exponents use fast binary exponentiation; fractional exponents use `exp(e * log(b))` |
|
||||
| `fabs`, `fmod` | Absolute value and floating-point remainder |
|
||||
| `floor`, `ceil` | Rounding |
|
||||
| `sinh`, `cosh`, `tanh` | Hyperbolic functions |
|
||||
| `hypot`, `fmin`, `fmax`, `fclamp` | Utility helpers |
|
||||
|
||||
The constants `M_PI`, `M_E`, `M_LN2`, `M_SQRT2` are also defined in the header.
|
||||
|
||||
This library is automatically linked into every userland ELF — any app can `#include "math.h"` to use it.
|
||||
|
||||
### Expression Parser
|
||||
|
||||
Equations are parsed in three stages:
|
||||
|
||||
1. **Tokenizer** (`tokenize`) — converts the input string into a flat token array. Handles implicit multiplication by inserting `*` tokens where needed.
|
||||
2. **Recursive Descent Parser** (`parse_expr`, `parse_term`, `parse_power`, `parse_unary`, `parse_atom`) — produces an Abstract Syntax Tree (AST) with up to `MAX_NODES = 128` nodes.
|
||||
3. **Bytecode Compiler** (`compile_ast`) — walks the AST in post-order and emits a flat instruction sequence for a simple stack machine. This avoids recursive evaluation during rendering hot paths.
|
||||
|
||||
The resulting bytecode is then executed by `run_bc` for every sample point.
|
||||
|
||||
### Rendering Pipeline
|
||||
|
||||
#### 2D Rendering
|
||||
|
||||
- **Explicit** — evaluates `y = f(x)` at every pixel column and connects adjacent samples with Bresenham lines.
|
||||
- **Implicit** — applies **marching squares** on a 200×130 grid to find sign changes in `f(x,y) - g(x,y)` and plots intersection pixels.
|
||||
|
||||
#### 3D Rendering
|
||||
|
||||
The 3D pipeline uses a multi-pass system parallelized across worker threads:
|
||||
|
||||
| Pass | Function | Description |
|
||||
|---|---|---|
|
||||
| 1 | **Evaluation** | Samples the surface at grid points. For implicit surfaces, this uses **tri-axis marching**. |
|
||||
| 2 | **Projection** | Projects 3D world coordinates to 2D screen coordinates with perspective. |
|
||||
| 3 | **Drawing** | Rasterizes wireframe lines or filled triangles with Z-buffering. |
|
||||
|
||||
##### Tri-Axis Marching (Implicit Surfaces)
|
||||
|
||||
Unlike explicit surfaces that only need one evaluation per grid point, implicit surfaces require finding roots of $f(x, y, z) = 0$. To ensure complete surface connectivity and eliminate "cracks," Grapher marches along all three primary axes:
|
||||
|
||||
1. **X-Axis Pass**: For every $(y, z)$ pair, march along $x$.
|
||||
2. **Y-Axis Pass**: For every $(x, z)$ pair, march along $y$.
|
||||
3. **Z-Axis Pass**: For every $(x, y)$ pair, march along $z$.
|
||||
|
||||
Each pass uses a multi-stage root finder (170 linear steps followed by 15 bisection iterations). By sampling along all three axes, the engine "catches" surfaces that are nearly parallel to any specific marching direction, ensuring that vertical walls and steep gradients are rendered solidly from any viewing angle.
|
||||
|
||||
##### Atomic Color-Depth Buffer
|
||||
|
||||
To prevent "z-fighting" and race conditions between parallel threads, Grapher uses a 64-bit atomic buffer (`graph_czb`). Each 64-bit word stores:
|
||||
- **Upper 32 bits**: Z-depth (integer).
|
||||
- **Lower 32 bits**: Pixel color (0xAARRGGBB).
|
||||
|
||||
A single `__atomic_compare_exchange_n` operation ensures that a pixel's color and depth are updated together only if the new depth is closer to the camera than the existing one.
|
||||
|
||||
Surface normals are estimated using central finite differences of the implicit function.
|
||||
|
||||
#### Filled Mod
|
||||
|
||||
When filled mode is active, each quad cell is split into two triangles. The average surface normal across the four corner vertices is computed and fed into `apply_shading`, which calculates:
|
||||
|
||||
```
|
||||
intensity = ambient(0.3) + diffuse(0.7) * dot(normal, light_direction)
|
||||
```
|
||||
|
||||
The light direction is fixed at `(0.577, 0.707, 0.408)` (normalized diagonal).
|
||||
|
||||
#### Z-Buffer
|
||||
|
||||
The depth buffer (`graph_zb`) stores integer depth values. `gfb_pixel_z` uses a **compare-and-swap (CAS) loop** via `__atomic_compare_exchange_n` so multiple parallel draw threads cannot produce race conditions.
|
||||
|
||||
### Coordinate Systems
|
||||
|
||||
#### 2D
|
||||
|
||||
World coordinates map linearly to screen pixels:
|
||||
|
||||
```c
|
||||
screen_x = (wx - view_x_min) / (view_x_max - view_x_min) * graph_w
|
||||
screen_y = (view_y_max - wy) / (view_y_max - view_y_min) * graph_h
|
||||
```
|
||||
|
||||
#### 3D
|
||||
|
||||
Points are first rotated by two Euler angles (`rot_y`, `rot_x`) then projected with a simple perspective divide:
|
||||
|
||||
```
|
||||
persp = d / (pz + d) // d = range_3d * 5
|
||||
sx = px * scale * persp + screen_cx
|
||||
sy = -py * scale * persp + screen_cy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Constants
|
||||
|
||||
These can be changed at the top of `grapher.c` to tune behaviour:
|
||||
|
||||
| Constant | Default | Effect |
|
||||
|---|---|---|
|
||||
| `ROTATE` | `1` | Set to `0` to disable auto-rotation in 3D mode |
|
||||
| `GRID_3D` | `41` | Grid resolution for 3D sampling. Higher = more detail, much slower |
|
||||
|
||||
> [!WARNING]
|
||||
> Setting `GRID_3D` too high (e.g. 9000) will exhaust available memory. The `surf` grid and `surf_x`/`surf_y_3d` arrays are statically allocated at compile time: memory usage grows as **O(GRID_3D²)**. Values above ~512 are not recommended.
|
||||
|
||||
> [!TIP]
|
||||
> `GRID_3D = 256` gives a good balance of detail and performance on typical BoredOS hardware emulation.
|
||||
|
||||
---
|
||||
|
||||
## Color Palette
|
||||
|
||||
|
||||
3D surfaces are colored by height using a 4-stop rainbow ramp:
|
||||
|
||||
```
|
||||
Low → Blue → Cyan → Green → Yellow → Red → High
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preset Equations
|
||||
|
||||
The built-in presets are shown in the dropdown when you click **Presets**:
|
||||
|
||||
| Label | Type |
|
||||
|---|---|
|
||||
| `y = sin(x)` | 2D explicit |
|
||||
| `y = x^2` | 2D explicit |
|
||||
| `y = cos(x)*x` | 2D explicit |
|
||||
| `z = sin(x)*cos(y)` | 3D explicit |
|
||||
| `z = x^2 - y^2` | 3D explicit |
|
||||
| `x^2+y^2+z^2=25` | 3D implicit (sphere) |
|
||||
| `x^2+y^2=16` | 3D implicit (cylinder) |
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- **No parameter slider** — equations are static; there is no way to animate a parameter.
|
||||
- **No multiple equations** — only one equation can be graphed at a time.
|
||||
- **Implicit surface precision** — extremely thin or high-frequency implicit surfaces may still have small artifacts if the grid resolution (`GRID_3D`) is too low.
|
||||
- **3D implicit performance** — tri-axis marching evaluates the function significantly more times than explicit rendering; high resolutions will impact frame rate.
|
||||
- **Integer axis labels only for large values** — very large axis values are capped at `>2G` or `<-2G` due to `itoa` limitations.
|
||||
@@ -7,9 +7,13 @@
|
||||
|
||||
BoredOS provides a custom `libc` implementation necessary for writing userland applications (`.elf` binaries). By avoiding a full-blown standard library like `glibc`, the OS ensures a minimal executable footprint tailored strictly to the existing kernel features.
|
||||
|
||||
All headers are located in `src/userland/libc/`.
|
||||
All headers are located in `src/userland/libc/` (standard functions) and `src/wm/` (UI and widgets).
|
||||
- `stdlib.h`: Memory, strings, and basic I/O.
|
||||
- `math.h`: Freestanding floating-point math library.
|
||||
- `libui.h`: Core window and drawing API.
|
||||
- `libwidget.h`: High-level UI components.
|
||||
|
||||
## 📚 Standard Library (`stdlib.h` & `string.h`)
|
||||
## Standard Library (`stdlib.h` & `string.h`)
|
||||
|
||||
The standard library wrappers provide memory management, string manipulation, and basic IO formatting without needing direct syscalls.
|
||||
|
||||
@@ -45,7 +49,60 @@ The standard library wrappers provide memory management, string manipulation, an
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ System Calls (`syscall.h`)
|
||||
## Math Library (`math.h`)
|
||||
|
||||
BoredOS ships a freestanding floating-point math library in `libc/math.h`. It uses pure arithmetic — Taylor series, Newton-Raphson, and range-reduction — with no dependency on a host `libm` or hardware math intrinsics. It is automatically linked into every userland ELF.
|
||||
|
||||
```c
|
||||
#include "math.h"
|
||||
```
|
||||
|
||||
### Constants
|
||||
|
||||
| Constant | Value | Description |
|
||||
|---|---|---|
|
||||
| `M_PI` | 3.14159… | π |
|
||||
| `M_E` | 2.71828… | Euler's number |
|
||||
| `M_LN2` | 0.69315… | Natural log of 2 |
|
||||
| `M_SQRT2` | 1.41421… | √2 |
|
||||
| `HUGE_VAL` | ~+∞ | Overflow sentinel |
|
||||
|
||||
### Functions
|
||||
|
||||
#### Absolute value & remainder
|
||||
* `double fabs(double x);` — Absolute value.
|
||||
* `double fmod(double x, double y);` — Floating-point remainder. Returns `0` when `y == 0`.
|
||||
|
||||
#### Rounding
|
||||
* `double floor(double x);` — Largest integer ≤ x.
|
||||
* `double ceil(double x);` — Smallest integer ≥ x.
|
||||
|
||||
#### Trigonometry *(arguments in radians)*
|
||||
* `double sin(double x);` — Sine. Range-reduced to `[-π, π]` then computed via 8-term Taylor series.
|
||||
* `double cos(double x);` — Cosine. Computed via `sin(x + π/2)`.
|
||||
* `double tan(double x);` — Tangent. Returns sentinel `1e15` near poles.
|
||||
|
||||
#### Exponential & logarithm
|
||||
* `double sqrt(double x);` — Square root via Newton-Raphson (25 iterations). Returns `0` for `x ≤ 0`.
|
||||
* `double log(double x);` — Natural logarithm (ln). Returns `-1e30` for `x ≤ 0`.
|
||||
* `double log2(double x);` — Base-2 logarithm.
|
||||
* `double log10(double x);` — Base-10 logarithm.
|
||||
* `double exp(double x);` — e^x. Saturates to `1e300` for `x > 700`, `0` for `x < -700`.
|
||||
* `double pow(double base, double exponent);` — General power. Integer exponents use fast binary exponentiation; fractional exponents use `exp(e * log(b))`.
|
||||
|
||||
#### Hyperbolic
|
||||
* `double sinh(double x);` — Hyperbolic sine.
|
||||
* `double cosh(double x);` — Hyperbolic cosine.
|
||||
* `double tanh(double x);` — Hyperbolic tangent.
|
||||
|
||||
#### Utility
|
||||
* `double hypot(double x, double y);` — `sqrt(x² + y²)` without intermediate overflow.
|
||||
* `double fmin(double a, double b);` — Minimum of two values.
|
||||
* `double fmax(double a, double b);` — Maximum of two values.
|
||||
* `double fclamp(double x, double lo, double hi);` — Clamps `x` into `[lo, hi]`.
|
||||
|
||||
> [!NOTE]
|
||||
> The implementation file is named `libc/libmath.c` (not `libc/math.c`) to avoid a name collision with the `math` CLI calculator app in userland. The public header is still included as `#include "math.h"`.
|
||||
|
||||
For advanced operations, `syscall.h` provides direct wrappers into the kernel.
|
||||
|
||||
@@ -108,7 +165,7 @@ BoredOS includes lwIP for hardware TCP/UDP networking.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Core Data Structures
|
||||
## Core Data Structures
|
||||
|
||||
### `os_info_t`
|
||||
Contains detailed build and version information about the OS.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
The UI library (`libui.h`) is the sole mechanism for Graphical Userland Applications to draw to the screen and receive input events in BoredOS. It wraps `SYS_GUI` kernel calls.
|
||||
|
||||
## 🪟 Window Management
|
||||
## Window Management
|
||||
|
||||
A "Window" is a reserved drawing canvas managed by the compositor.
|
||||
|
||||
@@ -21,7 +21,7 @@ A "Window" is a reserved drawing canvas managed by the compositor.
|
||||
* `void ui_get_screen_size(uint64_t *out_w, uint64_t *out_h);`
|
||||
Query the global screen resolution of the display.
|
||||
|
||||
## 🎨 Drawing Primitives
|
||||
## Drawing Primitives
|
||||
|
||||
All drawing functions write to an off-screen buffer associated with the window. **You must call `ui_mark_dirty()` to instruct the compositor to push your changes to the physical screen.**
|
||||
|
||||
@@ -38,7 +38,7 @@ All drawing functions write to an off-screen buffer associated with the window.
|
||||
> Colors are defined as 32-bit unsigned integers in **ARGB** format: `0xAARRGGBB`.
|
||||
> E.g., `0xFF000000` is opaque black, `0xFFFF0000` is opaque red.
|
||||
|
||||
## 🔤 Text Rendering
|
||||
## Text Rendering
|
||||
|
||||
BoredOS provides multiple text rendering methodologies, including a default system font and scaled/bitmap alternatives.
|
||||
|
||||
@@ -60,7 +60,7 @@ Used for calculating layout bounds before drawing:
|
||||
* `uint32_t ui_get_string_width_scaled(const char *str, float scale);`
|
||||
* `uint32_t ui_get_font_height_scaled(float scale);`
|
||||
|
||||
## 🔄 Event Handling
|
||||
## Event Handling
|
||||
|
||||
Applications must continuously poll for events inside an infinite `$while(1)` loop.
|
||||
|
||||
@@ -103,3 +103,9 @@ typedef struct {
|
||||
*(Note: Coordinate arguments (`arg1`, `arg2`) for mouse events are typically relative to the top-left corner of the window's client area).*
|
||||
|
||||
---
|
||||
|
||||
> [!TIP]
|
||||
> **Looking for Buttons, TextBoxes, or Scrollbars?**
|
||||
> While `libui.h` provides the foundation for drawing, most applications should use the higher-level [**Widget API**](widget_api.md) (`libwidget.h`) for standard interactive components.
|
||||
|
||||
---
|
||||
|
||||
108
docs/appdev/widget_api.md
Normal file
108
docs/appdev/widget_api.md
Normal file
@@ -0,0 +1,108 @@
|
||||
<div align="center">
|
||||
<h1>Widget API (<code>libwidget.h</code>)</h1>
|
||||
<p><em>High-level UI components for BoredOS applications.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
The Widget library (`libwidget.h`) provides a set of reusable UI components built on top of `libui.h`. It uses an abstract `widget_context_t` to decouple component logic from specific drawing implementations, making it easier to build complex graphical interfaces.
|
||||
|
||||
## Widget Context
|
||||
|
||||
To use any widget, you must first define a `widget_context_t`. This structure contains function pointers for basic drawing operations (rects, strings) and theme preferences.
|
||||
|
||||
```c
|
||||
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;
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Usually, `user_data` is set to your `ui_window_t` handle, and the functions are simple wrappers around `ui_draw_rect`, `ui_draw_string`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Button (`widget_button_t`)
|
||||
|
||||
Standard interactive button with hover and click states.
|
||||
|
||||
* `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);`
|
||||
* `bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data);`
|
||||
|
||||
### Usage Example:
|
||||
```c
|
||||
widget_button_t my_btn;
|
||||
widget_button_init(&my_btn, 10, 10, 80, 25, "Click Me");
|
||||
my_btn.on_click = my_callback_func;
|
||||
|
||||
// In your event loop:
|
||||
widget_button_handle_mouse(&my_btn, ev.arg1, ev.arg2, is_down, is_clicked, my_data);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scrollbar (`widget_scrollbar_t`)
|
||||
|
||||
Vertical scrollbar supporting dragging and track-paging.
|
||||
|
||||
* `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);`
|
||||
* `bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data);`
|
||||
|
||||
> [!NOTE]
|
||||
> The scrollbar automatically calculates the "thumb" size based on the ratio of `h` to `content_height`.
|
||||
|
||||
---
|
||||
|
||||
## TextBox (`widget_textbox_t`)
|
||||
|
||||
Editable text field with focus support and keyboard handling.
|
||||
|
||||
* `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 (`widget_dropdown_t`)
|
||||
|
||||
Selection menu for picking one item from a list.
|
||||
|
||||
* `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 (`widget_checkbox_t`)
|
||||
|
||||
Toggleable options with support for circular "Radio" style or square "Checkbox" style.
|
||||
|
||||
* `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);`
|
||||
|
||||
---
|
||||
|
||||
## Event Integration
|
||||
|
||||
Widgets are designed to be polled within your `libui` event loop. Most handle-mouse functions return `true` if the event was "consumed" by the widget, allowing you to stop further processing for that event.
|
||||
|
||||
```c
|
||||
if (ui_get_event(win, &ev)) {
|
||||
bool handled = false;
|
||||
handled |= widget_button_handle_mouse(&btn, ev.arg1, ev.arg2, is_down, is_clicked, NULL);
|
||||
if (!handled) {
|
||||
// Handle global window events...
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -9,7 +9,7 @@ BoredOS is a 64-bit hobbyist operating system designed for the x86_64 architectu
|
||||
|
||||
This document serves as an overview of the core architecture and the layout of the kernel source code.
|
||||
|
||||
## 📂 Source Code Layout (`src/`)
|
||||
## Source Code Layout (`src/`)
|
||||
|
||||
The OS heavily relies on module separation. The `src/` directory is logically split into several domains:
|
||||
|
||||
@@ -23,7 +23,7 @@ The OS heavily relies on module separation. The `src/` directory is logically sp
|
||||
- **`wm/`**: The graphical subsystem. It handles drawing primitives, window structures, font rendering, and double-buffering.
|
||||
- **`userland/`**: Out-of-kernel components. This includes the custom SDK/compiler environment (`libc/`) and user applications (`cli/`, `gui/`, `games/`).
|
||||
|
||||
## 🚀 Boot Process
|
||||
## Boot Process
|
||||
|
||||
BoredOS uses **Limine** as its primary bootloader.
|
||||
|
||||
@@ -42,7 +42,7 @@ BoredOS utilizes Symmetric Multi-Processing (SMP) to distribute workloads across
|
||||
- **Scheduler**: A round-robin scheduler runs on each core. Processes are pinned to specific CPUs (CPU Affinity) to maintain cache locality. The BSP timer interrupt (`60Hz`) broadcasts a scheduling IPI to all core to ensure balanced execution.
|
||||
- **Spinlocks**: Since multiple cores can access kernel structures (VFS, Process List) simultaneously, the kernel uses **interrupt-safe spinlocks** to prevent race conditions.
|
||||
|
||||
## 🛡️ Userland Transition
|
||||
## Userland Transition
|
||||
|
||||
The OS supports privilege separation (Ring 0 vs. Ring 3). When an application is launched, the kernel:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
BoredOS implements a rudimentary but functional filesystem layer designed to support reading system assets and user applications during runtime.
|
||||
|
||||
## 🗂️ Virtual File System (VFS)
|
||||
## Virtual File System (VFS)
|
||||
|
||||
The Virtual File System acts as an abstraction layer across different underlying storage mechanisms (even if, currently, only one type is fully utilized). System calls targeting files (`SYS_FS`) route through the VFS rather than interacting with the disk directly.
|
||||
|
||||
@@ -17,11 +17,11 @@ Key VFS functionalities include:
|
||||
- **Path Parsing**: Resolving absolute and relative paths.
|
||||
- **SMP Safety**: All VFS and underlying FAT32 operations are protected by a global **Spinlock**. This ensures that multiple cores can safely read from the filesystem simultaneously without corrupting internal file seek pointers or directory cache states.
|
||||
|
||||
## 💾 FAT32 Implementation
|
||||
## FAT32 Implementation
|
||||
|
||||
The primary filesystem logic in `fat32.c` handles both in-memory RAM-based filesystem simulation and physical ATA block devices.
|
||||
|
||||
### 💿 Storage Support
|
||||
### Storage Support
|
||||
|
||||
BoredOS supports two main types of storage for its FAT32 implementation:
|
||||
|
||||
@@ -30,7 +30,7 @@ BoredOS supports two main types of storage for its FAT32 implementation:
|
||||
- **GPT is NOT supported**: Currently, only **MBR (Master Boot Record)** partition tables or **raw (partitionless)** disks are supported.
|
||||
- **Filesystem**: The partition must be formatted as **FAT32**.
|
||||
|
||||
### 🔍 Auto-detection
|
||||
### Auto-detection
|
||||
The `Disk Manager` automatically probes primary and secondary IDE channels during initialization. If a valid FAT32 partition is found (either directly at sector 0 or via an MBR partition table), the disk is assigned a drive letter (starting from `B:`) and becomes accessible to the VFS.
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
Memory management in BoredOS is split into physical and virtual layers, designed to support both kernel operations and userland isolation on the x86_64 architecture.
|
||||
|
||||
## 🧠 Physical Memory Management (PMM)
|
||||
## Physical Memory Management (PMM)
|
||||
|
||||
The PMM is responsible for tracking which physical RAM frames (usually 4KB each) are free and which are in use.
|
||||
|
||||
@@ -19,7 +19,7 @@ The PMM is responsible for tracking which physical RAM frames (usually 4KB each)
|
||||
> [!NOTE]
|
||||
> 4KB frame sizes strike a balance between allocation speed and minimal memory fragmentation, fitting directly with the page tables.
|
||||
|
||||
## 🗺️ Virtual Memory Management (VMM) and Paging
|
||||
## Virtual Memory Management (VMM) and Paging
|
||||
|
||||
BoredOS uses 4-level paging (PML4), a requirement for x86_64 long mode, dividing the virtual address space between the kernel and userland.
|
||||
|
||||
@@ -30,7 +30,7 @@ BoredOS uses 4-level paging (PML4), a requirement for x86_64 long mode, dividing
|
||||
- **User Space**: Userland applications are loaded into lower virtual addresses.
|
||||
- **Page Faults**: The `mem/` subsystem registers an Interrupt Service Routine (ISR) for page faults (Interrupt 14). If a process accesses unmapped memory, the handler determines whether to allocate a new frame or terminate the process.
|
||||
|
||||
## 🏗️ Kernel Heap
|
||||
## Kernel Heap
|
||||
|
||||
Dynamic allocation within the kernel (`kmalloc` and `kfree`) is layered on top of the physical allocator. The kernel maintains its own heap area in virtual memory. When the heap requires more space, it requests physical frames from the PMM and maps them into the kernel's virtual address space.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
BoredOS features a fully custom, graphical Window Manager built directly into the kernel, residing in the `src/wm/` directory. It is responsible for compositing the screen, handling window logic, rendering text, and dispatching UI events.
|
||||
|
||||
## 🖼️ Framebuffer and Rendering
|
||||
## Framebuffer and Rendering
|
||||
|
||||
1. **Limine Framebuffer**: During boot, the Limine bootloader requests a graphical framebuffer from the hardware (e.g., GOP in UEFI environments) and passes a pointer to this linear memory buffer to the kernel.
|
||||
2. **Double Buffering**: To prevent screen tearing, the WM does not draw directly to the screen. It allocates a "back buffer" in kernel memory equal to the size of the screen. All drawing operations (lines, rectangles, windows) happen on this back buffer.
|
||||
@@ -24,7 +24,7 @@ The windowing system is built around a linked list of `Window` structures.
|
||||
- **Window Structures**: Each window object tracks its dimensions (`x`, `y`, `width`, `height`), title, background color, and an internal buffer if it's acting as a canvas for userland apps.
|
||||
- **Decorations**: The kernel handles drawing window borders, title bars, and close buttons automatically unless a borderless style is specified.
|
||||
|
||||
## 🖱️ Input Handling and Events
|
||||
## Input Handling and Events
|
||||
|
||||
The WM acts as the central hub for input routing.
|
||||
|
||||
@@ -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,21 +211,31 @@ void kmain(void) {
|
||||
if (fs_starts_with(clean_path, "boot():")) clean_path += 7;
|
||||
else if (fs_starts_with(clean_path, "boot:///")) clean_path += 8;
|
||||
|
||||
char dir_path[256];
|
||||
int last_slash = -1;
|
||||
for (int j = 0; clean_path[j]; j++) {
|
||||
if (clean_path[j] == '/') last_slash = j;
|
||||
}
|
||||
if (last_slash > 0) {
|
||||
for (int j = 0; j < last_slash; j++) dir_path[j] = clean_path[j];
|
||||
dir_path[last_slash] = '\0';
|
||||
fat32_mkdir_recursive(dir_path);
|
||||
}
|
||||
int len = 0;
|
||||
while(clean_path[len]) len++;
|
||||
|
||||
FAT32_FileHandle *fh = fat32_open(clean_path, "w");
|
||||
if (fh && fh->valid) {
|
||||
fat32_write(fh, mod->address, mod->size);
|
||||
fat32_close(fh);
|
||||
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++) {
|
||||
if (clean_path[j] == '/') last_slash = j;
|
||||
}
|
||||
if (last_slash > 0) {
|
||||
for (int j = 0; j < last_slash; j++) dir_path[j] = clean_path[j];
|
||||
dir_path[last_slash] = '\0';
|
||||
fat32_mkdir_recursive(dir_path);
|
||||
}
|
||||
|
||||
FAT32_FileHandle *fh = fat32_open(clean_path, "w");
|
||||
if (fh && fh->valid) {
|
||||
fat32_write(fh, mod->address, mod->size);
|
||||
fat32_close(fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
BIN
src/images/wallpapers/bored.jpg
Normal file
BIN
src/images/wallpapers/bored.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 574 KiB |
@@ -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,31 +625,32 @@ 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;
|
||||
if (ry + rh > (win->h - 20)) rh = (win->h - 20) - ry;
|
||||
|
||||
|
||||
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);
|
||||
needs_repaint = 1;
|
||||
}
|
||||
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 (sys_dns_lookup(hostname, &ip) != 0) return 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 (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 (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) {
|
||||
@@ -1253,9 +1456,19 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
||||
ui_window_set_title(win_browser, page_title);
|
||||
skip_content = true;
|
||||
}
|
||||
} else {
|
||||
} 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;
|
||||
needs_repaint = true;
|
||||
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 (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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (is_click) {
|
||||
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) {
|
||||
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 (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;
|
||||
}
|
||||
int my = ev.arg2 - URL_BAR_H + scroll_y;
|
||||
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)) {
|
||||
elements[k].checked = false;
|
||||
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,105 +144,93 @@ 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
|
||||
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_map[idx][0];
|
||||
|
||||
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[] = {
|
||||
"C", "s", "r", "/",
|
||||
"7", "8", "9", "*",
|
||||
"4", "5", "6", "-",
|
||||
"1", "2", "3", "+",
|
||||
"0", ".", "B", "="
|
||||
};
|
||||
char lbl = labels[i][0];
|
||||
|
||||
if (lbl >= '0' && lbl <= '9') {
|
||||
if (calc_new_entry || calc_error) {
|
||||
calc_curr = (lbl - '0') * SCALE;
|
||||
calc_new_entry = false;
|
||||
calc_decimal_mode = false;
|
||||
} else {
|
||||
if (calc_decimal_mode) {
|
||||
if (calc_decimal_divisor <= SCALE) {
|
||||
long long digit_val = ((long long)(lbl - '0') * SCALE) / calc_decimal_divisor;
|
||||
if (calc_curr >= 0) calc_curr += digit_val;
|
||||
else calc_curr -= digit_val;
|
||||
calc_decimal_divisor *= 10;
|
||||
}
|
||||
} else {
|
||||
if (calc_curr >= 0) calc_curr = calc_curr * 10 + (lbl - '0') * SCALE;
|
||||
else calc_curr = calc_curr * 10 - (lbl - '0') * SCALE;
|
||||
}
|
||||
}
|
||||
calc_error = false;
|
||||
} else if (lbl == '.') {
|
||||
if (calc_new_entry) {
|
||||
calc_curr = 0;
|
||||
calc_new_entry = false;
|
||||
}
|
||||
if (!calc_decimal_mode) {
|
||||
calc_decimal_mode = true;
|
||||
calc_decimal_divisor = 10;
|
||||
}
|
||||
} else if (lbl == 'C') {
|
||||
calc_curr = 0; calc_acc = 0; calc_op = 0;
|
||||
calc_new_entry = true; calc_error = false; calc_decimal_mode = false;
|
||||
} else if (lbl == 'B') {
|
||||
if (!calc_new_entry && !calc_error) {
|
||||
if (calc_decimal_mode) {
|
||||
if (calc_decimal_divisor > 10) {
|
||||
calc_decimal_divisor /= 10;
|
||||
long long unit = SCALE / calc_decimal_divisor;
|
||||
calc_curr = (calc_curr / (unit * 10)) * (unit * 10);
|
||||
} else {
|
||||
calc_decimal_mode = false;
|
||||
calc_decimal_divisor = 10;
|
||||
calc_curr = (calc_curr / SCALE) * SCALE;
|
||||
}
|
||||
} else {
|
||||
calc_curr = (calc_curr / SCALE / 10) * SCALE;
|
||||
}
|
||||
}
|
||||
} else if (lbl == 's') { // sqr
|
||||
calc_curr = (calc_curr * calc_curr) / SCALE; calc_new_entry = true;
|
||||
} else if (lbl == 'r') { // rt
|
||||
long long s = isqrt(calc_curr);
|
||||
if (s == -1) calc_error = true;
|
||||
else calc_curr = s * 1000;
|
||||
calc_new_entry = true;
|
||||
} else if (lbl == '=') {
|
||||
do_op();
|
||||
calc_curr = calc_acc; calc_op = 0; calc_new_entry = true; calc_decimal_mode = false;
|
||||
if (lbl >= '0' && lbl <= '9') {
|
||||
if (calc_new_entry || calc_error) {
|
||||
calc_curr = (lbl - '0') * SCALE;
|
||||
calc_new_entry = false;
|
||||
calc_decimal_mode = false;
|
||||
} else {
|
||||
if (calc_decimal_mode) {
|
||||
if (calc_decimal_divisor <= SCALE) {
|
||||
long long digit_val = ((long long)(lbl - '0') * SCALE) / calc_decimal_divisor;
|
||||
if (calc_curr >= 0) calc_curr += digit_val;
|
||||
else calc_curr -= digit_val;
|
||||
calc_decimal_divisor *= 10;
|
||||
}
|
||||
} else {
|
||||
if (!calc_new_entry) {
|
||||
if (calc_op) do_op();
|
||||
else calc_acc = calc_curr;
|
||||
}
|
||||
calc_op = lbl; calc_new_entry = true; calc_decimal_mode = false;
|
||||
if (calc_curr >= 0) calc_curr = calc_curr * 10 + (lbl - '0') * SCALE;
|
||||
else calc_curr = calc_curr * 10 - (lbl - '0') * SCALE;
|
||||
}
|
||||
|
||||
update_display();
|
||||
calculator_paint();
|
||||
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
|
||||
return;
|
||||
}
|
||||
calc_error = false;
|
||||
} else if (lbl == '.') {
|
||||
if (calc_new_entry) {
|
||||
calc_curr = 0;
|
||||
calc_new_entry = false;
|
||||
}
|
||||
if (!calc_decimal_mode) {
|
||||
calc_decimal_mode = true;
|
||||
calc_decimal_divisor = 10;
|
||||
}
|
||||
} else if (lbl == 'C') {
|
||||
calc_curr = 0; calc_acc = 0; calc_op = 0;
|
||||
calc_new_entry = true; calc_error = false; calc_decimal_mode = false;
|
||||
} else if (lbl == 'B') {
|
||||
if (!calc_new_entry && !calc_error) {
|
||||
if (calc_decimal_mode) {
|
||||
if (calc_decimal_divisor > 10) {
|
||||
calc_decimal_divisor /= 10;
|
||||
long long unit = SCALE / calc_decimal_divisor;
|
||||
calc_curr = (calc_curr / (unit * 10)) * (unit * 10);
|
||||
} else {
|
||||
calc_decimal_mode = false;
|
||||
calc_decimal_divisor = 10;
|
||||
calc_curr = (calc_curr / SCALE) * SCALE;
|
||||
}
|
||||
} else {
|
||||
calc_curr = (calc_curr / SCALE / 10) * SCALE;
|
||||
}
|
||||
}
|
||||
} else if (lbl == 's') { // sqr
|
||||
calc_curr = (calc_curr * calc_curr) / SCALE; calc_new_entry = true;
|
||||
} else if (lbl == 'r') { // rt
|
||||
long long s = isqrt(calc_curr);
|
||||
if (s == -1) calc_error = true;
|
||||
else calc_curr = s * 1000;
|
||||
calc_new_entry = true;
|
||||
} else if (lbl == '=') {
|
||||
do_op();
|
||||
calc_curr = calc_acc; calc_op = 0; calc_new_entry = true; calc_decimal_mode = false;
|
||||
} else {
|
||||
if (!calc_new_entry) {
|
||||
if (calc_op) do_op();
|
||||
else calc_acc = calc_curr;
|
||||
}
|
||||
calc_op = lbl; calc_new_entry = true; calc_decimal_mode = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
calc_op = 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);
|
||||
}
|
||||
|
||||
1768
src/userland/gui/grapher.c
Normal file
1768
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;
|
||||
int len = strlen(g_filename);
|
||||
g_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. 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) {
|
||||
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;
|
||||
}
|
||||
filename[len++] = '0' + (num / 10) % 10;
|
||||
filename[len++] = '0' + (num % 10);
|
||||
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';
|
||||
|
||||
// 6. Write to PNG
|
||||
int fd = sys_open(filename, "w"); // Open file
|
||||
int res = 0;
|
||||
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
|
||||
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,46 +98,42 @@ 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;
|
||||
uint32_t curr_y = 0;
|
||||
// Fixed-point 16.16
|
||||
uint32_t step_x = (viewer_img_w << 16) / disp_w;
|
||||
uint32_t step_y = (viewer_img_h << 16) / disp_h;
|
||||
uint32_t curr_y = 0;
|
||||
|
||||
for (int y = 0; y < disp_h; y++) {
|
||||
uint32_t src_y = curr_y >> 16;
|
||||
if (src_y >= (uint32_t)viewer_img_h) src_y = viewer_img_h - 1;
|
||||
uint32_t curr_x = 0;
|
||||
uint32_t src_row_off = src_y * viewer_img_w;
|
||||
uint32_t dst_row_off = y * disp_w;
|
||||
for (int x = 0; x < disp_w; x++) {
|
||||
uint32_t src_x = curr_x >> 16;
|
||||
if (src_x >= (uint32_t)viewer_img_w) src_x = viewer_img_w - 1;
|
||||
temp_buf[dst_row_off + x] = pixels[src_row_off + src_x];
|
||||
curr_x += step_x;
|
||||
}
|
||||
curr_y += step_y;
|
||||
for (int y = 0; y < disp_h; y++) {
|
||||
uint32_t src_y = curr_y >> 16;
|
||||
if (src_y >= (uint32_t)viewer_img_h) src_y = viewer_img_h - 1;
|
||||
uint32_t curr_x = 0;
|
||||
uint32_t src_row_off = src_y * viewer_img_w;
|
||||
uint32_t dst_row_off = y * disp_w;
|
||||
for (int x = 0; x < disp_w; x++) {
|
||||
uint32_t src_x = curr_x >> 16;
|
||||
if (src_x >= (uint32_t)viewer_img_w) src_x = viewer_img_w - 1;
|
||||
temp_buf[dst_row_off + x] = pixels[src_row_off + src_x];
|
||||
curr_x += step_x;
|
||||
}
|
||||
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) {
|
||||
|
||||
157
src/userland/libc/libmath.c
Normal file
157
src/userland/libc/libmath.c
Normal file
@@ -0,0 +1,157 @@
|
||||
// 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 "math.h"
|
||||
static double _pow_int(double b, int e) {
|
||||
if (e == 0) return 1.0;
|
||||
if (e < 0) { return 1.0 / _pow_int(b, -e); }
|
||||
double r = 1.0;
|
||||
while (e > 0) {
|
||||
if (e & 1) r *= b;
|
||||
b *= b;
|
||||
e >>= 1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
double fabs(double x) {
|
||||
return x < 0.0 ? -x : x;
|
||||
}
|
||||
double fmod(double x, double y) {
|
||||
if (y == 0.0) return 0.0;
|
||||
return x - (int)(x / y) * y;
|
||||
}
|
||||
|
||||
double floor(double x) {
|
||||
int i = (int)x;
|
||||
return (x < 0.0 && (double)i != x) ? (double)(i - 1) : (double)i;
|
||||
}
|
||||
|
||||
double ceil(double x) {
|
||||
int i = (int)x;
|
||||
return (x > 0.0 && (double)i != x) ? (double)(i + 1) : (double)i;
|
||||
}
|
||||
|
||||
double sin(double x) {
|
||||
x = fmod(x, 2.0 * M_PI);
|
||||
if (x > M_PI) x -= 2.0 * M_PI;
|
||||
if (x < -M_PI) x += 2.0 * M_PI;
|
||||
|
||||
double x2 = x * x;
|
||||
double term = x;
|
||||
double sum = x;
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
term *= -x2 / ((2*i) * (2*i + 1));
|
||||
sum += term;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
double cos(double x) {
|
||||
return sin(x + M_PI / 2.0);
|
||||
}
|
||||
|
||||
double tan(double x) {
|
||||
double c = cos(x);
|
||||
if (fabs(c) < 1e-10) return 1e15;
|
||||
return sin(x) / c;
|
||||
}
|
||||
|
||||
double sqrt(double x) {
|
||||
if (x <= 0.0) return 0.0;
|
||||
double g = x * 0.5;
|
||||
for (int i = 0; i < 25; i++) {
|
||||
g = (g + x / g) * 0.5;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
double log(double x) {
|
||||
if (x <= 0.0) return -1e30;
|
||||
|
||||
int e = 0;
|
||||
while (x > 2.0) { x /= 2.0; e++; }
|
||||
while (x < 0.5) { x *= 2.0; e--; }
|
||||
|
||||
double t = (x - 1.0) / (x + 1.0);
|
||||
double t2 = t * t;
|
||||
double sum = t, term = t;
|
||||
for (int i = 1; i <= 20; i++) {
|
||||
term *= t2;
|
||||
sum += term / (2*i + 1);
|
||||
}
|
||||
return 2.0 * sum + e * M_LN2;
|
||||
}
|
||||
|
||||
double log2(double x) {
|
||||
// log2(x) = ln(x) / ln(2)
|
||||
return log(x) / M_LN2;
|
||||
}
|
||||
|
||||
double log10(double x) {
|
||||
return log(x) / 2.302585092994046;
|
||||
}
|
||||
|
||||
double exp(double x) {
|
||||
if (x > 700.0) return 1e300;
|
||||
if (x < -700.0) return 0.0;
|
||||
|
||||
int k = (int)(x / M_LN2);
|
||||
if (x < 0.0 && (double)k * M_LN2 > x) k--;
|
||||
double r = x - (double)k * M_LN2;
|
||||
|
||||
double sum = 1.0, term = 1.0;
|
||||
for (int i = 1; i <= 20; i++) {
|
||||
term *= r / (double)i;
|
||||
sum += term;
|
||||
}
|
||||
|
||||
double result = sum;
|
||||
if (k >= 0) { for (int i = 0; i < k; i++) result *= 2.0; }
|
||||
else { for (int i = 0; i < -k; i++) result /= 2.0; }
|
||||
return result;
|
||||
}
|
||||
double pow(double base, double exponent) {
|
||||
if (base == 0.0) return 0.0;
|
||||
if (exponent == 0.0) return 1.0;
|
||||
int ie = (int)exponent;
|
||||
if ((double)ie == exponent) return _pow_int(base, ie);
|
||||
|
||||
if (base < 0.0) return 0.0;
|
||||
return exp(exponent * log(base));
|
||||
}
|
||||
|
||||
double sinh(double x) {
|
||||
double ep = exp(x);
|
||||
double em = exp(-x);
|
||||
return (ep - em) * 0.5;
|
||||
}
|
||||
|
||||
double cosh(double x) {
|
||||
double ep = exp(x);
|
||||
double em = exp(-x);
|
||||
return (ep + em) * 0.5;
|
||||
}
|
||||
|
||||
double tanh(double x) {
|
||||
double e2 = exp(2.0 * x);
|
||||
return (e2 - 1.0) / (e2 + 1.0);
|
||||
}
|
||||
|
||||
double hypot(double x, double y) {
|
||||
return sqrt(x*x + y*y);
|
||||
}
|
||||
|
||||
double fmin(double a, double b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
double fmax(double a, double b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
double fclamp(double x, double lo, double hi) {
|
||||
if (x < lo) return lo;
|
||||
if (x > hi) return hi;
|
||||
return x;
|
||||
}
|
||||
40
src/userland/libc/math.h
Normal file
40
src/userland/libc/math.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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 MATH_H
|
||||
#define MATH_H
|
||||
|
||||
#define M_PI 3.14159265358979323846
|
||||
#define M_E 2.71828182845904523536
|
||||
#define M_LN2 0.69314718055994530942
|
||||
#define M_SQRT2 1.41421356237309504880
|
||||
#define HUGE_VAL (1e300 * 1e300)
|
||||
|
||||
double fabs(double x);
|
||||
|
||||
double fmod(double x, double y);
|
||||
|
||||
double floor(double x);
|
||||
|
||||
double ceil(double x);
|
||||
|
||||
double sin(double x);
|
||||
double cos(double x);
|
||||
double tan(double x);
|
||||
double sqrt(double x);
|
||||
|
||||
double log(double x);
|
||||
double log2(double x);
|
||||
double log10(double x);
|
||||
double exp(double x);
|
||||
double pow(double base, double exponent);
|
||||
double sinh(double x);
|
||||
double cosh(double x);
|
||||
double tanh(double x);
|
||||
double hypot(double x, double y);
|
||||
double fmin(double a, double b);
|
||||
double fmax(double a, double b);
|
||||
double fclamp(double x, double lo, double hi);
|
||||
|
||||
#endif /* MATH_H */
|
||||
@@ -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,19 +201,33 @@ 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;
|
||||
|
||||
float real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||
|
||||
if (stbtt_FindGlyphIndex(info, codepoint) == 0 && fallback_font) {
|
||||
info = (stbtt_fontinfo *)fallback_font->info;
|
||||
real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||
}
|
||||
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) {
|
||||
info = (stbtt_fontinfo *)fallback_font->info;
|
||||
real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||
}
|
||||
|
||||
bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff);
|
||||
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++) {
|
||||
@@ -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,19 +248,33 @@ 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;
|
||||
|
||||
float real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||
|
||||
if (stbtt_FindGlyphIndex(info, codepoint) == 0 && fallback_font) {
|
||||
info = (stbtt_fontinfo *)fallback_font->info;
|
||||
real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||
}
|
||||
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) {
|
||||
info = (stbtt_fontinfo *)fallback_font->info;
|
||||
real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||
}
|
||||
|
||||
bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff);
|
||||
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++) {
|
||||
@@ -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,18 +416,25 @@ 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_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;
|
||||
}
|
||||
}
|
||||
for (int r = 0; r < h; r++) {
|
||||
int g_y = y + r;
|
||||
if (g_y < 0 || g_y >= sh) 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;
|
||||
int dx = 0, dy = 0;
|
||||
if (c < radius && r < radius) {
|
||||
@@ -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();
|
||||
|
||||
int sw = (int)g_fb->width;
|
||||
int sh = (int)g_fb->height;
|
||||
|
||||
// Copy the internal back object to the dest directly
|
||||
// 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;
|
||||
|
||||
for (int x = 0; x < w; x++) {
|
||||
int vx = dst_x + x;
|
||||
if (vx < 0 || vx >= sw) continue;
|
||||
|
||||
uint32_t pcol = src[y * w + x];
|
||||
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];
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
@@ -151,8 +151,8 @@ int wallpaper_get_height(void) { return wp_height; }
|
||||
void wallpaper_init(void) {
|
||||
// We expect Limine modules to have been copied to /Library/images/Wallpapers/ by main.c
|
||||
// Set a default wallpaper if one exists
|
||||
if (fat32_exists("/Library/images/Wallpapers/mountain.jpg")) {
|
||||
wallpaper_request_set_from_file("/Library/images/Wallpapers/mountain.jpg");
|
||||
if (fat32_exists("/Library/images/Wallpapers/bored.jpg")) {
|
||||
wallpaper_request_set_from_file("/Library/images/Wallpapers/bored.jpg");
|
||||
} else if (fat32_exists("/Library/images/Wallpapers/moon.jpg")) {
|
||||
wallpaper_request_set_from_file("/Library/images/Wallpapers/moon.jpg");
|
||||
}
|
||||
|
||||
640
src/wm/wm.c
640
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;
|
||||
|
||||
@@ -425,12 +433,14 @@ static void draw_dock_files(int x, int y);
|
||||
static void draw_dock_settings(int x, int y);
|
||||
static void draw_dock_notepad(int x, int y);
|
||||
static void draw_dock_calculator(int x, int y);
|
||||
static void draw_dock_grapher(int x, int y);
|
||||
static void draw_dock_terminal(int x, int y);
|
||||
static void draw_dock_minesweeper(int x, int y);
|
||||
static void draw_dock_paint(int x, int y);
|
||||
static void draw_dock_clock(int x, int y);
|
||||
static void draw_dock_taskman(int x, int y);
|
||||
static void draw_dock_editor(int x, int y);
|
||||
static void draw_dock_word(int x, int y);
|
||||
static void draw_dock_browser(int x, int y);
|
||||
static void draw_dock_editor(int x, int y);
|
||||
static void draw_filled_circle(int cx, int cy, int r, uint32_t color);
|
||||
|
||||
@@ -768,6 +778,11 @@ void draw_calculator_icon(int x, int y, const char *label) {
|
||||
draw_icon_label(x, y, label);
|
||||
}
|
||||
|
||||
void draw_grapher_icon(int x, int y, const char *label) {
|
||||
draw_scaled_icon(x, y, draw_dock_grapher);
|
||||
draw_icon_label(x, y, label);
|
||||
}
|
||||
|
||||
void draw_terminal_icon(int x, int y, const char *label) {
|
||||
draw_scaled_icon(x, y, draw_dock_terminal);
|
||||
draw_icon_label(x, y, label);
|
||||
@@ -986,6 +1001,42 @@ static void draw_dock_notepad(int x, int y) {
|
||||
draw_rect(x + 33, y + 24, 1, 2, 0xFF555555);
|
||||
}
|
||||
|
||||
static void draw_dock_grapher(int x, int y) {
|
||||
// Dark background with a panel look
|
||||
draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFF121212);
|
||||
draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFF1E1E1E);
|
||||
draw_rounded_rect_filled(x + 1, y + 24, 46, 23, 9, 0xFF161616);
|
||||
|
||||
// Subtle grid (matches Grapher's theme)
|
||||
uint32_t grid_color = 0xFF2A2A2A;
|
||||
for (int i = 8; i < 40; i += 8) {
|
||||
draw_rect(x + i, y + 6, 1, 36, grid_color);
|
||||
draw_rect(x + 6, y + i + 6, 36, 1, grid_color);
|
||||
}
|
||||
|
||||
// Axis line
|
||||
draw_rect(x + 24, y + 10, 1, 28, 0xFF444444);
|
||||
draw_rect(x + 10, y + 24, 28, 1, 0xFF444444);
|
||||
|
||||
// Vibrant Sine Wave (Neon Cyan)
|
||||
uint32_t curve_color = 0xFF00E5FF;
|
||||
int curve_y[] = {24, 23, 21, 19, 17, 16, 15, 15, 16, 17, 19, 21, 23, 24, 26, 28, 30, 32, 33, 33, 32, 30, 28, 26, 24, 23, 21, 19, 17, 16, 15, 15, 16, 17, 19, 21};
|
||||
for (int i = 0; i < 35; i++) {
|
||||
int x1 = x + 6 + i;
|
||||
int y1 = y + curve_y[i];
|
||||
int y2 = y + curve_y[i+1];
|
||||
|
||||
// Anti-aliased look with multi-point vertical connector
|
||||
if (y1 < y2) for (int j = y1; j <= y2; j++) put_pixel(x1, j, curve_color);
|
||||
else for (int j = y2; j <= y1; j++) put_pixel(x1, j, curve_color);
|
||||
}
|
||||
|
||||
// Add white indicator "nodes" at the peaks
|
||||
draw_filled_circle(x + 6 + 7, y + 15, 2, 0xFFFFFFFF);
|
||||
draw_filled_circle(x + 6 + 18, y + 33, 2, 0xFFFFFFFF);
|
||||
draw_filled_circle(x + 6 + 30, y + 15, 2, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
static void draw_dock_calculator(int x, int y) {
|
||||
draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFF111111);
|
||||
draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFF222222);
|
||||
@@ -1110,27 +1161,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 +1270,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,275 +1305,265 @@ 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();
|
||||
|
||||
int cx = 0, cy = y_start, cw = sw, ch = y_end - y_start;
|
||||
if (dirty.active) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (cw <= 0 || ch <= 0) return;
|
||||
|
||||
graphics_set_clipping(cx, cy, cw, ch);
|
||||
|
||||
if (pass == 1) {
|
||||
draw_desktop_background();
|
||||
|
||||
for (int i = 0; i < desktop_icon_count; i++) {
|
||||
DesktopIcon *icon = &desktop_icons[i];
|
||||
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) {
|
||||
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 (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);
|
||||
else if (str_starts_with(icon->name, "Minesweeper")) draw_minesweeper_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Settings")) draw_control_panel_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Clock")) draw_clock_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "About")) draw_about_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Recycle Bin")) draw_recycle_bin_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Files")) draw_folder_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Paint")) draw_paint_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Grapher")) draw_grapher_icon(icon->x, icon->y, label);
|
||||
else draw_icon(icon->x, icon->y, label);
|
||||
} else {
|
||||
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;
|
||||
draw_image_icon(icon->x, icon->y, full_path);
|
||||
draw_icon_label(icon->x, icon->y, icon->name);
|
||||
}
|
||||
else if (str_ends_with(icon->name, ".pdf")) draw_pdf_icon(icon->x, icon->y, icon->name);
|
||||
else draw_document_icon(icon->x, icon->y, icon->name);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < sorted_window_count_cache; i++) {
|
||||
Window *win = sorted_windows_cache[i];
|
||||
if (!win || !win->visible) 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 && 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, 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 = 12 * (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_grapher(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);
|
||||
}
|
||||
|
||||
if (desktop_menu_visible) {
|
||||
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;
|
||||
if (desktop_menu_target_icon != -1) {
|
||||
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", 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 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", cp ? COLOR_WHITE : COLOR_DKGRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (desktop_dialog_state != 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg_box_visible) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (notif_active) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
graphics_set_clipping(dirty.x, dirty.y, dirty.w, dirty.h);
|
||||
} else {
|
||||
graphics_clear_clipping();
|
||||
}
|
||||
|
||||
// 1. Desktop Background (respects wallpaper color/pattern)
|
||||
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->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;
|
||||
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 (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);
|
||||
else if (str_starts_with(icon->name, "Minesweeper")) draw_minesweeper_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Settings")) draw_control_panel_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Clock")) draw_clock_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "About")) draw_about_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Recycle Bin")) draw_recycle_bin_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Files")) draw_folder_icon(icon->x, icon->y, label);
|
||||
else if (str_starts_with(icon->name, "Paint")) draw_paint_icon(icon->x, icon->y, label);
|
||||
else draw_icon(icon->x, icon->y, label);
|
||||
} else {
|
||||
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;
|
||||
draw_image_icon(icon->x, icon->y, full_path);
|
||||
draw_icon_label(icon->x, icon->y, icon->name);
|
||||
}
|
||||
else if (str_ends_with(icon->name, ".pdf")) draw_pdf_icon(icon->x, icon->y, icon->name);
|
||||
else draw_document_icon(icon->x, icon->y, icon->name);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < local_window_count; i++) {
|
||||
Window *win = sorted_windows[i];
|
||||
if (!win || !win->visible) continue;
|
||||
// Memory barrier to ensure APs see the sorted window list correctly
|
||||
asm volatile("" ::: "memory");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
draw_window(win);
|
||||
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]);
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
// 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 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();
|
||||
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 * 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();
|
||||
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);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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]);
|
||||
}
|
||||
|
||||
// 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);
|
||||
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");
|
||||
}
|
||||
|
||||
// Notification (dark mode)
|
||||
if (notif_active) {
|
||||
int nx = sw - 280 + notif_x_offset;
|
||||
int ny = 40;
|
||||
int nw = 260;
|
||||
int nh = 50;
|
||||
|
||||
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 (is_dragging_file) {
|
||||
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)
|
||||
|
||||
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 +1587,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)) {
|
||||
wm_lock_release(rflags);
|
||||
return all_windows[i];
|
||||
}
|
||||
}
|
||||
uint64_t rflags = wm_lock_acquire();
|
||||
Window *win = wm_find_window_by_title_locked(title);
|
||||
wm_lock_release(rflags);
|
||||
return NULL;
|
||||
return win;
|
||||
}
|
||||
|
||||
void wm_remove_window(Window *win) {
|
||||
@@ -1717,7 +1746,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 +1841,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 +1976,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();
|
||||
|
||||
@@ -2005,7 +2037,7 @@ void wm_handle_right_click(int x, int y) {
|
||||
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 total_dock_width = 12 * (dock_item_size + dock_spacing);
|
||||
int dock_bg_x = (sw - total_dock_width) / 2 - 12;
|
||||
int dock_bg_w = total_dock_width + 24;
|
||||
|
||||
@@ -2020,13 +2052,14 @@ void wm_handle_right_click(int x, int y) {
|
||||
else if (item == 1) start_menu_pending_app = "Settings";
|
||||
else if (item == 2) start_menu_pending_app = "Notepad";
|
||||
else if (item == 3) start_menu_pending_app = "Calculator";
|
||||
else if (item == 4) start_menu_pending_app = "Terminal";
|
||||
else if (item == 5) start_menu_pending_app = "Minesweeper";
|
||||
else if (item == 6) start_menu_pending_app = "Paint";
|
||||
else if (item == 7) start_menu_pending_app = "Browser";
|
||||
else if (item == 8) start_menu_pending_app = "Task Manager";
|
||||
else if (item == 9) start_menu_pending_app = "Clock";
|
||||
else if (item == 10) start_menu_pending_app = "Word Processor";
|
||||
else if (item == 4) start_menu_pending_app = "Grapher";
|
||||
else if (item == 5) start_menu_pending_app = "Terminal";
|
||||
else if (item == 6) start_menu_pending_app = "Minesweeper";
|
||||
else if (item == 7) start_menu_pending_app = "Paint";
|
||||
else if (item == 8) start_menu_pending_app = "Browser";
|
||||
else if (item == 9) start_menu_pending_app = "Task Manager";
|
||||
else if (item == 10) start_menu_pending_app = "Clock";
|
||||
else if (item == 11) start_menu_pending_app = "Word Processor";
|
||||
}
|
||||
} else {
|
||||
wm_handle_click(mx, my);
|
||||
@@ -2153,53 +2186,57 @@ 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, "Grapher")) {
|
||||
Window *existing = wm_find_window_by_title_locked("Grapher");
|
||||
if (existing) wm_bring_to_front_locked(existing);
|
||||
else process_create_elf("/bin/grapher.elf", NULL);
|
||||
} 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 +2441,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 +2613,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 +2707,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 +2839,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