Compare commits
No commits in common. "master" and "dvx-a3" have entirely different histories.
850 changed files with 20830 additions and 328664 deletions
4
.gitattributes
vendored
4
.gitattributes
vendored
|
|
@ -2,9 +2,5 @@
|
|||
*.BMP filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.JPG filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.PNG filter=lfs diff=lfs merge=lfs -text
|
||||
*.xcf filter=lfs diff=lfs merge=lfs -text
|
||||
*.XCF filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.ZIP filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,11 +2,8 @@ dosbench/
|
|||
bin/
|
||||
obj/
|
||||
lib/
|
||||
*~
|
||||
*.~
|
||||
.gitignore~
|
||||
.gitattributes~
|
||||
*.SWP
|
||||
.claude/
|
||||
capture/
|
||||
just-stuff/
|
||||
|
|
|
|||
21
LICENSE.txt
21
LICENSE.txt
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2026 Scott Duensing
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the “Software”), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
212
Makefile
212
Makefile
|
|
@ -1,197 +1,57 @@
|
|||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (C) 2026 Scott Duensing
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
# DVX GUI -- Top-level Makefile
|
||||
#
|
||||
# Builds the full DVX stack: core library, task switcher,
|
||||
# bootstrap loader, text help library, widgets, shell, and apps.
|
||||
#
|
||||
# Source tree mirrors bin/ layout:
|
||||
# src/libs/kpunch/<libname>/ -> bin/libs/kpunch/<libname>/<libname>.lib
|
||||
# src/widgets/kpunch/<widget>/ -> bin/widgets/kpunch/<widget>/<widget>.wgt
|
||||
# src/apps/kpunch/<app>/ -> bin/apps/kpunch/<app>/<app>.app
|
||||
# src/loader/ -> bin/DVX.EXE
|
||||
# src/tools/ -> bin/host/<tool>
|
||||
|
||||
.PHONY: all clean libdvx libtasks loader texthelp listhelp widgets dvxshell taskmgr serial sql apps tools deploy-helpsrc compile-help deploy-sdk
|
||||
.PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr serial apps tools
|
||||
|
||||
all: libdvx libtasks loader texthelp listhelp tools widgets dvxshell taskmgr serial sql apps deploy-helpsrc compile-help deploy-sdk
|
||||
all: core tasks loader texthelp listhelp widgets shell taskmgr serial apps tools
|
||||
|
||||
libdvx:
|
||||
$(MAKE) -C src/libs/kpunch/libdvx
|
||||
core:
|
||||
$(MAKE) -C core
|
||||
|
||||
libtasks:
|
||||
$(MAKE) -C src/libs/kpunch/libtasks
|
||||
tasks:
|
||||
$(MAKE) -C tasks
|
||||
|
||||
loader: libdvx libtasks
|
||||
$(MAKE) -C src/loader
|
||||
loader: core tasks
|
||||
$(MAKE) -C loader
|
||||
|
||||
texthelp: libdvx libtasks
|
||||
$(MAKE) -C src/libs/kpunch/texthelp
|
||||
texthelp: core tasks
|
||||
$(MAKE) -C texthelp
|
||||
|
||||
listhelp: libdvx libtasks
|
||||
$(MAKE) -C src/libs/kpunch/listhelp
|
||||
listhelp: core tasks
|
||||
$(MAKE) -C listhelp
|
||||
|
||||
widgets: libdvx libtasks texthelp listhelp
|
||||
$(MAKE) -C src/widgets/kpunch
|
||||
widgets: core tasks texthelp listhelp
|
||||
$(MAKE) -C widgets
|
||||
|
||||
dvxshell: libdvx libtasks
|
||||
$(MAKE) -C src/libs/kpunch/dvxshell
|
||||
shell: core tasks
|
||||
$(MAKE) -C shell
|
||||
|
||||
taskmgr: dvxshell
|
||||
$(MAKE) -C src/libs/kpunch/taskmgr
|
||||
taskmgr: shell
|
||||
$(MAKE) -C taskmgr
|
||||
|
||||
serial: libdvx libtasks
|
||||
$(MAKE) -C src/libs/kpunch/serial
|
||||
|
||||
sql: libdvx libtasks
|
||||
$(MAKE) -C src/libs/kpunch/sql
|
||||
serial: core tasks
|
||||
$(MAKE) -C serial
|
||||
|
||||
tools:
|
||||
$(MAKE) -C src/tools
|
||||
$(MAKE) -C tools
|
||||
|
||||
apps: libdvx libtasks dvxshell tools
|
||||
$(MAKE) -C src/apps/kpunch
|
||||
|
||||
deploy-helpsrc:
|
||||
$(MAKE) -C src/tools deploy-helpsrc
|
||||
|
||||
HLPC = bin/host/dvxhlpc
|
||||
|
||||
SYSTEM_DHS = src/libs/kpunch/libdvx/sysdoc.dhs \
|
||||
src/libs/kpunch/libdvx/arch.dhs src/libs/kpunch/libdvx/apiref.dhs \
|
||||
src/libs/kpunch/libtasks/libtasks.dhs src/libs/kpunch/dvxshell/dvxshell.dhs src/libs/kpunch/sql/dvxsql.dhs \
|
||||
src/libs/kpunch/texthelp/texthelp.dhs src/libs/kpunch/listhelp/listhelp.dhs \
|
||||
src/libs/kpunch/taskmgr/taskmgr.dhs src/libs/kpunch/serial/serial.dhs \
|
||||
src/apps/kpunch/dvxbasic/basrt.dhs \
|
||||
src/widgets/kpunch/wgtsys.dhs
|
||||
|
||||
BASIC_DHS = src/apps/kpunch/dvxbasic/ideguide.dhs src/apps/kpunch/dvxbasic/langref.dhs \
|
||||
src/apps/kpunch/dvxbasic/ctrlover.dhs src/apps/kpunch/dvxbasic/form.dhs
|
||||
|
||||
compile-help:
|
||||
@mkdir -p docs
|
||||
$(HLPC) -o bin/apps/kpunch/dvxhelp/dvxhelp.hlp \
|
||||
--html docs/dvx_help_viewer.html \
|
||||
src/apps/kpunch/dvxhelp/help.dhs
|
||||
$(HLPC) -o bin/apps/kpunch/progman/dvxhelp.hlp \
|
||||
--html docs/dvx_system_reference.html \
|
||||
-i src/libs/kpunch/libdvx \
|
||||
$(SYSTEM_DHS) \
|
||||
$$(find src/widgets/kpunch -name "*.dhs" ! -path "*/wgtsys.dhs" | sort)
|
||||
$(HLPC) -o bin/apps/kpunch/dvxbasic/dvxbasic.hlp \
|
||||
--html docs/dvx_basic_reference.html \
|
||||
$(BASIC_DHS) \
|
||||
$$(find src/widgets/kpunch -name "*.bhs" | sort)
|
||||
|
||||
SDKDIR = bin/sdk
|
||||
|
||||
deploy-sdk:
|
||||
@echo "Building SDK..."
|
||||
@mkdir -p $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow
|
||||
@# Core headers (libdvx public API)
|
||||
@for f in src/libs/kpunch/libdvx/dvxApp.h src/libs/kpunch/libdvx/dvxTypes.h \
|
||||
src/libs/kpunch/libdvx/dvxWgt.h src/libs/kpunch/libdvx/dvxWgtP.h \
|
||||
src/libs/kpunch/libdvx/dvxWm.h src/libs/kpunch/libdvx/dvxDraw.h \
|
||||
src/libs/kpunch/libdvx/dvxVideo.h src/libs/kpunch/libdvx/dvxComp.h \
|
||||
src/libs/kpunch/libdvx/dvxPrefs.h src/libs/kpunch/libdvx/dvxDlg.h \
|
||||
src/libs/kpunch/libdvx/dvxRes.h src/libs/kpunch/libdvx/dvxFont.h \
|
||||
src/libs/kpunch/libdvx/dvxCur.h src/libs/kpunch/libdvx/dvxPal.h \
|
||||
src/libs/kpunch/libdvx/dvxMem.h src/libs/kpunch/libdvx/platform/dvxPlat.h; do \
|
||||
[ -f "$$f" ] && cp "$$f" $(SDKDIR)/include/core/; \
|
||||
done
|
||||
@# Shell header
|
||||
@cp src/libs/kpunch/dvxshell/shellApp.h $(SDKDIR)/include/shell/
|
||||
@cp src/libs/kpunch/dvxshell/shellInf.h $(SDKDIR)/include/shell/
|
||||
@# Tasks header
|
||||
@cp src/libs/kpunch/libtasks/taskSwch.h $(SDKDIR)/include/tasks/
|
||||
@# SQL header
|
||||
@cp src/libs/kpunch/sql/dvxSql.h $(SDKDIR)/include/sql/
|
||||
@# Widget headers -- one subdir per widget
|
||||
@for d in src/widgets/kpunch/*/; do \
|
||||
for h in "$$d"*.h; do \
|
||||
[ -f "$$h" ] || continue; \
|
||||
wgt=$$(basename "$$d"); \
|
||||
mkdir -p $(SDKDIR)/include/widget/"$$wgt"; \
|
||||
cp "$$h" $(SDKDIR)/include/widget/"$$wgt"/; \
|
||||
done; \
|
||||
done
|
||||
@# BASIC include files
|
||||
@cp src/include/basic/*.bas $(SDKDIR)/include/basic/
|
||||
@# BASIC sample: basdemo (project file + form + icon)
|
||||
@cp src/apps/kpunch/basdemo/basdemo.dbp $(SDKDIR)/samples/basic/basdemo/
|
||||
@cp src/apps/kpunch/basdemo/basdemo.frm $(SDKDIR)/samples/basic/basdemo/
|
||||
@cp src/apps/kpunch/basdemo/ICON32.BMP $(SDKDIR)/samples/basic/basdemo/
|
||||
@# BASIC sample: widshow (widget showcase)
|
||||
@cp src/apps/kpunch/widshow/widshow.dbp $(SDKDIR)/samples/basic/widshow/
|
||||
@cp src/apps/kpunch/widshow/widshow.frm $(SDKDIR)/samples/basic/widshow/
|
||||
@cp src/apps/kpunch/widshow/ICON32.BMP $(SDKDIR)/samples/basic/widshow/
|
||||
@# README
|
||||
@printf '%s\n' \
|
||||
'DVX SDK' \
|
||||
'=======' \
|
||||
'' \
|
||||
'Headers for developing applications and widgets against the DVX GUI.' \
|
||||
'' \
|
||||
'Directory Structure' \
|
||||
'-------------------' \
|
||||
'' \
|
||||
' include/' \
|
||||
' core/ DVX core headers (types, drawing, windows, widgets)' \
|
||||
' shell/ Shell/app loading API' \
|
||||
' tasks/ Cooperative task switching API' \
|
||||
' sql/ SQLite database wrapper API' \
|
||||
' widget/ Per-widget public API headers' \
|
||||
' basic/ BASIC include files (DECLARE LIBRARY modules)' \
|
||||
' samples/' \
|
||||
' basic/ Example BASIC projects (open in the DVX BASIC IDE)' \
|
||||
'' \
|
||||
'Requirements' \
|
||||
'------------' \
|
||||
'' \
|
||||
' - DJGPP cross-compiler (i586-pc-msdosdjgpp-gcc)' \
|
||||
' - dxe3gen (included with DJGPP)' \
|
||||
' - DVX resource compiler (SYSTEM/DVXRES.EXE on the target,' \
|
||||
' or bin/host/dvxres on the host)' \
|
||||
' - DVX help compiler (SYSTEM/DVXHLPC.EXE on the target,' \
|
||||
' or bin/host/dvxhlpc on the host)' \
|
||||
'' \
|
||||
'For complete working examples, see the app sources under' \
|
||||
'src/apps/kpunch/ in the DVX repository.' \
|
||||
> $(SDKDIR)/README.TXT
|
||||
apps: core tasks shell tools
|
||||
$(MAKE) -C apps
|
||||
|
||||
clean:
|
||||
$(MAKE) -C src/libs/kpunch/libdvx clean
|
||||
$(MAKE) -C src/libs/kpunch/libtasks clean
|
||||
$(MAKE) -C src/loader clean
|
||||
$(MAKE) -C src/libs/kpunch/texthelp clean
|
||||
$(MAKE) -C src/libs/kpunch/listhelp clean
|
||||
$(MAKE) -C src/widgets/kpunch clean
|
||||
$(MAKE) -C src/libs/kpunch/dvxshell clean
|
||||
$(MAKE) -C src/libs/kpunch/taskmgr clean
|
||||
$(MAKE) -C src/libs/kpunch/serial clean
|
||||
$(MAKE) -C src/libs/kpunch/sql clean
|
||||
$(MAKE) -C src/apps/kpunch clean
|
||||
$(MAKE) -C src/tools clean
|
||||
$(MAKE) -C core clean
|
||||
$(MAKE) -C tasks clean
|
||||
$(MAKE) -C loader clean
|
||||
$(MAKE) -C texthelp clean
|
||||
$(MAKE) -C listhelp clean
|
||||
$(MAKE) -C widgets clean
|
||||
$(MAKE) -C shell clean
|
||||
$(MAKE) -C taskmgr clean
|
||||
$(MAKE) -C serial clean
|
||||
$(MAKE) -C apps clean
|
||||
$(MAKE) -C tools clean
|
||||
-rmdir obj 2>/dev/null
|
||||
-rm -rf bin/config bin/widgets bin/libs bin/sdk
|
||||
-rm -f docs/*.html
|
||||
-rm -rf bin/config bin/widgets bin/libs
|
||||
-rmdir bin/apps/cpanel bin/apps/imgview bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin 2>/dev/null
|
||||
|
|
|
|||
275
README.md
275
README.md
|
|
@ -1,110 +1,219 @@
|
|||
# DVX -- DOS Visual eXecutive
|
||||
|
||||
DVX is a windowed GUI environment and application platform for DOS.
|
||||
It provides a Motif-inspired desktop, a cooperative multitasking kernel,
|
||||
a widget toolkit, a compiled BASIC language with a visual form designer,
|
||||
and a small set of bundled applications.
|
||||
A windowed GUI compositor and widget toolkit targeting DJGPP/DPMI on
|
||||
486+ hardware. VESA VBE 2.0+ LFB only. Motif-inspired visual style
|
||||
with 2px bevels, fixed bitmap fonts, and dirty-rect compositing.
|
||||
|
||||
The system is a compositor built on VESA VBE 2.0+ linear framebuffer
|
||||
video modes. A dirty-rectangle compositor keeps redraw cost proportional
|
||||
to what actually changes on screen, so the desktop is responsive even at
|
||||
1024x768 on modest hardware.
|
||||
The system runs on 86Box (primary target) and real DOS hardware.
|
||||
|
||||
|
||||
## What's Included
|
||||
## Architecture
|
||||
|
||||
* **DVX Shell** -- desktop, app lifecycle manager, Task Manager, crash
|
||||
recovery. The shell is the program the user interacts with and the
|
||||
host for all running applications.
|
||||
* **Widget toolkit** -- 31 widget types as plug-in modules: buttons,
|
||||
labels, text inputs, list boxes, tree views, tab controls, sliders,
|
||||
spinners, progress bars, canvases, scroll panes, splitters, an ANSI
|
||||
terminal, and more.
|
||||
* **DVX BASIC** -- a Visual Basic 3 compatible language with a visual
|
||||
form designer, per-procedure code editor, project management, and a
|
||||
bytecode compiler and VM. BASIC programs compile to standalone DVX
|
||||
applications.
|
||||
* **Help system** -- a full hypertext help viewer with table of contents,
|
||||
keyword index, full-text search, and inline images. Help files are
|
||||
compiled from a plain-text source format.
|
||||
* **Serial / networking stack** -- an ISR-driven UART driver, HDLC
|
||||
packet framing with CRC-16 and Go-Back-N reliable delivery, 1024-bit
|
||||
Diffie-Hellman key exchange, and XTEA-CTR encryption, plus a Linux
|
||||
bridge that connects a virtual serial port to a telnet BBS.
|
||||
* **Control Panel** -- themes, wallpapers, video mode, mouse
|
||||
configuration, all with live preview.
|
||||
* **Bundled applications** -- Program Manager (desktop launcher),
|
||||
Notepad, Clock, Image Viewer, DVX Demo, Control Panel, DVX BASIC
|
||||
IDE, Help Viewer, Resource Editor, Icon Editor, Help Editor, BASIC
|
||||
Demo.
|
||||
DVX is built as a set of DXE3 dynamic modules loaded by a bootstrap
|
||||
executable. The loader resolves dependencies and loads modules in
|
||||
topological order, then hands control to the shell.
|
||||
|
||||
```
|
||||
dvx.exe (loader)
|
||||
|
|
||||
+-- libs/libtasks.lib cooperative task switcher
|
||||
+-- libs/libdvx.lib core GUI (draw, comp, wm, app, widget infra)
|
||||
+-- libs/texthelp.lib shared text editing helpers
|
||||
+-- libs/listhelp.lib shared list/dropdown helpers
|
||||
+-- libs/dvxshell.lib shell (app lifecycle, desktop)
|
||||
+-- libs/taskmgr.lib task manager (Ctrl+Esc, separate DXE)
|
||||
|
|
||||
+-- widgets/*.wgt 26 widget type plugins
|
||||
|
|
||||
+-- apps/*/*.app DXE applications
|
||||
```
|
||||
|
||||
|
||||
## Target Hardware
|
||||
## Directory Structure
|
||||
|
||||
* DOS with a 386-class CPU or newer (486 recommended, Pentium for best
|
||||
performance)
|
||||
* VESA BIOS Extensions 2.0 or newer with linear framebuffer support
|
||||
* 4 MB of extended memory minimum; 8 MB or more recommended
|
||||
* Any resolution the video card reports at 8, 15, 16, 24, or 32 bits
|
||||
per pixel. 640x480, 800x600, and 1024x768 are the common cases.
|
||||
* Two-button mouse; scroll wheel supported if a compatible driver is
|
||||
loaded
|
||||
| Directory | Output | Description |
|
||||
|--------------|-------------------------|--------------------------------------------|
|
||||
| `loader/` | `bin/dvx.exe` | Bootstrap loader, platform layer, stb_ds |
|
||||
| `core/` | `bin/libs/libdvx.lib` | Core GUI library (5 layers + widget infra) |
|
||||
| `tasks/` | `bin/libs/libtasks.lib` | Cooperative task switching |
|
||||
| `texthelp/` | `bin/libs/texthelp.lib` | Shared text editing helpers |
|
||||
| `listhelp/` | `bin/libs/listhelp.lib` | Shared list/dropdown helpers |
|
||||
| `shell/` | `bin/libs/dvxshell.lib` | DVX Shell (app lifecycle, desktop) |
|
||||
| `taskmgr/` | `bin/libs/taskmgr.lib` | Task Manager (separate DXE, Ctrl+Esc) |
|
||||
| `widgets/` | `bin/widgets/*.wgt` | 26 individual widget DXE modules |
|
||||
| `apps/` | `bin/apps/*/*.app` | Application DXE modules |
|
||||
| `tools/` | `bin/dvxres` | Resource tool (host native, not DXE) |
|
||||
| `config/` | `bin/config/`, `bin/libs/`, `bin/widgets/` | INI config, themes, wallpapers, dep files |
|
||||
| `rs232/` | `lib/librs232.a` | ISR-driven UART serial driver |
|
||||
| `packet/` | `lib/libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ |
|
||||
| `security/` | `lib/libsecurity.a` | DH key exchange, XTEA-CTR cipher, RNG |
|
||||
| `seclink/` | `lib/libseclink.a` | Secure serial link (channels, encryption) |
|
||||
| `proxy/` | `bin/secproxy` | Linux proxy: 86Box <-> telnet BBS |
|
||||
| `termdemo/` | `bin/termdemo.exe` | Standalone encrypted terminal demo |
|
||||
|
||||
|
||||
## Building and Deploying
|
||||
## Build
|
||||
|
||||
Requires the DJGPP cross-compiler at `~/djgpp/djgpp`.
|
||||
|
||||
```
|
||||
make # build everything
|
||||
make clean # remove all build artifacts
|
||||
./mkcd.sh # build and produce a CD-ROM ISO image
|
||||
./mkcd.sh # build + create ISO for 86Box
|
||||
```
|
||||
|
||||
`make` builds in dependency order: core, tasks, loader, helpers,
|
||||
host tools, widgets, shell, task manager, apps. Build output goes to
|
||||
`bin/` and intermediate objects to `obj/`.
|
||||
|
||||
`./mkcd.sh` builds everything, then wraps `bin/` into an ISO 9660
|
||||
image for use with an emulator or for burning to physical media.
|
||||
|
||||
|
||||
## Directory Layout
|
||||
The top-level Makefile builds in dependency order:
|
||||
|
||||
```
|
||||
dvxgui/
|
||||
assets/ logo and splash artwork
|
||||
bin/ build output (runtime tree; mirrors what ships)
|
||||
config/ shipped INI, themes, wallpapers
|
||||
docs/ HTML exports of the built-in help content
|
||||
LICENSE.txt MIT license
|
||||
Makefile top-level build
|
||||
mkcd.sh build-and-package script
|
||||
src/
|
||||
apps/ bundled applications
|
||||
include/ shared include files (BASIC library declarations)
|
||||
libs/ core libraries (GUI, tasks, shell, helpers, serial, SQL)
|
||||
loader/ bootstrap executable
|
||||
tools/ host-native development tools and the BASIC compiler
|
||||
widgets/ widget plug-in modules
|
||||
core -> tasks -> loader -> texthelp -> listhelp -> widgets -> shell -> taskmgr -> apps
|
||||
tools (host native, parallel)
|
||||
```
|
||||
|
||||
See `src/tools/README.md` for the host-side development tools
|
||||
(resource tool, icon generators, help compiler, proxy), and
|
||||
`src/apps/kpunch/README.md` for the SDK contract applications must
|
||||
follow.
|
||||
Build output goes to `bin/` (executables, DXE modules, config) and
|
||||
`obj/` (intermediate object files).
|
||||
|
||||
|
||||
## Documentation
|
||||
## Runtime Directory Layout (bin/)
|
||||
|
||||
Full reference documentation ships with the system as browsable help:
|
||||
|
||||
* `docs/dvx_system_reference.html` -- system API, widget catalog
|
||||
* `docs/dvx_basic_reference.html` -- DVX BASIC language reference
|
||||
* `docs/dvx_help_viewer.html` -- help viewer guide
|
||||
|
||||
The same content is available from inside DVX via the Help Viewer and
|
||||
Program Manager.
|
||||
```
|
||||
bin/
|
||||
dvx.exe bootstrap loader (entry point)
|
||||
libs/
|
||||
libtasks.lib task switching library
|
||||
libdvx.lib core GUI library
|
||||
texthelp.lib text editing helpers
|
||||
listhelp.lib list/dropdown helpers
|
||||
dvxshell.lib DVX shell
|
||||
taskmgr.lib task manager (Ctrl+Esc)
|
||||
*.dep dependency files for load ordering
|
||||
widgets/
|
||||
box.wgt VBox/HBox/Frame containers
|
||||
button.wgt push button
|
||||
... (26 widget modules total)
|
||||
*.dep dependency files
|
||||
apps/
|
||||
progman/progman.app Program Manager (desktop)
|
||||
notepad/notepad.app text editor
|
||||
clock/clock.app clock display
|
||||
dvxdemo/dvxdemo.app widget showcase
|
||||
cpanel/cpanel.app control panel
|
||||
imgview/imgview.app image viewer
|
||||
config/
|
||||
dvx.ini system configuration
|
||||
themes/ color theme files (.thm)
|
||||
wpaper/ wallpaper images
|
||||
```
|
||||
|
||||
|
||||
## License
|
||||
## Core Architecture (5 Layers)
|
||||
|
||||
MIT. See `LICENSE.txt`.
|
||||
1. **dvxVideo** -- VESA VBE init, LFB mapping, backbuffer, pixel format, color packing
|
||||
2. **dvxDraw** -- Rectangle fills, bevels, text rendering, cursor/icon drawing
|
||||
3. **dvxComp** -- Dirty rectangle tracking, merge, compositor, LFB flush
|
||||
4. **dvxWm** -- Window stack, chrome, drag/resize, menus, scrollbars, hit testing
|
||||
5. **dvxApp** -- Event loop, input polling, public API, color schemes, wallpaper
|
||||
|
||||
|
||||
## Widget System
|
||||
|
||||
Widgets are isolated DXE modules. Core knows nothing about individual
|
||||
widget types -- no compile-time enum, no union, no per-widget structs in
|
||||
dvxWidget.h.
|
||||
|
||||
* **Dynamic type IDs**: `wgtRegisterClass()` assigns IDs at load time
|
||||
* **void *data**: Each widget allocates its own private data struct
|
||||
* **ABI-stable dispatch**: `WidgetClassT.handlers[]` is an array of function
|
||||
pointers indexed by `WGT_METHOD_*` constants (0-20, room for 32). Core
|
||||
dispatches via `w->wclass->handlers[WGT_METHOD_PAINT]` etc., so adding
|
||||
new methods does not break existing widget DXE binaries
|
||||
* **Generic drag**: `WGT_METHOD_ON_DRAG_UPDATE` and `WGT_METHOD_ON_DRAG_END`
|
||||
provide widget-level drag support without per-widget hacks in core
|
||||
* **Per-widget API registry**: `wgtRegisterApi("name", &api)` replaces the monolithic API
|
||||
* **Per-widget headers**: `widgets/widgetButton.h` etc. provide typed macros
|
||||
* **Shared helpers**: texthelp.lib (text editing) and listhelp.lib (dropdown/list)
|
||||
* **All limits dynamic**: widget child arrays, app slots, and desktop callbacks
|
||||
are stb_ds dynamic arrays with no fixed maximums
|
||||
|
||||
|
||||
## DXE Module System
|
||||
|
||||
All modules are DXE3 dynamic libraries loaded by dvx.exe at startup.
|
||||
The loader recursively scans `libs/` for `.lib` files and `widgets/` for `.wgt`
|
||||
files, reads `.dep` files for dependencies, and loads in topological
|
||||
order. Widget modules that export `wgtRegister()` have it called after
|
||||
loading.
|
||||
|
||||
Each `.dep` file lists base names of modules that must load first, one
|
||||
per line. Comments start with `#`.
|
||||
|
||||
|
||||
## App Types
|
||||
|
||||
**Callback-only** (`hasMainLoop = false`): `appMain` creates windows,
|
||||
registers callbacks, and returns. The app lives through event callbacks
|
||||
in the shell's main loop. No dedicated task or stack needed.
|
||||
|
||||
**Main-loop** (`hasMainLoop = true`): A cooperative task is created for
|
||||
the app. `appMain` runs its own loop calling `tsYield()` to share CPU.
|
||||
Used for apps that need continuous processing (clocks, terminal
|
||||
emulators, games).
|
||||
|
||||
|
||||
## Crash Recovery
|
||||
|
||||
The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL
|
||||
before loading any apps, so even crashes during app initialization are
|
||||
caught. If an app crashes, the handler `longjmp`s back to the shell's
|
||||
main loop, the crashed app is force-killed, and the shell continues
|
||||
running. Diagnostic information (registers, faulting EIP) is logged to
|
||||
`dvx.log`.
|
||||
|
||||
|
||||
## Bundled Applications
|
||||
|
||||
| App | File | Type | Description |
|
||||
|-----------------|---------------|-----------|------------------------------------------------------------|
|
||||
| Program Manager | `progman.app` | Callback | App launcher grid, system info dialog |
|
||||
| Notepad | `notepad.app` | Callback | Text editor with File/Edit menus, open/save, clipboard |
|
||||
| Clock | `clock.app` | Main-loop | Digital clock display; multi-instance capable |
|
||||
| DVX Demo | `dvxdemo.app` | Callback | Widget system showcase demonstrating 31 widget types |
|
||||
| Control Panel | `cpanel.app` | Callback | Themes, wallpaper, video mode, mouse configuration |
|
||||
| Image Viewer | `imgview.app` | Callback | Displays BMP, PNG, JPEG, GIF images with aspect-ratio zoom |
|
||||
|
||||
|
||||
## Serial / Networking Stack
|
||||
|
||||
A layered encrypted serial communications stack for connecting DVX to
|
||||
remote systems (BBS, etc.) through 86Box's emulated UART:
|
||||
|
||||
| Layer | Library | Description |
|
||||
|----------|------------------|---------------------------------------------------|
|
||||
| rs232 | `librs232.a` | ISR-driven UART driver, ring buffers, flow control |
|
||||
| packet | `libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ |
|
||||
| security | `libsecurity.a` | 1024-bit DH, XTEA-CTR cipher, DRBG RNG |
|
||||
| seclink | `libseclink.a` | Channel multiplexing, per-packet encryption |
|
||||
| proxy | `secproxy` | Linux bridge: 86Box serial <-> telnet BBS |
|
||||
|
||||
|
||||
## Third-Party Dependencies
|
||||
|
||||
All third-party code is vendored as single-header libraries:
|
||||
|
||||
| Library | Location | Purpose |
|
||||
|-------------------|--------------------|---------------------------------|
|
||||
| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) |
|
||||
| stb_image_write.h | `core/thirdparty/` | PNG export for screenshots |
|
||||
| stb_ds.h | `core/thirdparty/` | Dynamic arrays and hash maps |
|
||||
|
||||
stb_ds implementation is compiled into dvx.exe (the loader) with
|
||||
`STBDS_REALLOC`/`STBDS_FREE` overridden to use `dvxRealloc`/`dvxFree`,
|
||||
so all `arrput`/`arrfree` calls in DXE code are tracked per-app. The
|
||||
functions are exported via `dlregsym` to all DXE modules.
|
||||
|
||||
|
||||
## Target Hardware
|
||||
|
||||
* CPU: 486 baseline, Pentium-optimized paths where significant
|
||||
* Video: VESA VBE 2.0+ with LFB (no bank switching)
|
||||
* Platform: 86Box emulator (trusted reference), real DOS hardware
|
||||
* Resolutions: 640x480, 800x600, 1024x768 at 8/15/16/32 bpp
|
||||
|
|
|
|||
54
analyze.sh
54
analyze.sh
|
|
@ -1,54 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# analyze.sh -- run the DJGPP build under gcc's -fanalyzer.
|
||||
#
|
||||
# Usage:
|
||||
# ./analyze.sh # analyse the whole build (slow: 2-5x)
|
||||
# ./analyze.sh 2>&1 | tee analyze.log
|
||||
#
|
||||
# How it works:
|
||||
#
|
||||
# The project Makefiles hardcode -Werror in CFLAGS. gcc evaluates
|
||||
# diagnostic flags in order, so a trailing -Wno-error (appended
|
||||
# AFTER CFLAGS at compile time) demotes analyzer findings back to
|
||||
# warnings -- otherwise the first analyzer hit aborts the build.
|
||||
#
|
||||
# We do that by setting CC to a wrapper that execs the real compiler
|
||||
# with -fanalyzer prepended and -Wno-error appended. make -k keeps
|
||||
# building after individual failures so we see every hit in one run.
|
||||
#
|
||||
# Runs the existing top-level Makefile with an override CC; no
|
||||
# Makefile edits required. Output goes to stderr; redirect with
|
||||
# '2>&1 | tee analyze.log' to capture.
|
||||
|
||||
set -u
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
REAL_CC="$HOME/djgpp/djgpp/bin/i586-pc-msdosdjgpp-gcc"
|
||||
|
||||
if [ ! -x "$REAL_CC" ]; then
|
||||
echo "analyze.sh: $REAL_CC not executable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the wrapper in a temp dir that the spawned makes can see.
|
||||
WRAP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WRAP_DIR"' EXIT
|
||||
WRAP="$WRAP_DIR/analyze-cc"
|
||||
|
||||
cat > "$WRAP" <<WRAPEOF
|
||||
#!/bin/bash
|
||||
exec "$REAL_CC" -fanalyzer "\$@" -Wno-error
|
||||
WRAPEOF
|
||||
|
||||
chmod +x "$WRAP"
|
||||
|
||||
echo "analyze.sh: running full build under gcc -fanalyzer..." >&2
|
||||
echo "analyze.sh: this will be slow (2-5x normal build time)." >&2
|
||||
|
||||
# -k = keep going after errors so we collect every analyzer hit
|
||||
# CC=... overrides the assignment in each submakefile
|
||||
make -k CC="$WRAP" 2>&1
|
||||
|
||||
echo "analyze.sh: done. Grep for 'Wanalyzer' to see findings." >&2
|
||||
107
apps/Makefile
Normal file
107
apps/Makefile
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# DVX Shell Applications Makefile -- builds DXE3 modules
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../widgets -I../tasks -I../core/thirdparty -I../shell
|
||||
|
||||
OBJDIR = ../obj/apps
|
||||
BINDIR = ../bin/apps
|
||||
DVXRES = ../bin/dvxres
|
||||
|
||||
# App definitions: each is a subdir with a single .c file
|
||||
APPS = progman notepad clock dvxdemo cpanel imgview
|
||||
|
||||
.PHONY: all clean $(APPS)
|
||||
|
||||
all: $(APPS)
|
||||
|
||||
cpanel: $(BINDIR)/cpanel/cpanel.app
|
||||
imgview: $(BINDIR)/imgview/imgview.app
|
||||
progman: $(BINDIR)/progman/progman.app
|
||||
notepad: $(BINDIR)/notepad/notepad.app
|
||||
clock: $(BINDIR)/clock/clock.app
|
||||
dvxdemo: $(BINDIR)/dvxdemo/dvxdemo.app
|
||||
|
||||
$(BINDIR)/cpanel/cpanel.app: $(OBJDIR)/cpanel.o cpanel/cpanel.res cpanel/icon32.bmp | $(BINDIR)/cpanel
|
||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||
$(DVXRES) build $@ cpanel/cpanel.res
|
||||
|
||||
$(BINDIR)/imgview/imgview.app: $(OBJDIR)/imgview.o imgview/imgview.res imgview/icon32.bmp | $(BINDIR)/imgview
|
||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||
$(DVXRES) build $@ imgview/imgview.res
|
||||
|
||||
$(BINDIR)/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/progman
|
||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||
|
||||
$(BINDIR)/notepad/notepad.app: $(OBJDIR)/notepad.o notepad/notepad.res notepad/icon32.bmp | $(BINDIR)/notepad
|
||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||
$(DVXRES) build $@ notepad/notepad.res
|
||||
|
||||
$(BINDIR)/clock/clock.app: $(OBJDIR)/clock.o clock/clock.res clock/icon32.bmp | $(BINDIR)/clock
|
||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $<
|
||||
$(DVXRES) build $@ clock/clock.res
|
||||
|
||||
DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp
|
||||
|
||||
$(BINDIR)/dvxdemo/dvxdemo.app: $(OBJDIR)/dvxdemo.o $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) dvxdemo/dvxdemo.res dvxdemo/icon32.bmp | $(BINDIR)/dvxdemo
|
||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||
$(DVXRES) build $@ dvxdemo/dvxdemo.res
|
||||
cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/dvxdemo/
|
||||
|
||||
$(OBJDIR)/cpanel.o: cpanel/cpanel.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/imgview.o: imgview/imgview.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/progman.o: progman/progman.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/notepad.o: notepad/notepad.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/clock.o: clock/clock.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(BINDIR)/cpanel:
|
||||
mkdir -p $(BINDIR)/cpanel
|
||||
|
||||
$(BINDIR)/imgview:
|
||||
mkdir -p $(BINDIR)/imgview
|
||||
|
||||
$(BINDIR)/progman:
|
||||
mkdir -p $(BINDIR)/progman
|
||||
|
||||
$(BINDIR)/notepad:
|
||||
mkdir -p $(BINDIR)/notepad
|
||||
|
||||
$(BINDIR)/clock:
|
||||
mkdir -p $(BINDIR)/clock
|
||||
|
||||
$(BINDIR)/dvxdemo:
|
||||
mkdir -p $(BINDIR)/dvxdemo
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/imgview.o: imgview/imgview.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxWidget.h ../core/dvxWm.h ../core/dvxVideo.h ../shell/shellApp.h
|
||||
$(OBJDIR)/cpanel.o: cpanel/cpanel.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxPrefs.h ../core/dvxWidget.h ../core/dvxWm.h ../core/platform/dvxPlatform.h ../shell/shellApp.h
|
||||
$(OBJDIR)/progman.o: progman/progman.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxWidget.h ../core/dvxWm.h ../shell/shellApp.h ../shell/shellInfo.h
|
||||
$(OBJDIR)/notepad.o: notepad/notepad.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxWidget.h ../core/dvxWm.h ../shell/shellApp.h
|
||||
$(OBJDIR)/clock.o: clock/clock.c ../core/dvxApp.h ../core/dvxWidget.h ../core/dvxDraw.h ../core/dvxVideo.h ../shell/shellApp.h ../tasks/taskswitch.h
|
||||
$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxWidget.h ../core/dvxWm.h ../core/dvxVideo.h ../shell/shellApp.h
|
||||
|
||||
clean:
|
||||
rm -f $(OBJDIR)/cpanel.o $(OBJDIR)/imgview.o $(OBJDIR)/progman.o $(OBJDIR)/notepad.o $(OBJDIR)/clock.o $(OBJDIR)/dvxdemo.o
|
||||
rm -f $(BINDIR)/cpanel/cpanel.app
|
||||
rm -f $(BINDIR)/imgview/imgview.app
|
||||
rm -f $(BINDIR)/progman/progman.app
|
||||
rm -f $(BINDIR)/notepad/notepad.app
|
||||
rm -f $(BINDIR)/clock/clock.app
|
||||
rm -f $(BINDIR)/dvxdemo/dvxdemo.app $(addprefix $(BINDIR)/dvxdemo/,$(DVXDEMO_BMPS))
|
||||
178
apps/README.md
Normal file
178
apps/README.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# DVX Shell Applications
|
||||
|
||||
DXE3 application modules for the DVX Shell. Each app is a `.app` file
|
||||
(DXE3 shared object) in a subdirectory under `apps/`. The Program
|
||||
Manager scans this directory at startup and displays all discovered
|
||||
apps as launchable icons.
|
||||
|
||||
|
||||
## DXE App Contract
|
||||
|
||||
Every app exports two symbols:
|
||||
|
||||
* `appDescriptor` (`AppDescriptorT`) -- name, hasMainLoop, multiInstance, stackSize, priority
|
||||
* `appMain` (`int appMain(DxeAppContextT *)`) -- entry point
|
||||
|
||||
Optional: `appShutdown` (`void appShutdown(void)`) -- called during
|
||||
graceful shutdown.
|
||||
|
||||
### Callback-Only Apps (hasMainLoop = false)
|
||||
|
||||
`appMain()` creates windows, registers callbacks, and returns 0. The
|
||||
shell drives everything through event callbacks. No dedicated task or
|
||||
stack is allocated. Lifecycle ends when the last window closes.
|
||||
|
||||
### Main-Loop Apps (hasMainLoop = true)
|
||||
|
||||
A cooperative task is created for the app. `appMain()` runs its own
|
||||
loop calling `tsYield()` to share CPU. Lifecycle ends when `appMain()`
|
||||
returns.
|
||||
|
||||
|
||||
## Applications
|
||||
|
||||
### Program Manager (progman)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| File | `apps/progman/progman.app` |
|
||||
| Type | Callback-only |
|
||||
| Multi-instance | No |
|
||||
|
||||
The desktop app. Scans `apps/` recursively for `.app` files and
|
||||
displays them in a grid. Double-click or Enter launches an app.
|
||||
Includes a Help menu with system information dialog and About box.
|
||||
|
||||
Registers with `shellRegisterDesktopUpdate()` to refresh when apps
|
||||
are loaded, crash, or terminate.
|
||||
|
||||
Widget headers used: `widgetBox.h`, `widgetButton.h`, `widgetLabel.h`,
|
||||
`widgetStatusBar.h`, `widgetTextInput.h`.
|
||||
|
||||
### Notepad (notepad)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| File | `apps/notepad/notepad.app` |
|
||||
| Type | Callback-only |
|
||||
| Multi-instance | Yes |
|
||||
|
||||
Text editor with File menu (New, Open, Save, Save As) and Edit menu
|
||||
(Cut, Copy, Paste, Select All). Uses the TextArea widget for all
|
||||
editing. 32KB text buffer limit. Tracks dirty state for save prompts.
|
||||
Keyboard accelerators for all menu commands.
|
||||
|
||||
Widget headers used: `widgetTextInput.h`.
|
||||
|
||||
### Clock (clock)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| File | `apps/clock/clock.app` |
|
||||
| Type | Main-loop |
|
||||
| Multi-instance | Yes |
|
||||
|
||||
Digital clock display showing time and date. Demonstrates the main-loop
|
||||
app pattern -- polls the system clock every second and invalidates the
|
||||
window to trigger a repaint. Uses raw `onPaint` callbacks (no widgets)
|
||||
to draw centered text.
|
||||
|
||||
Widget headers used: none (raw paint callbacks).
|
||||
|
||||
### DVX Demo (dvxdemo)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| File | `apps/dvxdemo/dvxdemo.app` |
|
||||
| Type | Callback-only |
|
||||
| Multi-instance | No |
|
||||
|
||||
Comprehensive widget system showcase. Opens multiple windows
|
||||
demonstrating 31 of the 32 widget types (all except Timer):
|
||||
|
||||
* Main window: raw paint callbacks (gradients, patterns, text)
|
||||
* Widget demo: form widgets (TextInput, Checkbox, Radio, ListBox)
|
||||
* Controls window: TabControl with tabs for advanced widgets
|
||||
(Dropdown, ProgressBar, Slider, Spinner, TreeView, ListView,
|
||||
ScrollPane, Toolbar, Canvas, Splitter, Image)
|
||||
* Terminal window: AnsiTerm widget
|
||||
|
||||
Widget headers used: 25 of the 26 widget headers (all except widgetTimer.h).
|
||||
|
||||
Resources: `logo.bmp`, `new.bmp`, `open.bmp`, `sample.bmp`, `save.bmp`.
|
||||
|
||||
### Control Panel (cpanel)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| File | `apps/cpanel/cpanel.app` |
|
||||
| Type | Callback-only |
|
||||
| Multi-instance | No |
|
||||
|
||||
System configuration with four tabs:
|
||||
|
||||
* **Mouse** -- scroll direction, double-click speed, acceleration
|
||||
* **Colors** -- all 20 system colors with live preview, theme load/save
|
||||
* **Desktop** -- wallpaper image selection and display mode
|
||||
* **Video** -- resolution and color depth switching
|
||||
|
||||
Changes preview live. OK saves to `DVX.INI`, Cancel reverts to the
|
||||
state captured when the control panel was opened.
|
||||
|
||||
Widget headers used: `widgetBox.h`, `widgetButton.h`, `widgetCanvas.h`,
|
||||
`widgetDropdown.h`, `widgetLabel.h`, `widgetListBox.h`, `widgetSlider.h`,
|
||||
`widgetSpacer.h`, `widgetTabControl.h`.
|
||||
|
||||
### Image Viewer (imgview)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| File | `apps/imgview/imgview.app` |
|
||||
| Type | Callback-only |
|
||||
| Multi-instance | Yes |
|
||||
|
||||
Displays BMP, PNG, JPEG, and GIF images. The image is scaled to fit
|
||||
the window while preserving aspect ratio. Resize the window to zoom.
|
||||
Open files via the File menu or by launching with Run in the Task
|
||||
Manager.
|
||||
|
||||
Widget headers used: none (raw paint callbacks with `dvxLoadImage()`).
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
make # builds all 6 app DXE modules
|
||||
make clean # removes objects and app files
|
||||
```
|
||||
|
||||
Each app compiles to a single `.o`, then is packaged via `dxe3gen`
|
||||
into a `.app` DXE exporting `appDescriptor` and `appMain` (plus
|
||||
`appShutdown` for clock).
|
||||
|
||||
Output goes to `bin/apps/<name>/<name>.app`.
|
||||
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
apps/
|
||||
Makefile top-level build for all apps
|
||||
progman/
|
||||
progman.c Program Manager
|
||||
notepad/
|
||||
notepad.c text editor
|
||||
clock/
|
||||
clock.c digital clock
|
||||
dvxdemo/
|
||||
dvxdemo.c widget demo
|
||||
logo.bmp DVX logo bitmap
|
||||
new.bmp toolbar icon
|
||||
open.bmp toolbar icon
|
||||
sample.bmp sample image
|
||||
save.bmp toolbar icon
|
||||
cpanel/
|
||||
cpanel.c control panel
|
||||
imgview/
|
||||
imgview.c image viewer
|
||||
```
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// clock.c -- Clock DXE application (main-loop with tsYield)
|
||||
//
|
||||
// This is a main-loop DXE app (hasMainLoop = true), in contrast to callback-
|
||||
|
|
@ -41,11 +19,11 @@
|
|||
// would starve the shell and all other apps.
|
||||
|
||||
#include "dvxApp.h"
|
||||
#include "dvxWgt.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxVideo.h"
|
||||
#include "shellApp.h"
|
||||
#include "taskSwch.h"
|
||||
#include "taskswitch.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
|
@ -99,15 +77,9 @@ AppDescriptorT appDescriptor = {
|
|||
.priority = TS_PRIORITY_LOW
|
||||
};
|
||||
|
||||
|
||||
// The shell calls appShutdown (if exported) when force-killing an app via
|
||||
// Task Manager or during shell shutdown. For main-loop apps, this is the
|
||||
// signal to break out of the main loop. Without this, shellForceKillApp
|
||||
// would have to terminate the task forcibly, potentially leaking resources.
|
||||
void appShutdown(void) {
|
||||
sState.quit = true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Callbacks (fire in task 0 during dvxUpdate)
|
||||
// ============================================================
|
||||
|
||||
// Setting quit = true tells the main loop (running in a separate task) to
|
||||
// exit. The main loop then destroys the window and returns from appMain,
|
||||
|
|
@ -163,6 +135,9 @@ static void onPaint(WindowT *win, RectT *dirty) {
|
|||
drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Time update
|
||||
// ============================================================
|
||||
|
||||
static void updateTime(void) {
|
||||
time_t now = time(NULL);
|
||||
|
|
@ -182,11 +157,26 @@ static void updateTime(void) {
|
|||
hour12 = 12;
|
||||
}
|
||||
|
||||
snprintf(sState.timeStr, sizeof(sState.timeStr), "%d:%02d:%02d %s", (int)hour12, tm->tm_min, tm->tm_sec, ampm);
|
||||
snprintf(sState.timeStr, sizeof(sState.timeStr), "%d:%02d:%02d %s", hour12, tm->tm_min, tm->tm_sec, ampm);
|
||||
snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
|
||||
sState.lastUpdate = now;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Shutdown hook (optional DXE export)
|
||||
// ============================================================
|
||||
|
||||
// The shell calls appShutdown (if exported) when force-killing an app via
|
||||
// Task Manager or during shell shutdown. For main-loop apps, this is the
|
||||
// signal to break out of the main loop. Without this, shellForceKillApp
|
||||
// would have to terminate the task forcibly, potentially leaking resources.
|
||||
void appShutdown(void) {
|
||||
sState.quit = true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Entry point (runs in its own task)
|
||||
// ============================================================
|
||||
|
||||
// This runs in its own task. The shell creates the task before calling
|
||||
// appMain, and reaps it when appMain returns.
|
||||
|
|
@ -213,8 +203,10 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
sWin->onClose = onClose;
|
||||
sWin->onPaint = onPaint;
|
||||
|
||||
// Initial paint -- dvxInvalidateWindow calls onPaint automatically
|
||||
dvxInvalidateWindow(ac, sWin);
|
||||
|
||||
// Main loop: check if the second has changed, repaint if so, then yield.
|
||||
// First paint happens automatically on the next dvxUpdate frame.
|
||||
// tsYield() transfers control back to the shell's task scheduler.
|
||||
// On a 486, time() resolution is 1 second, so we yield many times per
|
||||
// second between actual updates -- this keeps CPU usage near zero.
|
||||
5
apps/clock/clock.res
Normal file
5
apps/clock/clock.res
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# clock.res -- Resource manifest for Clock
|
||||
icon32 icon clock/icon32.bmp
|
||||
name text "Clock"
|
||||
author text "DVX Project"
|
||||
description text "Digital clock with date display"
|
||||
File diff suppressed because it is too large
Load diff
5
apps/cpanel/cpanel.res
Normal file
5
apps/cpanel/cpanel.res
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# cpanel.res -- Resource manifest for Control Panel
|
||||
icon32 icon cpanel/icon32.bmp
|
||||
name text "Control Panel"
|
||||
author text "DVX Project"
|
||||
description text "System settings and preferences"
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvxdemo.c -- DVX GUI demonstration app (DXE version)
|
||||
//
|
||||
// Callback-only DXE app (hasMainLoop = false) that opens several windows
|
||||
|
|
@ -37,33 +15,33 @@
|
|||
// The app has no persistent state -- it's purely a showcase.
|
||||
|
||||
#include "dvxApp.h"
|
||||
#include "dvxDlg.h"
|
||||
#include "dvxWgt.h"
|
||||
#include "ansiTerm/ansiTerm.h"
|
||||
#include "box/box.h"
|
||||
#include "button/button.h"
|
||||
#include "canvas/canvas.h"
|
||||
#include "checkbox/checkbox.h"
|
||||
#include "comboBox/comboBox.h"
|
||||
#include "dropdown/dropdown.h"
|
||||
#include "image/image.h"
|
||||
#include "imageButton/imgBtn.h"
|
||||
#include "label/label.h"
|
||||
#include "listBox/listBox.h"
|
||||
#include "listView/listView.h"
|
||||
#include "progressBar/progress.h"
|
||||
#include "radio/radio.h"
|
||||
#include "scrollPane/scrlPane.h"
|
||||
#include "separator/separatr.h"
|
||||
#include "slider/slider.h"
|
||||
#include "spacer/spacer.h"
|
||||
#include "spinner/spinner.h"
|
||||
#include "splitter/splitter.h"
|
||||
#include "statusBar/statBar.h"
|
||||
#include "tabControl/tabCtrl.h"
|
||||
#include "textInput/textInpt.h"
|
||||
#include "toolbar/toolbar.h"
|
||||
#include "treeView/treeView.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetAnsiTerm.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
#include "widgetCanvas.h"
|
||||
#include "widgetCheckbox.h"
|
||||
#include "widgetComboBox.h"
|
||||
#include "widgetDropdown.h"
|
||||
#include "widgetImage.h"
|
||||
#include "widgetImageButton.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetListBox.h"
|
||||
#include "widgetListView.h"
|
||||
#include "widgetProgressBar.h"
|
||||
#include "widgetRadio.h"
|
||||
#include "widgetScrollPane.h"
|
||||
#include "widgetSeparator.h"
|
||||
#include "widgetSlider.h"
|
||||
#include "widgetSpacer.h"
|
||||
#include "widgetSpinner.h"
|
||||
#include "widgetSplitter.h"
|
||||
#include "widgetStatusBar.h"
|
||||
#include "widgetTabControl.h"
|
||||
#include "widgetTextInput.h"
|
||||
#include "widgetToolbar.h"
|
||||
#include "widgetTreeView.h"
|
||||
#include "dvxWm.h"
|
||||
#include "shellApp.h"
|
||||
|
||||
|
|
@ -108,26 +86,6 @@
|
|||
#define CMD_CTX_SELALL 504
|
||||
#define CMD_CTX_PROPS 505
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
// ============================================================
|
||||
|
||||
static AppContextT *sAc = NULL;
|
||||
static DxeAppContextT *sDxeCtx = NULL;
|
||||
|
||||
static const FileFilterT sFileFilters[] = {
|
||||
{"All Files (*.*)"},
|
||||
{"Text Files (*.txt)"},
|
||||
{"Batch Files (*.bat)"},
|
||||
{"Executables (*.exe)"},
|
||||
{"Bitmap Files (*.bmp)"}
|
||||
};
|
||||
|
||||
// Item arrays are static because dropdown/combobox widgets store pointers,
|
||||
// not copies. They must outlive the widgets that reference them.
|
||||
static const char *colorItems[] = {"Red", "Green", "Blue", "Yellow", "Cyan", "Magenta"};
|
||||
static const char *sizeItems[] = {"Small", "Medium", "Large", "Extra Large"};
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
|
@ -147,6 +105,13 @@ static void setupMainWindow(void);
|
|||
static void setupTerminalWindow(void);
|
||||
static void setupWidgetDemo(void);
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
// ============================================================
|
||||
|
||||
static AppContextT *sAc = NULL;
|
||||
static DxeAppContextT *sDxeCtx = NULL;
|
||||
|
||||
// ============================================================
|
||||
// App descriptor
|
||||
// ============================================================
|
||||
|
|
@ -161,6 +126,9 @@ AppDescriptorT appDescriptor = {
|
|||
.priority = TS_PRIORITY_NORMAL
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Callbacks
|
||||
// ============================================================
|
||||
|
||||
static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag) {
|
||||
(void)drag;
|
||||
|
|
@ -184,27 +152,35 @@ static void onCloseMainCb(WindowT *win) {
|
|||
}
|
||||
|
||||
|
||||
static const FileFilterT sFileFilters[] = {
|
||||
{"All Files (*.*)", "*.*"},
|
||||
{"Text Files (*.txt)", "*.txt"},
|
||||
{"Batch Files (*.bat)", "*.bat"},
|
||||
{"Executables (*.exe)", "*.exe"},
|
||||
{"Bitmap Files (*.bmp)", "*.bmp"}
|
||||
};
|
||||
|
||||
static void onMenuCb(WindowT *win, int32_t menuId) {
|
||||
switch (menuId) {
|
||||
case CMD_FILE_OPEN: {
|
||||
char path[DVX_MAX_PATH];
|
||||
char path[260];
|
||||
|
||||
if (dvxFileDialog(sAc, "Open File", FD_OPEN, NULL, sFileFilters, 5, path, sizeof(path))) {
|
||||
char msg[300];
|
||||
snprintf(msg, sizeof(msg), "Selected: %s", path);
|
||||
dvxInfoBox(sAc, "Open", msg);
|
||||
dvxMessageBox(sAc, "Open", msg, MB_OK | MB_ICONINFO);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_FILE_SAVE: {
|
||||
char path[DVX_MAX_PATH];
|
||||
char path[260];
|
||||
|
||||
if (dvxFileDialog(sAc, "Save As", FD_SAVE, NULL, sFileFilters, 5, path, sizeof(path))) {
|
||||
char msg[300];
|
||||
snprintf(msg, sizeof(msg), "Save to: %s", path);
|
||||
dvxInfoBox(sAc, "Save", msg);
|
||||
dvxMessageBox(sAc, "Save", msg, MB_OK | MB_ICONINFO);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -246,33 +222,34 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
|
|||
break;
|
||||
|
||||
case CMD_HELP_ABOUT:
|
||||
dvxInfoBox(sAc, "About DVX Demo",
|
||||
dvxMessageBox(sAc, "About DVX Demo",
|
||||
"DVX GUI Demonstration\n"
|
||||
"A DOS Visual eXecutive windowing system for DOS.");
|
||||
"A DOS Visual eXecutive windowing system for DOS.",
|
||||
MB_OK | MB_ICONINFO);
|
||||
break;
|
||||
|
||||
case CMD_CTX_CUT:
|
||||
dvxInfoBox(sAc, "Context Menu", "Cut selected.");
|
||||
dvxMessageBox(sAc, "Context Menu", "Cut selected.", MB_OK | MB_ICONINFO);
|
||||
break;
|
||||
|
||||
case CMD_CTX_COPY:
|
||||
dvxInfoBox(sAc, "Context Menu", "Copied to clipboard.");
|
||||
dvxMessageBox(sAc, "Context Menu", "Copied to clipboard.", MB_OK | MB_ICONINFO);
|
||||
break;
|
||||
|
||||
case CMD_CTX_PASTE:
|
||||
dvxInfoBox(sAc, "Context Menu", "Pasted from clipboard.");
|
||||
dvxMessageBox(sAc, "Context Menu", "Pasted from clipboard.", MB_OK | MB_ICONINFO);
|
||||
break;
|
||||
|
||||
case CMD_CTX_DELETE:
|
||||
dvxInfoBox(sAc, "Context Menu", "Deleted.");
|
||||
dvxMessageBox(sAc, "Context Menu", "Deleted.", MB_OK | MB_ICONINFO);
|
||||
break;
|
||||
|
||||
case CMD_CTX_SELALL:
|
||||
dvxInfoBox(sAc, "Context Menu", "Selected all.");
|
||||
dvxMessageBox(sAc, "Context Menu", "Selected all.", MB_OK | MB_ICONINFO);
|
||||
break;
|
||||
|
||||
case CMD_CTX_PROPS:
|
||||
dvxInfoBox(sAc, "Properties", "Window properties dialog would appear here.");
|
||||
dvxMessageBox(sAc, "Properties", "Window properties dialog would appear here.", MB_OK | MB_ICONINFO);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -384,8 +361,9 @@ static void onPaintText(WindowT *win, RectT *dirtyArea) {
|
|||
static const char *lines[] = {
|
||||
"DVX GUI Compositor",
|
||||
"",
|
||||
"A DOS Visual eXecutive windowed",
|
||||
"GUI compositor for DOS.",
|
||||
"A DOS Visual eXecutive windowed GUI",
|
||||
"compositor for DOS, targeting",
|
||||
"DJGPP/DPMI.",
|
||||
"",
|
||||
"Features:",
|
||||
" - VESA VBE 2.0+ LFB",
|
||||
|
|
@ -468,6 +446,15 @@ static void onToolbarClick(WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setupControlsWindow -- advanced widgets with tabs
|
||||
// ============================================================
|
||||
|
||||
// Item arrays are static because dropdown/combobox widgets store pointers,
|
||||
// not copies. They must outlive the widgets that reference them.
|
||||
static const char *colorItems[] = {"Red", "Green", "Blue", "Yellow", "Cyan", "Magenta"};
|
||||
static const char *sizeItems[] = {"Small", "Medium", "Large", "Extra Large"};
|
||||
|
||||
// Demonstrates every advanced widget type, organized into tabs. The TabControl
|
||||
// widget manages tab pages; each page is a container that shows/hides based
|
||||
// on the selected tab. This is the densest widget test in the project.
|
||||
|
|
@ -507,7 +494,7 @@ static void setupControlsWindow(void) {
|
|||
wgtLabel(page1, "&Progress:");
|
||||
WidgetT *pbRow = wgtHBox(page1);
|
||||
WidgetT *pb = wgtProgressBar(pbRow);
|
||||
pb->weight = WGT_WEIGHT_FILL;
|
||||
pb->weight = 100;
|
||||
wgtProgressBarSetValue(pb, 65);
|
||||
wgtSetTooltip(pb, "Task progress: 65%");
|
||||
WidgetT *pbV = wgtProgressBarV(pbRow);
|
||||
|
|
@ -576,13 +563,13 @@ static void setupControlsWindow(void) {
|
|||
wgtListViewSetSelected(lv, 0);
|
||||
wgtListViewSetMultiSelect(lv, true);
|
||||
wgtListViewSetReorderable(lv, true);
|
||||
lv->weight = WGT_WEIGHT_FILL;
|
||||
lv->weight = 100;
|
||||
|
||||
// --- Tab 4: ScrollPane ---
|
||||
WidgetT *page4sp = wgtTabPage(tabs, "&Scroll");
|
||||
|
||||
WidgetT *sp = wgtScrollPane(page4sp);
|
||||
sp->weight = WGT_WEIGHT_FILL;
|
||||
sp->weight = 100;
|
||||
sp->padding = wgtPixels(4);
|
||||
sp->spacing = wgtPixels(4);
|
||||
|
||||
|
|
@ -670,7 +657,7 @@ static void setupControlsWindow(void) {
|
|||
|
||||
wgtLabel(page7e, "TextArea:");
|
||||
WidgetT *ta = wgtTextArea(page7e, 512);
|
||||
ta->weight = WGT_WEIGHT_FILL;
|
||||
ta->weight = 100;
|
||||
wgtSetText(ta, "Multi-line text editor.\n\nFeatures:\n- Word wrap\n- Selection\n- Copy/Paste\n- Undo (Ctrl+Z)");
|
||||
|
||||
wgtHSeparator(page7e);
|
||||
|
|
@ -694,7 +681,7 @@ static void setupControlsWindow(void) {
|
|||
WidgetT *page8s = wgtTabPage(tabs, "S&plit");
|
||||
|
||||
WidgetT *hSplit = wgtSplitter(page8s, false);
|
||||
hSplit->weight = WGT_WEIGHT_FILL;
|
||||
hSplit->weight = 100;
|
||||
wgtSplitterSetPos(hSplit, 120);
|
||||
|
||||
// Top pane: vertical splitter (tree | list)
|
||||
|
|
@ -734,11 +721,11 @@ static void setupControlsWindow(void) {
|
|||
// Tests that wgtSetEnabled(w, false) correctly greys out each widget type.
|
||||
WidgetT *page9d = wgtTabPage(tabs, "&Disabled");
|
||||
WidgetT *disRow = wgtHBox(page9d);
|
||||
disRow->weight = WGT_WEIGHT_FILL;
|
||||
disRow->weight = 100;
|
||||
|
||||
// Enabled column
|
||||
WidgetT *enCol = wgtVBox(disRow);
|
||||
enCol->weight = WGT_WEIGHT_FILL;
|
||||
enCol->weight = 100;
|
||||
wgtLabel(enCol, "Enabled:");
|
||||
wgtHSeparator(enCol);
|
||||
wgtLabel(enCol, "A &label");
|
||||
|
|
@ -772,7 +759,7 @@ static void setupControlsWindow(void) {
|
|||
|
||||
// Disabled column
|
||||
WidgetT *disCol = wgtVBox(disRow);
|
||||
disCol->weight = WGT_WEIGHT_FILL;
|
||||
disCol->weight = 100;
|
||||
wgtLabel(disCol, "Disabled:");
|
||||
wgtHSeparator(disCol);
|
||||
|
||||
|
|
@ -817,12 +804,18 @@ static void setupControlsWindow(void) {
|
|||
// Status bar at bottom (outside tabs)
|
||||
WidgetT *sb = wgtStatusBar(root);
|
||||
WidgetT *sbLabel = wgtLabel(sb, "Ready");
|
||||
sbLabel->weight = WGT_WEIGHT_FILL;
|
||||
sbLabel->weight = 100;
|
||||
wgtSetName(sbLabel, "advStatus");
|
||||
wgtLabel(sb, "Line 1, Col 1");
|
||||
|
||||
wgtInvalidate(root);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setupMainWindow -- info window + paint demos
|
||||
// ============================================================
|
||||
|
||||
// Creates three windows that demonstrate raw onPaint rendering (no widgets):
|
||||
// 1. Text info window with full menu bar, accelerators, and context menu
|
||||
// 2. Color gradient window
|
||||
|
|
@ -960,6 +953,10 @@ static void setupMainWindow(void) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setupTerminalWindow -- ANSI terminal widget demo
|
||||
// ============================================================
|
||||
|
||||
// Creates an ANSI terminal widget with sample output demonstrating all
|
||||
// supported attributes (bold, reverse, blink), 8+8 colors, background
|
||||
// colors, and CP437 box-drawing/graphic characters. The terminal is
|
||||
|
|
@ -976,7 +973,7 @@ static void setupTerminalWindow(void) {
|
|||
WidgetT *root = wgtInitWindow(sAc, win);
|
||||
WidgetT *term = wgtAnsiTerm(root, 80, 25);
|
||||
|
||||
term->weight = WGT_WEIGHT_FILL;
|
||||
term->weight = 100;
|
||||
wgtAnsiTermSetScrollback(term, 500);
|
||||
|
||||
// Feed some ANSI content to demonstrate the terminal
|
||||
|
|
@ -1039,6 +1036,10 @@ static void setupTerminalWindow(void) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setupWidgetDemo -- form with accelerators
|
||||
// ============================================================
|
||||
|
||||
// Demonstrates the standard form pattern: labeled inputs in frames, checkbox
|
||||
// and radio groups, list boxes (single and multi-select with context menus),
|
||||
// and a button row. The '&' in label text marks the Alt+key mnemonic that
|
||||
|
|
@ -1099,7 +1100,7 @@ static void setupWidgetDemo(void) {
|
|||
WidgetT *lb = wgtListBox(listRow);
|
||||
wgtListBoxSetItems(lb, listItems, 5);
|
||||
wgtListBoxSetReorderable(lb, true);
|
||||
lb->weight = WGT_WEIGHT_FILL;
|
||||
lb->weight = 100;
|
||||
|
||||
// Context menu on the list box
|
||||
MenuT *lbCtx = wmCreateMenu();
|
||||
|
|
@ -1115,7 +1116,7 @@ static void setupWidgetDemo(void) {
|
|||
WidgetT *mlb = wgtListBox(listRow);
|
||||
wgtListBoxSetMultiSelect(mlb, true);
|
||||
wgtListBoxSetItems(mlb, multiItems, 7);
|
||||
mlb->weight = WGT_WEIGHT_FILL;
|
||||
mlb->weight = 100;
|
||||
|
||||
wgtHSeparator(root);
|
||||
|
||||
|
|
@ -1125,9 +1126,15 @@ static void setupWidgetDemo(void) {
|
|||
WidgetT *okBtn = wgtButton(btnRow, "&OK");
|
||||
okBtn->onClick = onOkClick;
|
||||
wgtButton(btnRow, "&Cancel");
|
||||
|
||||
wgtInvalidate(root);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Entry point
|
||||
// ============================================================
|
||||
|
||||
// Create all demo windows at once. Because this is a callback-only app,
|
||||
// appMain returns immediately and the shell's event loop takes over.
|
||||
// Each window is self-contained with its own callbacks.
|
||||
5
apps/dvxdemo/dvxdemo.res
Normal file
5
apps/dvxdemo/dvxdemo.res
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# dvxdemo.res -- Resource manifest for DVX Demo
|
||||
icon32 icon dvxdemo/icon32.bmp
|
||||
name text "DVX Demo"
|
||||
author text "DVX Project"
|
||||
description text "Widget toolkit demonstration"
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// imgview.c -- DVX Image Viewer
|
||||
//
|
||||
// Displays BMP, PNG, JPG, and GIF images. The image is scaled to fit
|
||||
|
|
@ -27,11 +5,11 @@
|
|||
// Open files via the File menu or by launching with Run in the Task Manager.
|
||||
|
||||
#include "dvxApp.h"
|
||||
#include "dvxDlg.h"
|
||||
#include "dvxWgt.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "dvxWm.h"
|
||||
#include "shellApp.h"
|
||||
#include "stb_image_wrap.h"
|
||||
#include "stb_image.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -203,12 +181,8 @@ static void loadAndDisplay(const char *path) {
|
|||
|
||||
dvxSetBusy(sAc, true);
|
||||
|
||||
int imgW;
|
||||
int imgH;
|
||||
int channels;
|
||||
sImgRgb = stbi_load(path, &imgW, &imgH, &channels, 3);
|
||||
sImgW = imgW;
|
||||
sImgH = imgH;
|
||||
int32_t channels;
|
||||
sImgRgb = stbi_load(path, &sImgW, &sImgH, &channels, 3);
|
||||
|
||||
if (!sImgRgb) {
|
||||
dvxSetBusy(sAc, false);
|
||||
|
|
@ -226,7 +200,7 @@ static void loadAndDisplay(const char *path) {
|
|||
|
||||
fname = fname ? fname + 1 : path;
|
||||
|
||||
char title[280];
|
||||
char title[128];
|
||||
snprintf(title, sizeof(title), "%s - Image Viewer", fname);
|
||||
dvxSetTitle(sAc, sWin, title);
|
||||
|
||||
|
|
@ -234,6 +208,9 @@ static void loadAndDisplay(const char *path) {
|
|||
buildScaled(sWin->contentW, sWin->contentH);
|
||||
dvxSetBusy(sAc, false);
|
||||
|
||||
RectT fullRect = {0, 0, sWin->contentW, sWin->contentH};
|
||||
sWin->onPaint(sWin, &fullRect);
|
||||
sWin->contentDirty = true;
|
||||
dvxInvalidateWindow(sAc, sWin);
|
||||
}
|
||||
|
||||
|
|
@ -350,10 +327,10 @@ static void onResize(WindowT *win, int32_t contentW, int32_t contentH) {
|
|||
|
||||
static void openFile(void) {
|
||||
FileFilterT filters[] = {
|
||||
{ "Images (*.bmp;*.jpg;*.png;*.gif)" },
|
||||
{ "All Files (*.*)" }
|
||||
{ "Images (*.bmp;*.jpg;*.png;*.gif)", "*.bmp;*.jpg;*.png;*.gif" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
char path[260];
|
||||
|
||||
if (dvxFileDialog(sAc, "Open Image", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
|
||||
loadAndDisplay(path);
|
||||
|
|
@ -395,7 +372,7 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
// Initial paint (dark background)
|
||||
RectT fullRect = {0, 0, sWin->contentW, sWin->contentH};
|
||||
onPaint(sWin, &fullRect);
|
||||
sWin->iconNeedsRefresh = true;
|
||||
sWin->contentDirty = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
5
apps/imgview/imgview.res
Normal file
5
apps/imgview/imgview.res
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# imgview.res -- Resource manifest for Image Viewer
|
||||
icon32 icon imgview/icon32.bmp
|
||||
name text "Image Viewer"
|
||||
author text "DVX Project"
|
||||
description text "BMP, PNG, JPEG, and GIF viewer"
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// notepad.c -- Simple text editor DXE application (callback-only)
|
||||
//
|
||||
// A callback-only DXE app (hasMainLoop = false) that provides basic text
|
||||
|
|
@ -33,11 +11,11 @@
|
|||
// up menus and file I/O around it.
|
||||
|
||||
#include "dvxApp.h"
|
||||
#include "dvxDlg.h"
|
||||
#include "dvxWgt.h"
|
||||
#include "textInput/textInpt.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetTextInput.h"
|
||||
#include "dvxWm.h"
|
||||
#include "dvxPlat.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "shellApp.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
|
@ -77,7 +55,7 @@
|
|||
static DxeAppContextT *sCtx = NULL;
|
||||
static WindowT *sWin = NULL;
|
||||
static WidgetT *sTextArea = NULL;
|
||||
static char sFilePath[DVX_MAX_PATH] = "";
|
||||
static char sFilePath[260] = "";
|
||||
// Hash of text content at last save/open, used for cheap dirty detection
|
||||
static uint32_t sCleanHash = 0;
|
||||
|
||||
|
|
@ -91,6 +69,7 @@ static void doNew(void);
|
|||
static void doOpen(void);
|
||||
static void doSave(void);
|
||||
static void doSaveAs(void);
|
||||
static uint32_t hashText(const char *text);
|
||||
static bool isDirty(void);
|
||||
static void markClean(void);
|
||||
static void onClose(WindowT *win);
|
||||
|
|
@ -114,18 +93,35 @@ AppDescriptorT appDescriptor = {
|
|||
// Dirty tracking
|
||||
// ============================================================
|
||||
|
||||
// Dirty tracking uses dvxTextHash from dvxApp.h.
|
||||
// djb2-xor hash for dirty detection. Not cryptographic -- just a fast way
|
||||
// to detect changes without storing a full copy of the last-saved text.
|
||||
// False negatives are theoretically possible but vanishingly unlikely for
|
||||
// text edits. This avoids the memory cost of keeping a shadow buffer.
|
||||
static uint32_t hashText(const char *text) {
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t h = 5381;
|
||||
|
||||
while (*text) {
|
||||
h = ((h << 5) + h) ^ (uint8_t)*text;
|
||||
text++;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
static bool isDirty(void) {
|
||||
const char *text = wgtGetText(sTextArea);
|
||||
return dvxTextHash(text) != sCleanHash;
|
||||
return hashText(text) != sCleanHash;
|
||||
}
|
||||
|
||||
|
||||
static void markClean(void) {
|
||||
const char *text = wgtGetText(sTextArea);
|
||||
sCleanHash = dvxTextHash(text);
|
||||
sCleanHash = hashText(text);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -172,10 +168,10 @@ static void doOpen(void) {
|
|||
}
|
||||
|
||||
FileFilterT filters[] = {
|
||||
{ "Text Files (*.txt)" },
|
||||
{ "All Files (*.*)" }
|
||||
{ "Text Files (*.txt)", "*.txt" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
char path[260];
|
||||
|
||||
if (!dvxFileDialog(sCtx->shellCtx, "Open", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
|
||||
return;
|
||||
|
|
@ -258,10 +254,10 @@ static void doSave(void) {
|
|||
|
||||
static void doSaveAs(void) {
|
||||
FileFilterT filters[] = {
|
||||
{ "Text Files (*.txt)" },
|
||||
{ "All Files (*.*)" }
|
||||
{ "Text Files (*.txt)", "*.txt" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
char path[260];
|
||||
|
||||
if (!dvxFileDialog(sCtx->shellCtx, "Save As", FD_SAVE, NULL, filters, 2, path, sizeof(path))) {
|
||||
return;
|
||||
|
|
@ -400,5 +396,6 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
sFilePath[0] = '\0';
|
||||
markClean();
|
||||
|
||||
wgtInvalidate(root);
|
||||
return 0;
|
||||
}
|
||||
5
apps/notepad/notepad.res
Normal file
5
apps/notepad/notepad.res
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# notepad.res -- Resource manifest for Notepad
|
||||
icon32 icon notepad/icon32.bmp
|
||||
name text "Notepad"
|
||||
author text "DVX Project"
|
||||
description text "Simple text editor"
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// progman.c -- Program Manager application for DVX Shell
|
||||
//
|
||||
// Displays a grid of available apps from the apps/ directory.
|
||||
|
|
@ -41,38 +19,41 @@
|
|||
// list current without polling.
|
||||
|
||||
#include "dvxApp.h"
|
||||
#include "dvxDlg.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxPrefs.h"
|
||||
#include "dvxWgt.h"
|
||||
#include "box/box.h"
|
||||
#include "button/button.h"
|
||||
#include "label/label.h"
|
||||
#include "statusBar/statBar.h"
|
||||
#include "textInput/textInpt.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetStatusBar.h"
|
||||
#include "widgetTextInput.h"
|
||||
#include "dvxWm.h"
|
||||
#include "dvxPlat.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "shellApp.h"
|
||||
#include "shellInf.h"
|
||||
#include "dvxRes.h"
|
||||
#include "imageButton/imgBtn.h"
|
||||
#include "scrollPane/scrlPane.h"
|
||||
#include "wrapBox/wrapBox.h"
|
||||
#include "shellInfo.h"
|
||||
#include "dvxResource.h"
|
||||
#include "widgetImageButton.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include "dvxMem.h"
|
||||
#include "stb_ds_wrap.h"
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
// 64 entries is generous; limited by screen real estate before this cap
|
||||
#define MAX_APP_FILES 64
|
||||
// DOS 8.3 paths are short, but long names under DJGPP can reach ~260
|
||||
#define MAX_PATH_LEN 260
|
||||
// Grid layout for app buttons: 4 columns, rows created dynamically
|
||||
#define PM_TOOLTIP_LEN 128
|
||||
#define PM_GRID_COLS 4
|
||||
#define PM_BTN_W 100
|
||||
#define PM_BTN_H 24
|
||||
#define PM_CELL_W 80
|
||||
|
|
@ -91,12 +72,9 @@
|
|||
#define CMD_TILE_H 202
|
||||
#define CMD_TILE_V 203
|
||||
#define CMD_MIN_ON_RUN 104
|
||||
#define CMD_RESTORE_ALONE 105
|
||||
#define CMD_RELOAD 106
|
||||
#define CMD_ABOUT 300
|
||||
#define CMD_TASK_MGR 301
|
||||
#define CMD_SYSINFO 302
|
||||
#define CMD_HELP 303
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
|
|
@ -106,7 +84,7 @@
|
|||
typedef struct {
|
||||
char name[SHELL_APP_NAME_MAX]; // short display name (from "name" resource or filename)
|
||||
char tooltip[PM_TOOLTIP_LEN]; // description for tooltip (from "description" resource)
|
||||
char path[DVX_MAX_PATH]; // full path
|
||||
char path[MAX_PATH_LEN]; // full path
|
||||
uint8_t *iconData; // 32x32 icon in display format (NULL if none)
|
||||
int32_t iconW;
|
||||
int32_t iconH;
|
||||
|
|
@ -120,10 +98,8 @@ static DxeAppContextT *sCtx = NULL;
|
|||
static AppContextT *sAc = NULL;
|
||||
static WindowT *sPmWindow = NULL;
|
||||
static WidgetT *sStatusLabel = NULL;
|
||||
static PrefsHandleT *sPrefs = NULL;
|
||||
static bool sMinOnRun = false;
|
||||
static bool sRestoreAlone = false;
|
||||
static AppEntryT *sAppFiles = NULL; // stb_ds dynamic array
|
||||
static AppEntryT sAppFiles[MAX_APP_FILES];
|
||||
static int32_t sAppCount = 0;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -131,8 +107,6 @@ static int32_t sAppCount = 0;
|
|||
// ============================================================
|
||||
|
||||
int32_t appMain(DxeAppContextT *ctx);
|
||||
void appShutdown(void);
|
||||
static int32_t appEntryCmpName(const void *a, const void *b);
|
||||
static void buildPmWindow(void);
|
||||
static void desktopUpdate(void);
|
||||
static void onAppButtonClick(WidgetT *w);
|
||||
|
|
@ -160,20 +134,9 @@ AppDescriptorT appDescriptor = {
|
|||
.priority = TS_PRIORITY_NORMAL
|
||||
};
|
||||
|
||||
|
||||
void appShutdown(void) {
|
||||
shellUnregisterDesktopUpdate(desktopUpdate);
|
||||
dvxQuit(sAc);
|
||||
}
|
||||
|
||||
|
||||
// qsort comparator for AppEntryT -- case-insensitive on display name.
|
||||
static int32_t appEntryCmpName(const void *a, const void *b) {
|
||||
const AppEntryT *ea = (const AppEntryT *)a;
|
||||
const AppEntryT *eb = (const AppEntryT *)b;
|
||||
return strcasecmp(ea->name, eb->name);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Static functions (alphabetical)
|
||||
// ============================================================
|
||||
|
||||
// Build the main Program Manager window with app buttons, menus, and status bar.
|
||||
// Window is centered horizontally and placed in the upper quarter vertically
|
||||
|
|
@ -199,13 +162,11 @@ static void buildPmWindow(void) {
|
|||
MenuBarT *menuBar = wmAddMenuBar(sPmWindow);
|
||||
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
|
||||
wmAddMenuItem(fileMenu, "&Run...", CMD_RUN);
|
||||
wmAddMenuItem(fileMenu, "Re&load", CMD_RELOAD);
|
||||
wmAddMenuSeparator(fileMenu);
|
||||
wmAddMenuItem(fileMenu, "E&xit DVX", CMD_EXIT);
|
||||
wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT);
|
||||
|
||||
MenuT *optMenu = wmAddMenu(menuBar, "&Options");
|
||||
wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, sMinOnRun);
|
||||
wmAddMenuCheckItem(optMenu, "&Restore when Alone", CMD_RESTORE_ALONE, sRestoreAlone);
|
||||
|
||||
MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
|
||||
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
|
||||
|
|
@ -214,9 +175,7 @@ static void buildPmWindow(void) {
|
|||
wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V);
|
||||
|
||||
MenuT *helpMenu = wmAddMenu(menuBar, "&Help");
|
||||
wmAddMenuItem(helpMenu, "&DVX Help\tF1", CMD_HELP);
|
||||
wmAddMenuSeparator(helpMenu);
|
||||
wmAddMenuItem(helpMenu, "&About DVX...", CMD_ABOUT);
|
||||
wmAddMenuItem(helpMenu, "&About DVX Shell...", CMD_ABOUT);
|
||||
wmAddMenuItem(helpMenu, "&System Information...", CMD_SYSINFO);
|
||||
wmAddMenuSeparator(helpMenu);
|
||||
wmAddMenuItem(helpMenu, "&Task Manager\tCtrl+Esc", CMD_TASK_MGR);
|
||||
|
|
@ -228,25 +187,23 @@ static void buildPmWindow(void) {
|
|||
// App button grid in a labeled frame. weight=100 tells the layout engine
|
||||
// this frame should consume all available vertical space (flex weight).
|
||||
WidgetT *appFrame = wgtFrame(root, "Applications");
|
||||
appFrame->weight = WGT_WEIGHT_FILL;
|
||||
appFrame->weight = 100;
|
||||
|
||||
if (sAppCount == 0) {
|
||||
wgtLabel(appFrame, "(No applications found in apps/ directory)");
|
||||
} else {
|
||||
// ScrollPane provides scrollbars when icons overflow.
|
||||
// WrapBox inside flows cells left-to-right, wrapping to
|
||||
// the next row when the window width is exceeded.
|
||||
WidgetT *scroll = wgtScrollPane(appFrame);
|
||||
scroll->weight = WGT_WEIGHT_FILL;
|
||||
wgtScrollPaneSetNoBorder(scroll, true);
|
||||
|
||||
WidgetT *wrap = wgtWrapBox(scroll);
|
||||
wrap->weight = WGT_WEIGHT_FILL;
|
||||
wrap->spacing = wgtPixels(PM_GRID_SPACING);
|
||||
wrap->align = AlignCenterE;
|
||||
// Build grid of app icons. Each cell is a VBox with an image
|
||||
// button (or text button if no icon) and a label underneath.
|
||||
WidgetT *hbox = NULL;
|
||||
|
||||
for (int32_t i = 0; i < sAppCount; i++) {
|
||||
WidgetT *cell = wgtVBox(wrap);
|
||||
if (i % PM_GRID_COLS == 0) {
|
||||
hbox = wgtHBox(appFrame);
|
||||
hbox->spacing = wgtPixels(PM_GRID_SPACING);
|
||||
hbox->align = AlignCenterE;
|
||||
}
|
||||
|
||||
WidgetT *cell = wgtVBox(hbox);
|
||||
cell->align = AlignCenterE;
|
||||
cell->minW = wgtPixels(PM_CELL_W);
|
||||
cell->minH = wgtPixels(PM_CELL_H);
|
||||
|
|
@ -272,6 +229,7 @@ static void buildPmWindow(void) {
|
|||
}
|
||||
|
||||
btn->userData = &sAppFiles[i];
|
||||
btn->onDblClick = onAppButtonClick;
|
||||
btn->onClick = onAppButtonClick;
|
||||
|
||||
if (sAppFiles[i].tooltip[0]) {
|
||||
|
|
@ -288,9 +246,13 @@ static void buildPmWindow(void) {
|
|||
// width so text can be left-aligned naturally.
|
||||
WidgetT *statusBar = wgtStatusBar(root);
|
||||
sStatusLabel = wgtLabel(statusBar, "");
|
||||
sStatusLabel->weight = WGT_WEIGHT_FILL;
|
||||
sStatusLabel->weight = 100;
|
||||
updateStatusText();
|
||||
|
||||
// dvxFitWindow sizes the window to tightly fit the widget tree,
|
||||
// honoring preferred sizes. Without this, the window would use the
|
||||
// initial dimensions from dvxCreateWindow even if widgets don't fit.
|
||||
dvxFitWindow(sAc, sPmWindow);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -299,13 +261,6 @@ static void buildPmWindow(void) {
|
|||
// (Task Manager refresh is handled by the shell's shellDesktopUpdate.)
|
||||
static void desktopUpdate(void) {
|
||||
updateStatusText();
|
||||
|
||||
// Auto-restore if we're the only running app and minimized
|
||||
if (sRestoreAlone && sPmWindow && sPmWindow->minimized) {
|
||||
if (shellRunningAppCount() <= 1) {
|
||||
wmRestoreMinimized(&sAc->stack, &sAc->dirty, &sAc->display, sPmWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -332,7 +287,7 @@ static void onAppButtonClick(WidgetT *w) {
|
|||
// shellTerminateAllApps() to gracefully tear down all loaded DXEs.
|
||||
static void onPmClose(WindowT *win) {
|
||||
(void)win;
|
||||
int32_t result = dvxMessageBox(sAc, "Exit DVX", "Are you sure you want to exit DVX?", MB_YESNO | MB_ICONQUESTION);
|
||||
int32_t result = dvxMessageBox(sAc, "Exit Shell", "Are you sure you want to exit DVX Shell?", MB_YESNO | MB_ICONQUESTION);
|
||||
|
||||
if (result == ID_YES) {
|
||||
dvxQuit(sAc);
|
||||
|
|
@ -347,10 +302,10 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
case CMD_RUN:
|
||||
{
|
||||
FileFilterT filters[] = {
|
||||
{ "Applications (*.app)" },
|
||||
{ "All Files (*.*)" }
|
||||
{ "Applications (*.app)", "*.app" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
char path[MAX_PATH_LEN];
|
||||
|
||||
if (dvxFileDialog(sAc, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
|
||||
shellLoadApp(sAc, path);
|
||||
|
|
@ -363,14 +318,6 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
}
|
||||
break;
|
||||
|
||||
case CMD_RELOAD:
|
||||
dvxDestroyWindow(sAc, sPmWindow);
|
||||
sPmWindow = NULL;
|
||||
sStatusLabel = NULL;
|
||||
scanAppsDir();
|
||||
buildPmWindow();
|
||||
break;
|
||||
|
||||
case CMD_EXIT:
|
||||
onPmClose(sPmWindow);
|
||||
break;
|
||||
|
|
@ -394,26 +341,10 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
case CMD_MIN_ON_RUN:
|
||||
sMinOnRun = !sMinOnRun;
|
||||
shellEnsureConfigDir(sCtx);
|
||||
prefsSetBool(sPrefs, "options", "minimizeOnRun", sMinOnRun);
|
||||
prefsSave(sPrefs);
|
||||
prefsSetBool("options", "minimizeOnRun", sMinOnRun);
|
||||
prefsSave();
|
||||
break;
|
||||
|
||||
case CMD_RESTORE_ALONE:
|
||||
sRestoreAlone = !sRestoreAlone;
|
||||
shellEnsureConfigDir(sCtx);
|
||||
prefsSetBool(sPrefs, "options", "restoreAlone", sRestoreAlone);
|
||||
prefsSave(sPrefs);
|
||||
break;
|
||||
|
||||
case CMD_HELP: {
|
||||
char viewerPath[DVX_MAX_PATH];
|
||||
char sysHlp[DVX_MAX_PATH];
|
||||
snprintf(viewerPath, sizeof(viewerPath), "APPS" DVX_PATH_SEP "KPUNCH" DVX_PATH_SEP "DVXHELP" DVX_PATH_SEP "DVXHELP.APP");
|
||||
snprintf(sysHlp, sizeof(sysHlp), "%s" DVX_PATH_SEP "%s", sCtx->appDir, "dvxhelp.hlp");
|
||||
shellLoadAppWithArgs(sAc, viewerPath, sysHlp);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_ABOUT:
|
||||
showAboutDialog();
|
||||
break;
|
||||
|
|
@ -435,20 +366,8 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
// The apps/ path is relative to the working directory, which the shell sets
|
||||
// to the root of the DVX install before loading any apps.
|
||||
static void scanAppsDir(void) {
|
||||
// Free icons from previous scan
|
||||
for (int32_t i = 0; i < sAppCount; i++) {
|
||||
free(sAppFiles[i].iconData);
|
||||
}
|
||||
|
||||
arrfree(sAppFiles);
|
||||
sAppFiles = NULL;
|
||||
sAppCount = 0;
|
||||
scanAppsDirRecurse("apps");
|
||||
|
||||
if (sAppCount > 1) {
|
||||
qsort(sAppFiles, sAppCount, sizeof(AppEntryT), (int (*)(const void *, const void *))appEntryCmpName);
|
||||
}
|
||||
|
||||
dvxLog("Progman: found %ld app(s)", (long)sAppCount);
|
||||
}
|
||||
|
||||
|
|
@ -458,12 +377,9 @@ static void scanAppsDir(void) {
|
|||
// object that the shell can dlopen(). We skip progman.app to avoid listing
|
||||
// ourselves in the launcher grid.
|
||||
static void scanAppsDirRecurse(const char *dirPath) {
|
||||
// Collect all entries first, close the handle, then process.
|
||||
// DOS has limited file handles; keeping a DIR open while
|
||||
// opening .app files for resource loading causes failures.
|
||||
char **names = dvxReadDir(dirPath);
|
||||
DIR *dir = opendir(dirPath);
|
||||
|
||||
if (!names) {
|
||||
if (!dir) {
|
||||
if (sAppCount == 0) {
|
||||
dvxLog("Progman: %s directory not found", dirPath);
|
||||
}
|
||||
|
|
@ -471,12 +387,18 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t nEntries = (int32_t)arrlen(names);
|
||||
struct dirent *ent;
|
||||
|
||||
for (int32_t i = 0; i < nEntries; i++) {
|
||||
char fullPath[DVX_MAX_PATH];
|
||||
snprintf(fullPath, sizeof(fullPath), "%s" DVX_PATH_SEP "%s", dirPath, names[i]);
|
||||
while ((ent = readdir(dir)) != NULL && sAppCount < MAX_APP_FILES) {
|
||||
// Skip . and ..
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char fullPath[MAX_PATH_LEN];
|
||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, ent->d_name);
|
||||
|
||||
// Check if this is a directory -- recurse into it
|
||||
struct stat st;
|
||||
|
||||
if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
|
|
@ -484,53 +406,87 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!dvxHasExt(names[i], ".app") || strcasecmp(names[i], "progman.app") == 0) {
|
||||
int32_t len = strlen(ent->d_name);
|
||||
|
||||
if (len < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AppEntryT newEntry;
|
||||
memset(&newEntry, 0, sizeof(newEntry));
|
||||
snprintf(newEntry.path, sizeof(newEntry.path), "%s", fullPath);
|
||||
// Check for .app extension (case-insensitive)
|
||||
const char *ext = ent->d_name + len - 4;
|
||||
|
||||
if (strcmp(ext, ".app") != 0 && strcmp(ext, ".APP") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip ourselves
|
||||
if (strcmp(ent->d_name, "progman.app") == 0 || strcmp(ent->d_name, "PROGMAN.APP") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AppEntryT *entry = &sAppFiles[sAppCount];
|
||||
|
||||
snprintf(entry->path, sizeof(entry->path), "%s", fullPath);
|
||||
entry->iconData = NULL;
|
||||
entry->tooltip[0] = '\0';
|
||||
|
||||
// Default name from filename (without .app extension)
|
||||
int32_t len = (int32_t)strlen(names[i]);
|
||||
int32_t nameLen = len - 4;
|
||||
|
||||
if (nameLen >= SHELL_APP_NAME_MAX) {
|
||||
nameLen = SHELL_APP_NAME_MAX - 1;
|
||||
}
|
||||
|
||||
memcpy(newEntry.name, names[i], nameLen);
|
||||
newEntry.name[nameLen] = '\0';
|
||||
memcpy(entry->name, ent->d_name, nameLen);
|
||||
entry->name[nameLen] = '\0';
|
||||
|
||||
if (newEntry.name[0] >= 'a' && newEntry.name[0] <= 'z') {
|
||||
newEntry.name[0] -= 32;
|
||||
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
|
||||
entry->name[0] -= 32;
|
||||
}
|
||||
|
||||
// Override from embedded resources if available
|
||||
newEntry.iconData = dvxResLoadIcon(sAc, fullPath, "icon32", &newEntry.iconW, &newEntry.iconH, &newEntry.iconPitch);
|
||||
DvxResHandleT *res = dvxResOpen(fullPath);
|
||||
|
||||
if (!newEntry.iconData) {
|
||||
dvxLog("Progman: no icon32 resource in %s", names[i]);
|
||||
if (res) {
|
||||
uint32_t iconSize = 0;
|
||||
void *iconBuf = dvxResRead(res, "icon32", &iconSize);
|
||||
|
||||
if (iconBuf) {
|
||||
entry->iconData = dvxLoadImageFromMemory(sAc, (const uint8_t *)iconBuf, (int32_t)iconSize, &entry->iconW, &entry->iconH, &entry->iconPitch);
|
||||
free(iconBuf);
|
||||
}
|
||||
|
||||
dvxResLoadText(fullPath, "name", newEntry.name, SHELL_APP_NAME_MAX);
|
||||
dvxResLoadText(fullPath, "description", newEntry.tooltip, sizeof(newEntry.tooltip));
|
||||
uint32_t nameSize = 0;
|
||||
void *nameBuf = dvxResRead(res, "name", &nameSize);
|
||||
|
||||
dvxLog("Progman: found %s (%s) icon=%s", newEntry.name, names[i], newEntry.iconData ? "yes" : "no");
|
||||
arrput(sAppFiles, newEntry);
|
||||
sAppCount = (int32_t)arrlen(sAppFiles);
|
||||
|
||||
dvxUpdate(sAc);
|
||||
if (nameBuf) {
|
||||
strncpy(entry->name, (const char *)nameBuf, SHELL_APP_NAME_MAX - 1);
|
||||
entry->name[SHELL_APP_NAME_MAX - 1] = '\0';
|
||||
free(nameBuf);
|
||||
}
|
||||
|
||||
dvxReadDirFree(names);
|
||||
uint32_t descSize = 0;
|
||||
void *descBuf = dvxResRead(res, "description", &descSize);
|
||||
|
||||
if (descBuf) {
|
||||
strncpy(entry->tooltip, (const char *)descBuf, sizeof(entry->tooltip) - 1);
|
||||
entry->tooltip[sizeof(entry->tooltip) - 1] = '\0';
|
||||
free(descBuf);
|
||||
}
|
||||
|
||||
dvxResClose(res);
|
||||
}
|
||||
|
||||
sAppCount++;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
|
||||
static void showAboutDialog(void) {
|
||||
dvxMessageBox(sAc, "About DVX",
|
||||
"DVX 1.0 - \"DOS Visual eXecutive\" GUI System.\n\n\"We have Windows at home.\"\n\nCopyright 2026 Scott Duensing\nKangaroo Punch Studios\nhttps://kangaroopunch.com",
|
||||
dvxMessageBox(sAc, "About DVX Shell",
|
||||
"DVX Shell 1.0\nA DOS Visual eXecutive desktop shell for DJGPP/DPMI. Using DXE3 dynamic loading for application modules.",
|
||||
MB_OK | MB_ICONINFO);
|
||||
}
|
||||
|
||||
|
|
@ -539,7 +495,7 @@ static void showSystemInfo(void) {
|
|||
const char *info = shellGetSystemInfo();
|
||||
|
||||
if (!info || !info[0]) {
|
||||
dvxInfoBox(sAc, "System Information", "No system information available.");
|
||||
dvxMessageBox(sAc, "System Information", "No system information available.", MB_OK | MB_ICONINFO);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -559,7 +515,7 @@ static void showSystemInfo(void) {
|
|||
|
||||
WidgetT *root = wgtInitWindow(sAc, win);
|
||||
WidgetT *ta = wgtTextArea(root, 4096);
|
||||
ta->weight = WGT_WEIGHT_FILL;
|
||||
ta->weight = 100;
|
||||
wgtSetText(ta, info);
|
||||
|
||||
// Don't disable -- wgtSetEnabled(false) blocks all input including scrollbar
|
||||
|
|
@ -586,6 +542,9 @@ static void updateStatusText(void) {
|
|||
wgtSetText(sStatusLabel, buf);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Entry point
|
||||
// ============================================================
|
||||
|
||||
// The shell calls appMain exactly once after dlopen() resolves our symbols.
|
||||
// We scan for apps, build the UI, register our desktop update callback, then
|
||||
|
|
@ -595,15 +554,11 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
sCtx = ctx;
|
||||
sAc = ctx->shellCtx;
|
||||
|
||||
// Set help file for F1 — system help lives in progman's app directory
|
||||
snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s" DVX_PATH_SEP "%s", ctx->appDir, "dvxhelp.hlp");
|
||||
|
||||
// Load saved preferences
|
||||
char prefsPath[DVX_MAX_PATH];
|
||||
char prefsPath[260];
|
||||
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
|
||||
sPrefs = prefsLoad(prefsPath);
|
||||
sMinOnRun = prefsGetBool(sPrefs, "options", "minimizeOnRun", false);
|
||||
sRestoreAlone = prefsGetBool(sPrefs, "options", "restoreAlone", false);
|
||||
prefsLoad(prefsPath);
|
||||
sMinOnRun = prefsGetBool("options", "minimizeOnRun", false);
|
||||
|
||||
scanAppsDir();
|
||||
buildPmWindow();
|
||||
BIN
assets/DVX Help Logo.xcf
(Stored with Git LFS)
BIN
assets/DVX Help Logo.xcf
(Stored with Git LFS)
Binary file not shown.
BIN
assets/DVX Logo.xcf
(Stored with Git LFS)
BIN
assets/DVX Logo.xcf
(Stored with Git LFS)
Binary file not shown.
BIN
assets/DVX Text.xcf
(Stored with Git LFS)
BIN
assets/DVX Text.xcf
(Stored with Git LFS)
Binary file not shown.
BIN
assets/help.png
(Stored with Git LFS)
BIN
assets/help.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/splash.bmp
(Stored with Git LFS)
BIN
assets/splash.bmp
(Stored with Git LFS)
Binary file not shown.
|
|
@ -29,8 +29,6 @@ build. Text files are converted to DOS line endings (CR+LF) via sed.
|
|||
| `listbox.dep` | `bin/widgets/listbox.dep` | ListBox widget dep file |
|
||||
| `listview.dep` | `bin/widgets/listview.dep` | ListView widget dep file |
|
||||
| `treeview.dep` | `bin/widgets/treeview.dep` | TreeView widget dep file |
|
||||
| `basrt.dep` | `bin/libs/basrt.dep` | BASIC runtime dep file |
|
||||
| `serial.dep` | `bin/libs/serial.dep` | Serial communications dep file |
|
||||
|
||||
|
||||
## dvx.ini Format
|
||||
|
|
@ -128,8 +126,6 @@ ignored. Names are case-insensitive.
|
|||
| `texthelp.dep` | texthelp.lib | libtasks, libdvx |
|
||||
| `listhelp.dep` | listhelp.lib | libtasks, libdvx |
|
||||
| `dvxshell.dep` | dvxshell.lib | libtasks, libdvx, texthelp, listhelp |
|
||||
| `basrt.dep` | basrt.lib | libtasks, libdvx |
|
||||
| `serial.dep` | serial.lib | libtasks, libdvx |
|
||||
|
||||
### Widget Dependencies
|
||||
|
||||
|
|
|
|||
|
|
@ -5,26 +5,22 @@
|
|||
; Supported color depths: 8, 15, 16, 24, 32
|
||||
|
||||
[video]
|
||||
width = 1024
|
||||
height = 768
|
||||
width = 640
|
||||
height = 480
|
||||
bpp = 16
|
||||
|
||||
; Mouse settings.
|
||||
; wheel: normal or reversed
|
||||
; wheelspeed: lines per wheel notch (1-10, default 3)
|
||||
; doubleclick: double-click speed in milliseconds (200-900, default 500)
|
||||
; acceleration: off, low, medium, high (default medium)
|
||||
; speed: cursor speed (2-32, default 8; higher = faster)
|
||||
|
||||
[mouse]
|
||||
wheel = normal
|
||||
wheelspeed = 3
|
||||
doubleclick = 500
|
||||
acceleration = medium
|
||||
speed = 8
|
||||
|
||||
; Shell settings.
|
||||
; desktop: path to the desktop app loaded at startup
|
||||
|
||||
[shell]
|
||||
desktop = apps/kpunch/progman/progman.app
|
||||
desktop = apps/progman/progman.app
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
; DVX Color Theme - Hot Dog Stand
|
||||
; The infamous Windows 3.1 color scheme. Yellow, red, black.
|
||||
; Included here for historical authenticity and eye strain.
|
||||
|
||||
[colors]
|
||||
desktop = 255,255,0
|
||||
windowFace = 255,255,0
|
||||
windowHighlight = 255,255,255
|
||||
windowShadow = 0,0,0
|
||||
activeTitleBg = 255,0,0
|
||||
activeTitleFg = 255,255,255
|
||||
inactiveTitleBg = 255,255,255
|
||||
inactiveTitleFg = 0,0,0
|
||||
contentBg = 255,255,255
|
||||
contentFg = 0,0,0
|
||||
menuBg = 255,255,255
|
||||
menuFg = 0,0,0
|
||||
menuHighlightBg = 255,0,0
|
||||
menuHighlightFg = 255,255,255
|
||||
buttonFace = 255,255,0
|
||||
scrollbarBg = 255,255,0
|
||||
scrollbarFg = 0,0,0
|
||||
scrollbarTrough = 255,255,255
|
||||
cursorColor = 255,255,0
|
||||
cursorOutline = 0,0,0
|
||||
74
core/Makefile
Normal file
74
core/Makefile
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# DVX Core Library Makefile for DJGPP cross-compilation
|
||||
#
|
||||
# Builds libdvx.lib -- core GUI infrastructure (draw, compositor,
|
||||
# window manager, event dispatch, layout engine, widget infrastructure).
|
||||
# Zero widget implementations -- those are in ../widgets/ as .wgt modules.
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -Iplatform -I../tasks -Ithirdparty
|
||||
|
||||
OBJDIR = ../obj/core
|
||||
LIBSDIR = ../bin/libs
|
||||
|
||||
# Core sources
|
||||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxImage.c dvxImageWrite.c \
|
||||
dvxApp.c dvxDialog.c dvxPrefs.c dvxResource.c \
|
||||
widgetClass.c widgetCore.c widgetScrollbar.c \
|
||||
widgetLayout.c widgetEvent.c widgetOps.c
|
||||
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
TARGET = $(LIBSDIR)/libdvx.lib
|
||||
|
||||
# libdvx.lib export prefixes
|
||||
DVX_EXPORTS = -E _dvx -E _wgt -E _wm -E _prefs -E _rect -E _draw -E _pack -E _text \
|
||||
-E _setClip -E _resetClip -E _stbi_ -E _stbi_write -E _dirtyList \
|
||||
-E _widget \
|
||||
-E _sCursor -E _sDbl -E _sDebug -E _sClosed -E _sFocused -E _sKey \
|
||||
-E _sOpen -E _sPressed -E _sDrag -E _sDrawing -E _sResize \
|
||||
-E _sListView -E _sSplitter -E _sTreeView \
|
||||
-E _accelParse -E _clipboard -E _multiClick
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET) $(LIBSDIR)/libdvx.dep
|
||||
|
||||
$(LIBSDIR)/libdvx.dep: ../config/libdvx.dep | $(LIBSDIR)
|
||||
sed 's/$$/\r/' $< > $@
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBSDIR)
|
||||
$(DXE3GEN) -o $(LIBSDIR)/libdvx.dxe $(DVX_EXPORTS) -U $(OBJS)
|
||||
mv $(LIBSDIR)/libdvx.dxe $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(LIBSDIR):
|
||||
mkdir -p $(LIBSDIR)
|
||||
|
||||
# Dependencies
|
||||
CORE_HDRS = dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h dvxWidget.h platform/dvxPlatform.h
|
||||
$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes.h dvxPalette.h
|
||||
$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h platform/dvxPlatform.h dvxTypes.h
|
||||
$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h platform/dvxPlatform.h dvxTypes.h
|
||||
$(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h dvxWidget.h thirdparty/stb_image.h
|
||||
$(OBJDIR)/dvxImage.o: dvxImage.c thirdparty/stb_image.h
|
||||
$(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h
|
||||
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h
|
||||
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h dvxWidgetPlugin.h dvxTypes.h dvxDraw.h
|
||||
$(OBJDIR)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h
|
||||
|
||||
WIDGET_DEPS = dvxWidgetPlugin.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
|
||||
$(OBJDIR)/widgetClass.o: widgetClass.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetCore.o: widgetCore.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetScrollbar.o: widgetScrollbar.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetLayout.o: widgetLayout.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetEvent.o: widgetEvent.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetOps.o: widgetOps.c $(WIDGET_DEPS)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET) $(LIBSDIR)/libdvx.dep
|
||||
449
core/README.md
Normal file
449
core/README.md
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
# DVX Core Library (libdvx.lib)
|
||||
|
||||
The core GUI infrastructure for DVX, built as a DXE3 module. Provides
|
||||
VESA video setup, 2D drawing primitives, dirty-rectangle compositing,
|
||||
a window manager with Motif-style chrome, and the widget infrastructure
|
||||
(layout engine, event dispatch, class registration). Individual widget
|
||||
type implementations live in `../widgets/` as separate `.wgt` DXE
|
||||
modules that register themselves at runtime via `wgtRegisterClass()`.
|
||||
|
||||
Core knows nothing about individual widget types. There is no
|
||||
WidgetTypeE enum, no widget union, and no per-widget structs in
|
||||
dvxWidget.h. All widget-specific behavior is dispatched through the
|
||||
WidgetClassT dispatch table.
|
||||
|
||||
|
||||
## 5-Layer Architecture
|
||||
|
||||
| Layer | Header | Source | Description |
|
||||
|-------|--------|--------|-------------|
|
||||
| 1. Video | `dvxVideo.h` | `dvxVideo.c` | VESA VBE init, LFB mapping, backbuffer, pixel format, `packColor()` |
|
||||
| 2. Draw | `dvxDraw.h` | `dvxDraw.c` | Rect fills, bevels, text, bitmap cursors, focus rects, lines |
|
||||
| 3. Compositor | `dvxComp.h` | `dvxComp.c` | Dirty rect tracking, merge, clip, LFB flush |
|
||||
| 4. Window Manager | `dvxWm.h` | `dvxWm.c` | Window stack, chrome, drag/resize, menus, scrollbars, hit test |
|
||||
| 5. Application | `dvxApp.h` | `dvxApp.c` | Event loop, input polling, color schemes, wallpaper, public API |
|
||||
|
||||
Additional modules built into libdvx.lib:
|
||||
|
||||
| Header | Source | Description |
|
||||
|--------|--------|-------------|
|
||||
| `dvxDialog.h` | `dvxDialog.c` | Modal message box and file open/save dialogs |
|
||||
| `dvxPrefs.h` | `dvxPrefs.c` | INI-based preferences (read/write with typed accessors) |
|
||||
| `dvxResource.h` | `dvxResource.c` | Resource system -- icons, text, and binary data appended to DXE files |
|
||||
| `dvxMem.h` | (header only) | Per-app memory tracking API declarations |
|
||||
| `dvxWidget.h` | `widgetClass.c`, `widgetCore.c`, `widgetEvent.c`, `widgetLayout.c`, `widgetOps.c`, `widgetScrollbar.c` | Widget infrastructure |
|
||||
| `dvxWidgetPlugin.h` | (header only) | Plugin API for widget DXE modules |
|
||||
| -- | `dvxImage.c` | Image loading via stb_image (BMP, PNG, JPEG, GIF) |
|
||||
| -- | `dvxImageWrite.c` | PNG export via stb_image_write |
|
||||
|
||||
|
||||
## Source Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `dvxVideo.c` | VESA mode negotiation, LFB mapping via DPMI, backbuffer alloc, `packColor()` |
|
||||
| `dvxDraw.c` | `rectFill()`, `rectCopy()`, `drawBevel()`, `drawText()`, `drawTextN()`, `drawTermRow()`, cursor rendering |
|
||||
| `dvxComp.c` | `dirtyListAdd()`, `dirtyListMerge()`, `flushRect()`, `rectIntersect()` |
|
||||
| `dvxWm.c` | Window create/destroy, Z-order, chrome drawing, drag/resize, menu bar, scrollbars, minimize/maximize |
|
||||
| `dvxApp.c` | `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, screenshots |
|
||||
| `dvxDialog.c` | `dvxMessageBox()`, `dvxFileDialog()` -- modal dialogs with own event loops |
|
||||
| `dvxPrefs.c` | `prefsLoad()`, `prefsSave()`, typed get/set for string/int/bool |
|
||||
| `dvxResource.c` | `dvxResOpen()`, `dvxResRead()`, `dvxResFind()`, `dvxResClose()` -- resource system |
|
||||
| `dvxImage.c` | `dvxLoadImage()`, `dvxLoadImageFromMemory()` -- stb_image loader, converts to native pixel format |
|
||||
| `dvxImageWrite.c` | `dvxSaveImage()` -- PNG writer for screenshots |
|
||||
| `widgetClass.c` | `wgtRegisterClass()`, `wgtRegisterApi()`, `wgtGetApi()`, class table |
|
||||
| `widgetCore.c` | Widget allocation, tree ops, focus management, clipboard, hit testing, cursor blink |
|
||||
| `widgetEvent.c` | `widgetOnMouse()`, `widgetOnKey()`, `widgetOnPaint()`, `widgetOnResize()`, scrollbar management |
|
||||
| `widgetLayout.c` | Two-pass flexbox layout: bottom-up `calcMinSize`, top-down space allocation with weights |
|
||||
| `widgetOps.c` | `wgtPaint()`, `wgtLayout()`, `wgtInitWindow()`, text get/set, invalidation |
|
||||
| `widgetScrollbar.c` | Scrollbar drawing (H/V), thumb calculation, hit testing, drag update |
|
||||
|
||||
|
||||
## Public Headers
|
||||
|
||||
| Header | Purpose |
|
||||
|--------|---------|
|
||||
| `dvxTypes.h` | All shared types: DisplayT, RectT, BlitOpsT, BevelStyleT, BitmapFontT, ColorSchemeT, WindowT, MenuT, ScrollbarT, CursorT, PopupStateT |
|
||||
| `dvxVideo.h` | `videoInit()`, `videoShutdown()`, `packColor()`, `setClipRect()`, `resetClipRect()` |
|
||||
| `dvxDraw.h` | All drawing functions: `rectFill()`, `drawBevel()`, `drawText()`, `drawTextN()`, `drawTermRow()`, etc. |
|
||||
| `dvxComp.h` | Dirty list operations: `dirtyListAdd()`, `dirtyListMerge()`, `flushRect()`, `rectIntersect()` |
|
||||
| `dvxWm.h` | Window management: `wmCreateWindow()`, `wmDestroyWindow()`, `wmRaiseWindow()`, menus, scrollbars, chrome |
|
||||
| `dvxApp.h` | Application API: `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, image I/O |
|
||||
| `dvxDialog.h` | Modal dialogs: `dvxMessageBox()`, `dvxFileDialog()` |
|
||||
| `dvxPrefs.h` | INI preferences: `prefsLoad()`, `prefsSave()`, typed accessors |
|
||||
| `dvxResource.h` | Resource system: `dvxResOpen()`, `dvxResRead()`, `dvxResFind()`, `dvxResClose()` |
|
||||
| `dvxMem.h` | Per-app memory tracking: `dvxMalloc()`, `dvxFree()`, `dvxMemGetAppUsage()`, etc. |
|
||||
| `dvxWidget.h` | Widget system public API: WidgetT, WidgetClassT, size tags, layout, API registry, `wclsFoo()` dispatch helpers |
|
||||
| `dvxWidgetPlugin.h` | Plugin API for widget DXE authors: tree ops, focus, scrollbar helpers, shared state |
|
||||
| `dvxFont.h` | Embedded 8x14 and 8x16 bitmap font data (CP437) |
|
||||
| `dvxCursor.h` | Mouse cursor AND/XOR mask data (arrow, resize H/V/diag, busy) |
|
||||
| `dvxPalette.h` | Default 256-color VGA palette for 8-bit mode |
|
||||
|
||||
|
||||
## Platform Layer
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `platform/dvxPlatform.h` | Platform abstraction API (video, input, spans, DXE, crash recovery, memory tracking) |
|
||||
| `platform/dvxPlatformDos.c` | DJGPP/DPMI implementation (VESA VBE, INT 33h mouse, INT 16h keyboard, asm spans) |
|
||||
|
||||
The platform layer is compiled into dvx.exe (the loader), not into
|
||||
libdvx.lib. Platform functions are exported to all DXE modules via
|
||||
`platformRegisterDxeExports()`.
|
||||
|
||||
|
||||
## Third-Party Libraries
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `thirdparty/stb_image.h` | Image loading (implementation compiled into dvxImage.c) |
|
||||
| `thirdparty/stb_image_write.h` | PNG writing (implementation compiled into dvxImageWrite.c) |
|
||||
| `thirdparty/stb_ds.h` | Dynamic arrays/hash maps (implementation in loader, exported to all DXEs) |
|
||||
|
||||
|
||||
## Dynamic Limits
|
||||
|
||||
All major data structures grow dynamically via realloc. There are no
|
||||
fixed-size limits for:
|
||||
|
||||
- **Windows** -- `WindowStackT.windows` is a dynamic array
|
||||
- **Menus** -- `MenuBarT.menus` and `MenuT.items` are dynamic arrays
|
||||
- **Accelerator entries** -- `AccelTableT.entries` is a dynamic array
|
||||
- **Dirty rectangles** -- `DirtyListT.rects` is a dynamic array
|
||||
- **Submenu depth** -- `PopupStateT.parentStack` is a dynamic array
|
||||
|
||||
The only fixed-size buffers remaining are per-element string fields
|
||||
(`MAX_TITLE_LEN = 128`, `MAX_MENU_LABEL = 32`, `MAX_WIDGET_NAME = 32`)
|
||||
and the system menu (`SYS_MENU_MAX_ITEMS = 10`).
|
||||
|
||||
|
||||
## Resource System
|
||||
|
||||
Resources are appended to DXE3 files (.app, .wgt, .lib) after the
|
||||
normal DXE content. The DXE loader never reads past the DXE header,
|
||||
so appended data is invisible to dlopen.
|
||||
|
||||
File layout:
|
||||
|
||||
[DXE3 content]
|
||||
[resource data entries] -- sequential, variable length
|
||||
[resource directory] -- fixed-size entries (48 bytes each)
|
||||
[footer] -- magic + directory offset + count (16 bytes)
|
||||
|
||||
### Resource Types
|
||||
|
||||
| Define | Value | Description |
|
||||
|--------|-------|-------------|
|
||||
| `DVX_RES_ICON` | 1 | Image data (BMP icon: 16x16, 32x32, etc.) |
|
||||
| `DVX_RES_TEXT` | 2 | Null-terminated string (author, copyright, etc.) |
|
||||
| `DVX_RES_BINARY` | 3 | Arbitrary binary data (app-specific) |
|
||||
|
||||
### Resource API
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `dvxResOpen(path)` | Open a resource handle by reading the footer and directory. Returns NULL if no resources. |
|
||||
| `dvxResRead(h, name, outSize)` | Find a resource by name and read its data into a malloc'd buffer. Caller frees. |
|
||||
| `dvxResFind(h, name)` | Find a resource by name and return its directory entry pointer. |
|
||||
| `dvxResClose(h)` | Close the handle and free associated memory. |
|
||||
|
||||
### Key Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `DvxResDirEntryT` | Directory entry: name[32], type, offset, size, reserved (48 bytes) |
|
||||
| `DvxResFooterT` | Footer: magic (`0x52585644` = "DVXR"), dirOffset, entryCount, reserved (16 bytes) |
|
||||
| `DvxResHandleT` | Runtime handle: path, entries array, entry count |
|
||||
|
||||
|
||||
## Memory Tracking (dvxMem.h)
|
||||
|
||||
Per-app memory tracking wraps malloc/free/calloc/realloc/strdup with a
|
||||
small header per allocation that records the owning app ID and size.
|
||||
DXE code does not need to include dvxMem.h -- the DXE export table maps
|
||||
the standard allocator names to these wrappers transparently.
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `dvxMalloc(size)` | Tracked malloc |
|
||||
| `dvxCalloc(nmemb, size)` | Tracked calloc |
|
||||
| `dvxRealloc(ptr, size)` | Tracked realloc |
|
||||
| `dvxFree(ptr)` | Tracked free (falls through to real free on non-tracked pointers) |
|
||||
| `dvxStrdup(s)` | Tracked strdup |
|
||||
| `dvxMemSnapshotLoad(appId)` | Record baseline memory for leak detection |
|
||||
| `dvxMemGetAppUsage(appId)` | Query current memory usage for an app (bytes) |
|
||||
| `dvxMemResetApp(appId)` | Free all allocations charged to an app |
|
||||
|
||||
The global `dvxMemAppIdPtr` pointer is set by the shell to
|
||||
`&ctx->currentAppId` so the allocator knows which app to charge.
|
||||
|
||||
|
||||
## WidgetT Structure
|
||||
|
||||
The WidgetT struct is generic -- no widget-specific fields or union:
|
||||
|
||||
```c
|
||||
typedef struct WidgetT {
|
||||
int32_t type; // assigned by wgtRegisterClass()
|
||||
const struct WidgetClassT *wclass; // dispatch table pointer
|
||||
char name[MAX_WIDGET_NAME];
|
||||
|
||||
// Tree linkage
|
||||
struct WidgetT *parent, *firstChild, *lastChild, *nextSibling;
|
||||
WindowT *window;
|
||||
|
||||
// Geometry (relative to window content area)
|
||||
int32_t x, y, w, h;
|
||||
int32_t calcMinW, calcMinH; // computed minimum size
|
||||
|
||||
// Size hints (tagged: wgtPixels/wgtChars/wgtPercent, 0 = auto)
|
||||
int32_t minW, minH, maxW, maxH;
|
||||
int32_t prefW, prefH;
|
||||
int32_t weight; // extra-space distribution (0 = fixed)
|
||||
|
||||
// Container properties
|
||||
WidgetAlignE align;
|
||||
int32_t spacing, padding; // tagged sizes
|
||||
|
||||
// Colors (0 = use color scheme defaults)
|
||||
uint32_t fgColor, bgColor;
|
||||
|
||||
// State
|
||||
bool visible, enabled, readOnly, focused;
|
||||
char accelKey;
|
||||
|
||||
// User data and callbacks
|
||||
void *userData;
|
||||
void *data; // widget-private data (allocated by widget DXE)
|
||||
const char *tooltip;
|
||||
MenuT *contextMenu;
|
||||
void (*onClick)(struct WidgetT *w);
|
||||
void (*onDblClick)(struct WidgetT *w);
|
||||
void (*onChange)(struct WidgetT *w);
|
||||
void (*onFocus)(struct WidgetT *w);
|
||||
void (*onBlur)(struct WidgetT *w);
|
||||
} WidgetT;
|
||||
```
|
||||
|
||||
|
||||
## WidgetClassT Dispatch Table
|
||||
|
||||
WidgetClassT is an ABI-stable dispatch table. Method IDs are fixed
|
||||
constants that never change -- adding new methods appends new IDs
|
||||
without shifting existing ones. Widget DXEs compiled against an older
|
||||
DVX version continue to work unmodified.
|
||||
|
||||
```c
|
||||
#define WGT_CLASS_VERSION 1 // bump on breaking ABI change
|
||||
#define WGT_METHOD_MAX 32 // room for future methods
|
||||
|
||||
typedef struct WidgetClassT {
|
||||
uint32_t version;
|
||||
uint32_t flags;
|
||||
void *handlers[WGT_METHOD_MAX];
|
||||
} WidgetClassT;
|
||||
```
|
||||
|
||||
### Method ID Table
|
||||
|
||||
21 methods are currently defined (IDs 0--20). WGT_METHOD_MAX is 32,
|
||||
leaving room for 11 future methods without a version bump.
|
||||
|
||||
| ID | Method ID | Signature | Purpose |
|
||||
|----|-----------|-----------|---------|
|
||||
| 0 | `WGT_METHOD_PAINT` | `void (w, d, ops, font, colors)` | Render the widget |
|
||||
| 1 | `WGT_METHOD_PAINT_OVERLAY` | `void (w, d, ops, font, colors)` | Render overlay (dropdown popup) |
|
||||
| 2 | `WGT_METHOD_CALC_MIN_SIZE` | `void (w, font)` | Compute minimum size (bottom-up pass) |
|
||||
| 3 | `WGT_METHOD_LAYOUT` | `void (w, font)` | Position children (top-down pass) |
|
||||
| 4 | `WGT_METHOD_GET_LAYOUT_METRICS` | `void (w, font, pad, gap, extraTop, borderW)` | Return padding/gap for box layout |
|
||||
| 5 | `WGT_METHOD_ON_MOUSE` | `void (w, root, vx, vy)` | Handle mouse click |
|
||||
| 6 | `WGT_METHOD_ON_KEY` | `void (w, key, mod)` | Handle keyboard input |
|
||||
| 7 | `WGT_METHOD_ON_ACCEL_ACTIVATE` | `void (w, root)` | Handle accelerator key match |
|
||||
| 8 | `WGT_METHOD_DESTROY` | `void (w)` | Free widget-private data |
|
||||
| 9 | `WGT_METHOD_ON_CHILD_CHANGED` | `void (parent, child)` | Notification when a child changes |
|
||||
| 10 | `WGT_METHOD_GET_TEXT` | `const char *(w)` | Return widget text |
|
||||
| 11 | `WGT_METHOD_SET_TEXT` | `void (w, text)` | Set widget text |
|
||||
| 12 | `WGT_METHOD_CLEAR_SELECTION` | `bool (w)` | Clear text/item selection |
|
||||
| 13 | `WGT_METHOD_CLOSE_POPUP` | `void (w)` | Close dropdown popup |
|
||||
| 14 | `WGT_METHOD_GET_POPUP_RECT` | `void (w, font, contentH, popX, popY, popW, popH)` | Compute popup rectangle |
|
||||
| 15 | `WGT_METHOD_ON_DRAG_UPDATE` | `void (w, root, x, y)` | Mouse move during drag |
|
||||
| 16 | `WGT_METHOD_ON_DRAG_END` | `void (w, root, x, y)` | Mouse release after drag |
|
||||
| 17 | `WGT_METHOD_GET_CURSOR_SHAPE` | `int32_t (w, vx, vy)` | Return cursor ID for this position |
|
||||
| 18 | `WGT_METHOD_POLL` | `void (w, win)` | Periodic polling (AnsiTerm comms) |
|
||||
| 19 | `WGT_METHOD_QUICK_REPAINT` | `int32_t (w, outY, outH)` | Fast incremental repaint |
|
||||
| 20 | `WGT_METHOD_SCROLL_CHILD_INTO_VIEW` | `void (parent, child)` | Scroll to make a child visible |
|
||||
|
||||
### Typed Dispatch Helpers
|
||||
|
||||
Each `wclsFoo()` inline function extracts a handler by stable method
|
||||
ID, casts it to the correct function pointer type, and calls it with
|
||||
a NULL check. This gives callers type-safe dispatch with the same
|
||||
codegen as a direct struct field call.
|
||||
|
||||
| Helper | Wraps Method ID |
|
||||
|--------|-----------------|
|
||||
| `wclsHas(w, methodId)` | Check if a method is implemented (non-NULL) |
|
||||
| `wclsPaint(w, d, ops, font, colors)` | `WGT_METHOD_PAINT` |
|
||||
| `wclsPaintOverlay(w, d, ops, font, colors)` | `WGT_METHOD_PAINT_OVERLAY` |
|
||||
| `wclsCalcMinSize(w, font)` | `WGT_METHOD_CALC_MIN_SIZE` |
|
||||
| `wclsLayout(w, font)` | `WGT_METHOD_LAYOUT` |
|
||||
| `wclsGetLayoutMetrics(w, font, pad, gap, extraTop, borderW)` | `WGT_METHOD_GET_LAYOUT_METRICS` |
|
||||
| `wclsOnMouse(w, root, vx, vy)` | `WGT_METHOD_ON_MOUSE` |
|
||||
| `wclsOnKey(w, key, mod)` | `WGT_METHOD_ON_KEY` |
|
||||
| `wclsOnAccelActivate(w, root)` | `WGT_METHOD_ON_ACCEL_ACTIVATE` |
|
||||
| `wclsDestroy(w)` | `WGT_METHOD_DESTROY` |
|
||||
| `wclsOnChildChanged(parent, child)` | `WGT_METHOD_ON_CHILD_CHANGED` |
|
||||
| `wclsGetText(w)` | `WGT_METHOD_GET_TEXT` |
|
||||
| `wclsSetText(w, text)` | `WGT_METHOD_SET_TEXT` |
|
||||
| `wclsClearSelection(w)` | `WGT_METHOD_CLEAR_SELECTION` |
|
||||
| `wclsClosePopup(w)` | `WGT_METHOD_CLOSE_POPUP` |
|
||||
| `wclsGetPopupRect(w, font, contentH, popX, popY, popW, popH)` | `WGT_METHOD_GET_POPUP_RECT` |
|
||||
| `wclsOnDragUpdate(w, root, x, y)` | `WGT_METHOD_ON_DRAG_UPDATE` |
|
||||
| `wclsOnDragEnd(w, root, x, y)` | `WGT_METHOD_ON_DRAG_END` |
|
||||
| `wclsGetCursorShape(w, vx, vy)` | `WGT_METHOD_GET_CURSOR_SHAPE` |
|
||||
| `wclsPoll(w, win)` | `WGT_METHOD_POLL` |
|
||||
| `wclsQuickRepaint(w, outY, outH)` | `WGT_METHOD_QUICK_REPAINT` |
|
||||
| `wclsScrollChildIntoView(parent, child)` | `WGT_METHOD_SCROLL_CHILD_INTO_VIEW` |
|
||||
|
||||
### WidgetClassT Flags
|
||||
|
||||
| Flag | Value | Description |
|
||||
|------|-------|-------------|
|
||||
| `WCLASS_FOCUSABLE` | 0x0001 | Can receive keyboard focus |
|
||||
| `WCLASS_BOX_CONTAINER` | 0x0002 | Uses VBox/HBox layout algorithm |
|
||||
| `WCLASS_HORIZ_CONTAINER` | 0x0004 | Lays out children horizontally |
|
||||
| `WCLASS_PAINTS_CHILDREN` | 0x0008 | Widget handles child rendering |
|
||||
| `WCLASS_NO_HIT_RECURSE` | 0x0010 | Hit testing stops here |
|
||||
| `WCLASS_FOCUS_FORWARD` | 0x0020 | Accel hit forwards focus to next focusable |
|
||||
| `WCLASS_HAS_POPUP` | 0x0040 | Has dropdown popup overlay |
|
||||
| `WCLASS_SCROLLABLE` | 0x0080 | Accepts mouse wheel events |
|
||||
| `WCLASS_SCROLL_CONTAINER` | 0x0100 | Scroll container (ScrollPane) |
|
||||
| `WCLASS_NEEDS_POLL` | 0x0200 | Needs periodic polling |
|
||||
| `WCLASS_SWALLOWS_TAB` | 0x0400 | Tab key goes to widget, not focus nav |
|
||||
| `WCLASS_RELAYOUT_ON_SCROLL` | 0x0800 | Full relayout on scrollbar drag |
|
||||
| `WCLASS_PRESS_RELEASE` | 0x1000 | Click = press+release (buttons) |
|
||||
| `WCLASS_ACCEL_WHEN_HIDDEN` | 0x2000 | Accel matching works when invisible |
|
||||
|
||||
|
||||
## Widget Registration
|
||||
|
||||
Each widget DXE exports `wgtRegister()`, called by the loader after
|
||||
`dlopen`. The WidgetClassT uses the `handlers[]` array indexed by
|
||||
method IDs:
|
||||
|
||||
```c
|
||||
static int32_t sButtonType;
|
||||
|
||||
static const WidgetClassT sButtonClass = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
|
||||
.handlers = {
|
||||
[WGT_METHOD_PAINT] = buttonPaint,
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = buttonCalcMinSize,
|
||||
[WGT_METHOD_ON_MOUSE] = buttonOnMouse,
|
||||
[WGT_METHOD_ON_KEY] = buttonOnKey,
|
||||
[WGT_METHOD_DESTROY] = buttonDestroy,
|
||||
[WGT_METHOD_GET_TEXT] = buttonGetText,
|
||||
[WGT_METHOD_SET_TEXT] = buttonSetText,
|
||||
[WGT_METHOD_ON_ACCEL_ACTIVATE] = buttonAccelActivate,
|
||||
}
|
||||
};
|
||||
|
||||
static const ButtonApiT sApi = { .create = buttonCreate };
|
||||
|
||||
void wgtRegister(void) {
|
||||
sButtonType = wgtRegisterClass(&sButtonClass);
|
||||
wgtRegisterApi("button", &sApi);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Per-Widget API Registry
|
||||
|
||||
The monolithic WidgetApiT is gone. Each widget registers a small API
|
||||
struct under a name via `wgtRegisterApi()`. Callers retrieve it via
|
||||
`wgtGetApi()` and cast to the widget-specific type. Per-widget headers
|
||||
(e.g. `widgetButton.h`) provide typed accessors and convenience macros:
|
||||
|
||||
```c
|
||||
// widgetButton.h
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
} ButtonApiT;
|
||||
|
||||
static inline const ButtonApiT *dvxButtonApi(void) {
|
||||
static const ButtonApiT *sApi;
|
||||
if (!sApi) { sApi = (const ButtonApiT *)wgtGetApi("button"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtButton(parent, text) dvxButtonApi()->create(parent, text)
|
||||
```
|
||||
|
||||
|
||||
## Tagged Size Values
|
||||
|
||||
Size hints encode both unit type and numeric value in a single int32_t:
|
||||
|
||||
| Macro | Encoding | Example |
|
||||
|-------|----------|---------|
|
||||
| `wgtPixels(v)` | Bits 31:30 = 00 | `w->minW = wgtPixels(200);` |
|
||||
| `wgtChars(v)` | Bits 31:30 = 01 | `w->minW = wgtChars(40);` |
|
||||
| `wgtPercent(v)` | Bits 31:30 = 10 | `w->minW = wgtPercent(50);` |
|
||||
| `0` | -- | Auto (use computed minimum) |
|
||||
|
||||
|
||||
## Layout Algorithm
|
||||
|
||||
Two-pass flexbox-like layout:
|
||||
|
||||
1. **Bottom-up** (`calcMinSize`): Each widget computes its minimum size.
|
||||
Containers sum children along the main axis, max across the cross axis.
|
||||
2. **Top-down** (`layout`): Available space is allocated to children.
|
||||
Extra space beyond minimum is distributed proportionally to weights.
|
||||
`weight=0` means fixed size, `weight=100` is the default flexible weight.
|
||||
|
||||
### Cross-Axis Centering
|
||||
|
||||
When a child widget has a `maxW` (in a VBox) or `maxH` (in an HBox)
|
||||
that constrains it smaller than the available cross-axis space, the
|
||||
layout engine automatically centers the child on the cross axis. This
|
||||
means setting `maxW` or `maxH` on a child inside a container will both
|
||||
cap its size and center it within the remaining space.
|
||||
|
||||
|
||||
## Image Loading
|
||||
|
||||
Two image loading functions are available:
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `dvxLoadImage(ctx, path, outW, outH, outPitch)` | Load from a file path |
|
||||
| `dvxLoadImageFromMemory(ctx, data, dataLen, outW, outH, outPitch)` | Load from a memory buffer (e.g. resource data) |
|
||||
|
||||
Both convert to the display's native pixel format. Caller frees the
|
||||
returned buffer with `dvxFreeImage()`. Supported formats: BMP, PNG,
|
||||
JPEG, GIF (via stb_image).
|
||||
|
||||
|
||||
## Exported Symbols
|
||||
|
||||
libdvx.lib exports symbols matching these prefixes:
|
||||
|
||||
```
|
||||
dvx*, wgt*, wm*, prefs*, rect*, draw*, pack*, text*, setClip*,
|
||||
resetClip*, stbi*, stbi_write*, dirtyList*, widget*,
|
||||
accelParse*, clipboard*, multiClick*,
|
||||
sCursor*, sDbl*, sDebug*, sClosed*, sFocused*, sKey*,
|
||||
sOpen*, sDrag*, sClosed*, sKey*
|
||||
```
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
make # builds bin/libs/libdvx.lib + bin/libs/libdvx.dep
|
||||
make clean # removes objects and library
|
||||
```
|
||||
|
||||
Depends on: `libtasks.lib` (via libdvx.dep).
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_app.h -- Layer 5: Application API for DVX GUI
|
||||
//
|
||||
// The topmost layer and the public-facing API for applications. Aggregates
|
||||
|
|
@ -35,9 +13,7 @@
|
|||
#ifndef DVX_APP_H
|
||||
#define DVX_APP_H
|
||||
|
||||
#include "dvxCur.h"
|
||||
#include "dvxTypes.h"
|
||||
#include "dvxPlat.h"
|
||||
#include "dvxVideo.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxComp.h"
|
||||
|
|
@ -66,7 +42,7 @@ typedef struct AppContextT {
|
|||
PopupStateT popup;
|
||||
SysMenuStateT sysMenu;
|
||||
KbMoveResizeT kbMoveResize;
|
||||
CursorT cursors[CURSOR_COUNT]; // indexed by CURSOR_xxx
|
||||
CursorT cursors[6]; // indexed by CURSOR_xxx
|
||||
int32_t cursorId; // active cursor shape
|
||||
uint32_t cursorFg; // pre-packed cursor colors
|
||||
uint32_t cursorBg;
|
||||
|
|
@ -82,10 +58,6 @@ typedef struct AppContextT {
|
|||
int32_t prevMouseX;
|
||||
int32_t prevMouseY;
|
||||
int32_t prevMouseButtons;
|
||||
// Set true when a menu/popup click consumed a mouse press; the
|
||||
// matching release is then swallowed so widgets underneath the
|
||||
// (now-closed) menu don't receive a stray click.
|
||||
bool suppressNextMouseUp;
|
||||
// Double-click detection for minimized window icons: timestamps and
|
||||
// window IDs track whether two clicks land on the same icon within
|
||||
// the system double-click interval.
|
||||
|
|
@ -108,8 +80,6 @@ typedef struct AppContextT {
|
|||
WindowT *modalWindow; // if non-NULL, only this window receives input
|
||||
void (*onCtrlEsc)(void *ctx); // system-wide Ctrl+Esc handler (e.g. task manager)
|
||||
void *ctrlEscCtx;
|
||||
void (*onF1)(void *ctx); // system-wide F1 handler (context-sensitive help)
|
||||
void *f1Ctx;
|
||||
void (*onTitleChange)(void *ctx); // called when any window title changes
|
||||
void *titleChangeCtx;
|
||||
int32_t currentAppId; // set by shell before calling app code (0 = shell)
|
||||
|
|
@ -130,7 +100,6 @@ typedef struct AppContextT {
|
|||
uint32_t charHeightRecip; // fixed-point 16.16 reciprocal of font.charHeight
|
||||
// Mouse configuration (loaded from preferences)
|
||||
int32_t wheelDirection; // 1 = normal, -1 = reversed
|
||||
int32_t wheelStep; // lines per wheel notch (1-10, default 3)
|
||||
clock_t dblClickTicks; // double-click speed in clock() ticks
|
||||
// Color scheme source RGB values (unpacked, for theme save/get)
|
||||
uint8_t colorRgb[ColorCountE][3];
|
||||
|
|
@ -142,7 +111,7 @@ typedef struct AppContextT {
|
|||
// kept so the image can be reloaded after a video mode or mode change.
|
||||
uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8)
|
||||
int32_t wallpaperPitch; // bytes per row
|
||||
char wallpaperPath[DVX_MAX_PATH]; // source image path (empty = none)
|
||||
char wallpaperPath[260]; // source image path (empty = none)
|
||||
WallpaperModeE wallpaperMode; // stretch, tile, or center
|
||||
} AppContextT;
|
||||
|
||||
|
|
@ -160,9 +129,7 @@ int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t request
|
|||
// Configure mouse behaviour. wheelDir: 1 = normal, -1 = reversed.
|
||||
// dblClickMs: double-click speed in milliseconds (e.g. 500).
|
||||
// accelThreshold: double-speed threshold in mickeys/sec (0 = don't change).
|
||||
// mickeyRatio: mickeys per 8 pixels (0 = don't change, 8 = default speed).
|
||||
// wheelStep: lines per wheel notch (1-10, default 3).
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio, int32_t wheelStep);
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold);
|
||||
|
||||
// ============================================================
|
||||
// Color scheme
|
||||
|
|
@ -235,24 +202,10 @@ WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w,
|
|||
// Destroy a window, free all its resources, and dirty its former region.
|
||||
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Raise a window to the top of the z-order and give it focus.
|
||||
void dvxRaiseWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Display a context menu at a screen position. `menu` must be a
|
||||
// standalone MenuT allocated with wmCreateMenu() and populated with
|
||||
// wmAddMenuItem() / wmAddMenuSeparator() / wmAddSubMenu(). `win`
|
||||
// supplies the onMenu dispatch target; menu-item clicks fire
|
||||
// win->onMenu(win, id) exactly like menu-bar items. Position is
|
||||
// clamped to screen edges.
|
||||
void dvxShowContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t screenX, int32_t screenY);
|
||||
|
||||
// Resize a window to exactly fit its widget tree's computed minimum size
|
||||
// (plus chrome). Used for dialog boxes and other fixed-layout windows
|
||||
// where the window should shrink-wrap its content.
|
||||
void dvxFitWindow(AppContextT *ctx, WindowT *win);
|
||||
void dvxFitWindowW(AppContextT *ctx, WindowT *win);
|
||||
void dvxFitWindowH(AppContextT *ctx, WindowT *win);
|
||||
void dvxResizeWindow(AppContextT *ctx, WindowT *win, int32_t newW, int32_t newH);
|
||||
|
||||
// Mark a sub-region of a window's content area as needing repaint. The
|
||||
// coordinates are relative to the content area, not the screen. The
|
||||
|
|
@ -263,12 +216,6 @@ void dvxInvalidateRect(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int
|
|||
// Mark the entire window content area as dirty.
|
||||
void dvxInvalidateWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Hide a window without destroying it (marks exposed region dirty).
|
||||
void dvxHideWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Show a previously hidden window (marks region dirty for repaint).
|
||||
void dvxShowWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Minimize a window (show as icon at bottom of screen)
|
||||
void dvxMinimizeWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
|
|
@ -346,19 +293,9 @@ uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, i
|
|||
// format as dvxLoadImage. Caller must free the result with dvxFreeImage.
|
||||
uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch);
|
||||
|
||||
// Load an image with alpha transparency support. Pixels with alpha < 128
|
||||
// are replaced with the key color (packed magenta). If any transparent
|
||||
// pixels were found, *outKeyColor receives the packed key color and
|
||||
// *outHasAlpha is set to true. Caller must free with dvxFreeImage.
|
||||
uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch, bool *outHasAlpha, uint32_t *outKeyColor);
|
||||
|
||||
// Free a pixel buffer returned by dvxLoadImage.
|
||||
void dvxFreeImage(uint8_t *data);
|
||||
|
||||
// Query image dimensions without decoding the full file.
|
||||
// Returns true on success, false if the file can't be read.
|
||||
bool dvxImageInfo(const char *path, int32_t *outW, int32_t *outH);
|
||||
|
||||
// Save native-format pixel data to a PNG file. The pixel data must be
|
||||
// in the display's native format (as returned by dvxLoadImage or
|
||||
// captured from a content buffer). Returns 0 on success, -1 on failure.
|
||||
|
|
@ -374,37 +311,4 @@ void dvxClipboardCopy(const char *text, int32_t len);
|
|||
// NULL if the clipboard is empty.
|
||||
const char *dvxClipboardGet(int32_t *outLen);
|
||||
|
||||
// ============================================================
|
||||
// Resource loading helpers
|
||||
// ============================================================
|
||||
//
|
||||
// Convenience functions for loading typed resources from DXE files.
|
||||
// These combine dvxResOpen/Read/Close with type-specific processing
|
||||
// so callers don't need to manage the resource handle or temp buffers.
|
||||
|
||||
// Load an icon/image resource and decode it to native pixel format.
|
||||
// Returns the decoded pixel data (caller must free with dvxFreeImage),
|
||||
// or NULL if not found. Sets outW/outH/outPitch on success.
|
||||
uint8_t *dvxResLoadIcon(AppContextT *ctx, const char *dxePath, const char *resName, int32_t *outW, int32_t *outH, int32_t *outPitch);
|
||||
|
||||
// Load a text resource into a caller-provided buffer.
|
||||
// Returns true on success. The text is null-terminated and truncated
|
||||
// to fit bufSize. Returns false if the resource is not found.
|
||||
bool dvxResLoadText(const char *dxePath, const char *resName, char *buf, int32_t bufSize);
|
||||
|
||||
// Load a raw binary resource. Returns a malloc'd buffer that the
|
||||
// caller must free, or NULL if not found. Sets *outSize to the
|
||||
// data size in bytes.
|
||||
void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize);
|
||||
|
||||
// ============================================================
|
||||
// Text hash for dirty tracking
|
||||
// ============================================================
|
||||
//
|
||||
// djb2-xor hash for cheap dirty detection. Compare the hash at save
|
||||
// time with the current hash to detect changes without keeping a
|
||||
// shadow copy of the text. Not cryptographic.
|
||||
|
||||
uint32_t dvxTextHash(const char *text);
|
||||
|
||||
#endif // DVX_APP_H
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_comp.c -- Layer 3: Dirty rectangle compositor for DVX GUI (optimized)
|
||||
//
|
||||
// This layer implements dirty rectangle tracking and merging. The compositor
|
||||
|
|
@ -40,7 +18,7 @@
|
|||
// exactly once per pixel per frame.
|
||||
|
||||
#include "dvxComp.h"
|
||||
#include "dvxPlat.h"
|
||||
#include "dvxPlatform.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -49,9 +27,10 @@
|
|||
// Rects within this many pixels of each other get merged even if they don't
|
||||
// overlap. A small gap tolerance absorbs jitter from mouse movement and
|
||||
// closely-spaced UI invalidations (e.g. title bar + content during a drag)
|
||||
// without bloating merged rects excessively. Derived from CHROME_BORDER_WIDTH
|
||||
// so adjacent chrome/content invalidations merge naturally.
|
||||
#define DIRTY_MERGE_GAP CHROME_BORDER_WIDTH
|
||||
// without bloating merged rects excessively. The value 4 was chosen to match
|
||||
// the chrome border width -- adjacent chrome/content invalidations merge
|
||||
// naturally.
|
||||
#define DIRTY_MERGE_GAP 4
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
|
|
@ -61,6 +40,10 @@ static inline bool rectsOverlapOrAdjacent(const RectT *a, const RectT *b, int32_
|
|||
static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dirtyListAdd
|
||||
// ============================================================
|
||||
//
|
||||
// Appends a dirty rect to the list. The array grows dynamically but a
|
||||
// merge pass fires at 128 entries to keep the list short. If the list
|
||||
// is still long after merging (pathological scatter), everything collapses
|
||||
|
|
@ -115,22 +98,28 @@ void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dirtyListClear
|
||||
// ============================================================
|
||||
|
||||
void dirtyListClear(DirtyListT *dl) {
|
||||
dl->count = 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dirtyListInit
|
||||
// ============================================================
|
||||
|
||||
void dirtyListInit(DirtyListT *dl) {
|
||||
dl->count = 0;
|
||||
// Pre-allocate to the soft cap so per-frame dirtyListAdd never reallocs.
|
||||
// Worth the ~1KB of heap to take realloc out of the hot path entirely.
|
||||
if (!dl->rects) {
|
||||
dl->rects = (RectT *)malloc(DIRTY_SOFT_CAP * sizeof(RectT));
|
||||
dl->cap = dl->rects ? DIRTY_SOFT_CAP : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dirtyListMerge
|
||||
// ============================================================
|
||||
//
|
||||
// Coalesces overlapping or nearby dirty rects to reduce the number of
|
||||
// composite+flush passes. The trade-off: merging two rects into their
|
||||
// bounding box may add "clean" pixels that get needlessly repainted, but
|
||||
|
|
@ -179,6 +168,10 @@ void dirtyListMerge(DirtyListT *dl) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// flushRect
|
||||
// ============================================================
|
||||
//
|
||||
// Copies one dirty rect from the system RAM backbuffer to the VESA LFB.
|
||||
// This is the single most bandwidth-sensitive operation in the entire GUI:
|
||||
// the LFB lives behind the ISA/PCI bus, so every byte written here is a
|
||||
|
|
@ -194,6 +187,10 @@ void flushRect(DisplayT *d, const RectT *r) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// rectIntersect
|
||||
// ============================================================
|
||||
//
|
||||
// Used heavily in the compositing loop to test whether a window overlaps
|
||||
// a dirty rect before painting it. The branch hint marks the non-overlapping
|
||||
// case as unlikely because the compositing loop already does a coarse AABB
|
||||
|
|
@ -219,11 +216,19 @@ bool rectIntersect(const RectT *a, const RectT *b, RectT *result) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// rectIsEmpty
|
||||
// ============================================================
|
||||
|
||||
bool rectIsEmpty(const RectT *r) {
|
||||
return (r->w <= 0 || r->h <= 0);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// rectsOverlapOrAdjacent
|
||||
// ============================================================
|
||||
//
|
||||
// Separating-axis test with a gap tolerance. Two rects merge if they
|
||||
// overlap OR if the gap between them is <= DIRTY_MERGE_GAP pixels.
|
||||
// The gap tolerance is the key tuning parameter for the merge algorithm:
|
||||
|
|
@ -242,6 +247,10 @@ static inline bool rectsOverlapOrAdjacent(const RectT *a, const RectT *b, int32_
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// rectUnion
|
||||
// ============================================================
|
||||
//
|
||||
// Axis-aligned bounding box of two rects. Supports in-place operation
|
||||
// (result == a) for the merge loop. Note that this may grow the rect
|
||||
// substantially if the two inputs are far apart -- this is the inherent
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_comp.h -- Layer 3: Dirty rectangle compositor for DVX GUI
|
||||
//
|
||||
// The compositor tracks which screen regions have changed and ensures
|
||||
|
|
@ -1,26 +1,4 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvxCur.h -- Embedded mouse cursor bitmaps for DVX GUI
|
||||
// dvxCursor.h -- Embedded mouse cursor bitmaps for DVX GUI
|
||||
//
|
||||
// All cursor shapes are compiled in as static const data -- no external
|
||||
// cursor files to load. This is intentional: the cursors are needed
|
||||
|
|
@ -36,8 +14,8 @@
|
|||
// The cursor table (dvxCursors[]) at the bottom provides all five shapes
|
||||
// in an array indexed by CURSOR_xxx constants, with hotspot coordinates
|
||||
// set appropriately (arrow hot spot at tip, resize cursors at center).
|
||||
#ifndef DVX_CUR_H
|
||||
#define DVX_CUR_H
|
||||
#ifndef DVX_CURSOR_H
|
||||
#define DVX_CURSOR_H
|
||||
|
||||
#include "dvxTypes.h"
|
||||
|
||||
|
|
@ -51,8 +29,7 @@
|
|||
#define CURSOR_RESIZE_DIAG_NWSE 3 // NW-SE diagonal (top-left / bottom-right)
|
||||
#define CURSOR_RESIZE_DIAG_NESW 4 // NE-SW diagonal (top-right / bottom-left)
|
||||
#define CURSOR_BUSY 5 // hourglass (wait)
|
||||
#define CURSOR_CROSSHAIR 6 // crosshair (+) for placement
|
||||
#define CURSOR_COUNT 7
|
||||
#define CURSOR_COUNT 6
|
||||
|
||||
// ============================================================
|
||||
// Standard arrow cursor, 16x16
|
||||
|
|
@ -377,50 +354,6 @@ static const uint16_t cursorBusyXor[16] = {
|
|||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Crosshair cursor (+), 16x16
|
||||
// ============================================================
|
||||
// Hot spot at center (7, 7)
|
||||
|
||||
static const uint16_t cursorCrosshairAnd[16] = {
|
||||
0xFFFF, // row 0
|
||||
0xFC3F, // 1111110000111111 row 1 black outline top
|
||||
0xFC3F, // 1111110000111111 row 2
|
||||
0xFC3F, // 1111110000111111 row 3
|
||||
0xFC3F, // 1111110000111111 row 4
|
||||
0xFC3F, // 1111110000111111 row 5
|
||||
0x0000, // 0000000000000000 row 6 outline + horizontal bar
|
||||
0x0000, // 0000000000000000 row 7 horizontal bar center
|
||||
0x0000, // 0000000000000000 row 8 outline + horizontal bar
|
||||
0xFC3F, // 1111110000111111 row 9
|
||||
0xFC3F, // 1111110000111111 row 10
|
||||
0xFC3F, // 1111110000111111 row 11
|
||||
0xFC3F, // 1111110000111111 row 12
|
||||
0xFC3F, // 1111110000111111 row 13 black outline bottom
|
||||
0xFFFF, // row 14
|
||||
0xFFFF // row 15
|
||||
};
|
||||
|
||||
static const uint16_t cursorCrosshairXor[16] = {
|
||||
0x0000, // row 0
|
||||
0x0180, // 0000000110000000 row 1 white inner
|
||||
0x0180, // row 2
|
||||
0x0180, // row 3
|
||||
0x0180, // row 4
|
||||
0x0180, // row 5
|
||||
0x0180, // 0000000110000000 row 6 white inner (black on sides)
|
||||
0x3FFC, // 0011111111111100 row 7 white horizontal (black edges)
|
||||
0x0180, // 0000000110000000 row 8 white inner (black on sides)
|
||||
0x0180, // row 9
|
||||
0x0180, // row 10
|
||||
0x0180, // row 11
|
||||
0x0180, // row 12
|
||||
0x0180, // row 13 white inner
|
||||
0x0000, // row 14
|
||||
0x0000 // row 15
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Cursor table
|
||||
// ============================================================
|
||||
|
|
@ -432,11 +365,10 @@ static const CursorT dvxCursors[CURSOR_COUNT] = {
|
|||
{ 16, 16, 7, 7, cursorResizeDiagNWSEAnd, cursorResizeDiagNWSEXor }, // CURSOR_RESIZE_DIAG_NWSE
|
||||
{ 16, 16, 7, 7, cursorResizeDiagNESWAnd, cursorResizeDiagNESWXor }, // CURSOR_RESIZE_DIAG_NESW
|
||||
{ 16, 16, 7, 7, cursorBusyAnd, cursorBusyXor }, // CURSOR_BUSY
|
||||
{ 16, 16, 7, 7, cursorCrosshairAnd, cursorCrosshairXor }, // CURSOR_CROSSHAIR
|
||||
};
|
||||
|
||||
// Legacy alias -- kept for backward compatibility with code that predates
|
||||
// the multi-cursor support.
|
||||
static const CursorT dvxCursor = { 16, 16, 0, 0, cursorArrowAnd, cursorArrowXor };
|
||||
|
||||
#endif // DVX_CUR_H
|
||||
#endif // DVX_CURSOR_H
|
||||
File diff suppressed because it is too large
Load diff
80
core/dvxDialog.h
Normal file
80
core/dvxDialog.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// dvxDialog.h -- Modal dialogs for DVX GUI
|
||||
//
|
||||
// Provides pre-built modal dialog boxes (message box, file dialog) that
|
||||
// block the caller and run their own event loop via dvxUpdate() until the
|
||||
// user dismisses them. Modal dialogs set ctx->modalWindow to prevent input
|
||||
// from reaching other windows during the dialog's lifetime.
|
||||
//
|
||||
// The flag encoding uses separate bit fields for button configuration
|
||||
// (low nibble) and icon type (high nibble) so they can be OR'd together
|
||||
// in a single int32_t argument, matching the Win16 MessageBox() convention.
|
||||
#ifndef DVX_DIALOG_H
|
||||
#define DVX_DIALOG_H
|
||||
|
||||
#include "dvxApp.h"
|
||||
|
||||
// ============================================================
|
||||
// Message box button flags (low nibble)
|
||||
// ============================================================
|
||||
|
||||
#define MB_OK 0x0000
|
||||
#define MB_OKCANCEL 0x0001
|
||||
#define MB_YESNO 0x0002
|
||||
#define MB_YESNOCANCEL 0x0003
|
||||
#define MB_RETRYCANCEL 0x0004
|
||||
|
||||
// ============================================================
|
||||
// Message box icon flags (high nibble, OR with button flags)
|
||||
// ============================================================
|
||||
|
||||
#define MB_ICONINFO 0x0010
|
||||
#define MB_ICONWARNING 0x0020
|
||||
#define MB_ICONERROR 0x0030
|
||||
#define MB_ICONQUESTION 0x0040
|
||||
|
||||
// ============================================================
|
||||
// Message box return values
|
||||
// ============================================================
|
||||
|
||||
#define ID_OK 1
|
||||
#define ID_CANCEL 2
|
||||
#define ID_YES 3
|
||||
#define ID_NO 4
|
||||
#define ID_RETRY 5
|
||||
|
||||
// Display a modal message box with the specified button and icon combination.
|
||||
// Blocks the caller by running dvxUpdate() in a loop until a button is
|
||||
// pressed or the dialog is closed. Returns the ID_xxx value of the button
|
||||
// that was pressed. The dialog window is automatically destroyed on return.
|
||||
int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags);
|
||||
|
||||
// ============================================================
|
||||
// File dialog flags
|
||||
// ============================================================
|
||||
|
||||
#define FD_OPEN 0x0000 // Open file (default)
|
||||
#define FD_SAVE 0x0001 // Save file
|
||||
|
||||
// ============================================================
|
||||
// File dialog filter
|
||||
// ============================================================
|
||||
//
|
||||
// Filters are displayed in a dropdown at the bottom of the file dialog.
|
||||
// Pattern matching is case-insensitive and supports only single glob
|
||||
// patterns (no semicolon-separated lists). This keeps the matching code
|
||||
// trivial for a DOS filesystem where filenames are short and simple.
|
||||
|
||||
typedef struct {
|
||||
const char *label; // e.g. "Text Files (*.txt)"
|
||||
const char *pattern; // e.g. "*.txt" (case-insensitive, single pattern)
|
||||
} FileFilterT;
|
||||
|
||||
// Display a modal file open/save dialog. The dialog shows a directory
|
||||
// listing with navigation (parent directory, drive letters on DOS), a
|
||||
// filename text input, and an optional filter dropdown. Blocks the caller
|
||||
// via dvxUpdate() loop. Returns true if the user selected a file (path
|
||||
// written to outPath), false if cancelled or closed. initialDir may be
|
||||
// NULL to start in the current working directory.
|
||||
bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);
|
||||
|
||||
#endif // DVX_DIALOG_H
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_draw.c -- Layer 2: Drawing primitives for DVX GUI (optimized)
|
||||
//
|
||||
// This is the second layer of the DVX compositor stack, sitting on top
|
||||
|
|
@ -72,8 +50,7 @@
|
|||
// unlikely, helping the branch predictor on Pentium and later.
|
||||
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxPal.h"
|
||||
#include "dvxPlat.h"
|
||||
#include "dvxPlatform.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -504,6 +481,175 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawTextN
|
||||
// ============================================================
|
||||
//
|
||||
// Renders exactly 'count' characters from a buffer in one pass.
|
||||
// Same idea as drawTermRow but for uniform fg/bg text runs.
|
||||
// Avoids per-character function call overhead, redundant clip
|
||||
// calculation, and spanFill startup costs.
|
||||
//
|
||||
// The key optimization over calling drawChar() in a loop is the
|
||||
// bg fill strategy: in opaque mode, instead of calling spanFill
|
||||
// once per character cell per row (count * charHeight spanFill
|
||||
// calls), we fill the entire visible span's background in one
|
||||
// spanFill per scanline (just charHeight calls total). Then we
|
||||
// overlay only the fg glyph pixels. For an 80-column line this
|
||||
// reduces spanFill calls from 80*16=1280 to just 16. Each
|
||||
// spanFill maps to a single rep stosl, so we're also getting
|
||||
// better write-combine utilization from the larger sequential
|
||||
// stores.
|
||||
//
|
||||
// Horizontal clipping is done at the character level (firstChar/
|
||||
// lastChar) to avoid iterating invisible characters, with per-pixel
|
||||
// edge clipping only for the partially visible first and last chars.
|
||||
|
||||
void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t fg, uint32_t bg, bool opaque) {
|
||||
if (count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t cw = font->charWidth;
|
||||
int32_t ch = font->charHeight;
|
||||
int32_t bpp = ops->bytesPerPixel;
|
||||
int32_t pitch = d->pitch;
|
||||
|
||||
// Row-level clip: reject if entirely outside vertically
|
||||
int32_t clipX1 = d->clipX;
|
||||
int32_t clipX2 = d->clipX + d->clipW;
|
||||
int32_t clipY1 = d->clipY;
|
||||
int32_t clipY2 = d->clipY + d->clipH;
|
||||
|
||||
if (y + ch <= clipY1 || y >= clipY2) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t totalW = count * cw;
|
||||
|
||||
if (x + totalW <= clipX1 || x >= clipX2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vertical clip for glyph scanlines
|
||||
int32_t rowStart = 0;
|
||||
int32_t rowEnd = ch;
|
||||
if (y < clipY1) { rowStart = clipY1 - y; }
|
||||
if (y + ch > clipY2) { rowEnd = clipY2 - y; }
|
||||
|
||||
// Horizontal clip: find first and last visible column (character index)
|
||||
int32_t firstChar = 0;
|
||||
int32_t lastChar = count;
|
||||
|
||||
if (x < clipX1) {
|
||||
firstChar = (clipX1 - x) / cw;
|
||||
}
|
||||
|
||||
if (x + totalW > clipX2) {
|
||||
lastChar = (clipX2 - x + cw - 1) / cw;
|
||||
if (lastChar > count) { lastChar = count; }
|
||||
}
|
||||
|
||||
// Per-pixel clip for partially visible edge characters
|
||||
int32_t edgeColStart = 0;
|
||||
|
||||
if (x + firstChar * cw < clipX1) {
|
||||
edgeColStart = clipX1 - (x + firstChar * cw);
|
||||
}
|
||||
|
||||
if (opaque) {
|
||||
// Opaque: fill background for the entire visible span once per scanline,
|
||||
// then overlay foreground glyph pixels
|
||||
int32_t fillX1 = x + firstChar * cw;
|
||||
int32_t fillX2 = x + lastChar * cw;
|
||||
|
||||
if (fillX1 < clipX1) { fillX1 = clipX1; }
|
||||
if (fillX2 > clipX2) { fillX2 = clipX2; }
|
||||
|
||||
int32_t fillW = fillX2 - fillX1;
|
||||
|
||||
if (fillW > 0) {
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t *dst = d->backBuf + (y + row) * pitch + fillX1 * bpp;
|
||||
ops->spanFill(dst, bg, fillW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render glyph foreground pixels
|
||||
for (int32_t ci = firstChar; ci < lastChar; ci++) {
|
||||
int32_t cx = x + ci * cw;
|
||||
|
||||
int32_t cStart = 0;
|
||||
int32_t cEnd = cw;
|
||||
|
||||
if (ci == firstChar) {
|
||||
cStart = edgeColStart;
|
||||
}
|
||||
|
||||
if (cx + cw > clipX2) {
|
||||
cEnd = clipX2 - cx;
|
||||
}
|
||||
|
||||
int32_t idx = (uint8_t)text[ci] - font->firstChar;
|
||||
const uint8_t *glyph = NULL;
|
||||
|
||||
if (idx >= 0 && idx < font->numChars) {
|
||||
glyph = font->glyphData + idx * ch;
|
||||
}
|
||||
|
||||
if (!glyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bpp == 2) {
|
||||
uint16_t fg16 = (uint16_t)fg;
|
||||
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint16_t *dst = (uint16_t *)(d->backBuf + (y + row) * pitch + cx * 2);
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg16;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (bpp == 4) {
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint32_t *dst = (uint32_t *)(d->backBuf + (y + row) * pitch + cx * 4);
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint8_t fg8 = (uint8_t)fg;
|
||||
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint8_t *dst = d->backBuf + (y + row) * pitch + cx;
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawFocusRect
|
||||
// ============================================================
|
||||
|
|
@ -588,65 +734,6 @@ void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// calcCenteredText
|
||||
// ============================================================
|
||||
//
|
||||
// Shared by every widget that centers a text label in its own rect
|
||||
// (buttons, checkbox labels, etc.). Centralising the arithmetic
|
||||
// avoids tiny off-by-one drift from copy-paste variations.
|
||||
|
||||
void calcCenteredText(const BitmapFontT *font, int32_t rectX, int32_t rectY, int32_t rectW, int32_t rectH, const char *text, int32_t *outX, int32_t *outY) {
|
||||
int32_t textW = textWidthAccel(font, text);
|
||||
|
||||
if (outX) {
|
||||
*outX = rectX + (rectW - textW) / 2;
|
||||
}
|
||||
|
||||
if (outY) {
|
||||
*outY = rectY + (rectH - font->charHeight) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawPressableBevel
|
||||
// ============================================================
|
||||
//
|
||||
// Standard 2px button / toggle bevel. Swaps highlight and shadow when
|
||||
// pressed to create the sunken look. face fills the interior (pass 0
|
||||
// to leave the underlying content alone, e.g. for transparent toggle
|
||||
// buttons).
|
||||
|
||||
void drawPressableBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, bool pressed, uint32_t face, const ColorSchemeT *colors) {
|
||||
BevelStyleT bevel;
|
||||
bevel.highlight = pressed ? colors->windowShadow : colors->windowHighlight;
|
||||
bevel.shadow = pressed ? colors->windowHighlight : colors->windowShadow;
|
||||
bevel.face = face;
|
||||
bevel.width = 2;
|
||||
drawBevel(d, ops, x, y, w, h, &bevel);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawWidgetTextAccel
|
||||
// ============================================================
|
||||
//
|
||||
// Every text-bearing widget paints this same enabled/disabled branch.
|
||||
// Centralising it keeps the embossed-disabled appearance consistent
|
||||
// and removes ~5 lines of boilerplate per widget.
|
||||
|
||||
void drawWidgetTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, uint32_t fg, uint32_t bg, bool opaque, bool enabled, const ColorSchemeT *colors) {
|
||||
if (!enabled) {
|
||||
drawTextAccel(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
|
||||
drawTextAccel(d, ops, font, x, y, text, colors->windowShadow, 0, false);
|
||||
return;
|
||||
}
|
||||
|
||||
drawTextAccel(d, ops, font, x, y, text, fg, bg, opaque);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawInit
|
||||
// ============================================================
|
||||
|
|
@ -1045,175 +1132,6 @@ void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, in
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawTextN
|
||||
// ============================================================
|
||||
//
|
||||
// Renders exactly 'count' characters from a buffer in one pass.
|
||||
// Same idea as drawTermRow but for uniform fg/bg text runs.
|
||||
// Avoids per-character function call overhead, redundant clip
|
||||
// calculation, and spanFill startup costs.
|
||||
//
|
||||
// The key optimization over calling drawChar() in a loop is the
|
||||
// bg fill strategy: in opaque mode, instead of calling spanFill
|
||||
// once per character cell per row (count * charHeight spanFill
|
||||
// calls), we fill the entire visible span's background in one
|
||||
// spanFill per scanline (just charHeight calls total). Then we
|
||||
// overlay only the fg glyph pixels. For an 80-column line this
|
||||
// reduces spanFill calls from 80*16=1280 to just 16. Each
|
||||
// spanFill maps to a single rep stosl, so we're also getting
|
||||
// better write-combine utilization from the larger sequential
|
||||
// stores.
|
||||
//
|
||||
// Horizontal clipping is done at the character level (firstChar/
|
||||
// lastChar) to avoid iterating invisible characters, with per-pixel
|
||||
// edge clipping only for the partially visible first and last chars.
|
||||
|
||||
void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t fg, uint32_t bg, bool opaque) {
|
||||
if (count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t cw = font->charWidth;
|
||||
int32_t ch = font->charHeight;
|
||||
int32_t bpp = ops->bytesPerPixel;
|
||||
int32_t pitch = d->pitch;
|
||||
|
||||
// Row-level clip: reject if entirely outside vertically
|
||||
int32_t clipX1 = d->clipX;
|
||||
int32_t clipX2 = d->clipX + d->clipW;
|
||||
int32_t clipY1 = d->clipY;
|
||||
int32_t clipY2 = d->clipY + d->clipH;
|
||||
|
||||
if (y + ch <= clipY1 || y >= clipY2) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t totalW = count * cw;
|
||||
|
||||
if (x + totalW <= clipX1 || x >= clipX2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vertical clip for glyph scanlines
|
||||
int32_t rowStart = 0;
|
||||
int32_t rowEnd = ch;
|
||||
if (y < clipY1) { rowStart = clipY1 - y; }
|
||||
if (y + ch > clipY2) { rowEnd = clipY2 - y; }
|
||||
|
||||
// Horizontal clip: find first and last visible column (character index)
|
||||
int32_t firstChar = 0;
|
||||
int32_t lastChar = count;
|
||||
|
||||
if (x < clipX1) {
|
||||
firstChar = (clipX1 - x) / cw;
|
||||
}
|
||||
|
||||
if (x + totalW > clipX2) {
|
||||
lastChar = (clipX2 - x + cw - 1) / cw;
|
||||
if (lastChar > count) { lastChar = count; }
|
||||
}
|
||||
|
||||
// Per-pixel clip for partially visible edge characters
|
||||
int32_t edgeColStart = 0;
|
||||
|
||||
if (x + firstChar * cw < clipX1) {
|
||||
edgeColStart = clipX1 - (x + firstChar * cw);
|
||||
}
|
||||
|
||||
if (opaque) {
|
||||
// Opaque: fill background for the entire visible span once per scanline,
|
||||
// then overlay foreground glyph pixels
|
||||
int32_t fillX1 = x + firstChar * cw;
|
||||
int32_t fillX2 = x + lastChar * cw;
|
||||
|
||||
if (fillX1 < clipX1) { fillX1 = clipX1; }
|
||||
if (fillX2 > clipX2) { fillX2 = clipX2; }
|
||||
|
||||
int32_t fillW = fillX2 - fillX1;
|
||||
|
||||
if (fillW > 0) {
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t *dst = d->backBuf + (y + row) * pitch + fillX1 * bpp;
|
||||
ops->spanFill(dst, bg, fillW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render glyph foreground pixels
|
||||
for (int32_t ci = firstChar; ci < lastChar; ci++) {
|
||||
int32_t cx = x + ci * cw;
|
||||
|
||||
int32_t cStart = 0;
|
||||
int32_t cEnd = cw;
|
||||
|
||||
if (ci == firstChar) {
|
||||
cStart = edgeColStart;
|
||||
}
|
||||
|
||||
if (cx + cw > clipX2) {
|
||||
cEnd = clipX2 - cx;
|
||||
}
|
||||
|
||||
int32_t idx = (uint8_t)text[ci] - font->firstChar;
|
||||
const uint8_t *glyph = NULL;
|
||||
|
||||
if (idx >= 0 && idx < font->numChars) {
|
||||
glyph = font->glyphData + idx * ch;
|
||||
}
|
||||
|
||||
if (!glyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bpp == 2) {
|
||||
uint16_t fg16 = (uint16_t)fg;
|
||||
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint16_t *dst = (uint16_t *)(d->backBuf + (y + row) * pitch + cx * 2);
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg16;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (bpp == 4) {
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint32_t *dst = (uint32_t *)(d->backBuf + (y + row) * pitch + cx * 4);
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint8_t fg8 = (uint8_t)fg;
|
||||
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint8_t *dst = d->backBuf + (y + row) * pitch + cx;
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawVLine
|
||||
// ============================================================
|
||||
|
|
@ -1345,157 +1263,6 @@ void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, cons
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// rectCopyGrayscale
|
||||
// ============================================================
|
||||
|
||||
void rectCopyGrayscale(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h) {
|
||||
int32_t bpp = ops->bytesPerPixel;
|
||||
|
||||
int32_t origDstX = dstX;
|
||||
int32_t origDstY = dstY;
|
||||
|
||||
clipRect(d, &dstX, &dstY, &w, &h);
|
||||
|
||||
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
srcX += dstX - origDstX;
|
||||
srcY += dstY - origDstY;
|
||||
|
||||
if (bpp == 1 && d->palette) {
|
||||
// 8-bit indexed: look up RGB from palette, compute gray, find nearest
|
||||
const uint8_t *pal = d->palette;
|
||||
|
||||
for (int32_t row = 0; row < h; row++) {
|
||||
const uint8_t *src = srcBuf + (srcY + row) * srcPitch + srcX;
|
||||
uint8_t *dst = d->backBuf + (dstY + row) * d->pitch + dstX;
|
||||
|
||||
for (int32_t col = 0; col < w; col++) {
|
||||
uint8_t idx = *src++;
|
||||
uint32_t r8 = pal[idx * 3 + 0];
|
||||
uint32_t g8 = pal[idx * 3 + 1];
|
||||
uint32_t b8 = pal[idx * 3 + 2];
|
||||
uint32_t gray = (r8 * 77 + g8 * 150 + b8 * 29) >> 8;
|
||||
*dst++ = dvxNearestPalEntry(pal, (uint8_t)gray, (uint8_t)gray, (uint8_t)gray);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 16/32-bit direct color: unpack, convert, repack
|
||||
int32_t rShift = d->format.redShift;
|
||||
int32_t gShift = d->format.greenShift;
|
||||
int32_t bShift = d->format.blueShift;
|
||||
int32_t rBits = d->format.redBits;
|
||||
int32_t gBits = d->format.greenBits;
|
||||
int32_t bBits = d->format.blueBits;
|
||||
uint32_t rMask = ((1 << rBits) - 1) << rShift;
|
||||
uint32_t gMask = ((1 << gBits) - 1) << gShift;
|
||||
uint32_t bMask = ((1 << bBits) - 1) << bShift;
|
||||
|
||||
for (int32_t row = 0; row < h; row++) {
|
||||
const uint8_t *src = srcBuf + (srcY + row) * srcPitch + srcX * bpp;
|
||||
uint8_t *dst = d->backBuf + (dstY + row) * d->pitch + dstX * bpp;
|
||||
|
||||
for (int32_t col = 0; col < w; col++) {
|
||||
uint32_t px;
|
||||
|
||||
if (bpp == 2) {
|
||||
px = *(const uint16_t *)src;
|
||||
} else {
|
||||
px = *(const uint32_t *)src;
|
||||
}
|
||||
|
||||
uint32_t r8 = ((px & rMask) >> rShift) << (8 - rBits);
|
||||
uint32_t g8 = ((px & gMask) >> gShift) << (8 - gBits);
|
||||
uint32_t b8 = ((px & bMask) >> bShift) << (8 - bBits);
|
||||
uint32_t gray = (r8 * 77 + g8 * 150 + b8 * 29) >> 8;
|
||||
|
||||
uint32_t grayPx = ((gray >> (8 - rBits)) << rShift) |
|
||||
((gray >> (8 - gBits)) << gShift) |
|
||||
((gray >> (8 - bBits)) << bShift);
|
||||
|
||||
if (bpp == 2) {
|
||||
*(uint16_t *)dst = (uint16_t)grayPx;
|
||||
} else {
|
||||
*(uint32_t *)dst = grayPx;
|
||||
}
|
||||
|
||||
src += bpp;
|
||||
dst += bpp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// rectCopyTransparent
|
||||
// ============================================================
|
||||
//
|
||||
// Per-pixel color-key blit. Pixels matching keyColor are skipped,
|
||||
// leaving the destination unchanged (background shows through).
|
||||
// Used for PNG images with alpha transparency converted to key color.
|
||||
|
||||
void rectCopyTransparent(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h, uint32_t keyColor) {
|
||||
int32_t bpp = ops->bytesPerPixel;
|
||||
|
||||
int32_t origDstX = dstX;
|
||||
int32_t origDstY = dstY;
|
||||
|
||||
clipRect(d, &dstX, &dstY, &w, &h);
|
||||
|
||||
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
srcX += dstX - origDstX;
|
||||
srcY += dstY - origDstY;
|
||||
|
||||
for (int32_t row = 0; row < h; row++) {
|
||||
const uint8_t *src = srcBuf + (srcY + row) * srcPitch + srcX * bpp;
|
||||
uint8_t *dst = d->backBuf + (dstY + row) * d->pitch + dstX * bpp;
|
||||
|
||||
if (bpp == 1) {
|
||||
uint8_t key8 = (uint8_t)keyColor;
|
||||
|
||||
for (int32_t col = 0; col < w; col++) {
|
||||
if (*src != key8) {
|
||||
*dst = *src;
|
||||
}
|
||||
|
||||
src++;
|
||||
dst++;
|
||||
}
|
||||
} else if (bpp == 2) {
|
||||
uint16_t key16 = (uint16_t)keyColor;
|
||||
|
||||
for (int32_t col = 0; col < w; col++) {
|
||||
uint16_t px = *(const uint16_t *)src;
|
||||
|
||||
if (px != key16) {
|
||||
*(uint16_t *)dst = px;
|
||||
}
|
||||
|
||||
src += 2;
|
||||
dst += 2;
|
||||
}
|
||||
} else {
|
||||
for (int32_t col = 0; col < w; col++) {
|
||||
uint32_t px = *(const uint32_t *)src;
|
||||
|
||||
if (px != keyColor) {
|
||||
*(uint32_t *)dst = px;
|
||||
}
|
||||
|
||||
src += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// rectFill
|
||||
// ============================================================
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_draw.h -- Layer 2: Drawing primitives for DVX GUI
|
||||
//
|
||||
// Provides all 2D drawing operations: rectangle fills, bitmap blits, text
|
||||
|
|
@ -55,14 +33,6 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
|
|||
// specify the origin within the source buffer, allowing sub-rectangle blits.
|
||||
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h);
|
||||
|
||||
// Copy with grayscale conversion. Each pixel's RGB is converted to
|
||||
// luminance (0.299R + 0.587G + 0.114B) for a disabled/grayed appearance.
|
||||
void rectCopyGrayscale(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h);
|
||||
|
||||
// Copy with color-key transparency. Pixels matching keyColor are skipped,
|
||||
// letting the destination (background) show through.
|
||||
void rectCopyTransparent(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h, uint32_t keyColor);
|
||||
|
||||
// Draw a beveled frame. The bevel is drawn as overlapping horizontal and
|
||||
// vertical spans -- top/left in highlight color, bottom/right in shadow.
|
||||
// The face color fills the interior if non-zero.
|
||||
|
|
@ -116,24 +86,6 @@ void drawTermRow(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
|||
// Windows 3.x focus rectangle convention.
|
||||
void drawFocusRect(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
|
||||
|
||||
// Compute the (x, y) origin for rendering `text` centered both
|
||||
// horizontally and vertically inside the rect (rectX, rectY, rectW,
|
||||
// rectH). Measures `text` with textWidthAccel (ignores & markers) and
|
||||
// the font's charHeight. Writes results to *outX / *outY.
|
||||
void calcCenteredText(const BitmapFontT *font, int32_t rectX, int32_t rectY, int32_t rectW, int32_t rectH, const char *text, int32_t *outX, int32_t *outY);
|
||||
|
||||
// Draw `text` with &-accelerator markers, automatically choosing
|
||||
// between the enabled and disabled (embossed) rendering paths. Saves
|
||||
// the if/else block every widget paints around drawTextAccel /
|
||||
// drawTextAccelEmbossed.
|
||||
void drawWidgetTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, uint32_t fg, uint32_t bg, bool opaque, bool enabled, const ColorSchemeT *colors);
|
||||
|
||||
// Draw a pressable 2px bevel. When pressed is true, highlight/shadow
|
||||
// colors swap to produce the "sunken" look (button pressed, toggle
|
||||
// checked). face is the fill color; pass 0 to leave the interior
|
||||
// alone. Used by buttons, checkboxes, toggle bars, etc.
|
||||
void drawPressableBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, bool pressed, uint32_t face, const ColorSchemeT *colors);
|
||||
|
||||
// Horizontal line (1px tall rectangle fill, but with simpler clipping).
|
||||
void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, uint32_t color);
|
||||
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_font.h -- Embedded VGA bitmap font data (CP437) for DVX GUI
|
||||
//
|
||||
// Contains the raw glyph bitmaps for the 8x16 VGA ROM font covering the
|
||||
|
|
@ -27,8 +5,12 @@
|
|||
// byte copy of the font stored in the VGA BIOS ROM.
|
||||
//
|
||||
// Embedding as a static const array (rather than reading from the VGA ROM
|
||||
// at INT 10h) avoids a real-mode interrupt call during init and the need
|
||||
// to copy the data from the ROM. Compiling it in is simpler.
|
||||
// at INT 10h) serves two purposes:
|
||||
// 1. The data is available on non-DOS platforms (Linux/SDL) where no
|
||||
// VGA BIOS exists.
|
||||
// 2. On DOS, reading the VGA ROM font requires a real-mode interrupt
|
||||
// call during init, and the data would need to be copied somewhere
|
||||
// anyway. Compiling it in is simpler and more portable.
|
||||
//
|
||||
// The glyph format is 1 bit per pixel, 8 pixels wide, with MSB = leftmost
|
||||
// pixel. Each glyph occupies 16 consecutive bytes. The drawing code in
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvxIcon.c -- stb_image implementation for DVX GUI
|
||||
//
|
||||
// This file exists solely to instantiate the stb_image implementation.
|
||||
|
|
@ -49,7 +27,7 @@
|
|||
#define STBI_ONLY_GIF
|
||||
#define STBI_NO_SIMD
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "thirdparty/stb_image_wrap.h"
|
||||
#include "thirdparty/stb_image.h"
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
23
core/dvxImageWrite.c
Normal file
23
core/dvxImageWrite.c
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// dvxImageWrite.c -- stb_image_write implementation for DVX GUI
|
||||
//
|
||||
// Companion to dvxIcon.c: instantiates stb_image_write for PNG output
|
||||
// (used by dvxScreenshot and dvxWindowScreenshot). Same rationale as
|
||||
// dvxIcon.c for using stb -- zero external dependencies, single header,
|
||||
// public domain. Kept in a separate translation unit from the read side
|
||||
// so projects that don't need screenshot support can omit this file and
|
||||
// save the code size.
|
||||
//
|
||||
// STBI_WRITE_NO_SIMD disables SSE codepaths for the same reason as
|
||||
// STBI_NO_SIMD in dvxIcon.c: the DOS target lacks SSE support.
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
|
||||
#define STBI_WRITE_NO_SIMD
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "thirdparty/stb_image_write.h"
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "dvxMem.h"
|
||||
28
core/dvxMem.h
Normal file
28
core/dvxMem.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// dvxMem.h -- Per-app memory tracking API for DVX
|
||||
//
|
||||
// Declares the tracked allocation functions. DXE code does NOT need
|
||||
// to include this header for tracking to work -- the DXE export table
|
||||
// maps malloc/free/calloc/realloc/strdup to these wrappers transparently.
|
||||
//
|
||||
// This header is provided for code that needs to call the tracking
|
||||
// functions by name (e.g. dvxMemGetAppUsage in the Task Manager) or
|
||||
// for the dvxMemAppIdPtr declaration.
|
||||
|
||||
#ifndef DVX_MEM_H
|
||||
#define DVX_MEM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern int32_t *dvxMemAppIdPtr;
|
||||
|
||||
void *dvxMalloc(size_t size);
|
||||
void *dvxCalloc(size_t nmemb, size_t size);
|
||||
void *dvxRealloc(void *ptr, size_t size);
|
||||
void dvxFree(void *ptr);
|
||||
char *dvxStrdup(const char *s);
|
||||
void dvxMemSnapshotLoad(int32_t appId);
|
||||
uint32_t dvxMemGetAppUsage(int32_t appId);
|
||||
void dvxMemResetApp(int32_t appId);
|
||||
|
||||
#endif // DVX_MEM_H
|
||||
|
|
@ -1,26 +1,4 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvxPal.h -- 8-bit mode palette definition for DVX GUI
|
||||
// dvx_palette.h -- 8-bit mode palette definition for DVX GUI
|
||||
//
|
||||
// Defines the 256-color palette used in 8-bit (VGA Mode 13h / VESA 8bpp)
|
||||
// video modes. The palette layout follows the same strategy as the X11
|
||||
|
|
@ -37,8 +15,8 @@
|
|||
//
|
||||
// This partition gives good general-purpose color reproduction while
|
||||
// guaranteeing the UI has pixel-perfect colors for its chrome elements.
|
||||
#ifndef DVX_PAL_H
|
||||
#define DVX_PAL_H
|
||||
#ifndef DVX_PALETTE_H
|
||||
#define DVX_PALETTE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
|
@ -178,4 +156,4 @@ static inline uint8_t dvxNearestPalEntry(const uint8_t *pal, uint8_t r, uint8_t
|
|||
return bestIdx;
|
||||
}
|
||||
|
||||
#endif // DVX_PAL_H
|
||||
#endif // DVX_PALETTE_H
|
||||
443
core/dvxPrefs.c
Normal file
443
core/dvxPrefs.c
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
// dvxPrefs.c -- INI-based preferences system (read/write)
|
||||
//
|
||||
// Custom INI parser and writer. Stores entries as a dynamic array of
|
||||
// section/key/value triples using stb_ds. Preserves insertion order
|
||||
// on save so the file remains human-readable.
|
||||
|
||||
#include "dvxPrefs.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// stb_ds dynamic arrays (implementation lives in libtasks.a)
|
||||
#include "thirdparty/stb_ds.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Internal types
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
char *section;
|
||||
char *key;
|
||||
char *value;
|
||||
} PrefsEntryT;
|
||||
|
||||
// Comment lines are stored to preserve them on save. A comment has
|
||||
// key=NULL and value=the full line text (including the ; prefix).
|
||||
// Section headers have key=NULL and value=NULL.
|
||||
|
||||
static PrefsEntryT *sEntries = NULL; // stb_ds dynamic array
|
||||
static char *sFilePath = NULL; // path used by prefsLoad (for prefsSave)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Helpers
|
||||
// ============================================================
|
||||
|
||||
static char *dupStr(const char *s) {
|
||||
if (!s) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t len = strlen(s);
|
||||
char *d = (char *)malloc(len + 1);
|
||||
|
||||
if (d) {
|
||||
memcpy(d, s, len + 1);
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
static void freeEntry(PrefsEntryT *e) {
|
||||
free(e->section);
|
||||
free(e->key);
|
||||
free(e->value);
|
||||
e->section = NULL;
|
||||
e->key = NULL;
|
||||
e->value = NULL;
|
||||
}
|
||||
|
||||
|
||||
// Case-insensitive string compare
|
||||
static int strcmpci(const char *a, const char *b) {
|
||||
for (;;) {
|
||||
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
|
||||
|
||||
if (d != 0 || !*a) {
|
||||
return d;
|
||||
}
|
||||
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Find an entry by section+key (case-insensitive). Returns index or -1.
|
||||
static int32_t findEntry(const char *section, const char *key) {
|
||||
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
||||
PrefsEntryT *e = &sEntries[i];
|
||||
|
||||
if (e->key && e->section &&
|
||||
strcmpci(e->section, section) == 0 &&
|
||||
strcmpci(e->key, key) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Find the index of a section header entry. Returns -1 if not found.
|
||||
static int32_t findSection(const char *section) {
|
||||
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
||||
PrefsEntryT *e = &sEntries[i];
|
||||
|
||||
if (!e->key && !e->value && e->section &&
|
||||
strcmpci(e->section, section) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Trim leading/trailing whitespace in place. Returns pointer into buf.
|
||||
static char *trimInPlace(char *buf) {
|
||||
while (*buf == ' ' || *buf == '\t') {
|
||||
buf++;
|
||||
}
|
||||
|
||||
char *end = buf + strlen(buf) - 1;
|
||||
|
||||
while (end >= buf && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) {
|
||||
*end-- = '\0';
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsFree
|
||||
// ============================================================
|
||||
|
||||
void prefsFree(void) {
|
||||
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
||||
freeEntry(&sEntries[i]);
|
||||
}
|
||||
|
||||
arrfree(sEntries);
|
||||
sEntries = NULL;
|
||||
free(sFilePath);
|
||||
sFilePath = NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsGetBool
|
||||
// ============================================================
|
||||
|
||||
bool prefsGetBool(const char *section, const char *key, bool defaultVal) {
|
||||
const char *val = prefsGetString(section, key, NULL);
|
||||
|
||||
if (!val) {
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
char c = (char)tolower((unsigned char)val[0]);
|
||||
|
||||
if (c == 't' || c == 'y' || c == '1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c == 'f' || c == 'n' || c == '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsGetInt
|
||||
// ============================================================
|
||||
|
||||
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal) {
|
||||
const char *val = prefsGetString(section, key, NULL);
|
||||
|
||||
if (!val) {
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
char *end = NULL;
|
||||
long n = strtol(val, &end, 10);
|
||||
|
||||
if (end == val) {
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
return (int32_t)n;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsGetString
|
||||
// ============================================================
|
||||
|
||||
const char *prefsGetString(const char *section, const char *key, const char *defaultVal) {
|
||||
int32_t idx = findEntry(section, key);
|
||||
|
||||
if (idx < 0) {
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
return sEntries[idx].value;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsLoad
|
||||
// ============================================================
|
||||
|
||||
bool prefsLoad(const char *filename) {
|
||||
prefsFree();
|
||||
|
||||
// Always store the path so prefsSave can create the file
|
||||
// even if it doesn't exist yet.
|
||||
sFilePath = dupStr(filename);
|
||||
|
||||
FILE *fp = fopen(filename, "rb");
|
||||
|
||||
if (!fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char line[512];
|
||||
char *currentSection = dupStr("");
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
// Strip trailing whitespace/newline
|
||||
char *end = line + strlen(line) - 1;
|
||||
|
||||
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ' || *end == '\t')) {
|
||||
*end-- = '\0';
|
||||
}
|
||||
|
||||
char *p = line;
|
||||
|
||||
// Skip leading whitespace
|
||||
while (*p == ' ' || *p == '\t') {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Blank line -- store as comment to preserve formatting
|
||||
if (*p == '\0') {
|
||||
PrefsEntryT e = {0};
|
||||
e.section = dupStr(currentSection);
|
||||
e.value = dupStr("");
|
||||
arrput(sEntries, e);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Comment line
|
||||
if (*p == ';' || *p == '#') {
|
||||
PrefsEntryT e = {0};
|
||||
e.section = dupStr(currentSection);
|
||||
e.value = dupStr(line);
|
||||
arrput(sEntries, e);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Section header
|
||||
if (*p == '[') {
|
||||
char *close = strchr(p, ']');
|
||||
|
||||
if (close) {
|
||||
*close = '\0';
|
||||
free(currentSection);
|
||||
currentSection = dupStr(trimInPlace(p + 1));
|
||||
|
||||
PrefsEntryT e = {0};
|
||||
e.section = dupStr(currentSection);
|
||||
arrput(sEntries, e);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Key=value
|
||||
char *eq = strchr(p, '=');
|
||||
|
||||
if (eq) {
|
||||
*eq = '\0';
|
||||
|
||||
PrefsEntryT e = {0};
|
||||
e.section = dupStr(currentSection);
|
||||
e.key = dupStr(trimInPlace(p));
|
||||
e.value = dupStr(trimInPlace(eq + 1));
|
||||
arrput(sEntries, e);
|
||||
}
|
||||
}
|
||||
|
||||
free(currentSection);
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsRemove
|
||||
// ============================================================
|
||||
|
||||
void prefsRemove(const char *section, const char *key) {
|
||||
int32_t idx = findEntry(section, key);
|
||||
|
||||
if (idx >= 0) {
|
||||
freeEntry(&sEntries[idx]);
|
||||
arrdel(sEntries, idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsSave
|
||||
// ============================================================
|
||||
|
||||
bool prefsSave(void) {
|
||||
if (!sFilePath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return prefsSaveAs(sFilePath);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsSaveAs
|
||||
// ============================================================
|
||||
|
||||
bool prefsSaveAs(const char *filename) {
|
||||
FILE *fp = fopen(filename, "wb");
|
||||
|
||||
if (!fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *lastSection = "";
|
||||
|
||||
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
||||
PrefsEntryT *e = &sEntries[i];
|
||||
|
||||
// Comment or blank line (key=NULL, value=text or empty)
|
||||
if (!e->key && e->value) {
|
||||
fprintf(fp, "%s\r\n", e->value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Section header (key=NULL, value=NULL)
|
||||
if (!e->key && !e->value) {
|
||||
fprintf(fp, "[%s]\r\n", e->section);
|
||||
lastSection = e->section;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Key=value
|
||||
if (e->key && e->value) {
|
||||
fprintf(fp, "%s = %s\r\n", e->key, e->value);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsSetBool
|
||||
// ============================================================
|
||||
|
||||
void prefsSetBool(const char *section, const char *key, bool value) {
|
||||
prefsSetString(section, key, value ? "true" : "false");
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsSetInt
|
||||
// ============================================================
|
||||
|
||||
void prefsSetInt(const char *section, const char *key, int32_t value) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%ld", (long)value);
|
||||
prefsSetString(section, key, buf);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prefsSetString
|
||||
// ============================================================
|
||||
|
||||
void prefsSetString(const char *section, const char *key, const char *value) {
|
||||
int32_t idx = findEntry(section, key);
|
||||
|
||||
if (idx >= 0) {
|
||||
// Update existing entry
|
||||
free(sEntries[idx].value);
|
||||
sEntries[idx].value = dupStr(value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find or create section header
|
||||
int32_t secIdx = findSection(section);
|
||||
|
||||
if (secIdx < 0) {
|
||||
// Add blank line before new section (unless file is empty)
|
||||
if (arrlen(sEntries) > 0) {
|
||||
PrefsEntryT blank = {0};
|
||||
blank.section = dupStr(section);
|
||||
blank.value = dupStr("");
|
||||
arrput(sEntries, blank);
|
||||
}
|
||||
|
||||
// Add section header
|
||||
PrefsEntryT secEntry = {0};
|
||||
secEntry.section = dupStr(section);
|
||||
arrput(sEntries, secEntry);
|
||||
secIdx = arrlen(sEntries) - 1;
|
||||
}
|
||||
|
||||
// Find insertion point: after last entry in this section
|
||||
int32_t insertAt = secIdx + 1;
|
||||
|
||||
while (insertAt < arrlen(sEntries)) {
|
||||
PrefsEntryT *e = &sEntries[insertAt];
|
||||
|
||||
// Stop if we've hit a different section header
|
||||
if (!e->key && !e->value && e->section &&
|
||||
strcmpci(e->section, section) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop if we've hit an entry from a different section
|
||||
if (e->section && strcmpci(e->section, section) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
insertAt++;
|
||||
}
|
||||
|
||||
// Insert new entry
|
||||
PrefsEntryT newEntry = {0};
|
||||
newEntry.section = dupStr(section);
|
||||
newEntry.key = dupStr(key);
|
||||
newEntry.value = dupStr(value);
|
||||
arrins(sEntries, insertAt, newEntry);
|
||||
}
|
||||
51
core/dvxPrefs.h
Normal file
51
core/dvxPrefs.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// dvxPrefs.h -- INI-based preferences system (read/write)
|
||||
//
|
||||
// Loads a configuration file at startup and provides typed accessors
|
||||
// with caller-supplied defaults. Values can be modified at runtime
|
||||
// and saved back to disk. If the file is missing or a key is absent,
|
||||
// getters return the default silently.
|
||||
|
||||
#ifndef DVX_PREFS_H
|
||||
#define DVX_PREFS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Load an INI file into memory. Returns true on success, false if the
|
||||
// file could not be opened (all getters will return their defaults).
|
||||
// Only one file may be loaded at a time; calling again frees the previous.
|
||||
bool prefsLoad(const char *filename);
|
||||
|
||||
// Save the current in-memory state back to the file that was loaded.
|
||||
// Returns true on success.
|
||||
bool prefsSave(void);
|
||||
|
||||
// Save the current in-memory state to a specific file.
|
||||
bool prefsSaveAs(const char *filename);
|
||||
|
||||
// Release all memory held by the preferences.
|
||||
void prefsFree(void);
|
||||
|
||||
// Retrieve a string value. Returns defaultVal if the key is not present.
|
||||
// The returned pointer is valid until the key is modified or prefsFree().
|
||||
const char *prefsGetString(const char *section, const char *key, const char *defaultVal);
|
||||
|
||||
// Retrieve an integer value.
|
||||
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal);
|
||||
|
||||
// Retrieve a boolean value. Recognises "true"/"yes"/"1" and "false"/"no"/"0".
|
||||
bool prefsGetBool(const char *section, const char *key, bool defaultVal);
|
||||
|
||||
// Set a string value. Creates the section and key if they don't exist.
|
||||
void prefsSetString(const char *section, const char *key, const char *value);
|
||||
|
||||
// Set an integer value.
|
||||
void prefsSetInt(const char *section, const char *key, int32_t value);
|
||||
|
||||
// Set a boolean value (stored as "true"/"false").
|
||||
void prefsSetBool(const char *section, const char *key, bool value);
|
||||
|
||||
// Remove a key from a section. No-op if not found.
|
||||
void prefsRemove(const char *section, const char *key);
|
||||
|
||||
#endif
|
||||
140
core/dvxResource.c
Normal file
140
core/dvxResource.c
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
// dvxResource.c -- DVX resource runtime API
|
||||
//
|
||||
// Reads the resource block appended to DXE3 files. The resource
|
||||
// block is located by reading the footer at the end of the file.
|
||||
|
||||
#include "dvxResource.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
DvxResHandleT *dvxResOpen(const char *path) {
|
||||
if (!path) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Read footer from end of file
|
||||
if (fseek(f, -(int32_t)sizeof(DvxResFooterT), SEEK_END) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DvxResFooterT footer;
|
||||
|
||||
if (fread(&footer, sizeof(footer), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (footer.magic != DVX_RES_MAGIC || footer.entryCount == 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Read directory
|
||||
if (fseek(f, footer.dirOffset, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DvxResDirEntryT *entries = (DvxResDirEntryT *)malloc(footer.entryCount * sizeof(DvxResDirEntryT));
|
||||
|
||||
if (!entries) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fread(entries, sizeof(DvxResDirEntryT), footer.entryCount, f) != footer.entryCount) {
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
DvxResHandleT *h = (DvxResHandleT *)malloc(sizeof(DvxResHandleT));
|
||||
|
||||
if (!h) {
|
||||
free(entries);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strncpy(h->path, path, sizeof(h->path) - 1);
|
||||
h->path[sizeof(h->path) - 1] = '\0';
|
||||
h->entries = entries;
|
||||
h->entryCount = footer.entryCount;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name) {
|
||||
if (!h || !name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < h->entryCount; i++) {
|
||||
if (strcmp(h->entries[i].name, name) == 0) {
|
||||
return &h->entries[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize) {
|
||||
const DvxResDirEntryT *entry = dvxResFind(h, name);
|
||||
|
||||
if (!entry) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FILE *f = fopen(h->path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f, entry->offset, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *buf = malloc(entry->size);
|
||||
|
||||
if (!buf) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fread(buf, 1, entry->size, f) != entry->size) {
|
||||
free(buf);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
if (outSize) {
|
||||
*outSize = entry->size;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
void dvxResClose(DvxResHandleT *h) {
|
||||
if (h) {
|
||||
free(h->entries);
|
||||
free(h);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,4 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvxRes.h -- DVX resource format and runtime API
|
||||
// dvxResource.h -- DVX resource format and runtime API
|
||||
//
|
||||
// Resources are appended to DXE3 files (.app, .wgt, .lib) after the
|
||||
// normal DXE content. dlopen never reads past the DXE header-specified
|
||||
|
|
@ -35,13 +13,11 @@
|
|||
// Reading starts from the end: seek to EOF - sizeof(footer), verify
|
||||
// the magic, then seek to the directory and read entries.
|
||||
|
||||
#ifndef DVX_RES_H
|
||||
#define DVX_RES_H
|
||||
#ifndef DVX_RESOURCE_H
|
||||
#define DVX_RESOURCE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "dvxPlat.h"
|
||||
|
||||
// Resource type IDs
|
||||
#define DVX_RES_ICON 1 // image data (BMP icon: 16x16, 32x32, etc.)
|
||||
#define DVX_RES_TEXT 2 // null-terminated string (author, copyright, etc.)
|
||||
|
|
@ -72,7 +48,7 @@ typedef struct {
|
|||
|
||||
// Runtime resource handle (opaque to callers)
|
||||
typedef struct {
|
||||
char path[DVX_MAX_PATH];
|
||||
char path[260];
|
||||
DvxResDirEntryT *entries;
|
||||
uint32_t entryCount;
|
||||
} DvxResHandleT;
|
||||
|
|
@ -94,13 +70,4 @@ const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name);
|
|||
// Close the handle and free all associated memory.
|
||||
void dvxResClose(DvxResHandleT *h);
|
||||
|
||||
// Append a resource to an existing DXE file.
|
||||
// Preserves existing resources and adds the new one.
|
||||
// Returns 0 on success, -1 on error.
|
||||
int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize);
|
||||
|
||||
// Remove a resource by name from a DXE file.
|
||||
// Returns 0 on success, -1 on error (not found or I/O failure).
|
||||
int32_t dvxResRemove(const char *path, const char *name);
|
||||
|
||||
#endif // DVX_RES_H
|
||||
#endif // DVX_RESOURCE_H
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_types.h -- Shared type definitions for DVX GUI
|
||||
//
|
||||
// Central type definitions shared across all five layers of the DVX GUI
|
||||
|
|
@ -33,7 +11,6 @@
|
|||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// ============================================================
|
||||
// ============================================================
|
||||
// Pixel format descriptor
|
||||
// ============================================================
|
||||
|
|
@ -66,7 +43,8 @@ typedef struct {
|
|||
//
|
||||
// The single display context passed by pointer through every layer.
|
||||
// Using one struct instead of globals makes the entire stack reentrant
|
||||
// in principle and keeps the API clean.
|
||||
// in principle and simplifies testing under Linux (where lfb points to
|
||||
// an SDL surface instead of real VESA memory).
|
||||
//
|
||||
// The double-buffer strategy (backBuf in system RAM, lfb is the real
|
||||
// framebuffer) exists because writes to video memory over the PCI bus
|
||||
|
|
@ -175,8 +153,8 @@ typedef struct {
|
|||
// (x = col * 8), glyph lookup is a single array index, and the 1bpp
|
||||
// format means each scanline of a glyph is exactly one byte.
|
||||
//
|
||||
// One font size is provided (8x16), matching the standard
|
||||
// VGA ROM font and CP437 encoding. Proportional fonts would require
|
||||
// Two font sizes are provided (8x14 and 8x16), matching the standard
|
||||
// VGA ROM fonts and CP437 encoding. Proportional fonts would require
|
||||
// glyph-width tables, kerning, and per-character positioning -- none of
|
||||
// which is worth the complexity for a DOS-era window manager targeting
|
||||
// 640x480 screens. The 8-pixel width also aligns nicely with byte
|
||||
|
|
@ -186,7 +164,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
int32_t charWidth; // fixed width per glyph (e.g. 8)
|
||||
int32_t charHeight; // 16 (only 8x16 font provided)
|
||||
int32_t charHeight; // e.g. 14 or 16
|
||||
int32_t firstChar; // ASCII code of first glyph (typically 0)
|
||||
int32_t numChars; // number of glyphs (typically 256)
|
||||
const uint8_t *glyphData; // packed 1bpp, charHeight bytes per glyph
|
||||
|
|
@ -306,10 +284,7 @@ typedef struct {
|
|||
#define CHROME_TITLE_HEIGHT 20
|
||||
#define CHROME_TITLE_PAD 4
|
||||
#define CHROME_INNER_BORDER 2
|
||||
// Menu bar height: matches the title bar so accent chrome and menu bar
|
||||
// stack as a uniform vertical rhythm. If the two ever need to differ,
|
||||
// split these back into independent values.
|
||||
#define CHROME_MENU_HEIGHT CHROME_TITLE_HEIGHT
|
||||
#define CHROME_MENU_HEIGHT 20
|
||||
#define CHROME_TOTAL_TOP (CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT + CHROME_INNER_BORDER)
|
||||
#define CHROME_TOTAL_SIDE (CHROME_BORDER_WIDTH + CHROME_INNER_BORDER)
|
||||
#define CHROME_TOTAL_BOTTOM (CHROME_BORDER_WIDTH + CHROME_INNER_BORDER)
|
||||
|
|
@ -424,44 +399,26 @@ typedef struct {
|
|||
#define ACCEL_CTRL 0x04
|
||||
#define ACCEL_ALT 0x08
|
||||
|
||||
// Non-ASCII keys are encoded as (scancode | KEY_EXT_FLAG) to distinguish
|
||||
// them from regular ASCII values in a single int32_t keycode space.
|
||||
#define KEY_EXT_FLAG 0x100
|
||||
|
||||
#define KEY_F1 (0x3B | KEY_EXT_FLAG)
|
||||
#define KEY_F2 (0x3C | KEY_EXT_FLAG)
|
||||
#define KEY_F3 (0x3D | KEY_EXT_FLAG)
|
||||
#define KEY_F4 (0x3E | KEY_EXT_FLAG)
|
||||
#define KEY_F5 (0x3F | KEY_EXT_FLAG)
|
||||
#define KEY_F6 (0x40 | KEY_EXT_FLAG)
|
||||
#define KEY_F7 (0x41 | KEY_EXT_FLAG)
|
||||
#define KEY_F8 (0x42 | KEY_EXT_FLAG)
|
||||
#define KEY_F9 (0x43 | KEY_EXT_FLAG)
|
||||
#define KEY_F10 (0x44 | KEY_EXT_FLAG)
|
||||
#define KEY_F11 (0x57 | KEY_EXT_FLAG)
|
||||
#define KEY_F12 (0x58 | KEY_EXT_FLAG)
|
||||
#define KEY_INSERT (0x52 | KEY_EXT_FLAG)
|
||||
#define KEY_DELETE (0x53 | KEY_EXT_FLAG)
|
||||
#define KEY_HOME (0x47 | KEY_EXT_FLAG)
|
||||
#define KEY_END (0x4F | KEY_EXT_FLAG)
|
||||
#define KEY_PGUP (0x49 | KEY_EXT_FLAG)
|
||||
#define KEY_PGDN (0x51 | KEY_EXT_FLAG)
|
||||
#define KEY_UP (0x48 | KEY_EXT_FLAG)
|
||||
#define KEY_DOWN (0x50 | KEY_EXT_FLAG)
|
||||
#define KEY_LEFT (0x4B | KEY_EXT_FLAG)
|
||||
#define KEY_RIGHT (0x4D | KEY_EXT_FLAG)
|
||||
|
||||
// ASCII control / printable ranges (non-extended codes).
|
||||
#define KEY_ESCAPE 0x1B
|
||||
#define KEY_ASCII_DEL 0x7F // not the same as KEY_DELETE (scancode 0x53)
|
||||
#define KEY_ASCII_PRINT_FIRST 0x20 // space
|
||||
#define KEY_ASCII_PRINT_LAST 0x7E // tilde
|
||||
|
||||
// Ctrl-letter codes (A=1, B=2, ..., Z=26) used for clipboard shortcuts.
|
||||
#define KEY_CTRL_C 0x03
|
||||
#define KEY_CTRL_V 0x16
|
||||
#define KEY_CTRL_X 0x18
|
||||
#define KEY_CTRL_Z 0x1A
|
||||
// Non-ASCII keys are encoded as (scancode | 0x100) to distinguish them
|
||||
// from regular ASCII values in a single int32_t keycode space.
|
||||
#define KEY_F1 (0x3B | 0x100)
|
||||
#define KEY_F2 (0x3C | 0x100)
|
||||
#define KEY_F3 (0x3D | 0x100)
|
||||
#define KEY_F4 (0x3E | 0x100)
|
||||
#define KEY_F5 (0x3F | 0x100)
|
||||
#define KEY_F6 (0x40 | 0x100)
|
||||
#define KEY_F7 (0x41 | 0x100)
|
||||
#define KEY_F8 (0x42 | 0x100)
|
||||
#define KEY_F9 (0x43 | 0x100)
|
||||
#define KEY_F10 (0x44 | 0x100)
|
||||
#define KEY_F11 (0x57 | 0x100)
|
||||
#define KEY_F12 (0x58 | 0x100)
|
||||
#define KEY_INSERT (0x52 | 0x100)
|
||||
#define KEY_DELETE (0x53 | 0x100)
|
||||
#define KEY_HOME (0x47 | 0x100)
|
||||
#define KEY_END (0x4F | 0x100)
|
||||
#define KEY_PGUP (0x49 | 0x100)
|
||||
#define KEY_PGDN (0x51 | 0x100)
|
||||
|
||||
typedef struct {
|
||||
int32_t key; // key code: ASCII char or KEY_Fxx for extended
|
||||
|
|
@ -508,18 +465,6 @@ typedef struct {
|
|||
#define MIN_WINDOW_H 60
|
||||
#define WM_MAX_FROM_SCREEN (-1) // use screen dimension as max (for maxW/maxH)
|
||||
|
||||
// Default / minimum VESA video mode. Used as the lower bound when
|
||||
// enumerating available modes and as the fallback mode when the
|
||||
// preferences file doesn't specify otherwise.
|
||||
#define DVX_DEFAULT_VIDEO_W 640
|
||||
#define DVX_DEFAULT_VIDEO_H 480
|
||||
#define DVX_DEFAULT_VIDEO_BPP 16
|
||||
|
||||
// Window paint states (for WindowT.paintNeeded)
|
||||
#define PAINT_NONE 0 // no paint needed
|
||||
#define PAINT_PARTIAL 1 // only dirty widgets (deferred wgtInvalidatePaint)
|
||||
#define PAINT_FULL 2 // clear + relayout + repaint all (wgtInvalidate)
|
||||
|
||||
// Hit test region identifiers (returned via hitPart from wmHitTest)
|
||||
#define HIT_CONTENT 0
|
||||
#define HIT_TITLE 1
|
||||
|
|
@ -561,12 +506,7 @@ typedef struct WindowT {
|
|||
bool maximized;
|
||||
bool resizable;
|
||||
bool modal;
|
||||
bool iconNeedsRefresh; // true when contentBuf has changed since last icon refresh
|
||||
// Paint state: PAINT_NONE = idle, PAINT_PARTIAL = only dirty widgets,
|
||||
// PAINT_FULL = clear background + relayout + repaint all.
|
||||
// wgtInvalidatePaint sets PARTIAL, wgtInvalidate sets FULL.
|
||||
// Higher values take priority (FULL > PARTIAL > NONE).
|
||||
uint8_t paintNeeded; // 0=none, 1=partial, 2=full
|
||||
bool contentDirty; // true when contentBuf has changed since last icon refresh
|
||||
int32_t maxW; // maximum width (WM_MAX_FROM_SCREEN = use screen width)
|
||||
int32_t maxH; // maximum height (WM_MAX_FROM_SCREEN = use screen height)
|
||||
// Pre-maximize geometry is saved so wmRestore() can put the window
|
||||
|
|
@ -610,15 +550,11 @@ typedef struct WindowT {
|
|||
void *userData;
|
||||
void (*onPaint)(struct WindowT *win, RectT *dirtyArea);
|
||||
void (*onKey)(struct WindowT *win, int32_t key, int32_t mod);
|
||||
void (*onKeyUp)(struct WindowT *win, int32_t scancode, int32_t mod);
|
||||
void (*onMouse)(struct WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||
void (*onResize)(struct WindowT *win, int32_t newW, int32_t newH);
|
||||
void (*onClose)(struct WindowT *win);
|
||||
void (*onMenu)(struct WindowT *win, int32_t menuId);
|
||||
void (*onScroll)(struct WindowT *win, ScrollbarOrientE orient, int32_t value);
|
||||
int32_t (*onCursorQuery)(struct WindowT *win, int32_t x, int32_t y); // return CURSOR_* or 0 for default
|
||||
void (*onFocus)(struct WindowT *win); // window gained focus
|
||||
void (*onBlur)(struct WindowT *win); // window lost focus
|
||||
} WindowT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -646,9 +582,6 @@ typedef struct {
|
|||
int32_t dragWindow;
|
||||
int32_t dragOffX;
|
||||
int32_t dragOffY;
|
||||
int32_t dragStartX; // mouse position at drag begin (for deadzone)
|
||||
int32_t dragStartY;
|
||||
bool dragActive; // true once mouse moved past deadzone
|
||||
int32_t resizeWindow;
|
||||
int32_t resizeEdge;
|
||||
int32_t scrollWindow; // window being scroll-dragged (HIT_NONE = none)
|
||||
|
|
@ -661,24 +594,8 @@ typedef struct {
|
|||
#define MOUSE_RIGHT 2
|
||||
#define MOUSE_MIDDLE 4
|
||||
|
||||
// Default scrollbar lines to scroll per mouse wheel notch
|
||||
#define MOUSE_WHEEL_STEP_DEFAULT 3
|
||||
#define MOUSE_WHEEL_STEP_MIN 1
|
||||
#define MOUSE_WHEEL_STEP_MAX 10
|
||||
|
||||
// Default mouse INI values (single source of truth for cpanel writer and shell reader).
|
||||
#define MOUSE_DBLCLICK_DEFAULT_MS 500
|
||||
#define MOUSE_SPEED_DEFAULT 8
|
||||
#define MOUSE_ACCEL_DEFAULT "medium"
|
||||
#define MOUSE_WHEEL_DIR_DEFAULT "normal"
|
||||
|
||||
// Mouse acceleration threshold levels. Higher values = less acceleration
|
||||
// (threshold above which doubling kicks in). MOUSE_ACCEL_OFF effectively
|
||||
// disables acceleration by setting the threshold above any realistic speed.
|
||||
#define MOUSE_ACCEL_OFF 10000
|
||||
#define MOUSE_ACCEL_LOW 100
|
||||
#define MOUSE_ACCEL_MEDIUM 64
|
||||
#define MOUSE_ACCEL_HIGH 32
|
||||
// Scrollbar lines to scroll per mouse wheel notch
|
||||
#define MOUSE_WHEEL_STEP 3
|
||||
|
||||
// ============================================================
|
||||
// Mouse cursor
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_video.c -- Layer 1: Video backend for DVX GUI
|
||||
//
|
||||
// Platform-independent video utilities. The actual VESA/VBE code
|
||||
|
|
@ -64,14 +42,18 @@
|
|||
// shuffling.
|
||||
|
||||
#include "dvxVideo.h"
|
||||
#include "dvxPlat.h"
|
||||
#include "dvxPal.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "dvxPalette.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
// packColor
|
||||
// ============================================================
|
||||
//
|
||||
// Converts an 8-bit-per-channel RGB triple into whatever pixel
|
||||
// representation the current display mode uses. The result is
|
||||
// always returned as a uint32_t regardless of actual pixel width,
|
||||
|
|
@ -103,6 +85,10 @@ uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// resetClipRect
|
||||
// ============================================================
|
||||
//
|
||||
// Restores the clip rect to the full screen. Called after the
|
||||
// compositor finishes flushing dirty rects, and during init.
|
||||
|
||||
|
|
@ -114,6 +100,10 @@ void resetClipRect(DisplayT *d) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setClipRect
|
||||
// ============================================================
|
||||
//
|
||||
// Sets the active clip rectangle, clamping to screen bounds.
|
||||
// The clip rect is stored directly on DisplayT so every draw call
|
||||
// in layer 2 can read it without an extra parameter. This is the
|
||||
|
|
@ -143,28 +133,10 @@ void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) {
|
|||
}
|
||||
|
||||
|
||||
void unpackColor(const DisplayT *d, uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b) {
|
||||
if (d->format.bitsPerPixel == 8) {
|
||||
if (color < 256 && d->palette) {
|
||||
*r = d->palette[color * 3];
|
||||
*g = d->palette[color * 3 + 1];
|
||||
*b = d->palette[color * 3 + 2];
|
||||
} else {
|
||||
*r = *g = *b = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t rv = (color >> d->format.redShift) & ((1u << d->format.redBits) - 1);
|
||||
uint32_t gv = (color >> d->format.greenShift) & ((1u << d->format.greenBits) - 1);
|
||||
uint32_t bv = (color >> d->format.blueShift) & ((1u << d->format.blueBits) - 1);
|
||||
|
||||
*r = (uint8_t)(rv << (8 - d->format.redBits));
|
||||
*g = (uint8_t)(gv << (8 - d->format.greenBits));
|
||||
*b = (uint8_t)(bv << (8 - d->format.blueBits));
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// videoInit
|
||||
// ============================================================
|
||||
//
|
||||
// Thin wrapper that delegates to the platform layer. All the heavy
|
||||
// lifting -- VBE enumeration, mode scoring, LFB DPMI mapping,
|
||||
// backbuffer allocation -- lives in dvxPlatformDos.c so this file
|
||||
|
|
@ -177,6 +149,10 @@ int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t p
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// videoShutdown
|
||||
// ============================================================
|
||||
//
|
||||
// Restores text mode, frees the backbuffer and palette, unmaps the
|
||||
// LFB, and disables DJGPP near pointers. Must be called before
|
||||
// exit or the console will be left in graphics mode.
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_video.h -- Layer 1: VESA VBE video backend for DVX GUI
|
||||
//
|
||||
// The lowest layer in the DVX stack. Responsible for VESA VBE mode
|
||||
|
|
@ -59,9 +37,6 @@ void videoShutdown(DisplayT *d);
|
|||
// linear scan of the grey ramp and chrome entries).
|
||||
uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b);
|
||||
|
||||
// Unpack a native pixel value back to 8-bit R, G, B components.
|
||||
void unpackColor(const DisplayT *d, uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b);
|
||||
|
||||
// Set the clip rectangle on the display. All subsequent draw operations
|
||||
// will be clipped to this rectangle. The caller is responsible for
|
||||
// saving and restoring the clip rect around scoped operations.
|
||||
|
|
@ -1,26 +1,4 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvxWgt.h -- Widget system for DVX GUI
|
||||
// dvxWidget.h -- Widget system for DVX GUI
|
||||
//
|
||||
// A retained-mode widget toolkit layered on top of the DVX window manager.
|
||||
// Widgets form a tree (parent-child via firstChild/lastChild/nextSibling
|
||||
|
|
@ -49,12 +27,11 @@
|
|||
// this halves the pointer overhead per widget and insertion/removal is
|
||||
// still O(n) in the worst case, which is acceptable given typical tree
|
||||
// depths of 5-10 nodes.
|
||||
#ifndef DVX_WGT_H
|
||||
#define DVX_WGT_H
|
||||
#ifndef DVX_WIDGET_H
|
||||
#define DVX_WIDGET_H
|
||||
|
||||
#include "dvxTypes.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
// Forward declarations
|
||||
|
|
@ -81,11 +58,6 @@ struct WidgetClassT;
|
|||
#define WGT_SIZE_CHARS 0x40000000
|
||||
#define WGT_SIZE_PERCENT 0x80000000
|
||||
|
||||
// Standard "fill remaining space" weight for VBox/HBox children.
|
||||
// A widget with WGT_WEIGHT_FILL gets one share of extra space; use
|
||||
// 2*, 3* etc. to give siblings proportionally more space.
|
||||
#define WGT_WEIGHT_FILL 100
|
||||
|
||||
#define wgtPixels(v) ((int32_t)(WGT_SIZE_PIXELS | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
|
||||
#define wgtChars(v) ((int32_t)(WGT_SIZE_CHARS | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
|
||||
#define wgtPercent(v) ((int32_t)(WGT_SIZE_PERCENT | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
|
||||
|
|
@ -164,6 +136,7 @@ typedef enum {
|
|||
// Flags encode static properties checked by the framework.
|
||||
// Widgets set these in their WidgetClassT definition.
|
||||
#define WCLASS_FOCUSABLE 0x00000001
|
||||
#define WCLASS_BOX_CONTAINER 0x00000002
|
||||
#define WCLASS_HORIZ_CONTAINER 0x00000004
|
||||
#define WCLASS_PAINTS_CHILDREN 0x00000008
|
||||
#define WCLASS_NO_HIT_RECURSE 0x00000010
|
||||
|
|
@ -176,7 +149,6 @@ typedef enum {
|
|||
#define WCLASS_RELAYOUT_ON_SCROLL 0x00000800 // full relayout on scrollbar drag
|
||||
#define WCLASS_PRESS_RELEASE 0x00001000 // click = press+release (Button, ImageButton)
|
||||
#define WCLASS_ACCEL_WHEN_HIDDEN 0x00002000 // accel matching works even when invisible
|
||||
#define WCLASS_HAS_TEXT 0x00004000 // first field of w->data is (const char *), owned by widget (strdup/free)
|
||||
|
||||
// Method IDs -- stable ABI, never reorder, never reuse.
|
||||
// New methods are appended at the end with the next sequential ID.
|
||||
|
|
@ -276,18 +248,9 @@ typedef struct WidgetT {
|
|||
bool visible;
|
||||
bool enabled;
|
||||
bool readOnly;
|
||||
bool swallowTab; // Tab key goes to widget, not focus nav
|
||||
bool paintDirty; // needs repaint (set by wgtInvalidatePaint)
|
||||
bool childDirty; // a descendant needs repaint (for WCLASS_PAINTS_CHILDREN)
|
||||
bool pressed; // WCLASS_PRESS_RELEASE: set while button is pressed
|
||||
bool focused;
|
||||
char accelKey; // lowercase accelerator character, 0 if none
|
||||
|
||||
// Content offset: mouse event coordinates are adjusted by this amount
|
||||
// so callbacks receive content-relative coords (e.g. canvas subtracts
|
||||
// its border so (0,0) = first drawable pixel, matching VB3 behavior).
|
||||
int32_t contentOffX;
|
||||
int32_t contentOffY;
|
||||
|
||||
// User data and callbacks. These fire for ALL widget types from the
|
||||
// central event dispatcher, not from individual widget handlers.
|
||||
// Type-specific handlers (e.g. button press animation, listbox
|
||||
|
|
@ -301,14 +264,6 @@ typedef struct WidgetT {
|
|||
void (*onChange)(struct WidgetT *w);
|
||||
void (*onFocus)(struct WidgetT *w);
|
||||
void (*onBlur)(struct WidgetT *w);
|
||||
void (*onKeyPress)(struct WidgetT *w, int32_t keyAscii);
|
||||
void (*onKeyDown)(struct WidgetT *w, int32_t keyCode, int32_t shift);
|
||||
void (*onKeyUp)(struct WidgetT *w, int32_t keyCode, int32_t shift);
|
||||
void (*onMouseDown)(struct WidgetT *w, int32_t button, int32_t x, int32_t y);
|
||||
void (*onMouseUp)(struct WidgetT *w, int32_t button, int32_t x, int32_t y);
|
||||
void (*onMouseMove)(struct WidgetT *w, int32_t button, int32_t x, int32_t y);
|
||||
void (*onScroll)(struct WidgetT *w, int32_t delta);
|
||||
bool (*onValidate)(struct WidgetT *w); // return false to cancel write
|
||||
|
||||
} WidgetT;
|
||||
|
||||
|
|
@ -373,26 +328,10 @@ static inline void wclsOnAccelActivate(WidgetT *w, WidgetT *root) {
|
|||
if (fn) { fn(w, root); }
|
||||
}
|
||||
|
||||
// Default destroy behavior: if the widget's class does not supply an
|
||||
// explicit WGT_METHOD_DESTROY handler, free w->data automatically (and
|
||||
// the text-at-offset-0 first, if WCLASS_HAS_TEXT is set). This removes
|
||||
// the need for one-line free(w->data) destroy handlers in every widget.
|
||||
// Widget classes that need to free multiple allocations or unregister
|
||||
// from global lists must still provide an explicit handler.
|
||||
static inline void wclsDestroy(WidgetT *w) {
|
||||
typedef void (*FnT)(WidgetT *);
|
||||
FnT fn = w->wclass ? (FnT)w->wclass->handlers[WGT_METHOD_DESTROY] : NULL;
|
||||
if (fn) {
|
||||
fn(w);
|
||||
return;
|
||||
}
|
||||
if (w->data) {
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_HAS_TEXT)) {
|
||||
free(*(char **)w->data);
|
||||
}
|
||||
free(w->data);
|
||||
w->data = NULL;
|
||||
}
|
||||
if (fn) { fn(w); }
|
||||
}
|
||||
|
||||
static inline void wclsOnChildChanged(WidgetT *parent, WidgetT *child) {
|
||||
|
|
@ -526,11 +465,6 @@ WidgetT *wgtFind(WidgetT *root, const char *name);
|
|||
// Destroy a widget and all its children (removes from parent)
|
||||
void wgtDestroy(WidgetT *w);
|
||||
|
||||
// Default window resize handler installed by wgtInitWindow. Re-evaluates
|
||||
// scrollbars and relayouts the widget tree. Call this from custom onResize
|
||||
// handlers to chain to the widget system before firing application events.
|
||||
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
|
||||
|
||||
// ============================================================
|
||||
// Tooltip
|
||||
// ============================================================
|
||||
|
|
@ -578,7 +512,7 @@ void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT
|
|||
// clip rect is set to its bounds before calling its paint function.
|
||||
// Overlays (dropdown popups, tooltips) are painted in a second pass
|
||||
// after the main tree so they render on top of everything.
|
||||
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, bool fullRepaint);
|
||||
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
|
||||
// ============================================================
|
||||
// Widget API registry
|
||||
|
|
@ -596,122 +530,4 @@ void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT
|
|||
void wgtRegisterApi(const char *name, const void *api);
|
||||
const void *wgtGetApi(const char *name);
|
||||
|
||||
// ============================================================
|
||||
// Widget interface descriptors
|
||||
// ============================================================
|
||||
//
|
||||
// Each widget DXE can register an interface descriptor that
|
||||
// describes its BASIC-facing properties, methods, and events.
|
||||
// The form runtime and IDE use these for generic dispatch and
|
||||
// property panel enumeration.
|
||||
|
||||
// Property data types
|
||||
#define WGT_IFACE_STRING 0
|
||||
#define WGT_IFACE_INT 1
|
||||
#define WGT_IFACE_BOOL 2
|
||||
#define WGT_IFACE_FLOAT 3
|
||||
#define WGT_IFACE_ENUM 4 // int32_t with named values
|
||||
|
||||
// Method calling conventions (how the form runtime marshals args)
|
||||
#define WGT_SIG_VOID 0 // void fn(WidgetT *)
|
||||
#define WGT_SIG_INT 1 // void fn(WidgetT *, int32_t)
|
||||
#define WGT_SIG_BOOL 2 // void fn(WidgetT *, bool)
|
||||
#define WGT_SIG_STR 3 // void fn(WidgetT *, const char *)
|
||||
#define WGT_SIG_INT_INT 4 // void fn(WidgetT *, int32_t, int32_t)
|
||||
#define WGT_SIG_INT_BOOL 5 // void fn(WidgetT *, int32_t, bool)
|
||||
#define WGT_SIG_RET_INT 6 // int32_t fn(const WidgetT *)
|
||||
#define WGT_SIG_RET_BOOL 7 // bool fn(const WidgetT *)
|
||||
#define WGT_SIG_RET_BOOL_INT 8 // bool fn(const WidgetT *, int32_t)
|
||||
#define WGT_SIG_INT3 9 // void fn(WidgetT *, int32_t, int32_t, int32_t)
|
||||
#define WGT_SIG_INT4 10 // void fn(WidgetT *, int32_t, int32_t, int32_t, int32_t)
|
||||
#define WGT_SIG_RET_INT_INT_INT 11 // int32_t fn(const WidgetT *, int32_t, int32_t)
|
||||
#define WGT_SIG_INT_INT_STR 12 // void fn(WidgetT *, int32_t, int32_t, const char *)
|
||||
#define WGT_SIG_INT5 13 // void fn(WidgetT *, int32_t, int32_t, int32_t, int32_t, int32_t)
|
||||
#define WGT_SIG_RET_STR_INT 14 // const char *fn(const WidgetT *, int32_t)
|
||||
#define WGT_SIG_RET_STR_INT_INT 15 // const char *fn(const WidgetT *, int32_t, int32_t)
|
||||
#define WGT_SIG_STR_BOOL_BOOL 16 // bool fn(WidgetT *, const char *, bool, bool)
|
||||
#define WGT_SIG_STR_STR_BOOL 17 // int32_t fn(WidgetT *, const char *, const char *, bool)
|
||||
#define WGT_SIG_RET_STR 18 // const char *fn(const WidgetT *)
|
||||
#define WGT_SIG_STR_INT 19 // void fn(WidgetT *, const char *, int32_t)
|
||||
#define WGT_SIG_INT_STR 20 // void fn(WidgetT *, int32_t, const char *)
|
||||
#define WGT_SIG_STR_STR 21 // void fn(WidgetT *, const char *, const char *)
|
||||
#define WGT_SIG_STR_BOOL 22 // void fn(WidgetT *, const char *, bool)
|
||||
|
||||
// Property descriptor
|
||||
typedef struct {
|
||||
const char *name; // BASIC property name (e.g. "Caption", "Value")
|
||||
uint8_t type; // WGT_IFACE_*
|
||||
void *getFn; // getter function pointer (NULL if write-only)
|
||||
void *setFn; // setter function pointer (NULL if read-only)
|
||||
const char **enumNames; // WGT_IFACE_ENUM only: NULL-terminated array of value names
|
||||
} WgtPropDescT;
|
||||
|
||||
// Method descriptor
|
||||
typedef struct {
|
||||
const char *name; // BASIC method name (e.g. "Clear", "SetFocus")
|
||||
uint8_t sig; // WGT_SIG_*
|
||||
void *fn; // function pointer
|
||||
} WgtMethodDescT;
|
||||
|
||||
// Event descriptor
|
||||
typedef struct {
|
||||
const char *name; // event name (e.g. "Click", "Change")
|
||||
} WgtEventDescT;
|
||||
|
||||
// Common events implicitly available on all widgets.
|
||||
// The form runtime wires these callbacks on every control.
|
||||
// Widget descriptors only need to list EXTRA events beyond these.
|
||||
// Click, DblClick, Change, GotFocus, LostFocus
|
||||
|
||||
// Create function signature types for design-time / runtime instantiation.
|
||||
// The create function is always the first slot in the widget API struct.
|
||||
typedef enum {
|
||||
WGT_CREATE_PARENT = 0, // fn(parent)
|
||||
WGT_CREATE_PARENT_TEXT, // fn(parent, const char *text)
|
||||
WGT_CREATE_PARENT_INT, // fn(parent, int32_t)
|
||||
WGT_CREATE_PARENT_INT_INT, // fn(parent, int32_t, int32_t)
|
||||
WGT_CREATE_PARENT_INT_INT_INT, // fn(parent, int32_t, int32_t, int32_t)
|
||||
WGT_CREATE_PARENT_INT_BOOL, // fn(parent, int32_t, bool)
|
||||
WGT_CREATE_PARENT_BOOL, // fn(parent, bool)
|
||||
WGT_CREATE_PARENT_DATA, // fn(parent, data, w, h, pitch) -- not auto-creatable
|
||||
} WgtCreateSigE;
|
||||
|
||||
#define WGT_MAX_CREATE_ARGS 3
|
||||
|
||||
// Widget interface descriptor (registered by each .wgt)
|
||||
typedef struct {
|
||||
const char *basName; // VB-style name (e.g. "CommandButton"), or NULL
|
||||
const WgtPropDescT *props; // type-specific properties
|
||||
int32_t propCount;
|
||||
const WgtMethodDescT *methods; // type-specific methods
|
||||
int32_t methodCount;
|
||||
const WgtEventDescT *events; // extra events beyond common set
|
||||
int32_t eventCount;
|
||||
uint8_t createSig; // WgtCreateSigE: how to call create fn
|
||||
int32_t createArgs[WGT_MAX_CREATE_ARGS]; // default numeric args
|
||||
bool isContainer; // can hold child widgets
|
||||
const char *defaultEvent; // default event name (e.g. "Click")
|
||||
const char *namePrefix; // auto-name prefix (NULL = use basName)
|
||||
} WgtIfaceT;
|
||||
|
||||
// Register/retrieve interface descriptors by widget type name.
|
||||
void wgtRegisterIface(const char *name, const WgtIfaceT *iface);
|
||||
const WgtIfaceT *wgtGetIface(const char *name);
|
||||
|
||||
// Find a widget type name by its VB-style name (e.g. "CommandButton" -> "button").
|
||||
// Returns NULL if no widget has that basName. Case-insensitive.
|
||||
const char *wgtFindByBasName(const char *basName);
|
||||
|
||||
// Enumerate all registered widget interfaces.
|
||||
int32_t wgtIfaceCount(void);
|
||||
const WgtIfaceT *wgtIfaceAt(int32_t idx, const char **outName);
|
||||
|
||||
// Get/set the .wgt file path for a registered widget (set by loader).
|
||||
const char *wgtIfaceGetPath(const char *name);
|
||||
void wgtIfaceSetPath(const char *name, const char *path);
|
||||
|
||||
// Get the 1-based index of this widget within its .wgt file.
|
||||
// Used to construct suffixed resource names (e.g. "name-2", "icon16-2").
|
||||
int32_t wgtIfaceGetPathIndex(const char *name);
|
||||
|
||||
#endif // DVX_WGT_H
|
||||
#endif // DVX_WIDGET_H
|
||||
|
|
@ -1,26 +1,4 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvxWgtP.h -- Plugin API for DVX widget DXE modules
|
||||
// dvxWidgetPlugin.h -- Plugin API for DVX widget DXE modules
|
||||
//
|
||||
// Included by widget .c files to access core infrastructure.
|
||||
// Provides: widget allocation, tree ops, class table, shared
|
||||
|
|
@ -31,10 +9,10 @@
|
|||
// internal functions (paint, onMouse, onKey, calcMinSize, etc.)
|
||||
// are private to its own .c file and referenced only through
|
||||
// the WidgetClassT vtable.
|
||||
#ifndef DVX_WGT_P_H
|
||||
#define DVX_WGT_P_H
|
||||
#ifndef DVX_WIDGET_PLUGIN_H
|
||||
#define DVX_WIDGET_PLUGIN_H
|
||||
|
||||
#include "dvxWgt.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "dvxApp.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxWm.h"
|
||||
|
|
@ -126,8 +104,6 @@ extern WidgetT *sFocusedWidget;
|
|||
extern WidgetT *sKeyPressedBtn;
|
||||
extern WidgetT *sOpenPopup;
|
||||
extern WidgetT *sDragWidget; // widget being dragged (any drag type)
|
||||
extern int32_t sPollWidgetCount;
|
||||
extern WidgetT **sPollWidgets; // stb_ds dynamic array
|
||||
extern void (*sCursorBlinkFn)(void);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -142,20 +118,8 @@ void widgetDestroyChildren(WidgetT *w);
|
|||
// Allocation
|
||||
WidgetT *widgetAlloc(WidgetT *parent, int32_t type);
|
||||
|
||||
// Undo a successful widgetAlloc when a subsequent data-struct
|
||||
// allocation fails. Detaches from parent, removes from the poll
|
||||
// list, frees the widget struct. Call only before w->data has
|
||||
// been set; once data is attached, use wgtDestroy instead.
|
||||
void widgetAllocRollback(WidgetT *w);
|
||||
|
||||
// Allocate a widget of the given type PLUS a data struct of dataSize
|
||||
// bytes. The data struct must begin with a `const char *text` field
|
||||
// (WCLASS_HAS_TEXT semantics); this field is set to strdup(text) and
|
||||
// the widget's accelKey is parsed from text. Other fields in the data
|
||||
// struct remain zeroed. Returns NULL on allocation failure.
|
||||
WidgetT *widgetAllocWithText(WidgetT *parent, int32_t type, size_t dataSize, const char *text);
|
||||
|
||||
// Focus management
|
||||
void widgetClearFocus(WidgetT *root);
|
||||
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
|
||||
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
|
||||
WidgetT *widgetFindByAccel(WidgetT *root, char key);
|
||||
|
|
@ -164,6 +128,7 @@ WidgetT *widgetFindByAccel(WidgetT *root, char key);
|
|||
int32_t widgetCountVisibleChildren(const WidgetT *w);
|
||||
int32_t widgetFrameBorderWidth(const WidgetT *w);
|
||||
bool widgetIsFocusable(int32_t type);
|
||||
bool widgetIsBoxContainer(int32_t type);
|
||||
bool widgetIsHorizContainer(int32_t type);
|
||||
int32_t multiClickDetect(int32_t vx, int32_t vy);
|
||||
|
||||
|
|
@ -192,9 +157,7 @@ typedef enum {
|
|||
} ScrollHitE;
|
||||
|
||||
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
void widgetDrawScrollbarHEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW);
|
||||
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
void widgetDrawScrollbarVEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW);
|
||||
ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -211,10 +174,7 @@ void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);
|
|||
// ============================================================
|
||||
|
||||
void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
|
||||
void widgetOnBlur(WindowT *win);
|
||||
void widgetOnFocus(WindowT *win);
|
||||
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
|
||||
void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod);
|
||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||
void widgetOnPaint(WindowT *win, RectT *dirtyArea);
|
||||
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
|
||||
|
|
@ -227,37 +187,9 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value);
|
|||
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
|
||||
// ============================================================
|
||||
// Pressable helpers (widgetOps.c)
|
||||
// ============================================================
|
||||
//
|
||||
// Shared drag/press state machine for WCLASS_PRESS_RELEASE widgets
|
||||
// (Button, ImageButton). The widget's w->pressed flag tracks the
|
||||
// visual state; these helpers handle mouse/key activation and fire
|
||||
// the widget's onClick callback on release-within-bounds.
|
||||
|
||||
void widgetPressableOnAccelActivate(WidgetT *w, WidgetT *root);
|
||||
void widgetPressableOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
||||
void widgetPressableOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
||||
void widgetPressableOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetPressableOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
|
||||
// ============================================================
|
||||
// Text helpers (widgetOps.c)
|
||||
// ============================================================
|
||||
//
|
||||
// Generic text get/set for widgets whose DataT struct has
|
||||
// `const char *text` as the first field (Button, Checkbox, Label,
|
||||
// Radio). The widget class must set WCLASS_HAS_TEXT so destroy
|
||||
// frees the text automatically. Widgets register these directly
|
||||
// as their WGT_METHOD_GET_TEXT / WGT_METHOD_SET_TEXT handlers.
|
||||
|
||||
const char *widgetTextGet(const WidgetT *w);
|
||||
void widgetTextSet(WidgetT *w, const char *text);
|
||||
|
||||
// Shared helper libraries:
|
||||
// texthelp/textHelp.h -- clipboard, text editing, word boundaries
|
||||
// listhelp/listHelp.h -- dropdown arrow, popup list, keyboard nav
|
||||
// Widget DXEs that use these include the headers directly.
|
||||
|
||||
#endif // DVX_WGT_P_H
|
||||
#endif // DVX_WIDGET_PLUGIN_H
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvx_wm.h -- Layer 4: Window manager for DVX GUI
|
||||
//
|
||||
// Manages the window lifecycle, Z-order stack, chrome drawing, hit testing,
|
||||
|
|
@ -81,12 +59,6 @@ int32_t wmReallocContentBuf(WindowT *win, const DisplayT *d);
|
|||
// menu bar per window is supported.
|
||||
MenuBarT *wmAddMenuBar(WindowT *win);
|
||||
|
||||
// Free the menu bar and reclaim the content area.
|
||||
void wmDestroyMenuBar(WindowT *win);
|
||||
|
||||
// Get the minimum window size (accounts for chrome, gadgets, and menu bar).
|
||||
void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
|
||||
|
||||
// Append a dropdown menu to the menu bar. Returns the MenuT to populate
|
||||
// with items. The label supports & accelerator markers (e.g. "&File").
|
||||
MenuT *wmAddMenu(MenuBarT *bar, const char *label);
|
||||
|
|
@ -107,21 +79,6 @@ void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked
|
|||
// Insert a horizontal separator line. Separators are not interactive.
|
||||
void wmAddMenuSeparator(MenuT *menu);
|
||||
|
||||
// Remove a menu item by command ID. Returns true if found and removed.
|
||||
bool wmRemoveMenuItem(MenuT *menu, int32_t id);
|
||||
|
||||
// Remove all items from a menu (preserves the menu itself on the menu bar).
|
||||
void wmClearMenuItems(MenuT *menu);
|
||||
|
||||
// Query or set the checked state of a menu item by command ID.
|
||||
// Searches all menus in the menu bar. For radio items, setting
|
||||
// checked=true also unchecks other radio items in the same group.
|
||||
bool wmMenuItemIsChecked(MenuBarT *bar, int32_t id);
|
||||
void wmMenuItemSetChecked(MenuBarT *bar, int32_t id, bool checked);
|
||||
|
||||
// Enable or disable a menu item by command ID.
|
||||
void wmMenuItemSetEnabled(MenuBarT *bar, int32_t id, bool enabled);
|
||||
|
||||
// Create a cascading submenu attached to the parent menu. Returns the
|
||||
// child MenuT to populate, or NULL on allocation failure.
|
||||
// The child MenuT is heap-allocated and freed when the parent window
|
||||
|
|
@ -234,7 +191,7 @@ void wmMinimizedIconRect(const WindowStackT *stack, const DisplayT *d, int32_t *
|
|||
void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
|
||||
|
||||
// Restore a minimized window
|
||||
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
|
||||
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, WindowT *win);
|
||||
|
||||
// Load an icon image for a window (converts to display pixel format)
|
||||
int32_t wmSetIcon(WindowT *win, const char *path, const DisplayT *d);
|
||||
|
|
@ -249,10 +206,4 @@ MenuT *wmCreateMenu(void);
|
|||
// heap-allocated submenu children recursively.
|
||||
void wmFreeMenu(MenuT *menu);
|
||||
|
||||
// Draw a standalone vertical scrollbar at the given screen coordinates.
|
||||
// Used by popup lists (dropdown, combobox) that need a scrollbar without
|
||||
// a full ScrollbarT struct. scrollPos/visibleItems/totalItems define the
|
||||
// scroll state; the function computes thumb position/size internally.
|
||||
void wmDrawVScrollbarAt(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t h, int32_t scrollPos, int32_t visibleItems, int32_t totalItems);
|
||||
|
||||
#endif // DVX_WM_H
|
||||
|
|
@ -1,34 +1,14 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// dvxPlat.h -- Platform abstraction layer for DVX GUI
|
||||
// dvxPlatform.h -- Platform abstraction layer for DVX GUI
|
||||
//
|
||||
// All OS-specific and CPU-specific code is isolated behind this
|
||||
// interface. To port DVX to a new platform, implement a new
|
||||
// dvxPlatformXxx.c against this header.
|
||||
//
|
||||
// Currently one implementation exists:
|
||||
// Currently two implementations exist:
|
||||
// dvxPlatformDos.c -- DJGPP/DPMI: real VESA VBE, INT 33h mouse,
|
||||
// INT 16h keyboard, rep movsd/stosl asm spans
|
||||
// dvxPlatformLinux.c -- SDL2: software rendering to an SDL window,
|
||||
// used for development and testing on Linux
|
||||
//
|
||||
// The abstraction covers five areas: video mode setup, framebuffer
|
||||
// flushing, optimized memory spans, mouse input, and keyboard input.
|
||||
|
|
@ -39,8 +19,8 @@
|
|||
// own internal state. They must not reference AppContextT or any layer
|
||||
// above dvxTypes.h. This ensures the platform layer can be compiled and
|
||||
// tested independently.
|
||||
#ifndef DVX_PLAT_H
|
||||
#define DVX_PLAT_H
|
||||
#ifndef DVX_PLATFORM_H
|
||||
#define DVX_PLATFORM_H
|
||||
|
||||
#include "dvxTypes.h"
|
||||
|
||||
|
|
@ -48,9 +28,6 @@
|
|||
|
||||
#include <setjmp.h>
|
||||
|
||||
// Maximum file path length. 260 matches DOS MAX_PATH.
|
||||
#define DVX_MAX_PATH 260
|
||||
|
||||
// ============================================================
|
||||
// Keyboard event
|
||||
// ============================================================
|
||||
|
|
@ -76,12 +53,13 @@ void dvxLog(const char *fmt, ...);
|
|||
// ============================================================
|
||||
|
||||
// One-time platform initialisation. On DOS this installs signal handlers
|
||||
// for clean shutdown on Ctrl+C/Ctrl+Break.
|
||||
// for clean shutdown on Ctrl+C/Ctrl+Break. On Linux this initializes SDL.
|
||||
void platformInit(void);
|
||||
|
||||
// Cooperative yield -- give up the CPU timeslice when the event loop has
|
||||
// nothing to do. On DOS this calls __dpmi_yield() to be friendly to
|
||||
// multitaskers (Windows 3.x, OS/2).
|
||||
// multitaskers (Windows 3.x, OS/2). On Linux this calls
|
||||
// SDL_Delay(1) to avoid busy-spinning at 100% CPU.
|
||||
void platformYield(void);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -90,7 +68,8 @@ void platformYield(void);
|
|||
|
||||
// Probe for a suitable video mode, enable it, map the framebuffer, and
|
||||
// allocate the system RAM backbuffer. On DOS this involves VBE BIOS calls
|
||||
// and DPMI physical memory mapping. Fills in all DisplayT fields on success.
|
||||
// and DPMI physical memory mapping. On Linux this creates an SDL window
|
||||
// and software surface. Fills in all DisplayT fields on success.
|
||||
int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
|
||||
|
||||
// Restore the previous video mode and free all video resources. On DOS
|
||||
|
|
@ -99,11 +78,13 @@ void platformVideoShutdown(DisplayT *d);
|
|||
|
||||
// Enumerate LFB-capable graphics modes. The callback is invoked for each
|
||||
// available mode. Used by videoInit() to find the best match for the
|
||||
// requested resolution and depth.
|
||||
// requested resolution and depth. On Linux, this reports a fixed set of
|
||||
// common resolutions since SDL doesn't enumerate modes the same way.
|
||||
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
|
||||
|
||||
// Program the VGA/VESA DAC palette registers (8-bit mode only). pal
|
||||
// points to RGB triplets (3 bytes per entry).
|
||||
// points to RGB triplets (3 bytes per entry). On Linux this is a no-op
|
||||
// since the SDL surface is always truecolor.
|
||||
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -113,6 +94,7 @@ void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t cou
|
|||
// Copy a rectangle from the system RAM backbuffer (d->backBuf) to the
|
||||
// display surface (d->lfb). On DOS this copies to real video memory via
|
||||
// the LFB mapping -- the critical path where PCI bus write speed matters.
|
||||
// On Linux this copies to the SDL surface, then SDL_UpdateRect is called.
|
||||
// Each scanline is copied as a contiguous block; rep movsd on DOS gives
|
||||
// near-optimal bus utilization for aligned 32-bit writes.
|
||||
void platformFlushRect(const DisplayT *d, const RectT *r);
|
||||
|
|
@ -124,7 +106,8 @@ void platformFlushRect(const DisplayT *d, const RectT *r);
|
|||
// These are the innermost loops of the renderer -- called once per
|
||||
// scanline of every rectangle fill, blit, and text draw. On DOS they
|
||||
// use inline assembly: rep stosl for fills (one instruction fills an
|
||||
// entire scanline) and rep movsd for copies.
|
||||
// entire scanline) and rep movsd for copies. On Linux they use memset/
|
||||
// memcpy which the compiler can auto-vectorize.
|
||||
//
|
||||
// Three variants per operation (8/16/32 bpp) because the fill semantics
|
||||
// differ by depth: 8-bit fills a byte per pixel, 16-bit fills a word
|
||||
|
|
@ -145,7 +128,7 @@ void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
|
|||
|
||||
// Initialize the mouse driver and constrain movement to the screen bounds.
|
||||
// On DOS this calls INT 33h functions to detect the mouse, set the X/Y
|
||||
// range, and center the cursor.
|
||||
// range, and center the cursor. On Linux this initializes SDL mouse state.
|
||||
void platformMouseInit(int32_t screenW, int32_t screenH);
|
||||
|
||||
// Poll the current mouse state. Buttons is a bitmask: bit 0 = left,
|
||||
|
|
@ -172,14 +155,9 @@ int32_t platformMouseWheelPoll(void);
|
|||
// A very high value (e.g. 10000) effectively disables acceleration.
|
||||
void platformMouseSetAccel(int32_t threshold);
|
||||
|
||||
// Set the mickey-to-pixel ratio. Controls base cursor speed.
|
||||
// horizMickeys/vertMickeys: mickeys per 8 pixels of cursor movement.
|
||||
// Higher values = slower cursor. Default is typically 8 horiz, 16 vert.
|
||||
void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys);
|
||||
|
||||
// Move the mouse cursor to an absolute screen position. Uses INT 33h
|
||||
// function 04h on DOS. Used to clamp the cursor to window edges during
|
||||
// resize operations.
|
||||
// function 04h on DOS, SDL_WarpMouseInWindow on Linux. Used to clamp
|
||||
// the cursor to window edges during resize operations.
|
||||
void platformMouseWarp(int32_t x, int32_t y);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -188,7 +166,8 @@ void platformMouseWarp(int32_t x, int32_t y);
|
|||
|
||||
// Return the current modifier key state in BIOS shift-state format:
|
||||
// bits 0-1 = either shift, bit 2 = ctrl, bit 3 = alt. On DOS this
|
||||
// reads the BIOS data area at 0040:0017.
|
||||
// reads the BIOS data area at 0040:0017. On Linux this queries SDL
|
||||
// modifier state and translates to the same bit format.
|
||||
int32_t platformKeyboardGetModifiers(void);
|
||||
|
||||
// Non-blocking read of the next key from the keyboard buffer. Returns
|
||||
|
|
@ -198,17 +177,6 @@ int32_t platformKeyboardGetModifiers(void);
|
|||
// them unambiguously.
|
||||
bool platformKeyboardRead(PlatformKeyEventT *evt);
|
||||
|
||||
// Non-blocking read of the next key-up event. Returns true if a
|
||||
// key release was detected. On DOS this requires an INT 9 hook to
|
||||
// detect break codes (scan code with bit 7 set).
|
||||
bool platformKeyUpRead(PlatformKeyEventT *evt);
|
||||
|
||||
// Install/remove the INT 9 hook for key-up detection. On DOS this
|
||||
// chains the hardware keyboard interrupt. Call Init before using
|
||||
// platformKeyUpRead, and Shutdown before exit.
|
||||
void platformKeyUpInit(void);
|
||||
void platformKeyUpShutdown(void);
|
||||
|
||||
// Translate an Alt+key scancode to its corresponding ASCII character.
|
||||
// When Alt is held, DOS doesn't provide the ASCII value -- only the
|
||||
// scancode. This function contains a lookup table mapping scancodes
|
||||
|
|
@ -228,6 +196,7 @@ char platformAltScanToChar(int32_t scancode);
|
|||
// The display pointer provides the current video mode info. Returns a
|
||||
// pointer to a static buffer valid for the lifetime of the process.
|
||||
// On DOS this uses CPUID, RDTSC, DPMI, INT 21h, INT 33h, and VBE.
|
||||
// On other platforms it returns whatever the OS can report.
|
||||
const char *platformGetSystemInfo(const DisplayT *display);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -237,6 +206,7 @@ const char *platformGetSystemInfo(const DisplayT *display);
|
|||
// Validate a filename against platform-specific rules. On DOS this
|
||||
// enforces 8.3 naming (no long filenames), checks for reserved device
|
||||
// names (CON, PRN, etc.), and rejects characters illegal in FAT filenames.
|
||||
// On Linux the rules are much more permissive (just no slashes or NUL).
|
||||
// Returns NULL if the filename is valid, or a human-readable error string
|
||||
// describing why it's invalid. Used by the file dialog's save-as validation.
|
||||
const char *platformValidateFilename(const char *name);
|
||||
|
|
@ -280,9 +250,8 @@ int32_t platformMkdirRecursive(const char *path);
|
|||
|
||||
// Change the working directory, including drive letter on DOS. Standard
|
||||
// chdir() does not switch drives under DJGPP; this wrapper calls setdisk()
|
||||
// first when the path contains a drive prefix (e.g. "A:\DVX"). Returns
|
||||
// 0 on success, -1 on failure (path not found etc.).
|
||||
int32_t platformChdir(const char *path);
|
||||
// first when the path contains a drive prefix (e.g. "A:\DVX").
|
||||
void platformChdir(const char *path);
|
||||
|
||||
// Free the backbuffer and palette without restoring text mode. Used
|
||||
// when switching between graphics modes live.
|
||||
|
|
@ -290,58 +259,11 @@ void platformVideoFreeBuffers(DisplayT *d);
|
|||
|
||||
// Return a pointer to the last directory separator in path, or NULL if
|
||||
// none is found. On DOS this checks both '/' and '\\' since DJGPP
|
||||
// accepts either.
|
||||
// accepts either. On other platforms only '/' is recognised.
|
||||
char *platformPathDirEnd(const char *path);
|
||||
|
||||
// Return a pointer to the leaf (basename) portion of path. Never returns
|
||||
// NULL -- if the path has no separator, the whole path is the basename.
|
||||
const char *platformPathBaseName(const char *path);
|
||||
|
||||
// Slurp a whole file into a freshly-malloc'd, NUL-terminated buffer.
|
||||
// Returns NULL on error (file missing, OOM, read truncated). On success,
|
||||
// *outLen (if non-NULL) receives the byte length (not counting the NUL).
|
||||
// Caller must free() the returned buffer. Binary-safe: the NUL is past
|
||||
// the end of the reported length.
|
||||
char *platformReadFile(const char *path, int32_t *outLen);
|
||||
|
||||
// Advance past leading ' ' and '\t' characters. Returns s unchanged if
|
||||
// it's NULL or already points at a non-whitespace byte.
|
||||
const char *dvxSkipWs(const char *s);
|
||||
|
||||
// Strip trailing ' ', '\t', '\r', '\n' from buf in place. Returns the
|
||||
// new length. buf may be NULL or empty.
|
||||
int32_t dvxTrimRight(char *buf);
|
||||
|
||||
// Case-insensitive check that `name` ends with `ext` (which should
|
||||
// include the leading dot, e.g. ".app"). Returns false if either
|
||||
// argument is NULL or name is shorter than ext.
|
||||
bool dvxHasExt(const char *name, const char *ext);
|
||||
|
||||
// Read all entries from `dirPath` (except "." and "..") into a
|
||||
// heap-allocated stb_ds array of malloc'd strings. Returns NULL on
|
||||
// open failure. An empty directory returns a valid empty array;
|
||||
// check arrlen() for count. Caller must pass the result to
|
||||
// dvxReadDirFree when done.
|
||||
char **dvxReadDir(const char *dirPath);
|
||||
|
||||
// Free an array returned by dvxReadDir: frees every entry and the
|
||||
// stb_ds array header. Safe to call with NULL.
|
||||
void dvxReadDirFree(char **entries);
|
||||
|
||||
// The platform's native directory separator as a string literal. Use
|
||||
// with string-literal concatenation in format strings or path constants:
|
||||
// snprintf(buf, sz, "%s" DVX_PATH_SEP "%s", dir, name);
|
||||
// #define SOMEDIR "CONFIG" DVX_PATH_SEP "WPAPER"
|
||||
// DJGPP accepts both '/' and '\\' for fopen/stat/etc., so either would
|
||||
// work, but '\\' is what DOS users expect to see in displayed paths.
|
||||
#define DVX_PATH_SEP "\\"
|
||||
|
||||
// Simple glob pattern matching for filenames. Case-insensitive.
|
||||
// Supports * (zero or more chars) and ? (one char).
|
||||
bool platformGlobMatch(const char *pattern, const char *name);
|
||||
|
||||
// Return the platform's native line ending string.
|
||||
// "\r\n" on DOS.
|
||||
// "\r\n" on DOS/Windows, "\n" on Unix/Linux.
|
||||
const char *platformLineEnding(void);
|
||||
|
||||
// Strip platform-specific line ending characters from a buffer in place.
|
||||
|
|
@ -355,7 +277,7 @@ int32_t platformStripLineEndings(char *buf, int32_t len);
|
|||
// Register platform and C runtime symbols with the dynamic module
|
||||
// loader so that DXE modules can resolve them at load time. On DOS
|
||||
// this calls dlregsym() with the full DJGPP libc/libm/libgcc/platform
|
||||
// export table.
|
||||
// export table. On platforms without DXE (Linux/SDL), this is a no-op.
|
||||
void platformRegisterDxeExports(void);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -377,23 +299,6 @@ void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal
|
|||
// also available for manual invocation if needed.
|
||||
void platformLogCrashDetail(int sig, PlatformLogFnT logFn);
|
||||
|
||||
// ============================================================
|
||||
// VGA splash screen (mode 13h, 320x200, 256-color)
|
||||
// ============================================================
|
||||
|
||||
// Enter VGA mode 13h (320x200x256).
|
||||
void platformSplashInit(void);
|
||||
|
||||
// Return to text mode 03h.
|
||||
void platformSplashShutdown(void);
|
||||
|
||||
// Load and display a raw splash file (768 bytes palette + 64000 bytes pixels).
|
||||
// Returns true on success, false if the file could not be loaded.
|
||||
bool platformSplashLoadRaw(const char *path);
|
||||
|
||||
// Fill a rectangle on the VGA mode 13h screen.
|
||||
void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color);
|
||||
|
||||
// ============================================================
|
||||
// DXE symbol overrides
|
||||
// ============================================================
|
||||
|
|
@ -412,4 +317,4 @@ typedef struct {
|
|||
// no-op (RTLD_GLOBAL or equivalent handles symbol resolution).
|
||||
void platformRegisterSymOverrides(const PlatformSymOverrideT *entries);
|
||||
|
||||
#endif // DVX_PLAT_H
|
||||
#endif // DVX_PLATFORM_H
|
||||
File diff suppressed because it is too large
Load diff
86
core/widgetClass.c
Normal file
86
core/widgetClass.c
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetClass.c -- Widget class table, API registry, and dynamic registration
|
||||
//
|
||||
// widgetClassTable is a stb_ds dynamic array. Widget DXEs call
|
||||
// wgtRegisterClass() during wgtRegister() to append their class
|
||||
// definition and receive a runtime type ID. No widget types are
|
||||
// known at compile time.
|
||||
//
|
||||
// The API registry uses an stb_ds string hashmap for O(1) lookup.
|
||||
// Each widget DXE calls wgtRegisterApi("name", &sApi) during
|
||||
// wgtRegister(). App/core code calls wgtGetApi("name") to get
|
||||
// the API pointer, then casts and calls through it.
|
||||
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "stb_ds.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// stb_ds dynamic array of class pointers. Grows on each
|
||||
// wgtRegisterClass() call. Index = type ID.
|
||||
const WidgetClassT **widgetClassTable = NULL;
|
||||
|
||||
// stb_ds string hashmap: key = widget name, value = API pointer
|
||||
typedef struct {
|
||||
char *key; // stb_ds string key (heap-allocated by shput)
|
||||
const void *value;
|
||||
} ApiMapEntryT;
|
||||
|
||||
static ApiMapEntryT *sApiMap = NULL;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtGetApi
|
||||
// ============================================================
|
||||
//
|
||||
// Look up a widget API by name. O(1) via stb_ds string hashmap.
|
||||
// Returns NULL if the widget is not loaded. Callers should still
|
||||
// cache the result in a static local to skip even the hash.
|
||||
|
||||
const void *wgtGetApi(const char *name) {
|
||||
if (!name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t idx = shgeti(sApiMap, name);
|
||||
|
||||
if (idx < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sApiMap[idx].value;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtRegisterApi
|
||||
// ============================================================
|
||||
//
|
||||
// Register a widget's public API struct under a name. Called by
|
||||
// each widget DXE during wgtRegister().
|
||||
|
||||
void wgtRegisterApi(const char *name, const void *api) {
|
||||
if (!name || !api) {
|
||||
return;
|
||||
}
|
||||
|
||||
shput(sApiMap, name, api);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtRegisterClass
|
||||
// ============================================================
|
||||
//
|
||||
// Appends a class to the table and returns the assigned type ID.
|
||||
// The ID is simply the array index.
|
||||
|
||||
int32_t wgtRegisterClass(const WidgetClassT *wclass) {
|
||||
if (!wclass) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t id = arrlen(widgetClassTable);
|
||||
arrput(widgetClassTable, wclass);
|
||||
return id;
|
||||
}
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#define DVX_WIDGET_IMPL
|
||||
// widgetCore.c -- Core widget infrastructure (alloc, tree ops, helpers)
|
||||
//
|
||||
|
|
@ -40,13 +18,9 @@
|
|||
// created and destroyed at runtime (dialog dynamics, tree item insertion),
|
||||
// which doesn't map cleanly to an arena pattern.
|
||||
|
||||
#include "dvxWgtP.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxPlat.h"
|
||||
#include "stb_ds_wrap.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "stb_ds.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -73,8 +47,6 @@ bool sDebugLayout = false;
|
|||
WidgetT *sFocusedWidget = NULL; // currently focused widget (O(1) access, avoids tree walk)
|
||||
WidgetT *sOpenPopup = NULL; // dropdown/combobox with open popup list
|
||||
WidgetT *sDragWidget = NULL; // widget being dragged (any drag type)
|
||||
int32_t sPollWidgetCount = 0;
|
||||
WidgetT **sPollWidgets = NULL; // stb_ds dynamic array
|
||||
|
||||
// Shared clipboard -- process-wide, not per-widget.
|
||||
static char *sClipboard = NULL;
|
||||
|
|
@ -89,12 +61,9 @@ static int32_t sClickCount = 0;
|
|||
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// clipboardCopy
|
||||
// ============================================================
|
||||
|
||||
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter);
|
||||
|
||||
|
||||
void clipboardCopy(const char *text, int32_t len) {
|
||||
if (!text || len <= 0) {
|
||||
return;
|
||||
|
|
@ -118,6 +87,10 @@ void clipboardCopy(const char *text, int32_t len) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// clipboardGet
|
||||
// ============================================================
|
||||
|
||||
const char *clipboardGet(int32_t *outLen) {
|
||||
if (outLen) {
|
||||
*outLen = sClipboardLen;
|
||||
|
|
@ -127,47 +100,18 @@ const char *clipboardGet(int32_t *outLen) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// clipboardMaxLen
|
||||
// ============================================================
|
||||
|
||||
int32_t clipboardMaxLen(void) {
|
||||
return 65536;
|
||||
}
|
||||
|
||||
|
||||
// Implements Tab-order navigation: finds the next focusable widget
|
||||
// after 'after' in depth-first tree order. The two-pass approach
|
||||
// (search from 'after' to end, then wrap to start) ensures circular
|
||||
// tabbing -- Tab on the last focusable widget wraps to the first.
|
||||
//
|
||||
// The pastAfter flag tracks whether we've passed the 'after' widget
|
||||
// during traversal. Once past it, the next focusable widget is the
|
||||
// answer. This avoids collecting all focusable widgets into an array
|
||||
// just to find the next one -- the common case returns quickly.
|
||||
|
||||
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) {
|
||||
if (!w->visible || !w->enabled) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (after == NULL) {
|
||||
*pastAfter = true;
|
||||
}
|
||||
|
||||
if (w == after) {
|
||||
*pastAfter = true;
|
||||
} else if (*pastAfter && widgetIsFocusable(w->type)) {
|
||||
return w;
|
||||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
WidgetT *found = findNextFocusableImpl(c, after, pastAfter);
|
||||
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// multiClickDetect
|
||||
// ============================================================
|
||||
|
||||
int32_t multiClickDetect(int32_t vx, int32_t vy) {
|
||||
clock_t now = clock();
|
||||
|
|
@ -194,6 +138,10 @@ int32_t multiClickDetect(int32_t vx, int32_t vy) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetAddChild
|
||||
// ============================================================
|
||||
//
|
||||
// Appends a child to the end of the parent's child list. O(1)
|
||||
// thanks to the lastChild tail pointer. The child list is singly-
|
||||
// linked (nextSibling), which saves 4 bytes per widget vs doubly-
|
||||
|
|
@ -213,6 +161,10 @@ void widgetAddChild(WidgetT *parent, WidgetT *child) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetAlloc
|
||||
// ============================================================
|
||||
//
|
||||
// Allocates and zero-initializes a new widget, links it to its
|
||||
// class vtable via widgetClassTable[], and optionally adds it as
|
||||
// a child of the given parent.
|
||||
|
|
@ -233,7 +185,6 @@ WidgetT *widgetAlloc(WidgetT *parent, int32_t type) {
|
|||
WidgetT *w = (WidgetT *)malloc(sizeof(WidgetT));
|
||||
|
||||
if (!w) {
|
||||
dvxLog("Widget: failed to allocate widget (type=%d)", type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -243,11 +194,6 @@ WidgetT *widgetAlloc(WidgetT *parent, int32_t type) {
|
|||
w->visible = true;
|
||||
w->enabled = true;
|
||||
|
||||
if (w->wclass->flags & WCLASS_NEEDS_POLL) {
|
||||
arrput(sPollWidgets, w);
|
||||
sPollWidgetCount = (int32_t)arrlen(sPollWidgets);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
w->window = parent->window;
|
||||
widgetAddChild(parent, w);
|
||||
|
|
@ -257,56 +203,26 @@ WidgetT *widgetAlloc(WidgetT *parent, int32_t type) {
|
|||
}
|
||||
|
||||
|
||||
// Undo a successful widgetAlloc for a widget whose data-struct
|
||||
// allocation subsequently failed. Detaches from parent, removes
|
||||
// from the poll list, and frees the widget struct. Safe only
|
||||
// before w->data has been set -- once data is attached, use
|
||||
// wgtDestroy to run the widget's full teardown path.
|
||||
void widgetAllocRollback(WidgetT *w) {
|
||||
if (!w) {
|
||||
// ============================================================
|
||||
// widgetClearFocus
|
||||
// ============================================================
|
||||
|
||||
void widgetClearFocus(WidgetT *root) {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (w->parent) {
|
||||
widgetRemoveChild(w->parent, w);
|
||||
}
|
||||
root->focused = false;
|
||||
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_NEEDS_POLL)) {
|
||||
for (int32_t i = 0; i < sPollWidgetCount; i++) {
|
||||
if (sPollWidgets[i] == w) {
|
||||
arrdel(sPollWidgets, i);
|
||||
sPollWidgetCount = (int32_t)arrlen(sPollWidgets);
|
||||
break;
|
||||
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
|
||||
widgetClearFocus(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(w);
|
||||
}
|
||||
|
||||
|
||||
WidgetT *widgetAllocWithText(WidgetT *parent, int32_t type, size_t dataSize, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, type);
|
||||
|
||||
if (!w) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *data = calloc(1, dataSize);
|
||||
|
||||
if (!data) {
|
||||
dvxLog("Widget: failed to allocate %u-byte data for type %d", (unsigned)dataSize, type);
|
||||
widgetAllocRollback(w);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// The data struct must begin with a `const char *text` field.
|
||||
*(const char **)data = text ? strdup(text) : NULL;
|
||||
w->data = data;
|
||||
w->accelKey = accelParse(text);
|
||||
return w;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// widgetCountVisibleChildren
|
||||
// ============================================================
|
||||
|
||||
int32_t widgetCountVisibleChildren(const WidgetT *w) {
|
||||
int32_t count = 0;
|
||||
|
|
@ -321,6 +237,10 @@ int32_t widgetCountVisibleChildren(const WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDestroyChildren
|
||||
// ============================================================
|
||||
//
|
||||
// Recursively destroys all descendants of a widget. Processes
|
||||
// children depth-first (destroy grandchildren before the child
|
||||
// itself) so that per-widget destroy callbacks see a consistent
|
||||
|
|
@ -363,6 +283,10 @@ void widgetDestroyChildren(WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetFindByAccel
|
||||
// ============================================================
|
||||
//
|
||||
// Finds a widget with the given Alt+key accelerator. Recurses the
|
||||
// tree depth-first, respecting visibility and enabled state.
|
||||
//
|
||||
|
|
@ -403,6 +327,46 @@ WidgetT *widgetFindByAccel(WidgetT *root, char key) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetFindNextFocusable
|
||||
// ============================================================
|
||||
//
|
||||
// Implements Tab-order navigation: finds the next focusable widget
|
||||
// after 'after' in depth-first tree order. The two-pass approach
|
||||
// (search from 'after' to end, then wrap to start) ensures circular
|
||||
// tabbing -- Tab on the last focusable widget wraps to the first.
|
||||
//
|
||||
// The pastAfter flag tracks whether we've passed the 'after' widget
|
||||
// during traversal. Once past it, the next focusable widget is the
|
||||
// answer. This avoids collecting all focusable widgets into an array
|
||||
// just to find the next one -- the common case returns quickly.
|
||||
|
||||
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) {
|
||||
if (!w->visible || !w->enabled) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (after == NULL) {
|
||||
*pastAfter = true;
|
||||
}
|
||||
|
||||
if (w == after) {
|
||||
*pastAfter = true;
|
||||
} else if (*pastAfter && widgetIsFocusable(w->type)) {
|
||||
return w;
|
||||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
WidgetT *found = findNextFocusableImpl(c, after, pastAfter);
|
||||
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
|
||||
bool pastAfter = false;
|
||||
WidgetT *found = findNextFocusableImpl(root, after, &pastAfter);
|
||||
|
|
@ -417,6 +381,10 @@ WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetFindPrevFocusable
|
||||
// ============================================================
|
||||
//
|
||||
// Shift+Tab navigation: finds the previous focusable widget.
|
||||
// Collects all focusable widgets via DFS, then returns the one
|
||||
// before 'before' (with wraparound). Uses stb_ds dynamic arrays
|
||||
|
|
@ -477,6 +445,10 @@ WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetFrameBorderWidth
|
||||
// ============================================================
|
||||
|
||||
int32_t widgetFrameBorderWidth(const WidgetT *w) {
|
||||
if (!wclsHas(w, WGT_METHOD_GET_LAYOUT_METRICS)) {
|
||||
return 0;
|
||||
|
|
@ -493,6 +465,10 @@ int32_t widgetFrameBorderWidth(const WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetHitTest
|
||||
// ============================================================
|
||||
//
|
||||
// Recursive hit testing: finds the deepest (most specific) widget
|
||||
// under the given coordinates. Returns the widget itself if no
|
||||
// child is hit, or NULL if the point is outside this widget.
|
||||
|
|
@ -538,6 +514,10 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetIsFocusable
|
||||
// ============================================================
|
||||
|
||||
bool widgetIsFocusable(int32_t type) {
|
||||
if (type < 0 || type >= arrlen(widgetClassTable) || !widgetClassTable[type]) {
|
||||
return false;
|
||||
|
|
@ -547,6 +527,25 @@ bool widgetIsFocusable(int32_t type) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetIsBoxContainer
|
||||
// ============================================================
|
||||
//
|
||||
// Returns true for widget types that use the generic box layout.
|
||||
|
||||
bool widgetIsBoxContainer(int32_t type) {
|
||||
if (type < 0 || type >= arrlen(widgetClassTable) || !widgetClassTable[type]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (widgetClassTable[type]->flags & WCLASS_BOX_CONTAINER) != 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetIsHorizContainer
|
||||
// ============================================================
|
||||
//
|
||||
// Returns true for container types that lay out children horizontally.
|
||||
|
||||
bool widgetIsHorizContainer(int32_t type) {
|
||||
|
|
@ -558,6 +557,44 @@ bool widgetIsHorizContainer(int32_t type) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetScrollbarThumb
|
||||
// ============================================================
|
||||
//
|
||||
// Calculates thumb position and size for a scrollbar track.
|
||||
// Used by both the WM-level scrollbars and widget-internal scrollbars
|
||||
// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior.
|
||||
//
|
||||
// The thumb size is proportional to visibleSize/totalSize -- a larger
|
||||
// visible area means a larger thumb, giving visual feedback about how
|
||||
// much content is scrollable. SB_MIN_THUMB prevents the thumb from
|
||||
// becoming too small to grab with a mouse.
|
||||
|
||||
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize) {
|
||||
*thumbSize = (trackLen * visibleSize) / totalSize;
|
||||
|
||||
if (*thumbSize < SB_MIN_THUMB) {
|
||||
*thumbSize = SB_MIN_THUMB;
|
||||
}
|
||||
|
||||
if (*thumbSize > trackLen) {
|
||||
*thumbSize = trackLen;
|
||||
}
|
||||
|
||||
int32_t maxScroll = totalSize - visibleSize;
|
||||
|
||||
if (maxScroll > 0) {
|
||||
*thumbPos = ((trackLen - *thumbSize) * scrollPos) / maxScroll;
|
||||
} else {
|
||||
*thumbPos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRemoveChild
|
||||
// ============================================================
|
||||
//
|
||||
// Unlinks a child from its parent's child list. O(n) in the number
|
||||
// of children because the singly-linked list requires walking to
|
||||
// find the predecessor. This is acceptable because child removal
|
||||
|
|
@ -588,31 +625,3 @@ void widgetRemoveChild(WidgetT *parent, WidgetT *child) {
|
|||
}
|
||||
|
||||
|
||||
// Calculates thumb position and size for a scrollbar track.
|
||||
// Used by both the WM-level scrollbars and widget-internal scrollbars
|
||||
// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior.
|
||||
//
|
||||
// The thumb size is proportional to visibleSize/totalSize -- a larger
|
||||
// visible area means a larger thumb, giving visual feedback about how
|
||||
// much content is scrollable. SB_MIN_THUMB prevents the thumb from
|
||||
// becoming too small to grab with a mouse.
|
||||
|
||||
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize) {
|
||||
*thumbSize = (trackLen * visibleSize) / totalSize;
|
||||
|
||||
if (*thumbSize < SB_MIN_THUMB) {
|
||||
*thumbSize = SB_MIN_THUMB;
|
||||
}
|
||||
|
||||
if (*thumbSize > trackLen) {
|
||||
*thumbSize = trackLen;
|
||||
}
|
||||
|
||||
int32_t maxScroll = totalSize - visibleSize;
|
||||
|
||||
if (maxScroll > 0) {
|
||||
*thumbPos = ((trackLen - *thumbSize) * scrollPos) / maxScroll;
|
||||
} else {
|
||||
*thumbPos = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#define DVX_WIDGET_IMPL
|
||||
// widgetEvent.c -- Window event handlers for widget system
|
||||
//
|
||||
|
|
@ -40,8 +18,7 @@
|
|||
// still adjusts the value, because sDragSlider captures the event
|
||||
// before hit testing runs.
|
||||
|
||||
#include "dvxWgtP.h"
|
||||
#include "platform/dvxPlat.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
// Widget whose popup was just closed by click-outside -- prevents
|
||||
// immediate re-open on the same click. Without this, clicking the
|
||||
|
|
@ -49,19 +26,11 @@
|
|||
// button again and re-open the popup in the same event.
|
||||
WidgetT *sClosedPopup = NULL;
|
||||
|
||||
// Mouse state for tracking button transitions and movement
|
||||
static int32_t sPrevMouseButtons = 0;
|
||||
static int32_t sPrevMouseX = -1;
|
||||
static int32_t sPrevMouseY = -1;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// widgetManageScrollbars
|
||||
// ============================================================
|
||||
|
||||
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons);
|
||||
|
||||
|
||||
//
|
||||
// Manages automatic scrollbar addition/removal for widget-based windows.
|
||||
// Called on every invalidation to ensure scrollbars match the current
|
||||
// widget tree's minimum size requirements.
|
||||
|
|
@ -171,36 +140,10 @@ void widgetManageScrollbars(WindowT *win, AppContextT *ctx) {
|
|||
}
|
||||
|
||||
|
||||
// widgetOnBlur -- window lost focus
|
||||
// ============================================================
|
||||
// widgetOnKey
|
||||
// ============================================================
|
||||
//
|
||||
// When a widget-managed window loses WM focus (user clicked another
|
||||
// window's title bar, or the window was minimized), clear the widget
|
||||
// focus so the cursor/highlight is removed. Without this, the text
|
||||
// cursor stays visible in the unfocused window.
|
||||
|
||||
void widgetOnBlur(WindowT *win) {
|
||||
if (sFocusedWidget && sFocusedWidget->window == win) {
|
||||
WidgetT *prev = sFocusedWidget;
|
||||
sFocusedWidget = NULL;
|
||||
wgtInvalidatePaint(prev);
|
||||
|
||||
if (prev->onBlur) {
|
||||
prev->onBlur(prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// widgetOnFocus -- window gained focus
|
||||
//
|
||||
// Mark the window's content dirty so the compositor knows to
|
||||
// refresh its minimized icon thumbnail if needed.
|
||||
|
||||
void widgetOnFocus(WindowT *win) {
|
||||
win->iconNeedsRefresh = true;
|
||||
}
|
||||
|
||||
|
||||
// Keyboard event dispatch. Unlike mouse events which use hit testing,
|
||||
// keyboard events go directly to the focused widget (sFocusedWidget).
|
||||
// The cached pointer avoids an O(n) tree walk to find the focused
|
||||
|
|
@ -220,7 +163,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
// Use cached focus pointer -- O(1) instead of O(n) tree walk
|
||||
WidgetT *focus = sFocusedWidget;
|
||||
|
||||
if (!focus || focus->window != win) {
|
||||
if (!focus || !focus->focused || focus->window != win) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -236,48 +179,14 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
|
||||
wclsOnKey(focus, key, mod);
|
||||
|
||||
// Fire user callbacks after the widget's internal handler
|
||||
if (key >= KEY_ASCII_PRINT_FIRST && key <= KEY_ASCII_PRINT_LAST && focus->onKeyPress) {
|
||||
focus->onKeyPress(focus, key);
|
||||
}
|
||||
|
||||
if (focus->onKeyDown) {
|
||||
focus->onKeyDown(focus, key, mod);
|
||||
}
|
||||
|
||||
ctx->currentAppId = prevAppId;
|
||||
}
|
||||
|
||||
|
||||
void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) {
|
||||
WidgetT *root = win->widgetRoot;
|
||||
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
WidgetT *focus = sFocusedWidget;
|
||||
|
||||
if (!focus || focus->window != win) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!focus->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (focus->onKeyUp) {
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
int32_t prevAppId = ctx->currentAppId;
|
||||
ctx->currentAppId = win->appId;
|
||||
|
||||
focus->onKeyUp(focus, scancode, mod);
|
||||
|
||||
ctx->currentAppId = prevAppId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetOnMouse
|
||||
// ============================================================
|
||||
//
|
||||
// Main mouse event handler. This is the most complex event handler
|
||||
// because it must manage multiple overlapping interaction states.
|
||||
//
|
||||
|
|
@ -292,6 +201,8 @@ void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) {
|
|||
// are also in content-buffer space (set during layout), so no
|
||||
// coordinate transform is needed for hit testing.
|
||||
|
||||
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons);
|
||||
|
||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||
WidgetT *root = win->widgetRoot;
|
||||
sClosedPopup = NULL;
|
||||
|
|
@ -322,7 +233,7 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
if (sDragWidget && !(buttons & MOUSE_LEFT)) {
|
||||
wclsOnDragEnd(sDragWidget, root, x, y);
|
||||
|
||||
wgtInvalidatePaint(sDragWidget);
|
||||
wgtInvalidatePaint(root);
|
||||
sDragWidget = NULL;
|
||||
return;
|
||||
}
|
||||
|
|
@ -342,7 +253,7 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
int32_t rectX = win->x + win->contentX;
|
||||
int32_t rectY = win->y + win->contentY + dirtyY - scrollY2;
|
||||
int32_t rectW = win->contentW;
|
||||
win->iconNeedsRefresh = true;
|
||||
win->contentDirty = true;
|
||||
dirtyListAdd(&ctx->dirty, rectX, rectY, rectW, dirtyH);
|
||||
return;
|
||||
}
|
||||
|
|
@ -352,7 +263,7 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
if (sDragWidget->wclass && (sDragWidget->wclass->flags & WCLASS_RELAYOUT_ON_SCROLL)) {
|
||||
wgtInvalidate(sDragWidget);
|
||||
} else {
|
||||
wgtInvalidatePaint(sDragWidget);
|
||||
wgtInvalidatePaint(root);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -376,20 +287,11 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
wclsGetPopupRect(sOpenPopup, font, win->contentH, &popX, &popY, &popW, &popH);
|
||||
|
||||
if (x >= popX && x < popX + popW && y >= popY && y < popY + popH) {
|
||||
// Click on popup area -- dispatch to widget's onMouse.
|
||||
// The widget may keep the popup open (e.g. scrollbar click)
|
||||
// or close it (item selection sets sOpenPopup = NULL).
|
||||
WidgetT *popupWidget = sOpenPopup;
|
||||
wclsOnMouse(popupWidget, root, x, y);
|
||||
// Click on popup item -- dispatch to widget's onMouse
|
||||
wclsOnMouse(sOpenPopup, root, x, y);
|
||||
|
||||
// If the popup closed, need a full repaint to erase the
|
||||
// overlay (it's painted outside widget bounds).
|
||||
if (!sOpenPopup) {
|
||||
sOpenPopup = NULL;
|
||||
wgtInvalidate(root);
|
||||
} else {
|
||||
wgtInvalidatePaint(root);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -398,12 +300,11 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
|
||||
wclsClosePopup(sOpenPopup);
|
||||
sOpenPopup = NULL;
|
||||
wgtInvalidate(root);
|
||||
wgtInvalidatePaint(root);
|
||||
// Fall through to normal click handling
|
||||
}
|
||||
|
||||
if (!(buttons & MOUSE_LEFT)) {
|
||||
sPrevMouseButtons = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -418,18 +319,18 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
return;
|
||||
}
|
||||
|
||||
// Clear focus from the previously focused widget. Must set
|
||||
// sFocusedWidget to NULL BEFORE invalidating so the inline paint
|
||||
// sees the widget as unfocused and erases its highlight.
|
||||
// Clear focus from the previously focused widget. This is done via
|
||||
// the cached sFocusedWidget pointer rather than walking the tree to
|
||||
// find the focused widget -- an O(1) operation vs O(n).
|
||||
WidgetT *prevFocus = sFocusedWidget;
|
||||
|
||||
if (sFocusedWidget) {
|
||||
sFocusedWidget->focused = false;
|
||||
sFocusedWidget = NULL;
|
||||
wgtInvalidatePaint(prevFocus);
|
||||
}
|
||||
|
||||
// Dispatch to the hit widget's mouse handler via vtable. The handler
|
||||
// is responsible for setting sFocusedWidget if it wants focus.
|
||||
// is responsible for setting hit->focused=true if it wants focus.
|
||||
if (hit->enabled) {
|
||||
wclsOnMouse(hit, root, vx, vy);
|
||||
}
|
||||
|
|
@ -450,62 +351,10 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
}
|
||||
}
|
||||
|
||||
// Fire mouse event callbacks (content-relative coordinates)
|
||||
if (hit->enabled) {
|
||||
int32_t relX = vx - hit->x - hit->contentOffX;
|
||||
int32_t relY = vy - hit->y - hit->contentOffY;
|
||||
|
||||
// MouseDown: button just pressed
|
||||
if ((buttons & MOUSE_LEFT) && !(sPrevMouseButtons & MOUSE_LEFT)) {
|
||||
if (hit->onMouseDown) {
|
||||
hit->onMouseDown(hit, 1, relX, relY);
|
||||
// Update the cached focus pointer for O(1) access in widgetOnKey
|
||||
if (hit->focused) {
|
||||
sFocusedWidget = hit;
|
||||
}
|
||||
}
|
||||
|
||||
if ((buttons & MOUSE_RIGHT) && !(sPrevMouseButtons & MOUSE_RIGHT)) {
|
||||
if (hit->onMouseDown) {
|
||||
hit->onMouseDown(hit, 2, relX, relY);
|
||||
}
|
||||
}
|
||||
|
||||
if ((buttons & MOUSE_MIDDLE) && !(sPrevMouseButtons & MOUSE_MIDDLE)) {
|
||||
if (hit->onMouseDown) {
|
||||
hit->onMouseDown(hit, 3, relX, relY);
|
||||
}
|
||||
}
|
||||
|
||||
// MouseUp: button just released
|
||||
if (!(buttons & MOUSE_LEFT) && (sPrevMouseButtons & MOUSE_LEFT)) {
|
||||
if (hit->onMouseUp) {
|
||||
hit->onMouseUp(hit, 1, relX, relY);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(buttons & MOUSE_RIGHT) && (sPrevMouseButtons & MOUSE_RIGHT)) {
|
||||
if (hit->onMouseUp) {
|
||||
hit->onMouseUp(hit, 2, relX, relY);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(buttons & MOUSE_MIDDLE) && (sPrevMouseButtons & MOUSE_MIDDLE)) {
|
||||
if (hit->onMouseUp) {
|
||||
hit->onMouseUp(hit, 3, relX, relY);
|
||||
}
|
||||
}
|
||||
|
||||
// MouseMove: position changed
|
||||
if (vx != sPrevMouseX || vy != sPrevMouseY) {
|
||||
if (hit->onMouseMove) {
|
||||
hit->onMouseMove(hit, buttons, relX, relY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sPrevMouseButtons = buttons;
|
||||
sPrevMouseX = vx;
|
||||
sPrevMouseY = vy;
|
||||
|
||||
// sFocusedWidget is now set directly by the widget's mouse handler
|
||||
|
||||
// Fire focus/blur callbacks on transitions
|
||||
if (prevFocus && prevFocus != sFocusedWidget && prevFocus->onBlur) {
|
||||
|
|
@ -515,9 +364,15 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
if (sFocusedWidget && sFocusedWidget != prevFocus && sFocusedWidget->onFocus) {
|
||||
sFocusedWidget->onFocus(sFocusedWidget);
|
||||
}
|
||||
|
||||
wgtInvalidate(root);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetOnPaint
|
||||
// ============================================================
|
||||
//
|
||||
// Paints the entire widget tree into the window's content buffer.
|
||||
// Called whenever the window needs a redraw (invalidation, scroll,
|
||||
// resize).
|
||||
|
|
@ -563,13 +418,8 @@ void widgetOnPaint(WindowT *win, RectT *dirtyArea) {
|
|||
cd.clipW = win->contentW;
|
||||
cd.clipH = win->contentH;
|
||||
|
||||
bool full = (win->paintNeeded >= PAINT_FULL);
|
||||
win->paintNeeded = PAINT_NONE;
|
||||
|
||||
if (full) {
|
||||
// Full repaint: clear background, relayout, paint everything
|
||||
// Clear background
|
||||
rectFill(&cd, &ctx->blitOps, 0, 0, win->contentW, win->contentH, ctx->colors.contentBg);
|
||||
}
|
||||
|
||||
// Apply scroll offset and re-layout at virtual size
|
||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
||||
|
|
@ -577,12 +427,12 @@ void widgetOnPaint(WindowT *win, RectT *dirtyArea) {
|
|||
int32_t layoutW = DVX_MAX(win->contentW, root->calcMinW);
|
||||
int32_t layoutH = DVX_MAX(win->contentH, root->calcMinH);
|
||||
|
||||
// Only re-layout if root position or size actually changed
|
||||
if (root->x != -scrollX || root->y != -scrollY || root->w != layoutW || root->h != layoutH) {
|
||||
root->x = -scrollX;
|
||||
root->y = -scrollY;
|
||||
root->w = layoutW;
|
||||
root->h = layoutH;
|
||||
|
||||
if (full) {
|
||||
widgetLayoutChildren(root, &ctx->font);
|
||||
}
|
||||
|
||||
|
|
@ -591,18 +441,23 @@ void widgetOnPaint(WindowT *win, RectT *dirtyArea) {
|
|||
WidgetT *first = widgetFindNextFocusable(root, NULL);
|
||||
|
||||
if (first) {
|
||||
first->focused = true;
|
||||
sFocusedWidget = first;
|
||||
}
|
||||
}
|
||||
|
||||
// Paint widget tree (full = all widgets, partial = only dirty ones)
|
||||
wgtPaint(root, &cd, &ctx->blitOps, &ctx->font, &ctx->colors, full);
|
||||
// Paint widget tree (clip rect limits drawing to visible area)
|
||||
wgtPaint(root, &cd, &ctx->blitOps, &ctx->font, &ctx->colors);
|
||||
|
||||
// Paint overlay popups (dropdown/combobox)
|
||||
widgetPaintOverlays(root, &cd, &ctx->blitOps, &ctx->font, &ctx->colors);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetOnResize
|
||||
// ============================================================
|
||||
//
|
||||
// Called when the window is resized. Triggers a full scrollbar
|
||||
// re-evaluation and relayout, since the available content area
|
||||
// changed and scrollbars may need to be added or removed.
|
||||
|
|
@ -626,6 +481,10 @@ void widgetOnResize(WindowT *win, int32_t newW, int32_t newH) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetOnScroll
|
||||
// ============================================================
|
||||
//
|
||||
// Called by the WM when a window scrollbar value changes (user dragged
|
||||
// the thumb, clicked the track, or used arrow buttons). Triggers a
|
||||
// full repaint so the widget tree is redrawn at the new scroll offset.
|
||||
|
|
@ -636,9 +495,7 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
|
|||
(void)orient;
|
||||
(void)value;
|
||||
|
||||
// Repaint with new scroll position. dvxInvalidateWindow sets
|
||||
// PAINT_FULL so widgetOnPaint re-lays out children at the new
|
||||
// root x/y offset.
|
||||
// Repaint with new scroll position -- dvxInvalidateWindow calls onPaint
|
||||
if (win->widgetRoot) {
|
||||
AppContextT *ctx = wgtGetContext(win->widgetRoot);
|
||||
|
||||
|
|
@ -647,3 +504,5 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#define DVX_WIDGET_IMPL
|
||||
// widgetLayout.c -- Layout engine (measure + arrange)
|
||||
//
|
||||
|
|
@ -49,9 +27,13 @@
|
|||
// unit types into a single int32_t without needing a separate struct.
|
||||
// The wgtResolveSize() function decodes the tag and converts to pixels.
|
||||
|
||||
#include "dvxWgtP.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCalcMinSizeBox
|
||||
// ============================================================
|
||||
//
|
||||
// Measure pass for box containers (VBox, HBox, RadioGroup, StatusBar,
|
||||
// Toolbar, Frame, TabPage). Recursively measures all visible children,
|
||||
// then computes this container's minimum size as:
|
||||
|
|
@ -129,6 +111,10 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCalcMinSizeTree
|
||||
// ============================================================
|
||||
//
|
||||
// Top-level measure dispatcher. Routes to the appropriate measure
|
||||
// function based on widget type:
|
||||
// - Box containers use the generic widgetCalcMinSizeBox()
|
||||
|
|
@ -142,7 +128,9 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
|
|||
// size calculation. Hints only increase the minimum, never shrink it.
|
||||
|
||||
void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font) {
|
||||
if (wclsHas(w, WGT_METHOD_CALC_MIN_SIZE)) {
|
||||
if (widgetIsBoxContainer(w->type)) {
|
||||
widgetCalcMinSizeBox(w, font);
|
||||
} else if (wclsHas(w, WGT_METHOD_CALC_MIN_SIZE)) {
|
||||
wclsCalcMinSize(w, font);
|
||||
} else {
|
||||
w->calcMinW = 0;
|
||||
|
|
@ -171,6 +159,10 @@ void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetLayoutBox
|
||||
// ============================================================
|
||||
//
|
||||
// Arrange pass for box containers. This is the core of the flexbox-
|
||||
// like layout algorithm, working in two sub-passes:
|
||||
//
|
||||
|
|
@ -300,22 +292,6 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
|
|||
// Assign geometry
|
||||
int32_t crossSize = availCross;
|
||||
|
||||
// Apply preferred size as cross-axis cap (e.g. Width in a VBox).
|
||||
// When set, the widget won't stretch beyond this on the cross axis.
|
||||
if (horiz && c->prefH) {
|
||||
int32_t prefPx = wgtResolveSize(c->prefH, innerH, font->charWidth);
|
||||
|
||||
if (crossSize > prefPx) {
|
||||
crossSize = prefPx;
|
||||
}
|
||||
} else if (!horiz && c->prefW) {
|
||||
int32_t prefPx = wgtResolveSize(c->prefW, innerW, font->charWidth);
|
||||
|
||||
if (crossSize > prefPx) {
|
||||
crossSize = prefPx;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply max size on cross axis
|
||||
if (horiz && c->maxH) {
|
||||
int32_t maxPx = wgtResolveSize(c->maxH, innerH, font->charWidth);
|
||||
|
|
@ -346,6 +322,21 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
|
|||
c->h = mainSize;
|
||||
}
|
||||
|
||||
// Apply preferred/max on cross axis
|
||||
if (horiz && c->maxH) {
|
||||
int32_t maxPx = wgtResolveSize(c->maxH, innerH, font->charWidth);
|
||||
|
||||
if (c->h > maxPx) {
|
||||
c->h = maxPx;
|
||||
}
|
||||
} else if (!horiz && c->maxW) {
|
||||
int32_t maxPx = wgtResolveSize(c->maxW, innerW, font->charWidth);
|
||||
|
||||
if (c->w > maxPx) {
|
||||
c->w = maxPx;
|
||||
}
|
||||
}
|
||||
|
||||
pos += mainSize + gap;
|
||||
|
||||
// Recurse into child containers
|
||||
|
|
@ -354,6 +345,10 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetLayoutChildren
|
||||
// ============================================================
|
||||
//
|
||||
// Top-level layout dispatcher. Mirrors the measure dispatcher
|
||||
// in widgetCalcMinSizeTree(): box containers use the generic
|
||||
// algorithm, widgets with custom layout (TabControl, TreeView,
|
||||
|
|
@ -361,12 +356,18 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
|
|||
// do nothing (they have no children to lay out).
|
||||
|
||||
void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font) {
|
||||
if (wclsHas(w, WGT_METHOD_LAYOUT)) {
|
||||
if (widgetIsBoxContainer(w->type)) {
|
||||
widgetLayoutBox(w, font);
|
||||
} else if (wclsHas(w, WGT_METHOD_LAYOUT)) {
|
||||
wclsLayout(w, font);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtLayout
|
||||
// ============================================================
|
||||
//
|
||||
// Public entry point: runs both passes on the entire widget tree.
|
||||
// The root widget is positioned at (0,0) and given the full available
|
||||
// area, then the arrange pass distributes space to its children.
|
||||
|
|
@ -393,6 +394,10 @@ void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtResolveSize
|
||||
// ============================================================
|
||||
//
|
||||
// Decodes a tagged size value into an actual pixel count.
|
||||
//
|
||||
// The tagged integer format uses the high 2 bits as a type tag:
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#define DVX_WIDGET_IMPL
|
||||
// widgetOps.c -- Paint dispatcher and public widget operations
|
||||
//
|
||||
|
|
@ -33,23 +11,14 @@
|
|||
// because they share the same invalidation infrastructure (wgtInvalidate
|
||||
// and wgtInvalidatePaint).
|
||||
|
||||
#include "dvxWgtP.h"
|
||||
#include "dvxPlat.h"
|
||||
#include "stb_ds_wrap.h"
|
||||
#include "../../../widgets/kpunch/box/box.h"
|
||||
|
||||
static bool sFullRepaint = false;
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "../widgets/widgetBox.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// debugContainerBorder
|
||||
// ============================================================
|
||||
|
||||
static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops);
|
||||
static bool pressableHitTest(const WidgetT *w, const WidgetT *root, int32_t x, int32_t y);
|
||||
static WidgetT *wgtFindImpl(WidgetT *w, const char *name);
|
||||
|
||||
|
||||
//
|
||||
// Draws a 1px border in a neon color derived from the widget pointer.
|
||||
// The Knuth multiplicative hash (2654435761) distributes pointer values
|
||||
// across the palette evenly so adjacent containers get different colors.
|
||||
|
|
@ -84,6 +53,87 @@ static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetPaintOne
|
||||
// ============================================================
|
||||
//
|
||||
// Recursive paint walker. For each visible widget:
|
||||
// 1. Call the widget's paint function (if any) via vtable.
|
||||
// 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion --
|
||||
// the widget's paint function already handled its children
|
||||
// (e.g. TabControl only paints the active tab's children).
|
||||
// 3. Otherwise, recurse into children (default child painting).
|
||||
// 4. Draw debug borders on top if debug layout is enabled.
|
||||
//
|
||||
// The paint order is parent-before-children, which means parent
|
||||
// backgrounds are drawn first and children paint on top. This is
|
||||
// the standard painter's algorithm for nested UI elements.
|
||||
|
||||
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
if (!w->visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Paint this widget via vtable
|
||||
wclsPaint(w, d, ops, font, colors);
|
||||
|
||||
// Widgets that paint their own children return early
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_PAINTS_CHILDREN)) {
|
||||
if (sDebugLayout) {
|
||||
debugContainerBorder(w, d, ops);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Paint children
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
widgetPaintOne(c, d, ops, font, colors);
|
||||
}
|
||||
|
||||
// Debug: draw container borders on top of children
|
||||
if (sDebugLayout && w->firstChild) {
|
||||
debugContainerBorder(w, d, ops);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetPaintOverlays
|
||||
// ============================================================
|
||||
//
|
||||
// Paints popup overlays (open dropdowns/comboboxes) on top of
|
||||
// the widget tree. Called AFTER the main paint pass so popups
|
||||
// always render above all other widgets regardless of tree position.
|
||||
//
|
||||
// Only one popup can be open at a time (tracked by sOpenPopup).
|
||||
// The tree-root ownership check prevents a popup from one window
|
||||
// being painted into a different window's content buffer.
|
||||
|
||||
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
if (!sOpenPopup) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the popup belongs to this widget tree
|
||||
WidgetT *check = sOpenPopup;
|
||||
|
||||
while (check->parent) {
|
||||
check = check->parent;
|
||||
}
|
||||
|
||||
if (check != root) {
|
||||
return;
|
||||
}
|
||||
|
||||
wclsPaintOverlay(sOpenPopup, d, ops, font, colors);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtDestroy
|
||||
// ============================================================
|
||||
//
|
||||
// Destroys a widget and its entire subtree. The order is:
|
||||
// 1. Unlink from parent (so the parent doesn't reference freed memory)
|
||||
// 2. Recursively destroy all children (depth-first)
|
||||
|
|
@ -96,16 +146,6 @@ static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops) {
|
|||
// access the widget's data (step 3 comes after child cleanup but
|
||||
// before the widget itself is freed).
|
||||
|
||||
|
||||
// Shared hit test for pressable widgets: true when (x,y) falls within
|
||||
// the widget bounds and the drag originated in the same window.
|
||||
static bool pressableHitTest(const WidgetT *w, const WidgetT *root, int32_t x, int32_t y) {
|
||||
return w->window == root->window &&
|
||||
x >= w->x && x < w->x + w->w &&
|
||||
y >= w->y && y < w->y + w->h;
|
||||
}
|
||||
|
||||
|
||||
void wgtDestroy(WidgetT *w) {
|
||||
if (!w) {
|
||||
return;
|
||||
|
|
@ -142,16 +182,6 @@ void wgtDestroy(WidgetT *w) {
|
|||
sDragWidget = NULL;
|
||||
}
|
||||
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_NEEDS_POLL)) {
|
||||
for (int32_t i = 0; i < sPollWidgetCount; i++) {
|
||||
if (sPollWidgets[i] == w) {
|
||||
arrdel(sPollWidgets, i);
|
||||
sPollWidgetCount = (int32_t)arrlen(sPollWidgets);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the root, clear the window's reference
|
||||
if (w->window && w->window->widgetRoot == w) {
|
||||
w->window->widgetRoot = NULL;
|
||||
|
|
@ -161,14 +191,9 @@ void wgtDestroy(WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
WidgetT *wgtFind(WidgetT *root, const char *name) {
|
||||
if (!root || !name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return wgtFindImpl(root, name);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// wgtFind
|
||||
// ============================================================
|
||||
|
||||
static WidgetT *wgtFindImpl(WidgetT *w, const char *name) {
|
||||
if (w->name[0] && strcmp(w->name, name) == 0) {
|
||||
|
|
@ -186,7 +211,33 @@ static WidgetT *wgtFindImpl(WidgetT *w, const char *name) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
WidgetT *wgtFind(WidgetT *root, const char *name) {
|
||||
if (!root || !name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return wgtFindImpl(root, name);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSetName
|
||||
// ============================================================
|
||||
|
||||
void wgtSetName(WidgetT *w, const char *name) {
|
||||
if (!w || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
strncpy(w->name, name, MAX_WIDGET_NAME - 1);
|
||||
w->name[MAX_WIDGET_NAME - 1] = '\0';
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtGetContext
|
||||
// ============================================================
|
||||
//
|
||||
// Retrieves the AppContextT from any widget by walking up to the root.
|
||||
// The root widget stores the context in its userData field (set during
|
||||
// wgtInitWindow). This is the only way to get the AppContextT from
|
||||
|
|
@ -209,11 +260,10 @@ AppContextT *wgtGetContext(const WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
WidgetT *wgtGetFocused(void) {
|
||||
return sFocusedWidget;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtGetText
|
||||
// ============================================================
|
||||
//
|
||||
// Polymorphic text getter -- dispatches through the vtable to the
|
||||
// appropriate getText implementation for the widget's type. Returns
|
||||
// an empty string (not NULL) if the widget has no text or no getText
|
||||
|
|
@ -228,6 +278,10 @@ const char *wgtGetText(const WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtInitWindow
|
||||
// ============================================================
|
||||
//
|
||||
// Sets up a window for widget-based content. Creates a root VBox
|
||||
// container and installs the four window callbacks (onPaint, onMouse,
|
||||
// onKey, onResize) that bridge WM events into the widget system.
|
||||
|
|
@ -241,7 +295,6 @@ WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
|||
WidgetT *root = dvxBoxApi() ? dvxBoxApi()->vBox(NULL) : NULL;
|
||||
|
||||
if (!root) {
|
||||
dvxLog("Widget: wgtInitWindow failed (%s)", dvxBoxApi() ? "vBox returned NULL" : "dvxBoxApi returned NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -252,15 +305,16 @@ WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
|||
win->onPaint = widgetOnPaint;
|
||||
win->onMouse = widgetOnMouse;
|
||||
win->onKey = widgetOnKey;
|
||||
win->onKeyUp = widgetOnKeyUp;
|
||||
win->onResize = widgetOnResize;
|
||||
win->onBlur = widgetOnBlur;
|
||||
win->onFocus = widgetOnFocus;
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtInvalidate
|
||||
// ============================================================
|
||||
//
|
||||
// Full invalidation: re-measures the widget tree, manages scrollbars,
|
||||
// re-lays out, repaints, and dirties the window on screen.
|
||||
//
|
||||
|
|
@ -298,12 +352,15 @@ void wgtInvalidate(WidgetT *w) {
|
|||
widgetManageScrollbars(w->window, ctx);
|
||||
}
|
||||
|
||||
// Full repaint — layout changed, all widgets need redrawing
|
||||
w->window->paintNeeded = PAINT_FULL;
|
||||
// Dirty the window -- dvxInvalidateWindow calls onPaint automatically
|
||||
dvxInvalidateWindow(ctx, w->window);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtInvalidatePaint
|
||||
// ============================================================
|
||||
//
|
||||
// Lightweight repaint -- skips measure/layout/scrollbar management.
|
||||
// Use when only visual state changed (slider value, cursor blink,
|
||||
// selection highlight, checkbox toggle) but widget sizes are stable.
|
||||
|
|
@ -313,41 +370,40 @@ void wgtInvalidatePaint(WidgetT *w) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Mark only this widget as needing repaint
|
||||
w->paintDirty = true;
|
||||
|
||||
// Propagate childDirty up through WCLASS_PAINTS_CHILDREN ancestors
|
||||
// so they know to recurse into children during partial repaints.
|
||||
WidgetT *root = w;
|
||||
|
||||
while (root->parent) {
|
||||
root = root->parent;
|
||||
|
||||
if (root->wclass && (root->wclass->flags & WCLASS_PAINTS_CHILDREN)) {
|
||||
root->childDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Defer the actual paint — it will happen once in the main loop
|
||||
// before compositing, batching multiple invalidations into one
|
||||
// tree walk instead of one per call. Don't downgrade FULL to PARTIAL.
|
||||
if (w->window->paintNeeded < PAINT_PARTIAL) {
|
||||
w->window->paintNeeded = PAINT_PARTIAL;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dirty the window -- dvxInvalidateWindow calls onPaint automatically
|
||||
dvxInvalidateWindow(ctx, w->window);
|
||||
}
|
||||
|
||||
|
||||
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, bool fullRepaint) {
|
||||
// ============================================================
|
||||
// wgtPaint
|
||||
// ============================================================
|
||||
|
||||
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
sFullRepaint = fullRepaint;
|
||||
widgetPaintOne(root, d, ops, font, colors);
|
||||
sFullRepaint = false;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSetDebugLayout
|
||||
// ============================================================
|
||||
|
||||
void wgtSetDebugLayout(AppContextT *ctx, bool enabled) {
|
||||
sDebugLayout = enabled;
|
||||
|
||||
|
|
@ -361,6 +417,19 @@ void wgtSetDebugLayout(AppContextT *ctx, bool enabled) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtGetFocused
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtGetFocused(void) {
|
||||
return sFocusedWidget;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSetEnabled
|
||||
// ============================================================
|
||||
|
||||
void wgtSetEnabled(WidgetT *w, bool enabled) {
|
||||
if (w) {
|
||||
w->enabled = enabled;
|
||||
|
|
@ -369,6 +438,10 @@ void wgtSetEnabled(WidgetT *w, bool enabled) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSetFocused
|
||||
// ============================================================
|
||||
|
||||
void wgtSetFocused(WidgetT *w) {
|
||||
if (!w || !w->enabled) {
|
||||
return;
|
||||
|
|
@ -377,9 +450,11 @@ void wgtSetFocused(WidgetT *w) {
|
|||
WidgetT *prev = sFocusedWidget;
|
||||
|
||||
if (prev && prev != w) {
|
||||
prev->focused = false;
|
||||
wgtInvalidatePaint(prev);
|
||||
}
|
||||
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
wgtInvalidatePaint(w);
|
||||
|
||||
|
|
@ -393,15 +468,9 @@ void wgtSetFocused(WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
void wgtSetName(WidgetT *w, const char *name) {
|
||||
if (!w || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
strncpy(w->name, name, MAX_WIDGET_NAME - 1);
|
||||
w->name[MAX_WIDGET_NAME - 1] = '\0';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// wgtSetReadOnly
|
||||
// ============================================================
|
||||
|
||||
void wgtSetReadOnly(WidgetT *w, bool readOnly) {
|
||||
if (w) {
|
||||
|
|
@ -410,6 +479,10 @@ void wgtSetReadOnly(WidgetT *w, bool readOnly) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSetText
|
||||
// ============================================================
|
||||
//
|
||||
// Polymorphic text setter. Dispatches to the type-specific setText
|
||||
// via vtable, then does a full invalidation because changing text
|
||||
// can change the widget's minimum size (triggering relayout).
|
||||
|
|
@ -425,6 +498,10 @@ void wgtSetText(WidgetT *w, const char *text) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSetTooltip
|
||||
// ============================================================
|
||||
|
||||
void wgtSetTooltip(WidgetT *w, const char *text) {
|
||||
if (w) {
|
||||
w->tooltip = text;
|
||||
|
|
@ -432,6 +509,10 @@ void wgtSetTooltip(WidgetT *w, const char *text) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSetVisible
|
||||
// ============================================================
|
||||
|
||||
void wgtSetVisible(WidgetT *w, bool visible) {
|
||||
if (w) {
|
||||
w->visible = visible;
|
||||
|
|
@ -449,196 +530,3 @@ void wgtSetVisible(WidgetT *w, bool visible) {
|
|||
wgtInvalidate(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Recursive paint walker. For each visible widget:
|
||||
// 1. Call the widget's paint function (if any) via vtable.
|
||||
// 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion --
|
||||
// the widget's paint function already handled its children
|
||||
// (e.g. TabControl only paints the active tab's children).
|
||||
// 3. Otherwise, recurse into children (default child painting).
|
||||
// 4. Draw debug borders on top if debug layout is enabled.
|
||||
//
|
||||
// The paint order is parent-before-children, which means parent
|
||||
// backgrounds are drawn first and children paint on top. This is
|
||||
// the standard painter's algorithm for nested UI elements.
|
||||
|
||||
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
if (!w->visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On partial repaints (fullRepaint=false), only paint dirty widgets.
|
||||
// The window's fullRepaint flag is stored in sFullRepaint for the
|
||||
// duration of the paint walk.
|
||||
bool dirty = w->paintDirty || sFullRepaint;
|
||||
|
||||
// For WCLASS_PAINTS_CHILDREN widgets (TabControl, TreeView, ScrollPane,
|
||||
// Splitter): the generic child recursion below can't reach their
|
||||
// children, so these widgets must handle child painting themselves.
|
||||
// Only call their paint when something actually needs drawing.
|
||||
bool paintsChildren = w->wclass && (w->wclass->flags & WCLASS_PAINTS_CHILDREN);
|
||||
|
||||
if (paintsChildren) {
|
||||
// On full repaint, ensure paintDirty is set so the paint function
|
||||
// redraws its chrome (the window background was cleared).
|
||||
if (sFullRepaint) {
|
||||
w->paintDirty = true;
|
||||
}
|
||||
|
||||
// Skip entirely if nothing needs painting in this subtree
|
||||
if (!w->paintDirty && !w->childDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When this widget itself is dirty (will clear its background),
|
||||
// all descendants must repaint on the fresh background.
|
||||
bool savedFull = sFullRepaint;
|
||||
|
||||
if (w->paintDirty) {
|
||||
sFullRepaint = true;
|
||||
}
|
||||
|
||||
wclsPaint(w, d, ops, font, colors);
|
||||
sFullRepaint = savedFull;
|
||||
w->paintDirty = false;
|
||||
w->childDirty = false;
|
||||
|
||||
if (sDebugLayout && dirty) {
|
||||
debugContainerBorder(w, d, ops);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
w->paintDirty = false;
|
||||
|
||||
if (dirty) {
|
||||
wclsPaint(w, d, ops, font, colors);
|
||||
}
|
||||
|
||||
// Always recurse into children — a clean parent may have dirty children
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
widgetPaintOne(c, d, ops, font, colors);
|
||||
}
|
||||
|
||||
// Debug: draw container borders on top of children
|
||||
if (sDebugLayout && dirty && w->firstChild) {
|
||||
debugContainerBorder(w, d, ops);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Paints popup overlays (open dropdowns/comboboxes) on top of
|
||||
// the widget tree. Called AFTER the main paint pass so popups
|
||||
// always render above all other widgets regardless of tree position.
|
||||
//
|
||||
// Only one popup can be open at a time (tracked by sOpenPopup).
|
||||
// The tree-root ownership check prevents a popup from one window
|
||||
// being painted into a different window's content buffer.
|
||||
|
||||
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
if (!sOpenPopup) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the popup belongs to this widget tree
|
||||
WidgetT *check = sOpenPopup;
|
||||
|
||||
while (check->parent) {
|
||||
check = check->parent;
|
||||
}
|
||||
|
||||
if (check != root) {
|
||||
return;
|
||||
}
|
||||
|
||||
wclsPaintOverlay(sOpenPopup, d, ops, font, colors);
|
||||
}
|
||||
|
||||
|
||||
// Accelerator key activation: mimics keyboard press. The matching key-up
|
||||
// event clears sKeyPressedBtn and fires onClick via wclsOnDragEnd.
|
||||
void widgetPressableOnAccelActivate(WidgetT *w, WidgetT *root) {
|
||||
(void)root;
|
||||
w->pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// End of drag: clear pressed, fire onClick if released inside bounds.
|
||||
void widgetPressableOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
|
||||
w->pressed = false;
|
||||
|
||||
if (pressableHitTest(w, root, x, y)) {
|
||||
if (w->onClick) {
|
||||
w->onClick(w);
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// Drag update: pressed tracks whether the cursor is still inside bounds,
|
||||
// giving visual feedback as the user drags away or back onto the button.
|
||||
void widgetPressableOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
|
||||
w->pressed = pressableHitTest(w, root, x, y);
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// Keyboard activation (Space/Enter): sets pressed and stores the widget
|
||||
// in sKeyPressedBtn so the key-up handler can fire onClick.
|
||||
void widgetPressableOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
(void)mod;
|
||||
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
w->pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Mouse press: take focus, mark pressed, register as the drag widget so
|
||||
// the event dispatcher routes subsequent drag updates/end back here.
|
||||
void widgetPressableOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
sFocusedWidget = w;
|
||||
w->pressed = true;
|
||||
sDragWidget = w;
|
||||
}
|
||||
|
||||
|
||||
// Shared text getter for widgets whose DataT first field is
|
||||
// `const char *text`. Returns empty string when nothing is set so
|
||||
// callers never need a NULL check.
|
||||
const char *widgetTextGet(const WidgetT *w) {
|
||||
if (!w || !w->data) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const char *t = *(const char **)w->data;
|
||||
|
||||
return t ? t : "";
|
||||
}
|
||||
|
||||
|
||||
// Shared text setter for widgets whose DataT first field is
|
||||
// `const char *text`. Replaces the owned strdup'd string and
|
||||
// recomputes the accelerator from the '&' prefix.
|
||||
void widgetTextSet(WidgetT *w, const char *text) {
|
||||
if (!w || !w->data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char **slot = (const char **)w->data;
|
||||
|
||||
free((void *)*slot);
|
||||
*slot = text ? strdup(text) : NULL;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#define DVX_WIDGET_IMPL
|
||||
// widgetScrollbar.c -- Shared scrollbar painting and hit-testing
|
||||
//
|
||||
|
|
@ -38,40 +16,35 @@
|
|||
// producing 7-pixel-wide arrow glyphs. This avoids any font or bitmap
|
||||
// dependency for the scrollbar chrome.
|
||||
//
|
||||
// The minimum scrollbar length guard (sbW < barW * 3) ensures
|
||||
// The minimum scrollbar length guard (sbW < WGT_SB_W * 3) ensures
|
||||
// there is at least room for both arrow buttons plus a minimal track.
|
||||
// If the container is too small, the scrollbar is simply not drawn
|
||||
// rather than rendering a corrupted mess.
|
||||
//
|
||||
// The *Ex variants accept a caller-supplied bar thickness for widgets
|
||||
// (such as ScrollPane) that need a non-default scrollbar width. The
|
||||
// plain variants forward to the Ex variants using WGT_SB_W.
|
||||
|
||||
#include "dvxWgtP.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDrawScrollbarH
|
||||
// ============================================================
|
||||
|
||||
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) {
|
||||
widgetDrawScrollbarHEx(d, ops, colors, sbX, sbY, sbW, totalSize, visibleSize, scrollPos, WGT_SB_W);
|
||||
}
|
||||
|
||||
|
||||
void widgetDrawScrollbarHEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW) {
|
||||
if (sbW < barW * 3) {
|
||||
if (sbW < WGT_SB_W * 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trough background
|
||||
BevelStyleT troughBevel = BEVEL_TROUGH(colors);
|
||||
drawBevel(d, ops, sbX, sbY, sbW, barW, &troughBevel);
|
||||
drawBevel(d, ops, sbX, sbY, sbW, WGT_SB_W, &troughBevel);
|
||||
|
||||
// Left arrow button
|
||||
BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors);
|
||||
drawBevel(d, ops, sbX, sbY, barW, barW, &btnBevel);
|
||||
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
|
||||
|
||||
// Left arrow triangle
|
||||
{
|
||||
int32_t cx = sbX + barW / 2;
|
||||
int32_t cy = sbY + barW / 2;
|
||||
int32_t cx = sbX + WGT_SB_W / 2;
|
||||
int32_t cy = sbY + WGT_SB_W / 2;
|
||||
uint32_t fg = colors->scrollbarFg;
|
||||
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
|
|
@ -80,13 +53,13 @@ void widgetDrawScrollbarHEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT
|
|||
}
|
||||
|
||||
// Right arrow button
|
||||
int32_t rightX = sbX + sbW - barW;
|
||||
drawBevel(d, ops, rightX, sbY, barW, barW, &btnBevel);
|
||||
int32_t rightX = sbX + sbW - WGT_SB_W;
|
||||
drawBevel(d, ops, rightX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
|
||||
|
||||
// Right arrow triangle
|
||||
{
|
||||
int32_t cx = rightX + barW / 2;
|
||||
int32_t cy = sbY + barW / 2;
|
||||
int32_t cx = rightX + WGT_SB_W / 2;
|
||||
int32_t cy = sbY + WGT_SB_W / 2;
|
||||
uint32_t fg = colors->scrollbarFg;
|
||||
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
|
|
@ -95,40 +68,39 @@ void widgetDrawScrollbarHEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT
|
|||
}
|
||||
|
||||
// Thumb
|
||||
int32_t trackLen = sbW - barW * 2;
|
||||
int32_t trackLen = sbW - WGT_SB_W * 2;
|
||||
|
||||
if (trackLen > 0 && totalSize > 0) {
|
||||
int32_t thumbPos;
|
||||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize);
|
||||
|
||||
drawBevel(d, ops, sbX + barW + thumbPos, sbY, thumbSize, barW, &btnBevel);
|
||||
drawBevel(d, ops, sbX + WGT_SB_W + thumbPos, sbY, thumbSize, WGT_SB_W, &btnBevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDrawScrollbarV
|
||||
// ============================================================
|
||||
|
||||
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) {
|
||||
widgetDrawScrollbarVEx(d, ops, colors, sbX, sbY, sbH, totalSize, visibleSize, scrollPos, WGT_SB_W);
|
||||
}
|
||||
|
||||
|
||||
void widgetDrawScrollbarVEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW) {
|
||||
if (sbH < barW * 3) {
|
||||
if (sbH < WGT_SB_W * 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trough background
|
||||
BevelStyleT troughBevel = BEVEL_TROUGH(colors);
|
||||
drawBevel(d, ops, sbX, sbY, barW, sbH, &troughBevel);
|
||||
drawBevel(d, ops, sbX, sbY, WGT_SB_W, sbH, &troughBevel);
|
||||
|
||||
// Up arrow button
|
||||
BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors);
|
||||
drawBevel(d, ops, sbX, sbY, barW, barW, &btnBevel);
|
||||
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
|
||||
|
||||
// Up arrow triangle
|
||||
{
|
||||
int32_t cx = sbX + barW / 2;
|
||||
int32_t cy = sbY + barW / 2;
|
||||
int32_t cx = sbX + WGT_SB_W / 2;
|
||||
int32_t cy = sbY + WGT_SB_W / 2;
|
||||
uint32_t fg = colors->scrollbarFg;
|
||||
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
|
|
@ -137,13 +109,13 @@ void widgetDrawScrollbarVEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT
|
|||
}
|
||||
|
||||
// Down arrow button
|
||||
int32_t downY = sbY + sbH - barW;
|
||||
drawBevel(d, ops, sbX, downY, barW, barW, &btnBevel);
|
||||
int32_t downY = sbY + sbH - WGT_SB_W;
|
||||
drawBevel(d, ops, sbX, downY, WGT_SB_W, WGT_SB_W, &btnBevel);
|
||||
|
||||
// Down arrow triangle
|
||||
{
|
||||
int32_t cx = sbX + barW / 2;
|
||||
int32_t cy = downY + barW / 2;
|
||||
int32_t cx = sbX + WGT_SB_W / 2;
|
||||
int32_t cy = downY + WGT_SB_W / 2;
|
||||
uint32_t fg = colors->scrollbarFg;
|
||||
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
|
|
@ -152,18 +124,22 @@ void widgetDrawScrollbarVEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT
|
|||
}
|
||||
|
||||
// Thumb
|
||||
int32_t trackLen = sbH - barW * 2;
|
||||
int32_t trackLen = sbH - WGT_SB_W * 2;
|
||||
|
||||
if (trackLen > 0 && totalSize > 0) {
|
||||
int32_t thumbPos;
|
||||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize);
|
||||
|
||||
drawBevel(d, ops, sbX, sbY + barW + thumbPos, barW, thumbSize, &btnBevel);
|
||||
drawBevel(d, ops, sbX, sbY + WGT_SB_W + thumbPos, WGT_SB_W, thumbSize, &btnBevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetScrollbarHitTest
|
||||
// ============================================================
|
||||
|
||||
// Axis-agnostic hit test. The caller converts (vx,vy) into a 1D
|
||||
// position along the scrollbar axis (relPos) and the scrollbar
|
||||
// length (sbLen). Returns which zone was hit: arrow buttons,
|
||||
76
cppcheck.sh
76
cppcheck.sh
|
|
@ -1,76 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# cppcheck.sh -- static analysis for the DVX source tree.
|
||||
#
|
||||
# Usage:
|
||||
# ./cppcheck.sh # whole tree
|
||||
# ./cppcheck.sh src/widgets # one subtree
|
||||
# ./cppcheck.sh src/libs/kpunch/texthelp/textHelp.c # one file
|
||||
#
|
||||
# Exits 0 even when warnings are found -- cppcheck's exit status is
|
||||
# not useful as a gate for this codebase (too many DJGPP-isms it can't
|
||||
# resolve). Read the output.
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
TARGETS="${@:-src}"
|
||||
|
||||
INCLUDES=(
|
||||
-Isrc/libs/kpunch/libdvx
|
||||
-Isrc/libs/kpunch/libdvx/platform
|
||||
-Isrc/libs/kpunch/libdvx/thirdparty
|
||||
-Isrc/libs/kpunch/libtasks
|
||||
-Isrc/libs/kpunch/texthelp
|
||||
-Isrc/libs/kpunch/listhelp
|
||||
-Isrc/libs/kpunch/dvxshell
|
||||
-Isrc/libs/kpunch/taskmgr
|
||||
-Isrc/libs/kpunch/serial
|
||||
-Isrc/widgets/kpunch
|
||||
-Isrc/apps/kpunch/dvxbasic
|
||||
-Isrc/apps/kpunch/dvxbasic/compiler
|
||||
-Isrc/apps/kpunch/dvxbasic/runtime
|
||||
-Isrc/apps/kpunch/dvxbasic/formrt
|
||||
)
|
||||
|
||||
# DJGPP / platform macros cppcheck can't find. Expand them to nothing
|
||||
# so the preprocessor leaves the code visible to the analyser instead
|
||||
# of eliding whole functions behind unknown macro calls.
|
||||
DEFINES=(
|
||||
-DDXE_EXPORT=
|
||||
-DDVX_WIDGET_IMPL
|
||||
-D__DJGPP__=2
|
||||
)
|
||||
|
||||
# Suppressions: things cppcheck flags that aren't actually wrong for
|
||||
# this codebase. Keep the list short so real issues stay visible.
|
||||
SUPPRESS=(
|
||||
--suppress=missingIncludeSystem # system headers aren't on host
|
||||
--suppress=unusedFunction # many public exports look "unused" to a .c-only scan
|
||||
--suppress=constParameterPointer # noisy on this style
|
||||
--suppress=constVariablePointer # ditto
|
||||
--suppress=normalCheckLevelMaxBranches
|
||||
# stb_ds arrput is a macro -- cppcheck can't see that the value
|
||||
# (including heap pointers inside it) is stored, so it reports
|
||||
# spurious null-derefs on the stb_ds dynamic-array handle.
|
||||
--suppress=nullPointerRedundantCheck
|
||||
--suppress=nullPointerArithmeticRedundantCheck
|
||||
# Thirdparty headers -- their own style isn't our problem.
|
||||
--suppress='*:src/libs/kpunch/libdvx/thirdparty/*'
|
||||
--suppress='*:src/libs/kpunch/sql/thirdparty/*'
|
||||
)
|
||||
|
||||
exec cppcheck \
|
||||
--enable=warning,style,performance,portability \
|
||||
--inline-suppr \
|
||||
--std=c99 \
|
||||
--platform=unix32 \
|
||||
--quiet \
|
||||
-j"$(nproc)" \
|
||||
-i src/libs/kpunch/sql/thirdparty \
|
||||
-i src/libs/kpunch/libdvx/thirdparty \
|
||||
"${INCLUDES[@]}" \
|
||||
"${DEFINES[@]}" \
|
||||
"${SUPPRESS[@]}" \
|
||||
$TARGETS
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,171 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>DVX Help Viewer</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 0; padding: 0; display: flex; }
|
||||
nav { width: 250px; min-width: 250px; background: #f0f0f0; padding: 16px;
|
||||
border-right: 1px solid #ccc; height: 100vh; overflow-y: auto;
|
||||
position: sticky; top: 0; box-sizing: border-box; }
|
||||
nav ul { list-style: none; padding-left: 16px; margin: 4px 0; }
|
||||
nav > ul { padding-left: 0; }
|
||||
nav a { text-decoration: none; color: #0066cc; }
|
||||
nav a:hover { text-decoration: underline; }
|
||||
main { flex: 1; padding: 24px 32px; max-width: 800px; }
|
||||
h1 { border-bottom: 2px solid #333; padding-bottom: 4px; }
|
||||
h2 { border-bottom: 1px solid #999; padding-bottom: 2px; margin-top: 32px; }
|
||||
h3 { margin-top: 24px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; padding: 8px;
|
||||
overflow-x: auto; font-size: 14px; }
|
||||
blockquote { background: #fffde7; border-left: 4px solid #ffc107;
|
||||
padding: 8px 12px; margin: 12px 0; }
|
||||
hr { border: none; border-top: 1px solid #ccc; margin: 24px 0; }
|
||||
img { max-width: 100%; }
|
||||
.topic { margin-bottom: 48px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<h3>Contents</h3>
|
||||
<ul>
|
||||
<li><a href="#help.overview">DVX Help Viewer</a></li>
|
||||
<li><a href="#help.format">Help Source Format</a></li>
|
||||
<li><a href="#help.compiler">Help Compiler</a></li>
|
||||
<li><a href="#help.integration">Application Integration</a></li>
|
||||
</ul>
|
||||
<h3>Index</h3>
|
||||
<ul>
|
||||
<li><a href="#help.format">.dhs</a></li>
|
||||
<li><a href="#help.compiler">Compiler</a></li>
|
||||
<li><a href="#help.integration">Context Help</a></li>
|
||||
<li><a href="#help.format">Directives</a></li>
|
||||
<li><a href="#help.overview">DVX Help</a></li>
|
||||
<li><a href="#help.compiler">dvxhlpc</a></li>
|
||||
<li><a href="#help.integration">F1</a></li>
|
||||
<li><a href="#help.overview">Help Viewer</a></li>
|
||||
<li><a href="#help.integration">helpFile</a></li>
|
||||
<li><a href="#help.integration">helpTopic</a></li>
|
||||
<li><a href="#help.integration">shellLoadAppWithArgs</a></li>
|
||||
<li><a href="#help.format">Source Format</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main>
|
||||
<div class="topic" id="help.overview">
|
||||
<h1>DVX Help Viewer</h1>
|
||||
<p>The DVX Help Viewer displays .hlp help files compiled from .dhs source documents. It provides a tree-based table of contents, scrollable content with word-wrapped text, clickable hyperlinks, full-text search, and a keyword index.</p>
|
||||
<h2>Opening Help</h2>
|
||||
<p>Press F1 from any DVX application to open context-sensitive help. Applications can register their own help file and topic so F1 opens the relevant page.</p>
|
||||
<p>You can also launch the help viewer from an application's Help menu, or by clicking the DVX Help icon in the Program Manager.</p>
|
||||
<h2>Navigation</h2>
|
||||
<ul>
|
||||
<li>Click a topic in the tree on the left to display it</li>
|
||||
<li>Click underlined links in the content to jump to other topics</li>
|
||||
<li>Use the Back and Forward buttons (or Navigate menu) to retrace your steps</li>
|
||||
<li>Use Navigate > Index to browse an alphabetical keyword list</li>
|
||||
</ul>
|
||||
<p>Use Navigate > Search to find topics by keyword</p>
|
||||
<h2>Keyboard Shortcuts</h2>
|
||||
<pre> Alt+Left Back
|
||||
Alt+Right Forward
|
||||
Ctrl+F Search
|
||||
Escape Close viewer</pre>
|
||||
</div>
|
||||
<div class="topic" id="help.format">
|
||||
<h1>Help Source Format (.dhs)</h1>
|
||||
<p>Help files are authored as plain text .dhs source files using a simple line-oriented directive format. Lines beginning with a period at column 0 are directives. All other lines are body text, which is automatically word-wrapped by the viewer at display time.</p>
|
||||
<h2>Topic Directives</h2>
|
||||
<pre> .topic <id> Start a new topic with a unique string ID
|
||||
.title <text> Set the topic's display title
|
||||
.toc <depth> <text> Add a table of contents entry (0=root, 1=child, etc.)
|
||||
.default Mark this topic as the one shown when the file opens</pre>
|
||||
<h2>Content Directives</h2>
|
||||
<pre> .h1 <text> Level 1 heading (colored bar)
|
||||
.h2 <text> Level 2 heading (underlined)
|
||||
.h3 <text> Level 3 heading (plain)
|
||||
.hr Horizontal rule
|
||||
.link <id> <text> Hyperlink to another topic
|
||||
.image <file.bmp> Inline image (BMP format)</pre>
|
||||
<h2>Block Directives</h2>
|
||||
<pre> .list Start a bulleted list
|
||||
.item <text> List item (must be inside .list)
|
||||
.endlist End the bulleted list
|
||||
.table Start a preformatted table block
|
||||
.endtable End table block
|
||||
.code Start a preformatted code block
|
||||
.endcode End code block
|
||||
.note [info|tip|warning] Start a callout box
|
||||
.endnote End callout box</pre>
|
||||
<h2>Index Directives</h2>
|
||||
<pre> .index <keyword> Add a keyword to the index pointing to this topic</pre>
|
||||
<h2>Example</h2>
|
||||
<pre><code>.topic intro
|
||||
.title Welcome
|
||||
.toc 0 Welcome
|
||||
.default
|
||||
.index Welcome
|
||||
|
||||
.h1 Welcome
|
||||
|
||||
This is a paragraph of body text. It will be
|
||||
automatically word-wrapped by the viewer.
|
||||
|
||||
.list
|
||||
.item First item
|
||||
.item Second item
|
||||
.endlist
|
||||
|
||||
.link other.topic See also: Other Topic
|
||||
|
||||
.note info
|
||||
This is an informational note.
|
||||
.endnote
|
||||
|
||||
.note tip
|
||||
This is a helpful tip.
|
||||
.endnote
|
||||
|
||||
.note warning
|
||||
This is a warning message.
|
||||
.endnote</code></pre>
|
||||
<h2>Callout Boxes</h2>
|
||||
<p>Three types of callout boxes are available, each with a distinct colored accent bar:</p>
|
||||
<blockquote><strong>Note:</strong> Use info notes for general supplementary information.</blockquote>
|
||||
<blockquote><strong>Tip:</strong> Use tip notes for helpful suggestions and best practices.</blockquote>
|
||||
<blockquote><strong>Warning:</strong> Use warning notes for important cautions the reader should be aware of.</blockquote>
|
||||
</div>
|
||||
<div class="topic" id="help.compiler">
|
||||
<h1>Help Compiler (dvxhlpc)</h1>
|
||||
<p>The dvxhlpc tool runs on the host (Linux) and compiles .dhs source files into binary .hlp files for the viewer, and optionally into self-contained HTML.</p>
|
||||
<h2>Usage</h2>
|
||||
<pre><code>dvxhlpc -o output.hlp [-i imagedir] [--html out.html] input.dhs [...]</code></pre>
|
||||
<h2>Options</h2>
|
||||
<pre> -o output.hlp Output binary help file (required)
|
||||
-i imagedir Directory to find .image files (default: current dir)
|
||||
--html out.html Also emit a self-contained HTML file</pre>
|
||||
<p>Multiple input files are merged into a single help file. This allows per-widget or per-feature documentation fragments to be combined automatically.</p>
|
||||
<h2>Build Integration</h2>
|
||||
<p>The standard build pattern globs all fragments:</p>
|
||||
<pre><code>dvxhlpc -o dvxhelp.hlp docs/src/overview.dhs widgets/*/*.dhs</code></pre>
|
||||
<p>New widgets or features just drop a .dhs file in their source directory and it appears in the help on the next build.</p>
|
||||
<h2>HTML Output</h2>
|
||||
<p>The --html flag produces a single self-contained HTML file with a sidebar table of contents, styled headings, lists, code blocks, notes, and embedded images (base64 data URIs). This is useful for viewing documentation on the host machine without running the DOS help viewer.</p>
|
||||
</div>
|
||||
<div class="topic" id="help.integration">
|
||||
<h1>Application Integration</h1>
|
||||
<p>Any DVX application can provide context-sensitive help via the F1 key.</p>
|
||||
<h2>Setting Up Help</h2>
|
||||
<p>In your appMain, set the help file path on the app context:</p>
|
||||
<pre><code>snprintf(ctx->helpFile, sizeof(ctx->helpFile),
|
||||
"%s%cMYAPP.HLP", ctx->appDir, DVX_PATH_SEP);</code></pre>
|
||||
<h2>Context-Sensitive Topics</h2>
|
||||
<p>Update helpTopic as the user navigates your application:</p>
|
||||
<pre><code>snprintf(ctx->helpTopic, sizeof(ctx->helpTopic), "settings.video");</code></pre>
|
||||
<p>When the user presses F1, the shell launches the help viewer with your help file opened to the specified topic.</p>
|
||||
<h2>Launching Help from Menus</h2>
|
||||
<p>To add a Help menu item that opens your help file:</p>
|
||||
<pre><code>shellLoadAppWithArgs(ctx, viewerPath, helpFilePath);</code></pre>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,37 +0,0 @@
|
|||
[sdl]
|
||||
windowresolution = 1024x768
|
||||
|
||||
[dosbox]
|
||||
machine = svga_s3
|
||||
memsize = 16
|
||||
|
||||
[cpu]
|
||||
core = dynamic
|
||||
#cputype = 486
|
||||
#cpu_cycles = 25000
|
||||
#cpu_cycles_protected = 25000
|
||||
|
||||
[render]
|
||||
aspect = true
|
||||
|
||||
[dos]
|
||||
umb = true
|
||||
xms = true
|
||||
ems = true
|
||||
ver = 6.22
|
||||
|
||||
[mouse]
|
||||
mouse_capture = seamless
|
||||
dos_mouse_immediate = true
|
||||
|
||||
[serial]
|
||||
serial1 = nullmodem server:127.0.0.1 port:2323 transparent:1
|
||||
|
||||
[autoexec]
|
||||
mount d ~/dos
|
||||
rem d:\ctmouse\bin\ctmouse.exe /O
|
||||
mount c .
|
||||
c:
|
||||
cd bin
|
||||
dvx
|
||||
rem exit
|
||||
|
|
@ -26,15 +26,12 @@ vesa oldvbe10 = false
|
|||
umb = true
|
||||
xms = true
|
||||
ems = true
|
||||
ver = 6.22
|
||||
lfn = false
|
||||
|
||||
[serial]
|
||||
serial1 = nullmodem server:127.0.0.1 port:2323 transparent:1
|
||||
|
||||
[autoexec]
|
||||
mount d ~/dos
|
||||
mount c .
|
||||
c:
|
||||
cd bin
|
||||
dvx
|
||||
dir
|
||||
42
listhelp/Makefile
Normal file
42
listhelp/Makefile
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# List Help Library Makefile for DJGPP cross-compilation
|
||||
#
|
||||
# Builds listhelp.lib -- shared list/dropdown helper infrastructure
|
||||
# (dropdown arrow, item length, keyboard navigation, popup list painting).
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -I../core -I../core/platform -I../tasks -I../core/thirdparty
|
||||
|
||||
OBJDIR = ../obj/listhelp
|
||||
LIBSDIR = ../bin/libs
|
||||
|
||||
SRCS = listHelp.c
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
TARGET = $(LIBSDIR)/listhelp.lib
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET) $(LIBSDIR)/listhelp.dep
|
||||
|
||||
$(LIBSDIR)/listhelp.dep: ../config/listhelp.dep | $(LIBSDIR)
|
||||
sed 's/$$/\r/' $< > $@
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBSDIR)
|
||||
$(DXE3GEN) -o $(LIBSDIR)/listhelp.dxe -E _widgetDraw -E _widgetDropdown -E _widgetMax -E _widgetNavigate -E _widgetPaint -U $(OBJS)
|
||||
mv $(LIBSDIR)/listhelp.dxe $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(LIBSDIR):
|
||||
mkdir -p $(LIBSDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/listHelp.o: listHelp.c listHelp.h ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET) $(LIBSDIR)/listhelp.dep
|
||||
101
listhelp/README.md
Normal file
101
listhelp/README.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# listhelp -- Shared List/Dropdown Helper Library
|
||||
|
||||
Shared infrastructure for list and dropdown widgets, built as
|
||||
`listhelp.lib` (DXE3 module). Provides dropdown arrow drawing, item
|
||||
measurement, keyboard navigation, popup rectangle calculation, and
|
||||
popup list painting.
|
||||
|
||||
Used by: Dropdown, ComboBox, ListBox, ListView, TreeView.
|
||||
|
||||
|
||||
## API Reference
|
||||
|
||||
### Dropdown Arrow Glyph
|
||||
|
||||
```c
|
||||
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops,
|
||||
int32_t centerX, int32_t centerY, uint32_t color);
|
||||
```
|
||||
|
||||
Draws the small downward-pointing triangle glyph used on dropdown
|
||||
buttons.
|
||||
|
||||
### Item Measurement
|
||||
|
||||
```c
|
||||
int32_t widgetMaxItemLen(const char **items, int32_t count);
|
||||
```
|
||||
|
||||
Scans an array of item strings and returns the length of the longest
|
||||
one. Used to size dropdown popups and list columns to fit their
|
||||
content.
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
```c
|
||||
int32_t widgetNavigateIndex(int32_t key, int32_t current,
|
||||
int32_t count, int32_t pageSize);
|
||||
```
|
||||
|
||||
Maps arrow key presses to index changes for list navigation. Handles
|
||||
Up, Down, Home, End, PageUp, and PageDown. Returns the new selected
|
||||
index, clamped to valid range.
|
||||
|
||||
### Popup Rectangle Calculation
|
||||
|
||||
```c
|
||||
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font,
|
||||
int32_t contentH, int32_t itemCount,
|
||||
int32_t *popX, int32_t *popY,
|
||||
int32_t *popW, int32_t *popH);
|
||||
```
|
||||
|
||||
Computes the screen rectangle for a dropdown popup overlay. Positions
|
||||
the popup below the widget (or above if there is not enough room
|
||||
below). Limits height to `DROPDOWN_MAX_VISIBLE` items.
|
||||
|
||||
### Popup List Painting
|
||||
|
||||
```c
|
||||
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops,
|
||||
const BitmapFontT *font, const ColorSchemeT *colors,
|
||||
int32_t popX, int32_t popY, int32_t popW, int32_t popH,
|
||||
const char **items, int32_t itemCount,
|
||||
int32_t hoverIdx, int32_t scrollPos);
|
||||
```
|
||||
|
||||
Renders the popup overlay list with items, selection highlight, scroll
|
||||
position, and beveled border. Used by Dropdown and ComboBox for their
|
||||
popup overlays.
|
||||
|
||||
### Constants
|
||||
|
||||
| Name | Value | Description |
|
||||
|------|-------|-------------|
|
||||
| `DROPDOWN_BTN_WIDTH` | 16 | Width of dropdown arrow button area |
|
||||
| `DROPDOWN_MAX_VISIBLE` | 8 | Maximum visible items in popup list |
|
||||
|
||||
|
||||
## Exported Symbols
|
||||
|
||||
Matches prefixes: `_widgetDraw*`, `_widgetDropdown*`, `_widgetMax*`,
|
||||
`_widgetNavigate*`, `_widgetPaint*`.
|
||||
|
||||
|
||||
## Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `listHelp.h` | Public API header |
|
||||
| `listHelp.c` | Complete implementation |
|
||||
| `Makefile` | Builds `bin/libs/listhelp.lib` + dep file |
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
make # builds bin/libs/listhelp.lib + listhelp.dep
|
||||
make clean # removes objects, library, and dep file
|
||||
```
|
||||
|
||||
Depends on: `libtasks.lib`, `libdvx.lib` (via listhelp.dep).
|
||||
181
listhelp/listHelp.c
Normal file
181
listhelp/listHelp.c
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// listHelp.c -- Shared list/dropdown helper functions
|
||||
//
|
||||
// Implements dropdown arrow drawing, item length scanning, keyboard
|
||||
// navigation, and popup list painting used by ListBox, Dropdown,
|
||||
// ComboBox, ListView, and TreeView widgets.
|
||||
|
||||
#include "listHelp.h"
|
||||
#include "../texthelp/textHelp.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDrawDropdownArrow
|
||||
// ============================================================
|
||||
//
|
||||
// Draws a small downward-pointing filled triangle (7, 5, 3, 1 pixels
|
||||
// wide across 4 rows) centered at the given position. Used by both
|
||||
// Dropdown and ComboBox for the drop button arrow glyph.
|
||||
|
||||
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color) {
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
drawHLine(d, ops, centerX - 3 + i, centerY + i, 7 - i * 2, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetMaxItemLen
|
||||
// ============================================================
|
||||
//
|
||||
// Scans an array of string items and returns the maximum strlen.
|
||||
// Shared by ListBox, Dropdown, and ComboBox to cache the widest
|
||||
// item length for calcMinSize without duplicating the loop.
|
||||
|
||||
int32_t widgetMaxItemLen(const char **items, int32_t count) {
|
||||
int32_t maxLen = 0;
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
int32_t slen = (int32_t)strlen(items[i]);
|
||||
|
||||
if (slen > maxLen) {
|
||||
maxLen = slen;
|
||||
}
|
||||
}
|
||||
|
||||
return maxLen;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetNavigateIndex
|
||||
// ============================================================
|
||||
//
|
||||
// Shared keyboard navigation for list-like widgets (ListBox, Dropdown,
|
||||
// ListView, etc.). Encapsulates the Up/Down/Home/End/PgUp/PgDn logic
|
||||
// so each widget doesn't have to reimplement index clamping.
|
||||
//
|
||||
// Key values use the 0x100 flag to mark extended scan codes (arrow
|
||||
// keys, Home, End, etc.) -- this is the DVX convention for passing
|
||||
// scan codes through the same int32_t channel as ASCII values.
|
||||
//
|
||||
// Returns -1 for unrecognized keys so callers can check whether the
|
||||
// key was consumed.
|
||||
|
||||
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize) {
|
||||
if (key == (0x50 | 0x100)) {
|
||||
// Down arrow
|
||||
if (current < count - 1) {
|
||||
return current + 1;
|
||||
}
|
||||
|
||||
return current < 0 ? 0 : current;
|
||||
}
|
||||
|
||||
if (key == (0x48 | 0x100)) {
|
||||
// Up arrow
|
||||
if (current > 0) {
|
||||
return current - 1;
|
||||
}
|
||||
|
||||
return current < 0 ? 0 : current;
|
||||
}
|
||||
|
||||
if (key == (0x47 | 0x100)) {
|
||||
// Home
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (key == (0x4F | 0x100)) {
|
||||
// End
|
||||
return count - 1;
|
||||
}
|
||||
|
||||
if (key == (0x51 | 0x100)) {
|
||||
// Page Down
|
||||
int32_t n = current + pageSize;
|
||||
return n >= count ? count - 1 : n;
|
||||
}
|
||||
|
||||
if (key == (0x49 | 0x100)) {
|
||||
// Page Up
|
||||
int32_t n = current - pageSize;
|
||||
return n < 0 ? 0 : n;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetPaintPopupList
|
||||
// ============================================================
|
||||
//
|
||||
// Shared popup list painting for Dropdown and ComboBox.
|
||||
|
||||
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos) {
|
||||
// Draw popup border
|
||||
BevelStyleT bevel;
|
||||
bevel.highlight = colors->windowHighlight;
|
||||
bevel.shadow = colors->windowShadow;
|
||||
bevel.face = colors->contentBg;
|
||||
bevel.width = 2;
|
||||
drawBevel(d, ops, popX, popY, popW, popH, &bevel);
|
||||
|
||||
// Draw items
|
||||
int32_t visibleItems = popH / font->charHeight;
|
||||
int32_t textX = popX + TEXT_INPUT_PAD;
|
||||
int32_t textY = popY + 2;
|
||||
int32_t textW = popW - TEXT_INPUT_PAD * 2 - 4;
|
||||
|
||||
for (int32_t i = 0; i < visibleItems && (scrollPos + i) < itemCount; i++) {
|
||||
int32_t idx = scrollPos + i;
|
||||
int32_t iy = textY + i * font->charHeight;
|
||||
uint32_t ifg = colors->contentFg;
|
||||
uint32_t ibg = colors->contentBg;
|
||||
|
||||
if (idx == hoverIdx) {
|
||||
ifg = colors->menuHighlightFg;
|
||||
ibg = colors->menuHighlightBg;
|
||||
rectFill(d, ops, popX + 2, iy, textW + TEXT_INPUT_PAD * 2, font->charHeight, ibg);
|
||||
}
|
||||
|
||||
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownPopupRect
|
||||
// ============================================================
|
||||
//
|
||||
// Calculates the screen rectangle for a dropdown/combobox popup list.
|
||||
// Shared between Dropdown and ComboBox since they have identical
|
||||
// popup positioning logic.
|
||||
|
||||
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t itemCount, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
|
||||
int32_t visibleItems = itemCount;
|
||||
|
||||
if (visibleItems > DROPDOWN_MAX_VISIBLE) {
|
||||
visibleItems = DROPDOWN_MAX_VISIBLE;
|
||||
}
|
||||
|
||||
if (visibleItems < 1) {
|
||||
visibleItems = 1;
|
||||
}
|
||||
|
||||
*popX = w->x;
|
||||
*popW = w->w;
|
||||
*popH = visibleItems * font->charHeight + 4;
|
||||
|
||||
if (w->y + w->h + *popH <= contentH) {
|
||||
*popY = w->y + w->h;
|
||||
} else {
|
||||
*popY = w->y - *popH;
|
||||
|
||||
if (*popY < 0) {
|
||||
*popY = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
listhelp/listHelp.h
Normal file
45
listhelp/listHelp.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// listHelp.h -- Public API for the shared list/dropdown helper library
|
||||
//
|
||||
// Declares dropdown arrow drawing, item length scanning, keyboard
|
||||
// navigation, and popup list painting shared across widget DXEs
|
||||
// (ListBox, Dropdown, ComboBox, ListView, TreeView).
|
||||
|
||||
#ifndef LIST_HELP_H
|
||||
#define LIST_HELP_H
|
||||
|
||||
#include "../core/dvxWidgetPlugin.h"
|
||||
|
||||
#define DROPDOWN_BTN_WIDTH 16
|
||||
#define DROPDOWN_MAX_VISIBLE 8
|
||||
|
||||
// ============================================================
|
||||
// Dropdown arrow glyph
|
||||
// ============================================================
|
||||
|
||||
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color);
|
||||
|
||||
// ============================================================
|
||||
// Item measurement
|
||||
// ============================================================
|
||||
|
||||
int32_t widgetMaxItemLen(const char **items, int32_t count);
|
||||
|
||||
// ============================================================
|
||||
// Keyboard navigation (Up/Down/Home/End/PgUp/PgDn)
|
||||
// ============================================================
|
||||
|
||||
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize);
|
||||
|
||||
// ============================================================
|
||||
// Popup rect calculation
|
||||
// ============================================================
|
||||
|
||||
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t itemCount, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
|
||||
|
||||
// ============================================================
|
||||
// Popup list painting
|
||||
// ============================================================
|
||||
|
||||
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos);
|
||||
|
||||
#endif // LIST_HELP_H
|
||||
54
loader/Makefile
Normal file
54
loader/Makefile
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# DVX Loader Makefile for DJGPP cross-compilation
|
||||
#
|
||||
# Builds the bootstrap loader (dvx.exe) that loads DXE modules.
|
||||
# Links dvxPlatformDos.c directly -- the platform layer provides
|
||||
# the DXE export table via platformRegisterDxeExports().
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
EXE2COFF = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/exe2coff
|
||||
CWSDSTUB = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../core/thirdparty
|
||||
LDFLAGS = -lm
|
||||
|
||||
OBJDIR = ../obj/loader
|
||||
POBJDIR = ../obj/loader/platform
|
||||
BINDIR = ../bin
|
||||
|
||||
SRCS = loaderMain.c
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
POBJS = $(POBJDIR)/dvxPlatformDos.o
|
||||
TARGET = $(BINDIR)/dvx.exe
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(POBJS) | $(BINDIR)
|
||||
$(CC) $(CFLAGS) -o $@ $(OBJS) $(POBJS) $(LDFLAGS) -Wl,-Map=$(BINDIR)/dvx.map
|
||||
$(EXE2COFF) $@
|
||||
cat $(CWSDSTUB) $(BINDIR)/dvx > $@
|
||||
rm -f $(BINDIR)/dvx
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(POBJDIR)/dvxPlatformDos.o: ../core/platform/dvxPlatformDos.c | $(POBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(POBJDIR):
|
||||
mkdir -p $(POBJDIR)
|
||||
|
||||
$(BINDIR):
|
||||
mkdir -p $(BINDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/loaderMain.o: loaderMain.c ../core/platform/dvxPlatform.h ../core/dvxTypes.h
|
||||
$(POBJDIR)/dvxPlatformDos.o: ../core/platform/dvxPlatformDos.c ../core/platform/dvxPlatform.h ../core/dvxTypes.h ../core/dvxPalette.h
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(POBJS) $(TARGET) $(BINDIR)/dvx.map
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue