Version 1.5

Changes are just too long to list...

Signed-off-by: Michel Pollet <buserror@gmail.com>
This commit is contained in:
Michel Pollet 2024-01-20 12:05:26 +00:00
parent 4f3127eed3
commit 3fd0540a83
119 changed files with 142127 additions and 36526 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ build-*
compile_commands.json
cachegrind.out.*
callgrind.out.*
.cache
*.miov

View File

@ -3,15 +3,24 @@
CC = gcc
SHELL = /bin/bash
# This is where (g)make looks for the source files for implicit rules
VPATH := src src/format src/drivers nuklear contrib
VPATH := src src/format src/drivers contrib
VPATH += ui_gl
CPPFLAGS += -Isrc -Isrc/format -Isrc/roms -Isrc/drivers
CPPFLAGS += -Icontrib -Inuklear
CPPFLAGS += -Icontrib
CPPFLAGS += -Ilibmish/src
CFLAGS += --std=gnu99 -Wall -Wextra -O2 -g
CPPFLAGS += -Ilibmui/mui
OPTIMIZE ?= -O2 -march=native
CFLAGS += --std=gnu99 -Wall -Wextra -g
CFLAGS += -fno-omit-frame-pointer
CFLAGS += $(OPTIMIZE)
CFLAGS += -Wno-unused-parameter -Wno-unused-function
LDLIBS += -lX11 -lm -lGL -lGLU
LDLIBS += -lpthread -lutil
LDLIBS += -lX11 -lGL -lGLU
LDLIBS += -lpthread -lutil -lm
VERSION := ${shell git log -1 --date=short --pretty="%h %cd"}
CPPFLAGS += -DMII_VERSION="\"$(VERSION)\""
HAS_ALSA := $(shell pkg-config --exists alsa && echo 1)
ifeq ($(HAS_ALSA),1)
@ -26,21 +35,35 @@ BIN := $(O)/bin
LIB := $(O)/lib
OBJ := $(O)/obj
all : $(BIN)/mii_emu
all : $(BIN)/mii_emu_gl
MII_SRC := $(wildcard src/*.c src/format/*.c \
src/drivers/*.c contrib/*.c)
UI_SRC := $(wildcard nuklear/*.c)
UI_SRC := $(wildcard ui_gl/*.c)
SRC := $(MII_SRC) $(UI_SRC)
ALL_OBJ := ${patsubst %, ${OBJ}/%, ${notdir ${SRC:.c=.o}}}
$(BIN)/mii_emu : $(ALL_OBJ)
$(BIN)/mii_emu : $(LIB)/libmish.a
CPPFLAGS += ${shell pkg-config --cflags pixman-1}
LDLIBS += ${shell pkg-config --libs pixman-1}
libmish : $(LIB)/libmish.a
$(BIN)/mii_emu_gl : $(ALL_OBJ) | mui mish
$(BIN)/mii_emu_gl : $(LIB)/libmish.a
$(BIN)/mii_emu_gl : $(LIB)/libmui.a
.PHONY : mish mui
mish : $(LIB)/libmish.a
LDLIBS += $(LIB)/libmish.a
$(LIB)/libmish.a : | $(LIB) $(OBJ) $(BIN)
make -j -C libmish O="../" CC="$(CC)" V="$(V)"
$(LIB)/libmish.a : ${wildcard libmish/src/*} | $(LIB) $(OBJ) $(BIN)
mkdir -p $(OBJ)/libmish && \
make -j -C libmish O="../" CC="$(CC)" V="$(V)" static
LDLIBS += $(LIB)/libmui.a
mui : $(LIB)/libmui.a
$(LIB)/libmui.a : ${wildcard libmui/mui/*} | $(LIB) $(OBJ) $(BIN)
mkdir -p $(OBJ)/libmui && \
make -j -C libmui BUILD_DIR="../" CC="$(CC)" \
V="$(V)" OPTIMIZE="$(OPTIMIZE)" static
# Smartport firmware needs the assembler first
test/asm/%.bin : test/asm/%.asm | $(BIN)/mii_asm
@ -48,17 +71,16 @@ test/asm/%.bin : test/asm/%.asm | $(BIN)/mii_asm
# And it also INCBIN the firmware driver
$(OBJ)/mii_smarport.o : test/asm/mii_smartport_driver.bin
$(OBJ)/libsofd.o : CPPFLAGS += -DHAVE_X11
clean :
rm -rf $(O)
rm -rf $(O); make -C libmui clean; make -C libmish clean
# This is for development purpose. This will recompile the project
# everytime a file is modified.
watch :
while true; do \
clear; $(MAKE) -j all tests; \
inotifywait -qre close_write src src/format nuklear test; \
inotifywait -qre close_write src src/format ui_gl test \
libmui libmui/mui; \
done
tests : $(BIN)/mii_test $(BIN)/mii_cpu_test $(BIN)/mii_asm
@ -82,8 +104,8 @@ else
Q := @
endif
$(OBJ)/%.o : %.c | $(OBJ)
@echo " CC $<"
$(OBJ)/%.o : %.c | $(OBJ)
@echo " CC " ${filter -O%, $(CPPFLAGS) $(CFLAGS)} " $<"
$(Q)$(CC) -MMD $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
$(BIN)/% : | $(BIN)
@ -95,12 +117,19 @@ $(OBJ) $(BIN) $(LIB) :
# Generates the necessary file to help clangd index the files properly.
# This currently has to be done manually, but helps a lot if you use 'kate'
# editor or anthing else that is compatible with the LSP protocol
# editor or anthing else that is compatible with the LSP protocol (vscode too)
compile_commands.json: lsp
lsp:
{ $$(which gmake) CC=gcc V=1 --always-make --dry-run all tests; \
$$(which gmake) CC=gcc V=1 --always-make --dry-run -C libmish ; } | \
$$(which gmake) CC=gcc V=1 --always-make --dry-run -C libmish ; \
$$(which gmake) CC=gcc V=1 --always-make --dry-run -C libmui ; } | \
sh utils/clangd_gen.sh >compile_commands.json
-include $(O)/*.d
-include $(O)/obj/*.d
install:
mkdir -p $(DESTDIR)/bin
mkdir -p $(DESTDIR)/share/games/mii/
cp $(BIN)/mii_emu_gl $(DESTDIR)/bin/

View File

@ -1,3 +1,21 @@
# MII Version Changelog
## 1.5
* BIG update, loads of changes, fixes, improvements.
* New super UI, using home-made libmui, channeling both GS/OS and MacOS 7.x!
* New emulation fixes, way more accurate. Video redone, audio redone.
* New front-end program using XLib and OpenGL 'low level'.
* New Icon.
## 1.0
* Fixed a few graphics rendering bugs/color swapped
* Fixed a few Makefile issues involving pathnamed with 'spaces' in them.
* More tweaks to the emulation, added a few cycles here and there.
## 0.9
* Added a 'debugger' shell, accessible via telnet.
* Added a mini-assembler, used to compile the drivers and the CPU unit tests.
* Added a 'Titan Accelerator IIe' simulation, to turn on/off fast mode.
## 0.5
* Initial release
# MII Apple //e Emulator
I know there are many out there, but none of them were ticking my fancy, so I decide to write my own. To start with it was "How hard can it be really?" then it snowballed as more and more things were fixed & added. It's been shelved for a while because well, it lacked documentation, headers, licence and stuff, so I spent some time cleaning it up for release.
@ -5,8 +23,8 @@ I know there are many out there, but none of them were ticking my fancy, so I de
One primary reason for this project was that linapple (or -pie) codebase is really horrible. It dates back from 2000's or before, with loads of Windows crud leftover, some SDL crud added, the audio just doesn't really work, and overall if you want to hack around the codebase, it's pretty dreadful.
![Monochrome Double-Hi res](docs/screen_main.png)
*Double hires in monochrome*
![Quick how to load and boot](docs/video_main.webm)
*Quick Howto Load & Boot*
I wanted something:
@ -32,12 +50,14 @@ I wanted something:
* Adds a small 'attack' filter when playing back to soften the often annoying 'click' of typical audio effects from the apple II.
* Mouse Card -- mouse isn't captured like in some other emulators.
* No Slot Clock
* Joystick (in a limited way...)
* Joystick Support
* Smartport DMA 'hard drive' card
* "Titan Accelerator //e" simulation, to turn on/off fast mode.
* Terence's J Boldt [1MB ROM card](https://github.com/tjboldt/ProDOS-ROM-Drive), also because I own a couple!
* Floppy Drive [more on that later]
* No dependencies (X11) OpenGL rendering, using Nuklear backend for UI
* No dependencies (X11) OpenGL rendering
* Built-in debugger (using telnet access)
* Super cool looking UI!
![Phosphorescent Green](docs/screen_green.png)
*Good old green monitor style. Theres Amber too.*
@ -48,13 +68,24 @@ I wanted something:
* libgl-dev
* libglu-dev
* libx11-dev
* libpixman-1-dev
* Many of them will probably be installed already.
* For more details on development, see [Compiling](docs/Compiling.md)
* Then just type `make` and it should compile.
* To run it, just type `build-x86_64-linux-gnu/bin/bin/mii_emu` and it should start.
* `mii_emu --help` will display:
* To run it, just type `build-x86_64-linux-gnu/bin/mii_emu_gl` and it should start.
Usage: ./build-x86_64-linux-gnu/bin/mii_emu [options]
## Command line options
If you run it with no options, and there are no config file, it will present
you with a dialog to select the ROMs and the drives.
![Config dialog](docs/screen_config.png)
*Main slot configuration dialog*
You can also use the command line to specify them, and other options.
* `mii_emu_gl --help` will display:
Usage: ./build-x86_64-linux-gnu/bin/mii_emu_gl [options]
Options:
-h, --help This help
-v, --verbose Verbose output
@ -89,16 +120,17 @@ I wanted something:
disk2 - Apple Disk ][
## Key Bindings
There are just a few keys that are mapped for anything useful.
There are just a few keys that are mapped for anything useful. List is not exausive, but here are the main ones:
* **Control-F12** is Control-Reset on the IIe. (**Shift-Control-F12** is **Open Apple-Reset**)
* **'Super'** left and **'Super'** right are **Open** and **Close Apple** keys.
These keys are mapped to the left and right 'Windows' keys on a PC keyboard, and they might want to open the start menu (I know it's the case with Cinnamon), so you might want to disable that.
* **F5** sets the CPU speed to 1MHz
* **F6** to 4MHz.
* These keys control the built-in debugger:
* **Control-F11** Stops the emulator; see the command prompt/telnet for how to proceed, dump state, disassembly etc.
* **Control-F10** 'steps' the emulator, ie one instruction at a time.
* **Control-F9** is 'continue' -- resumes the emulator.
* **Control-F4** Stops the emulator; see the command prompt/telnet for how to proceed, dump state, disassembly etc.
* **Control-F5** is 'continue' -- resumes the emulator.
* **Control-F6** 'steps' the emulator, ie one instruction at a time.
* **Control-F7** 'next' instruction, ie step over a JSR instruction.
![Telnet into mii_emu](docs/screen_mish.png)
@ -124,20 +156,15 @@ There are just a few keys that are mapped for anything useful.
+ This allows you to make sure your disk images aren't corrupted when 'hard rebooting' the emulator, if you are in the process of testing/developing a program for example.
## What it cannot do
* MouseCalc crashes (VBL IRQ, or a mouse mode I don't support yet)
* A2Desktop PT3 player doesn't see keypresses.
* Sometimes the emulator goes in 'slow mode', ie 0.2MHz. Likely the frame scheduler playing up.
* Thats' about it really, all the other things I tried work
* Joystick support is a bit limited, no 'mapping' I used a (USB) 8bitdo NES30 Pro, and it works, but it's not perfect. But, I can play choplifter with it, so it's good enough for now...
* Joystick support is a bit limited, no 'mapping' I used a (USB) 8bitdo NES30 Pro, and it works, but it's not perfect. But, I can play choplifter with it, so it's good enough for now... *NOTE* Soon will have it's own config dialog to do mapping.
## What it could do with
* Not sure about keeping Nuklear, it does a lot bit it's hard work customizing anything
* Add a memory extension 'card' -- not sure why, but hey, why not.
* Joystick support. As soon as I find a USB joystick that vaguely looks retro, I'll get one.
* The main window is 1280x720 on purpose, so it could do Full Screen.
* Port it to Raspbery Pi. I don't expect compiling issues, just video issues with GLes
* Make a tool to 'flatten' overlay files back into the primary image.
* Some sort of UI to select/eject disks.
* Make a UI for the debugger, instead of telnet.
![Total Replay](docs/screen_total.png)

File diff suppressed because it is too large Load Diff

View File

@ -1,194 +0,0 @@
/* libSOFD - Simple Open File Dialog [for X11 without toolkit]
*
* Copyright (C) 2014 Robin Gareus <robin@gareus.org>
*
* 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.
*/
#ifndef LIBSOFD_H
#define LIBSOFD_H 1
#ifdef HAVE_X11
#include <X11/Xlib.h>
#ifdef __cplusplus
extern "C" {
#endif
///////////////////////////////////////////////////////////////////////////////
/* public API */
/** open a file select dialog
* @param dpy X Display connection
* @param parent (optional) if not NULL, become transient for given window
* @param x if >0 set explict initial width of the window
* @param y if >0 set explict initial height of the window
* @return 0 on success
*/
int x_fib_show (Display *dpy, Window parent, int x, int y);
/** force close the dialog.
* This is normally not needed, the dialog closes itself
* when a file is selected or the user cancels selection.
* @param dpy X Display connection
*/
void x_fib_close (Display *dpy);
/** non-blocking X11 event handler.
* It is safe to run this function even if the dialog is
* closed or was not initialized.
*
* @param dpy X Display connection
* @param event the XEvent to process
* @return status
* 0: the event was not for this window, or file-dialog still
* active, or the dialog window is not displayed.
* >0: file was selected, dialog closed
* <0: file selection was cancelled.
*/
int x_fib_handle_events (Display *dpy, XEvent *event);
/** last status of the dialog
* @return >0: file was selected, <0: canceled or inactive. 0: active
*/
int x_fib_status ();
/** query the selected filename
* @return NULL if none set, or allocated string to be free()ed by the called
*/
char *x_fib_filename ();
/** customize/configure the dialog before calling \ref x_fib_show
* changes only have any effect if the dialog is not visible.
* @param k key to change
* 0: set current dir to display (must end with slash)
* 1: set title of dialog window
* 2: specify a custom X11 font to use
* 3: specify a custom 'places' file to include
* (following gtk-bookmark convention)
* @param v value
* @return 0 on success.
*/
int x_fib_configure (int k, const char *v);
/** customize/configure the dialog before calling \ref x_fib_show
* changes only have any effect if the dialog is not visible.
*
* @param k button to change:
* 1: show hidden files
* 2: show places
* 3: show filter/list all (automatically hidden if there is no
* filter function)
* @param v <0 to hide the button >=0 show button,
* 0: set button-state to not-checked
* 1: set button-state to checked
* >1: retain current state
* @return 0 on success.
*/
int x_fib_cfg_buttons (int k, int v);
/** set custom callback to filter file-names.
* NULL will disable the filter and hide the 'show all' button.
* changes only have any effect if the dialog is not visible.
*
* @param cb callback function to check file
* the callback function is called with the file name (basename only)
* and is expected to return 1 if the file passes the filter
* and 0 if the file should not be listed by default.
* @return 0 on success.
*/
int x_fib_cfg_filter_callback (int (*cb)(const char*));
#ifdef __cplusplus
}
#endif
#endif /* END X11 specific functions */
#ifdef __cplusplus
extern "C" {
#endif
/* 'recently used' API. x-platform
* NOTE: all functions use a static cache and are not reentrant.
* It is expected that none of these functions are called in
* parallel from different threads.
*/
/** release static resources of 'recently used files'
*/
void x_fib_free_recent ();
/** add an entry to the recently used list
*
* The dialog does not add files automatically on open,
* if the application succeeds to open a selected file,
* this function should be called.
*
* @param path complete path to file
* @param atime time of last use, 0: NOW
* @return -1 on error, number of current entries otherwise
*/
int x_fib_add_recent (const char *path, time_t atime);
/** get a platform specific path to a good location for
* saving the recently used file list.
* (follows XDG_DATA_HOME on Unix, and CSIDL_LOCAL_APPDATA spec)
*
* @param application-name to use to include in file
* @return pointer to static path or NULL
*/
const char *x_fib_recent_file(const char *appname);
/** save the current list of recently used files to the given filename
* (the format is one file per line, filename URL encoded and space separated
* with last-used timestamp)
*
* This function tries to creates the containing directory if it does
* not exist.
*
* @param fn file to save the list to
* @return 0: on success
*/
int x_fib_save_recent (const char *fn);
/** load a recently used file list.
*
* @param fn file to load the list from
* @return 0: on success
*/
int x_fib_load_recent (const char *fn);
/** get number of entries in the current list
* @return number of entries in the recently used list
*/
unsigned int x_fib_recent_count ();
/** get recently used entry at given position
*
* @param i entry to query
* @return pointer to static string
*/
const char *x_fib_recent_at (unsigned int i);
#ifdef __cplusplus
}
#endif
#endif // header guard

580
contrib/mii-icon-64.h Normal file
View File

@ -0,0 +1,580 @@
/* this file is auto-generated by icon-convert-tcc.c */
static const unsigned long mii_icon64[] = {
64,64,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01000000,
0x01000000, 0x039e9ea1, 0x04bebfc1, 0x04c9cacd, 0x04c9cacd, 0x05ccced0, 0x05ccced0, 0x05cfcfd1,
0x05d0d1d3, 0x06d2d3d6, 0x06d3d4d6, 0x06d2d3d6, 0x06d4d5d7, 0x0778a88e, 0x07499b67, 0x07499b67,
0x07499b67, 0x07499b67, 0x07499b67, 0x074a9b67, 0x06499b65, 0x064a9c65, 0x064a9c64, 0x064a9c63,
0x064a9b61, 0x064a9c5f, 0x064b9d5e, 0x054b9c5d, 0x054b9c5c, 0x054c9c55, 0x044d9a48, 0x01000000,
0x01000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x2ff1f3f7, 0x7bf4f6fa, 0xaff4f7fb, 0xd0f4f7fb, 0xf2f5f7fb,
0xfdf5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc,
0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xffc2ddc8, 0xff5fba4f, 0xff60ba4e,
0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4e,
0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4e, 0xff60ba4f, 0xfe5fba52,
0xf65fba53, 0xd35fba52, 0xb15eb955, 0x7c5db85c, 0x315ab663, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x51f3f5f9, 0xcef5f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc,
0xfff5f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc,
0xfff5f8fc, 0xfff5f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f7fb, 0xfff1f5f7, 0xff70be68, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff60bb4b, 0xcd5eb954, 0x515bb75f, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x13edeff3,
0xb4f4f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc,
0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc,
0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f8fc, 0xffbad9be, 0xff61bb47,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff60bb49, 0xb25db959,
0x1358b361, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x20eef1f4, 0xdff5f7fb,
0xfff5f8fc, 0xfff5f8fc, 0xfff5f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f7fb, 0xfff5f8fc, 0xfff5f8fc,
0xfff5f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f7fb, 0xfff5f8fc,
0xfff5f8fc, 0xfff5f7fb, 0xfff5f8fc, 0xfff5f8fc, 0xfff5f7fb, 0xfff5f8fc, 0xffecf3f2, 0xff61ba52,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xdd5eb953, 0x1f59b465, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x14eaecf0, 0xdff5f7fb, 0xfff5f7fb,
0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb,
0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb,
0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f8fb, 0xffa2cfa3,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xdd5eb954, 0x1256b062, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01000000, 0xb4f4f6fa, 0xfff5f7fb, 0xfff5f7fb,
0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb,
0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb,
0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xfff5f7fb, 0xffd6e6dc,
0xff60bb48, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xb05db858, 0x01000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x4feff1f5, 0xfff5f7fa, 0xfff5f7fb, 0xfff4f7fa,
0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fb, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fb, 0xfff5f7fa, 0xfff5f7fb,
0xfff4f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fb, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fb, 0xfff5f7fa,
0xfff5f7fb, 0xfff4f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fb, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa,
0xff75bf6e, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff60bb4a, 0x4959b460, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x01000000, 0xccf3f5f9, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa,
0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f6fa, 0xfff5f7fa, 0xfff5f7fa,
0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f6fa, 0xfff5f7fa,
0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa, 0xfff5f7fa,
0xffb3d6b5, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46, 0xff61bb46,
0xff61bb46, 0xff61bb46, 0xff61bb46, 0xc85db854, 0x01000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x2ce8e9ed, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff4f6fa,
0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa,
0xfff4f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa,
0xfff5f6fa, 0xfff4f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa, 0xfff5f6fa,
0xffdbe9e1, 0xff60ba49, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47,
0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47,
0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff61bb47,
0xff61bb47, 0xff61bb47, 0xff61bb47, 0xff60ba4c, 0x2854ae62, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x76f0f1f4, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9,
0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9,
0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9,
0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9,
0xfff5f6f9, 0xffc8b863, 0xffcbb440, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540,
0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540,
0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540, 0xffccb540,
0xffccb540, 0xffccb540, 0xffccb540, 0xffccb541, 0x70cdaf48, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x01000000, 0xa7f1f2f5, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9,
0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9,
0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9,
0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9, 0xfff5f6f9,
0xfff5f6f9, 0xfffad0a0, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xa2f9b325, 0x01000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x02000000, 0xcef2f3f6, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f8,
0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9,
0xfff4f5f8, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9,
0xfff4f5f9, 0xfff4f5f8, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9, 0xfff4f5f9,
0xfff4f5f9, 0xfff8e3d0, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xcafbb425, 0x02000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x04000000, 0xdef2f3f6, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff6f2f0, 0xfffcb625, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xd7fab425, 0x03000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x05000000, 0xeff2f3f6, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff5f5f8, 0xfffbc376, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xe6fbb526, 0x05000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x07000000, 0xf8f3f4f7, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8, 0xfff4f5f8,
0xfff4f5f8, 0xfff4f5f8, 0xfff9d4ad, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xf3fcb525, 0x06000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x07000000, 0xfdf3f5f7, 0xfff3f4f7, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f4f7,
0xfff4f5f8, 0xfff3f5f8, 0xfff3f4f7, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f4f7, 0xfff3f5f8, 0xfff3f5f8,
0xfff3f4f7, 0xfff4f5f8, 0xfff3f5f8, 0xfff3f4f7, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f4f7, 0xfff3f5f8,
0xfff3f5f8, 0xfff3f4f7, 0xfff4f5f8, 0xfff3f5f8, 0xfff3f4f7, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f4f7,
0xfff3f5f8, 0xfff3f5f8, 0xfff7e2cf, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb726, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xf9fcb526, 0x06000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f7,
0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f8,
0xfff3f5f7, 0xfff3f5f7, 0xffe7e8eb, 0xff88898a, 0xff939496, 0xffeeeff2, 0xfff3f5f8, 0xfff3f5f8,
0xfff3f5f8, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f8, 0xfff3f5f8,
0xfff3f5f8, 0xfff3f5f8, 0xfff5efea, 0xfffcb726, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xffc58919, 0xff82540b, 0xffd7981d, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfcfdb626, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff3f5f8, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7,
0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7,
0xfff3f5f7, 0xfff3f5f7, 0xffc9cbcd, 0xff616162, 0xff616162, 0xffd9dbdd, 0xfff3f5f7, 0xfff3f5f7,
0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7, 0xfff3f5f7,
0xfff3f5f7, 0xfff3f5f7, 0xfff4f6f8, 0xfffbbb53, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb726, 0xff784a0b, 0xff653b08, 0xff9d6912, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827,
0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb827, 0xfffdb626, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7,
0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7,
0xfff3f4f7, 0xfff3f4f7, 0xffc9cbcd, 0xff616162, 0xff616162, 0xffd7d9db, 0xfff3f4f7, 0xfff3f4f7,
0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7,
0xfff3f4f7, 0xfff3f4f7, 0xfff3f4f7, 0xfff9c88d, 0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb626,
0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb425, 0xfe76460f, 0xfe65370d, 0xfe9a6414, 0xfffdb525,
0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb626,
0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb626, 0xfffdb424, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff3f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6,
0xfff3f4f7, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6,
0xfff2f4f6, 0xfff3f4f7, 0xffc9cacc, 0xff606162, 0xff606162, 0xffd7d9db, 0xfff2f4f6, 0xfff2f4f6,
0xfff2f4f6, 0xfff2f4f6, 0xfff3f4f7, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6,
0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2bcb3, 0xfff86c3f, 0xfffd6e41, 0xfffe6e42, 0xfffe6e42,
0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfe723618, 0xfe653113, 0xfe974425, 0xfffe6e42,
0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfffe6e42,
0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfffe6e42, 0xfffe6e43, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6,
0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6,
0xfff2f4f6, 0xfff2f4f6, 0xffc9cacc, 0xff606162, 0xff606162, 0xffd7d8da, 0xfff2f4f6, 0xfff2f4f6,
0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6,
0xfff2f4f6, 0xfff2f4f6, 0xfff2f4f6, 0xfff2cdcb, 0xfff56240, 0xfffd6542, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6744, 0xff72311c, 0xff652c17, 0xff973f27, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6745, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff2f3f6, 0xfff2f3f6, 0xfff2f3f6, 0xfff2f3f6, 0xfff2f3f6,
0xfff2f4f6, 0xfff2f3f6, 0xfff2f3f6, 0xfff2f4f6, 0xfff2f3f6, 0xfff2f3f6, 0xfff2f3f6, 0xfff2f3f6,
0xfff2f3f6, 0xfff2f4f6, 0xffd1d2d4, 0xff606162, 0xff606162, 0xffe1e3e5, 0xfff2f3f6, 0xfff2f3f6,
0xfff2f3f6, 0xfff2f3f6, 0xfff2f4f6, 0xfff2f3f6, 0xfff2f3f6, 0xfff2f4f6, 0xfff2f3f6, 0xfff2f3f6,
0xfff2f3f6, 0xfff2f3f6, 0xfff2f3f6, 0xfff3e0e0, 0xfff36140, 0xfffc6542, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xff8d3c27, 0xff65291a, 0xffac4931, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6745, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5,
0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5,
0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xffd2d3d5, 0xffd8d9db, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5,
0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5,
0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff4eff0, 0xfff0603f, 0xfffb6442, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfff16646, 0xffd35d42, 0xfff86847, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6745, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5,
0xfff2f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5,
0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5,
0xfff2f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff2f3f5, 0xfff1f3f5,
0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff3f5f6, 0xffef6b51, 0xfffa6441, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6745, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5,
0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5,
0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5,
0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5,
0xfff1f3f5, 0xfff2f3f5, 0xfff1f3f5, 0xfff2f3f5, 0xfff08f82, 0xfff96441, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6745, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff1f2f5, 0xfff1f2f4, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f4,
0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f4, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f4, 0xfff1f2f5, 0xfff1f2f5,
0xfff1f2f4, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f4, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f4, 0xfff1f2f5,
0xfff1f2f5, 0xfff1f2f4, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f4, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f4,
0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f4, 0xfff2f3f5, 0xffef9f95, 0xfff86341, 0xfffd6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6745, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5,
0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5,
0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5,
0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5,
0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff1f2f5, 0xfff0b1ab, 0xfff66340, 0xfffd6542, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6745, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4,
0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4,
0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4,
0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4,
0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xffefbab5, 0xfff56240, 0xfffd6542, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643,
0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6643, 0xfffe6745, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff1f2f4, 0xfff1f1f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f1f4,
0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f1f4, 0xfff1f2f4, 0xfff1f2f4,
0xfff1f1f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f1f4, 0xfff1f2f4,
0xfff1f2f4, 0xfff1f1f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f2f4, 0xfff1f1f4,
0xfff1f2f4, 0xfff1f2f4, 0xfff1f1f4, 0xfff1f2f4, 0xfeedc8c6, 0xfdeb5a41, 0xfdf35e43, 0xfdf55f44,
0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44,
0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44,
0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfdf55f44, 0xfef56046, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff1f1f4, 0xfff1f1f3, 0xfff1f1f4, 0xfff1f1f3, 0xfff1f1f4,
0xfff1f1f4, 0xfff1f1f4, 0xfff1f1f3, 0xfff1f1f4, 0xfff1f1f4, 0xfff1f1f3, 0xfff1f1f4, 0xfff1f1f3,
0xfff1f1f4, 0xfff1f1f4, 0xfff1f1f4, 0xfff1f1f3, 0xfff1f1f4, 0xfff1f1f4, 0xfff1f1f3, 0xfff1f1f4,
0xfff1f1f3, 0xfff1f1f4, 0xfff1f1f4, 0xfff1f1f4, 0xfff1f1f3, 0xfff1f1f4, 0xfff1f1f4, 0xfff1f1f3,
0xfff1f1f4, 0xfff1f1f3, 0xfff1f1f4, 0xfff1f1f4, 0xffe0c5c8, 0xffc6313f, 0xffce3341, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03644, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3,
0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3,
0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3,
0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3,
0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xffe5cfd1, 0xffc5313e, 0xffcd3341, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03644, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff0f1f3, 0xfff1f1f3,
0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff0f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff0f1f3,
0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff0f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3,
0xfff0f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xfff0f1f3, 0xfff1f1f3, 0xfff1f1f3,
0xfff1f1f3, 0xfff0f1f3, 0xfff1f1f3, 0xfff1f1f3, 0xffe5d0d2, 0xffc4313e, 0xffcd3341, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03644, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff1f2f3, 0xffd09da0, 0xffc4313e, 0xffcd3341, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xfed03644, 0x08000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff1f2f3, 0xffc9a8aa, 0xffbb898c, 0xffb98587, 0xffb88386,
0xffb88386, 0xffb88285, 0xffb87e81, 0xffb6696e, 0xffb62d39, 0xffc5313e, 0xffcd3341, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xfcd03644, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2,
0xfff0f1f2, 0xfff0f1f2, 0xfff0f1f2, 0xfff2f1f2, 0xffa42934, 0xffa72935, 0xffa82a35, 0xffa82a35,
0xffa82a35, 0xffa82a35, 0xffaa2a36, 0xffb12c38, 0xffbc2f3b, 0xffc8313f, 0xffce3341, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xfbd03644, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfff0f0f1, 0xffeff0f1, 0xffeff0f1, 0xfff0f0f1, 0xffeff0f1,
0xfff0f0f1, 0xffeff0f1, 0xfff0f0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xfff0f0f1,
0xffeff0f1, 0xfff0f0f1, 0xffeff0f1, 0xfff0f0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1,
0xfff0f0f1, 0xffeff0f1, 0xfff0f0f1, 0xfff2f2f3, 0xffae2b37, 0xffb52d39, 0xffb82e3a, 0xffb82e3a,
0xffb82e3a, 0xffb82e3a, 0xffba2e3b, 0xffbe2f3c, 0xffc5313e, 0xffcb3240, 0xffcf3341, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xfad03644, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1,
0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffe4e5e6,
0xffedeeef, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1,
0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xfff2f3f4, 0xffb92f3b, 0xffc3303d, 0xffc6313f, 0xffc7313f,
0xffc7313f, 0xffc7313f, 0xffc8313f, 0xffca323f, 0xffcc3340, 0xffce3341, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffcf3442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442,
0xffd03442, 0xffd03442, 0xffd03442, 0xffd03442, 0xf8d03643, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1,
0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffdadbdc, 0xff626363,
0xff919292, 0xffebeced, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1,
0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xfff2f2f4, 0xffbe3241, 0xffc83245, 0xffcc3347, 0xffcc3347,
0xffcc3347, 0xffcc3347, 0xffcd3348, 0xffcd3448, 0xffcd3448, 0xffce3448, 0xffce3448, 0xffce3448,
0xffce3448, 0xffce3448, 0xffce3448, 0xffce3448, 0xffce3448, 0xffce3448, 0xffce3448, 0xffce3446,
0xfe892439, 0xfe461832, 0xffb83145, 0xffce3549, 0xffce3549, 0xffce3549, 0xffce3549, 0xffce3549,
0xffce3549, 0xffce3549, 0xffce3549, 0xffce3549, 0xf7cd384c, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffefeff1,
0xffeff0f1, 0xffeff0f1, 0xffeff0f1, 0xffefeff1, 0xffeff0f1, 0xffeff0f1, 0xffe3e3e5, 0xff6a6b6b,
0xff5f5f60, 0xff949495, 0xffeaebec, 0xffeff0f1, 0xffefeff1, 0xffeff0f1, 0xffeff0f1, 0xffeff0f1,
0xffeff0f1, 0xffefeff1, 0xffeff0f1, 0xfff2f2f3, 0xff933e9a, 0xff953d98, 0xff953d98, 0xff953d98,
0xff953d98, 0xff953d98, 0xff953d98, 0xff953d98, 0xff953d98, 0xff953d98, 0xff953d98, 0xff953d98,
0xff953d98, 0xff953d98, 0xff953d98, 0xff953d98, 0xff953d98, 0xff953d98, 0xff943d99, 0xfe672d75,
0xfe36193d, 0xfe391c41, 0xff823f8f, 0xff953f99, 0xff953f99, 0xff953f99, 0xff953f99, 0xff953f99,
0xff953f99, 0xff953f99, 0xff953f99, 0xff953f99, 0xfd92449d, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xffefeff1, 0xffeeeff0, 0xffefeff1, 0xffefeff1, 0xffefeff1,
0xffefeff1, 0xffefeff1, 0xffefeff1, 0xffefeff1, 0xffefeff1, 0xffeeeff0, 0xffefeff1, 0xffd6d6d7,
0xff686969, 0xff5f5f60, 0xff878788, 0xffe2e2e4, 0xffefeff1, 0xffefeff1, 0xffeeeff0, 0xffefeff1,
0xffefeff1, 0xffefeff1, 0xffefeff1, 0xfff2f2f3, 0xff953d98, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff933c96, 0xff632b70, 0xfe2e1b49,
0xfe321e4c, 0xff783a85, 0xff963e98, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xfe944099, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0,
0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0,
0xffd6d7d8, 0xff6d6e6e, 0xff5f5f60, 0xff6c6c6d, 0xffc4c4c5, 0xffeeeef0, 0xffeeeff0, 0xffeeeff0,
0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xfff0eff1, 0xff953d98, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff86388e, 0xff4d2564, 0xff281c4f, 0xff332055,
0xff7a3b89, 0xff963e98, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xfe944099, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x08000000, 0xfdeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0,
0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeeeff0,
0xffeeeff0, 0xffe0e1e2, 0xff868787, 0xff5f5f60, 0xff5f5f60, 0xff888889, 0xffcdcdce, 0xffedeeef,
0xffeeeff0, 0xffeeeff0, 0xffeeeff0, 0xffeae8ed, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff8a3991, 0xff632c74, 0xff2e1d54, 0xff261c51, 0xff442863, 0xff843e90,
0xff963e97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xfd953f99, 0x07000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x07000000, 0xf9edeeef, 0xffeeeeef, 0xffeeeef0, 0xffeeeef0, 0xffeeeeef,
0xffeeeef0, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeef0, 0xffeeeef0,
0xffeeeeef, 0xffeeeef0, 0xffebeced, 0xffb7b7b8, 0xff666667, 0xff5f5f5f, 0xff5f5f5f, 0xff7a7a7a,
0xffb4b5b6, 0xffd6d6d8, 0xffededef, 0xffe2dde5, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff953d98, 0xff8e3b93,
0xff7a3485, 0xff58296d, 0xff2e1d55, 0xff261c52, 0xff302057, 0xff67357c, 0xff904097, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xfb93429a, 0x06000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x07000000, 0xf0ececed, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef,
0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef,
0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffe2e2e3, 0xffababab, 0xff676767, 0xff5f5f5f,
0xff5e5e5f, 0xff5a5a5e, 0xff5b5a64, 0xff7f7888, 0xff71377d, 0xff7a3584, 0xff7f3688, 0xff82368a,
0xff85378b, 0xff83378a, 0xff803688, 0xff7a3485, 0xff70307d, 0xff612b73, 0xff492464, 0xff2f1e56,
0xff261c52, 0xff261c52, 0xff332159, 0xff623378, 0xff893f93, 0xff963e98, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xe7934199, 0x06000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x06000000, 0xdfebebec, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef,
0xffeeeeef, 0xffeeeeef, 0xffedeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffeeeeef,
0xffeeeeef, 0xffeeeeef, 0xffeeeeef, 0xffedeeef, 0xffeeeeef, 0xffeeeeef, 0xffe7e7e7, 0xffc0c0c1,
0xff939394, 0xff5b5b5f, 0xff514f5b, 0xff444057, 0xff362f54, 0xff2c2352, 0xff281e51, 0xff271c52,
0xff281c53, 0xff271c53, 0xff271c52, 0xff261c52, 0xff261c52, 0xff261c52, 0xff281d53, 0xff35225a,
0xff552e6f, 0xff753985, 0xff8e4096, 0xff963e98, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xdb924098, 0x05000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x04000000, 0xd0eaeaeb, 0xffeeeeee, 0xffededee, 0xffeeeeee, 0xffededee,
0xffeeeeee, 0xffededee, 0xffeeeeee, 0xffededee, 0xffededee, 0xffeeeeee, 0xffededee, 0xffeeeeee,
0xffededee, 0xffeeeeee, 0xffededee, 0xffeeeeee, 0xffededee, 0xffededee, 0xffeeeeee, 0xffededee,
0xffeeeeee, 0xffe8e8e9, 0xffd1d1d3, 0xffa196ad, 0xff6f3d80, 0xff623577, 0xff583071, 0xff532f70,
0xff4f2b6b, 0xff532e6f, 0xff572f71, 0xff5e3175, 0xff6a357e, 0xff773986, 0xff853d8f, 0xff914098,
0xff953e98, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97,
0xff963d97, 0xff963d97, 0xff963d97, 0xff963d97, 0xcf914199, 0x04000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x03000000, 0xace7e7e8, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee,
0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee,
0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee,
0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffbfa8c6, 0xff953f98, 0xff953f98, 0xff954098, 0xff954099,
0xff954099, 0xff954099, 0xff954098, 0xff954098, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98,
0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98,
0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98,
0xff953f98, 0xff953f98, 0xff953f98, 0xff953f98, 0xa78e4398, 0x02000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x01000000, 0x7ee2e2e3, 0xffeeeeee, 0xffededee, 0xffeeeeee, 0xffededed,
0xffeeeeee, 0xffeeeeee, 0xffededed, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffededee, 0xffeeeeee,
0xffededed, 0xffeeeeee, 0xffeeeeee, 0xffededed, 0xffeeeeee, 0xffeeeeee, 0xffeeeeee, 0xffededee,
0xffeeeeee, 0xffededed, 0xffeeeeee, 0xff8aacd9, 0xff4f8bcd, 0xff518acc, 0xff5289cc, 0xff5289cc,
0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc,
0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc,
0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc,
0xff5289cc, 0xff5289cc, 0xff5289cc, 0xff5289cc, 0x7a5b7abd, 0x01000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x38cbcbcb, 0xffedeeee, 0xffedeeee, 0xffeeeeee, 0xffedeeee,
0xffeeeeee, 0xffedeeee, 0xffedeeee, 0xffedeeee, 0xffedeeee, 0xffedeeee, 0xffedeeee, 0xffeeeeee,
0xffedeeee, 0xffeeeeee, 0xffedeeee, 0xffedeeee, 0xffedeeee, 0xffedeeee, 0xffedeeee, 0xffedeeee,
0xffeeeeee, 0xffedeeee, 0xfff0f0f0, 0xff3da2dc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff129bdb, 0x343579b4, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x0b000000, 0xd3e9e9e9, 0xffededee, 0xffededee, 0xffededee,
0xffededee, 0xffededee, 0xffededee, 0xffededee, 0xffededee, 0xffededee, 0xffededee, 0xffededee,
0xffededee, 0xffededee, 0xffededee, 0xffededee, 0xffededee, 0xffededee, 0xffededee, 0xffededee,
0xffededee, 0xffededee, 0xffe1e7ed, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xcf2295d4, 0x0a000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x03000000, 0x62d5d5d5, 0xffededed, 0xffededed, 0xffededed,
0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed,
0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed,
0xffededed, 0xffededed, 0xffc7d8ea, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff099cdb, 0x5c2884c0, 0x02000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0e000000, 0xc0e5e5e6, 0xffededed, 0xffededed,
0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed,
0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed, 0xffededed,
0xffededed, 0xffededed, 0xffa5c6e5, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff019ddc, 0xbd2093d2, 0x0d000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x02000000, 0x2ca4a4a4, 0xe5eaeaea, 0xffececec,
0xffededed, 0xffededed, 0xffececec, 0xffeceded, 0xffececed, 0xffececec, 0xffececed, 0xffeceded,
0xffececec, 0xffededed, 0xffededed, 0xffececec, 0xffeceded, 0xffececed, 0xffececec, 0xffececed,
0xffeceded, 0xffeeeeee, 0xff66acdf, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xe41798d8, 0x29216392, 0x02000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x06000000, 0x3cb1b1b1, 0xe6eaeaea,
0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec,
0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec,
0xffececec, 0xffe3e8ed, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xe51598d8, 0x391d6ea2, 0x05000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x07000000, 0x339f9f9f,
0xc4e4e4e4, 0xffededed, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec,
0xffececec, 0xffececec, 0xffededed, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec,
0xffececec, 0xffbbd1e7, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff039ddc, 0xc31994d3,
0x301e6290, 0x06000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x05000000,
0x19020202, 0x73d0d0d0, 0xdce8e8e8, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec,
0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec, 0xffececec,
0xffededed, 0xff84b7e1, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc,
0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff009ddc, 0xff069cdc, 0xdc1697d6, 0x712084be, 0x18000101,
0x04000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x02000000, 0x0b000000, 0x1d000000, 0x55bdbdbd, 0x97dbdbdb, 0xc4e3e3e3, 0xdee7e7e7, 0xfaececec,
0xffececec, 0xffededed, 0xffededed, 0xffececec, 0xffececec, 0xffededed, 0xffececec, 0xffededed,
0xffe4e7ec, 0xff069cdb, 0xff009ddc, 0xff019ddc, 0xff089cdb, 0xff0b9cdb, 0xff0b9cdb, 0xff0b9cdb,
0xff0b9cdb, 0xff0b9cdb, 0xff0b9cdb, 0xff0b9cdb, 0xff0b9cdb, 0xff0b9cdb, 0xff0b9cdb, 0xff0b9cdb,
0xff0b9cdb, 0xff0b9cdb, 0xff0b9cdb, 0xff0c9cdb, 0xff0d9cdb, 0xff0e9cdb, 0xff0e9bdb, 0xff119bdb,
0xfc159ada, 0xe31598d7, 0xc61794d3, 0x971b8dc9, 0x572378af, 0x1c000000, 0x0b000000, 0x01000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x02000000, 0x07000000, 0x10000000, 0x18000000, 0x1e000000, 0x22000000,
0x261c1c1c, 0x29424242, 0x2a4f4f4f, 0x2b555555, 0x2b565656, 0x2b575757, 0x2b575757, 0x2b575757,
0x2b484c51, 0x2b06304a, 0x2b05324c, 0x2b07324c, 0x2b0a314c, 0x2b0b304b, 0x2b0b304b, 0x2b0b304b,
0x2b0b304b, 0x2b0b304b, 0x2b0b304b, 0x2b0b304b, 0x2b0b304b, 0x2b0b304b, 0x2b0b304b, 0x2b0b304b,
0x2b0b304b, 0x2b0b304b, 0x2b0b304b, 0x2b0c304b, 0x2b0c304b, 0x2b0c2f4a, 0x2a0c2e48, 0x27082134,
0x22000000, 0x1e000000, 0x17000000, 0x0f000000, 0x07000000, 0x02000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01000000, 0x01000000, 0x02000000,
0x03000000, 0x03000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000,
0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000,
0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000,
0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x04000000, 0x03000000, 0x03000000,
0x02000000, 0x01000000, 0x01000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
};

BIN
contrib/mii-icon-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

File diff suppressed because it is too large Load Diff

BIN
docs/screen_config.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
docs/video_main.webm Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,20 +0,0 @@
KREATIVE SOFTWARE RELAY FONTS FREE USE LICENSE
version 1.2f
Permission is hereby granted, free of charge, to any person or entity (the "User") obtaining a copy of the included font files (the "Software") produced by Kreative Software, to utilize, display, embed, or redistribute the Software, subject to the following conditions:
1. The User may not sell copies of the Software for a fee.
1a. The User may give away copies of the Software free of charge provided this license and any documentation is included verbatim and credit is given to Kreative Korporation or Kreative Software.
2. The User may not modify, reverse-engineer, or create any derivative works of the Software.
3. Any Software carrying the following font names or variations thereof is not covered by this license and may not be used under the terms of this license: Jewel Hill, Miss Diode n Friends, This is Beckie's font!
3a. Any Software carrying a font name ending with the string "Pro CE" is not covered by this license and may not be used under the terms of this license.
4. This license becomes null and void if any of the above conditions are not met.
5. Kreative Software reserves the right to change this license at any time without notice.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE SOFTWARE OR FROM OTHER DEALINGS IN THE SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -23,8 +23,10 @@ EXTRA_LDFLAGS = $(BASE_LDFLAGS)
LIBSRC := ${notdir ${wildcard src/*.c}}
# Tell make/gcc to find the files in VPATH
VPATH = src
VPATH += tests
SRC_VPATH = src
SRC_VPATH += tests
vpath %.c $(SRC_VPATH)
IPATH = src
include ./Makefile.common
@ -47,6 +49,7 @@ tools: $(TOOLS)
tests: $(TESTS)
LIBOBJ := ${patsubst %, ${OBJ}/%, ${notdir ${LIBSRC:.c=.o}}}
$(LIBOBJ) : | $(OBJ)
$(LIB)/$(TARGET).a : $(LIBOBJ) | $(LIB)
$(LIB)/$(TARGET).so.$(SOV) : $(LIBOBJ) | $(LIB)/$(TARGET).a

View File

@ -69,7 +69,40 @@ _mish_capture_select(
if (c->input.fd == -1 || (c->flags & MISH_CLIENT_DELETE))
mish_client_delete(m, c);
}
unsigned int max_lines = m->backlog.max_lines;
if (m->flags & MISH_CLEAR_BACKLOG) {
max_lines = 1; // zero is unlimited, we don't want that
m->flags &= ~MISH_CLEAR_BACKLOG;
printf("Clearing backlog has %d lines\n", m->backlog.size);
}
/*
* It is not enough just to remove the top lines from the backlog,
* We also need to check all the current clients in case they have
* a line we are going to remove in their display... We just have to
* check the top line in this case, and 'scroll' their display to the
* next line, if applicable
*/
if (max_lines && m->backlog.size > max_lines) {
mish_line_p l;
while ((l = TAILQ_FIRST(&m->backlog.log)) != NULL) {
TAILQ_REMOVE(&m->backlog.log, l, self);
m->backlog.size--;
m->backlog.alloc -= sizeof(*l) + l->size;
// now check the clients for this line
TAILQ_FOREACH_SAFE(c, &m->clients, self, safe) {
if (c->bottom == l)
c->bottom = TAILQ_NEXT(l, self);
if (c->sending == l)
c->sending = TAILQ_NEXT(l, self);
}
free(l);
if (m->backlog.size <= max_lines)
break;
}
}
}
if ((m->flags & MISH_CONSOLE_TTY) &&
tcsetattr(0, TCSAFLUSH, &m->orig_termios))
perror("thread tcsetattr");

View File

@ -166,9 +166,10 @@ mish_argv_make(
state = s_copy;
break;
case s_copyquote:
if (*dup == '\\')
if (*dup == '\\') {
state = s_skip;
else if (*dup == quote) {
dup++;
} else if (*dup == quote) {
state = s_newarg;
dup++;
if (*dup) *dup++ = 0;

View File

@ -37,7 +37,7 @@ _mish_input_init(
int flags = fcntl(fd, F_GETFL, NULL);
if (flags == 1) {
perror("mish: input F_GETFL");
// perror("mish: input F_GETFL");
flags = 0;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)

View File

@ -134,6 +134,8 @@ enum {
MISH_QUIT = (1 << 31),
// the process console we started was a tty, so has terminal settings
MISH_CONSOLE_TTY = (1 << 30),
// request to clear the backlog
MISH_CLEAR_BACKLOG = (1 << 29),
};
typedef struct mish_t {
@ -151,6 +153,7 @@ typedef struct mish_t {
pthread_t main; // todo: allow pause/stop/resume?
struct {
unsigned int max_lines; // max lines in backlog (0 = unlimited)
mish_line_queue_t log;
unsigned int size; // number of lines in backlog
size_t alloc; // number of bytes in the backlog

View File

@ -11,7 +11,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for isatty etc
#include <ctype.h> // for isdigit
#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
@ -106,12 +106,12 @@ mish_prepare(
if (tty)
m->flags |= MISH_CONSOLE_TTY;
if (tcgetattr(0, &m->orig_termios))
perror("tcgetattr");
;//perror("tcgetattr");
struct termios raw = m->orig_termios;
raw.c_iflag &= ~(ICRNL | IXON);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); // ISIG
if (tcsetattr(0, TCSAFLUSH, &raw))
perror("tcsetattr");
;//perror("tcsetattr");
}
#endif
if (!(caps & MISH_CAP_NO_TELNET)) {
@ -271,12 +271,36 @@ _mish_cmd_mish(
printf(" max sizes: vector: %d input: %d\n",
c->output.size, c->input.line ? c->input.line->size : 0);
}
if (argv[1] && !strcmp(argv[1], "clear")) {
printf("Clearing backlog\n");
m->flags |= MISH_CLEAR_BACKLOG;
}
if (argv[1] && !strcmp(argv[1], "backlog")) {
if (argv[2]) {
if (!strcmp(argv[2], "clear")) {
m->flags |= MISH_CLEAR_BACKLOG;
} else if (!strcmp(argv[2], "max") && argv[3] && isdigit(argv[3][0])) {
m->backlog.max_lines = atoi(argv[3]);
printf("Backlog max lines set to %d\n", m->backlog.max_lines);
} else if (isdigit(argv[2][0])) {
m->backlog.max_lines = atoi(argv[2]);
printf("Backlog max lines set to %d\n", m->backlog.max_lines);
} else
fprintf(stderr, "Unknown backlog command '%s'\n", argv[2]);
} else {
printf("Backlog: %6d/%6d lines (%5dKB)\n",
m->backlog.size, m->backlog.max_lines,
(int)m->backlog.alloc / 1024);
}
}
}
MISH_CMD_NAMES(mish, "mish");
MISH_CMD_HELP(mish,
"Displays mish status.",
"[cmd...] Displays mish status.",
"backlog [clear] [max <n>] - show backlog status\n"
" also set the maximum lines in the backlog\n"
" (0 = unlimited)\n"
"Show status and a few bits of internals.");
MISH_CMD_REGISTER(mish, _mish_cmd_mish);

View File

@ -176,11 +176,12 @@ mish_telnet_prepare(
port = port & 0x3fff;
}
int tries = 5;
int tries = 10;
do {
b.sin_port = htons(port);
if (bind(m->telnet.listen, (struct sockaddr *)&b, sizeof(b)) == -1) {
fprintf(stderr, "%s can't bind %d\n", __func__, ntohs(b.sin_port));
perror("bind");
port = port + (random() & 0x3ff);
continue;
}

View File

@ -47,9 +47,10 @@ mish_argv_make(
state = s_copy;
break;
case s_copyquote:
if (*dup == '\\')
if (*dup == '\\') {
state = s_skip;
else if (*dup == quote) {
dup++;
} else if (*dup == quote) {
state = s_newarg;
dup++;
if (*dup) *dup++ = 0;

View File

@ -5,7 +5,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
#define MISH_DEBUG 1
#include "mish_cmd.c"
int main()

101
libmui/Makefile Normal file
View File

@ -0,0 +1,101 @@
CC = gcc
CPPFLAGS += -Imui
# for bsd_queue.h
CPPFLAGS += -I../libmish/src
CPPFLAGS += -DUI_HAS_XCB=1 -DUI_HAS_XKB=1
OPTIMIZE ?= -O0 -g
CFLAGS += --std=gnu99 -Wall -Wextra
CFLAGS += $(OPTIMIZE)
CFLAGS += -Wno-unused-parameter -Wno-unused-function
# PIC is necessary for the shared library/plugin to work
CFLAGS += -fPIC
CPPFLAGS += ${shell pkg-config --cflags pixman-1}
LDLIBS += ${shell pkg-config --libs pixman-1}
LDLIBS += $(shell pkg-config --libs \
xcb xcb-shm xcb-randr \
xkbcommon-x11)
LDLIBS += -lm -ldl
O := $(BUILD_DIR)build-$(shell $(CC) -dumpmachine)
BIN := $(O)/bin
OBJ := $(O)/obj/libmui
LIB := $(O)/lib
MUI_SRC := $(wildcard mui/*.c)
SRC := $(MUI_SRC)
MUI_OBJ := ${patsubst %, $(OBJ)/%, ${notdir ${SRC:.c=.o}}}
SRC_VPATH := mui tests
SRC_VPATH += ../ui_gl
vpath %.c $(SRC_VPATH)
CPPFLAGS += -I../contrib
VERSION := ${shell git log -1 --date=short --pretty="%h %cd"}
CPPFLAGS += -DUI_VERSION="\"$(VERSION)\""
TARGET_LIB := $(LIB)/libmui.a
all : $(BIN)/mui_playground $(LIB)/ui_tests.so
.PHONY : static
static : $(TARGET_LIB)
ifeq ($(V),1)
Q :=
else
Q := @
endif
$(TARGET_LIB) : $(MUI_OBJ) | $(LIB)
@echo " AR $@"
$(Q)$(AR) rcs $@ $^
$(OBJ)/ui_tests.o : CPPFLAGS += -Itests -I../ui_gl
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_slots.o
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_loadbin.o
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_1mb.o
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_2dsk.o
# use a .temp file, otherwise the playground tries to reload before the file
# is fully written, and it fails.
# the ${filter} are there to make the sure object files are linked before the .a
$(LIB)/ui_tests.so : $(OBJ)/ui_tests.o $(LIB)/libmui.a | $(O)
@echo " LDSO $@"
$(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -shared -fPIC -o $@.temp \
${filter %.o, $^} ${filter %.a, $^} $(LDLIBS) && \
mv $@.temp $@
$(BIN)/mui_playground : $(OBJ)/mui_playground.o $(LIB)/libmui.a
$(OBJ)/%.o : %.c | $(OBJ)
@echo " CC " ${filter -O%, $(CPPFLAGS) $(CFLAGS)} " $<"
$(Q)$(CC) -MMD $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
$(BIN)/% : | $(BIN)
@echo " LD $@"
$(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(O) $(OBJ) $(BIN) $(LIB):
@mkdir -p $@
clean :
rm -rf $(O)
# This is for development purpose. This will recompile the project
# everytime a file is modified.
watch :
while true; do \
clear; $(MAKE) -j all; \
inotifywait -qre close_write mui tests ../ui_gl; \
done
compile_commands.json: lsp
lsp:
{ $$(which gmake) CC=gcc V=1 --always-make --dry-run all ; } | \
sh ../utils/clangd_gen.sh >compile_commands.json
-include $(OBJ)/*.d

132
libmui/README.md Normal file
View File

@ -0,0 +1,132 @@
# What is this?
This is a contender for the World Record for Feature Creep Side Project. It is pretty high in the contender list as it's a bolt on to *another* contender for the World Record for Feature Creep Side Project (the MII Apple //e emulator).
It is a library that duplicate a lot of a Macintosh Classic "Toolbox" APIs. It is not a complete implementation, but it is enough to make a few simple applications, also, all the bits I needed for the MII emulator.
# Why?
Well I wanted a UI library for MII -- something without tons of dependencies, and I didn't want the typical 'game like' style with "Arrow Keys + Return + Escape" sort of menus.
I started with Nuklear immediate mode UI, but it both look super ugly, AND is very limited as soon as you want to do something 'custom', and I didn't see myself hacking into that codebase. One thing I particularly dislike is the 'layout' engine that decide to put stuff where it wants, and it's *never* in the 'right' place, like a hard case of 'computer say so' -- typicaly result into
something like Programmer's Art. That's why Linux On The Deskop is famous for it's Pixel Perfection polished UIs. *Cough*.
The other things I don't like with the trendy IM UIs is that they promise you that you don't have to keep a separate state around blah blah, however they forget to mention that there IS a state kept for you, based on hash values, and if you are unlucky enough to have a hash clash, you are screwed. I've seen that happen in real life, and it's not pretty to debug.
Also, I realized I had quite a few bits that I could use to make my own library anyway, so I started to do that.
Also, I like making my own toys. There, happy now?
# What can it do?
Well, it has a few of the classic 'managers' that the Macintosh Toolbox had. Or the later GS/OS. However it behaves more like a modern system, it's not 'synchronous' like the old toolbox. Stuff gets redrawn even if you are clicking/dragging etc without having to 'do it yourself'.
It is designed to draw into a 'screen' that is an ARGB buffer. You can then copy that to wherever you fancy.
* In the case of MII, it's an OpenGL texture that gets overlayed;
* In the 'example' folder, the playground demo copies it to an X11 window via a XCB 'shared' pixmap, so works great even via remote X11.
The library is 'smart', like the old OSes, it keeps track of 'invalid' regions, and only redraws what is needed, so theres very very little overdraw.
One small drawback is that the output *has* to be ARGB -- so if you want to render say on a 16 bit framebuffer, you'll have to do the conversion yourself,
luckily, you only have to draw/convert the 'dirty' regions, so it's not too bad.
It could be possible to 'vectorize' the rendering to vertice buffers and stuff, but really, it's not needed, it's fast enough as it is and it would fall back the 'lets redraw everything' behaviour of the IMmediate UI libraries.
# How does it differ from the original?
Well, in terms of looks, it's kinda like I started with MacOS8/9, but removed all the grayscale bits.
Bizarelly, I think System 7 'flat' looks has aged better than the 'grayscale' look of 8/9, so I went with that. Most of the 'visible' difference are really related to the dialog's popup menus, which are a lot more 'OS8' than 'OS7'.
In terms of of the API, one massive change is that it is fully asynchronous, and
you *can't* just spinloop and draw things in a window or GrafPort whenever you feel like it, like on the original. Instead, you change the state of the UI, and it will redraw itself when it needs to. This is a lot more like modern UI libraries in that respect.
Many things will look familiar, if like me you were a developer back then,
I tried to use most of the names of things as is; and I even have all the
elements drawn by WDEF, CDEF, LDEF functions, which is fun. Also pretty easy
to customize. by adding your own, like in the old days.
Also, for the API, it's all 'callback' based. No more polling. You don't have
to 'ask' the UI if something happened, it will tell you when it does. This is
also a lot more like modern UI libraries.
It is also a lot simpler than the original in concept; everything is either a *mui_window* (Well, windows, menubars and menus) OR a *mui_control* (Menu titles, menu items, everything in windows, even separator lines).
## Window Manager
It can create windows, and it can draw into them. Has up to 15 'layers', and can do clipping and stuff. Got the typical 'BringToFront' behaviour, and you can drag windows around.
I deliberately limited the number of coordinate systems to 2 -- like the old one; so you get the 'screen coordinates' and the 'window content coordinates'. I was half tempted to create a fully hierarchical system, but realized it was really not neeeded, and would just make things more complicated.
It's a 'smart' window manager, it keeps track of an 'invalid' list of rectangles, and clips to that when redrawing, so it doesn't redraw the whole window every time, yeah, like the original. None of that 'lets redraw absolutely everything every frame' stuff.
- It's missing bits like 'zooming' (TODO), and 'resizing' (TODO).
- It doesn't do transparent windows. It is by design, it draws windows 'top down' to optimize clipping -- adding transparency wouldn't be hard, but I'd have to draw the windows 'bottom up' to handle blending, and we'd revert back to drawing a lot of stuff for very little return.
- Also, you can always alpha blend the whole *ui* 'screen' to wherever you want, so it's not like you can't have transparency.
## Menu Manager
Menubar, menus, checkmarks, keyboard shortcuts, all that stuff. Made to looks like System 7/8, or GS/OS. This was the most complicated bit to do, and it's still not perfect -- turns out the original had quite a few features that are not obvious at first glance.
- It has hierarchical menus, they don't behave *perfectly* like the original yet, I'll have to revisit that for a better match.
- It's missing displaying and scrolling super large popups (TODO).
+ It will call your 'action' callback when you click on a menu item, or when you press the key of a keyboard shortcut. Easy, peasy.
+ There is half baked support for sticky menus, but it's disabled for now as it's not quite right yet.
## Control Manager
Buttons, checkboxes, radio buttons, scrollbars (vertical), wrapping textboxes, all that stuff.
- It's missing bits like Edit Field (TODO), and a Slider.
## List Manager
More or less hard coded to display filenames so far, but plain lists are actually easier than this so. Handle arrow keys/page up/down, scroll wheel, etc.
- It's missing a way to 'compress' the font and/or use ellipsis abreviations (TODO) when the item text is too long.
## Alerts
It has the typical 'Cancel'+'OK' alert.
- Could do with more types of alerts (TODO).
## Standard File
It has the classic 'Open' a file dialog. Haven't needed the other one. yet.
- Could do with a 'Save' dialog (TODO).
- Maybe a 'period correct' way to handle previously visited folders...
## Resource Manager
Nope! Not there; I'd need some sort of ResEdit and stuff -- and now that is *ONE* Feature Creep Too Far thank you very much.
I have a vague idea of making some sort of MessagePack format for resources, but that's for another day.
# So, what *are* the dependencies?
Well, *external* dependencies are just *libpixman* -- that's it. It's a library that does pixel pushing, it's used everywhere, it's massively optimized, and it has 'regions' which are super useful for clipping -- they aren't *as good* as the regions in QuickDraw, but they are good enough.
Other bits I used to make it are rolled into the source code anyway, so it's not like you need to install them.
* libpixman: as mentioned, you'll need libpixman-dev installed.
* [libcg](https://github.com/xboot/libcg): a small antialiased renderer that looks vaguely like *cairo* but comes in just 2 files, with a nice licence. It's likely far from being the most optimized, but it's largely good enough.
* [stb_truetype.h](https://github.com/nothings/stb): a small library to load truetype fonts, it's used to load the system fonts.
* stb_ttc.h: my own extension to stb_truetype.h, makes up a font/glyph dictionary with hash tables, font textures etc.
That's it, all the other bits I already had 'in stock' -- the 2D geometry bits I made 25+ years ago, they were rolled in [libc3](https://github.com/buserror/simreprap) as well.
# How do I build it?
It's a simple Makefile, so you just need to do a 'make' in the root directory. It will build the library, and the tests/demos/samples.
To build the tests/demos/samples, you'll need:
* xcb xcb-shm xcb-randr xkbcommon-x11 -- this is just to run the 'playground' demo, which is a simple window with a few buttons and stuff.
## Nvidia Driver gotcha
* *Note* that if you use the nvidia binary driver (I do), you will need to add a flag to your config, otherwise the playground won't work.
- Add: 'Option \"AllowSHMPixmaps\" \"1\"' to the "Device" In your /etc/X11/xorg.conf file.
# How do I use it?
Well the best way is to hack around *mui_playground.c* and *ui_tests.c*. It's a simple window with a few buttons and stuff, and it's a good place to start.
The cool thing about ui_playground is that it loads ui_test.so as a *plugin* and auto-reload it if it detects a change. So you can hack around ui_test.c, and it will reload it and run it again! You can code a new dialog insanely fast with that, pretty must as fast as you would with a resource editor.
A good trick is to use 'make watch' on the *libmui* directory in a terminal tab, and it will rebuild the library and the playground automatically when you change something, that with the 'auto save' of your editor, and you will have a constantly building/running playground as you hack around.
Have fun!
# FAQ!
* Ok so, is there a dark mode?
- nope
* Fine, but at least it is themable, right?
- nope
* Tranparent Windows and a Cube Effect ?!@?!
- nope
* What, menu aren't sticky? They don't stay up when you release the mouse button? Common throw us a bone here man, it's 2023!
- nope
* But, is it at least using the super trendy tools? cmake? meson? ninja?
- nope
* What about autotools, seriously?
- nope
* Are there any bindings for any of the fancy new languages? Rust? Go? Python?
- nope
* Is it using any of the super trendy framerworks? GTK? QT?
- nope
* What about SDL? It's cross Platform, it is Ugly Everywhere Equally!
- nope
* And Wayland then? Wayland is The Future after all!
- nope

BIN
libmui/fonts/Cairo.ttf Normal file

Binary file not shown.

31701
libmui/fonts/Charcoal_mui.sfd Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

BIN
libmui/fonts/Dingbat.ttf Normal file

Binary file not shown.

BIN
libmui/fonts/typicon.ttf Normal file

Binary file not shown.

53788
libmui/fonts/typicons.sfd Normal file

File diff suppressed because it is too large Load Diff

46
libmui/mui/c2_arrays.c Normal file
View File

@ -0,0 +1,46 @@
/*
* c2_arrays.c
*
* Created on: 4 Feb 2023
* Author: michel
*/
#include "c2_arrays.h"
/*! Simplify array 'a' into 'b', return 1 if it was.
* This takes a list of rectangles 'a', duplicates are skipped. If two rectangles
* overlap, see if the union is 'worth' it. Returns results in array 'b'
* Returns 1 if the b array has been simplified somehow, zero if not
*/
int
c2_rect_array_simplify(
c2_rect_array_p a,
c2_rect_array_p b)
{
c2_rect_array_clear(b);
for (unsigned int i = 0; i < a->count; i++) {
int addit = 1;
c2_rect_p ra = &a->e[i];
for (unsigned int j = 0; j < b->count && addit; j++) {
c2_rect_p rb = &b->e[j];
if (c2_rect_equal(rb, ra)) { // already a copy here
addit = 0;
} else if (c2_rect_contains_rect(rb, ra)) {
addit = 0;
} else if (c2_rect_intersect_rect(rb, ra)) {
c2_rect_t o = *ra;
c2_rect_union(&o, rb);
long sa = c2_rect_surface_squared(ra);
long sb = c2_rect_surface_squared(rb);
long so = c2_rect_surface_squared(&o);
if (so <= (sa + sb)) {
*rb = o;
addit = 0;
}
}
}
if (addit)
c2_rect_array_add(b, *ra);
}
return b->count < a->count;
}

33
libmui/mui/c2_arrays.h Normal file
View File

@ -0,0 +1,33 @@
/*
* c2_arrays.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef C2_ARRAYS_H_
#define C2_ARRAYS_H_
#include "c2_geometry.h"
#include "c_array.h"
DECLARE_C_ARRAY(c2_pt_t, c2_pt_array, 16);
DECLARE_C_ARRAY(c2_coord_t, c2_coord_array, 16);
DECLARE_C_ARRAY(c2_rect_t, c2_rect_array, 16);
IMPLEMENT_C_ARRAY(c2_pt_array);
IMPLEMENT_C_ARRAY(c2_coord_array);
IMPLEMENT_C_ARRAY(c2_rect_array);
/*! Simplify array 'a' into 'b', return 1 if it was.
* This takes a list of rectangles 'a', duplicates are skipped. If two rectangles
* overlap, see if the union is 'worth' it. Returns results in array 'b'
* Returns 1 if the b array has been simplified somehow, zero if not
*/
int
c2_rect_array_simplify(
c2_rect_array_p a,
c2_rect_array_p b);
#endif /* C2_ARRAYS_H_ */

819
libmui/mui/c2_geometry.c Normal file
View File

@ -0,0 +1,819 @@
/*
* c2_geometry.c
*
* C2DGeometry Implementation
* Created: Monday, March 30, 1998 11:51:47
* Converted back to C99 May 2013
*
* Copyright (C) 1998-2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include "c2_geometry.h"
#include "c2_geometry_poly.h"
const char *
c2_rect_as_str(
const c2_rect_p r )
{
static char ret[8][32];
static int reti = 0;
char *d = ret[reti];
reti = (reti+1) & 7;
if (r)
sprintf(d, "[%d,%d,%d,%d]",r->v[0],r->v[1],r->v[2],r->v[3]);
else strcpy(d, "[NULL]");
return d;
}
uint8_t
c2_rect_get_next_edge(
uint8_t inEdge,
int inCW )
{
uint8_t ret = inCW ? (inEdge << 1) & 0xf : (inEdge >> 1) & 0xf;
return ret ? ret : inCW ? out_Left : out_Bottom;
}
uint8_t
c2_rect_is_on_edge(
const c2_rect_p r,
const c2_pt_p p )
{
if (p->v[X] == r->tl.v[X])
return out_Left;
if (p->v[Y] == r->tl.v[Y])
return out_Top;
if (p->v[X] == r->br.v[X])
return out_Right;
if (p->v[Y] == r->br.v[Y])
return out_Bottom;
return 0;
}
int
c2_rect_get_edge(
const c2_rect_p r,
uint8_t inEdge,
c2_segment_p outEdge )
{
switch (inEdge) {
case out_Left:
c2_segment_set(outEdge, r->tl.v[X], r->tl.v[Y], r->tl.v[X], r->br.v[Y]);
break;
case out_Top:
c2_segment_set(outEdge, r->tl.v[X], r->tl.v[Y], r->br.v[X], r->tl.v[Y]);
break;
case out_Right:
c2_segment_set(outEdge, r->br.v[X], r->tl.v[Y], r->br.v[X], r->br.v[Y]);
break;
case out_Bottom:
c2_segment_set(outEdge, r->tl.v[X], r->br.v[Y], r->br.v[X], r->br.v[Y]);
break;
default:
return -1;
}
return 0;
}
int
c2_rect_get_corner(
const c2_rect_p r,
uint8_t inCorner,
c2_pt_p outCorner,
int inCW )
{
uint8_t corner = inCW ? inCorner : c2_rect_get_next_edge(inCorner, 0);
switch (corner) {
case corner_TopLeft:
*outCorner = r->tl;
break;
case corner_TopRight:
outCorner->v[X] = r->br.v[X];
outCorner->v[Y] = r->tl.v[Y];
break;
case corner_BottomRight:
*outCorner = r->br;
break;
case corner_BottomLeft:
outCorner->v[X] = r->tl.v[X];
outCorner->v[Y] = r->br.v[Y];
break;
default:
return -1;
}
return 0;
}
int
c2_rect_clip_segment(
const c2_rect_p r,
c2_segment_p s,
c2_segment_p o,
char * outEdges )
{
int accept = 0, done = 0;
uint8_t outcode0 = c2_rect_get_out_code(r, &s->a);
uint8_t outcode1 = c2_rect_get_out_code(r, &s->b);
*o = *s;
do {
if (!(outcode0 | outcode1)) {
accept = 1; done = 1;
continue;
} else if ((outcode0 & outcode1) != 0) {
if (outEdges) {
outEdges[0] = outcode0;
outEdges[1] = outcode1; // return offending borders
}
done = 1; // don't accept
continue;
}
// at least one end is outside, pick it
c2_pt_t *p;
uint8_t *outcode;
uint8_t dummy;
uint8_t *edge = &dummy;
if (outcode0) {
outcode = &outcode0;
p = &o->a;
if (outEdges) edge = &edge[0];
} else {
outcode = &outcode1;
p = &o->b;
if (outEdges) edge = &edge[1];
}
if (*outcode & out_Top) {
p->v[X] = s->a.v[X] +
(s->b.v[X] - s->a.v[X]) *
(double)(r->tl.v[Y] - s->a.v[Y]) / (s->b.v[Y] - s->a.v[Y]);
p->v[Y] = r->tl.v[Y];
*edge = out_Top;
*outcode &= ~out_Top;
} else if (*outcode & out_Bottom) {
p->v[X] = s->a.v[X] + (s->b.v[X] - s->a.v[X]) *
(double)(r->br.v[Y] - s->a.v[Y]) / (s->b.v[Y] - s->a.v[Y]);
p->v[Y] = r->br.v[Y];
*edge = out_Bottom;
*outcode &= ~out_Bottom;
}
if (*outcode & out_Left) {
p->v[Y] = s->a.v[Y] + (s->b.v[Y] - s->a.v[Y]) *
(double)(r->tl.v[X] - s->a.v[X]) / (s->b.v[X] - s->a.v[X]);
p->v[X] = r->tl.v[X];
*edge = out_Left;
*outcode &= ~out_Left;
} else if (*outcode & out_Right) {
p->v[Y] = s->a.v[Y] + (s->b.v[Y] - s->a.v[Y]) *
(double)(r->br.v[X] - s->a.v[X]) / (s->b.v[X] - s->a.v[X]);
p->v[X] = r->br.v[X];
*edge = out_Right;
*outcode &= ~out_Right;
}
*outcode = c2_rect_get_out_code(r, p);
*outcode = 0;
} while (!done);
return accept;
}
// C2DRectangle::Intersect(
int
c2_rect_clip_rect(
const c2_rect_p r,
const c2_rect_p s,
c2_rect_p o )
{
uint8_t outcode0 = c2_rect_get_out_code(r, &s->tl);
uint8_t outcode1 = c2_rect_get_out_code(r, &s->br);
// if both corners are on the same 'side' as the
// other corner, there can't be any intersection anyway
if (outcode0 & outcode1) {
o->br = o->tl;// make sure result rect is empty
return 0;
}
// ^^ this is equivalent to testing for all corners:
// for (int c = out_Left; c <= out_Bottom; c <<= 1)
// if ((outcode0 & c) && (outcode1 & c))
// return 0;
/* here we /know/ the rectangle intersects, or contains the
* other, so just detect where the corners are in relation to
* the rectangle, and copy the 'clipped' coordinates over
*/
*o = *s;
if (outcode0 & out_Left)
o->tl.x = r->tl.x;
if (outcode0 & out_Top)
o->tl.y = r->tl.y;
if (outcode1 & out_Right)
o->br.x = r->br.x;
if (outcode1 & out_Bottom)
o->br.y = r->br.y;
// only return 1 is the result is a true
// rectangle. adjacent rectangles would
// return a 'valid' intersection with with
// width or height being 0
return c2_rect_width(o) && c2_rect_height(o);
}
//! return true if all corners of 'r2' are inside 'r1'
int
c2_rect_contains_rect(
const c2_rect_p r1,
const c2_rect_p r2 )
{
for (int c = corner_TopLeft; c <= corner_BottomLeft; c <<= 1) {
c2_pt_t p = {};
c2_rect_get_corner(r2, c, &p, 1);
if (!c2_rect_contains_pt(r1, &p))
return 0;
}
return 1;
}
// Return true if rectangle 'r' intersects with rectangle 's'
int
c2_rect_intersect_rect(
const c2_rect_p s,
const c2_rect_p r )
{
// if any of the corners are inside, we are intersecting anyway
for (int c = corner_TopLeft; c <= corner_BottomLeft; c <<= 1) {
c2_pt_t p = {};
c2_rect_get_corner(r, c, &p, 1);
if (c2_rect_contains_pt(s, &p))
return 1;
}
// if any of the edges of 'r' intersects 's', we intersect
for (int e = out_Left; e <= out_Bottom; e <<= 1) {
c2_segment_t seg, clip;
c2_rect_get_edge(r, e, &seg);
if (c2_rect_clip_segment(s, &seg, &clip, NULL))
return 1;
}
// if 'r' *contains* 's', we intersect
return c2_rect_contains_rect(r, s);
}
uint8_t
c2_rect_get_out_code(
const c2_rect_p r,
const c2_pt_p p )
{
return ((p->v[X] < r->tl.v[X] ? out_Left : (p->v[X] > r->br.v[X]) ? out_Right : 0) |
(p->v[Y] < r->tl.v[Y] ? out_Top : (p->v[Y] > r->br.v[Y]) ? out_Bottom : 0));
}
void
c2_rect_clip_pt(
const c2_rect_p r,
c2_pt_p p )
{
uint8_t o = c2_rect_get_out_code(r, p);
if (o & out_Left)
p->v[X] = r->tl.v[X];
else
p->v[X] = r->br.v[X];
if (o & out_Top)
p->v[Y] = r->tl.v[Y];
else
p->v[Y] = r->br.v[Y];
}
void
c2_polyline_clear(
c2_polyline_p pl)
{
const c2_rect_t z = C2_RECT_ZERO;
pl->bounds = z;
c2_pt_array_clear(&pl->pt);
}
//c2_polyline_t::GetSegment(
int
c2_polyline_get_segment(
c2_polyline_p pl,
long ind,
c2_segment_p o )
{
if (ind > pl->pt.count)
return 0;
o->a = pl->pt.e[ind];
o->b = pl->pt.e[ind == pl->pt.count ? 1 : ind];
return 1;
}
void
c2_polyline_offset(
c2_polyline_p pl,
c2_coord_t inX,
c2_coord_t inY )
{
for (unsigned int i = 0; i < pl->pt.count; i++) {
pl->pt.e[i].x += inX;
pl->pt.e[i].y += inY;
}
c2_rect_offset(&pl->bounds, inX, inY);
}
void
c2_polyline_scale(
c2_polyline_p pl,
double inFactor,
c2_rect_p inSkip /* = NULL */ )
{
long pMax = pl->pt.count;
c2_pt_t * c = pl->pt.e;
long count = 0;
long total = pMax;
int testrect = inSkip != NULL;
c2_rect_scale(&pl->bounds, inFactor);
if (c2_rect_height(&pl->bounds) > 3 && c2_rect_width(&pl->bounds) > 3 && total >= 8) {
c2_pt_t delta = C2_PT_ZERO;
c2_pt_t o = C2_PT_ZERO;
short skip = 11;
c2_pt_t *d = c;
c2_pt_t orig;
for (int i = 0; i < total; i++, c++) {
orig = *c;
c2_pt_scale(c, inFactor);
delta.v[X] += c->v[X] - o.v[X];
delta.v[Y] += c->v[Y] - o.v[Y];
int add = 0;
if (testrect)
add = orig.v[X] == inSkip->tl.v[X] || orig.v[X] == inSkip->br.v[X] ||
orig.v[Y] == inSkip->tl.v[Y] || orig.v[X] == inSkip->br.v[Y];
if (!add)
add = skip > 10 || delta.v[X] > 3 || delta.v[X] < -3 ||
delta.v[Y] > 3 || delta.v[Y] < -3;
if (add) {
*d++ = *c;
count++;
delta.v[X] = delta.v[Y] = 0;
skip = 0;
} else
skip++;
o = *c;
}
if (count < 2) {
c = pl->pt.e;
c[0] = pl->bounds.tl;
c[1] = pl->bounds.br;
count = 2;
}
if (count & 1) {
*d++ = o;
count++;
}
} else {
for (int i = 1; i <= total; i++, c++)
c2_pt_scale(c, inFactor);
}
pl->pt.count = count;
// TODO: trim?
}
void
c2_polyline_add_pt(
c2_polyline_p pl,
c2_pt_p p )
{
if (!pl->pt.count) {
pl->bounds.tl = *p;
pl->bounds.br = *p;
} else {
pl->bounds.tl.v[X] = PMIN(pl->bounds.tl.v[X], p->v[X]);
pl->bounds.tl.v[Y] = PMIN(pl->bounds.tl.v[Y], p->v[Y]);
pl->bounds.br.v[X] = PMAX(pl->bounds.br.v[X], p->v[X]);
pl->bounds.br.v[Y] = PMAX(pl->bounds.br.v[Y], p->v[Y]);
}
c2_pt_array_add(&pl->pt, *p);
}
#if 0
void
c2_polyline_t::Frame()
{
Lock();
SInt32 pMax = GetCount();
c2_pt_t *c = (c2_pt_t*)GetItemPtr(1);
MoveTo(Pixel_32k(c->v[X]), Pixel_32k(c->v[Y]));
for (int i = 1; i <= GetCount(); i++, c++) {
if (i == 1)
i = 1;
LineTo(Pixel_32k(c->v[X]), Pixel_32k(c->v[Y]));
}
Unlock();
}
#endif
void
c2_polyline_array_break(
c2_polyline_array_p pa)
{
pa->current = NULL;
}
void
c2_polyline_array_add_pt(
c2_polyline_array_p pa,
c2_pt_p p )
{
if (!pa->current) {
pa->current = malloc(sizeof(c2_polyline_t));
memset(pa->current, 0, sizeof(*pa->current));
c2_polyline_array_add(pa, pa->current);
}
c2_polyline_add_pt(pa->current, p);
}
int
c2_polyline_array_clip(
c2_polyline_array_p pa,
c2_rect_p clip,
c2_polyline_array_p outPoly )
{
for (long poly = 0; poly < pa->count; poly++) {
c2_polyline_t *p = pa->e[poly];
if (!p)
break;
long pMax = p->pt.count;
c2_pt_t * last = p->pt.e;
int lastIn = c2_rect_contains_pt(clip, last);
c2_pt_t * current = last+1;
int currentIn = c2_rect_contains_pt(clip, current);
for (long pIndex = 2; pIndex <= pMax; pIndex++) {
if (lastIn && currentIn) { // both points are IN
//outPoly.AddPoint(*last);
c2_polyline_array_add_pt(outPoly, current);
} else if (lastIn && !currentIn) { // line goes OUT
c2_segment_t src = { .a = *last, .b = *current };
c2_segment_t dst;
c2_rect_clip_segment(clip, &src, &dst, NULL);
c2_polyline_array_add_pt(outPoly, &dst.b);
c2_polyline_array_break(outPoly);
} else if (!lastIn && currentIn) { // line goes IN
c2_segment_t src = { .a = *last, .b = *current };
c2_segment_t dst;
c2_rect_clip_segment(clip, &src, &dst, NULL);
c2_polyline_array_break(outPoly);
c2_polyline_array_add_pt(outPoly, &dst.a);
c2_polyline_array_add_pt(outPoly, &dst.b);
} else { // line outside
c2_segment_t src = { .a = *last, .b = *current };
c2_segment_t dst;
if (c2_rect_clip_segment(clip, &src, &dst, NULL)) { // both point are out, but crosses the rectangle
c2_polyline_array_break(outPoly);
c2_polyline_array_add_pt(outPoly, &dst.a);
c2_polyline_array_add_pt(outPoly, &dst.b);
}
}
last++;
current++;
lastIn = c2_rect_contains_pt(clip, last);
currentIn = c2_rect_contains_pt(clip, current);
}
}
return 0;
}
void
c2_polyline_array_scale(
c2_polyline_array_p pa,
float inFactor,
c2_rect_p inSkip /* = NULL */ )
{
for (unsigned int ind = 0; ind < pa->count; ind++) {
c2_polyline_scale(pa->e[ind], inFactor, inSkip);
}
}
void
c2_polyline_array_offset(
c2_polyline_array_p pa,
c2_coord_t inX,
c2_coord_t inY )
{
for (unsigned int ind = 0; ind < pa->count; ind++) {
c2_polyline_offset(pa->e[ind], inX, inY);
}
}
void
c2_scanline_array_proper_alloc(
c2_scanline_array_p a,
c2_coord_t height)
{
c2_scanline_array_realloc(a, height);
memset(a->e, 0, height * sizeof(a->e[0]));
}
void
c2_scanline_array_proper_clear(
c2_scanline_array_p a)
{
for (unsigned int i = 0; i < a->count; i++)
c2_coord_array_free(&a->e[i]);
}
void
c2_scanline_array_add_coord(
c2_scanline_array_p a,
int inY,
c2_coord_t inX )
{
if (inY < 0 || inY >= (int)a->count) return;
c2_coord_array_p l = &a->e[inY];
if (l->count == 0 || inX >= l->e[l->count-1]) {
c2_coord_array_add(l, inX);
return;
}
int num = l->count;
int ind = 0;
c2_coord_t * cur = l->e;
while (*cur < inX && num--) {
cur++;
ind++;
}
c2_coord_array_insert(l, ind, &inX, 1);
}
int
c2_polygon_isempty(
c2_polygon_p pl)
{
return !pl->pt.count || c2_rect_isempty(&pl->bounds);
}
c2_coord_t
c2_polygon_get_heigth(
c2_polygon_p pl)
{
return c2_rect_height(&pl->bounds);
}
void
c2_polygon_clip(
c2_polygon_p pl,
c2_rect_p clip,
c2_polygon_p outPoly )
{
int sect = c2_rect_intersect_rect(&pl->bounds, clip);
int contains = c2_rect_contains_rect(&pl->bounds, clip);
if (!sect && !contains)
return; // don't even bother
long pMax = pl->pt.count;
c2_pt_t * last = pl->pt.e;
int lastIn = c2_rect_contains_pt(clip, last);
c2_pt_t * current = last+1;
int currentIn = c2_rect_contains_pt(clip, current);
//char outPart = clip.GetOutCode(*last);
uint8_t edgesStack[40];
short edgesClock[40];
long edgesCount = 0;
char edgesIndexes[13] = // Bit valued to quadrant index
{ 0, 1, 3, 2, 5, 0, 4, 0, 7, 8, 0, 0, 6 };
// if we start outside, remember our quadrant
if (!lastIn)
edgesStack[edgesCount++] = edgesIndexes[c2_rect_get_out_code(clip, last)];
if (lastIn)
c2_polyline_add_pt(outPoly, last);
for (long pIndex = 2; pIndex <= pMax; pIndex++) {
if (lastIn && currentIn) { // both points are IN
c2_polyline_add_pt(outPoly, current);
} else if (lastIn && !currentIn) { // line goes OUT
c2_segment_t src = { .a = *last, .b = *current };
c2_segment_t dst;
c2_rect_clip_segment(clip, &src, &dst, NULL);
c2_polyline_add_pt(outPoly, &dst.b);
edgesStack[edgesCount++] = edgesIndexes[c2_rect_is_on_edge(clip, &dst.b)];
} else if (!lastIn && currentIn) { // line goes IN
// flush corner stack
if (edgesCount > 1) {
for (int c = 0; c < edgesCount; c++) {
c2_pt_t p;
switch (edgesStack[c]) {
case 2: // topleft
p = clip->tl;
c2_polyline_add_pt(outPoly, &p);
break;
case 4: // topright
p.v[X] = clip->br.v[X]; p.v[Y] = clip->tl.v[Y];
c2_polyline_add_pt(outPoly, &p);
break;
case 6: // botright
p = clip->br;
c2_polyline_add_pt(outPoly, &p);
break;
case 8: // botleft
p.v[X] = clip->tl.v[X]; p.v[Y] = clip->br.v[Y];
c2_polyline_add_pt(outPoly, &p);
break;
}
}
}
edgesCount = 0;
c2_segment_t src = { .a = *last, .b = *current };
c2_segment_t dst;
c2_rect_clip_segment(clip, &src, &dst, NULL);
c2_polyline_add_pt(outPoly, &dst.a);
c2_polyline_add_pt(outPoly, &dst.b);
} else { // line outside
c2_segment_t src = { .a = *last, .b = *current };
c2_segment_t dst;
if (c2_rect_clip_segment(clip, &src, &dst, NULL)) { // both point are out, but crosses the rectangle
// flush corner stack
if (edgesCount > 1) {
for (int c = 0; c < edgesCount; c++) {
c2_pt_t p;
switch (edgesStack[c]) {
case 2: // topleft
p = clip->tl;
c2_polyline_add_pt(outPoly, &p);
break;
case 4: // topright
p.v[X] = clip->br.v[X]; p.v[Y] = clip->tl.v[Y];
c2_polyline_add_pt(outPoly, &p);
break;
case 6: // botright
p = clip->br;
c2_polyline_add_pt(outPoly, &p);
break;
case 8: // botleft
p.v[X] = clip->tl.v[X]; p.v[Y] = clip->br.v[Y];
c2_polyline_add_pt(outPoly, &p);
break;
}
}
}
edgesCount = 0;
c2_polyline_add_pt(outPoly, &dst.a);
c2_polyline_add_pt(outPoly, &dst.b);
edgesStack[edgesCount++] = edgesIndexes[c2_rect_is_on_edge(clip, &dst.b)];
} else { // setup edge stack properly
unsigned char clockTable[9] =
{ 0, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xc1, 0x83, 0x07 };
uint8_t o = edgesIndexes[c2_rect_get_out_code(clip, current)];
if (o != edgesStack[edgesCount - 1]) {
short clock = clockTable[edgesStack[edgesCount - 1]] & (1 << (o-1)) ? 1 : -1;
do {
if (edgesCount == 1)
edgesClock[edgesCount -1] = clock;
if (clock == edgesClock[edgesCount -1]) {
int c = edgesStack[edgesCount - 1];
do {
c += clock;
if (c == 0) c = 8;
else if (c == 9) c = 1;
edgesClock[edgesCount] = clock;
edgesStack[edgesCount++] = c;
#ifdef TRACELIB
{
static long ecount = 0;
if (edgesCount > ecount) {
ecount = edgesCount;
printf("Max edgecount = %d\n", ecount);
}
}
if (edgesCount == 40) {
DebugStr("\p HELP!");
}
#endif
} while (c != o);
} else { // change direction, remove points
while (edgesCount > 1 && edgesStack[edgesCount - 1] != o)
edgesCount--;
}
} while (o != edgesStack[edgesCount -1]);
}
}
}
last++;
current++;
lastIn = c2_rect_contains_pt(clip, last);
currentIn = c2_rect_contains_pt(clip, current);
}
// flush corner stack
if (edgesCount > 1) {
for (int c = 0; c < edgesCount; c++) {
c2_pt_t p;
switch (edgesStack[c]) {
case 2: // topleft
p = clip->tl;
c2_polyline_add_pt(outPoly, &p);
break;
case 4: // topright
p.v[X] = clip->br.v[X]; p.v[Y] = clip->tl.v[Y];
c2_polyline_add_pt(outPoly, &p);
break;
case 6: // botright
p = clip->br;
c2_polyline_add_pt(outPoly, &p);
break;
case 8: // botleft
p.v[X] = clip->tl.v[X]; p.v[Y] = clip->br.v[Y];
c2_polyline_add_pt(outPoly, &p);
break;
}
}
}
edgesCount = 0;
}
#define exchange(t, p1, p2) { t e=p1;p1=p2;p2=e; }
void
c2_polygon_scanline(
c2_polygon_p pl,
c2_scanline_array_p ioList,
c2_coord_t ymin )
{
c2_coord_t ysize = ioList->height;//TODO check if we meant the polygon height?
if (ysize <= 0 || c2_rect_width(&pl->bounds) <= 0)
return;
c2_pt_t *first = pl->pt.e;
c2_pt_t *sp1 = first;
c2_pt_t *sp2 = sp1+1;
long numPoints = pl->pt.count;
//
// Compute list of intersections between the vertex and the horizontal
// lines. The lists are automaticaly sorted
//
do {
if (numPoints == 1) sp2 = first;
c2_pt_t *p1 = sp1;
c2_pt_t *p2 = sp2;
if (p2->v[Y] < p1->v[Y])
exchange(c2_pt_t *, p1, p2);
c2_coord_t y1 = p1->v[Y];
c2_coord_t y2 = p2->v[Y];
c2_coord_t idy = y2 - y1;
if ((y1 < ymin && y2 < ymin) ||
((y1-ymin > ioList->height) && (y2-ymin > ioList->height)))
continue;
if (idy) {
double x, dx;
x = p1->v[X];
dx = (p2->v[X] - p1->v[X]) / (double)idy;
do {
c2_coord_t ix = x;
c2_scanline_array_add_coord(ioList, y1-ymin, x - ix > 0.5 ? ix+1 : ix);
x += dx;
y1++;
} while (y1 < y2);
} else {
c2_scanline_array_add_coord(ioList, y1-ymin, p1->v[X]);
c2_scanline_array_add_coord(ioList, y1-ymin, p2->v[X]);
}
sp1++;
sp2++;
} while(numPoints-- > 1);
}
#if 0
void
c2_polygon_t::Paint()
{
c2_scanline_list_t scan(Height()+1);
AddIntersections(scan, bounds.tl.v[Y]);
for (int i = 0; i < Height(); i++) {
SInt32 num = scan.Get(i).GetCount();
if (num > 1) {
c2_coord_t * cur = scan.Get(i);
for (SInt32 ind = 1; ind <= num - 1; ind += 2, cur += 2) {
if (cur[0] < cur[1]) {
MoveTo(cur[0], bounds.tl.v[Y]+i);
LineTo(cur[1], bounds.tl.v[Y]+i);
}
}
}
}
}
#endif

148
libmui/mui/c2_geometry.h Normal file
View File

@ -0,0 +1,148 @@
/*
* c2_geometry.h
*
* C2DGeometry Implementation
* Created: Monday, March 30, 1998 11:51:47
* Converted back to C99 May 2013
*
* Copyright (C) 1998-2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __C2_GEOMETRY_H__
#define __C2_GEOMETRY_H__
#include <stdint.h>
#define PMIN(x1,x2) ((x1 < x2) ? x1 : x2)
#define PMAX(x1,x2) ((x1 > x2) ? x1 : x2)
typedef int32_t c2_coord_t;
enum {
X = 0, Y,
// L = 0, T, R, B,
};
//
// a basic x,y point
//
typedef union c2_pt_t {
struct {
c2_coord_t x, y;
};
c2_coord_t v[2];
} c2_pt_t;//, *c2_pt_p;
#define c2_pt_p union c2_pt_t*
#define C2_PT(_a, _b) (c2_pt_t){ .x = (_a), .y = (_b) }
#define C2_PT_ZERO C2_PT(0,0)
//
// a basic segment, a pair of points
//
typedef struct c2_segment_t {
c2_pt_t a, b;
} c2_segment_t;//, *c2_segment_p;
#define c2_segment_p struct c2_segment_t*
#define C2_SEGMENT_ZERO { C2_PT_ZERO, C2_PT_ZERO }
//
// a rectangle defined by two points
//
typedef union c2_rect_t {
struct {
c2_pt_t tl, br;
};
c2_coord_t v[4];
struct {
c2_coord_t l,t,r,b;
};
} c2_rect_t;//, *c2_rect_p;
#define c2_rect_p union c2_rect_t*
#define C2_RECT(_l,_t,_r,_b) (c2_rect_t){ .l = _l, .t = _t, .r = _r, .b = _b }
#define C2_RECT_WH(_l,_t,_w,_h) (c2_rect_t){ \
.l = _l, .t = _t, .r = (_l)+(_w), .b = (_t)+(_h) }
/* Return the squared surface of the rectangle, for area comparison sake */
#define c2_rect_surface_squared(_r) \
((c2_rect_width(_r)*c2_rect_width(_r))+\
(c2_rect_height(_r)*c2_rect_height(_r)))
#define C2_RECT_ZERO C2_RECT(0,0,0,0)
int
c2_rect_contains_rect(
const c2_rect_p r1,
const c2_rect_p r2 );
int
c2_rect_clip_segment(
const c2_rect_p r,
c2_segment_p s,
c2_segment_p o,
char * outEdges );
int
c2_rect_clip_rect(
const c2_rect_p r,
const c2_rect_p s,
c2_rect_p o );
int
c2_rect_intersect_rect(
const c2_rect_p s,
const c2_rect_p r );
enum {
out_Left = (1 << 0),
out_Top = (1 << 1),
out_Right = (1 << 2),
out_Bottom = (1 << 3),
};
enum {
corner_TopLeft = out_Left,
corner_TopRight = out_Top,
corner_BottomRight = out_Right,
corner_BottomLeft = out_Bottom,
};
uint8_t
c2_rect_get_out_code(
const c2_rect_p r,
const c2_pt_p p );
void
c2_rect_clip_pt(
const c2_rect_p r,
c2_pt_p p );
uint8_t
c2_rect_get_next_edge(
uint8_t inEdge,
int inCW );
uint8_t
c2_rect_is_on_edge(
const c2_rect_p r,
const c2_pt_p p );
int
c2_rect_get_edge(
const c2_rect_p r,
uint8_t inEdge,
c2_segment_p outEdge );
int
c2_rect_get_corner(
const c2_rect_p r,
uint8_t inCorner,
c2_pt_p outCorner,
int inCW );
const char *
c2_rect_as_str(
const c2_rect_p r );
#ifndef C2_GEOMETRY_INLINE_H_
#ifndef C2_DECL
#include "c2_geometry_inline.h"
#endif
#endif
#endif

View File

@ -0,0 +1,244 @@
/*
* c2_geometry_inline.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef C2_GEOMETRY_INLINE_H_
#define C2_GEOMETRY_INLINE_H_
#include "c2_geometry.h"
#ifndef C2_DECL
#define C2_DECL static inline __attribute__((unused))
#endif
C2_DECL void
c2_pt_offset(
c2_pt_p p,
c2_coord_t inX,
c2_coord_t inY)
{
p->x += inX;
p->y += inY;
}
C2_DECL int
c2_pt_equal(
const c2_pt_p p1,
const c2_pt_p p2)
{
return p1->v[0] == p2->v[0] && p1->v[1] == p2->v[1];
}
C2_DECL void
c2_pt_scale(
c2_pt_p p,
float inFactor )
{// TODO fix rounding
p->v[X] *= inFactor;
p->v[Y] *= inFactor;
}
C2_DECL c2_segment_p
c2_segment_set(
c2_segment_p s,
c2_coord_t x1,
c2_coord_t y1,
c2_coord_t x2,
c2_coord_t y2 )
{
s->a.v[X] = PMIN(x1, x2);
s->a.v[Y] = PMIN(y1, y2);
s->b.v[X] = PMAX(x1, x2);
s->b.v[Y] = PMAX(y1, y2);
return s;
}
C2_DECL int
c2_segment_equal(
const c2_segment_p s1,
const c2_segment_p s2)
{
return (c2_pt_equal(&s1->a, &s2->a) && c2_pt_equal(&s1->b, &s2->b)) ||
(c2_pt_equal(&s1->a, &s2->b) && c2_pt_equal(&s1->b, &s2->a));
}
C2_DECL int
c2_segment_isempty(
const c2_segment_p s)
{
return c2_pt_equal(&s->a, &s->b);
}
C2_DECL void
c2_segment_offset(
c2_segment_p s,
c2_coord_t inX,
c2_coord_t inY )
{
c2_pt_offset(&s->a, inX, inY);
c2_pt_offset(&s->b, inX, inY);
}
C2_DECL void
c2_segment_scale(
c2_segment_p s,
double inFactor )
{
c2_pt_scale(&s->a, inFactor);
c2_pt_scale(&s->b, inFactor);
}
C2_DECL c2_rect_p
c2_rect_set(
c2_rect_p r,
c2_coord_t x1,
c2_coord_t y1,
c2_coord_t x2,
c2_coord_t y2 )
{
r->tl.v[X] = PMIN(x1, x2);
r->tl.v[Y] = PMIN(y1, y2);
r->br.v[X] = PMAX(x1, x2);
r->br.v[Y] = PMAX(y1, y2);
return r;
}
C2_DECL int
c2_rect_isempty(
const c2_rect_p r)
{
return c2_pt_equal(&r->tl, &r->br) ||
r->tl.x >= r->br.x || r->tl.y >= r->br.y;
}
C2_DECL int
c2_rect_equal(
const c2_rect_p r,
const c2_rect_p o)
{
return r->v[0] == o->v[0] && r->v[1] == o->v[1] &&
r->v[2] == o->v[2] && r->v[3] == o->v[3];
/* This got miscompiled under gcc 4.8 and 4.9. The above is more straightforward.
* return c2_pt_equal(&r->tl, &o->tl) && c2_pt_equal(&r->br, &o->br);
*/
}
C2_DECL c2_coord_t
c2_rect_height(
const c2_rect_p r)
{
return r->br.v[Y] - r->tl.v[Y];
}
C2_DECL c2_coord_t
c2_rect_width(
const c2_rect_p r)
{
return r->br.v[X] - r->tl.v[X];
}
C2_DECL c2_pt_t
c2_rect_size(
const c2_rect_p r)
{
return (c2_pt_t){ .x = c2_rect_width(r), .y = c2_rect_height(r) };
}
C2_DECL void
c2_rect_offset(
c2_rect_p r,
c2_coord_t inX,
c2_coord_t inY)
{
c2_pt_offset(&r->tl, inX, inY);
c2_pt_offset(&r->br, inX, inY);
}
C2_DECL void
c2_rect_inset(
c2_rect_p r,
c2_coord_t inX,
c2_coord_t inY)
{
c2_pt_offset(&r->tl, inX, inY);
c2_pt_offset(&r->br, -inX, -inY);
}
C2_DECL void
c2_rect_scale(
c2_rect_p r,
double inFactor )
{
c2_pt_scale(&r->tl, inFactor);
c2_pt_scale(&r->br, inFactor);
}
C2_DECL int
c2_rect_contains_pt(
const c2_rect_p r,
c2_pt_p p )
{
return (p->v[X] >= r->tl.v[X] && p->v[X] <= r->br.v[X]) &&
(p->v[Y] >= r->tl.v[Y] && p->v[Y] <= r->br.v[Y]);
}
/* Returns 'r' as the union of 'r' and 'u' (enclosing rectangle) */
C2_DECL c2_rect_p
c2_rect_union(
c2_rect_p r,
c2_rect_p u )
{
if (!r || !u) return r;
if (c2_rect_isempty(r)) {
*r = *u;
return r;
}
r->l = PMIN(r->l, u->l);
r->t = PMIN(r->t, u->t);
r->r = PMAX(r->r, u->r);
r->b = PMAX(r->b, u->b);
return r;
}
static c2_rect_t
c2_rect_left_of(
c2_rect_t * r,
int mark,
int margin)
{
c2_rect_offset(r, -r->l + mark - c2_rect_width(r) - margin, 0);
return *r;
}
static c2_rect_t
c2_rect_right_of(
c2_rect_t * r,
int mark,
int margin)
{
c2_rect_offset(r, -r->l + mark + margin, 0);
return *r;
}
static c2_rect_t
c2_rect_top_of(
c2_rect_t * r,
int mark,
int margin)
{
c2_rect_offset(r, 0, -r->t + mark - c2_rect_height(r) - margin);
return *r;
}
static c2_rect_t
c2_rect_bottom_of(
c2_rect_t * r,
int mark,
int margin)
{
c2_rect_offset(r, 0, -r->t + mark + margin);
return *r;
}
#endif /* C2_GEOMETRY_INLINE_H_ */

View File

@ -0,0 +1,119 @@
/*
* c2_geometry_poly.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef C2_GEOMETRY_POLY_H_
#define C2_GEOMETRY_POLY_H_
#include "c2_arrays.h"
typedef struct c2_polyline_t {
c2_pt_array_t pt;
c2_rect_t bounds;
} c2_polyline_t, *c2_polyline_p;
DECLARE_C_ARRAY(c2_polyline_p, c2_polyline_array, 16,
c2_polyline_p current; );
DECLARE_C_ARRAY(c2_coord_array_t, c2_scanline_array, 16,
c2_coord_t height; );
IMPLEMENT_C_ARRAY(c2_scanline_array);
IMPLEMENT_C_ARRAY(c2_polyline_array);
void
c2_polyline_clear(
c2_polyline_p pl);
int
c2_polyline_get_segment(
c2_polyline_p pl,
long ind,
c2_segment_p o );
void
c2_polyline_offset(
c2_polyline_p pl,
c2_coord_t inX,
c2_coord_t inY );
void
c2_polyline_scale(
c2_polyline_p pl,
double inFactor,
c2_rect_p inSkip /* = NULL */ );
void
c2_polyline_add_pt(
c2_polyline_p pl,
c2_pt_p p );
int
c2_polyline_array_clip(
c2_polyline_array_p pa,
c2_rect_p clip,
c2_polyline_array_p outPoly );
void
c2_polyline_array_break(
c2_polyline_array_p pa);
void
c2_polyline_array_add_pt(
c2_polyline_array_p pa,
c2_pt_p p );
void
c2_polyline_array_scale(
c2_polyline_array_p pa,
float inFactor,
c2_rect_p inSkip /* = NULL */ );
void
c2_polyline_array_offset(
c2_polyline_array_p pa,
c2_coord_t inX,
c2_coord_t inY );
void
c2_scanline_array_proper_alloc(
c2_scanline_array_p a,
c2_coord_t height);
void
c2_scanline_array_proper_clear(
c2_scanline_array_p a);
void
c2_scanline_array_add_coord(
c2_scanline_array_p a,
int inY,
c2_coord_t inX );
typedef c2_polyline_p c2_polygon_p;
int
c2_polygon_isempty(
c2_polygon_p pl);
c2_coord_t
c2_polygon_get_heigth(
c2_polygon_p pl);
void
c2_polygon_clip(
c2_polygon_p pl,
c2_rect_p clip,
c2_polygon_p outPoly );
void
c2_polygon_scanline(
c2_polygon_p pl,
c2_scanline_array_p ioList,
c2_coord_t ymin );
#endif /* C2_GEOMETRY_POLY_H_ */

161
libmui/mui/c_array.h Normal file
View File

@ -0,0 +1,161 @@
/*
* c_array.h
*
* Copyright 2012 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
* vi: ts=4
*/
#ifndef __C_ARRAY_H___
#define __C_ARRAY_H___
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifndef C_ARRAY_INLINE
#define C_ARRAY_INLINE inline
#endif
#ifndef C_ARRAY_SIZE_TYPE
#define C_ARRAY_SIZE_TYPE uint32_t
#endif
/* New compilers don't like unused static functions. However,
* we do like 'static inlines' for these small accessors,
* so we mark them as 'unused'. It stops it complaining */
#ifdef __GNUC__
#define C_ARRAY_DECL static __attribute__ ((unused))
#else
#define C_ARRAY_DECL static
#endif
#define DECLARE_C_ARRAY(__type, __name, __page, __args...) \
enum { __name##_page_size = __page }; \
typedef __type __name##_element_t; \
typedef C_ARRAY_SIZE_TYPE __name##_count_t; \
typedef struct __name##_t {\
volatile __name##_count_t count;\
volatile __name##_count_t size;\
__name##_element_t * e;\
__args \
} __name##_t, *__name##_p;
#define C_ARRAY_NULL { 0, 0, NULL }
#ifndef NO_DECL
#define IMPLEMENT_C_ARRAY(__name) \
C_ARRAY_DECL C_ARRAY_INLINE \
void __name##_init(\
__name##_p a) \
{\
static const __name##_t zero = {}; \
if (!a) return;\
*a = zero;\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
void __name##_free(\
__name##_p a) \
{\
if (!a) return;\
if (a->e) free(a->e);\
a->count = a->size = 0;\
a->e = NULL;\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
void __name##_clear(\
__name##_p a) \
{\
if (!a) return;\
a->count = 0;\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
void __name##_realloc(\
__name##_p a, __name##_count_t size) \
{\
if (!a || a->size == size) return; \
if (size == 0) { if (a->e) free(a->e); a->e = NULL; } \
else a->e = (__name##_element_t*)realloc(a->e, \
size * sizeof(__name##_element_t));\
a->size = size; \
}\
C_ARRAY_DECL C_ARRAY_INLINE \
void __name##_trim(\
__name##_p a) \
{\
if (!a) return;\
__name##_count_t n = a->count + __name##_page_size;\
n -= (n % __name##_page_size);\
if (n != a->size)\
__name##_realloc(a, n);\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
__name##_element_t * __name##_get_ptr(\
__name##_p a, __name##_count_t index) \
{\
if (!a) return NULL;\
if (index > a->count) index = a->count;\
return index < a->count ? a->e + index : NULL;\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
__name##_count_t __name##_add(\
__name##_p a, __name##_element_t e) \
{\
if (!a) return 0;\
if (a->count + 1 >= a->size)\
__name##_realloc(a, a->size + __name##_page_size);\
a->e[a->count++] = e;\
return a->count;\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
__name##_count_t __name##_push(\
__name##_p a, __name##_element_t e) \
{\
return __name##_add(a, e);\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
int __name##_pop(\
__name##_p a, __name##_element_t *e) \
{\
if (a->count) { *e = a->e[--a->count]; return 1; } \
return 0;\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
__name##_count_t __name##_insert(\
__name##_p a, __name##_count_t index, \
__name##_element_t * e, __name##_count_t count) \
{\
if (!a) return 0;\
if (index > a->count) index = a->count;\
if (a->count + count >= a->size) \
__name##_realloc(a, (((a->count + count) / __name##_page_size)+1) * __name##_page_size);\
if (index < a->count)\
memmove(&a->e[index + count], &a->e[index], \
(a->count - index) * sizeof(__name##_element_t));\
memmove(&a->e[index], e, count * sizeof(__name##_element_t));\
a->count += count;\
return a->count;\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
__name##_count_t __name##_delete(\
__name##_p a, __name##_count_t index, __name##_count_t count) \
{\
if (!a) return 0;\
if (index > a->count) index = a->count;\
if (index + count > a->count) \
count = a->count - index;\
if (count && a->count - index) { \
memmove(&a->e[index], &a->e[index + count], \
(a->count - index - count) * sizeof(__name##_element_t));\
}\
a->count -= count;\
return a->count;\
}
#else /* NO_DECL */
#define IMPLEMENT_C_ARRAY(__name)
#endif
#endif /* __C_ARRAY_H___ */

2512
libmui/mui/cg.c Normal file

File diff suppressed because it is too large Load Diff

314
libmui/mui/cg.h Normal file
View File

@ -0,0 +1,314 @@
#ifndef __CG_H__
#define __CG_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "xft.h"
struct cg_point_t {
double x;
double y;
};
struct cg_rect_t {
double x;
double y;
double w;
double h;
};
struct cg_matrix_t {
double a; double b;
double c; double d;
double tx; double ty;
};
struct cg_color_t {
double r;
double g;
double b;
double a;
};
struct cg_gradient_stop_t {
double offset;
struct cg_color_t color;
};
enum cg_path_element_t {
CG_PATH_ELEMENT_MOVE_TO = 0,
CG_PATH_ELEMENT_LINE_TO = 1,
CG_PATH_ELEMENT_CURVE_TO = 2,
CG_PATH_ELEMENT_CLOSE = 3,
};
enum cg_spread_method_t {
CG_SPREAD_METHOD_PAD = 0,
CG_SPREAD_METHOD_REFLECT = 1,
CG_SPREAD_METHOD_REPEAT = 2,
};
enum cg_gradient_type_t {
CG_GRADIENT_TYPE_LINEAR = 0,
CG_GRADIENT_TYPE_RADIAL = 1,
};
enum cg_texture_type_t {
CG_TEXTURE_TYPE_PLAIN = 0,
CG_TEXTURE_TYPE_TILED = 1,
};
enum cg_line_cap_t {
CG_LINE_CAP_BUTT = 0,
CG_LINE_CAP_ROUND = 1,
CG_LINE_CAP_SQUARE = 2,
};
enum cg_line_join_t {
CG_LINE_JOIN_MITER = 0,
CG_LINE_JOIN_ROUND = 1,
CG_LINE_JOIN_BEVEL = 2,
};
enum cg_fill_rule_t {
CG_FILL_RULE_NON_ZERO = 0,
CG_FILL_RULE_EVEN_ODD = 1,
};
enum cg_paint_type_t {
CG_PAINT_TYPE_COLOR = 0,
CG_PAINT_TYPE_GRADIENT = 1,
CG_PAINT_TYPE_TEXTURE = 2,
};
enum cg_operator_t {
CG_OPERATOR_SRC = 0, /* r = s * ca + d * cia */
CG_OPERATOR_SRC_OVER = 1, /* r = (s + d * sia) * ca + d * cia */
CG_OPERATOR_DST_IN = 2, /* r = d * sa * ca + d * cia */
CG_OPERATOR_DST_OUT = 3, /* r = d * sia * ca + d * cia */
};
struct cg_surface_t {
int ref;
int width;
int height;
int stride;
int owndata;
void * pixels;
};
struct cg_path_t {
int contours;
struct cg_point_t start;
struct {
enum cg_path_element_t * data;
int size;
int capacity;
} elements;
struct {
struct cg_point_t * data;
int size;
int capacity;
} points;
};
struct cg_gradient_t {
enum cg_gradient_type_t type;
enum cg_spread_method_t spread;
struct cg_matrix_t matrix;
double values[6];
double opacity;
struct {
struct cg_gradient_stop_t * data;
int size;
int capacity;
} stops;
};
struct cg_texture_t {
enum cg_texture_type_t type;
struct cg_surface_t * surface;
struct cg_matrix_t matrix;
double opacity;
};
struct cg_paint_t {
enum cg_paint_type_t type;
struct cg_color_t color;
struct cg_gradient_t gradient;
struct cg_texture_t texture;
};
struct cg_span_t {
int x;
int len;
int y;
unsigned char coverage;
};
struct cg_rle_t {
struct {
struct cg_span_t * data;
int size;
int capacity;
} spans;
int x;
int y;
int w;
int h;
};
struct cg_dash_t {
double offset;
double * data;
int size;
};
struct cg_stroke_data_t {
double width;
double miterlimit;
enum cg_line_cap_t cap;
enum cg_line_join_t join;
struct cg_dash_t * dash;
};
struct cg_state_t {
struct cg_rle_t * clippath;
struct cg_paint_t paint;
struct cg_matrix_t matrix;
enum cg_fill_rule_t winding;
struct cg_stroke_data_t stroke;
enum cg_operator_t op;
double opacity;
struct cg_state_t * next;
};
struct cg_ctx_t {
struct cg_surface_t * surface;
struct cg_state_t * state;
struct cg_path_t * path;
struct cg_rle_t * rle;
struct cg_rle_t * clippath;
struct cg_rect_t clip;
void * outline_data;
size_t outline_size;
};
#ifndef CG_MIN
#define CG_MIN(a, b) ({typeof(a) _amin = (a); typeof(b) _bmin = (b); (void)(&_amin == &_bmin); _amin < _bmin ? _amin : _bmin;})
#endif
#ifndef CG_MAX
#define CG_MAX(a, b) ({typeof(a) _amax = (a); typeof(b) _bmax = (b); (void)(&_amax == &_bmax); _amax > _bmax ? _amax : _bmax;})
#endif
#ifndef CG_CLAMP
#define CG_CLAMP(v, a, b) CG_MIN(CG_MAX(a, v), b)
#endif
#ifndef CG_ALPHA
#define CG_ALPHA(c) ((c) >> 24)
#endif
#ifndef CG_DIV255
#define CG_DIV255(x) (((x) + ((x) >> 8) + 0x80) >> 8)
#endif
#ifndef CG_BYTE_MUL
#define CG_BYTE_MUL(x, a) ((((((x) >> 8) & 0x00ff00ff) * (a)) & 0xff00ff00) + (((((x) & 0x00ff00ff) * (a)) >> 8) & 0x00ff00ff))
#endif
void cg_memfill32(uint32_t * dst, uint32_t val, int len);
void cg_comp_solid_source(uint32_t * dst, int len, uint32_t color, uint32_t alpha);
void cg_comp_solid_source_over(uint32_t * dst, int len, uint32_t color, uint32_t alpha);
void cg_comp_solid_destination_in(uint32_t * dst, int len, uint32_t color, uint32_t alpha);
void cg_comp_solid_destination_out(uint32_t * dst, int len, uint32_t color, uint32_t alpha);
void cg_comp_source(uint32_t * dst, int len, uint32_t * src, uint32_t alpha);
void cg_comp_source_over(uint32_t * dst, int len, uint32_t * src, uint32_t alpha);
void cg_comp_destination_in(uint32_t * dst, int len, uint32_t * src, uint32_t alpha);
void cg_comp_destination_out(uint32_t * dst, int len, uint32_t * src, uint32_t alpha);
void cg_matrix_init(struct cg_matrix_t * m, double a, double b, double c, double d, double tx, double ty);
void cg_matrix_init_identity(struct cg_matrix_t * m);
void cg_matrix_init_translate(struct cg_matrix_t * m, double tx, double ty);
void cg_matrix_init_scale(struct cg_matrix_t * m, double sx, double sy);
void cg_matrix_init_rotate(struct cg_matrix_t * m, double r);
void cg_matrix_translate(struct cg_matrix_t * m, double tx, double ty);
void cg_matrix_scale(struct cg_matrix_t * m, double sx, double sy);
void cg_matrix_rotate(struct cg_matrix_t * m, double r);
void cg_matrix_multiply(struct cg_matrix_t * m, struct cg_matrix_t * m1, struct cg_matrix_t * m2);
void cg_matrix_invert(struct cg_matrix_t * m);
void cg_matrix_map_point(struct cg_matrix_t * m, struct cg_point_t * p1, struct cg_point_t * p2);
struct cg_surface_t * cg_surface_create(int width, int height);
struct cg_surface_t * cg_surface_create_for_data(int width, int height, void * pixels);
void cg_surface_destroy(struct cg_surface_t * surface);
struct cg_surface_t * cg_surface_reference(struct cg_surface_t * surface);
void cg_gradient_set_values_linear(struct cg_gradient_t * gradient, double x1, double y1, double x2, double y2);
void cg_gradient_set_values_radial(struct cg_gradient_t * gradient, double cx, double cy, double cr, double fx, double fy, double fr);
void cg_gradient_set_spread(struct cg_gradient_t * gradient, enum cg_spread_method_t spread);
void cg_gradient_set_matrix(struct cg_gradient_t * gradient, struct cg_matrix_t * m);
void cg_gradient_set_opacity(struct cg_gradient_t * gradient, double opacity);
void cg_gradient_add_stop_rgb(struct cg_gradient_t * gradient, double offset, double r, double g, double b);
void cg_gradient_add_stop_rgba(struct cg_gradient_t * gradient, double offset, double r, double g, double b, double a);
void cg_gradient_add_stop_color(struct cg_gradient_t * gradient, double offset, struct cg_color_t * color);
void cg_gradient_add_stop(struct cg_gradient_t * gradient, struct cg_gradient_stop_t * stop);
void cg_gradient_clear_stops(struct cg_gradient_t * gradient);
void cg_texture_set_type(struct cg_texture_t * texture, enum cg_texture_type_t type);
void cg_texture_set_matrix(struct cg_texture_t * texture, struct cg_matrix_t * m);
void cg_texture_set_surface(struct cg_texture_t * texture, struct cg_surface_t * surface);
void cg_texture_set_opacity(struct cg_texture_t * texture, double opacity);
struct cg_ctx_t * cg_create(struct cg_surface_t * surface);
void cg_destroy(struct cg_ctx_t * ctx);
void cg_save(struct cg_ctx_t * ctx);
void cg_restore(struct cg_ctx_t * ctx);
struct cg_color_t * cg_set_source_rgb(struct cg_ctx_t * ctx, double r, double g, double b);
struct cg_color_t * cg_set_source_rgba(struct cg_ctx_t * ctx, double r, double g, double b, double a);
struct cg_color_t * cg_set_source_color(struct cg_ctx_t * ctx, struct cg_color_t * color);
struct cg_gradient_t * cg_set_source_linear_gradient(struct cg_ctx_t * ctx, double x1, double y1, double x2, double y2);
struct cg_gradient_t * cg_set_source_radial_gradient(struct cg_ctx_t * ctx, double cx, double cy, double cr, double fx, double fy, double fr);
struct cg_texture_t * cg_set_source_surface(struct cg_ctx_t * ctx, struct cg_surface_t * surface, double x, double y);
void cg_set_operator(struct cg_ctx_t * ctx, enum cg_operator_t op);
void cg_set_opacity(struct cg_ctx_t * ctx, double opacity);
void cg_set_fill_rule(struct cg_ctx_t * ctx, enum cg_fill_rule_t winding);
void cg_set_line_width(struct cg_ctx_t * ctx, double width);
void cg_set_line_cap(struct cg_ctx_t * ctx, enum cg_line_cap_t cap);
void cg_set_line_join(struct cg_ctx_t * ctx, enum cg_line_join_t join);
void cg_set_miter_limit(struct cg_ctx_t * ctx, double limit);
void cg_set_dash(struct cg_ctx_t * ctx, double * dashes, int ndash, double offset);
void cg_translate(struct cg_ctx_t * ctx, double tx, double ty);
void cg_scale(struct cg_ctx_t * ctx, double sx, double sy);
void cg_rotate(struct cg_ctx_t * ctx, double r);
void cg_transform(struct cg_ctx_t * ctx, struct cg_matrix_t * m);
void cg_set_matrix(struct cg_ctx_t * ctx, struct cg_matrix_t * m);
void cg_identity_matrix(struct cg_ctx_t * ctx);
void cg_move_to(struct cg_ctx_t * ctx, double x, double y);
void cg_line_to(struct cg_ctx_t * ctx, double x, double y);
void cg_curve_to(struct cg_ctx_t * ctx, double x1, double y1, double x2, double y2, double x3, double y3);
void cg_quad_to(struct cg_ctx_t * ctx, double x1, double y1, double x2, double y2);
void cg_rel_move_to(struct cg_ctx_t * ctx, double dx, double dy);
void cg_rel_line_to(struct cg_ctx_t * ctx, double dx, double dy);
void cg_rel_curve_to(struct cg_ctx_t * ctx, double dx1, double dy1, double dx2, double dy2, double dx3, double dy3);
void cg_rel_quad_to(struct cg_ctx_t * ctx, double dx1, double dy1, double dx2, double dy2);
void cg_rectangle(struct cg_ctx_t * ctx, double x, double y, double w, double h);
void cg_round_rectangle(struct cg_ctx_t * ctx, double x, double y, double w, double h, double rx, double ry);
void cg_ellipse(struct cg_ctx_t * ctx, double cx, double cy, double rx, double ry);
void cg_circle(struct cg_ctx_t * ctx, double cx, double cy, double r);
void cg_arc(struct cg_ctx_t * ctx, double cx, double cy, double r, double a0, double a1);
void cg_arc_negative(struct cg_ctx_t * ctx, double cx, double cy, double r, double a0, double a1);
void cg_new_path(struct cg_ctx_t * ctx);
void cg_close_path(struct cg_ctx_t * ctx);
void cg_reset_clip(struct cg_ctx_t * ctx);
void cg_clip(struct cg_ctx_t * ctx);
void cg_clip_preserve(struct cg_ctx_t * ctx);
void cg_fill(struct cg_ctx_t * ctx);
void cg_fill_preserve(struct cg_ctx_t * ctx);
void cg_stroke(struct cg_ctx_t * ctx);
void cg_stroke_preserve(struct cg_ctx_t * ctx);
void cg_paint(struct cg_ctx_t * ctx);
#ifdef __cplusplus
}
#endif
#endif /* __CG_H__ */

285
libmui/mui/mui.c Normal file
View File

@ -0,0 +1,285 @@
/*
* mui.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui_priv.h"
void
mui_init(
mui_t *ui)
{
//memset(ui, 0, sizeof(*ui));
ui->clear_color = MUI_COLOR(0xccccccff);
TAILQ_INIT(&ui->windows);
TAILQ_INIT(&ui->zombies);
TAILQ_INIT(&ui->fonts);
mui_font_init(ui);
pixman_region32_init(&ui->redraw);
c2_rect_t whole = C2_RECT(0, 0, ui->screen_size.x, ui->screen_size.y);
pixman_region32_reset(&ui->inval, (pixman_box32_t*)&whole);
}
void
mui_dispose(
mui_t *ui)
{
pixman_region32_fini(&ui->inval);
mui_font_dispose(ui);
mui_window_t *w;
while ((w = TAILQ_FIRST(&ui->windows))) {
mui_window_dispose(w);
}
}
void
mui_draw(
mui_t *ui,
mui_drawable_t *dr,
uint16_t all)
{
if (!(all || pixman_region32_not_empty(&ui->inval)))
return;
if (all) {
// printf("%s: all\n", __func__);
c2_rect_t whole = C2_RECT(0, 0, dr->pix.size.x, dr->pix.size.y);
pixman_region32_reset(&ui->inval, (pixman_box32_t*)&whole);
}
mui_drawable_set_clip(dr, NULL);
/*
* Windows are drawn top to bottom, their area/rectangle is added to the
* done region, the done region (any windows that are overlaping others)
* is substracted to any other windows update region before drawing...
* once all windows are done, the 'done' region (sum of all the windows),
* is substracted from the 'desk' area and erased.
*/
pixman_region32_t done = {};
mui_window_t * win;
TAILQ_FOREACH_REVERSE(win, &ui->windows, windows, self) {
pixman_region32_intersect_rect(&win->inval, &win->inval,
win->frame.l, win->frame.t,
c2_rect_width(&win->frame), c2_rect_height(&win->frame));
mui_drawable_set_clip(dr, NULL);
if (!all)
mui_drawable_clip_push_region(dr, &win->inval);
else
mui_drawable_clip_push(dr, &win->frame);
pixman_region32_clear(&win->inval);
mui_drawable_clip_substract_region(dr, &done);
mui_window_draw(win, dr);
// printf(" %s : %s\n", win->title, c2_rect_as_str(&win->frame));
pixman_region32_union_rect(&done, &done,
win->frame.l, win->frame.t,
c2_rect_width(&win->frame), c2_rect_height(&win->frame));
}
mui_drawable_set_clip(dr, NULL);
pixman_region32_t sect = {};
c2_rect_t desk = C2_RECT(0, 0, dr->pix.size.x, dr->pix.size.y);
pixman_region32_inverse(&sect, &done, (pixman_box32_t*)&desk);
mui_drawable_clip_push_region(dr, &sect);
pixman_image_fill_boxes(
ui->clear_color.value ? PIXMAN_OP_SRC : PIXMAN_OP_CLEAR,
mui_drawable_get_pixman(dr),
&PIXMAN_COLOR(ui->clear_color), 1, (pixman_box32_t*)&desk);
pixman_region32_fini(&sect);
pixman_region32_fini(&done);
pixman_region32_union(&ui->redraw, &ui->redraw, &ui->inval);
pixman_region32_clear(&ui->inval);
if (ui->draw_debug) {
// save a png of the current screen
ui->draw_debug = 0;
printf("%s: saving debug.png\n", __func__);
// mui_drawable_save_to_png(dr, "debug.png");
}
}
bool
mui_handle_event(
mui_t *ui,
mui_event_t *ev)
{
bool res = false;
if (!ev->when)
ev->when = mui_get_time();
ui->action_active++;
switch (ev->type) {
case MUI_EVENT_KEYUP:
case MUI_EVENT_KEYDOWN: {
if (ev->modifiers & MUI_MODIFIER_EVENT_TRACE)
printf("%s modifiers %04x key %x\n", __func__,
ev->modifiers, ev->key.key);
mui_window_t *w, *safe;
TAILQ_FOREACH_REVERSE_SAFE(w, &ui->windows, windows, self, safe) {
if ((res = mui_window_handle_keyboard(w, ev))) {
if (ev->modifiers & MUI_MODIFIER_EVENT_TRACE)
printf(" window:%s handled it\n",
w->title);
break;
}
}
if (ev->modifiers & MUI_MODIFIER_EVENT_TRACE)
if (!res)
printf(" no window handled it\n");
} break;
case MUI_EVENT_BUTTONUP:
case MUI_EVENT_BUTTONDOWN:
case MUI_EVENT_WHEEL:
case MUI_EVENT_DRAG: {
if (ev->type == MUI_EVENT_BUTTONDOWN && ev->mouse.button > 1) {
printf("%s: button %d not handled\n", __func__,
ev->mouse.button);
ui->draw_debug++;
c2_rect_t whole = C2_RECT(0, 0, ui->screen_size.x, ui->screen_size.y);
pixman_region32_reset(&ui->inval, (pixman_box32_t*)&whole);
}
if (ev->modifiers & MUI_MODIFIER_EVENT_TRACE)
printf("%s %d mouse %d %3dx%3d capture:%s\n", __func__,
ev->type, ev->mouse.button,
ev->mouse.where.x, ev->mouse.where.y,
ui->event_capture ?
ui->event_capture->title : "(none)");
if (ui->event_capture) {
res = mui_window_handle_mouse(ui->event_capture, ev);
break;
} else {
mui_window_t *w, *safe;
TAILQ_FOREACH_REVERSE_SAFE(w, &ui->windows, windows, self, safe) {
if ((res = mui_window_handle_mouse(w, ev))) {
if (ev->modifiers & MUI_MODIFIER_EVENT_TRACE)
printf(" window:%s handled it\n",
w->title);
break;
}
}
}
if (ev->modifiers & MUI_MODIFIER_EVENT_TRACE)
if (!res)
printf(" no window handled it\n");
} break;
}
ui->action_active--;
return res;
}
static uint16_t
_mui_simplify_mods(
uint16_t mods)
{
uint16_t res = 0;
if (mods & MUI_MODIFIER_SHIFT)
res |= MUI_MODIFIER_RSHIFT;
if (mods & MUI_MODIFIER_CTRL)
res |= MUI_MODIFIER_RCTRL;
if (mods & MUI_MODIFIER_ALT)
res |= MUI_MODIFIER_RALT;
if (mods & MUI_MODIFIER_SUPER)
res |= MUI_MODIFIER_RSUPER;
return res;
}
bool
mui_event_match_key(
mui_event_t *ev,
mui_key_equ_t key_equ)
{
if (ev->type != MUI_EVENT_KEYUP && ev->type != MUI_EVENT_KEYDOWN)
return false;
if (toupper(ev->key.key) != toupper(key_equ.key))
return false;
if (_mui_simplify_mods(ev->modifiers) != _mui_simplify_mods(key_equ.mod))
return false;
return true;
}
uint8_t
mui_timer_register(
mui_t *ui,
mui_timer_p cb,
void *param,
uint32_t delay)
{
if (ui->timer.map == (uint64_t)-1) {
fprintf(stderr, "%s ran out of timers\n", __func__);
return -1;
}
int ti = ffsl(~ui->timer.map) - 1;
ui->timer.map |= 1 << ti;
ui->timer.timers[ti].cb = cb;
ui->timer.timers[ti].param = param;
ui->timer.timers[ti].when = mui_get_time() + delay;
return 0;
}
void
mui_timers_run(
mui_t *ui )
{
uint64_t now = mui_get_time();
uint64_t map = ui->timer.map;
while (map) {
int ti = ffsl(map) - 1;
map &= ~(1 << ti);
if (ui->timer.timers[ti].when > now)
continue;
mui_time_t r = ui->timer.timers[ti].cb(
ui, now,
ui->timer.timers[ti].param);
if (r == 0)
ui->timer.map &= ~(1 << ti);
else
ui->timer.timers[ti].when += r;
}
}
void
_mui_window_free(
mui_window_t *win);
void
mui_garbage_collect(
mui_t * ui)
{
mui_window_t *win, *safe;
TAILQ_FOREACH_SAFE(win, &ui->zombies, self, safe) {
TAILQ_REMOVE(&ui->zombies, win, self);
_mui_window_free(win);
}
}
void
mui_run(
mui_t *ui)
{
mui_timers_run(ui);
mui_garbage_collect(ui);
}
bool
mui_has_active_windows(
mui_t *ui)
{
mui_window_t *win;
TAILQ_FOREACH(win, &ui->windows, self) {
if (mui_menubar_window(win) || win->flags.hidden)
continue;
return true;
}
return false;
}

984
libmui/mui/mui.h Normal file
View File

@ -0,0 +1,984 @@
/*
* mui.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
/*
* This is the main include file for the libmui UI library, it should be
* the only one you need to include.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <pixman.h>
#include "c2_arrays.h"
#include "bsd_queue.h"
#include "stb_ttc.h"
/* Four Character Constants are used everywhere. Wish this had become a standard,
* as it is so handy -- but nope, thus the macro. Annoyingly, the little-
* endianess of them makes it a pain to do a printf() with them, this is why
* the values are reversed here.
*/
#ifndef FCC
#define FCC(_a,_b,_c,_d) (((_d)<<24)|((_c)<<16)|((_b)<<8)|(_a))
#endif
enum mui_event_e {
MUI_EVENT_KEYUP = 0,
MUI_EVENT_KEYDOWN,
MUI_EVENT_BUTTONUP,
MUI_EVENT_BUTTONDOWN,
MUI_EVENT_WHEEL,
MUI_EVENT_DRAG,
// the following ones aren't supported yet
MUI_EVENT_MOUSEENTER,
MUI_EVENT_MOUSELEAVE,
MUI_EVENT_RESIZE,
MUI_EVENT_CLOSE,
MUI_EVENT_COUNT,
};
enum mui_key_e {
MUI_KEY_ESCAPE = 0x1b,
MUI_KEY_LEFT = 0x80,
MUI_KEY_UP,
MUI_KEY_RIGHT,
MUI_KEY_DOWN,
MUI_KEY_INSERT,
MUI_KEY_HOME,
MUI_KEY_END,
MUI_KEY_PAGEUP,
MUI_KEY_PAGEDOWN,
MUI_KEY_MODIFIERS = 0x90,
MUI_KEY_LSHIFT = MUI_KEY_MODIFIERS,
MUI_KEY_RSHIFT,
MUI_KEY_LCTRL,
MUI_KEY_RCTRL,
MUI_KEY_LALT,
MUI_KEY_RALT,
MUI_KEY_RSUPER,
MUI_KEY_LSUPER,
MUI_KEY_MODIFIERS_LAST,
MUI_KEY_F1 = 0x100,
MUI_KEY_F2,
MUI_KEY_F3,
MUI_KEY_F4,
MUI_KEY_F5,
MUI_KEY_F6,
MUI_KEY_F7,
MUI_KEY_F8,
MUI_KEY_F9,
MUI_KEY_F10,
MUI_KEY_F11,
MUI_KEY_F12,
};
enum mui_modifier_e {
MUI_MODIFIER_LSHIFT = (1 << (MUI_KEY_LSHIFT - MUI_KEY_MODIFIERS)),
MUI_MODIFIER_RSHIFT = (1 << (MUI_KEY_RSHIFT - MUI_KEY_MODIFIERS)),
MUI_MODIFIER_LCTRL = (1 << (MUI_KEY_LCTRL - MUI_KEY_MODIFIERS)),
MUI_MODIFIER_RCTRL = (1 << (MUI_KEY_RCTRL - MUI_KEY_MODIFIERS)),
MUI_MODIFIER_LALT = (1 << (MUI_KEY_LALT - MUI_KEY_MODIFIERS)),
MUI_MODIFIER_RALT = (1 << (MUI_KEY_RALT - MUI_KEY_MODIFIERS)),
MUI_MODIFIER_RSUPER = (1 << (MUI_KEY_RSUPER - MUI_KEY_MODIFIERS)),
MUI_MODIFIER_LSUPER = (1 << (MUI_KEY_LSUPER - MUI_KEY_MODIFIERS)),
// special flag, trace events handling for this event
MUI_MODIFIER_EVENT_TRACE= (1 << 15),
MUI_MODIFIER_SHIFT = (MUI_MODIFIER_LSHIFT | MUI_MODIFIER_RSHIFT),
MUI_MODIFIER_CTRL = (MUI_MODIFIER_LCTRL | MUI_MODIFIER_RCTRL),
MUI_MODIFIER_ALT = (MUI_MODIFIER_LALT | MUI_MODIFIER_RALT),
MUI_MODIFIER_SUPER = (MUI_MODIFIER_LSUPER | MUI_MODIFIER_RSUPER),
};
/*
* The following constants are in UTF8 format, and relate to glyphs in
* the TTF fonts
*/
/* These are from the icon font */
#define MUI_ICON_FOLDER ""
#define MUI_ICON_FOLDER_OPEN ""
#define MUI_ICON_ROOT ""
#define MUI_ICON_FILE ""
#define MUI_ICON_POPUP_ARROWS ""
#define MUI_ICON_HOME ""
#define MUI_ICON_SBAR_UP ""
#define MUI_ICON_SBAR_DOWN ""
/* These are specific the Charcoal System font */
#define MUI_GLYPH_APPLE "" // solid apple
#define MUI_GLYPH_OAPPLE "" // open apple
#define MUI_GLYPH_COMMAND ""
#define MUI_GLYPH_OPTION ""
#define MUI_GLYPH_CONTROL ""
#define MUI_GLYPH_SHIFT ""
#define MUI_GLYPH_TICK "" // tickmark for menus
#define MUI_GLYPH_SUBMENU "" // custom, for the hierarchical menus
#define MUI_GLYPH_IIE "" // custom, IIe glyph
/* These are also from Charcoal System font (added to the original) */
#define MUI_GLYPH_F1 ""
#define MUI_GLYPH_F2 ""
#define MUI_GLYPH_F3 ""
#define MUI_GLYPH_F4 ""
#define MUI_GLYPH_F5 ""
#define MUI_GLYPH_F6 ""
#define MUI_GLYPH_F7 ""
#define MUI_GLYPH_F8 ""
#define MUI_GLYPH_F9 ""
#define MUI_GLYPH_F10 ""
#define MUI_GLYPH_F11 ""
#define MUI_GLYPH_F12 ""
typedef uint64_t mui_time_t;
/*
* Even description. pretty standard stuff here -- the 'when' field is
* only used really to detect double clicks so far.
*
* Even handlers should return true if the event was handled, (in which case
* even processing stops for that event) or false to continue passing the even
* down the chain.
*
* Events are passed to the top window first, and then down the chain of
* windows, until one of them returns true.
* Implicitely, it means the menubar gets to see the events first, even clicks,
* even if the click wasn't in the menubar. This is also true of key events of
* course, which allows the menu to detect key combos, first.
*/
typedef struct mui_event_t {
uint8_t type;
mui_time_t when;
uint32_t modifiers;
union {
struct key {
uint32_t key;
bool up;
} key;
struct {
uint32_t button;
c2_pt_t where;
} mouse;
struct {
int32_t delta;
c2_pt_t where;
} wheel;
};
} mui_event_t;
/*
* Key equivalent, used to match key events to menu items
* Might be extended to controls, right now only the 'key' is checked,
* mostly for Return and ESC.
*/
typedef union mui_key_equ_t {
struct {
uint16_t mod;
uint16_t key;
};
uint32_t value;
} mui_key_equ_t;
#define MUI_KEY_EQU(_mask, _key) \
(mui_key_equ_t){ .mod = (_mask), .key = (_key) }
struct mui_t;
typedef struct mui_listbox_elem_t {
uint32_t disabled : 1;
char icon[8];
void * elem; // char * or... ?
} mui_listbox_elem_t;
DECLARE_C_ARRAY(mui_listbox_elem_t, mui_listbox_elems, 2);
IMPLEMENT_C_ARRAY(mui_listbox_elems);
struct mui_control_t;
struct mui_window_t;
struct mui_listbox_elem_t;
/*
* Window DEFinition -- Handle all related to a window, from drawing to
* even handling.
*/
enum {
MUI_WDEF_INIT = 0,
MUI_WDEF_DISPOSE,
MUI_WDEF_DRAW,
MUI_WDEF_EVENT,
};
typedef bool (*mui_wdef_p)(
struct mui_window_t * win,
uint8_t what,
void * param);
enum {
MUI_CDEF_INIT = 0,
MUI_CDEF_DISPOSE,
MUI_CDEF_DRAW,
MUI_CDEF_EVENT,
MUI_CDEF_SET_STATE,
MUI_CDEF_SET_VALUE,
MUI_CDEF_SET_TITLE,
MUI_CDEF_SELECT,
};
typedef bool (*mui_cdef_p)(
struct mui_control_t * c,
uint8_t what,
void * param);
typedef void (*mui_ldef_p)(
struct mui_control_t * c,
uint32_t elem_index,
struct mui_listbox_elem_t * elem);
/*
* Timer callback definition. Behaves in a pretty standard way; the timer
* returns 0 to be cancelled (for one shot timers for example) or return
* the delay to the next call.
*/
typedef mui_time_t (*mui_timer_p)(
struct mui_t * mui,
mui_time_t now,
void * param);
/*
* Actions are the provided way to add custom response to events for the
* application; action handlers are called for a variety of things, from clicks
* in controls, to menu selections, to window close etc.
*
* The 'what' parameter is a 4 character code, that can be used to identify
* the action, and the 'param' is a pointer to a structure that depends on
* the 'what' action (hopefully documented with that action constant)
*
* the 'cb_param' is specific to this action function pointer and is passed as
* is to the callback.
*/
typedef int (*mui_window_action_p)(
struct mui_window_t * win,
void * cb_param,
uint32_t what,
void * param);
typedef int (*mui_control_action_p)(
struct mui_control_t *c,
void * cb_param,
uint32_t what,
void * param);
/*
* This is a standardized way of installing 'action' handlers onto windows
* and controls. The 'current' field is used to prevent re-entrance. This structure
* is opaque and is not accessible by the application, typically.
*/
typedef struct mui_action_t {
STAILQ_ENTRY(mui_action_t) self;
uint32_t current; // prevents re-entrance
union {
mui_window_action_p window_cb;
mui_control_action_p control_cb;
};
void * cb_param;
} mui_action_t;
typedef STAILQ_HEAD(, mui_action_t) mui_action_queue_t;
struct cg_surface_t;
struct cg_ctx_t;
/*
* Describes a pixmap. Currently only used for the screen destination pixels.
* And really, only bpp:43 for ARGB is supported.
*/
typedef struct mui_pixmap_t {
uint8_t * pixels;
uint32_t bpp : 8;
c2_pt_t size;
uint32_t row_bytes;
} mui_pixmap_t;
typedef pixman_region32_t mui_region_t;
DECLARE_C_ARRAY(mui_region_t, mui_clip_stack, 2);
/*
* The Drawable is a drawing context -- currently there's only one for the
* whole screen, but technically we could have several. The important feature
* of this is that it keeps a context for the pixman library destination
* image, AND also the context for the 'cg' vectorial library.
* Furthermore it keeps track of a stack of clipping rectangles, and is able
* to 'sync' the current clipping area for either (or both) cg and libpixman.
*/
typedef struct mui_drawable_t {
mui_pixmap_t pix; // *has* to be first in struct
void * _pix_hash; // used to detect if pix has changed
struct cg_surface_t * cg_surface;
struct cg_ctx_t * cg;
union pixman_image * pixman; // (try) not to use these directly
unsigned int pixman_clip_dirty: 1,
cg_clip_dirty : 1;
mui_clip_stack_t clip;
} mui_drawable_t;
/*
* Drawable related
*/
void
mui_drawable_dispose(
mui_drawable_t * dr);
// get/allocate a pixman structure for this drawable
union pixman_image *
mui_drawable_get_pixman(
mui_drawable_t * dr);
// get/allocate a cg drawing context for this
struct cg_ctx_t *
mui_drawable_get_cg(
mui_drawable_t * dr);
// return 0 (no intersect), 1: fully contained and 2: partial contains
int
mui_drawable_clip_intersects(
mui_drawable_t * dr,
c2_rect_p r );
void
mui_drawable_set_clip(
mui_drawable_t * dr,
c2_rect_array_p clip );
int
mui_drawable_clip_push(
mui_drawable_t * dr,
c2_rect_p r );
int
mui_drawable_clip_push_region(
mui_drawable_t * dr,
pixman_region32_t * rgn );
int
mui_drawable_clip_substract_region(
mui_drawable_t * dr,
pixman_region32_t * rgn );
void
mui_drawable_clip_pop(
mui_drawable_t * dr );
pixman_region32_t *
mui_drawable_clip_get(
mui_drawable_t * dr);
/*
* Your typical ARGB color. Note that the components are NOT
* alpha-premultiplied at this stage.
* This struct should be able to be passed as a value, not a pointer
*/
typedef union mui_color_t {
struct {
uint8_t a,r,g,b;
} __attribute__((packed));
uint32_t value;
uint8_t v[4];
} mui_color_t;
typedef struct mui_control_color_t {
mui_color_t fill, frame, text;
} mui_control_color_t;
#define MUI_COLOR(_v) ((mui_color_t){ .value = (_v)})
#define CG_COLOR(_c) (struct cg_color_t){ \
.a = (_c).a / 255.0, .r = (_c).r / 255.0, \
.g = (_c).g / 255.0, .b = (_c).b / 255.0 }
/*
* Pixman use premultiplied alpha values
*/
#define PIXMAN_COLOR(_c) (pixman_color_t){ \
.alpha = (_c).a * 257, .red = (_c).r * (_c).a, \
.green = (_c).g * (_c).a, .blue = (_c).b * (_c).a }
typedef struct mui_font_t {
mui_drawable_t font; // points to ttc pixels!
char * name; // not filename, internal name, aka 'main'
unsigned int size; // in pixels
TAILQ_ENTRY(mui_font_t) self;
struct stb_ttc_info ttc;
} mui_font_t;
/*
* Font related
*/
void
mui_font_init(
struct mui_t * ui);
void
mui_font_dispose(
struct mui_t * ui);
mui_font_t *
mui_font_find(
struct mui_t * ui,
const char * name);
void
mui_font_text_draw(
mui_font_t * font,
mui_drawable_t *dr,
c2_pt_t where,
const char * text,
unsigned int text_len,
mui_color_t color);
int
mui_font_text_measure(
mui_font_t * font,
const char * text,
struct stb_ttc_measure *m );
enum mui_text_align_e {
MUI_TEXT_ALIGN_LEFT = 0,
MUI_TEXT_ALIGN_CENTER = (1 << 0),
MUI_TEXT_ALIGN_RIGHT = (1 << 1),
MUI_TEXT_ALIGN_TOP = 0,
MUI_TEXT_ALIGN_MIDDLE = (MUI_TEXT_ALIGN_CENTER << 2),
MUI_TEXT_ALIGN_BOTTOM = (MUI_TEXT_ALIGN_RIGHT << 2),
};
void
mui_font_textbox(
mui_font_t * font,
mui_drawable_t *dr,
c2_rect_t bbox,
const char * text,
unsigned int text_len,
mui_color_t color,
uint16_t flags );
DECLARE_C_ARRAY(stb_ttc_g*, mui_glyph_array, 8, int x, y, w; );
DECLARE_C_ARRAY(mui_glyph_array_t, mui_glyph_line_array, 8);
/*
* Measure a text string, return the number of lines, and each glyphs
* position already aligned to the MUI_TEXT_ALIGN_* flags.
*/
void
mui_font_measure(
mui_font_t * font,
c2_rect_t bbox,
const char * text,
unsigned int text_len,
mui_glyph_line_array_t *lines,
uint16_t flags);
// clear all the lines, and glyph lists. Use it after mui_font_measure
void
mui_font_measure_clear(
mui_glyph_line_array_t *lines);
enum mui_window_layer_e {
MUI_WINDOW_LAYER_NORMAL = 0,
MUI_WINDOW_LAYER_MODAL = 3,
MUI_WINDOW_LAYER_ALERT = 5,
MUI_WINDOW_LAYER_TOP = 15,
// Menubar and Menus (popups) are also windows
MUI_WINDOW_MENUBAR_LAYER = MUI_WINDOW_LAYER_TOP - 1,
MUI_WINDOW_MENU_LAYER,
};
enum mui_window_action_e {
MUI_WINDOW_ACTION_NONE = 0,
MUI_WINDOW_ACTION_CLOSE = FCC('w','c','l','s'),
};
typedef struct mui_window_t {
TAILQ_ENTRY(mui_window_t) self;
struct mui_t * ui;
mui_wdef_p wdef;
uint32_t uid; // optional, pseudo unique id
struct {
unsigned long hidden: 1,
zombie: 1, // is in pre-delete ui->zombies
layer : 4,
hit_part : 8;
} flags;
c2_pt_t click_loc;
struct mui_drawable_t * dr;
// both these rectangles are in screen coordinates, even tho
// 'contents' is fully included in 'frame'
c2_rect_t frame, content;
char * title;
mui_action_queue_t actions;
TAILQ_HEAD(controls, mui_control_t) controls;
// anything deleted during an action goes in zombies
TAILQ_HEAD(zombies, mui_control_t) zombies;
struct mui_control_t * control_clicked;
mui_region_t inval;
} mui_window_t;
/*
* Window related
*/
mui_window_t *
mui_window_create(
struct mui_t * ui,
c2_rect_t frame,
mui_wdef_p wdef,
uint8_t layer,
const char * title,
uint32_t instance_size);
// Dispose a window and it's content (controls).
/*
* Note: if an action is in progress the window is not freed immediately
* but added to the zombie list, and freed when the action is done.
* This is to prevent re-entrance problems. This allows window actions to
* delete their own window without crashing.
*/
void
mui_window_dispose(
mui_window_t * win);
// Invalidate 'r' in window coordinates, or the whole window if 'r' is NULL
void
mui_window_inval(
mui_window_t * win,
c2_rect_t * r);
// return true if the window is the frontmost window (in that window's layer)
bool
mui_window_isfront(
mui_window_t * win);
// return the top (non menubar/menu) window
mui_window_t *
mui_window_front(
struct mui_t *ui);
// move win to the front (of its layer), return true if it was moved
bool
mui_window_select(
mui_window_t * win);
// call the window action callback, if any
void
mui_window_action(
mui_window_t * c,
uint32_t what,
void * param );
// add an action callback for this window
void
mui_window_set_action(
mui_window_t * c,
mui_window_action_p cb,
void * param );
// return the window whose UID is 'uid', or NULL if not found
mui_window_t *
mui_window_get_by_id(
struct mui_t * ui,
uint32_t uid );
// set the window UID
void
mui_window_set_id(
mui_window_t * win,
uint32_t uid);
struct mui_menu_items_t;
/*
* This is a menu item descriptor (also used for the titles, bar a few bits).
* This does not a *control* in the window, instead this is used to describe
* the menus and menu item controls that are created when the menu becomes visible.
*/
typedef struct mui_menu_item_t {
uint32_t disabled : 1, hilited : 1;
uint32_t index: 9;
uint32_t uid;
char * title;
char mark[8]; // UTF8 -- Charcoal
char icon[8]; // UTF8 -- Wider, icon font
char kcombo[16]; // UTF8 -- display only
mui_key_equ_t key_equ; // keystroke to select this item
struct mui_menu_item_t * submenu;
c2_pt_t location; // calculated by menu creation code
} mui_menu_item_t;
/*
* The menu item array is atypical as the items ('e' field) are not allocated
* by the array, but by the menu creation code. This is because the menu
* reuses the pointer to the items that is passed when the menu is added to
* the menubar.
* the 'read only' field is used to prevent the array from trying to free the
* items when being disposed.
*/
DECLARE_C_ARRAY(mui_menu_item_t, mui_menu_items, 2,
bool read_only; );
IMPLEMENT_C_ARRAY(mui_menu_items);
enum {
// parameter is a mui_menu_item_t* for the first item of the menu,
// this is exactly the parameter passed to add_simple()
// you can use this to disable/enable menu items etc
MUI_MENUBAR_ACTION_PREPARE = FCC('m','b','p','r'),
// parameter 'target' is a mui_menuitem_t*
MUI_MENUBAR_ACTION_SELECT = FCC('m','b','a','r'),
};
/*
* Menu related.
* Menubar, and menus/popups are windows as well, in a layer above the
* normal ones.
*/
mui_window_t *
mui_menubar_new(
struct mui_t * ui );
// return the previously created menubar (or NULL)
mui_window_t *
mui_menubar_get(
struct mui_t * ui );
/*
* Add a menu to the menubar. 'items' is an array of mui_menu_item_t
* terminated by an element with a NULL title.
* Note: The array is NOT const, it will be tweaked for storing items position,
* it can also be tweaked to set/reset the disabled state, check marks etc
*/
struct mui_control_t *
mui_menubar_add_simple(
mui_window_t * win,
const char * title,
uint32_t menu_uid,
mui_menu_item_t * items );
/* Turn off any highlighted menu titles */
mui_window_t *
mui_menubar_highlight(
mui_window_t * win,
bool ignored );
enum mui_control_type_e {
MUI_CONTROL_NONE = 0,
MUI_CONTROL_BUTTON,
MUI_CONTROL_GROUP,
MUI_CONTROL_SEPARATOR,
MUI_CONTROL_TEXTBOX,
MUI_CONTROL_GROUPBOX,
MUI_CONTROL_LISTBOX,
MUI_CONTROL_SCROLLBAR,
MUI_CONTROL_MENUTITLE,
MUI_CONTROL_MENUITEM,
MUI_CONTROL_SUBMENUITEM,
MUI_CONTROL_POPUP,
};
enum {
MUI_BUTTON_STYLE_NORMAL = 0,
MUI_BUTTON_STYLE_DEFAULT = 1,
MUI_BUTTON_STYLE_RADIO,
MUI_BUTTON_STYLE_CHECKBOX,
};
enum {
MUI_CONTROL_STATE_NORMAL = 0,
MUI_CONTROL_STATE_HOVER,
MUI_CONTROL_STATE_CLICKED,
MUI_CONTROL_STATE_DISABLED,
MUI_CONTROL_STATE_COUNT
};
enum {
MUI_CONTROL_ACTION_NONE = 0,
MUI_CONTROL_ACTION_VALUE_CHANGED = FCC('c','v','a','l'),
MUI_CONTROL_ACTION_CLICKED = FCC('c','l','k','d'),
MUI_CONTROL_ACTION_SELECT = FCC('c','s','e','l'),
MUI_CONTROL_ACTION_DOUBLECLICK = FCC('c','d','c','l'),
};
/*
* Control record... this are the 'common' fields, most of the controls
* have their own 'extended' record using their own fields.
*/
typedef struct mui_control_t {
TAILQ_ENTRY(mui_control_t) self;
struct mui_window_t * win;
mui_cdef_p cdef;
uint32_t state;
uint32_t type;
uint32_t style;
struct {
unsigned int hidden : 1,
zombie : 1,
hit_part : 8;
} flags;
uint32_t value;
uint32_t uid;
uint32_t uid_mask; // for radio buttons
c2_rect_t frame;
mui_key_equ_t key_equ; // keystroke to select this control
char * title;
mui_action_queue_t actions;
} mui_control_t;
/*
* Control related
*/
mui_control_t *
mui_control_new(
mui_window_t * win,
uint8_t type,
mui_cdef_p cdef,
c2_rect_t frame,
const char * title,
uint32_t uid,
uint32_t instance_size );
void
mui_control_dispose(
mui_control_t * c );
uint32_t
mui_control_get_type(
mui_control_t * c );
uint32_t
mui_control_get_uid(
mui_control_t * c );
mui_control_t *
mui_control_locate(
mui_window_t * win,
c2_pt_t pt );
mui_control_t *
mui_control_get_by_id(
mui_window_t * win,
uint32_t uid );
void
mui_control_inval(
mui_control_t * c );
void
mui_control_action(
mui_control_t * c,
uint32_t what,
void * param );
void
mui_control_set_action(
mui_control_t * c,
mui_control_action_p cb,
void * param );
void
mui_control_set_state(
mui_control_t * c,
uint32_t state );
uint32_t
mui_control_get_state(
mui_control_t * c );
int32_t
mui_control_get_value(
mui_control_t * c);
int32_t
mui_control_set_value(
mui_control_t * c,
int32_t selected);
const char *
mui_control_get_title(
mui_control_t * c );
void
mui_control_set_title(
mui_control_t * c,
const char * text );
mui_control_t *
mui_button_new(
mui_window_t * win,
c2_rect_t frame,
uint8_t style,
const char * title,
uint32_t uid );
/*
* Create a static text box. Font is optional, flags correponds to the MUI_TEXT_ALIGN_*
* PLUS the extrast listed below.
*/
enum mui_textbox_e {
MUI_CONTROL_TEXTBOX_FRAME = (1 << 8),
};
mui_control_t *
mui_textbox_new(
mui_window_t * win,
c2_rect_t frame,
const char * text,
const char * font,
uint16_t flags );
mui_control_t *
mui_groupbox_new(
mui_window_t * win,
c2_rect_t frame,
const char * title,
uint16_t flags );
mui_control_t *
mui_scrollbar_new(
mui_window_t * win,
c2_rect_t frame,
uint32_t uid );
uint32_t
mui_scrollbar_get_max(
mui_control_t * c);
void
mui_scrollbar_set_max(
mui_control_t * c,
uint32_t max);
void
mui_scrollbar_set_page(
mui_control_t * c,
uint32_t page);
mui_control_t *
mui_listbox_new(
mui_window_t * win,
c2_rect_t frame,
uint32_t uid );
void
mui_listbox_prepare(
mui_control_t * c);
mui_listbox_elems_t *
mui_listbox_get_elems(
mui_control_t * c);
mui_control_t *
mui_separator_new(
mui_window_t * win,
c2_rect_t frame);
mui_control_t *
mui_popupmenu_new(
mui_window_t * win,
c2_rect_t frame,
const char * title,
uint32_t uid );
mui_menu_items_t *
mui_popupmenu_get_items(
mui_control_t * c);
void
mui_popupmenu_prepare(
mui_control_t * c);
/*
* Standard file dialog
*/
enum mui_std_action_e {
MUI_STDF_ACTION_NONE = 0,
// parameter 'target' is a char * with full pathname of selected file
MUI_STDF_ACTION_SELECT = FCC('s','t','d','s'),
MUI_STDF_ACTION_CANCEL = FCC('s','t','d','c'),
};
mui_window_t *
mui_stdfile_get(
struct mui_t * ui,
c2_pt_t where,
const char * prompt,
const char * regexp,
const char * start_path );
// return the curently selected pathname -- caller must free() it
char *
mui_stdfile_get_selected_path(
mui_window_t * w );
/*
* Alert dialog
*/
enum {
MUI_ALERT_FLAG_OK = (1 << 0),
MUI_ALERT_FLAG_CANCEL = (1 << 1),
MUI_ALERT_ICON_INFO = (1 << 8),
MUI_ALERT_INFO = (MUI_ALERT_FLAG_OK | MUI_ALERT_ICON_INFO),
MUI_ALERT_WARN = (MUI_ALERT_FLAG_OK | MUI_ALERT_FLAG_CANCEL),
};
enum {
MUI_ALERT_BUTTON_OK = FCC('o','k',' ',' '),
MUI_ALERT_BUTTON_CANCEL = FCC('c','a','n','c'),
};
mui_window_t *
mui_alert(
struct mui_t * ui,
c2_pt_t where, // (0,0) will center it
const char * title,
const char * message,
uint16_t flags );
enum {
MUI_TIME_RES = 1,
MUI_TIME_SECOND = 1000000,
MUI_TIME_MS = (MUI_TIME_SECOND/1000),
};
mui_time_t
mui_get_time();
typedef struct mui_timer_group_t {
uint64_t map;
struct {
mui_time_t when;
mui_timer_p cb;
void * param;
} timers[64];
} mui_timer_group_t;
/*
* Register 'cb' to be called after 'delay'. Returns a timer id (0 to 63)
* or 0xff if no timer is available. The timer function cb can return 0 for a one
* shot timer, or another delay that will be added to the current stamp for a further
* call of the timer.
* 'param' will be also passed to the timer callback.
*/
uint8_t
mui_timer_register(
struct mui_t * ui,
mui_timer_p cb,
void * param,
uint32_t delay);
typedef struct mui_t {
c2_pt_t screen_size;
mui_color_t clear_color;
uint16_t modifier_keys;
int draw_debug;
// this is the sum of all the window's dirty regions, inc moved windows etc
mui_region_t inval;
// once the pixels have been refreshed, 'inval' is copied to 'redraw'
// to push the pixels to the screen.
mui_region_t redraw;
TAILQ_HEAD(, mui_font_t) fonts;
TAILQ_HEAD(windows, mui_window_t) windows;
mui_window_t * menubar;
TAILQ_HEAD(, mui_window_t) zombies;
// this is used to track any active action callbacks to
// prevent recursion problem and track any 'delete' happening
// during an action callback
uint32_t action_active;
mui_window_t * event_capture;
mui_timer_group_t timer;
char * pref_directory; /* optional */
} mui_t;
void
mui_init(
mui_t * ui);
void
mui_dispose(
mui_t * ui);
void
mui_draw(
mui_t * ui,
mui_drawable_t *dr,
uint16_t all);
void
mui_run(
mui_t * ui);
// return true if the event was handled by the ui
bool
mui_handle_event(
mui_t * ui,
mui_event_t * ev);
// return true if event 'ev' is a key combo matching key_equ
bool
mui_event_match_key(
mui_event_t * ev,
mui_key_equ_t key_equ);
/* Return true if the ui has any active windows, ie, not hidden, zombie;
* This does not include the menubar, but it does include any menus or popups
*
* This is used to decide wether to hide the mouse cursor or not
*/
bool
mui_has_active_windows(
mui_t * ui);
/* Return a hash value for string inString */
uint32_t
mui_hash(
const char * inString );

90
libmui/mui/mui_alert.c Normal file
View File

@ -0,0 +1,90 @@
/*
* mui_alert.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mui.h"
typedef struct mui_alert_t {
mui_window_t win;
mui_control_t * ok, *cancel;
} mui_alert_t;
static int
_mui_alert_button_cb(
mui_control_t * c,
void * cb_param,
uint32_t what,
void * param)
{
// mui_alert_t * alert = (mui_alert_t *)c->win;
// notify the window handler of the control action
mui_window_action(c->win, what, c);
mui_window_dispose(c->win);
return 0;
}
mui_window_t *
mui_alert(
struct mui_t * ui,
c2_pt_t where,
const char * title,
const char * message,
uint16_t flags )
{
c2_rect_t cf = C2_RECT_WH(0, 0, 540, 200);
if (where.x && where.y)
c2_rect_offset(&cf, where.x, where.y);
else
c2_rect_offset(&cf,
(ui->screen_size.x / 2) - (c2_rect_width(&cf) / 2),
(ui->screen_size.y * 0.3) - (c2_rect_height(&cf) / 2));
mui_window_t *w = mui_window_create(ui, cf,
NULL, MUI_WINDOW_LAYER_ALERT,
title, sizeof(mui_alert_t));
mui_alert_t * alert = (mui_alert_t *)w;
mui_control_t * c = NULL;
cf = C2_RECT_WH(0, 0, 120, 40);
c2_rect_left_of(&cf, c2_rect_width(&w->content), 20);
c2_rect_top_of(&cf, c2_rect_height(&w->content), 20);
if (flags & MUI_ALERT_FLAG_OK) {
alert->ok = c = mui_button_new(w, cf, MUI_BUTTON_STYLE_DEFAULT,
"OK", MUI_ALERT_BUTTON_OK);
alert->ok->key_equ = MUI_KEY_EQU(0, 13); // return
c2_rect_left_of(&cf, cf.l, 30);
}
if (flags & MUI_ALERT_FLAG_CANCEL) {
alert->cancel = c = mui_button_new(w, cf, MUI_BUTTON_STYLE_NORMAL,
"Cancel", MUI_ALERT_BUTTON_CANCEL);
alert->cancel->key_equ = MUI_KEY_EQU(0, 27); // ESC
}
cf = C2_RECT_WH(0, 10, 540-140, 70);
c2_rect_left_of(&cf, c2_rect_width(&w->content), 20);
c = mui_textbox_new(w, cf, message, NULL, 0);
cf = C2_RECT_WH(10, 10, 80, 75);
c = mui_textbox_new(w, cf,
"", "icon_large",
MUI_TEXT_ALIGN_CENTER | MUI_TEXT_ALIGN_MIDDLE);
c = NULL;
TAILQ_FOREACH(c, &w->controls, self) {
if (mui_control_get_uid(c) == 0)
continue;
mui_control_set_action(c, _mui_alert_button_cb, alert);
}
return w;
}

188
libmui/mui/mui_cdef_boxes.c Normal file
View File

@ -0,0 +1,188 @@
/*
* mui_cdef_boxes.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
#include "cg.h"
typedef struct mui_textbox_control_t {
mui_control_t control;
mui_font_t * font;
uint16_t flags;
} mui_textbox_control_t;
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
static void
mui_textbox_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
mui_textbox_control_t *tb = (mui_textbox_control_t *)c;
c2_rect_t text_frame = f;
mui_drawable_clip_push(dr, &f);
mui_font_textbox(tb->font, dr,
text_frame, c->title, strlen(c->title),
mui_control_color[c->state].text,
tb->flags);
if (tb->flags & MUI_CONTROL_TEXTBOX_FRAME) {
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_line_width(cg, 1);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_rectangle(cg, f.l, f.t,
c2_rect_width(&f), c2_rect_height(&f));
cg_stroke(cg);
}
mui_drawable_clip_pop(dr);
}
static void
mui_groupbox_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
mui_textbox_control_t *tb = (mui_textbox_control_t *)c;
c2_rect_t text_frame = f;
c2_rect_t box_frame = f;
mui_font_t * main = mui_font_find(win->ui, "main");
stb_ttc_measure m = {};
mui_font_text_measure(main, c->title, &m);
text_frame.l += main->size * 0.3;
text_frame.b = text_frame.t + main->size;
text_frame.r = text_frame.l + m.x1 + m.x0;
box_frame.t += m.ascent * 0.85;
mui_color_t contentFill = MUI_COLOR(0xf0f0f0ff);
mui_color_t decoColor = MUI_COLOR(0x666666ff);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_line_width(cg, 1);
cg_set_source_color(cg, &CG_COLOR(decoColor));
cg_rectangle(cg, box_frame.l, box_frame.t,
c2_rect_width(&box_frame), c2_rect_height(&box_frame));
cg_stroke(cg);
// now erase text background
cg_set_source_color(cg, &CG_COLOR(contentFill));
cg_rectangle(cg, text_frame.l, text_frame.t,
c2_rect_width(&text_frame), c2_rect_height(&text_frame));
cg_fill(cg);
// mui_drawable_clip_push(dr, &f);
mui_font_textbox(main, dr,
text_frame, c->title, strlen(c->title),
mui_control_color[c->state].text,
tb->flags);
// mui_drawable_clip_pop(dr);
}
static void
mui_separator_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
// mui_drawable_clip_push(dr, &f);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_line_width(cg, 1);
mui_color_t decoColor = MUI_COLOR(0x666666ff);
cg_set_source_color(cg, &CG_COLOR(decoColor));
cg_move_to(cg, f.l + 0, f.t);
cg_line_to(cg, f.r - 0, f.t);
cg_stroke(cg);
// mui_drawable_clip_pop(dr);
}
static bool
mui_cdef_boxes(
struct mui_control_t * c,
uint8_t what,
void * param)
{
// mui_textbox_control_t *tb = (mui_textbox_control_t *)c;
switch (what) {
case MUI_CDEF_DRAW: {
mui_drawable_t * dr = param;
switch (c->type) {
case MUI_CONTROL_SEPARATOR:
mui_separator_draw(c->win, c, dr);
break;
case MUI_CONTROL_GROUPBOX:
mui_groupbox_draw(c->win, c, dr);
break;
case MUI_CONTROL_TEXTBOX:
mui_textbox_draw(c->win, c, dr);
break;
default:
break;
}
} break;
}
return false;
}
mui_control_t *
mui_textbox_new(
mui_window_t * win,
c2_rect_t frame,
const char * text,
const char * font,
uint16_t flags )
{
mui_control_t * c = mui_control_new(
win, MUI_CONTROL_TEXTBOX, mui_cdef_boxes,
frame, text, 0, sizeof(mui_textbox_control_t));
mui_textbox_control_t *tb = (mui_textbox_control_t *)c;
tb->font = mui_font_find(win->ui, font ? font : "main");
tb->flags = flags;
return c;
}
mui_control_t *
mui_separator_new(
mui_window_t * win,
c2_rect_t frame)
{
return mui_control_new(
win, MUI_CONTROL_SEPARATOR, mui_cdef_boxes,
frame, NULL, 0, sizeof(mui_textbox_control_t));
}
mui_control_t *
mui_groupbox_new(
mui_window_t * win,
c2_rect_t frame,
const char * title,
uint16_t flags )
{
mui_control_t * c = mui_control_new(
win, MUI_CONTROL_GROUPBOX, mui_cdef_boxes,
frame, title, 0, sizeof(mui_textbox_control_t));
mui_textbox_control_t *tb = (mui_textbox_control_t *)c;
tb->flags = flags;
return c;
}

View File

@ -0,0 +1,267 @@
/*
* mui_cdef_buttons.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
#include "cg.h"
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
#define BUTTON_INSET 4
void
mui_button_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
if (c->style == MUI_BUTTON_STYLE_DEFAULT) {
cg_set_line_width(cg, 3);
cg_round_rectangle(cg, f.l, f.t,
c2_rect_width(&f), c2_rect_height(&f),
10, 10);
cg_stroke(cg);
c2_rect_inset(&f, BUTTON_INSET, BUTTON_INSET);
}
mui_font_t * main = TAILQ_FIRST(&win->ui->fonts);
stb_ttc_measure m = {};
mui_font_text_measure(main, c->title, &m);
int title_width = m.x1 - m.x0;
c2_rect_t title = f;
title.r = title.l + title_width + 1;
title.b = title.t + m.ascent - m.descent;
c2_rect_offset(&title, -m.x0 +
(int)((c2_rect_width(&f) / 2) - (c2_rect_width(&title)) / 2),
(c2_rect_height(&f) / 2) - (c2_rect_height(&title) / 2));
mui_drawable_clip_push(dr, &f);
cg = mui_drawable_get_cg(dr);
c2_rect_t inner = f;
c2_rect_inset(&inner, 1, 1);
cg_set_line_width(cg, 2);
cg_round_rectangle(cg, inner.l, inner.t,
c2_rect_width(&inner), c2_rect_height(&inner), 6, 6);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].fill));
cg_fill_preserve(cg);
// cg_rectangle(cg, title.l, title.t,
// c2_rect_width(&title), c2_rect_height(&title));
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_stroke(cg);
mui_font_text_draw(main, dr,
C2_PT(title.l, title.t), c->title, strlen(c->title),
mui_control_color[c->state].text);
mui_drawable_clip_pop(dr);
}
void
mui_check_rad_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
mui_font_t * main = mui_font_find(win->ui, "main");
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
c2_rect_t box = f;
box.r = box.l + (main->size * 0.95);
box.b = box.t + (main->size * 0.95);
c2_rect_offset(&box, 0, (c2_rect_height(&f) / 2) - (c2_rect_height(&box) / 2));
c2_rect_t title = f;
title.l = box.r + 8;
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_drawable_clip_push(dr, &f);
// draw the box/circle
if (c->style == MUI_BUTTON_STYLE_RADIO) {
cg_circle(cg, box.l + (c2_rect_width(&box) / 2),
box.t + (c2_rect_height(&box) / 2),
c2_rect_width(&box) / 2);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].fill));
cg_fill_preserve(cg);
cg_set_line_width(cg, 2);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_stroke(cg);
if (c->value) { // fill the inside circle
c2_rect_inset(&box, 5, 5);
cg_circle(cg, box.l + (c2_rect_width(&box) / 2),
box.t + (c2_rect_height(&box) / 2),
c2_rect_width(&box) / 2);
cg_fill(cg);
}
} else {
cg_rectangle(cg, box.l, box.t,
c2_rect_width(&box), c2_rect_height(&box));
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].fill));
cg_fill_preserve(cg);
cg_set_line_width(cg, 2);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_stroke(cg);
// now the cross
if (c->value) {
cg_set_line_width(cg, 2);
cg_move_to(cg, box.l, box.t);
cg_line_to(cg, box.r, box.b);
cg_move_to(cg, box.r, box.t);
cg_line_to(cg, box.l, box.b);
cg_stroke(cg);
}
}
mui_font_textbox(main, dr,
title, c->title, 0,
mui_control_color[0].text,
MUI_TEXT_ALIGN_MIDDLE);
mui_drawable_clip_pop(dr);
}
static bool
mui_button_mouse(
struct mui_control_t * c,
mui_event_t * ev)
{
if (c->state == MUI_CONTROL_STATE_DISABLED)
return false;
c2_rect_t f = c->frame;
c2_rect_offset(&f, c->win->content.l, c->win->content.t);
switch (ev->type) {
case MUI_EVENT_BUTTONDOWN: {
if (c2_rect_contains_pt(&f, &ev->mouse.where))
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
} break;
case MUI_EVENT_BUTTONUP: {
if (c->state != MUI_CONTROL_STATE_CLICKED)
break;
mui_control_set_state(c, MUI_CONTROL_STATE_NORMAL);
switch (c->style) {
case MUI_BUTTON_STYLE_NORMAL:
case MUI_BUTTON_STYLE_DEFAULT:
break;
/* Look for all other matching radio buttons, turn
* their values off, before setting this one to on */
case MUI_BUTTON_STYLE_RADIO: {
if (c->uid_mask) {
mui_control_t * c2 = NULL;
TAILQ_FOREACH(c2, &c->win->controls, self) {
if (c2->type == MUI_CONTROL_BUTTON &&
c2->style == MUI_BUTTON_STYLE_RADIO &&
(c2->uid & c->uid_mask) == (c->uid & c->uid_mask) &&
c2 != c) {
// printf("OFF %4.4s\n", (char*)&c2->uid);
mui_control_set_value(c2, false);
}
}
}
// printf("ON %4.4s\n", (char*)&c->uid);
mui_control_set_value(c, true);
} break;
case MUI_BUTTON_STYLE_CHECKBOX:
mui_control_set_value(c,
!mui_control_get_value(c));
break;
}
mui_control_action(c, MUI_CONTROL_ACTION_SELECT, NULL);
} break;
case MUI_EVENT_DRAG: {
if (c2_rect_contains_pt(&f, &ev->mouse.where))
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
else
mui_control_set_state(c, MUI_CONTROL_STATE_NORMAL);
} break;
}
return true;
}
static bool
mui_cdef_button(
struct mui_control_t * c,
uint8_t what,
void * param)
{
switch (what) {
case MUI_CDEF_INIT:
break;
case MUI_CDEF_DISPOSE:
break;
case MUI_CDEF_DRAW: {
mui_drawable_t * dr = param;
switch (c->style) {
case MUI_BUTTON_STYLE_NORMAL:
case MUI_BUTTON_STYLE_DEFAULT:
mui_button_draw(c->win, c, dr);
break;
case MUI_BUTTON_STYLE_CHECKBOX:
case MUI_BUTTON_STYLE_RADIO:
mui_check_rad_draw(c->win, c, dr);
break;
default:
return false;
}
} break;
case MUI_CDEF_EVENT: {
mui_event_t *ev = param;
// printf("%s event %d where %dx%d\n", __func__, ev->type,
// ev->mouse.where.x,
// ev->mouse.where.y);
switch (ev->type) {
case MUI_EVENT_BUTTONUP:
case MUI_EVENT_DRAG:
case MUI_EVENT_BUTTONDOWN: {
return mui_button_mouse(c, ev);
} break;
}
} break;
case MUI_CDEF_SELECT: {
if (c->style == MUI_BUTTON_STYLE_CHECKBOX) {
mui_control_set_value(c, !c->value);
}
} break;
}
return false;
}
mui_control_t *
mui_button_new(
mui_window_t * win,
c2_rect_t frame,
uint8_t style,
const char * title,
uint32_t uid )
{
switch (style) {
case MUI_BUTTON_STYLE_NORMAL:
break;
case MUI_BUTTON_STYLE_DEFAULT:
c2_rect_inset(&frame, -BUTTON_INSET, -BUTTON_INSET);
break;
case MUI_BUTTON_STYLE_CHECKBOX:
break;
case MUI_BUTTON_STYLE_RADIO:
break;
}
mui_control_t * c = mui_control_new(
win, MUI_CONTROL_BUTTON, mui_cdef_button,
frame, title, uid, 0);
c->style = style;
return c;
}

View File

@ -0,0 +1,307 @@
/*
* mui_cdef_listbox.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
#include "cg.h"
typedef struct mui_listbox_control_t {
mui_control_t control;
struct mui_control_t * scrollbar;
int32_t scroll;
uint8_t elem_height;
mui_listbox_elems_t elems;
mui_ldef_p ldef;
// to handle double-click
mui_time_t last_click;
} mui_listbox_control_t;
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
static void
mui_listbox_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_line_width(cg, 1);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_rectangle(cg, f.l, f.t,
c2_rect_width(&f), c2_rect_height(&f));
cg_stroke(cg);
{
c2_rect_t clip = f;
c2_rect_inset(&clip, 1, 1);
mui_drawable_clip_push(dr, &clip);
}
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
uint32_t top_element = lb->scroll / lb->elem_height;
uint32_t bottom_element = top_element + 1 +
(c2_rect_height(&f) / lb->elem_height);
// printf("%s draw from %d to %d\n", __func__, top_element, bottom_element);
mui_font_t * icons = mui_font_find(win->ui, "icon_small");
mui_font_t * main = mui_font_find(win->ui, "main");
mui_color_t highlight = MUI_COLOR(0xd6fcc0ff);
for (unsigned int ii = top_element;
ii < lb->elems.count && ii < bottom_element; ii++) {
c2_rect_t ef = f;
ef.b = ef.t + lb->elem_height;
c2_rect_offset(&ef, 0, ii * lb->elem_height - lb->scroll);
if (ii == c->value) {
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_line_width(cg, 1);
cg_set_source_color(cg, &CG_COLOR(highlight));
cg_rectangle(cg, ef.l, ef.t,
c2_rect_width(&ef), c2_rect_height(&ef));
cg_fill(cg);
}
ef.l += 8;
mui_listbox_elem_t *e = &lb->elems.e[ii];
if (lb->elems.e[ii].icon[0])
mui_font_text_draw(icons, dr, ef.tl, lb->elems.e[ii].icon, 0,
mui_control_color[e->disabled ?
MUI_CONTROL_STATE_DISABLED : 0].text);
ef.l += 26;
mui_font_text_draw(main, dr, ef.tl, e->elem, 0,
mui_control_color[e->disabled ?
MUI_CONTROL_STATE_DISABLED : 0].text);
}
mui_drawable_clip_pop(dr);
}
static bool
mui_listbox_key(
mui_control_t * c,
mui_event_t * ev)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
printf("%s key: %d\n", __func__, ev->key.key);
c2_rect_t f = c->frame;
c2_rect_offset(&f, -f.l, -f.t);
uint32_t page_size = (c2_rect_height(&f) / lb->elem_height)-1;
int delta = 0;
if (ev->modifiers & (MUI_MODIFIER_SUPER | MUI_MODIFIER_CTRL))
return false;
switch (ev->key.key) {
case MUI_KEY_UP: delta = -1; break;
case MUI_KEY_DOWN: delta = 1; break;
case MUI_KEY_PAGEUP: delta = -page_size; break;
case MUI_KEY_PAGEDOWN: delta = page_size; break;
}
if (!delta)
return false;
int nsel = c->value + delta;
if (nsel < 0)
nsel = 0;
if (nsel >= (int)lb->elems.count)
nsel = lb->elems.count - 1;
if (nsel != (int)c->value) {
c->value = nsel;
c2_rect_t e = c->frame;
e.b = e.t + lb->elem_height;
c2_rect_offset(&e, -e.l,
-e.t + (c->value * lb->elem_height));
c2_rect_t w = f;
c2_rect_offset(&w, 0, lb->scroll);
printf(" e:%s f:%s\n", c2_rect_as_str(&e), c2_rect_as_str(&w));
if (e.b > w.b) {
lb->scroll = (e.b - c2_rect_height(&c->frame));
printf(" over %d\n", lb->scroll);
}
if (e.t < w.t)
lb->scroll = e.t;
printf(" scroll:%d\n", lb->scroll);
mui_control_set_value(lb->scrollbar, lb->scroll);
mui_control_inval(c);
// mui_control_inval(lb->scrollbar);
mui_control_action(c, MUI_CONTROL_ACTION_VALUE_CHANGED,
&lb->elems.e[nsel]);
return true;
}
return false;
}
static bool
mui_cdef_event(
struct mui_control_t * c,
mui_event_t *ev)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
switch (ev->type) {
case MUI_EVENT_BUTTONDOWN: {
c2_rect_t f = c->frame;
c2_rect_offset(&f, c->win->content.l, c->win->content.t);
// uint32_t page_size = (c2_rect_height(&f) / lb->elem_height)-1;
int nsel = lb->scroll + (ev->mouse.where.y - f.t);
nsel /= lb->elem_height;
if (nsel < 0)
nsel = 0;
if (nsel >= (int)lb->elems.count)
nsel = lb->elems.count - 1;
if (nsel != (int)c->value) {
mui_control_set_value(c, nsel);
mui_control_action(c,
MUI_CONTROL_ACTION_VALUE_CHANGED,
&lb->elems.e[nsel]);
}
{
mui_time_t now = mui_get_time();
if ((now - lb->last_click) <
(MUI_TIME_MS * 300)) {
lb->last_click = 0;
if (lb->elems.e[nsel].disabled)
return true;
mui_control_action(c,
MUI_CONTROL_ACTION_SELECT,
&lb->elems.e[nsel]);
} else
lb->last_click = now;
}
return true;
} break;
case MUI_EVENT_KEYUP: { // ignore keydowns
if (ev->key.key == 13) {
if (!lb->elems.e[c->value].disabled) {
mui_control_action(c,
MUI_CONTROL_ACTION_SELECT,
&lb->elems.e[c->value]);
}
return true;
}
if (mui_listbox_key(c, ev))
return true;
} break;
case MUI_EVENT_WHEEL: {
// printf("%s wheel delta %d\n", __func__, ev->wheel.delta);
lb->scroll += ev->wheel.delta * 20;
if (lb->scroll < 0)
lb->scroll = 0;
if (lb->scroll >
(int32_t)((lb->elems.count * lb->elem_height) -
c2_rect_height(&c->frame)))
lb->scroll = (lb->elems.count * lb->elem_height) -
c2_rect_height(&c->frame);
mui_control_set_value(lb->scrollbar, lb->scroll);
mui_control_inval(c);
return true;
} break;
}
return false;
}
bool
mui_cdef_listbox(
struct mui_control_t * c,
uint8_t what,
void * param)
{
switch (what) {
case MUI_CDEF_INIT:
break;
case MUI_CDEF_DISPOSE:
break;
case MUI_CDEF_DRAW: {
mui_drawable_t * dr = param;
mui_listbox_draw(c->win, c, dr);
} break;
case MUI_CDEF_EVENT: {
mui_event_t *ev = param;
return mui_cdef_event(c, ev);
} break;
}
return false;
}
static int
mui_listbox_sbar_action(
mui_control_t * c,
void * cb_param,
uint32_t what,
void * param)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)cb_param;
lb->scroll = mui_control_get_value(lb->scrollbar);
// printf("%s scroll %d\n", __func__, lb->scroll);
mui_control_inval(&lb->control);
return 0;
}
mui_listbox_elems_t *
mui_listbox_get_elems(
mui_control_t * c)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
return &lb->elems;
}
mui_control_t *
mui_listbox_new(
mui_window_t * win,
c2_rect_t frame,
uint32_t uid )
{
c2_rect_t lbf = frame;
c2_rect_t sb = frame;
mui_font_t * main = mui_font_find(win->ui, "main");
lbf.r -= main->size;
sb.l = sb.r - main->size;
mui_control_t *c = mui_control_new(
win, MUI_CONTROL_LISTBOX, mui_cdef_listbox,
lbf, NULL, uid, sizeof(mui_listbox_control_t));
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
lb->scrollbar = mui_scrollbar_new(win, sb, 0);
mui_control_set_action(lb->scrollbar, mui_listbox_sbar_action, c);
lb->elem_height = main->size + 2;
return c;
}
void
mui_listbox_prepare(
mui_control_t * c)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
c2_rect_t content = C2_RECT_WH(0, 0,
c2_rect_width(&c->frame), c2_rect_height(&c->frame));
content.b = lb->elems.count * lb->elem_height;
c2_rect_offset(&content, 0, lb->scroll);
if (content.b < c2_rect_height(&c->frame)) {
c2_rect_offset(&content, 0, c2_rect_height(&c->frame) - content.b);
}
if (content.t > 0) {
c2_rect_offset(&content, 0, -content.t);
}
lb->scroll = content.t;
if (c2_rect_height(&content) > c2_rect_height(&c->frame)) {
mui_scrollbar_set_max(lb->scrollbar,
c2_rect_height(&content));
mui_control_set_value(lb->scrollbar, -lb->scroll);
mui_scrollbar_set_page(lb->scrollbar, c2_rect_height(&c->frame));
} else {
mui_scrollbar_set_max(lb->scrollbar, 0);
mui_control_set_value(lb->scrollbar, 0);
mui_scrollbar_set_page(lb->scrollbar, 0);
}
mui_control_inval(lb->scrollbar);
mui_control_inval(c);
}

View File

@ -0,0 +1,366 @@
/*
* mui_cdef_scrollbar.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
#include "cg.h"
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
enum mui_sb_part_e {
MUI_SB_PART_FRAME = 0,
MUI_SB_PART_UP,
MUI_SB_PART_DOWN,
MUI_SB_PART_PAGEUP,
MUI_SB_PART_PAGEDOWN,
MUI_SB_PART_THUMB,
MUI_SB_PART_COUNT,
};
typedef struct mui_scrollbar_control_t {
mui_control_t control;
uint32_t visible;
uint32_t max;
c2_pt_t drag_offset;
uint32_t saved_value; // to handle 'snapback'
} mui_scrollbar_control_t;
static void
mui_scrollbar_make_rects(
mui_control_t * c,
c2_rect_t * parts)
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, c->win->content.l, c->win->content.t);
parts[MUI_SB_PART_FRAME] = f;
c2_rect_t part = f;
part.b = part.t + c2_rect_width(&part);
parts[MUI_SB_PART_UP] = part;
part = f;
part.t = part.b - c2_rect_width(&part);
parts[MUI_SB_PART_DOWN] = part;
mui_scrollbar_control_t *sb = (mui_scrollbar_control_t *)c;
if (sb->max <= sb->visible) {
c2_rect_t z = {};
parts[MUI_SB_PART_THUMB] = z;
parts[MUI_SB_PART_PAGEUP] = z;
parts[MUI_SB_PART_PAGEDOWN] = z;
return;
}
part = f;
part.t = parts[MUI_SB_PART_UP].b + 1;
part.b = parts[MUI_SB_PART_DOWN].t - 1;
float visible = sb->visible / (float)sb->max;
float thumb_size = visible * sb->visible;
if (thumb_size < 20)
thumb_size = 20;
float thumb_pos = c->value / ((float)sb->max- sb->visible);
float thumb_offset = 0.5 + thumb_pos * (c2_rect_height(&part) - thumb_size);
// printf("%s visible:%.2f ts: %.2f thumb_pos:%.2f thumb_offset:%.2f\n",
// __func__, visible, thumb_size, thumb_pos, thumb_offset);
part.b = part.t + thumb_size;
c2_rect_offset(&part, 0, thumb_offset);
if (part.b > parts[MUI_SB_PART_DOWN].t) {
c2_rect_offset(&part, 0, parts[MUI_SB_PART_DOWN].t - part.b);
}
parts[MUI_SB_PART_THUMB] = part;
part = f;
part.t = parts[MUI_SB_PART_UP].b + 1;
part.b = parts[MUI_SB_PART_THUMB].t - 1;
parts[MUI_SB_PART_PAGEUP] = part;
part = f;
part.t = parts[MUI_SB_PART_THUMB].b + 1;
part.b = parts[MUI_SB_PART_DOWN].t - 1;
parts[MUI_SB_PART_PAGEDOWN] = part;
}
static void
mui_scrollbar_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_line_width(cg, 1);
cg_rectangle(cg, f.l, f.t,
c2_rect_width(&f), c2_rect_height(&f));
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].fill));
cg_fill_preserve(cg);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_stroke(cg);
mui_font_t * icons = mui_font_find(win->ui, "icon_small");
c2_rect_t parts[MUI_SB_PART_COUNT];
mui_scrollbar_make_rects(c, parts);
mui_color_t contentFill = MUI_COLOR(0xa0a0a0ff);
mui_color_t decoColor = MUI_COLOR(0x666666ff);
c2_rect_t pf;
pf = parts[MUI_SB_PART_UP];
cg_rectangle(cg, pf.l, pf.t,
c2_rect_width(&pf), c2_rect_height(&pf));
cg_set_source_color(cg,
c->flags.hit_part == MUI_SB_PART_UP ?
&CG_COLOR(decoColor) :
&CG_COLOR(mui_control_color[c->state].fill));
cg_fill_preserve(cg);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_stroke(cg);
stb_ttc_measure m = {};
mui_font_text_measure(icons, "", &m);
pf.l = pf.l + (c2_rect_width(&pf) - m.x1 - m.x0) / 2;
mui_font_text_draw(icons, dr, pf.tl, "", 0,
mui_control_color[c->state].text);
pf = parts[MUI_SB_PART_DOWN];
cg_rectangle(cg, pf.l, pf.t,
c2_rect_width(&pf), c2_rect_height(&pf));
cg_set_source_color(cg,
c->flags.hit_part == MUI_SB_PART_DOWN ?
&CG_COLOR(decoColor) :
&CG_COLOR(mui_control_color[c->state].fill));
cg_fill_preserve(cg);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_stroke(cg);
mui_font_text_measure(icons, "", &m);
pf.l = pf.l + (c2_rect_width(&pf) - m.x1 - m.x0) / 2;
mui_font_text_draw(icons, dr, pf.tl, "", 0,
mui_control_color[c->state].text);
pf = parts[MUI_SB_PART_PAGEUP];
if (c2_rect_height(&pf) > 0) {
cg_rectangle(cg, pf.l, pf.t,
c2_rect_width(&pf), c2_rect_height(&pf));
cg_set_source_color(cg,
c->flags.hit_part == MUI_SB_PART_PAGEUP ?
&CG_COLOR(decoColor) :
&CG_COLOR(contentFill));
cg_fill(cg);
}
pf = parts[MUI_SB_PART_PAGEDOWN];
if (c2_rect_height(&pf) > 0) {
cg_rectangle(cg, pf.l, pf.t,
c2_rect_width(&pf), c2_rect_height(&pf));
cg_set_source_color(cg,
c->flags.hit_part == MUI_SB_PART_PAGEDOWN ?
&CG_COLOR(decoColor) :
&CG_COLOR(contentFill));
cg_fill(cg);
}
pf = parts[MUI_SB_PART_THUMB];
if (c2_rect_height(&pf) > 0) {
cg_rectangle(cg, pf.l, pf.t,
c2_rect_width(&pf), c2_rect_height(&pf));
cg_set_source_color(cg,
c->flags.hit_part == MUI_SB_PART_THUMB ?
&CG_COLOR(decoColor) :
&CG_COLOR(mui_control_color[c->state].fill));
cg_fill_preserve(cg);
cg_set_source_color(cg,
&CG_COLOR(mui_control_color[c->state].frame));
cg_stroke(cg);
}
}
static void
_mui_scrollbar_scroll(
mui_control_t * c,
int32_t delta )
{
mui_scrollbar_control_t *sb = (mui_scrollbar_control_t *)c;
int32_t v = (int32_t)c->value + delta;
if (v < 0)
v = 0;
if (v > ((int32_t)sb->max - (int32_t)sb->visible))
v = sb->max - sb->visible;
c->value = v;
mui_control_inval(c);
mui_control_action(c, MUI_CONTROL_ACTION_VALUE_CHANGED, NULL);
}
static bool
mui_scrollbar_mouse(
struct mui_control_t * c,
mui_event_t * ev)
{
mui_scrollbar_control_t *sb = (mui_scrollbar_control_t *)c;
c2_rect_t parts[MUI_SB_PART_COUNT];
mui_scrollbar_make_rects(c, parts);
switch (ev->type) {
case MUI_EVENT_BUTTONDOWN: {
for (int i = 1; i < MUI_SB_PART_COUNT; ++i) {
if (c2_rect_contains_pt(&parts[i], &ev->mouse.where)) {
c->flags.hit_part = i;
sb->drag_offset.x =
ev->mouse.where.x - parts[i].l;
sb->drag_offset.y =
ev->mouse.where.y - parts[i].t;
sb->saved_value = c->value;
break;
}
}
int part = c->flags.hit_part % MUI_SB_PART_COUNT;
switch (part) {
case MUI_SB_PART_DOWN:
case MUI_SB_PART_UP: {
_mui_scrollbar_scroll(c,
part == MUI_SB_PART_UP ? -30 : 30);
} break;
case MUI_SB_PART_PAGEUP:
case MUI_SB_PART_PAGEDOWN: {
int32_t offset = sb->visible;
_mui_scrollbar_scroll(c,
part == MUI_SB_PART_PAGEUP ?
-offset : offset);
} break;
case MUI_SB_PART_THUMB:
mui_control_inval(c);
break;
}
// printf("%s hit part %d\n", __func__, c->flags.hit_part);
} break;
case MUI_EVENT_DRAG: {
if (!c->flags.hit_part)
break;
int part = c->flags.hit_part % MUI_SB_PART_COUNT;
c2_rect_t test_rect = parts[part];
if (part == MUI_SB_PART_THUMB)
c2_rect_inset(&test_rect, -60, -60);
if (c2_rect_contains_pt(&test_rect, &ev->mouse.where)) {
c->flags.hit_part = part;
switch (part) {
case MUI_SB_PART_THUMB: {
c2_rect_t nt = parts[part];
c2_rect_offset(&nt, 0,
-(nt.t + parts[MUI_SB_PART_UP].b) +
ev->mouse.where.y - sb->drag_offset.y);
// printf("%s thumb %s\n", __func__, c2_rect_as_str(&nt));
if (nt.t < 0)
c2_rect_offset(&nt, 0, -nt.t);
if (nt.b > parts[MUI_SB_PART_DOWN].t)
c2_rect_offset(&nt, 0, parts[MUI_SB_PART_DOWN].t - nt.b);
int max_pixels = parts[MUI_SB_PART_DOWN].t -
parts[MUI_SB_PART_UP].b -
c2_rect_height(&nt);
uint32_t nv = nt.t * (sb->max - sb->visible) / max_pixels;
if (nv > (sb->max - sb->visible))
nv = sb->max - sb->visible;
c->value = nv;
// printf("v is %d vs %d max %d = %d new val %d\n",
// nt.t, max_pixels, sb->max, nv,
// mui_control_get_value(c));
mui_control_inval(c);
mui_control_action(c, MUI_CONTROL_ACTION_VALUE_CHANGED, NULL);
} break;
}
} else {
c->flags.hit_part = part + MUI_SB_PART_COUNT;
if (part == MUI_SB_PART_THUMB) {
c->value = sb->saved_value;
mui_control_inval(c);
mui_control_action(c, MUI_CONTROL_ACTION_VALUE_CHANGED, NULL);
}
}
} break;
case MUI_EVENT_BUTTONUP: {
if (!c->flags.hit_part)
break;
mui_control_inval(c);
c->flags.hit_part = 0;
} break;
}
return true;
}
static bool
mui_cdef_scrollbar(
struct mui_control_t * c,
uint8_t what,
void * param)
{
switch (what) {
case MUI_CDEF_INIT:
break;
case MUI_CDEF_DISPOSE:
break;
case MUI_CDEF_DRAW: {
mui_drawable_t * dr = param;
mui_scrollbar_draw(c->win, c, dr);
} break;
case MUI_CDEF_EVENT: {
// printf("%s event\n", __func__);
mui_event_t *ev = param;
switch (ev->type) {
case MUI_EVENT_BUTTONUP:
case MUI_EVENT_DRAG:
case MUI_EVENT_BUTTONDOWN: {
return mui_scrollbar_mouse(c, ev);
} break;
}
} break;
}
return false;
}
uint32_t
mui_scrollbar_get_max(
mui_control_t * c)
{
mui_scrollbar_control_t *sb = (mui_scrollbar_control_t *)c;
return sb->max;
}
void
mui_scrollbar_set_max(
mui_control_t * c,
uint32_t max)
{
mui_scrollbar_control_t *sb = (mui_scrollbar_control_t *)c;
sb->max = max;
mui_control_inval(c);
}
void
mui_scrollbar_set_page(
mui_control_t * c,
uint32_t page)
{
mui_scrollbar_control_t *sb = (mui_scrollbar_control_t *)c;
sb->visible = page;
mui_control_inval(c);
}
mui_control_t *
mui_scrollbar_new(
mui_window_t * win,
c2_rect_t frame,
uint32_t uid )
{
return mui_control_new(
win, MUI_CONTROL_SCROLLBAR, mui_cdef_scrollbar,
frame, NULL, uid,
sizeof(mui_scrollbar_control_t));
}

318
libmui/mui/mui_controls.c Normal file
View File

@ -0,0 +1,318 @@
/*
* mui_controls.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT] = {
[MUI_CONTROL_STATE_NORMAL] = {
.fill = MUI_COLOR(0xeeeeeeff),
.frame = MUI_COLOR(0x000000ff),
.text = MUI_COLOR(0x000000ff),
},
[MUI_CONTROL_STATE_HOVER] = {
.fill = MUI_COLOR(0xaaaaaaff),
.frame = MUI_COLOR(0x000000ff),
.text = MUI_COLOR(0x0000ffff),
},
[MUI_CONTROL_STATE_CLICKED] = {
.fill = MUI_COLOR(0x777777ff),
.frame = MUI_COLOR(0x000000ff),
.text = MUI_COLOR(0xffffffff),
},
[MUI_CONTROL_STATE_DISABLED] = {
.fill = MUI_COLOR(0xeeeeeeff),
.frame = MUI_COLOR(0x666666ff),
.text = MUI_COLOR(0xccccccff),
},
};
void
mui_control_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
if (!c)
return;
if (c->cdef)
c->cdef(c, MUI_CDEF_DRAW, dr);
}
mui_control_t *
mui_control_new(
mui_window_t * win,
uint8_t type,
mui_cdef_p cdef,
c2_rect_t frame,
const char * title,
uint32_t uid,
uint32_t instance_size )
{
if (!win)
return NULL;
mui_control_t *c = calloc(1, instance_size >= sizeof(*c) ?
instance_size : sizeof(*c));
c->type = type;
c->cdef = cdef;
c->frame = frame;
c->title = title ? strdup(title) : NULL;
c->win = win;
c->uid = uid;
STAILQ_INIT(&c->actions);
TAILQ_INSERT_TAIL(&win->controls, c, self);
if (c->cdef)
c->cdef(c, MUI_CDEF_INIT, NULL);
return c;
}
void
_mui_control_free(
mui_control_t * c )
{
if (!c)
return;
if (c->title)
free(c->title);
c->title = NULL;
if (c->cdef)
c->cdef(c, MUI_CDEF_DISPOSE, NULL);
free(c);
}
void
mui_control_dispose(
mui_control_t * c )
{
if (!c)
return;
if (c->flags.zombie) {
printf("%s: DOUBLE delete %s\n", __func__, c->title);
return;
}
TAILQ_REMOVE(&c->win->controls, c, self);
if (c->win->flags.zombie || c->win->ui->action_active) {
c->flags.zombie = true;
TAILQ_INSERT_TAIL(&c->win->zombies, c, self);
} else
_mui_control_free(c);
}
uint32_t
mui_control_get_type(
mui_control_t * c )
{
if (!c)
return 0;
return c->type;
}
uint32_t
mui_control_get_uid(
mui_control_t * c )
{
if (!c)
return 0;
return c->uid;
}
mui_control_t *
mui_control_locate(
mui_window_t * win,
c2_pt_t pt )
{
if (!win)
return NULL;
mui_control_t * c;
TAILQ_FOREACH(c, &win->controls, self) {
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
if (c2_rect_contains_pt(&f, &pt))
return c;
}
return NULL;
}
static mui_time_t
_mui_control_highlight_timer_cb(
struct mui_t * mui,
mui_time_t now,
void * param)
{
mui_control_t * c = param;
printf("%s: %s\n", __func__, c->title);
mui_control_set_state(c, MUI_CONTROL_STATE_NORMAL);
if (c->cdef)
c->cdef(c, MUI_CDEF_SELECT, NULL);
mui_control_action(c, MUI_CONTROL_ACTION_SELECT, NULL);
return 0;
}
int32_t
mui_control_get_value(
mui_control_t * c)
{
if (!c)
return 0;
return c->value;
}
int32_t
mui_control_set_value(
mui_control_t * c,
int32_t value)
{
if (!c)
return 0;
if (c->cdef && c->cdef(c, MUI_CDEF_SET_VALUE, &value))
return c->value;
if (value != (int)c->value)
mui_control_inval(c);
c->value = value;
return c->value;
}
bool
mui_control_event(
mui_control_t * c,
mui_event_t * ev )
{
if (!c)
return false;
bool res = false;
res = c->cdef && c->cdef(c, MUI_CDEF_EVENT, ev);
if (res || !c->key_equ.key)
return res;
switch (ev->type) {
case MUI_EVENT_KEYDOWN:
if (c->state != MUI_CONTROL_STATE_DISABLED &&
mui_event_match_key(ev, c->key_equ)) {
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
mui_timer_register(
c->win->ui, _mui_control_highlight_timer_cb,
c, MUI_TIME_SECOND / 10);
res = true;
}
break;
}
return res;
}
void
mui_control_inval(
mui_control_t * c )
{
if (!c)
return;
mui_window_inval(c->win, &c->frame);
}
void
mui_control_set_state(
mui_control_t * c,
uint32_t state )
{
if (!c)
return;
if (c->cdef && c->cdef(c, MUI_CDEF_SET_STATE, &state))
return;
if (c->state == state)
return;
c->state = state;
mui_control_inval(c);
}
uint32_t
mui_control_get_state(
mui_control_t * c )
{
if (!c)
return 0;
return c->state;
}
const char *
mui_control_get_title(
mui_control_t * c )
{
if (!c)
return NULL;
return c->title;
}
void
mui_control_set_title(
mui_control_t * c,
const char * text )
{
if (!c)
return;
if (c->cdef && c->cdef(c, MUI_CDEF_SET_TITLE, (void*)text))
return;
if (text && c->title && !strcmp(text, c->title))
return;
if (c->title)
free(c->title);
c->title = text ? strdup(text) : NULL;
mui_control_inval(c);
}
void
mui_control_action(
mui_control_t * c,
uint32_t what,
void * param )
{
if (!c)
return;
c->win->ui->action_active++;
mui_action_t *a;
STAILQ_FOREACH(a, &c->actions, self) {
if (!a->control_cb)
continue;
a->control_cb(c, a->cb_param, what, param);
}
c->win->ui->action_active--;
}
void
mui_control_set_action(
mui_control_t * c,
mui_control_action_p cb,
void * param )
{
if (!c)
return;
mui_action_t *a = calloc(1, sizeof(*a));
a->control_cb = cb;
a->cb_param = param;
STAILQ_INSERT_TAIL(&c->actions, a, self);
}
mui_control_t *
mui_control_get_by_id(
mui_window_t * win,
uint32_t uid )
{
if (!win)
return NULL;
mui_control_t *c;
TAILQ_FOREACH(c, &win->controls, self) {
if (c->uid == uid)
return c;
}
return NULL;
}

280
libmui/mui/mui_drawable.c Normal file
View File

@ -0,0 +1,280 @@
/*
* mui_drawable.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pixman.h>
#include "mui.h"
#include "cg.h"
IMPLEMENT_C_ARRAY(mui_clip_stack);
void
mui_drawable_clear(
mui_drawable_t * dr)
{
if (!dr)
return;
if (dr->cg)
cg_destroy(dr->cg);
dr->cg = NULL;
if (dr->cg_surface)
cg_surface_destroy(dr->cg_surface);
dr->cg_surface = NULL;
if (dr->pixman)
pixman_image_unref(dr->pixman);
dr->pixman = NULL;
for (int i = 0; i < (int)dr->clip.count; i++)
pixman_region32_fini(&dr->clip.e[i]);
mui_clip_stack_clear(&dr->clip);
dr->_pix_hash = NULL;
}
void
mui_drawable_dispose(
mui_drawable_t * dr)
{
if (!dr)
return;
mui_drawable_clear(dr);
mui_clip_stack_free(&dr->clip);
// free(dr);
}
static struct cg_ctx_t *
_cg_updated_clip(
mui_drawable_t * dr)
{
if (!dr->cg_clip_dirty)
return dr->cg;
dr->cg_clip_dirty = 0;
cg_reset_clip(dr->cg);
if (!dr->clip.count)
return dr->cg;
int cnt = 0;
pixman_box32_t *r = pixman_region32_rectangles(
&dr->clip.e[dr->clip.count-1], &cnt);
for (int i = 0; i < cnt; i++) {
cg_rectangle(dr->cg, r[i].x1, r[i].y1,
r[i].x2 - r[i].x1, r[i].y2 - r[i].y1);
}
cg_clip(dr->cg);
return dr->cg;
}
struct cg_ctx_t *
mui_drawable_get_cg(
mui_drawable_t * dr)
{
if (!dr)
return NULL;
if (dr->cg) {
// in case the pix structure had changed...
dr->cg_surface->stride = dr->pix.row_bytes;
dr->cg_surface->pixels = dr->pix.pixels;
dr->cg_surface->width = dr->pix.size.x;
dr->cg_surface->height = dr->pix.size.y;
return _cg_updated_clip(dr);
}
dr->cg_surface = cg_surface_create_for_data(
dr->pix.size.x, dr->pix.size.y,
dr->pix.pixels);
dr->cg_surface->stride = dr->pix.row_bytes;
dr->cg = cg_create(dr->cg_surface);
return _cg_updated_clip(dr);
}
/*
* Update clip to the 'latest' one from the stack of clips.
*/
static union pixman_image *
_pixman_updated_clip(
mui_drawable_t * dr)
{
if (!dr->pixman_clip_dirty)
return dr->pixman;
dr->pixman_clip_dirty = 0;
if (dr->clip.count)
pixman_image_set_clip_region32(
dr->pixman, &dr->clip.e[dr->clip.count-1]);
else
pixman_image_set_clip_region32(
dr->pixman, NULL);
return dr->pixman;
}
union pixman_image *
mui_pixmap_make_pixman(
mui_pixmap_t * pix)
{
pixman_format_code_t k = PIXMAN_a8r8g8b8;
switch (pix->bpp) {
case 8: k = PIXMAN_a8; break;
case 16: k = PIXMAN_r5g6b5; break;
case 24: k = PIXMAN_r8g8b8; break;
case 32: k = PIXMAN_a8r8g8b8; break;
}
union pixman_image * res = pixman_image_create_bits_no_clear(
k, pix->size.x, pix->size.y,
(uint32_t*)pix->pixels, pix->row_bytes);
return res;
}
union pixman_image *
mui_drawable_get_pixman(
mui_drawable_t * dr)
{
if (!dr)
return NULL;
void * _hash = dr->pix.pixels + dr->pix.size.y * dr->pix.row_bytes;
if (dr->_pix_hash != _hash) {
dr->_pix_hash = _hash;
dr->_pix_hash = dr->pix.pixels + dr->pix.size.y * dr->pix.row_bytes;
if (dr->pixman)
pixman_image_unref(dr->pixman);
dr->pixman = NULL;
}
// return _pixman_updated_clip(dr);
if (dr->pixman)
return _pixman_updated_clip(dr);
dr->pixman = mui_pixmap_make_pixman(&dr->pix);
dr->pixman_clip_dirty = 1;
return _pixman_updated_clip(dr);
}
pixman_region32_t *
mui_drawable_clip_get(
mui_drawable_t * dr)
{
if (!dr || !dr->clip.count)
return NULL;
return &dr->clip.e[dr->clip.count-1];
}
void
mui_drawable_set_clip(
mui_drawable_t * dr,
c2_rect_array_p clip )
{
if (!dr)
return;
for (int i = 0; i < (int)dr->clip.count; i++)
pixman_region32_fini(&dr->clip.e[i]);
mui_clip_stack_clear(&dr->clip);
if (clip && clip->count) {
pixman_region32_t r = {};
pixman_region32_init_rects(&r,
(pixman_box32_t*)clip->e, clip->count);
// intersect the whole lot with the whole pixmap boundary
pixman_region32_t rg = {};
pixman_region32_intersect_rect(&rg, &r,
0, 0, dr->pix.size.x, dr->pix.size.y);
pixman_region32_fini(&r);
mui_clip_stack_add(&dr->clip, rg);
}
dr->pixman_clip_dirty = 1;
dr->cg_clip_dirty = 1;
}
int
mui_drawable_clip_intersects(
mui_drawable_t * dr,
c2_rect_p r )
{
if (!dr || !r)
return 0;
if (!dr->clip.count)
return 1;
// conveniently, c2_rect_t is the same as pixman_box_32_t
pixman_region_overlap_t o = pixman_region32_contains_rectangle(
&dr->clip.e[dr->clip.count-1], (pixman_box32_t*)r);
return o == PIXMAN_REGION_OUT ? 0 :
o == PIXMAN_REGION_IN ? 1 : -1;
}
int
mui_drawable_clip_push(
mui_drawable_t * dr,
c2_rect_p r )
{
if (!dr || !r)
return 0;
pixman_region32_t rg = {};
if (dr->clip.count == 0) {
pixman_region32_init_rect(&rg,
r->l, r->t, c2_rect_width(r), c2_rect_height(r));
} else {
pixman_region32_intersect_rect(&rg, &dr->clip.e[dr->clip.count-1],
r->l, r->t, c2_rect_width(r), c2_rect_height(r));
}
mui_clip_stack_add(&dr->clip, rg);
dr->pixman_clip_dirty = 1;
dr->cg_clip_dirty = 1;
return dr->clip.count;
}
int
mui_drawable_clip_push_region(
mui_drawable_t * dr,
pixman_region32_t * rgn )
{
if (!dr || !rgn)
return 0;
pixman_region32_t rg = {};
if (dr->clip.count == 0) {
pixman_region32_copy(&rg, rgn);
} else {
pixman_region32_intersect(&rg, &dr->clip.e[dr->clip.count-1], rgn);
}
mui_clip_stack_add(&dr->clip, rg);
dr->pixman_clip_dirty = 1;
dr->cg_clip_dirty = 1;
return dr->clip.count;
}
int
mui_drawable_clip_substract_region(
mui_drawable_t * dr,
pixman_region32_t * rgn )
{
if (!dr || !rgn)
return 0;
pixman_region32_t rg = {};
if (dr->clip.count != 0) {
pixman_region32_subtract(&rg, &dr->clip.e[dr->clip.count-1], rgn);
}
mui_clip_stack_add(&dr->clip, rg);
dr->pixman_clip_dirty = 1;
dr->cg_clip_dirty = 1;
return dr->clip.count;
}
void
mui_drawable_clip_pop(
mui_drawable_t * dr )
{
if (!dr || !dr->clip.count)
return;
pixman_region32_fini(&dr->clip.e[dr->clip.count-1]);
dr->clip.count--;
dr->pixman_clip_dirty = 1;
dr->cg_clip_dirty = 1;
}

351
libmui/mui/mui_font.c Normal file
View File

@ -0,0 +1,351 @@
/*
* mui_font.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#define STB_TRUETYPE_IMPLEMENTATION
#define STB_TTC_IMPLEMENTATION
#include "stb_ttc.h"
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mui_
#include "incbin.h"
INCBIN(main_font, "fonts/Charcoal_mui.ttf");
INCBIN(icon_font, "fonts/typicon.ttf");
INCBIN(dingbat_font, "fonts/Dingbat.ttf");
#include "mui.h"
mui_font_t *
mui_font_find(
mui_t *ui,
const char *name)
{
mui_font_t *f;
TAILQ_FOREACH(f, &ui->fonts, self) {
if (!strcmp(f->name, name))
return f;
}
return NULL;
}
static void
_mui_font_pixman_prep(
mui_font_t *f)
{
f->font.pix.bpp = 8;
f->font.pix.size.x = f->ttc.p_stride;
f->font.pix.size.y = f->ttc.p_height;
f->font.pix.row_bytes = f->ttc.p_stride;
f->font.pix.pixels = f->ttc.pixels;
}
mui_font_t *
mui_font_from_mem(
mui_t *ui,
const char *name,
unsigned int size,
const void *font_data,
unsigned int font_size )
{
mui_font_t *f = calloc(1, sizeof(*f));
f->name = strdup(name);
f->size = size;
stb_ttc_LoadFont(&f->ttc, font_data, font_size);
TAILQ_INSERT_TAIL(&ui->fonts, f, self);
printf("%s: Loaded font %s:%d\n", __func__, name, size);
return f;
}
void
mui_font_init(
mui_t *ui)
{
printf("%s: Loading fonts\n", __func__);
mui_font_from_mem(ui, "main", 28,
mui_main_font_data, mui_main_font_size);
mui_font_from_mem(ui, "icon_large", 96,
mui_icon_font_data, mui_icon_font_size);
mui_font_from_mem(ui, "icon_small", 30,
mui_icon_font_data, mui_icon_font_size);
}
void
mui_font_dispose(
mui_t *ui)
{
mui_font_t *f;
while ((f = TAILQ_FIRST(&ui->fonts))) {
TAILQ_REMOVE(&ui->fonts, f, self);
stb_ttc_Free(&f->ttc);
free(f->name);
free(f);
}
}
int
mui_font_text_measure(
mui_font_t *font,
const char *text,
stb_ttc_measure *m )
{
struct stb_ttc_info * ttc = &font->ttc;
float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size);
int w = stb_ttc_MeasureText(ttc, scale, text, m);
return w;
}
void
mui_font_text_draw(
mui_font_t *font,
mui_drawable_t *dr,
c2_pt_t where,
const char *text,
unsigned int text_len,
mui_color_t color)
{
struct stb_ttc_info * ttc = &font->ttc;
unsigned int state = 0;
float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size);
double xpos = 0;
unsigned int last = 0;
unsigned int cp = 0;
if (!text_len)
text_len = strlen(text);
mui_drawable_t * src = &font->font;
mui_drawable_t * dst = dr;
pixman_color_t pc = PIXMAN_COLOR(color);
pixman_image_t * fill = pixman_image_create_solid_fill(&pc);
where.y += font->ttc.ascent * scale;
for (unsigned int ch = 0; text[ch] && ch < text_len; ch++) {
if (stb_ttc__UTF8_Decode(&state, &cp, text[ch]) != UTF8_ACCEPT)
continue;
if (last) {
int kern = scale * stb_ttc__CodepointsGetKerning(ttc, last, cp);
xpos += kern;
}
last = cp;
int gl = stb_ttc__CodepointGetGlyph(ttc, cp);
if (gl == -1)
continue;
stb_ttc_g *gc = stb_ttc__ScaledGlyphGetCache(ttc, gl, scale);
if (!gc)
continue;
if (gc->p_y == (unsigned short) -1)
stb_ttc__ScaledGlyphRenderToCache(ttc, gc);
// int pxpos = gc->x0 + ((xpos + gc->lsb) * scale);
int pxpos = where.x + gc->x0 + ((xpos + 0) * scale);
// if (gc->lsb)
// printf("glyph %3d : %04x:%c lsb %d\n", ch, cp, cp < 32 ? '.' : cp, gc->lsb);
int ph = gc->y1 - gc->y0;
int pw = gc->x1 - gc->x0;
_mui_font_pixman_prep(font);
pixman_image_composite32(
PIXMAN_OP_OVER,
fill,
mui_drawable_get_pixman(src),
mui_drawable_get_pixman(dst),
0, 0, gc->p_x, gc->p_y,
pxpos, where.y + gc->y0, pw, ph);
xpos += gc->advance;
}
pixman_image_unref(fill);
}
IMPLEMENT_C_ARRAY(mui_glyph_array);
IMPLEMENT_C_ARRAY(mui_glyph_line_array);
void
mui_font_measure(
mui_font_t *font,
c2_rect_t bbox,
const char *text,
unsigned int text_len,
mui_glyph_line_array_t *lines,
uint16_t flags)
{
struct stb_ttc_info * ttc = &font->ttc;
unsigned int state = 0;
float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size);
unsigned int last = 0;
unsigned int cp = 0;
if (!text_len)
text_len = strlen(text);
c2_pt_t where = {};
unsigned int ch = 0;
int wrap_chi = 0;
int wrap_w = 0;
int wrap_count = 0;
do {
where.y += font->ttc.ascent * scale;
const mui_glyph_array_t zero = {};
mui_glyph_line_array_push(lines, zero);
mui_glyph_array_t * line = &lines->e[lines->count - 1];
line->x = 0;
line->y = where.y;
line->w = 0;
wrap_chi = ch;
wrap_w = 0;
wrap_count = 0;
for (;text[ch]; ch++) {
if (stb_ttc__UTF8_Decode(&state, &cp, text[ch]) != UTF8_ACCEPT)
continue;
if (last) {
int kern = scale * stb_ttc__CodepointsGetKerning(ttc, last, cp);
line->w += kern;
}
last = cp;
// printf("glyph %3d : %04x:%c\n", ch, cp, cp < 32 ? '.' : cp);
if (cp == '\n') {
ch++;
break;
}
if (isspace(cp) || ispunct(cp)) {
wrap_chi = ch;
wrap_w = line->w;
wrap_count = line->count;
}
int gl = stb_ttc__CodepointGetGlyph(ttc, cp);
if (gl == -1)
continue;
stb_ttc_g *gc = stb_ttc__ScaledGlyphGetCache(ttc, gl, scale);
if (!gc)
continue;
if (gc->p_y == (unsigned short) -1)
stb_ttc__ScaledGlyphRenderToCache(ttc, gc);
if (((line->w + gc->advance) * scale) > c2_rect_width(&bbox)) {
if (wrap_count) {
ch = wrap_chi + 1;
line->count = wrap_count;
line->w = wrap_w;
}
break;
}
line->w += gc->advance;
mui_glyph_array_push(line, gc);
};
} while (text[ch] && ch < text_len);
int bh = 0;
for (int i = 0; i < (int)lines->count; i++) {
mui_glyph_array_t * line = &lines->e[i];
bh = line->y - (font->ttc.descent * scale);
line->w *= scale;
// printf(" line %d y %3d size %d width %d\n", i,
// line->y, line->count, line->w);
}
// printf("box height is %d/%d\n", bh, c2_rect_height(&bbox));
int ydiff = 0;
if (flags & MUI_TEXT_ALIGN_MIDDLE) {
ydiff = (c2_rect_height(&bbox) - bh) / 2;
} else if (flags & MUI_TEXT_ALIGN_BOTTOM) {
ydiff = c2_rect_height(&bbox) - bh;
}
for (int i = 0; i < (int)lines->count; i++) {
mui_glyph_array_t * line = &lines->e[i];
line->y += ydiff;
if (flags & MUI_TEXT_ALIGN_RIGHT) {
line->x = c2_rect_width(&bbox) - line->w;
} else if (flags & MUI_TEXT_ALIGN_CENTER) {
line->x = (c2_rect_width(&bbox) - line->w) / 2;
}
}
}
void
mui_font_measure_clear(
mui_glyph_line_array_t *lines)
{
if (!lines)
return;
for (int i = 0; i < (int)lines->count; i++) {
mui_glyph_array_t * line = &lines->e[i];
mui_glyph_array_free(line);
}
mui_glyph_line_array_free(lines);
}
void
mui_font_measure_draw(
mui_font_t *font,
mui_drawable_t *dr,
c2_rect_t bbox,
mui_glyph_line_array_t *lines,
mui_color_t color,
uint16_t flags)
{
pixman_color_t pc = PIXMAN_COLOR(color);
pixman_image_t * fill = pixman_image_create_solid_fill(&pc);
struct stb_ttc_info * ttc = &font->ttc;
float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size);
mui_drawable_t * src = &font->font;
mui_drawable_t * dst = dr;
// all glyphs we need were loaded, update the pixman texture
_mui_font_pixman_prep(font);
for (int li = 0; li < (int)lines->count; li++) {
mui_glyph_array_t * line = &lines->e[li];
int xpos = 0;//where.x / scale;
for (int ci = 0; ci < (int)line->count; ci++) {
stb_ttc_g *gc = line->e[ci];
// int pxpos = gc->x0 + ((xpos + gc->lsb) * scale);
int pxpos = gc->x0 + ((xpos + 0) * scale);
int ph = gc->y1 - gc->y0;
int pw = gc->x1 - gc->x0;
pixman_image_composite32(
PIXMAN_OP_OVER,
fill,
mui_drawable_get_pixman(src),
mui_drawable_get_pixman(dst),
0, 0, gc->p_x, gc->p_y,
bbox.l + line->x + pxpos,
bbox.t + line->y + gc->y0, pw, ph);
xpos += gc->advance;
}
}
pixman_image_unref(fill);
}
void
mui_font_textbox(
mui_font_t *font,
mui_drawable_t *dr,
c2_rect_t bbox,
const char *text,
unsigned int text_len,
mui_color_t color,
uint16_t flags)
{
mui_glyph_line_array_t lines = {};
if (!text_len)
text_len = strlen(text);
mui_font_measure(font, bbox, text, text_len, &lines, flags);
mui_font_measure_draw(font, dr, bbox, &lines, color, flags);
mui_font_measure_clear(&lines);
}

916
libmui/mui/mui_menus.c Normal file
View File

@ -0,0 +1,916 @@
/*
* mui_menus.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
#include "mui_priv.h"
#include "cg.h"
#ifndef D
#define D(_x) //_x
#endif
/* These are *window action* -- parameter 'target' is a mui_menu_t* */
enum mui_menu_action_e {
MUI_MENU_ACTION_NONE = 0,
MUI_MENU_ACTION_OPEN = FCC('m','e','n','o'),
MUI_MENU_ACTION_CLOSE = FCC('m','e','n','c'),
MUI_MENU_ACTION_SELECT = FCC('m','e','n','s'),
// BEFORE a menu/submenu get opened/created
MUI_MENU_ACTION_PREPARE = FCC('m','e','p','r'),
};
struct mui_menu_control_t;
struct mui_menubar_t;
typedef struct mui_menu_t {
mui_window_t win;
unsigned int click_inside : 1,
drag_ev : 1,
closing: 1, // prevent double-delete
timer_call_count : 2; // used by mui_menu_close_timer_cb
mui_control_t * highlighted;
mui_time_t sub_open_stamp;
// currently open menu, if any
struct mui_menu_control_t * sub;
struct mui_menubar_t * menubar;
} mui_menu_t;
typedef struct mui_menubar_t {
mui_window_t win;
unsigned int click_inside : 1,
drag_ev : 1,
was_highlighted : 1,
timer_call_count : 2; // used by mui_menu_close_timer_cb
// currently open menu title
struct mui_menu_control_t * selected_title;
// keep track of the menus, and their own submenus as they are being opened
// this is to keep track of the 'hierarchy' of menus, so that we can close
// them all when the user clicks outside of them, or release the mouse.
mui_menu_t * open[8];
int open_count;
bool delayed_closing;
} mui_menubar_t;
/* These are *control action* -- parameter is a menu title or popup,
* parameter is a mui_menu_control_t*
*/
enum mui_menu_control_action_e {
MUI_MENU_CONTROL_ACTION_NONE = 0,
// called *before* the window is opened, you can change the items state/list
MUI_MENU_CONTROL_ACTION_PREPARE = FCC('m','c','p','r'),
// when an item is selected.
MUI_MENU_CONTROL_ACTION_SELECT = FCC('m','c','s','l'),
};
static mui_window_t *
_mui_menu_create(
mui_t * ui,
mui_menubar_t * mbar,
c2_pt_t origin,
mui_menu_item_t* items );
static void
mui_menu_close(
mui_window_t * win );
static bool
mui_wdef_menu(
struct mui_window_t * win,
uint8_t what,
void * param);
static bool
mui_wdef_menubar(
struct mui_window_t * win,
uint8_t what,
void * param);
static bool
mui_cdef_popup(
struct mui_control_t * c,
uint8_t what,
void * param);
#define MUI_MENU_CLOSE_BLINK_DELAY (MUI_TIME_SECOND / 20)
// The menu bar title is a control, reset it's state to normal, and close
// any open menus (and their submenus, if any)
static bool
_mui_menubar_close_menu(
mui_menubar_t *mbar )
{
if (mbar->delayed_closing)
return false;
mbar->click_inside = false;
mui_control_set_state((mui_control_t*)mbar->selected_title, 0);
if (mbar->selected_title)
mbar->selected_title->menu_window = NULL;
if (!mbar->open_count)
return false;
mbar->selected_title = NULL;
for (int i = 0; i < mbar->open_count; i++) {
mui_menu_close(&mbar->open[i]->win);
mbar->open[i] = NULL;
}
mbar->open_count = 0;
return true;
}
// close the submenu from a hierarchical menu item
static bool
_mui_menu_close_submenu(
mui_menu_t * menu )
{
mui_menu_control_t * sub = menu->sub;
if (!sub)
return false;
menu->sub = NULL;
mui_control_set_state((mui_control_t*)sub, 0);
if (sub->menu_window) {
mui_menu_close(sub->menu_window);
}
sub->menu_window = NULL;
return true;
}
/*
* This does the blinking of the selected item, and closes the menu
*/
static mui_time_t
mui_menu_close_timer_cb(
struct mui_t * mui,
mui_time_t now,
void * param)
{
mui_menu_t * menu = param;
if (!menu->highlighted) {
printf("%s: no selected item, closing\n", __func__);
mui_window_dispose(&menu->win);
return 0;
}
menu->timer_call_count++;
mui_control_set_state(menu->highlighted,
menu->highlighted->state == MUI_CONTROL_STATE_CLICKED ?
MUI_CONTROL_STATE_NORMAL : MUI_CONTROL_STATE_CLICKED);
if (menu->timer_call_count == 3) {
// we are done!
mui_menuitem_control_t * item = (mui_menuitem_control_t*)menu->highlighted;
mui_window_action(&menu->win, MUI_MENU_ACTION_SELECT, &item->item);
// mui_menu_close_all(&menu->win);
if (menu->menubar) {
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar;
mui_window_action(&mbar->win,
MUI_MENUBAR_ACTION_SELECT, &item->item);
mbar->delayed_closing = false;
_mui_menubar_close_menu((mui_menubar_t*)menu->menubar);
} else
mui_menu_close(&menu->win);
return 0;
}
return MUI_MENU_CLOSE_BLINK_DELAY;
}
/*
* This restore the menubar to normal after a keystroke/menu equivalent was hit
*/
static mui_time_t
mui_menubar_unhighlight_cb(
struct mui_t * mui,
mui_time_t now,
void * param)
{
mui_menubar_t * mbar = param;
mui_menubar_highlight(&mbar->win, false);
return 0;
}
static int
mui_menu_action_cb(
mui_window_t * win,
void * cb_param,
uint32_t what,
void * param) // menu item here
{
mui_menubar_t * mbar = cb_param;
D(printf("%s %s %4.4s\n", __func__, mbar->win.title, (char*)&what);)
switch (what) {
/*
* If a submenu is created, add ourselves as a callback to it as well,
* so we get notified when it is closed or an item is selected
*/
case MUI_MENU_ACTION_OPEN: {
mui_menu_t * submenu = param;
mui_window_set_action(&submenu->win, mui_menu_action_cb, mbar);
} break;
case MUI_MENU_ACTION_SELECT: {
mui_menu_item_t * item = param;
D(printf(" %s calling menubar action: %s\n", __func__, item->title);)
// this is typically application code!
mui_window_action(&mbar->win, MUI_MENUBAR_ACTION_SELECT, item);
} break;
case MUI_WINDOW_ACTION_CLOSE: {
D(mui_menu_t * menu = (mui_menu_t*)win;)
D(printf("%s closing %s\n", __func__, menu->win.title);)
// mui_menu_close_all(&mbar->win);
} break;
}
return 0;
}
static int
mui_submenu_action_cb(
mui_window_t * win,
void * cb_param,
uint32_t what,
void * param) // menu item here
{
mui_menu_t * menu = cb_param;
D(printf("%s %s %4.4s\n", __func__, menu->win.title, (char*)&what);)
switch (what) {
case MUI_MENU_ACTION_SELECT: {
mui_menu_item_t * item = param;
D(printf(" %s calling menubar action: %s\n", __func__, item->title);)
// this is typically application code!
mui_window_action(&menu->win, MUI_MENUBAR_ACTION_SELECT, item);
} break;
}
return 0;
}
static bool
mui_menubar_handle_mouse(
mui_menubar_t * mbar,
mui_event_t * ev )
{
mui_window_t * win = &mbar->win;
bool inside = c2_rect_contains_pt(&win->frame, &ev->mouse.where);
mui_control_t * c = inside ? mui_control_locate(win, ev->mouse.where) : NULL;
switch (ev->type) {
case MUI_EVENT_BUTTONUP: {
D(printf("%s up drag %d click in:%d high:%d was:%d\n", __func__,
mbar->drag_ev, mbar->click_inside,
mbar->selected_title ? 1 : 0, mbar->was_highlighted);)
if (mbar->drag_ev == 0 && mbar->click_inside) {
if (mbar->was_highlighted) {
return _mui_menubar_close_menu(mbar);
} else
mbar->click_inside = false;
return true;
} else if (mbar->drag_ev == 0 && !mbar->click_inside) {
return false;
}
return _mui_menubar_close_menu(mbar);
} break;
case MUI_EVENT_DRAG:
if (!mbar->click_inside)
break;
mbar->drag_ev = 1;
// fallthrough
case MUI_EVENT_BUTTONDOWN: {
// printf("%s button %d\n", __func__, ev->mouse.button);
if (ev->mouse.button > 1)
break;
// save click, for detecting click+drag vs sticky menus
if (ev->type == MUI_EVENT_BUTTONDOWN) {
D(printf("%s click inside %d\n", __func__, inside);)
mbar->drag_ev = 0;
mbar->click_inside = inside;
mbar->was_highlighted = mbar->selected_title != NULL;
}
if (c && mui_control_get_state(c) != MUI_CONTROL_STATE_DISABLED) {
if (mbar->selected_title &&
c != (mui_control_t*)mbar->selected_title)
_mui_menubar_close_menu(mbar);
mbar->click_inside = true;
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
mui_menu_control_t *title = (mui_menu_control_t*)c;
mbar->selected_title = title;
if (mui_control_get_type(c) == MUI_CONTROL_MENUTITLE) {
if (title->menu_window == NULL) {
title->menu_window = _mui_menu_create(
win->ui, mbar, C2_PT(c->frame.l, c->frame.b),
title->menu.e);
}
}
return true;
}
} break;
}
return false;
}
/*
* Look into a menu item list for a key_equ match -- also handle
* recursive submenus, return true the match itself, if found
*/
static mui_menu_item_t *
mui_menu_handle_keydown(
mui_menu_item_t * items,
mui_event_t *ev )
{
// D(printf("%s\n", __func__);)
for (int ii = 0; items[ii].title; ii++) {
mui_menu_item_t * item = &items[ii];
if (item->submenu) {
mui_menu_item_t * sub = mui_menu_handle_keydown(item->submenu, ev);
if (sub)
return sub;
continue;
}
if (!item->key_equ.value || item->disabled)
continue;
if (mui_event_match_key(ev, item->key_equ)) {
// printf("%s KEY MATCH %s\n", __func__, item->title);
return item;
}
}
return NULL;
}
static bool
mui_menubar_handle_keydown(
mui_menubar_t * mbar,
mui_event_t *ev )
{
// iterate all the menus, check any with a key_equ that is
// non-zero, if would, highlight that menu (temporarily)
// and call the action function witht that menu item. score!
mui_window_t * win = &mbar->win;
for (mui_control_t * c = TAILQ_FIRST(&win->controls);
c; c = TAILQ_NEXT(c, self)) {
if (c->type != MUI_CONTROL_MENUTITLE)
continue;
mui_menu_control_t * title = (mui_menu_control_t*)c;
// printf("%s title %s\n", __func__, c->title);
mui_menu_item_t * item = mui_menu_handle_keydown(
title->menu.e, ev);
if (item) {
// printf("%s KEY MATCH %s\n", __func__, item->title);
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
mui_window_action(&mbar->win,
MUI_MENUBAR_ACTION_SELECT, item);
mui_timer_register(win->ui, mui_menubar_unhighlight_cb, mbar,
MUI_MENU_CLOSE_BLINK_DELAY);
return true;
}
}
return false;
}
static bool
mui_wdef_menubar(
struct mui_window_t * win,
uint8_t what,
void * param)
{
mui_menubar_t * mbar = (mui_menubar_t*)win;
switch (what) {
case MUI_WDEF_DRAW: {
mui_drawable_t * dr = param;
mui_wdef_menubar_draw(win, dr);
} break;
case MUI_WDEF_EVENT: {
mui_event_t *ev = param;
switch (ev->type) {
case MUI_EVENT_BUTTONUP:
case MUI_EVENT_DRAG:
case MUI_EVENT_BUTTONDOWN: {
if (mui_menubar_handle_mouse(mbar, ev)) {
// printf("%s handled\n", __func__);
return true;
}
} break;
case MUI_EVENT_KEYDOWN: {
// printf("%s keydown %c\n", __func__, ev->key.key);
if (mui_menubar_handle_keydown(mbar, ev))
return true;
} break;
}
} break;
}
return false;
}
static bool
mui_menu_handle_mouse(
mui_menu_t * menu,
mui_event_t * ev )
{
mui_window_t * win = &menu->win;
// bool inside = c2_rect_contains_pt(&win->frame, &ev->mouse.where);
bool is_front = mui_window_isfront(win);
mui_control_t * c = mui_control_locate(win, ev->mouse.where);
switch (ev->type) {
case MUI_EVENT_BUTTONUP: {
D(printf("%s (%s) up drag %d\n", __func__, win->title, menu->drag_ev);)
if (menu->drag_ev == 0) {
// return true;
}
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar;
if (menu->highlighted &&
menu->highlighted->type != MUI_CONTROL_SUBMENUITEM) {
/*
* This tells the normal closing code that we are
* taking care of the closing with the timer, and not *now*
*/
if (mbar)
mbar->delayed_closing = true;
menu->timer_call_count = 0;
mui_timer_register(win->ui, mui_menu_close_timer_cb, menu,
MUI_MENU_CLOSE_BLINK_DELAY);
} else {
menu->highlighted = NULL;
if (mbar)
_mui_menubar_close_menu(mbar);
else
mui_menu_close(win);
}
} break;
case MUI_EVENT_DRAG:
/*
* if we've just opened a submenu, make it 'sticky' for a while
* so that the user can drag the mouse to it without it closing
*/
if (!is_front &&
(mui_get_time() - menu->sub_open_stamp) < (MUI_TIME_SECOND / 2))
return false;
menu->drag_ev = 1;
// fallthrough
case MUI_EVENT_BUTTONDOWN: {
// save click, for detecting click+drag vs sticky menus
if (ev->type == MUI_EVENT_BUTTONDOWN) {
menu->drag_ev = 0;
}
// printf("%s in:%d c:%s\n", __func__, inside, c ? c->title : "");
if (c && mui_control_get_state(c) != MUI_CONTROL_STATE_DISABLED) {
if (menu->sub && c != (mui_control_t*)menu->sub)
_mui_menu_close_submenu(menu);
if (menu->highlighted && c != menu->highlighted)
mui_control_set_state(menu->highlighted, 0);
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
menu->highlighted = c;
if (c->type == MUI_CONTROL_SUBMENUITEM) {
mui_menu_control_t *title = (mui_menu_control_t*)c;
if (title->menu_window == NULL) {
c2_pt_t where = C2_PT(c->frame.r, c->frame.t);
c2_pt_offset(&where, win->content.l, win->content.t);
title->menu_window = _mui_menu_create(
win->ui,
(mui_menubar_t*)menu->menubar, where,
title->menu.e);
menu->sub = title;
menu->sub_open_stamp = mui_get_time();
mui_window_action(&menu->win, MUI_MENU_ACTION_OPEN,
title->menu_window);
mui_window_set_action(title->menu_window,
mui_submenu_action_cb, menu);
}
}
} else {
if (!menu->sub) {
if (menu->highlighted)
mui_control_set_state(menu->highlighted, 0);
menu->highlighted = NULL;
}
}
} break;
}
return false;
}
static bool
mui_wdef_menu(
struct mui_window_t * win,
uint8_t what,
void * param)
{
mui_menu_t * menu = (mui_menu_t*)win;
switch (what) {
case MUI_WDEF_DISPOSE: {
_mui_menu_close_submenu(menu);
} break;
case MUI_WDEF_DRAW: {
mui_drawable_t * dr = param;
// shared drawing code for the menubar and menu
mui_wdef_menubar_draw(win, dr);
} break;
case MUI_WDEF_EVENT: {
mui_event_t *ev = param;
switch (ev->type) {
case MUI_EVENT_BUTTONUP:
case MUI_EVENT_DRAG:
case MUI_EVENT_BUTTONDOWN: {
if (mui_menu_handle_mouse(menu, ev))
return true;
} break;
}
} break;
}
return false;
}
mui_window_t *
mui_menubar_new(
mui_t * ui )
{
mui_font_t * main = mui_font_find(ui, "main");
c2_rect_t mbf = C2_RECT_WH(0, 0, ui->screen_size.x, main->size + 4);
// printf("%s frame %s\n", __func__, c2_rect_as_str(&mbf));
mui_menubar_t * mbar = (mui_menubar_t*)mui_window_create(
ui, mbf,
mui_wdef_menubar, MUI_WINDOW_MENUBAR_LAYER,
"Menubar", sizeof(*mbar));
ui->menubar = &mbar->win;
return &mbar->win;
}
mui_window_t *
mui_menubar_get(
mui_t * ui )
{
return ui ? ui->menubar : NULL;
}
bool
mui_menubar_window(
mui_window_t * win)
{
return win && win->wdef == mui_wdef_menubar;
}
mui_control_t *
mui_menubar_add_simple(
mui_window_t *win,
const char * title,
uint32_t menu_uid,
mui_menu_item_t * items )
{
mui_font_t * main = mui_font_find(win->ui, "main");
stb_ttc_measure m = {};
mui_font_text_measure(main, title, &m);
int title_width = m.x1 - m.x0 + (main->size / 2);
c2_rect_t title_rect = { .t = 2 };
mui_control_t * last = TAILQ_LAST(&win->controls, controls);
if (last) {
c2_rect_offset(&title_rect, last->frame.r, 0);
} else
title_rect.l = 4;
title_rect.r = title_rect.l + title_width + 6;
title_rect.b = win->content.b + 2;// title_rect.t + m.ascent - m.descent;
mui_control_t * c = mui_control_new(
win, MUI_CONTROL_MENUTITLE, mui_cdef_popup,
title_rect, title, menu_uid,
sizeof(mui_menu_control_t));
//printf("%s title %s rect %s\n", __func__, title, c2_rect_as_str(&title_rect));
mui_menu_control_t *menu = (mui_menu_control_t*)c;
menu->menubar = win;
/*
* We do not clone the items, so they must be static
* from somewhere -- we do not free them either.
*/
int sub_count = 0;
for (int ii = 0; items[ii].title; ii++)
sub_count++;
menu->menu.count = sub_count;
menu->menu.e = items;
menu->menu.read_only = 1;
return c;
}
mui_window_t *
mui_menubar_highlight(
mui_window_t * win,
bool ignored )
{
// mui_menubar_t * mbar = (mui_menubar_t*)win;
mui_control_t * c = NULL;
TAILQ_FOREACH(c, &win->controls, self) {
if (c->type == MUI_CONTROL_MENUTITLE ||
mui_control_get_state(c)) {
mui_control_set_state(c, 0);
}
}
return win;
}
static c2_rect_t
mui_menu_get_enclosing_rect(
mui_t * ui,
mui_menu_item_t * items)
{
c2_rect_t frame = {};
mui_font_t * main = mui_font_find(ui, "main");
stb_ttc_measure m = {};
for (int i = 0; items[i].title; i++) {
items[i].location = frame.br;
if (items[i].title && items[i].title[0] != '-') {
mui_font_text_measure(main, items[i].title, &m);
int title_width = main->size + m.x1 - m.x0 ;
if (items[i].kcombo[0]) {
mui_font_text_measure(main, items[i].kcombo, &m);
title_width += m.x1 - m.x0 + main->size;
} else
title_width += main->size;
if (title_width > frame.r)
frame.r = title_width;
frame.b += main->size + 2;
} else {
frame.b += main->size / 4;
}
}
return frame;
}
/*
* Given a list of items, create the actual windows & controls to show
* it onscreen
*/
static mui_window_t *
_mui_menu_create(
mui_t * ui,
mui_menubar_t * mbar,
c2_pt_t origin,
mui_menu_item_t * items )
{
if (mbar)
mui_window_action(&mbar->win,
MUI_MENUBAR_ACTION_PREPARE,
items);
c2_rect_t frame = mui_menu_get_enclosing_rect(ui, items);
c2_rect_t on_screen = frame;
c2_rect_offset(&on_screen, origin.x, origin.y);
/* Make sure the whole popup is on screen */
/* TODO: handle very large popups, that'll be considerably more
* complicated to do, with the arrows, scrolling and stuff */
c2_rect_t screen = C2_RECT_WH(0, 0, ui->screen_size.x, ui->screen_size.y);
if (on_screen.r > screen.r)
c2_rect_offset(&on_screen, screen.r - on_screen.r, 0);
if (on_screen.b > screen.b)
c2_rect_offset(&on_screen, 0, screen.b - on_screen.b);
if (on_screen.t < screen.t)
c2_rect_offset(&on_screen, 0, screen.t - on_screen.t);
if (on_screen.l < screen.l)
c2_rect_offset(&on_screen, screen.l - on_screen.l, 0);
mui_menu_t * menu = (mui_menu_t*)mui_window_create(
ui, on_screen,
mui_wdef_menu, MUI_WINDOW_MENU_LAYER,
items[0].title, sizeof(*menu));
if (mbar) {
mbar->open[mbar->open_count++] = menu;
menu->menubar = mbar;
}
/* Walk all the items in out static structure, and create the controls
* for each of them with their own corresponding item */
for (int i = 0; items[i].title; i++) {
mui_menu_item_t * item = &items[i];
item->index = i;
c2_rect_t title_rect = frame;
title_rect.t = item->location.y - 1;
if (items[i+1].title)
title_rect.b = items[i+1].location.y;
else
title_rect.b = frame.b;
mui_control_t * c = NULL;
if (item->submenu) {
// printf("%s submenu %s\n", __func__, item->title);
c = mui_control_new(
&menu->win,
MUI_CONTROL_SUBMENUITEM,
mui_cdef_popup,
title_rect, item->title, item->uid,
sizeof(mui_menu_control_t));
mui_menu_control_t *sub = (mui_menu_control_t*)c;
/* Note: menuitems aren't copied, we use the same static structure */
sub->menu.count = 0;
for (int i = 0; item->submenu[i].title; i++)
sub->menu.count++;
sub->menu.e = item->submenu;
sub->menu.read_only = 1;
} else {
c = mui_control_new(
&menu->win,
MUI_CONTROL_MENUITEM,
mui_cdef_popup,
title_rect, item->title, item->uid,
sizeof(mui_menuitem_control_t));
}
if (item->disabled)
mui_control_set_state(c, MUI_CONTROL_STATE_DISABLED);
// both item and submenu share a menuitem control
mui_menuitem_control_t *mic = (mui_menuitem_control_t*)c;
mic->item = *item;
}
return &menu->win;
}
static void
mui_menu_close(
mui_window_t * win )
{
mui_menu_t * menu = (mui_menu_t*)win;
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar; // can be NULL
menu->highlighted = NULL;
if (mbar && mbar->open_count) {
mbar->open[mbar->open_count-1] = NULL;
mbar->open_count--;
}
mui_window_dispose(win);
}
static int
mui_popupmenu_action_cb(
mui_window_t * win,
void * cb_param,
uint32_t what,
void * param) // popup control here
{
mui_menu_control_t * pop = cb_param;
printf("%s %4.4s\n", __func__, (char*)&what);
switch (what) {
case MUI_MENU_ACTION_SELECT: {
mui_menu_item_t * item = param;
pop->item.control.value = item->index;
mui_control_inval(&pop->item.control);
mui_control_action(&pop->item.control,
MUI_CONTROL_ACTION_VALUE_CHANGED, NULL);
} break;
}
return 0;
}
static bool
mui_popupmenu_handle_mouse(
mui_menu_control_t * pop,
mui_event_t * ev )
{
mui_control_t * c = &pop->item.control;
switch (ev->type) {
case MUI_EVENT_BUTTONUP: {
printf("%s up has popup %d\n", __func__, pop->menu_window != NULL);
if (pop->menu_window) {
// mui_menu_close(pop->menu_window);
pop->menu_window = NULL;
}
mui_control_set_state(c, 0);
} break;
case MUI_EVENT_BUTTONDOWN: {
mui_control_set_state(c,
ev->type != MUI_EVENT_BUTTONUP ?
MUI_CONTROL_STATE_CLICKED : 0);
if (!pop->menu_window) {
c2_pt_t loc = pop->menu_frame.tl;
c2_pt_offset(&loc, c->win->content.l, c->win->content.t);
c2_pt_offset(&loc, 0, -pop->menu.e[c->value].location.y);
pop->menu_window = _mui_menu_create(
c->win->ui, NULL, loc,
pop->menu.e);
mui_window_set_action(pop->menu_window,
mui_popupmenu_action_cb, pop);
// pass the mousedown to the new popup
// mui_window_handle_mouse(pop->menu_window, ev);
}
mui_control_inval(c);
} break;
}
return true;
}
static bool
mui_cdef_popup(
struct mui_control_t * c,
uint8_t what,
void * param)
{
switch (what) {
case MUI_CDEF_INIT: {
} break;
case MUI_CDEF_DISPOSE:
switch (c->type) {
case MUI_CONTROL_POPUP:
case MUI_CONTROL_MENUTITLE: {
mui_menu_control_t *pop = (mui_menu_control_t*)c;
if (pop->menu_window) {
mui_menu_close(pop->menu_window);
pop->menu_window = NULL;
}
mui_menu_items_clear(&pop->menu);
if (!pop->menu.read_only)
mui_menu_items_free(&pop->menu);
} break;
}
break;
case MUI_CDEF_DRAW: {
mui_drawable_t * dr = param;
switch (c->type) {
case MUI_CONTROL_POPUP:
mui_popuptitle_draw(c->win, c, dr);
break;
case MUI_CONTROL_MENUTITLE:
mui_menutitle_draw(c->win, c, dr);
break;
case MUI_CONTROL_MENUITEM:
case MUI_CONTROL_SUBMENUITEM:
mui_menuitem_draw(c->win, c, dr);
break;
}
} break;
case MUI_CDEF_EVENT: {
mui_event_t *ev = param;
// printf("%s event %d\n", __func__, ev->type);
switch (ev->type) {
case MUI_EVENT_BUTTONUP:
case MUI_EVENT_DRAG:
case MUI_EVENT_BUTTONDOWN: {
switch (c->type) {
case MUI_CONTROL_POPUP: {
mui_menu_control_t *pop = (mui_menu_control_t*)c;
return mui_popupmenu_handle_mouse(pop, ev);
} break;
case MUI_CONTROL_MENUTITLE:
// mui_menutitle_draw(c->win, c, dr);
break;
}
} break;
}
} break;
}
return false;
}
mui_control_t *
mui_popupmenu_new(
mui_window_t * win,
c2_rect_t frame,
const char * title,
uint32_t uid )
{
return mui_control_new(
win, MUI_CONTROL_POPUP, mui_cdef_popup,
frame, title, uid, sizeof(mui_menu_control_t));
}
mui_menu_items_t *
mui_popupmenu_get_items(
mui_control_t * c)
{
if (!c)
return NULL;
if (c->type != MUI_CONTROL_POPUP && c->type != MUI_CONTROL_MENUTITLE) {
printf("%s: not a popup or menutitle\n", __func__);
return NULL;
}
mui_menu_control_t *pop = (mui_menu_control_t*)c;
return &pop->menu;
}
void
mui_popupmenu_prepare(
mui_control_t * c)
{
mui_menu_control_t *pop = (mui_menu_control_t*)c;
if (pop->menu_window) {
mui_window_dispose(pop->menu_window);
pop->menu_window = NULL;
}
c2_rect_t frame = mui_menu_get_enclosing_rect(c->win->ui, pop->menu.e);
pop->menu_frame = frame;
c2_rect_offset(&frame, c->frame.l, c->frame.t);
frame.r += 32; // add the popup symbol width
if (c2_rect_width(&frame) < c2_rect_width(&c->frame)) {
c2_rect_offset(&frame, (c2_rect_width(&c->frame) / 2) -
(c2_rect_width(&frame) / 2), 0);
}
pop->menu_frame = frame;
c->value = 0;
mui_control_inval(c);
}

240
libmui/mui/mui_menus_draw.c Normal file
View File

@ -0,0 +1,240 @@
/*
* mui_menus_draw.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
#include "mui_priv.h"
#include "cg.h"
/*
* Menubar/menus frames is easy as pie -- just a framed rectangle
*/
void
mui_wdef_menubar_draw(
struct mui_window_t * win,
mui_drawable_t * dr)
{
c2_rect_t content = win->frame;
win->content = content;
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_color_t frameColor = MUI_COLOR(0x000000ff);
mui_color_t contentFill = MUI_COLOR(0xf0f0f0ff);
cg_set_line_width(cg, 1);
cg_rectangle(cg, win->frame.l + 0.5f, win->frame.t + 0.5f,
c2_rect_width(&win->frame) - 1, c2_rect_height(&win->frame) - 1);
cg_set_source_color(cg, &CG_COLOR(contentFill));
cg_fill_preserve(cg);
cg_set_source_color(cg, &CG_COLOR(frameColor));
cg_stroke(cg);
}
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
void
mui_menutitle_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_font_t * main = mui_font_find(win->ui, "main");
stb_ttc_measure m = {};
mui_font_text_measure(main, c->title, &m);
int title_width = m.x1 - m.x0;
c2_rect_t title = f;
title.r = title.l + title_width + 1;
title.b = title.t + m.ascent - m.descent;
c2_rect_offset(&title, //-m.x0 +
(int)((c2_rect_width(&f) / 2) - (c2_rect_width(&title)) / 2),
(c2_rect_height(&f) / 2) - (c2_rect_height(&title) / 2));
mui_drawable_clip_push(dr, &f);
uint32_t state = mui_control_get_state(c);
if (state) {
cg_set_source_color(cg, &CG_COLOR(mui_control_color[state].fill));
cg_rectangle(cg, f.l, f.t, c2_rect_width(&f), c2_rect_height(&f));
cg_fill(cg);
}
mui_font_text_draw(main, dr,
C2_PT(title.l, title.t), c->title, strlen(c->title),
mui_control_color[state].text);
mui_drawable_clip_pop(dr);
}
static void
mui_menuitem_get_locations(
mui_t * ui,
c2_rect_t * frame,
mui_menu_item_t * item,
c2_pt_t out[3] )
{
mui_font_t * main = mui_font_find(ui, "main");
stb_ttc_measure m = {};
mui_font_text_measure(main, item->title, &m);
c2_rect_t title = *frame;
title.b = title.t + m.ascent - m.descent;
c2_rect_offset(&title, 0,
(c2_rect_height(frame) / 2) - (c2_rect_height(&title) / 2));
if (item->icon[0]) {
title.l += 6;
mui_font_t * icons = mui_font_find(ui, "icon_small");
mui_font_text_measure(icons, item->icon, &m);
c2_pt_t loc = title.tl;
loc.x = loc.x + (icons->size / 2) - ((m.x1 - m.x0) / 2);
out[0] = loc;
title.l += 6;
} else if (item->mark[0]) {
mui_font_text_measure(main, item->mark, &m);
c2_pt_t loc = title.tl;
loc.x = loc.x + (main->size / 2) - ((m.x1 - m.x0) / 2);
out[0] = loc;
}
title.l += main->size;
out[1] = title.tl;
if (item->kcombo[0]) {
mui_font_text_measure(main, item->kcombo, &m);
c2_pt_t loc = C2_PT(title.r - m.x1 - m.x0 - (main->size/3), title.t);
out[2] = loc;
}
}
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
void
mui_menuitem_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_drawable_clip_push(dr, &f);
mui_font_t * main = TAILQ_FIRST(&win->ui->fonts);
if (c->title && c->title[0] != '-') {
c2_pt_t loc[3];
mui_menuitem_control_t *mic = (mui_menuitem_control_t*)c;
mui_menuitem_get_locations(win->ui, &f, &mic->item, loc);
uint32_t state = mui_control_get_state(c);
if (state && state != MUI_CONTROL_STATE_DISABLED) {
c2_rect_t b = f;
c2_rect_inset(&b, 1, 0);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[state].fill));
cg_rectangle(cg, b.l, b.t, c2_rect_width(&b), c2_rect_height(&b));
cg_fill(cg);
}
if (mic->item.icon[0]) {
mui_font_t * icons = mui_font_find(win->ui, "icon_small");
mui_font_text_draw(icons, dr,
loc[0], mic->item.icon, 0,
mui_control_color[state].text);
} else if (mic->item.mark[0]) {
mui_font_text_draw(main, dr,
loc[0], mic->item.mark, 0,
mui_control_color[state].text);
}
mui_font_text_draw(main, dr,
loc[1], mic->item.title, 0,
mui_control_color[state].text);
if (mic->item.kcombo[0]) {
mui_font_text_draw(main, dr,
loc[2], mic->item.kcombo, 0,
mui_control_color[state].text);
}
} else {
cg_move_to(cg, f.l, f.t + c2_rect_height(&f) / 2);
cg_line_to(cg, f.r, f.t + c2_rect_height(&f) / 2);
mui_color_t decoColor = MUI_COLOR(0x666666ff);
cg_set_source_color(cg, &CG_COLOR(decoColor));
cg_stroke(cg);
}
mui_drawable_clip_pop(dr);
}
void
mui_popuptitle_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
mui_menu_control_t *pop = (mui_menu_control_t*)c;
c2_rect_t f = c->frame;
if (c2_rect_width(&pop->menu_frame) &&
c2_rect_width(&pop->menu_frame) < c2_rect_width(&f)) {
f = pop->menu_frame;
f.b = c->frame.b;
}
c2_rect_offset(&f, win->content.l, win->content.t);
mui_font_t * main = TAILQ_FIRST(&win->ui->fonts);
mui_font_t * icons = mui_font_find(win->ui, "icon_small");
uint32_t state = mui_control_get_state(c);
mui_drawable_clip_push(dr, &f);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
c2_rect_t inner = f;
c2_rect_inset(&inner, 1, 1);
cg_set_line_width(cg, 2);
cg_round_rectangle(cg, inner.l, inner.t,
c2_rect_width(&inner), c2_rect_height(&inner), 3, 3);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[state].fill));
cg_fill_preserve(cg);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[state].frame));
cg_stroke(cg);
cg_move_to(cg, inner.r - 32, inner.t + 2);
cg_line_to(cg, inner.r - 32, inner.b - 2);
mui_color_t decoColor = MUI_COLOR(0x666666ff);
cg_set_source_color(cg, &CG_COLOR(decoColor));
cg_stroke(cg);
if (pop->menu.count) {
mui_menu_item_t item = pop->menu.e[c->value];
c2_pt_t loc[3];
c2_rect_offset(&f, 0, -1);
mui_menuitem_get_locations(win->ui, &f, &item, loc);
if (item.icon[0])
mui_font_text_draw(icons, dr,
loc[0], item.icon, 0,
mui_control_color[state].text);
mui_font_text_draw(main, dr,
loc[1], item.title, 0,
mui_control_color[state].text);
}
mui_font_text_draw(icons, dr,
C2_PT(inner.r - 32 + 8, inner.t + 2), "", 0,
mui_control_color[state].text);
mui_drawable_clip_pop(dr);
}

87
libmui/mui/mui_priv.h Normal file
View File

@ -0,0 +1,87 @@
/*
* mui_priv.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
/*
* This are functions that are used internally by the MUI library itself,
* but are not part of the public API.
*/
#pragma once
#include "mui.h"
// private, used my mui_draw() -- do not use outside of this context
void
mui_window_draw(
mui_window_t *win,
mui_drawable_t *dr);
bool
mui_window_handle_mouse(
mui_window_t *win,
mui_event_t *event);
bool
mui_window_handle_keyboard(
mui_window_t *win,
mui_event_t *event);
void
mui_control_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr );
bool
mui_control_event(
mui_control_t * c,
mui_event_t * ev );
/* This is common to:
* - Menu titles in the menubar
* - Menu items in a popup menu
* - Menu items pointing to a sub-menu
*/
typedef struct mui_menuitem_control_t {
mui_control_t control;
mui_menu_item_t item;
} mui_menuitem_control_t;
/* this is for menu title, popup menu labels, and hierarchical menu items */
typedef struct mui_menu_control_t {
mui_menuitem_control_t item;
mui_menu_items_t menu;
c2_rect_t menu_frame;
mui_window_t * menubar;
mui_window_t * menu_window; // when open
} mui_menu_control_t;
void
mui_wdef_menubar_draw(
struct mui_window_t * win,
mui_drawable_t * dr);
void
mui_popuptitle_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr );
void
mui_menuitem_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr );
void
mui_menutitle_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr );
// return true if window win is the menubar
bool
mui_menubar_window(
mui_window_t * win);

409
libmui/mui/mui_stdfile.c Normal file
View File

@ -0,0 +1,409 @@
/*
* mui_stdfile.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <regex.h>
#include <errno.h>
#include <sys/stat.h>
#include <glob.h>
#include <libgen.h>
#include "mui.h"
#include "c2_geometry.h"
#include <sys/types.h>
#include <dirent.h>
DECLARE_C_ARRAY(char*, string_array, 2);
IMPLEMENT_C_ARRAY(string_array);
typedef struct mui_stdfile_t {
mui_window_t win;
mui_control_t * ok, *cancel, *home, *root;
mui_control_t * listbox, *popup;
char * pref_file; // pathname we put last path used
char * re_pattern;
char * current_path;
char * selected_path;
regex_t re;
string_array_t pop_path;
} mui_stdfile_t;
enum {
MUI_STD_FILE_PART_FRAME = 0,
MUI_STD_FILE_PART_OK,
MUI_STD_FILE_PART_CANCEL,
MUI_STD_FILE_PART_HOME,
MUI_STD_FILE_PART_ROOT,
MUI_STD_FILE_PART_LISTBOX,
MUI_STD_FILE_PART_POPUP,
MUI_STD_FILE_PART_COUNT,
};
static int
_mui_stdfile_sort_cb(
const void * a,
const void * b)
{
const mui_listbox_elem_t * ea = a;
const mui_listbox_elem_t * eb = b;
#if 0
if (ea->icon == MUI_ICON_FOLDER && eb->icon != MUI_ICON_FOLDER)
return -1;
if (ea->icon != MUI_ICON_FOLDER && eb->icon == MUI_ICON_FOLDER)
return 1;
#endif
return strcmp(ea->elem, eb->elem);
}
static int
_mui_stdfile_populate(
mui_stdfile_t * std,
const char * path)
{
if (std->current_path && !strcmp(std->current_path, path))
return 0;
printf("%s %s\n", __func__, path);
errno = 0;
DIR * dir = opendir(path);
if (!dir) {
// show an alert of some sort
char * msg = NULL;
asprintf(&msg, "%s\n%s", path,
strerror(errno));
mui_alert(std->win.ui, C2_PT(0,0),
"Could not open directory",
msg,
MUI_ALERT_FLAG_OK);
return -1;
}
if (std->current_path)
free(std->current_path);
std->current_path = strdup(path);
path = NULL; // this COULD be in the list we are now deleting!
for (int i = 0; i < (int)std->pop_path.count; i++)
free(std->pop_path.e[i]);
std->pop_path.count = 0;
mui_control_t *pop = std->popup;
mui_menu_items_t * items = mui_popupmenu_get_items(pop);
mui_menu_items_clear(items);
char * p = strdup(std->current_path);
char * d;
const char *home = getenv("HOME");
int item_id = 1000;
while ((d = basename(p)) != NULL) {
mui_menu_item_t i = {
.title = strdup(d),
.uid = item_id++,
};
if (!strcmp(d, "/"))
strcpy(i.icon, MUI_ICON_ROOT);
else if (home && !strcmp(p, home))
strcpy(i.icon, MUI_ICON_HOME);
else
strcpy(i.icon, MUI_ICON_FOLDER_OPEN);
mui_menu_items_push(items, i);
// printf(" %s - %s\n", p, d);
string_array_push(&std->pop_path, strdup(p));
if (!strcmp(d, "/"))
break;
*d = 0;
}
free(p);
mui_menu_item_t z = {};
mui_menu_items_push(items, z);
mui_popupmenu_prepare(pop);
mui_control_t * lb = std->listbox;
mui_listbox_elems_t * elems = mui_listbox_get_elems(lb);
mui_listbox_elems_clear(elems);
struct dirent * ent;
while ((ent = readdir(dir))) {
if (ent->d_name[0] == '.')
continue;
struct stat st;
char * full_path = NULL;
asprintf(&full_path, "%s/%s", std->current_path, ent->d_name);
stat(full_path, &st);
free(full_path);
mui_listbox_elem_t e = {};
// usr the regex to filter file names
if (std->re_pattern) {
if (!S_ISDIR(st.st_mode) && regexec(&std->re, ent->d_name, 0, NULL, 0))
e.disabled = 1;
}
e.elem = strdup(ent->d_name);
if (S_ISDIR(st.st_mode))
strcpy(e.icon, MUI_ICON_FOLDER);
else
strcpy(e.icon, MUI_ICON_FILE);
mui_listbox_elems_push(elems, e);
}
qsort(elems->e, elems->count,
sizeof(mui_listbox_elem_t), _mui_stdfile_sort_cb);
mui_control_set_value(lb, 0);
mui_listbox_prepare(lb);
closedir(dir);
return 0;
}
static int
_mui_stdfile_window_action(
mui_window_t * win,
void * cb_param,
uint32_t what,
void * param)
{
mui_stdfile_t * std = (mui_stdfile_t*)win;
switch (what) {
case MUI_WINDOW_ACTION_CLOSE: {
// dispose of anything we had allocated
printf("%s close\n", __func__);
if (std->pref_file)
free(std->pref_file);
if (std->re_pattern)
free(std->re_pattern);
if (std->current_path)
free(std->current_path);
if (std->selected_path)
free(std->selected_path);
regfree(&std->re);
for (int i = 0; i < (int)std->pop_path.count; i++)
free(std->pop_path.e[i]);
std->pop_path.count = 0;
} break;
}
return 0;
}
static int
_mui_stdfile_control_action(
mui_control_t * c,
void * cb_param,
uint32_t what,
void * param)
{
mui_stdfile_t * std = cb_param;
switch (c->uid) {
case MUI_STD_FILE_PART_OK: {
mui_listbox_elem_t * e = mui_listbox_get_elems(std->listbox)->e;
int idx = mui_control_get_value(std->listbox);
if (idx < 0 || idx >= (int)mui_listbox_get_elems(std->listbox)->count)
return 0;
mui_listbox_elem_t * elem = &e[idx];
if (elem->disabled)
break;
// save pref file
if (std->pref_file) {
FILE * f = fopen(std->pref_file, "w");
if (f) {
fprintf(f, "%s\n", std->current_path);
fclose(f);
}
}
_mui_stdfile_control_action(std->listbox, std,
MUI_CONTROL_ACTION_SELECT, elem);
} break;
case MUI_STD_FILE_PART_CANCEL:
mui_window_action(&std->win, MUI_STDF_ACTION_CANCEL, NULL);
break;
case MUI_STD_FILE_PART_HOME:
// printf("%s Home\n", __func__);
_mui_stdfile_populate(std, getenv("HOME"));
break;
case MUI_STD_FILE_PART_ROOT:
// printf("%s Root\n", __func__);
_mui_stdfile_populate(std, "/");
break;
case MUI_STD_FILE_PART_LISTBOX: {
// printf("%s Listbox\n", __func__);
if (what == MUI_CONTROL_ACTION_SELECT ||
what == MUI_CONTROL_ACTION_DOUBLECLICK) {
mui_listbox_elem_t * e = param;
if (e->disabled)
break;
char * full_path = NULL;
asprintf(&full_path, "%s/%s",
std->current_path, (char*)e->elem);
char *dbl;
while ((dbl = strstr(full_path, "//")) != NULL) {
memmove(dbl, dbl + 1, strlen(dbl)); // include zero
}
struct stat st;
stat(full_path, &st);
if (S_ISDIR(st.st_mode)) {
_mui_stdfile_populate(std, full_path);
} else {
printf("Selected: %s\n", full_path);
mui_window_action(&std->win, MUI_STDF_ACTION_SELECT, NULL);
}
free(full_path);
}
} break;
case MUI_STD_FILE_PART_POPUP:
// printf("%s POPUP\n", __func__);
if (what == MUI_CONTROL_ACTION_VALUE_CHANGED) {
int idx = mui_control_get_value(c);
printf("Selected: %s\n", std->pop_path.e[idx]);
_mui_stdfile_populate(std, std->pop_path.e[idx]);
}
break;
}
return 0;
}
mui_window_t *
mui_stdfile_get(
struct mui_t * ui,
c2_pt_t where,
const char * prompt,
const char * regexp,
const char * start_path )
{
c2_rect_t wpos = C2_RECT_WH(where.x, where.y, 700, 400);
if (where.x == 0 && where.y == 0)
c2_rect_offset(&wpos,
(ui->screen_size.x / 2) - (c2_rect_width(&wpos) / 2),
(ui->screen_size.y * 0.4) - (c2_rect_height(&wpos) / 2));
mui_window_t *w = mui_window_create(ui,
wpos,
NULL, MUI_WINDOW_LAYER_MODAL,
prompt, sizeof(mui_stdfile_t));
mui_window_set_action(w, _mui_stdfile_window_action, NULL);
mui_stdfile_t *std = (mui_stdfile_t *)w;
if (regexp) {
std->re_pattern = strdup(regexp);
int re = regcomp(&std->re, std->re_pattern, REG_EXTENDED);
if (re) {
char * msg = NULL;
asprintf(&msg, "%s\n%s", std->re_pattern,
strerror(errno));
mui_alert(std->win.ui, C2_PT(0,0),
"Could not compile regexp",
msg,
MUI_ALERT_FLAG_OK);
free(std->re_pattern);
std->re_pattern = NULL;
}
}
mui_control_t * c = NULL;
c2_rect_t cf;
cf = C2_RECT_WH(0, 0, 120, 40);
c2_rect_left_of(&cf, c2_rect_width(&w->content), 20);
c2_rect_top_of(&cf, c2_rect_height(&w->content), 20);
std->cancel = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_NORMAL,
"Cancel", MUI_STD_FILE_PART_CANCEL);
c2_rect_top_of(&cf, cf.t, 20);
std->ok = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_DEFAULT,
"Select", MUI_STD_FILE_PART_OK);
std->ok->key_equ = MUI_KEY_EQU(0, 13); // return
std->cancel->key_equ = MUI_KEY_EQU(0, 27); // ESC
c2_rect_t t = cf;
t.b = t.t + 1;
c2_rect_top_of(&t, cf.t, 25);
c = mui_separator_new(w, t);
c2_rect_top_of(&cf, cf.t, 40);
std->home = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_NORMAL,
"Home", MUI_STD_FILE_PART_HOME);
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, 'h');
c2_rect_top_of(&cf, cf.t, 20);
std->root = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_NORMAL,
"Root", MUI_STD_FILE_PART_ROOT);
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, '/');
cf = C2_RECT_WH(15, 45, 700-185, 300);
std->listbox = c = mui_listbox_new(w, cf,
MUI_STD_FILE_PART_LISTBOX);
cf = C2_RECT_WH(15, 0, 700-185, 34);
c2_rect_top_of(&cf, std->listbox->frame.t, 6);
std->popup = c = mui_popupmenu_new(w, cf,
"Popup", MUI_STD_FILE_PART_POPUP);
// printf("Popup: %p\n", c);
c = NULL;
TAILQ_FOREACH(c, &w->controls, self) {
if (mui_control_get_uid(c) == 0)
continue;
mui_control_set_action(c, _mui_stdfile_control_action, std);
}
int dopop = 1; // populate to start_path by default
if (ui->pref_directory) {
uint32_t hash = std->re_pattern ? mui_hash(std->re_pattern) : 0;
asprintf(&std->pref_file, "%s/std_path_%04x", ui->pref_directory, hash);
printf("%s pref file: %s\n", __func__, std->pref_file);
/* read last used pathname */
FILE * f = fopen(std->pref_file, "r");
if (f) {
char * path = NULL;
size_t len = 0;
getline(&path, &len, f);
fclose(f);
if (path) {
char *nl = strrchr(path, '\n');
if (nl)
*nl = 0;
if (_mui_stdfile_populate(std, path) == 0) {
printf("%s last path[%s]: %s\n", __func__,
std->re_pattern, path);
dopop = 0;
}
free(path);
}
}
}
if (dopop)
_mui_stdfile_populate(std, start_path);
return w;
}
char *
mui_stdfile_get_path(
mui_window_t * w )
{
mui_stdfile_t * std = (mui_stdfile_t *)w;
return std->current_path;
}
char *
mui_stdfile_get_selected_path(
mui_window_t * w )
{
mui_stdfile_t * std = (mui_stdfile_t *)w;
mui_listbox_elem_t * e = mui_listbox_get_elems(std->listbox)->e;
int idx = mui_control_get_value(std->listbox);
if (idx < 0 || idx >= (int)mui_listbox_get_elems(std->listbox)->count)
return NULL;
mui_listbox_elem_t * elem = &e[idx];
char * full_path = NULL;
asprintf(&full_path, "%s/%s",
std->current_path, (char*)elem->elem);
char *dbl;
while ((dbl = strstr(full_path, "//")) != NULL) {
memmove(dbl, dbl + 1, strlen(dbl)); // include zero
}
return full_path;
}

40
libmui/mui/mui_utils.c Normal file
View File

@ -0,0 +1,40 @@
/*
* mui_utils.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <time.h>
#include "mui.h"
mui_time_t
mui_get_time()
{
struct timespec tim;
clock_gettime(CLOCK_MONOTONIC_RAW, &tim);
uint64_t time = ((uint64_t)tim.tv_sec) * (1000000 / MUI_TIME_RES) +
tim.tv_nsec / (1000 * MUI_TIME_RES);
return time;
}
uint32_t
mui_hash(
const char * inString )
{
if (!inString)
return 0;
/* Described http://papa.bretmulvey.com/post/124027987928/hash-functions */
const uint32_t p = 16777619;
uint32_t hash = 0x811c9dc5;
while (*inString)
hash = (hash ^ (*inString++)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return hash;
}

503
libmui/mui/mui_window.c Normal file
View File

@ -0,0 +1,503 @@
/*
* mui_window.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui_priv.h"
#include "cg.h"
enum mui_window_part_e {
MUI_WINDOW_PART_NONE = 0,
MUI_WINDOW_PART_CONTENT,
MUI_WINDOW_PART_TITLE,
MUI_WINDOW_PART_FRAME,
MUI_WINDOW_PART_COUNT,
};
static void
mui_window_update_rects(
mui_window_t *win,
mui_font_t * main )
{
int title_height = main->size;
c2_rect_t content = win->frame;
c2_rect_inset(&content, 4, 4);
content.t += title_height - 1;
win->content = content;
}
void
mui_titled_window_draw(
struct mui_t *ui,
mui_window_t *win,
mui_drawable_t *dr)
{
mui_font_t * main = mui_font_find(ui, "main");
if (!main)
return;
mui_window_update_rects(win, main);
int title_height = main->size;
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_color_t frameFill = MUI_COLOR(0xbbbbbbff);
mui_color_t contentFill = MUI_COLOR(0xf0f0f0ff);
mui_color_t frameColor = MUI_COLOR(0x000000ff);
mui_color_t decoColor = MUI_COLOR(0x999999ff);
mui_color_t titleColor = MUI_COLOR(0x000000aa);
mui_color_t dimTitleColor = MUI_COLOR(0x00000055);
cg_set_line_width(cg, 1);
cg_rectangle(cg, win->frame.l + 0.5f, win->frame.t + 0.5f,
c2_rect_width(&win->frame) - 1, c2_rect_height(&win->frame) - 1);
cg_rectangle(cg, win->content.l + 0.5f, win->content.t + 0.5f,
c2_rect_width(&win->content) - 1, c2_rect_height(&win->content) - 1);
cg_set_source_color(cg, &CG_COLOR(frameFill));
cg_fill_preserve(cg);
cg_set_source_color(cg, &CG_COLOR(frameColor));
cg_stroke(cg);
bool isFront = mui_window_front(ui) == win;
if (isFront) {
const int lrMargin = 6;
const int steps = 6;
cg_set_line_width(cg, 2);
for (int i = 1; i < (title_height + 4) / steps; i++) {
cg_move_to(cg, win->frame.l + lrMargin, win->frame.t + i * steps);
cg_line_to(cg, win->frame.r - lrMargin, win->frame.t + i * steps);
}
cg_set_source_color(cg, &CG_COLOR(decoColor));
cg_stroke(cg);
}
if (win->title) {
stb_ttc_measure m = {};
mui_font_text_measure(main, win->title, &m);
int title_width = m.x1 - m.x0;
c2_rect_t title = win->frame;
c2_rect_offset(&title, 0, 1);
title.b = title.t + title_height;
title.l += (c2_rect_width(&win->frame) / 2) - (title_width / 2);
title.r = title.l + title_width;
if (isFront) {
c2_rect_t titleBack = title;
c2_rect_inset(&titleBack, -6, 0);
cg_round_rectangle(cg, titleBack.l, titleBack.t,
c2_rect_width(&titleBack), c2_rect_height(&titleBack), 12, 12);
cg_set_source_color(cg, &CG_COLOR(frameFill));
cg_fill(cg);
}
mui_font_text_draw(main, dr,
C2_PT(-m.x0 + 1 + title.l, title.t + 0),
win->title, strlen(win->title),
isFront ? titleColor : dimTitleColor);
}
cg_set_source_color(cg, &CG_COLOR(contentFill));
cg_rectangle(cg, win->content.l + 0.5f, win->content.t + 0.5f,
c2_rect_width(&win->content) - 1, c2_rect_height(&win->content) - 1);
cg_fill(cg);
}
mui_window_t *
mui_window_create(
struct mui_t *ui,
c2_rect_t frame,
mui_wdef_p wdef,
uint8_t layer,
const char *title,
uint32_t instance_size)
{
mui_window_t * w = calloc(1,
instance_size >= sizeof(*w) ?
instance_size : sizeof(*w));
w->ui = ui;
w->frame = frame;
w->title = title ? strdup(title) : NULL;
w->wdef = wdef;
w->flags.layer = layer;
TAILQ_INIT(&w->controls);
TAILQ_INIT(&w->zombies);
STAILQ_INIT(&w->actions);
pixman_region32_init(&w->inval);
TAILQ_INSERT_HEAD(&ui->windows, w, self);
mui_window_select(w); // place it in it's own layer
mui_font_t * main = mui_font_find(ui, "main");
mui_window_update_rects(w, main);
mui_window_inval(w, NULL); // just to mark the UI dirty
return w;
}
void
_mui_control_free(
mui_control_t * c );
void
_mui_window_free(
mui_window_t *win)
{
if (!win)
return;
pixman_region32_fini(&win->inval);
mui_control_t * c;
while ((c = TAILQ_FIRST(&win->controls))) {
mui_control_dispose(c);
}
while ((c = TAILQ_FIRST(&win->zombies))) {
TAILQ_REMOVE(&win->zombies, c, self);
_mui_control_free(c);
}
if (win->title)
free(win->title);
free(win);
}
void
mui_window_dispose(
mui_window_t *win)
{
if (!win)
return;
if (win->flags.zombie) {
printf("%s: DOUBLE delete %s\n", __func__, win->title);
return;
}
bool was_front = mui_window_isfront(win);
mui_window_action(win, MUI_WINDOW_ACTION_CLOSE, NULL);
mui_window_inval(win, NULL); // just to mark the UI dirty
if (win->wdef)
win->wdef(win, MUI_WDEF_DISPOSE, NULL);
struct mui_t *ui = win->ui;
TAILQ_REMOVE(&ui->windows, win, self);
if (ui->event_capture == win)
ui->event_capture = NULL;
if (ui->action_active) {
// printf("%s %s is now zombie\n", __func__, win->title);
win->flags.zombie = true;
TAILQ_INSERT_TAIL(&ui->zombies, win, self);
} else
_mui_window_free(win);
if (was_front) {
mui_window_t * front = mui_window_front(ui);
if (front)
mui_window_inval(front, NULL);
}
}
void
mui_window_draw(
mui_window_t *win,
mui_drawable_t *dr)
{
mui_drawable_clip_push(dr, &win->frame);
if (win->wdef)
win->wdef(win, MUI_WDEF_DRAW, dr);
else
mui_titled_window_draw(win->ui, win, dr);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_save(cg);
// cg_translate(cg, content.l, content.t);
mui_control_t * c, *safe;
TAILQ_FOREACH_SAFE(c, &win->controls, self, safe) {
mui_control_draw(win, c, dr);
}
cg_restore(cg);
mui_drawable_clip_pop(dr);
}
bool
mui_window_handle_keyboard(
mui_window_t *win,
mui_event_t *event)
{
if (!mui_window_isfront(win))
return false;
if (win->wdef && win->wdef(win, MUI_WDEF_EVENT, event)) {
// printf("%s %s handled it\n", __func__, win->title);
return true;
}
// printf("%s %s checkint controls\n", __func__, win->title);
mui_control_t * c, *safe;
TAILQ_FOREACH_SAFE(c, &win->controls, self, safe) {
if (mui_control_event(c, event)) {
// printf("%s control %s handled it\n", __func__, c->title);
return true;
}
}
return false;
}
bool
mui_window_handle_mouse(
mui_window_t *win,
mui_event_t *event)
{
if (win->wdef && win->wdef(win, MUI_WDEF_EVENT, event))
return true;
switch (event->type) {
case MUI_EVENT_WHEEL: {
if (!c2_rect_contains_pt(&win->frame, &event->wheel.where))
return false;
mui_control_t * c = mui_control_locate(win, event->wheel.where);
if (!c)
return false;
if (c->cdef && c->cdef(c, MUI_CDEF_EVENT, event)) {
return true;
}
} break;
case MUI_EVENT_BUTTONDOWN: {
if (!c2_rect_contains_pt(&win->frame, &event->mouse.where))
return false;
mui_control_t * c = mui_control_locate(win, event->mouse.where);
/* if modifiers like control is down, don't select */
if (!(event->modifiers & MUI_MODIFIER_CTRL))
mui_window_select(win);
if (mui_window_front(win->ui) != win)
c = NULL;
if (!c) {
/* find where we clicked in the window */
win->ui->event_capture = win;
win->click_loc = event->mouse.where;
c2_pt_offset(&win->click_loc, -win->frame.l, -win->frame.t);
win->flags.hit_part = MUI_WINDOW_PART_FRAME;
if (event->mouse.where.y < win->content.t)
win->flags.hit_part = MUI_WINDOW_PART_TITLE;
else if (c2_rect_contains_pt(&win->content, &event->mouse.where))
win->flags.hit_part = MUI_WINDOW_PART_CONTENT;
} else
win->flags.hit_part = MUI_WINDOW_PART_CONTENT;
if (c) {
if (c->cdef && c->cdef(c, MUI_CDEF_EVENT, event)) {
// c->state = MUI_CONTROL_STATE_CLICKED;
win->control_clicked = c;
}
}
return true;
} break;
case MUI_EVENT_DRAG:
if (win->flags.hit_part == MUI_WINDOW_PART_TITLE) {
c2_rect_t frame = win->frame;
c2_rect_offset(&frame,
-win->frame.l + event->mouse.where.x - win->click_loc.x,
-win->frame.t + event->mouse.where.y - win->click_loc.y);
// todo, get that visibel rectangle from somewhere else
c2_rect_t screen = { .br = win->ui->screen_size };
screen.t += 35;
c2_rect_t title_bar = frame;
title_bar.b = title_bar.t + 35; // TODO fix that
if (c2_rect_intersect_rect(&title_bar, &screen)) {
c2_rect_t o;
c2_rect_clip_rect(&title_bar, &screen, &o);
if (c2_rect_width(&o) > 10 && c2_rect_height(&o) > 10) {
mui_window_inval(win, NULL); // old frame
win->frame = frame;
mui_window_inval(win, NULL); // new frame
}
}
// mui_window_inval(win, NULL);
return true;
}
if (win->control_clicked) {
mui_control_t * c = win->control_clicked;
if (c->cdef && c->cdef(c, MUI_CDEF_EVENT, event)) {
return true;
} else
win->control_clicked = NULL;
}
return win->flags.hit_part != MUI_WINDOW_PART_NONE;
break;
case MUI_EVENT_BUTTONUP: {
int part = win->flags.hit_part;
win->flags.hit_part = MUI_WINDOW_PART_NONE;
win->ui->event_capture = NULL;
if (win->control_clicked) {
mui_control_t * c = win->control_clicked;
win->control_clicked = NULL;
if (c->cdef && c->cdef(c, MUI_CDEF_EVENT, event))
return true;
}
return part != MUI_WINDOW_PART_NONE;
} break;
case MUI_EVENT_MOUSEENTER:
case MUI_EVENT_MOUSELEAVE:
break;
}
// printf("MOUSE %s button %d\n", __func__, event->mouse.button);
// printf("MOUSE %s %s\n", __func__, c->title);
return false;
}
void
mui_window_inval(
mui_window_t *win,
c2_rect_t * r)
{
if (!win)
return;
c2_rect_t frame = win->frame;
c2_rect_t forward = {};
if (!r) {
// printf("%s %s inval %s (whole)\n", __func__, win->title, c2_rect_as_str(&frame));
pixman_region32_reset(&win->inval, (pixman_box32_t*)&frame);
forward = frame;
mui_window_t * w;
TAILQ_FOREACH(w, &win->ui->windows, self) {
if (w == win || !c2_rect_intersect_rect(&w->frame, &forward))
continue;
pixman_region32_union_rect(&w->inval, &w->inval,
forward.l, forward.t,
c2_rect_width(&forward), c2_rect_height(&forward));
}
} else {
c2_rect_t local = *r;
c2_rect_offset(&local, win->content.l, win->content.t);
forward = local;
pixman_region32_union_rect(&win->inval, &win->inval,
forward.l, forward.t,
c2_rect_width(&forward), c2_rect_height(&forward));
}
if (c2_rect_isempty(&forward))
return;
pixman_region32_union_rect(&win->ui->inval, &win->ui->inval,
forward.l, forward.t,
c2_rect_width(&forward), c2_rect_height(&forward));
}
mui_window_t *
mui_window_front(
struct mui_t *ui)
{
if (!ui)
return NULL;
mui_window_t * w;
TAILQ_FOREACH_REVERSE(w, &ui->windows, windows, self) {
if (w->flags.hidden)
continue;
if (w->flags.layer < MUI_WINDOW_MENUBAR_LAYER)
return w;
}
return NULL;
}
bool
mui_window_isfront(
mui_window_t *win)
{
if (!win)
return NULL;
mui_window_t * next = TAILQ_NEXT(win, self);
while (next && next->flags.hidden)
next = TAILQ_NEXT(next, self);
if (!next)
return true;
if (next->flags.layer > win->flags.layer)
return true;
return false;
}
bool
mui_window_select(
mui_window_t *win)
{
bool res = false;
if (!win)
return false;
mui_window_t *last = NULL;
if (mui_window_isfront(win))
goto done;
res = true;
mui_window_inval(win, NULL);
TAILQ_REMOVE(&win->ui->windows, win, self);
mui_window_t *w, *save;
TAILQ_FOREACH_SAFE(w, &win->ui->windows, self, save) {
if (w->flags.layer > win->flags.layer) {
TAILQ_INSERT_BEFORE(w, win, self);
goto done;
}
last = w;
}
TAILQ_INSERT_TAIL(&win->ui->windows, win, self);
done:
if (last) // we are deselecting this one, so redraw it
mui_window_inval(last, NULL);
#if 0
printf("%s %s res:%d stack is now:\n", __func__, win->title, res);
TAILQ_FOREACH(w, &win->ui->windows, self) {
printf(" L:%2d T:%s\n", w->flags.layer, w->title);
}
#endif
return res;
}
void
mui_window_action(
mui_window_t * win,
uint32_t what,
void * param )
{
if (!win)
return;
win->ui->action_active++;
mui_action_t *a;
STAILQ_FOREACH(a, &win->actions, self) {
if (!a->window_cb)
continue;
a->window_cb(win, a->cb_param, what, param);
}
win->ui->action_active--;
}
void
mui_window_set_action(
mui_window_t * win,
mui_window_action_p cb,
void * param )
{
if (!win || !cb)
return;
mui_action_t *a;
STAILQ_FOREACH(a, &win->actions, self) {
if (a->window_cb == cb && a->cb_param == param)
return;
}
a = calloc(1, sizeof(*a));
a->window_cb = cb;
a->cb_param = param;
STAILQ_INSERT_TAIL(&win->actions, a, self);
}
mui_window_t *
mui_window_get_by_id(
struct mui_t *ui,
uint32_t uid )
{
mui_window_t *w;
TAILQ_FOREACH(w, &ui->windows, self) {
if (w->uid == uid)
return w;
}
return NULL;
}
void
mui_window_set_id(
mui_window_t *win,
uint32_t uid)
{
if (!win)
return;
win->uid = uid;
}

5077
libmui/mui/stb_truetype.h Normal file

File diff suppressed because it is too large Load Diff

660
libmui/mui/stb_ttc.h Normal file
View File

@ -0,0 +1,660 @@
/*
* stb_ttc.h
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef STB_TTC_H_
#define STB_TTC_H_
#include "stb_truetype.h"
#define STB_TTC_PACKED __attribute__((packed))
/* Number of bins in the hash table(s) */
#define STB_TTC_BINCOUNT 16
/* Number of structures we prealocate when resizing arrays */
#define STB_TTC_PAGESIZE 16
/*
* This is used in the hash table mapping the glyph/scale pair to the
* primary array of cached glyphs.
*/
typedef struct stb_ttc_index {
unsigned int intscale, glyph, index;
} STB_TTC_PACKED stb_ttc_index;
/*
* This is used by MeasureText. Returns the number os glyphs in the string,
* the ascent and descent /of that text/ and the bounding box of that text.
* Note that x0 can be negative, if the first character is for example, a 'j'
*/
typedef struct stb_ttc_measure {
short glyph_count;
short ascent, descent;
short x0,y0,x1,y1;
} STB_TTC_PACKED stb_ttc_measure;
/*
* A glyph info for a certain scale. You can have the same glyph at multiple
* scales in one cache.
*/
typedef struct stb_ttc_g {
unsigned int intscale; // for comparison purpose
float scale;
unsigned int glyph;
int advance, lsb;
short x0,y0,x1,y1;
// position in the pixel cache, or 0xffff if not yet cached.
unsigned short p_x, p_y;
} STB_TTC_PACKED stb_ttc_g;
/*
* Used to cache the codepoints to glyphs map in a hash table
*/
typedef struct stb_ttc_cp_gl {
unsigned int cp;
unsigned int glyph;
} STB_TTC_PACKED stb_ttc_cp_gl;
/*
* Used to cache the kerning between cp1 and cp2 in a hash
*/
typedef struct stb_ttc_cp_kern {
unsigned int hash;
unsigned int cp1, cp2;
int kern;
} STB_TTC_PACKED stb_ttc_cp_kern;
// to remap codepoint->glyph without calling back into stb
struct _stb_ttc_cp_bin {
unsigned int cp_count;
stb_ttc_cp_gl * cp_gl;
};
// to remap codepoint pairs to associated kerning
struct _stb_ttc_kn_bin {
unsigned int kn_count;
stb_ttc_cp_kern * cp_kn;
};
// for glyph -> position in the glyph array
struct _stb_ttc_g_bin {
unsigned int i_count;
stb_ttc_index * index;
};
/*
* Main STB TrueType Cache structure.
*/
typedef struct stb_ttc_info {
stbtt_fontinfo font;
unsigned int font_size;
unsigned font_mmap: 1; // is font from a file
int ascent, descent;
// hash to remap codepoint->glyph without calling back into stb
struct _stb_ttc_cp_bin cp_bin[STB_TTC_BINCOUNT];
// hash to remap codepoint pairs to associated kerning
struct _stb_ttc_kn_bin kn_bin[STB_TTC_BINCOUNT];
// hash that for glyph -> position in the glyph array
struct _stb_ttc_g_bin g_bin[STB_TTC_BINCOUNT];
// glyph array, contains glyph dimensions, and x,y of pixel in cache
unsigned int g_count;
stb_ttc_g * glyph;
// pixels cache for glyphs
unsigned int p_stride;
unsigned int p_height; // current total height
unsigned int p_line_height; // current glyph line height
unsigned int p_line_x, p_line_y; // current x,y position in pixels
unsigned char * pixels;
} stb_ttc_info;
#ifdef STBTTC_STATIC
#define STBTTC_DEF static
#else
#define STBTTC_DEF extern
#endif
/*
* Preload a range of codepoints into the glyph cache. This doesn't add the
* pixels, it just load the glyph measurement, advance, lsb into the cache
*/
STBTTC_DEF int
stb_ttc_CacheCodepointRange(
struct stb_ttc_info * ttc,
unsigned int cp,
unsigned int count,
float scale );
/*
* This walks the cache for glyphs that haven't been pre-rendered and
* render them in the cache. Also, it attempts to sort the glyphs by height
* into the cache to save a little bit of memory
* Note: This call is optional, the glyph renderer will populate the pixel
* cache lazily on the fly.
*/
STBTTC_DEF int
stb_ttc_RenderAllCachedGlyphs(
struct stb_ttc_info * ttc );
STBTTC_DEF int
stb_ttc_MeasureText(
struct stb_ttc_info * ttc,
float scale,
const char * text,
stb_ttc_measure *out);
STBTTC_DEF int
stb_ttc_DrawText(
struct stb_ttc_info * ttc,
float scale,
const char * text,
unsigned int dx,
unsigned int base_dy,
unsigned char * pixels,
unsigned int p_w,
unsigned int p_h,
unsigned int p_stride);
// load a font from a file
STBTTC_DEF int
stb_ttc_MapFont(
struct stb_ttc_info * ttc,
const char * font_file);
// load a font from memory
STBTTC_DEF int
stb_ttc_LoadFont(
struct stb_ttc_info * ttc,
const void * font_data,
unsigned int font_size);
STBTTC_DEF void
stb_ttc_Free(
struct stb_ttc_info * ttc);
#ifdef STB_TTC_IMPLEMENTATION
/*
* return font glyph index from codepoint, cache the result if it wasn't.
*/
static int
stb_ttc__CodepointGetGlyph(
struct stb_ttc_info *fi,
unsigned int cp )
{
struct _stb_ttc_cp_bin *b = &fi->cp_bin[cp & (STB_TTC_BINCOUNT - 1)];
int di = 0;
for (unsigned int gi = 0; gi < b->cp_count; gi++, di++)
if (b->cp_gl[gi].cp == cp)
return b->cp_gl[gi].glyph;
else if (b->cp_gl[gi].cp > cp)
break;
int gl = stbtt_FindGlyphIndex(&fi->font, cp);
if (gl == 0)
return -1;
if (!(b->cp_count % STB_TTC_PAGESIZE))
b->cp_gl = realloc(b->cp_gl,
sizeof(b->cp_gl[0]) * (b->cp_count + STB_TTC_PAGESIZE));
memmove(b->cp_gl + di + 1, b->cp_gl + di,
sizeof(b->cp_gl[0]) * (b->cp_count - di));
b->cp_count++;
b->cp_gl[di].cp = cp;
b->cp_gl[di].glyph = gl;
return gl;
}
/*
* return kerning for 2 codepoints, cache the result.
*/
static int
stb_ttc__CodepointsGetKerning(
struct stb_ttc_info *fi,
unsigned int cp1,
unsigned int cp2 )
{
unsigned int hash = cp1 + ((cp1 * 100) * cp2);
struct _stb_ttc_kn_bin *b = &fi->kn_bin[hash & (STB_TTC_BINCOUNT - 1)];
int di = 0;
for (unsigned int gi = 0; gi < b->kn_count; gi++, di++)
if (b->cp_kn[gi].hash == hash &&
b->cp_kn[gi].cp1 == cp1 &&
b->cp_kn[gi].cp2 == cp2)
return b->cp_kn[gi].kern;
else if (b->cp_kn[gi].hash > hash)
break;
int kern = stbtt_GetCodepointKernAdvance(&fi->font, cp1, cp2);
if (!(b->kn_count % STB_TTC_PAGESIZE))
b->cp_kn = realloc(b->cp_kn,
sizeof(b->cp_kn[0]) * (b->kn_count + STB_TTC_PAGESIZE));
memmove(b->cp_kn + di + 1, b->cp_kn + di,
sizeof(b->cp_kn[0]) * (b->kn_count - di));
stb_ttc_cp_kern k = {
.hash = hash,
.cp1 = cp1,
.cp2 = cp2,
.kern = kern,
};
b->kn_count++;
b->cp_kn[di] = k;
return kern;
}
/*
* returns the index of glyph in the glyph table, or -1
*/
static int
stb_ttc__ScaledGlyphGetOffset(
struct stb_ttc_info *fi,
unsigned int glyph,
float scale )
{
unsigned int intscale = 1.0f / scale * 1000;
unsigned int hash = glyph + (glyph * intscale);
struct _stb_ttc_g_bin *b = &fi->g_bin[hash & (STB_TTC_BINCOUNT - 1)];
for (unsigned int gi = 0; gi < b->i_count; gi++)
if (b->index[gi].intscale == intscale &&
b->index[gi].glyph == glyph)
return b->index[gi].index;
else if (b->index[gi].glyph > glyph)
break;
return -1;
}
static struct stb_ttc_g *
stb_ttc__ScaledGlyphGetCache(
struct stb_ttc_info *ttc,
unsigned int glyph,
float scale )
{
if (glyph == (unsigned int)-1)
return NULL;
int cached_index = stb_ttc__ScaledGlyphGetOffset(ttc, glyph, scale);
if (cached_index != -1)
return &ttc->glyph[cached_index];
stb_ttc_g gc = { };
gc.glyph = glyph;
gc.intscale = 1.0f / scale * 1000;
gc.scale = scale;
gc.p_x = gc.p_y = -1; // not initialised yet
// we use locals, as we are storing values in shorter types
{
int advance, lsb, x0, y0, x1, y1;
stbtt_GetGlyphHMetrics(&ttc->font, glyph, &advance, &lsb);
stbtt_GetGlyphBitmapBox(&ttc->font, glyph, scale, scale, &x0, &y0, &x1,
&y1);
gc.advance = advance;
gc.lsb = lsb;
gc.x0 = x0;
gc.y0 = y0;
gc.x1 = x1;
gc.y1 = y1;
}
if (!(ttc->g_count % STB_TTC_PAGESIZE))
ttc->glyph = realloc(ttc->glyph,
sizeof(ttc->glyph[0]) * (ttc->g_count + STB_TTC_PAGESIZE));
stb_ttc_index gh = {
.intscale = gc.intscale,
.glyph = glyph,
.index = ttc->g_count
};
ttc->g_count++;
unsigned int hash = glyph + (glyph * gc.intscale);
struct _stb_ttc_g_bin *b = &ttc->g_bin[hash & (STB_TTC_BINCOUNT - 1)];
if (!(b->i_count % STB_TTC_PAGESIZE))
b->index = realloc(b->index,
sizeof(b->index[0]) * (b->i_count + STB_TTC_PAGESIZE));
unsigned int di = b->i_count;
for (unsigned int i = 0; i < b->i_count; i++)
if (b->index[i].glyph > glyph) {
di = i;
break;
}
if (b->i_count - di)
memmove(&b->index[di + 1], &b->index[di],
sizeof(b->index[0]) * (b->i_count - di));
b->index[di] = gh;
b->i_count++;
ttc->glyph[gh.index] = gc;
return &ttc->glyph[gh.index];
}
static void
stb_ttc__ScaledGlyphRenderToCache(
struct stb_ttc_info *fi,
struct stb_ttc_g *g )
{
int wt = g->x1 - g->x0;
wt = (wt + 3) & ~3;
unsigned int ht = g->y1 - g->y0;
// find the new horizontal position for glyph, "wrap" next line if needed
if (fi->p_line_x + wt > fi->p_stride) {
fi->p_line_x = 0;
fi->p_line_y += fi->p_line_height;
fi->p_line_height = 0;
}
if (ht > fi->p_line_height)
fi->p_line_height = ht;
// reallocate the pixel cache to accommodate more lines, if needed
if (fi->p_line_y + ht > fi->p_height) {
int add = fi->p_line_y + ht - fi->p_height;
fi->pixels = realloc(fi->pixels, (fi->p_height + add) * fi->p_stride);
memset(fi->pixels + (fi->p_height * fi->p_stride), 0xff,
add * fi->p_stride);
fi->p_height += add;
}
g->p_x = fi->p_line_x;
g->p_y = fi->p_line_y;
stbtt_MakeGlyphBitmap(&fi->font,
fi->pixels + (g->p_y * fi->p_stride) + g->p_x, g->x1 - g->x0,
g->y1 - g->y0, fi->p_stride, g->scale, g->scale, g->glyph);
fi->p_line_x += wt;
}
static void
stb_ttc__GlyphRenderFromCache(
struct stb_ttc_info * fi,
struct stb_ttc_g * g,
unsigned int dx,
unsigned int base_dy,
unsigned char * pixels,
unsigned int p_w,
unsigned int p_h,
unsigned int p_stride)
{
int _dy = base_dy + g->y0;
int _sy = 0;
if (_dy >= (int)p_h || dx >= p_w || (int)base_dy + (g->y1 - g->y0) < 0)
return;
unsigned char * src = fi->pixels + (g->p_y * fi->p_stride);
// skip lines that would be over the top of dst pixels
if (_dy < 0) {
_sy -= _dy;
src += (-_dy) * fi->p_stride;
_dy = 0;
}
unsigned char * dst = pixels + (_dy * p_stride); // beginning of line in pixels
while (_dy < (int)p_h && _sy < (g->y1 - g->y0)) {
int rw = g->x1 - g->x0; // remaining width of glyph line
int line_dx = (int)dx - g->x0;
unsigned char * src_p = src + g->p_x;
// skip what would be before the left border of pixels
if (line_dx < 0) {
src_p += -line_dx;
rw += line_dx;
line_dx = 0;
}
if (line_dx + rw >= (int)p_w)
rw = p_w - line_dx;
unsigned char * dst_p = dst + line_dx;
for (; rw > 0; rw--) {
unsigned short s = *dst_p + *src_p;
unsigned char d = -(s >> 8) | (unsigned char)s;
*dst_p++ = d;
src_p++;
}
dst += p_stride;
src += fi->p_stride;
_dy++;
_sy++;
}
}
/*
* Preload a range of codepoints into the glyph cache. This doesn't add the
* pixels, it just load the glyph measurement, advance, lsb into the cache
*/
STBTTC_DEF int
stb_ttc_CacheCodepointRange(
struct stb_ttc_info * ttc,
unsigned int cp,
unsigned int count,
float scale )
{
int res = 0;
for (unsigned int c = cp; c < cp + count; c++) {
int gl = stb_ttc__CodepointGetGlyph(ttc, c);
if (gl == -1)
continue;
stb_ttc__ScaledGlyphGetCache(ttc, gl, scale);
res++;
}
return res;
}
static stb_ttc_info * _ttc;
static int
_compare(
const void *a,
const void *b)
{
stb_ttc_info *ttc = _ttc;
int g1 = *((int*)a), g2 = *((int*)b);
return (((ttc->glyph[g1].y1 - ttc->glyph[g1].y0) * 500) + ttc->glyph[g1].glyph) -
(((ttc->glyph[g2].y1 - ttc->glyph[g2].y0) * 500) + ttc->glyph[g2].glyph);
}
/*
* This walks the cache for glyphs that haven't been pre-rendered and
* render them in the cache. Also, it attempts to sort the glyphs by height
* into the cache to save a little bit of memory
* Note: This call is optional, the glyph renderer will populate the pixel
* cache lazily on the fly.
*/
STBTTC_DEF int
stb_ttc_RenderAllCachedGlyphs(
struct stb_ttc_info * ttc )
{
// go on and sort the glyph indexes by height
int * to_sort = malloc(ttc->g_count * sizeof(int));
for (int i = 0; i < (int)ttc->g_count; i++)
to_sort[i] = i;
_ttc = ttc;
qsort(to_sort, ttc->g_count, sizeof(int), _compare);
int count = 0;
// now load all the glyph pixels to the pixel cache
for (int i = 0; i < (int)ttc->g_count; i++) {
stb_ttc_g * g = &ttc->glyph[to_sort[i]];
if (g->p_y == (unsigned short)-1) {
stb_ttc__ScaledGlyphRenderToCache(ttc, &ttc->glyph[to_sort[i]]);
count++;
}
}
free(to_sort);
return count;
}
// Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
#define UTF8_ACCEPT 0
#define UTF8_REJECT 12
static inline unsigned int
stb_ttc__UTF8_Decode(
unsigned int* state,
unsigned int* codep,
unsigned char byte)
{
static const unsigned char utf8d[] = {
// The first part of the table maps bytes to character classes that
// to reduce the size of the transition table and create bitmasks.
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
// The second part is a transition table that maps a combination
// of a state of the automaton and a character class to a state.
0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
12,36,12,12,12,12,12,12,12,12,12,12,
};
unsigned int type = utf8d[byte];
*codep = (*state != UTF8_ACCEPT) ?
(byte & 0x3fu) | (*codep << 6) :
(0xff >> type) & (byte);
*state = utf8d[256 + *state + type];
return *state;
}
STBTTC_DEF int
stb_ttc_MeasureText(
struct stb_ttc_info * ttc,
float scale,
const char * text,
stb_ttc_measure *out)
{
unsigned int state = 0;
int xpos = 0;
unsigned int last = 0;
stb_ttc_measure m = { .ascent = ttc->ascent * scale,
.descent = ttc->descent * scale};
unsigned int cp = 0;
for (int ch = 0; text[ch]; ch++) {
if (stb_ttc__UTF8_Decode(&state, &cp, text[ch]) != UTF8_ACCEPT)
continue;
if (last) {
int kern = scale * stb_ttc__CodepointsGetKerning(ttc, last, cp);
xpos += kern;
}
last = cp;
int gl = stb_ttc__CodepointGetGlyph(ttc, cp);
if (gl == -1)
continue;
stb_ttc_g *gc = stb_ttc__ScaledGlyphGetCache(ttc, gl, scale);
if (!gc)
continue;
if (m.glyph_count == 0)
m.x0 = gc->x0;
if (gc->y0 < m.y0) m.y0 = gc->y0;
if (gc->y1 > m.y1) m.y1 = gc->y1;
m.glyph_count++;
xpos += gc->advance;
}
m.x1 = xpos * scale;
if (out) *out = m;
return m.x1 - m.x0;
}
STBTTC_DEF int
stb_ttc_DrawText(
struct stb_ttc_info * ttc,
float scale,
const char * text,
unsigned int dx,
unsigned int base_dy,
unsigned char * pixels,
unsigned int p_w,
unsigned int p_h,
unsigned int p_stride)
{
unsigned int state = 0;
int xpos = dx / scale;
unsigned int last = 0;
int glyph_count = 0;
unsigned int cp = 0;
for (int ch = 0; text[ch]; ch++) {
if (stb_ttc__UTF8_Decode(&state, &cp, text[ch]) != UTF8_ACCEPT)
continue;
if (last) {
int kern = scale * stb_ttc__CodepointsGetKerning(ttc, last, cp);
xpos += kern;
}
glyph_count++;
last = cp;
int gl = stb_ttc__CodepointGetGlyph(ttc, cp);
if (gl == -1)
continue;
stb_ttc_g *gc = stb_ttc__ScaledGlyphGetCache(ttc, gl, scale);
if (!gc)
continue;
// if (glyph_count == 1)
// xpos += -gc->x0 / scale;
if (gc->p_y == (unsigned short) -1)
stb_ttc__ScaledGlyphRenderToCache(ttc, gc);
int pxpos = gc->x0 + ((xpos + gc->lsb) * scale);
stb_ttc__GlyphRenderFromCache(ttc, gc, pxpos, base_dy,
pixels, p_w, p_h, p_stride);
xpos += gc->advance;
}
return glyph_count;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
STBTTC_DEF int
stb_ttc_MapFont(
struct stb_ttc_info * ttc,
const char * font_file)
{
int fd = open(font_file, O_RDONLY);
if (fd == -1)
return -1;
struct stat st;
if (fstat(fd, &st) == -1) {
close(fd);
return -1;
}
ttc->font_size = st.st_size;
unsigned char *map = mmap(NULL, ttc->font_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (map == MAP_FAILED)
return -1;
ttc->font_mmap = 1;
ttc->p_stride = 100; // default value;
// this make the code work for both ttf and ttc files
int offset = stbtt_GetFontOffsetForIndex(map, 0);
stbtt_InitFont(&ttc->font, map, offset);
stbtt_GetFontVMetrics(&ttc->font, &ttc->ascent, &ttc->descent, 0);
return 0;
}
STBTTC_DEF int
stb_ttc_LoadFont(
struct stb_ttc_info * ttc,
const void * font_data,
unsigned int font_size)
{
ttc->font_size = font_size;
unsigned char *map = (unsigned char *)font_data;
ttc->font_mmap = 0;
ttc->p_stride = 100; // default value;
// this make the code work for both ttf and ttc files
int offset = stbtt_GetFontOffsetForIndex(map, 0);
stbtt_InitFont(&ttc->font, map, offset);
stbtt_GetFontVMetrics(&ttc->font, &ttc->ascent, &ttc->descent, 0);
return 0;
}
STBTTC_DEF void
stb_ttc_Free(
struct stb_ttc_info * ttc)
{
for (int i = 0; i < STB_TTC_BINCOUNT; i++) {
free(ttc->cp_bin[i].cp_gl);
free(ttc->kn_bin[i].cp_kn);
free(ttc->g_bin[i].index);
}
free(ttc->pixels);
free(ttc->glyph);
if (ttc->font_mmap)
munmap(ttc->font.data, ttc->font_size);
}
#endif /* STB_TTC_IMPLEMENTATION */
#endif /* STB_TTC_H_ */

2874
libmui/mui/xft.c Normal file

File diff suppressed because it is too large Load Diff

141
libmui/mui/xft.h Normal file
View File

@ -0,0 +1,141 @@
#ifndef __XFT_H__
#define __XFT_H__
#include <assert.h>
#include <limits.h>
#include <setjmp.h>
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
/*
* type
*/
typedef signed long XCG_FT_Fixed;
typedef signed int XCG_FT_Int;
typedef unsigned int XCG_FT_UInt;
typedef signed long XCG_FT_Long;
typedef unsigned long XCG_FT_ULong;
typedef signed short XCG_FT_Short;
typedef unsigned char XCG_FT_Byte;
typedef unsigned char XCG_FT_Bool;
typedef int XCG_FT_Error;
typedef signed long XCG_FT_Pos;
typedef struct XCG_FT_Vector_ {
XCG_FT_Pos x;
XCG_FT_Pos y;
} XCG_FT_Vector;
/*
* math
*/
typedef XCG_FT_Fixed XCG_FT_Angle;
XCG_FT_Long XCG_FT_MulFix(XCG_FT_Long a, XCG_FT_Long b);
XCG_FT_Long XCG_FT_MulDiv(XCG_FT_Long a, XCG_FT_Long b, XCG_FT_Long c);
XCG_FT_Long XCG_FT_DivFix(XCG_FT_Long a, XCG_FT_Long b);
XCG_FT_Fixed XCG_FT_Sin(XCG_FT_Angle angle);
XCG_FT_Fixed XCG_FT_Cos(XCG_FT_Angle angle);
XCG_FT_Fixed XCG_FT_Tan(XCG_FT_Angle angle);
XCG_FT_Angle XCG_FT_Atan2(XCG_FT_Fixed x, XCG_FT_Fixed y);
XCG_FT_Angle XCG_FT_Angle_Diff(XCG_FT_Angle angle1, XCG_FT_Angle angle2);
void XCG_FT_Vector_Unit(XCG_FT_Vector *vec, XCG_FT_Angle angle);
void XCG_FT_Vector_Rotate(XCG_FT_Vector *vec, XCG_FT_Angle angle);
XCG_FT_Fixed XCG_FT_Vector_Length(XCG_FT_Vector *vec);
void XCG_FT_Vector_Polarize(XCG_FT_Vector *vec, XCG_FT_Fixed *length, XCG_FT_Angle *angle);
void XCG_FT_Vector_From_Polar(XCG_FT_Vector *vec, XCG_FT_Fixed length, XCG_FT_Angle angle);
/*
* raster
*/
typedef struct XCG_FT_BBox_ {
XCG_FT_Pos xMin, yMin;
XCG_FT_Pos xMax, yMax;
} XCG_FT_BBox;
typedef struct XCG_FT_Outline_ {
int n_contours;
int n_points;
XCG_FT_Vector * points;
char * tags;
int * contours;
char * contours_flag;
int flags;
} XCG_FT_Outline;
#define XCG_FT_OUTLINE_NONE 0x0
#define XCG_FT_OUTLINE_OWNER 0x1
#define XCG_FT_OUTLINE_EVEN_ODD_FILL 0x2
#define XCG_FT_OUTLINE_REVERSE_FILL 0x4
#define XCG_FT_CURVE_TAG(flag) (flag & 3)
#define XCG_FT_CURVE_TAG_ON 1
#define XCG_FT_CURVE_TAG_CONIC 0
#define XCG_FT_CURVE_TAG_CUBIC 2
#define XCG_FT_Curve_Tag_On XCG_FT_CURVE_TAG_ON
#define XCG_FT_Curve_Tag_Conic XCG_FT_CURVE_TAG_CONIC
#define XCG_FT_Curve_Tag_Cubic XCG_FT_CURVE_TAG_CUBIC
typedef struct XCG_FT_Span_ {
int x;
int len;
int y;
unsigned char coverage;
} XCG_FT_Span;
typedef void (*XCG_FT_SpanFunc)(int count, const XCG_FT_Span * spans, void * user);
#define XCG_FT_Raster_Span_Func XCG_FT_SpanFunc
#define XCG_FT_RASTER_FLAG_DEFAULT 0x0
#define XCG_FT_RASTER_FLAG_AA 0x1
#define XCG_FT_RASTER_FLAG_DIRECT 0x2
#define XCG_FT_RASTER_FLAG_CLIP 0x4
typedef struct XCG_FT_Raster_Params_ {
const void * source;
int flags;
XCG_FT_SpanFunc gray_spans;
void * user;
XCG_FT_BBox clip_box;
} XCG_FT_Raster_Params;
XCG_FT_Error XCG_FT_Outline_Check(XCG_FT_Outline * outline);
void XCG_FT_Outline_Get_CBox(const XCG_FT_Outline * outline, XCG_FT_BBox * acbox);
void XCG_FT_Raster_Render(const XCG_FT_Raster_Params * params);
/*
* stroker
*/
typedef struct XCG_FT_StrokerRec_ * XCG_FT_Stroker;
typedef enum XCG_FT_Stroker_LineJoin_ {
XCG_FT_STROKER_LINEJOIN_ROUND = 0,
XCG_FT_STROKER_LINEJOIN_BEVEL = 1,
XCG_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2,
XCG_FT_STROKER_LINEJOIN_MITER = XCG_FT_STROKER_LINEJOIN_MITER_VARIABLE,
XCG_FT_STROKER_LINEJOIN_MITER_FIXED = 3,
} XCG_FT_Stroker_LineJoin;
typedef enum XCG_FT_Stroker_LineCap_ {
XCG_FT_STROKER_LINECAP_BUTT = 0,
XCG_FT_STROKER_LINECAP_ROUND = 1,
XCG_FT_STROKER_LINECAP_SQUARE = 2,
} XCG_FT_Stroker_LineCap;
typedef enum XCG_FT_StrokerBorder_ {
XCG_FT_STROKER_BORDER_LEFT = 0,
XCG_FT_STROKER_BORDER_RIGHT = 1,
} XCG_FT_StrokerBorder;
XCG_FT_Error XCG_FT_Stroker_New(XCG_FT_Stroker * astroker);
void XCG_FT_Stroker_Set(XCG_FT_Stroker stroker, XCG_FT_Fixed radius, XCG_FT_Stroker_LineCap line_cap, XCG_FT_Stroker_LineJoin line_join, XCG_FT_Fixed miter_limit);
XCG_FT_Error XCG_FT_Stroker_ParseOutline(XCG_FT_Stroker stroker, const XCG_FT_Outline * outline);
XCG_FT_Error XCG_FT_Stroker_GetCounts(XCG_FT_Stroker stroker, XCG_FT_UInt * anum_points, XCG_FT_UInt * anum_contours);
void XCG_FT_Stroker_Export(XCG_FT_Stroker stroker, XCG_FT_Outline * outline);
void XCG_FT_Stroker_Done(XCG_FT_Stroker stroker);
#endif /* __XFT_H__ */

View File

@ -0,0 +1,616 @@
/*
* mui_playground.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#define MUI_HAS_XCB 1
#define MUI_HAS_XKB 1
#if MUI_HAS_XCB
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <xcb/xcb.h>
#include <xcb/shm.h>
#include <xcb/xcb_image.h>
#include <xcb/randr.h>
#if MUI_HAS_XKB
#include <xcb/xkb.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#else
struct xkb_state;
#endif
#include "mui.h"
#include "mui_plugin.h"
typedef struct mui_xcb_t {
mui_t ui;
mui_plug_t * plug;
void * plug_data;
float ui_scale_x, ui_scale_y;
c2_pt_t size;
xcb_connection_t * xcb;
xcb_shm_segment_info_t shm;
xcb_window_t window;
xcb_pixmap_t xcb_pix;
xcb_gcontext_t xcb_context;
struct xkb_state * xkb_state;
int redraw;
} mui_xcb_t;
int
_mui_xcb_init_keyboard(
mui_xcb_t *ui)
{
#if MUI_HAS_XKB
uint8_t xkb_event_base, xkb_error_base;
if (!xkb_x11_setup_xkb_extension (ui->xcb,
XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
NULL, NULL,
&xkb_event_base, &xkb_error_base)) {
fprintf(stderr, "%s needs version %d.%d or newer\n", __func__,
XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION);
goto error;
}
struct xkb_context *ctx =
xkb_context_new(XKB_CONTEXT_NO_FLAGS);
int32_t device_id =
xkb_x11_get_core_keyboard_device_id(ui->xcb);
if (device_id == -1) {
fprintf(stderr, "%s xkb_x11_get_core_keyboard_device_id\n", __func__);
goto error;
}
struct xkb_keymap *keymap =
xkb_x11_keymap_new_from_device(ctx, ui->xcb,
device_id, XKB_KEYMAP_COMPILE_NO_FLAGS);
ui->xkb_state =
xkb_x11_state_new_from_device(keymap, ui->xcb, device_id);
xkb_context_unref(ctx);
return 0;
#endif
error:
printf("XCB Keyboard initialisation: %s\n", ui->xkb_state ? "OK" : "Failed");
ui->xkb_state = NULL;
return -1;
}
/*
* xmodmap -pke or -pk will print the list of keycodes
*/
static bool
_mui_xcb_convert_keycode(
mui_xcb_t *ui,
xkb_keysym_t sym,
mui_event_t *out )
{
switch (sym) {
case XKB_KEY_F1 ... XKB_KEY_F12:
out->key.key = MUI_KEY_F1 + (sym - XKB_KEY_F1);
break;
case XKB_KEY_Escape: out->key.key = MUI_KEY_ESCAPE; break;
case XKB_KEY_Left: out->key.key = MUI_KEY_LEFT; break;
case XKB_KEY_Up: out->key.key = MUI_KEY_UP; break;
case XKB_KEY_Right: out->key.key = MUI_KEY_RIGHT; break;
case XKB_KEY_Down: out->key.key = MUI_KEY_DOWN; break;
// XKB_KEY_Begin
case XKB_KEY_Insert: out->key.key = MUI_KEY_INSERT; break;
case XKB_KEY_Home: out->key.key = MUI_KEY_HOME; break;
case XKB_KEY_End: out->key.key = MUI_KEY_END; break;
case XKB_KEY_Page_Up: out->key.key = MUI_KEY_PAGEUP; break;
case XKB_KEY_Page_Down: out->key.key = MUI_KEY_PAGEDOWN; break;
case XKB_KEY_Shift_R: out->key.key = MUI_KEY_RSHIFT; break;
case XKB_KEY_Shift_L: out->key.key = MUI_KEY_LSHIFT; break;
case XKB_KEY_Control_R: out->key.key = MUI_KEY_RCTRL; break;
case XKB_KEY_Control_L: out->key.key = MUI_KEY_LCTRL; break;
case XKB_KEY_Alt_L: out->key.key = MUI_KEY_LALT; break;
case XKB_KEY_Alt_R: out->key.key = MUI_KEY_RALT; break;
case XKB_KEY_Super_L: out->key.key = MUI_KEY_LSUPER; break;
case XKB_KEY_Super_R: out->key.key = MUI_KEY_RSUPER; break;
default:
out->key.key = sym & 0xff;
break;
}
// printf("%s %08x to %04x\n", __func__, sym, out->key.key);
return true;
}
int
mui_xcb_list_physical_screens(
struct xcb_connection_t * xcb,
struct c2_rect_array_t *out)
{
if (!xcb || !out)
return -1;
c2_rect_array_clear(out);
xcb_screen_t *screen = xcb_setup_roots_iterator(
xcb_get_setup(xcb)).data;
xcb_randr_get_screen_resources_current_reply_t *reply =
xcb_randr_get_screen_resources_current_reply(
xcb,
xcb_randr_get_screen_resources_current(
xcb, screen->root),
NULL);
xcb_timestamp_t timestamp = reply->config_timestamp;
int len = xcb_randr_get_screen_resources_current_outputs_length(reply);
xcb_randr_output_t *randr_outputs =
xcb_randr_get_screen_resources_current_outputs(reply);
for (int i = 0; i < len; i++) {
xcb_randr_get_output_info_reply_t *output =
xcb_randr_get_output_info_reply(
xcb,
xcb_randr_get_output_info(
xcb, randr_outputs[i], timestamp),
NULL);
if (!output || output->crtc == XCB_NONE ||
output->connection == XCB_RANDR_CONNECTION_DISCONNECTED)
continue;
xcb_randr_get_crtc_info_reply_t *crtc =
xcb_randr_get_crtc_info_reply(xcb,
xcb_randr_get_crtc_info(
xcb, output->crtc, timestamp),
NULL);
c2_rect_t r = C2_RECT(crtc->x, crtc->y,
crtc->x +crtc->width, crtc->y + crtc->height);
c2_rect_array_add(out, r);
free(crtc);
free(output);
}
free(reply);
return 0;
}
static bool
_cui_match_physical_screen(
xcb_connection_t *xcb,
c2_pt_t want_size,
c2_pt_p found_pos )
{
bool res = false;
c2_rect_array_t sc = {};
mui_xcb_list_physical_screens(xcb, &sc);
for (unsigned int i = 0; i < sc.count; i++) {
if (c2_rect_width(&sc.e[i]) == want_size.x &&
c2_rect_height(&sc.e[i]) == want_size.y) {
*found_pos = sc.e[i].tl;
res = true;
}
}
return res;
}
struct mui_t *
mui_xcb_init(
struct mui_t *mui,
struct mui_pixmap_t * pix )
{
mui_xcb_t *ui = (mui_xcb_t *)mui;
pix->size.y = 720;
pix->size.x = 1280;
ui->ui.screen_size = pix->size;
ui->ui_scale_x = ui->ui_scale_y = 1;
printf("XCB: Starting on %dx%d window\n",
pix->size.x, pix->size.y);
pix->size.x *= ui->ui_scale_x;
pix->size.y *= ui->ui_scale_y;
ui->size = pix->size;
uint32_t value_mask;
uint32_t value_list[6];
ui->xcb = xcb_connect(NULL, NULL);
bool windowed = 1;
bool opaque = 1;
c2_pt_t found_position = {};
bool has_position = !windowed && _cui_match_physical_screen(
ui->xcb, ui->size, &found_position);
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(
xcb_get_setup(ui->xcb));
printf("%s %d screens\n", __func__, iter.rem);
xcb_screen_t *screen = NULL;
while (iter.rem) {
screen = iter.data;
printf("%s screen %d: width: %d, height: %d\n", __func__,
iter.index,
screen->width_in_pixels, screen->height_in_pixels);
xcb_screen_next(&iter);
}
printf("XCB Screen depth %d\n", screen->root_depth);
/*
* This walks thru the 'visual', looking for a true colour *32 bits* one
* which means it handles ARGB colors, which we can draw into. Also find
* one which color bit masks matches libcui & libpixman.
*/
xcb_visualtype_t *argb_visual = NULL;
xcb_depth_iterator_t depth_iter =
xcb_screen_allowed_depths_iterator(screen);
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
xcb_visualtype_iterator_t visual_iter =
xcb_depth_visuals_iterator(depth_iter.data);
// printf("XCB Depth %d\n", depth_iter.data->depth);
if (depth_iter.data->depth != 32)
continue;
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
if (visual_iter.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR
&& visual_iter.data->red_mask == 0xff0000
&& visual_iter.data->green_mask == 0x00ff00
&& visual_iter.data->blue_mask == 0x0000ff) {
argb_visual = visual_iter.data;
break;
}
}
}
printf("XCB ARGB Transparency %s\n",
argb_visual ? "Supported" : "Not available");
if (windowed || opaque)
argb_visual = NULL;
xcb_shm_query_version_reply_t *xcb_shm_present;
xcb_shm_present = xcb_shm_query_version_reply(
ui->xcb, xcb_shm_query_version(ui->xcb), NULL);
if (!xcb_shm_present || !xcb_shm_present->shared_pixmaps) {
printf("xcb_shm error... %p\n", xcb_shm_present);
printf("If using nvidia driver, you need\n"
" Option \"AllowSHMPixmaps\" \"1\"\n"
" In your /etc/X11/xorg.conf file\n");
exit(0);
}
printf("XCB Shared memory present\n");
_mui_xcb_init_keyboard(ui);
value_mask = XCB_CW_BACK_PIXEL |
XCB_CW_BORDER_PIXEL |
XCB_CW_OVERRIDE_REDIRECT |
XCB_CW_EVENT_MASK;
xcb_colormap_t cmap = xcb_generate_id(ui->xcb);
/* required for having transparent windows */
if (argb_visual) {
xcb_create_colormap(ui->xcb, XCB_COLORMAP_ALLOC_NONE, cmap,
screen->root, argb_visual->visual_id);
value_mask |= XCB_CW_COLORMAP;
}
uint32_t w_mask[] = {
screen->black_pixel,
// Border Pixel; not really needed for anything, but needed
// for ARGB window otherwise it doesn't get created properly
0x88888888,
// if we found a screen of the exact size, remove the border
has_position ? 1 : 0,
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE,
cmap
};
ui->window = xcb_generate_id(ui->xcb);
xcb_create_window(
ui->xcb,
argb_visual ? 32 : XCB_COPY_FROM_PARENT,
ui->window, screen->root,
found_position.x, found_position.y,
pix->size.x, pix->size.y, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
argb_visual ? argb_visual->visual_id : screen->root_visual,
value_mask, w_mask);
xcb_free_colormap(ui->xcb, cmap);
const char * title = "MII UI Playground";
xcb_change_property(ui->xcb, XCB_PROP_MODE_REPLACE,
ui->window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
strlen(title), title);
// create a graphic context
value_mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
value_list[0] = screen->white_pixel;
value_list[1] = 0;
ui->xcb_context = xcb_generate_id(ui->xcb);
xcb_create_gc(
ui->xcb, ui->xcb_context, ui->window, value_mask, value_list);
// map the window onto the screen
xcb_map_window(ui->xcb, ui->window);
// wont show unless I do this
xcb_flush(ui->xcb);
ui->shm.shmid = shmget(IPC_PRIVATE,
pix->size.x * pix->size.y * 4, IPC_CREAT | 0777);
ui->shm.shmaddr = shmat(ui->shm.shmid, 0, 0);
ui->shm.shmseg = xcb_generate_id(ui->xcb);
xcb_shm_attach(ui->xcb, ui->shm.shmseg, ui->shm.shmid, 0);
shmctl(ui->shm.shmid, IPC_RMID, 0);
ui->xcb_pix = xcb_generate_id(ui->xcb);
xcb_shm_create_pixmap(
ui->xcb, ui->xcb_pix, ui->window,
pix->size.x, pix->size.y,
argb_visual ? 32 : screen->root_depth,
ui->shm.shmseg, 0);
pix->pixels = ui->shm.shmaddr;
pix->row_bytes = pix->size.x * 4;
// printf("%s pix is %p\n", __func__, pix->pixels);
ui->redraw = 1;
return &ui->ui;
}
int
mui_xcb_poll(
struct mui_t * mui,
bool redrawn)
{
mui_xcb_t * ui = (mui_xcb_t *)mui;
int gameover = 0;
xcb_generic_event_t *event;
static bool buttondown = false;
static mui_event_t key_ev;
while (!gameover && (event = xcb_poll_for_event(ui->xcb)) != NULL) {
switch (event->response_type & ~0x80) {
case XCB_KEY_RELEASE: {
xcb_key_release_event_t *key =
(xcb_key_press_event_t*) event;
xkb_state_update_key(ui->xkb_state,
key->detail, XKB_KEY_UP);
xkb_keysym_t keysym = xkb_state_key_get_one_sym(
ui->xkb_state, key->detail);
key_ev.type = MUI_EVENT_KEYUP;
key_ev.key.up = 1;
if (_mui_xcb_convert_keycode(ui, keysym, &key_ev)) {
if (key_ev.key.key >= MUI_KEY_MODIFIERS &&
key_ev.key.key <= MUI_KEY_MODIFIERS_LAST) {
mui->modifier_keys &= ~(1 << (key_ev.key.key - MUI_KEY_MODIFIERS));
}
key_ev.modifiers = mui->modifier_keys;
// key_ev.modifiers |= MUI_MODIFIER_EVENT_TRACE;
if (ui->plug && ui->plug->event)
ui->plug->event(mui, ui->plug_data, &key_ev);
}
} break;
case XCB_KEY_PRESS: {
xcb_key_press_event_t *key =
(xcb_key_press_event_t*) event;
if (key->same_screen == 0) // repeat key
break;
xkb_state_update_key(ui->xkb_state,
key->detail, XKB_KEY_DOWN);
xkb_keysym_t keysym = xkb_state_key_get_one_sym(
ui->xkb_state, key->detail);
key_ev.type = MUI_EVENT_KEYDOWN;
key_ev.key.up = 0;
// printf("%s %08x\n", __func__, keysym);
if (_mui_xcb_convert_keycode(ui, keysym, &key_ev)) {
if (key_ev.key.key >= MUI_KEY_MODIFIERS &&
key_ev.key.key <= MUI_KEY_MODIFIERS_LAST) {
mui->modifier_keys |= (1 << (key_ev.key.key - MUI_KEY_MODIFIERS));
}
key_ev.modifiers = mui->modifier_keys;
// key_ev.modifiers |= MUI_MODIFIER_EVENT_TRACE;
// gameover = key_ev.key.key == 'q';
if (ui->plug && ui->plug->event)
ui->plug->event(mui, ui->plug_data, &key_ev);
}
} break;
case XCB_BUTTON_RELEASE:
buttondown = false;
// fall through
case XCB_BUTTON_PRESS: {
xcb_button_press_event_t *m =
(xcb_button_press_event_t *)event;
#if 0
printf("%s %s %02x %d at %4dx%4d\n", __func__,
(event->response_type & ~0x80) == XCB_BUTTON_PRESS ?
"down" : "up",
event->response_type,
m->detail, m->event_x, m->event_y);
#endif
switch (m->detail) {
case XCB_BUTTON_INDEX_1: {
case XCB_BUTTON_INDEX_3:
buttondown = (event->response_type & ~0x80) == XCB_BUTTON_PRESS;
mui_event_t ev = {
.type = buttondown ?
MUI_EVENT_BUTTONDOWN :
MUI_EVENT_BUTTONUP,
.mouse.button = m->detail,
.mouse.where.x = (float)m->event_x / ui->ui_scale_x,
.mouse.where.y = (float)m->event_y / ui->ui_scale_y,
.modifiers = mui->modifier_keys,
};
if (ui->plug && ui->plug->event)
ui->plug->event(mui, ui->plug_data, &ev);
} break;
case XCB_BUTTON_INDEX_4:
case XCB_BUTTON_INDEX_5: {
mui_event_t ev = {
.type = MUI_EVENT_WHEEL,
.wheel.delta = m->detail == XCB_BUTTON_INDEX_4 ?
-1 : 1,
.wheel.where.x = (float)m->event_x / ui->ui_scale_x,
.wheel.where.y = (float)m->event_y / ui->ui_scale_y,
.modifiers = mui->modifier_keys,
};
if (ui->plug && ui->plug->event)
ui->plug->event(mui, ui->plug_data, &ev);
} break;
}
} break;
case XCB_MOTION_NOTIFY: {
xcb_motion_notify_event_t *m =
(xcb_motion_notify_event_t*)event;
// if (buttondown) {
// printf("x=%d y=%d\n", event.motion.x, event.motion.y);
mui_event_t ev = {
.type = MUI_EVENT_DRAG,
.mouse.button = buttondown ? 1 : 0,
.mouse.where.x = (float)m->event_x / ui->ui_scale_x,
.mouse.where.y = (float)m->event_y / ui->ui_scale_y,
.modifiers = mui->modifier_keys,
};
if (ui->plug && ui->plug->event)
ui->plug->event(mui, ui->plug_data, &ev);
// }
} break;
case XCB_ENTER_NOTIFY:
case XCB_LEAVE_NOTIFY:
break;
case XCB_EXPOSE: {
// xcb_expose_event_t *expose_event = (xcb_expose_event_t*) event;
ui->redraw++;
} break;
default:
// Handle other events
break;
}
free(event);
}
if (redrawn || ui->redraw || pixman_region32_not_empty(&mui->redraw)) {
// Handle window refresh event
int rc = 0;
c2_rect_t whole = C2_RECT(0, 0, ui->size.x, ui->size.y);
c2_rect_t *ra = (c2_rect_t*)pixman_region32_rectangles(&mui->redraw, &rc);
if (ui->redraw) {
ui->redraw = 0;
rc = 1;
ra = &whole;
}
if (rc) {
// printf("XCB: %d rects to redraw\n", rc);
for (int i = 0; i < rc; i++) {
c2_rect_t r = ra[i];
// printf("XCB: %d,%d %dx%d\n", r.l, r.t, c2_rect_width(&r), c2_rect_height(&r));
xcb_copy_area(
ui->xcb, ui->xcb_pix, ui->window, ui->xcb_context,
r.l, r.t, r.l, r.t, c2_rect_width(&r), c2_rect_height(&r));
}
}
pixman_region32_clear(&mui->redraw);
}
xcb_flush(ui->xcb);
return gameover;
}
void
mui_xcb_terminate(
struct mui_t * mui)
{
mui_xcb_t * ui = (mui_xcb_t *)mui;
xcb_shm_detach(ui->xcb, ui->shm.shmseg);
shmdt(ui->shm.shmaddr);
xcb_free_pixmap(ui->xcb, ui->xcb_pix);
xcb_destroy_window(ui->xcb, ui->window);
xcb_disconnect(ui->xcb);
}
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <dlfcn.h>
int main()
{
mui_xcb_t xcb_ui = {};
mui_drawable_t dr = {};
// note: the mui_t is *not* initialized yet, it will be done
// in the _init() of the plugin. This is just to get the
// init/dispose done in the plugin to check for leaks etc
mui_t *mui = mui_xcb_init((struct mui_t *)&xcb_ui, &dr.pix);
mui_xcb_t * ui = &xcb_ui;
void * dynload = NULL;
char * filename = "build-x86_64-linux-gnu/lib/ui_tests.so";
struct stat st_current = {}, st = {};
mui_time_t stamp = 0;
do {
if (stat(filename, &st) == 0 && st.st_mtime != st_current.st_mtime) {
st_current = st;
if (dynload) {
if (ui->plug_data && ui->plug && ui->plug->dispose) {
ui->plug->dispose(ui->plug_data);
ui->plug = NULL;
ui->plug_data = NULL;
}
printf("Closed %s\n", filename);
dlclose(dynload);
dynload = NULL;
}
}
if (!dynload) {
dynload = dlopen(filename, RTLD_NOW);
printf("Loading %s\n", filename);
if (!dynload) {
printf("Failed to load %s : %s\n", filename, dlerror());
perror(filename);
sleep(2);
continue;
}
ui->plug = dlsym(dynload, "mui_plug");
if (!ui->plug) {
printf("Failed to find mui_plug in %s\n", filename);
dlclose(dynload);
dynload = NULL;
sleep(10);
continue;
}
if (ui->plug->init) {
ui->plug_data = ui->plug->init(mui, ui->plug, &dr);
if (!ui->plug_data) {
printf("Failed to init plugin %s\n", filename);
dlclose(dynload);
dynload = NULL;
sleep(10);
continue;
}
}
stamp = mui_get_time();
}
bool draw = false;
mui_run(mui);
if (ui->plug && ui->plug->draw)
draw = ui->plug->draw(mui, ui->plug_data, &dr, false);
if (mui_xcb_poll(mui, draw))
break;
mui_time_t now = mui_get_time();
while (stamp < now)
stamp += (MUI_TIME_SECOND / 60);
usleep(stamp-now);
} while (1);
if (dynload) {
if (ui->plug_data && ui->plug && ui->plug->dispose) {
ui->plug->dispose(ui->plug_data);
ui->plug = NULL;
ui->plug_data = NULL;
}
printf("Closed %s\n", filename);
dlclose(dynload);
}
mui_drawable_dispose(&dr);
mui_xcb_terminate(mui);
return 0;
}

33
libmui/tests/mui_plugin.h Normal file
View File

@ -0,0 +1,33 @@
/*
* mui_plugin.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct mui_t;
struct mui_drawable_t;
typedef struct mui_plug_t {
void * (*init)(
struct mui_t * ui,
struct mui_plug_t * plug,
struct mui_drawable_t * dr );
void (*dispose)(
void * plug );
int (*draw)(
struct mui_t *ui,
void *param,
struct mui_drawable_t * dr,
uint16_t all );
int (*event)(
struct mui_t *ui,
void *param,
struct mui_event_t * event );
} mui_plug_t;

236
libmui/tests/ui_tests.c Normal file
View File

@ -0,0 +1,236 @@
/*
* ui_tests.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#define _GNU_SOURCE // for asprintf
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
#include "mui_plugin.h"
#include "c2_geometry.h"
typedef struct cg_ui_t {
mui_t *ui;
} cg_ui_t;
#define MII_MUI_MENUS_C
#include "mii_mui_menus.h"
#include "mii_mui_settings.h"
#ifndef UI_VERSION
#define UI_VERSION "0.0.0"
#endif
static void
_test_show_about(
cg_ui_t *g)
{
mui_window_t *w = mui_window_get_by_id(g->ui, FCC('a','b','o','t'));
if (w) {
mui_window_select(w);
return;
}
w = mui_alert(g->ui, C2_PT(0,0),
"About MII",
"Version " UI_VERSION "\n"
"Build " __DATE__ " " __TIME__,
MUI_ALERT_INFO);
mui_window_set_id(w, FCC('a','b','o','t'));
}
static mii_machine_config_t g_machine_conf = {};
static mii_loadbin_conf_t g_loadbin_conf = {};
/* this is heavily endian dependend, as is the FCC macro */
#define FCC_INDEX(_fcc) (isdigit(_fcc>>24) ? ((_fcc >> 24) - '0') : 0)
int
_test_menubar_action(
mui_window_t *win,
void * cb_param,
uint32_t what,
void * param)
{
cg_ui_t *g = cb_param;
printf("%s %4.4s\n", __func__, (char*)&what);
static int video_mode = 0;
static int audio_mute = 0;
switch (what) {
case MUI_MENUBAR_ACTION_PREPARE: {
mui_menu_item_t * items = param;
if (items == m_video_menu) {
printf("%s prepare video %d\n", __func__, video_mode);
for (int i = 0; m_video_menu[i].title; i++) {
switch (m_video_menu[i].uid) {
case FCC('v','d','c','0'):
case FCC('v','d','c','1'):
case FCC('v','d','c','2'): {
int idx = FCC_INDEX(m_video_menu[i].uid);
if (video_mode == idx)
strcpy(m_video_menu[i].mark, MUI_GLYPH_TICK);
else
m_video_menu[i].mark[0] = 0;
} break;
}
}
} else if (items == m_audio_menu) {
printf("%s prepare audio %d\n", __func__, audio_mute);
for (int i = 0; m_audio_menu[i].title; i++) {
switch (m_audio_menu[i].uid) {
case FCC('a','u','d','0'):
if (audio_mute)
strcpy(m_audio_menu[i].mark, MUI_GLYPH_TICK);
else
m_audio_menu[i].mark[0] = 0;
break;
}
}
} else {
printf("%s prepare (%s)\n", __func__, items[0].title);
}
} break;
case MUI_MENUBAR_ACTION_SELECT: {
mui_menu_item_t * item = param;
printf("%s Selected %4.4s '%s'\n", __func__,
(char*)&item->uid, item->title);
switch (item->uid) {
case FCC('a','b','o','t'): {
_test_show_about(g);
} break;
case FCC('q','u','i','t'): {
printf("%s Quit\n", __func__);
} break;
case FCC('s','l','o','t'): {
mii_mui_configure_slots(g->ui, &g_machine_conf);
} break;
case FCC('l', 'r', 'u', 'n'): {
mii_mui_load_binary(g->ui, &g_loadbin_conf);
} break;
case FCC('a','u','d','0'):
audio_mute = !audio_mute;
break;
case FCC('v','d','C','l'): {
// printf("%s Cycle video\n", __func__);
video_mode = (video_mode + 1) % 3;
} break;
case FCC('v','d','c','0'):
case FCC('v','d','c','1'):
case FCC('v','d','c','2'):
video_mode = FCC_INDEX(item->uid);
break;
default:
printf("%s menu item %4.4s %s IGNORED\n",
__func__, (char*)&item->uid, item->title);
break;
}
} break;
default:
printf("%s %4.4s IGNORED?\n", __func__, (char*)&what);
break;
}
return 0;
}
static void *
_init(
struct mui_t * ui,
struct mui_plug_t * plug,
mui_drawable_t * pix)
{
mui_init(ui);
ui->screen_size = pix->pix.size;
asprintf(&ui->pref_directory, "%s/.local/share/mii", getenv("HOME"));
cg_ui_t *g = calloc(1, sizeof(*g));
g->ui = ui;
printf("%s\n", __func__);
mui_window_t * mbar = mui_menubar_new(ui);
mui_window_set_action(mbar, _test_menubar_action, g);
mui_menubar_add_simple(mbar, MUI_GLYPH_APPLE,
FCC('a','p','p','l'),
m_apple_menu);
mui_menubar_add_simple(mbar, "File",
FCC('f','i','l','e'),
m_file_menu);
mui_menubar_add_simple(mbar, "Machine",
FCC('m','a','c','h'),
m_machine_menu);
mui_menubar_add_simple(mbar, "CPU",
FCC('c','p','u','m'),
m_cpu_menu);
// mii_mui_configure_slots(g->ui, &g_machine_conf);
// mii_mui_load_binary(g->ui, &g_loadbin_conf);
// mii_mui_load_1mbrom(g->ui, &g_machine_conf.slot[0].conf.rom1mb);
// mii_mui_load_2dsk(g->ui,
// &g_machine_conf.slot[0].conf.disk2, MII_2DSK_DISKII);
#if 0
mui_alert(ui, C2_PT(0,0),
"Testing one Two",
"Do you really want the printer to catch fire?\n"
"This operation cannot be cancelled.",
MUI_ALERT_WARN);
#endif
#if 1
mui_stdfile_get(ui,
C2_PT(0, 0),
"Select image for SmartPort card",
"\\.(hdv|po|2mg)$",
getenv("HOME"));
#endif
return g;
}
static void
_dispose(
void *_ui)
{
cg_ui_t *ui = _ui;
mui_dispose(ui->ui);
free(ui);
}
static int
_draw(
struct mui_t *ui,
void *param,
mui_drawable_t *dr,
uint16_t all)
{
// cg_ui_t *g = param;
mui_draw(ui, dr, all);
return 1;
}
static int
_event(
struct mui_t *ui,
void *param,
mui_event_t *event)
{
// cg_ui_t *g = param;
// printf("%s %d\n", __func__, event->type);
mui_handle_event(ui, event);
return 0;
}
mui_plug_t mui_plug = {
.init = _init,
.dispose = _dispose,
.draw = _draw,
.event = _event,
};

View File

@ -1,323 +0,0 @@
/*
* mii_emu.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <time.h>
#include <limits.h>
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_BUTTON_TRIGGER_ON_RELEASE
#include "nuklear.h"
#define NK_XLIB_GL3_IMPLEMENTATION
#define NK_XLIB_LOAD_OPENGL_EXTENSIONS
#include "nuklear_xlib_gl3.h"
#include "mii.h"
#include "mish.h"
#define WINDOW_WIDTH 1280
#define WINDOW_HEIGHT 720 // (10 + (192) * 3)
#define MAX_VERTEX_BUFFER 512 * 1024
#define MAX_ELEMENT_BUFFER 128 * 1024
void
mii_nuklear(
mii_t *mii,
struct nk_context *ctx);
void
mii_nuklear_init(
mii_t *mii,
struct nk_context *ctx);
/* ===============================================================
*
* DEMO
*
* ===============================================================*/
struct XWindow {
Display *dpy;
Window win;
XVisualInfo *vis;
Colormap cmap;
XSetWindowAttributes swa;
XWindowAttributes attr;
GLXFBConfig fbc;
Atom wm_delete_window;
int width, height;
};
static int gl_err = nk_false;
static int gl_error_handler(Display *dpy, XErrorEvent *ev)
{NK_UNUSED(dpy); NK_UNUSED(ev); gl_err = nk_true;return 0;}
static void
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputs("\n", stderr);
exit(EXIT_FAILURE);
}
static int
has_extension(const char *string, const char *ext)
{
const char *start, *where, *term;
where = strchr(ext, ' ');
if (where || *ext == '\0')
return nk_false;
for (start = string;;) {
where = strstr((const char*)start, ext);
if (!where) break;
term = where + strlen(ext);
if (where == start || *(where - 1) == ' ') {
if (*term == ' ' || *term == '\0')
return nk_true;
}
start = term;
}
return nk_false;
}
struct nk_font *nk_main_font = NULL;
/* global now, mish commands use it */
mii_t g_mii = {};
int
main(
int argc,
const char *argv[])
{
/* Platform */
int running = 1;
struct XWindow win;
GLXContext glContext;
struct nk_context *ctx;
struct nk_colorf bg;
mii_init(&g_mii);
int idx = 1;
uint32_t flags = MII_INIT_DEFAULT;
int r = mii_argv_parse(&g_mii, argc, argv, &idx, &flags);
if (r == 0) {
printf("mii: Invalid argument %s, skipped\n", argv[idx]);
} else if (r == -1)
exit(1);
mii_prepare(&g_mii, flags);
mish_prepare(1);
mish_set_command_parameter(MISH_FCC('m','i','i',' '), &g_mii);
printf("MISH_TELNET_PORT = %s\n", getenv("MISH_TELNET_PORT"));
memset(&win, 0, sizeof(win));
win.dpy = XOpenDisplay(NULL);
if (!win.dpy) die("Failed to open X display\n");
{
/* check glx version */
int glx_major, glx_minor;
if (!glXQueryVersion(win.dpy, &glx_major, &glx_minor))
die("[X11]: Error: Failed to query OpenGL version\n");
if ((glx_major == 1 && glx_minor < 3) || (glx_major < 1))
die("[X11]: Error: Invalid GLX version!\n");
}
{
/* find and pick matching framebuffer visual */
int fb_count;
static GLint attr[] = {
GLX_X_RENDERABLE, True,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_ALPHA_SIZE, 8,
GLX_DEPTH_SIZE, 24,
GLX_STENCIL_SIZE, 8,
GLX_DOUBLEBUFFER, True,
None
};
GLXFBConfig *fbc;
fbc = glXChooseFBConfig(win.dpy, DefaultScreen(win.dpy), attr, &fb_count);
if (!fbc) die("[X11]: Error: failed to retrieve framebuffer configuration\n");
{
/* pick framebuffer with most samples per pixel */
int i;
int fb_best = -1, best_num_samples = -1;
for (i = 0; i < fb_count; ++i) {
XVisualInfo *vi = glXGetVisualFromFBConfig(win.dpy, fbc[i]);
if (vi) {
int sample_buffer, samples;
glXGetFBConfigAttrib(win.dpy, fbc[i], GLX_SAMPLE_BUFFERS, &sample_buffer);
glXGetFBConfigAttrib(win.dpy, fbc[i], GLX_SAMPLES, &samples);
if ((fb_best < 0) || (sample_buffer && samples > best_num_samples))
fb_best = i, best_num_samples = samples;
XFree(vi);
}
}
win.fbc = fbc[fb_best];
XFree(fbc);
win.vis = glXGetVisualFromFBConfig(win.dpy, win.fbc);
}
}
{
/* create window */
win.cmap = XCreateColormap(win.dpy, RootWindow(win.dpy, win.vis->screen), win.vis->visual, AllocNone);
win.swa.colormap = win.cmap;
win.swa.background_pixmap = None;
win.swa.border_pixel = 0;
win.swa.event_mask =
ExposureMask | KeyPressMask | KeyReleaseMask |
ButtonPress | ButtonReleaseMask| ButtonMotionMask |
Button1MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask|
PointerMotionMask| StructureNotifyMask;
win.win = XCreateWindow(win.dpy, RootWindow(win.dpy, win.vis->screen), 0, 0,
WINDOW_WIDTH, WINDOW_HEIGHT, 0, win.vis->depth, InputOutput,
win.vis->visual, CWBorderPixel|CWColormap|CWEventMask, &win.swa);
if (!win.win) die("[X11]: Failed to create window\n");
XFree(win.vis);
char title[128];
sprintf(title, "MII //e Emulator");
char *telnet = getenv("MISH_TELNET_PORT");
if (telnet)
sprintf(title + strlen(title), " (telnet port %s)", telnet);
else
sprintf(title + strlen(title), " (telnet disabled)");
XStoreName(win.dpy, win.win, title);
XMapWindow(win.dpy, win.win);
win.wm_delete_window = XInternAtom(win.dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(win.dpy, win.win, &win.wm_delete_window, 1);
}
{
/* create opengl context */
typedef GLXContext(*glxCreateContext)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
int(*old_handler)(Display*, XErrorEvent*) = XSetErrorHandler(gl_error_handler);
const char *extensions_str = glXQueryExtensionsString(win.dpy, DefaultScreen(win.dpy));
glxCreateContext create_context = (glxCreateContext)
glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB");
gl_err = nk_false;
if (!has_extension(extensions_str, "GLX_ARB_create_context") || !create_context) {
fprintf(stdout, "[X11]: glXCreateContextAttribARB() not found...\n");
fprintf(stdout, "[X11]: ... using old-style GLX context\n");
glContext = glXCreateNewContext(win.dpy, win.fbc, GLX_RGBA_TYPE, 0, True);
} else {
GLint attr[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 0,
None
};
glContext = create_context(win.dpy, win.fbc, 0, True, attr);
XSync(win.dpy, False);
if (gl_err || !glContext) {
/* Could not create GL 3.0 context. Fallback to old 2.x context.
* If a version below 3.0 is requested, implementations will
* return the newest context version compatible with OpenGL
* version less than version 3.0.*/
attr[1] = 1; attr[3] = 0;
gl_err = nk_false;
fprintf(stdout, "[X11] Failed to create OpenGL 3.0 context\n");
fprintf(stdout, "[X11] ... using old-style GLX context!\n");
glContext = create_context(win.dpy, win.fbc, 0, True, attr);
}
}
XSync(win.dpy, False);
XSetErrorHandler(old_handler);
if (gl_err || !glContext)
die("[X11]: Failed to create an OpenGL context\n");
glXMakeCurrent(win.dpy, win.win, glContext);
}
//extern const unsigned char mii_proggy_data[];
//extern const unsigned int mii_proggy_size;
extern const unsigned char mii_droid_data[];
extern const unsigned int mii_droid_size;
ctx = nk_x11_init(win.dpy, win.win);
/* Load Fonts: if none of these are loaded a default font will be used */
{
struct nk_font_atlas *atlas;
nk_x11_font_stash_begin(&atlas);
struct nk_font_config cfg = nk_font_config(0);
#if 0
nk_rune ranges[] = {
0x0020, 0x007E, /* Ascii */
0x00A1, 0x00FF, /* Symbols + Umlaute */
0
};
#endif
cfg.range = nk_font_default_glyph_ranges();
cfg.oversample_h = cfg.oversample_v = 1;
cfg.pixel_snap = true;
nk_main_font = nk_font_atlas_add_from_memory(atlas,
(void*)mii_droid_data, mii_droid_size, 20, &cfg);
nk_x11_font_stash_end();
/*nk_style_load_all_cursors(ctx, atlas->cursors);*/
if (nk_main_font)
nk_style_set_font(ctx, &nk_main_font->handle);
}
mii_nuklear_init(&g_mii, ctx);
bg.r = 0.0f; bg.g = 0.0f; bg.b = 0.0f; bg.a = 1.0f;
// bg.r = 0.8f; bg.g = 0.8f; bg.b = 0.8f; bg.a = 1.0f;
while (running) {
/* Input */
XEvent evt;
nk_input_begin(ctx);
while (XPending(win.dpy)) {
XNextEvent(win.dpy, &evt);
if (evt.type == ClientMessage) goto cleanup;
if (XFilterEvent(&evt, win.win)) continue;
nk_x11_handle_event(&evt);
}
nk_input_end(ctx);
/* GUI */
mii_nuklear(&g_mii, ctx);
/* Draw */
XGetWindowAttributes(win.dpy, win.win, &win.attr);
glViewport(0, 0, win.width, win.height);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(bg.r, bg.g, bg.b, bg.a);
/* IMPORTANT: `nk_x11_render` modifies some global OpenGL state
* with blending, scissor, face culling, depth test and viewport and
* defaults everything back into a default state.
* Make sure to either a.) save and restore or b.) reset your own state after
* rendering the UI. */
nk_x11_render(NK_ANTI_ALIASING_ON, MAX_VERTEX_BUFFER, MAX_ELEMENT_BUFFER);
glXSwapBuffers(win.dpy, win.win);
}
mii_dispose(&g_mii);
cleanup:
nk_x11_shutdown();
glXMakeCurrent(win.dpy, 0, 0);
glXDestroyContext(win.dpy, glContext);
XUnmapWindow(win.dpy, win.win);
XFreeColormap(win.dpy, win.cmap);
XDestroyWindow(win.dpy, win.win);
XCloseDisplay(win.dpy);
return 0;
}

View File

@ -1,9 +0,0 @@
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mii_
#include "incbin.h"
INCBIN(proggy, "fonts/ProggyClean.ttf");
INCBIN(droid, "fonts/DroidSans.ttf");

View File

@ -1,500 +0,0 @@
/*
* mii_nuklear.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_sw.h"
#include "mii_thread.h"
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_BUTTON_TRIGGER_ON_RELEASE
#include "nuklear.h"
#include "nuklear_xlib_gl3.h"
//#include "stb_image_write.h"
#include <GL/gl.h>
#include <GL/glx.h>
#define _NK_RGBA(_r,_g,_b,_a) {.r=_r,.g=_g,.b=_b,.a=_a}
static const struct nk_color style[NK_COLOR_COUNT] = {
[NK_COLOR_TEXT] = _NK_RGBA(0, 0, 0, 255),
[NK_COLOR_WINDOW] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_HEADER] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_BORDER] = _NK_RGBA(0, 0, 0, 255),
[NK_COLOR_BUTTON] = _NK_RGBA(185, 185, 185, 255),
[NK_COLOR_BUTTON_HOVER] = _NK_RGBA(170, 170, 170, 255),
[NK_COLOR_BUTTON_ACTIVE] = _NK_RGBA(160, 160, 160, 255),
[NK_COLOR_TOGGLE] = _NK_RGBA(150, 150, 150, 255),
[NK_COLOR_TOGGLE_HOVER] = _NK_RGBA(120, 120, 120, 255),
[NK_COLOR_TOGGLE_CURSOR] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_SELECT] = _NK_RGBA(190, 190, 190, 255),
[NK_COLOR_SELECT_ACTIVE] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_SLIDER] = _NK_RGBA(190, 190, 190, 255),
[NK_COLOR_SLIDER_CURSOR] = _NK_RGBA(80, 80, 80, 255),
[NK_COLOR_SLIDER_CURSOR_HOVER] = _NK_RGBA(70, 70, 70, 255),
[NK_COLOR_SLIDER_CURSOR_ACTIVE] = _NK_RGBA(60, 60, 60, 255),
[NK_COLOR_PROPERTY] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_EDIT] = _NK_RGBA(150, 150, 150, 255),
[NK_COLOR_EDIT_CURSOR] = _NK_RGBA(0, 0, 0, 255),
[NK_COLOR_COMBO] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_CHART] = _NK_RGBA(160, 160, 160, 255),
[NK_COLOR_CHART_COLOR] = _NK_RGBA(45, 45, 45, 255),
[NK_COLOR_CHART_COLOR_HIGHLIGHT] = _NK_RGBA( 255, 0, 0, 255),
[NK_COLOR_SCROLLBAR] = _NK_RGBA(180, 180, 180, 255),
[NK_COLOR_SCROLLBAR_CURSOR] = _NK_RGBA(140, 140, 140, 255),
[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = _NK_RGBA(150, 150, 150, 255),
[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = _NK_RGBA(160, 160, 160, 255),
[NK_COLOR_TAB_HEADER] = _NK_RGBA(180, 180, 180, 255),
};
static GLuint screen_texture;
static struct nk_image screen_nk;
extern struct nk_font *nk_main_font;
struct nk_font *nk_mono_font = NULL;
extern const unsigned char mii_proggy_data[];
extern const unsigned int mii_proggy_size;
extern const unsigned char mii_droid_data[];
extern const unsigned int mii_droid_size;
void
mii_nuklear_init(
mii_t *mii,
struct nk_context *ctx)
{
nk_style_from_table(ctx, style);
{
struct nk_font_atlas *atlas;
nk_x11_font_stash_begin(&atlas);
struct nk_font_config cfg = nk_font_config(0);
#if 0
nk_rune ranges[] = {
0x0020, 0x007E, /* Ascii */
0x00A1, 0x00FF, /* Symbols + Umlaute */
0
};
#endif
cfg.range = nk_font_default_glyph_ranges();
cfg.oversample_h = cfg.oversample_v = 1;
cfg.pixel_snap = true;
struct nk_font *nkf = nk_font_atlas_add_from_memory(atlas,
(void*)mii_proggy_data, mii_proggy_size, 20, &cfg);
nk_x11_font_stash_end();
nk_mono_font = nkf;
}
glGenTextures(1, &screen_texture);
glBindTexture(GL_TEXTURE_2D, screen_texture);
glTexImage2D(GL_TEXTURE_2D, 0, 4,
MII_VRAM_WIDTH,
MII_VRAM_HEIGHT, 0, GL_RGBA,
GL_UNSIGNED_BYTE,
mii->video.pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// printf("%s texture created %d\n", __func__, screen_texture);
// display opengl error
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
printf("Error creating texture: %d\n", err);
}
screen_nk = nk_subimage_id(
screen_texture, MII_VRAM_WIDTH, MII_VRAM_HEIGHT,
nk_rect(0, 0, MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT));
/* start the CPU/emulator thread */
mii_thread_start(mii);
}
extern int disk2_debug;
int show_zero_page = 0;
static void
mii_nuklear_handle_input(
mii_t *mii,
struct nk_context *ctx)
{
struct nk_input *in = &ctx->input;
if (in->keyboard.text_len) {
// printf("INPUT %d %02x %s\n",
// in->keyboard.text_len, in->keyboard.text[0], in->keyboard.text);
if (in->keyboard.text_len < 4) {
mii_keypress(mii, in->keyboard.text[0]);
} else if (in->keyboard.text_len > 1) {
uint32_t *raw = ((uint32_t *) in->keyboard.text);
for (int ki = 0; ki < in->keyboard.text_len / 4; ki ++) {
uint32_t key = (raw[ki] >> 16) & 0xffff;
uint8_t down = raw[ki] & 0xff;
printf("KEY %04x %s\n", key, down ? "down" : "up");
if (down) {
if (key == 0xffc9) { // F12
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
mii_th_signal_t sig = {
.cmd = SIGNAL_RESET,
.data = nk_input_is_key_down(in, NK_KEY_SHIFT)
};
mii_th_fifo_write(mii_thread_get_fifo(mii), sig);
printf("RESET\n");
}
} else if (key == 0xffc8) { // F11
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
mii_th_signal_t sig = {
.cmd = SIGNAL_STOP,
};
mii_th_fifo_write(mii_thread_get_fifo(mii), sig);
printf("STOP\n");
}
} else if (key == 0xffc7) { // F10
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
mii_th_signal_t sig = {
.cmd = SIGNAL_STEP,
};
mii_th_fifo_write(mii_thread_get_fifo(mii), sig);
printf("STEP\n");
}
} else if (key == 0xffc6) { // F9
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
mii_th_signal_t sig = {
.cmd = SIGNAL_RUN,
};
mii_th_fifo_write(mii_thread_get_fifo(mii), sig);
printf("RUN\n");
}
} else if (key == 0xffc2) { // F5
mii->speed = 1.0;
printf("Speed Normal (1MHz)\n");
} else if (key == 0xffc3) { // F6
mii->speed = 4;
printf("Speed Fast (4MHz)\n");
}
}
if (key == 0xffeb || key == 0xffec) { // super left/right
key -= 0xffeb;
mii_bank_t *bank = &mii->bank[MII_BANK_MAIN];
uint8_t old = mii_bank_peek(bank, 0xc061 + key);
mii_bank_poke(bank, 0xc061 + key, down ? 0x80 : 0);
if (!!down != !!old) {
printf("Apple %s %s\n", key ? "Open" : "Close",
down ? "down" : "up");
}
}
}
}
in->keyboard.text_len = 0;
} else {
mii_th_signal_t sig = {.cmd = -1 };
if (nk_input_is_key_pressed(in, NK_KEY_ENTER))
sig.data = 0x0d;
else if (nk_input_is_key_pressed(in, NK_KEY_BACKSPACE))
sig.data = 0x08;
else if (nk_input_is_key_pressed(in, NK_KEY_DEL))
sig.data = 0x7f;
else if (nk_input_is_key_pressed(in, NK_KEY_UP))
sig.data = 'K' - 'A' + 1;
else if (nk_input_is_key_pressed(in, NK_KEY_DOWN))
sig.data = 'J' - 'A' + 1;
else if (nk_input_is_key_pressed(in, NK_KEY_LEFT))
sig.data = 'H' - 'A' + 1;
else if (nk_input_is_key_pressed(in, NK_KEY_RIGHT))
sig.data = 'U' - 'A' + 1;
else if (nk_input_is_key_pressed(in, NK_KEY_ESCAPE))
sig.data = 0x1b;
if (sig.data) {
// mii_th_fifo_write(mii_thread_get_fifo(mii), sig);
// printf("Key %d\n", sig.data);
mii_keypress(mii, sig.data);
}
}
}
void
mii_nuklear(
mii_t *mii,
struct nk_context *ctx)
{
if (mii->video.frame_count != mii->video.frame_drawn) {
mii->video.frame_drawn = mii->video.frame_count;
// update texture with new pixels; we only need 192 lines, the others
// are padding for the power of 2 texture
glBindTexture(GL_TEXTURE_2D, screen_texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
MII_VRAM_WIDTH,
MII_VIDEO_HEIGHT, GL_RGBA,
GL_UNSIGNED_BYTE,
mii->video.pixels);
}
mii_nuklear_handle_input(mii, ctx);
int height = 720 - 8;
int width = MII_VIDEO_WIDTH * (height / (float)MII_VIDEO_HEIGHT);
int xpos = 1280 / 2 - width / 2;
{
struct nk_style *s = &ctx->style;
nk_style_push_color(ctx, &s->window.background,
nk_rgba(0,0,0, 255));
nk_style_push_style_item(ctx, &s->window.fixed_background,
nk_style_item_color(nk_rgba(0,0,0, 255)));
if (nk_begin(ctx, "Apple //e Enhanced",
nk_rect(xpos, 0, width + 10, height + 20),
NK_WINDOW_NO_SCROLLBAR)) {
nk_layout_row_static(ctx, height, width, 1);
static int was_in = -1;
if (nk_widget_is_hovered(ctx) && mii->mouse.enabled) {
if (was_in != 1) {
was_in = 1;
ctx->input.mouse.grab = 1;
// printf("IN\n");
}
struct nk_rect bounds = nk_widget_bounds(ctx);
// normalize mouse coordinates
double x = ctx->input.mouse.pos.x - bounds.x;
double y = ctx->input.mouse.pos.y - bounds.y;
// get mouse button state
int button = ctx->input.mouse.buttons[NK_BUTTON_LEFT].down;
// clamp coordinates inside bounds
double vw = bounds.w;
double vh = bounds.h;
double mw = mii->mouse.max_x - mii->mouse.min_x;
double mh = mii->mouse.max_y - mii->mouse.min_y;
mii->mouse.x = mii->mouse.min_x + (x * mw / vw) + 0.5;
mii->mouse.y = mii->mouse.min_y + (y * mh / vh) + 0.5;
mii->mouse.button = button;
// printf("Mouse is %d %d\n", (int)mii->mouse.x, (int)mii->mouse.y);
} else {
if (was_in == 1) {
was_in = 0;
ctx->input.mouse.ungrab = 1;
// printf("OUT\n");
}
}
nk_image(ctx, screen_nk);
nk_end(ctx);
nk_style_pop_color(ctx);
nk_style_pop_style_item(ctx);
}
}
struct nk_rect bounds = { .x = 0, .y = 0, .w = xpos, .h = height };
// nk_window_get_bounds(ctx);
bool in = nk_input_is_mouse_hovering_rect(&ctx->input, bounds);
static bool menu_open = false;
if (in || menu_open) {
struct nk_style *s = &ctx->style;
nk_style_push_color(ctx, &s->window.background,
nk_rgba(175, 175, 175, 255));
nk_style_push_style_item(ctx, &s->window.fixed_background,
nk_style_item_color(nk_rgba(175, 175, 175, 255)));
if (nk_begin(ctx, "Left Bar",
nk_rect(0, 0, xpos, height + 20),
NK_WINDOW_NO_SCROLLBAR)) {
#if 0
nk_menubar_begin(ctx);
nk_layout_row_begin(ctx, NK_STATIC, 25, 2);
nk_layout_row_push(ctx, 60);
bool menu = false;
if (nk_menu_begin_label(ctx, "MII", NK_TEXT_LEFT,
nk_vec2(140, 200))) {
static size_t prog = 40;
static float slider = 0.5;
static int check = nk_true;
nk_layout_row_dynamic(ctx, 25, 1);
if (nk_menu_item_label(ctx, "Hide", NK_TEXT_LEFT))
;//show_menu = nk_false;
if (nk_menu_item_label(ctx, "About", NK_TEXT_LEFT))
;//show_app_about = nk_true;
nk_progress(ctx, &prog, 100, NK_MODIFIABLE);
nk_slider_float(ctx, 0.01, &slider, 1.0, 0.05);
nk_checkbox_label(ctx, "Mute", &check);
nk_menu_end(ctx);
menu = true;
}
menu_open = menu;
nk_menubar_end(ctx);
nk_layout_space_end(ctx);
#endif
int rw = xpos - 8;
// nk_layout_row_dynamic(ctx, 4, 1);
// nk_spacing(ctx, 1);
nk_layout_row_static(ctx, 30, rw, 1);
if (nk_button_label(ctx, "C-RESET"))
mii_reset(mii, false);
if (nk_button_label(ctx, "C-OA-RESET"))
mii_reset(mii, true);
// nk_layout_row_dynamic(ctx, 0, 1);
nk_layout_row_begin(ctx, NK_STATIC, 30, 2);
nk_layout_row_push(ctx, 20);
nk_label(ctx, "V:", NK_TEXT_LEFT);
nk_layout_row_push(ctx, rw - 20 - 4);
const char *video_modes[] = {
"Color",
"Green",
"Amber",
};
mii->video.color_mode = nk_combo(ctx,
video_modes, NK_LEN(video_modes),
mii->video.color_mode, 25, nk_vec2(80,200));
nk_layout_space_end(ctx);
nk_layout_row_begin(ctx, NK_STATIC, 30, 1);
nk_layout_row_push(ctx, rw);
const char *speed[] = {
"1 MHz",
"Slow",
"Fast",
};
nk_label(ctx, "Speed:", NK_TEXT_LEFT);
nk_layout_row_push(ctx, 100);
int spi = mii->speed > .95 && mii->speed < 1.05 ? 0 :
mii->speed < 0.9 ? 1 : 2;
spi = nk_combo(ctx,
speed, NK_LEN(speed),
spi, 25, nk_vec2(80,200));
mii->speed = spi == 0 ? 1.0 : spi == 1 ? 0.2 : 4.0;
nk_layout_space_end(ctx);
}
nk_end(ctx);
nk_style_pop_color(ctx);
nk_style_pop_style_item(ctx);
}
if ( 0 && nk_begin(ctx, "Controls",
nk_rect(width, 0, 350, 10 + 192 * 3),
NK_WINDOW_NO_SCROLLBAR)) {
nk_layout_row_static(ctx, 30, 110, 2);
if (nk_button_label(ctx, "C-RESET"))
mii_reset(mii, false);
if (nk_button_label(ctx, "C-OA-RESET"))
mii_reset(mii, true);
#if 0
if (nk_button_label(ctx, "Screenshot")) {
stbi_write_png("screen.png",
MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT, 4, mii->video.pixels,
MII_VRAM_WIDTH * 4);
printf("Screenshot taken\n");
}
#endif
nk_layout_row_dynamic(ctx, 30, 4);
nk_label(ctx, "Speed:", NK_TEXT_CENTERED);
if (nk_option_label(ctx, "Slow", mii->speed < 0.9)) mii->speed = 0.2;
if (nk_option_label(ctx, "1 MHz", mii->speed > .95 && mii->speed < 1.05)) mii->speed = 1.0;
if (nk_option_label(ctx, "Fast", mii->speed > 1.1 && mii->speed < 4.1)) mii->speed = 4.0;
nk_layout_row_dynamic(ctx, 30, 4);
nk_label(ctx, "Video:", NK_TEXT_CENTERED);
if (nk_option_label(ctx, "Color", mii->video.color_mode == MII_VIDEO_COLOR))
mii->video.color_mode = MII_VIDEO_COLOR;
if (nk_option_label(ctx, "Green", mii->video.color_mode == MII_VIDEO_GREEN))
mii->video.color_mode = MII_VIDEO_GREEN;
if (nk_option_label(ctx, "Amber", mii->video.color_mode == MII_VIDEO_AMBER))
mii->video.color_mode = MII_VIDEO_AMBER;
#if 0
nk_layout_row_dynamic(ctx, 20, 1);
nk_style_set_font(ctx, &nk_mono_font->handle);
struct nk_color save = ctx->style.window.background;
ctx->style.window.background = (struct nk_color)_NK_RGBA(0, 0, 0, 255);
struct nk_color fore = (struct nk_color)_NK_RGBA(0, 255, 0, 255);
char label[64];
mii_dasm_t _dasm = {};
mii_dasm_t *dasm = &_dasm;
mii_dasm_init(dasm, mii, 0);
dasm->mii = mii;
// display the last few cycles up to the PC
for (int di = 0; di < 3; di++) {
int pci = (mii_trace.idx + MII_PC_LOG_SIZE - 3 + di) % MII_PC_LOG_SIZE;
dasm->pc = mii_trace.log[pci];
mii_dasm_step(dasm, label, sizeof(label));
if (di == 2)
label[0] = '*';
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
}
// and the (potentially) next instruction here
mii_dasm_step(dasm, label, sizeof(label));
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
sprintf(label, "A:%02X X:%02X Y:%02X S:%02X",
mii->cpu.A, mii->cpu.X, mii->cpu.Y, mii->cpu.S);
nk_label_colored(ctx, label, NK_TEXT_CENTERED, fore);
char n[] = {'C','Z','I','D','B','V','N'};
label[0] = 0;
sprintf(label, "%04x ", mii->cpu.PC);
for (int i = 0; i < 7; i++)
sprintf(label + strlen(label), "%c%d ", n[i],
mii->cpu.P.P[i]);
nk_label_colored(ctx, label, NK_TEXT_CENTERED, fore);
ctx->style.window.background = save;
nk_style_set_font(ctx, &nk_main_font->handle);
#endif
nk_layout_row_static(ctx, 30, 30, 3);
if (nk_button_symbol(ctx, NK_SYMBOL_RECT_SOLID)) {
mii_th_fifo_write(mii_thread_get_fifo(mii), (mii_th_signal_t){.cmd = SIGNAL_STOP});
}
if (nk_button_symbol(ctx, NK_SYMBOL_PLUS)) {
mii_th_fifo_write(mii_thread_get_fifo(mii), (mii_th_signal_t){.cmd = SIGNAL_STEP});
}
if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT)) {
mii_th_fifo_write(mii_thread_get_fifo(mii), (mii_th_signal_t){.cmd = SIGNAL_RUN});
}
nk_layout_row_dynamic(ctx, 20, 3);
{
char label[32];
sprintf(label, "CPU: %.1fMHz", mii->speed_current);
nk_label(ctx, label, NK_TEXT_CENTERED);
}
#if 0
nk_layout_row_dynamic(ctx, 20, 2);
nk_checkbox_label(ctx, "Disk II Debug", &disk2_debug);
nk_checkbox_label(ctx, "Slowmo", &mii_SLOW);
// nk_checkbox_label(ctx, "Zero Page", &show_zero_page);
#endif
nk_end(ctx);
}
if (show_zero_page) {
if (nk_begin(ctx, "Zero Page",
nk_rect(0, 10 + 192 * 3, 600, 10 + 16 * 20),
0 )) {
nk_layout_row_dynamic(ctx, 20, 1);
nk_style_set_font(ctx, &nk_mono_font->handle);
struct nk_color fore = (struct nk_color)_NK_RGBA(0, 255, 0, 255);
uint8_t *zp = mii->bank[0].mem;
char label[128];
for (int ri = 0; ri < (256 / 16); ri++) {
sprintf(label, "%02x: ", ri * 16);
for (int ci = 0; ci < 16; ci++) {
sprintf(label + strlen(label), "%02X ",
zp[ri * 16 + ci]);
}
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
}
nk_style_set_font(ctx, &nk_main_font->handle);
nk_end(ctx);
}
}
}

View File

@ -1,22 +0,0 @@
/*
* mii_stb.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_BUTTON_TRIGGER_ON_RELEASE
#define NK_IMPLEMENTATION
#include "nuklear.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

File diff suppressed because it is too large Load Diff

View File

@ -1,758 +0,0 @@
/*
* Nuklear - v1.17 - public domain
* no warrenty implied; use at your own risk.
* authored from 2015-2016 by Micha Mettke
*/
/*
* ==============================================================
*
* API
*
* ===============================================================
*/
#ifndef NK_XLIB_GL3_H_
#define NK_XLIB_GL3_H_
#include <X11/Xlib.h>
NK_API struct nk_context* nk_x11_init(Display *dpy, Window win);
NK_API void nk_x11_font_stash_begin(struct nk_font_atlas **atlas);
NK_API void nk_x11_font_stash_end(void);
NK_API int nk_x11_handle_event(XEvent *evt);
NK_API void nk_x11_render(enum nk_anti_aliasing, int max_vertex_buffer, int max_element_buffer);
NK_API void nk_x11_shutdown(void);
NK_API int nk_x11_device_create(void);
NK_API void nk_x11_device_destroy(void);
#endif
/*
* ==============================================================
*
* IMPLEMENTATION
*
* ===============================================================
*/
#ifdef NK_XLIB_GL3_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/Xlocale.h>
#include <GL/gl.h>
#include <GL/glx.h>
#ifndef NK_X11_DOUBLE_CLICK_LO
#define NK_X11_DOUBLE_CLICK_LO 20
#endif
#ifndef NK_X11_DOUBLE_CLICK_HI
#define NK_X11_DOUBLE_CLICK_HI 200
#endif
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
#include <GL/glxext.h>
/* GL_ARB_vertex_buffer_object */
typedef void(*nkglGenBuffers)(GLsizei, GLuint*);
typedef void(*nkglBindBuffer)(GLenum, GLuint);
typedef void(*nkglBufferData)(GLenum, GLsizeiptr, const GLvoid*, GLenum);
typedef void(*nkglBufferSubData)(GLenum, GLintptr, GLsizeiptr, const GLvoid*);
typedef void*(*nkglMapBuffer)(GLenum, GLenum);
typedef GLboolean(*nkglUnmapBuffer)(GLenum);
typedef void(*nkglDeleteBuffers)(GLsizei, GLuint*);
/* GL_ARB_vertex_array_object */
typedef void (*nkglGenVertexArrays)(GLsizei, GLuint*);
typedef void (*nkglBindVertexArray)(GLuint);
typedef void (*nkglDeleteVertexArrays)(GLsizei, const GLuint*);
/* GL_ARB_vertex_program / GL_ARB_fragment_program */
typedef void(*nkglVertexAttribPointer)(GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid*);
typedef void(*nkglEnableVertexAttribArray)(GLuint);
typedef void(*nkglDisableVertexAttribArray)(GLuint);
/* GL_ARB_framebuffer_object */
typedef void(*nkglGenerateMipmap)(GLenum target);
/* GLSL/OpenGL 2.0 core */
typedef GLuint(*nkglCreateShader)(GLenum);
typedef void(*nkglShaderSource)(GLuint, GLsizei, const GLchar**, const GLint*);
typedef void(*nkglCompileShader)(GLuint);
typedef void(*nkglGetShaderiv)(GLuint, GLenum, GLint*);
typedef void(*nkglGetShaderInfoLog)(GLuint, GLsizei, GLsizei*, GLchar*);
typedef void(*nkglDeleteShader)(GLuint);
typedef GLuint(*nkglCreateProgram)(void);
typedef void(*nkglAttachShader)(GLuint, GLuint);
typedef void(*nkglDetachShader)(GLuint, GLuint);
typedef void(*nkglLinkProgram)(GLuint);
typedef void(*nkglUseProgram)(GLuint);
typedef void(*nkglGetProgramiv)(GLuint, GLenum, GLint*);
typedef void(*nkglGetProgramInfoLog)(GLuint, GLsizei, GLsizei*, GLchar*);
typedef void(*nkglDeleteProgram)(GLuint);
typedef GLint(*nkglGetUniformLocation)(GLuint, const GLchar*);
typedef GLint(*nkglGetAttribLocation)(GLuint, const GLchar*);
typedef void(*nkglUniform1i)(GLint, GLint);
typedef void(*nkglUniform1f)(GLint, GLfloat);
typedef void(*nkglUniformMatrix3fv)(GLint, GLsizei, GLboolean, const GLfloat*);
typedef void(*nkglUniformMatrix4fv)(GLint, GLsizei, GLboolean, const GLfloat*);
static nkglGenBuffers glGenBuffers;
static nkglBindBuffer glBindBuffer;
static nkglBufferData glBufferData;
static nkglBufferSubData glBufferSubData;
static nkglMapBuffer glMapBuffer;
static nkglUnmapBuffer glUnmapBuffer;
static nkglDeleteBuffers glDeleteBuffers;
static nkglGenVertexArrays glGenVertexArrays;
static nkglBindVertexArray glBindVertexArray;
static nkglDeleteVertexArrays glDeleteVertexArrays;
static nkglVertexAttribPointer glVertexAttribPointer;
static nkglEnableVertexAttribArray glEnableVertexAttribArray;
static nkglDisableVertexAttribArray glDisableVertexAttribArray;
static nkglGenerateMipmap glGenerateMipmap;
static nkglCreateShader glCreateShader;
static nkglShaderSource glShaderSource;
static nkglCompileShader glCompileShader;
static nkglGetShaderiv glGetShaderiv;
static nkglGetShaderInfoLog glGetShaderInfoLog;
static nkglDeleteShader glDeleteShader;
static nkglCreateProgram glCreateProgram;
static nkglAttachShader glAttachShader;
static nkglDetachShader glDetachShader;
static nkglLinkProgram glLinkProgram;
static nkglUseProgram glUseProgram;
static nkglGetProgramiv glGetProgramiv;
static nkglGetProgramInfoLog glGetProgramInfoLog;
static nkglDeleteProgram glDeleteProgram;
static nkglGetUniformLocation glGetUniformLocation;
static nkglGetAttribLocation glGetAttribLocation;
static nkglUniform1i glUniform1i;
static nkglUniform1f glUniform1f;
static nkglUniformMatrix3fv glUniformMatrix3fv;
static nkglUniformMatrix4fv glUniformMatrix4fv;
enum graphics_card_vendors {
VENDOR_UNKNOWN,
VENDOR_NVIDIA,
VENDOR_AMD,
VENDOR_INTEL
};
struct opengl_info {
/* info */
const char *vendor_str;
const char *version_str;
const char *extensions_str;
const char *renderer_str;
const char *glsl_version_str;
enum graphics_card_vendors vendor;
/* version */
float version;
int major_version;
int minor_version;
/* extensions */
int glsl_available;
int vertex_buffer_obj_available;
int vertex_array_obj_available;
int map_buffer_range_available;
int fragment_program_available;
int frame_buffer_object_available;
};
#endif
struct nk_x11_vertex {
float position[2];
float uv[2];
nk_byte col[4];
};
struct nk_x11_device {
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
struct opengl_info info;
#endif
struct nk_buffer cmds;
struct nk_draw_null_texture tex_null;
GLuint vbo, vao, ebo;
GLuint prog;
GLuint vert_shdr;
GLuint frag_shdr;
GLint attrib_pos;
GLint attrib_uv;
GLint attrib_col;
GLint uniform_tex;
GLint uniform_proj;
GLuint font_tex;
};
static struct nk_x11 {
struct nk_x11_device ogl;
struct nk_context ctx;
struct nk_font_atlas atlas;
Cursor cursor;
Display *dpy;
Window win;
long last_button_click;
} x11;
#ifdef __APPLE__
#define NK_SHADER_VERSION "#version 150\n"
#else
#define NK_SHADER_VERSION "#version 300 es\n"
#endif
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
#include <GL/glx.h>
NK_INTERN long
nk_timestamp(void)
{
struct timeval tv;
if (gettimeofday(&tv, NULL) < 0) return 0;
return (long)((long)tv.tv_sec * 1000 + (long)tv.tv_usec/1000);
}
NK_INTERN int
nk_x11_stricmpn(const char *a, const char *b, int len)
{
int i = 0;
for (i = 0; i < len && a[i] && b[i]; ++i)
if (a[i] != b[i]) return 1;
if (i != len) return 1;
return 0;
}
NK_INTERN int
nk_x11_check_extension(struct opengl_info *GL, const char *ext)
{
const char *start, *where, *term;
where = strchr(ext, ' ');
if (where || *ext == '\0')
return nk_false;
for (start = GL->extensions_str;;) {
where = strstr((const char*)start, ext);
if (!where) break;
term = where + strlen(ext);
if (where == start || *(where - 1) == ' ') {
if (*term == ' ' || *term == '\0')
return nk_true;
}
start = term;
}
return nk_false;
}
#define GL_EXT(name) (nk##name)nk_gl_ext(#name)
NK_INTERN __GLXextFuncPtr
nk_gl_ext(const char *name)
{
__GLXextFuncPtr func;
func = glXGetProcAddress((const GLubyte*)name);
if (!func) {
fprintf(stdout, "[GL]: failed to load extension: %s", name);
return NULL;
}
return func;
}
NK_INTERN int
nk_load_opengl(struct opengl_info *gl)
{
int failed = nk_false;
gl->version_str = (const char*)glGetString(GL_VERSION);
glGetIntegerv(GL_MAJOR_VERSION, &gl->major_version);
glGetIntegerv(GL_MINOR_VERSION, &gl->minor_version);
if (gl->major_version < 2) {
fprintf(stderr, "[GL]: Graphics card does not fulfill minimum OpenGL 2.0 support\n");
return 0;
}
gl->version = (float)gl->major_version + (float)gl->minor_version * 0.1f;
gl->renderer_str = (const char*)glGetString(GL_RENDERER);
gl->extensions_str = (const char*)glGetString(GL_EXTENSIONS);
gl->glsl_version_str = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
gl->vendor_str = (const char*)glGetString(GL_VENDOR);
if (!nk_x11_stricmpn(gl->vendor_str, "ATI", 4) ||
!nk_x11_stricmpn(gl->vendor_str, "AMD", 4))
gl->vendor = VENDOR_AMD;
else if (!nk_x11_stricmpn(gl->vendor_str, "NVIDIA", 6))
gl->vendor = VENDOR_NVIDIA;
else if (!nk_x11_stricmpn(gl->vendor_str, "Intel", 5))
gl->vendor = VENDOR_INTEL;
else gl->vendor = VENDOR_UNKNOWN;
/* Extensions */
gl->glsl_available = (gl->version >= 2.0f);
if (gl->glsl_available) {
/* GLSL core in OpenGL > 2 */
glCreateShader = GL_EXT(glCreateShader);
glShaderSource = GL_EXT(glShaderSource);
glCompileShader = GL_EXT(glCompileShader);
glGetShaderiv = GL_EXT(glGetShaderiv);
glGetShaderInfoLog = GL_EXT(glGetShaderInfoLog);
glDeleteShader = GL_EXT(glDeleteShader);
glCreateProgram = GL_EXT(glCreateProgram);
glAttachShader = GL_EXT(glAttachShader);
glDetachShader = GL_EXT(glDetachShader);
glLinkProgram = GL_EXT(glLinkProgram);
glUseProgram = GL_EXT(glUseProgram);
glGetProgramiv = GL_EXT(glGetProgramiv);
glGetProgramInfoLog = GL_EXT(glGetProgramInfoLog);
glDeleteProgram = GL_EXT(glDeleteProgram);
glGetUniformLocation = GL_EXT(glGetUniformLocation);
glGetAttribLocation = GL_EXT(glGetAttribLocation);
glUniform1i = GL_EXT(glUniform1i);
glUniform1f = GL_EXT(glUniform1f);
glUniformMatrix3fv = GL_EXT(glUniformMatrix3fv);
glUniformMatrix4fv = GL_EXT(glUniformMatrix4fv);
}
gl->vertex_buffer_obj_available = nk_x11_check_extension(gl, "GL_ARB_vertex_buffer_object");
if (gl->vertex_buffer_obj_available) {
/* GL_ARB_vertex_buffer_object */
glGenBuffers = GL_EXT(glGenBuffers);
glBindBuffer = GL_EXT(glBindBuffer);
glBufferData = GL_EXT(glBufferData);
glBufferSubData = GL_EXT(glBufferSubData);
glMapBuffer = GL_EXT(glMapBuffer);
glUnmapBuffer = GL_EXT(glUnmapBuffer);
glDeleteBuffers = GL_EXT(glDeleteBuffers);
}
gl->fragment_program_available = nk_x11_check_extension(gl, "GL_ARB_fragment_program");
if (gl->fragment_program_available) {
/* GL_ARB_vertex_program / GL_ARB_fragment_program */
glVertexAttribPointer = GL_EXT(glVertexAttribPointer);
glEnableVertexAttribArray = GL_EXT(glEnableVertexAttribArray);
glDisableVertexAttribArray = GL_EXT(glDisableVertexAttribArray);
}
gl->vertex_array_obj_available = nk_x11_check_extension(gl, "GL_ARB_vertex_array_object");
if (gl->vertex_array_obj_available) {
/* GL_ARB_vertex_array_object */
glGenVertexArrays = GL_EXT(glGenVertexArrays);
glBindVertexArray = GL_EXT(glBindVertexArray);
glDeleteVertexArrays = GL_EXT(glDeleteVertexArrays);
}
gl->frame_buffer_object_available = nk_x11_check_extension(gl, "GL_ARB_framebuffer_object");
if (gl->frame_buffer_object_available) {
/* GL_ARB_framebuffer_object */
glGenerateMipmap = GL_EXT(glGenerateMipmap);
}
if (!gl->vertex_buffer_obj_available) {
fprintf(stdout, "[GL] Error: GL_ARB_vertex_buffer_object is not available!\n");
failed = nk_true;
}
if (!gl->fragment_program_available) {
fprintf(stdout, "[GL] Error: GL_ARB_fragment_program is not available!\n");
failed = nk_true;
}
if (!gl->vertex_array_obj_available) {
fprintf(stdout, "[GL] Error: GL_ARB_vertex_array_object is not available!\n");
failed = nk_true;
}
if (!gl->frame_buffer_object_available) {
fprintf(stdout, "[GL] Error: GL_ARB_framebuffer_object is not available!\n");
failed = nk_true;
}
return !failed;
}
#endif
NK_API int
nk_x11_device_create(void)
{
GLint status;
static const GLchar *vertex_shader =
NK_SHADER_VERSION
"uniform mat4 ProjMtx;\n"
"in vec2 Position;\n"
"in vec2 TexCoord;\n"
"in vec4 Color;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main() {\n"
" Frag_UV = TexCoord;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n"
"}\n";
static const GLchar *fragment_shader =
NK_SHADER_VERSION
"precision mediump float;\n"
"uniform sampler2D Texture;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"out vec4 Out_Color;\n"
"void main(){\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
struct nk_x11_device *dev = &x11.ogl;
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
if (!nk_load_opengl(&dev->info)) return 0;
#endif
nk_buffer_init_default(&dev->cmds);
dev->prog = glCreateProgram();
dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER);
dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0);
glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0);
glCompileShader(dev->vert_shdr);
glCompileShader(dev->frag_shdr);
glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status);
assert(status == GL_TRUE);
glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status);
assert(status == GL_TRUE);
glAttachShader(dev->prog, dev->vert_shdr);
glAttachShader(dev->prog, dev->frag_shdr);
glLinkProgram(dev->prog);
glGetProgramiv(dev->prog, GL_LINK_STATUS, &status);
assert(status == GL_TRUE);
dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture");
dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx");
dev->attrib_pos = glGetAttribLocation(dev->prog, "Position");
dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord");
dev->attrib_col = glGetAttribLocation(dev->prog, "Color");
{
/* buffer setup */
GLsizei vs = sizeof(struct nk_x11_vertex);
size_t vp = offsetof(struct nk_x11_vertex, position);
size_t vt = offsetof(struct nk_x11_vertex, uv);
size_t vc = offsetof(struct nk_x11_vertex, col);
glGenBuffers(1, &dev->vbo);
glGenBuffers(1, &dev->ebo);
glGenVertexArrays(1, &dev->vao);
glBindVertexArray(dev->vao);
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
glEnableVertexAttribArray((GLuint)dev->attrib_pos);
glEnableVertexAttribArray((GLuint)dev->attrib_uv);
glEnableVertexAttribArray((GLuint)dev->attrib_col);
glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp);
glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt);
glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc);
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
return 1;
}
NK_INTERN void
nk_x11_device_upload_atlas(const void *image, int width, int height)
{
struct nk_x11_device *dev = &x11.ogl;
glGenTextures(1, &dev->font_tex);
glBindTexture(GL_TEXTURE_2D, dev->font_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, image);
}
NK_API void
nk_x11_device_destroy(void)
{
struct nk_x11_device *dev = &x11.ogl;
glDetachShader(dev->prog, dev->vert_shdr);
glDetachShader(dev->prog, dev->frag_shdr);
glDeleteShader(dev->vert_shdr);
glDeleteShader(dev->frag_shdr);
glDeleteProgram(dev->prog);
glDeleteTextures(1, &dev->font_tex);
glDeleteBuffers(1, &dev->vbo);
glDeleteBuffers(1, &dev->ebo);
nk_buffer_free(&dev->cmds);
}
NK_API void
nk_x11_render(enum nk_anti_aliasing AA, int max_vertex_buffer, int max_element_buffer)
{
int width, height;
XWindowAttributes attr;
struct nk_x11_device *dev = &x11.ogl;
GLfloat ortho[4][4] = {
{2.0f, 0.0f, 0.0f, 0.0f},
{0.0f,-2.0f, 0.0f, 0.0f},
{0.0f, 0.0f,-1.0f, 0.0f},
{-1.0f,1.0f, 0.0f, 1.0f},
};
XGetWindowAttributes(x11.dpy, x11.win, &attr);
width = attr.width;
height = attr.height;
ortho[0][0] /= (GLfloat)width;
ortho[1][1] /= (GLfloat)height;
/* setup global state */
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glActiveTexture(GL_TEXTURE0);
/* setup program */
glUseProgram(dev->prog);
glUniform1i(dev->uniform_tex, 0);
glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]);
glViewport(0,0,(GLsizei)width,(GLsizei)height);
{
/* convert from command queue into draw list and draw to screen */
const struct nk_draw_command *cmd;
void *vertices, *elements;
const nk_draw_index *offset = NULL;
struct nk_buffer vbuf, ebuf;
/* allocate vertex and element buffer */
glBindVertexArray(dev->vao);
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
glBufferData(GL_ARRAY_BUFFER, max_vertex_buffer, NULL, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_buffer, NULL, GL_STREAM_DRAW);
/* load draw vertices & elements directly into vertex + element buffer */
vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
{
/* fill convert configuration */
struct nk_convert_config config;
static const struct nk_draw_vertex_layout_element vertex_layout[] = {
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_x11_vertex, position)},
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_x11_vertex, uv)},
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct nk_x11_vertex, col)},
{NK_VERTEX_LAYOUT_END}
};
memset(&config, 0, sizeof(config));
config.vertex_layout = vertex_layout;
config.vertex_size = sizeof(struct nk_x11_vertex);
config.vertex_alignment = NK_ALIGNOF(struct nk_x11_vertex);
config.tex_null = dev->tex_null;
config.circle_segment_count = 22;
config.curve_segment_count = 22;
config.arc_segment_count = 22;
config.global_alpha = 1.0f;
config.shape_AA = AA;
config.line_AA = AA;
/* setup buffers to load vertices and elements */
nk_buffer_init_fixed(&vbuf, vertices, (size_t)max_vertex_buffer);
nk_buffer_init_fixed(&ebuf, elements, (size_t)max_element_buffer);
nk_convert(&x11.ctx, &dev->cmds, &vbuf, &ebuf, &config);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
/* iterate over and execute each draw command */
nk_draw_foreach(cmd, &x11.ctx, &dev->cmds)
{
if (!cmd->elem_count) continue;
// GLint unit = cmd->texture.id
glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id);
glScissor(
(GLint)(cmd->clip_rect.x),
(GLint)((height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h))),
(GLint)(cmd->clip_rect.w),
(GLint)(cmd->clip_rect.h));
glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset);
offset += cmd->elem_count;
}
nk_clear(&x11.ctx);
nk_buffer_clear(&dev->cmds);
}
/* default OpenGL state */
glUseProgram(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
}
NK_API void
nk_x11_font_stash_begin(struct nk_font_atlas **atlas)
{
nk_font_atlas_init_default(&x11.atlas);
nk_font_atlas_begin(&x11.atlas);
*atlas = &x11.atlas;
}
NK_API void
nk_x11_font_stash_end(void)
{
const void *image; int w, h;
image = nk_font_atlas_bake(&x11.atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
nk_x11_device_upload_atlas(image, w, h);
nk_font_atlas_end(&x11.atlas, nk_handle_id((int)x11.ogl.font_tex), &x11.ogl.tex_null);
if (x11.atlas.default_font)
nk_style_set_font(&x11.ctx, &x11.atlas.default_font->handle);
}
NK_API int
nk_x11_handle_event(XEvent *evt)
{
struct nk_context *ctx = &x11.ctx;
/* optional grabbing behavior */
if (ctx->input.mouse.grab) {
XDefineCursor(x11.dpy, x11.win, x11.cursor);
ctx->input.mouse.grab = 0;
} else if (ctx->input.mouse.ungrab) {
XWarpPointer(x11.dpy, None, x11.win, 0, 0, 0, 0,
(int)ctx->input.mouse.pos.x, (int)ctx->input.mouse.pos.y);
XUndefineCursor(x11.dpy, x11.win);
ctx->input.mouse.ungrab = 0;
}
if (evt->type == KeyPress || evt->type == KeyRelease)
{
/* Key handler */
int ret, down = (evt->type == KeyPress);
KeySym *code = XGetKeyboardMapping(x11.dpy, (KeyCode)evt->xkey.keycode, 1, &ret);
if (*code == XK_Shift_L || *code == XK_Shift_R) nk_input_key(ctx, NK_KEY_SHIFT, down);
else if (*code == XK_Control_L || *code == XK_Control_R) nk_input_key(ctx, NK_KEY_CTRL, down);
else if (*code == XK_Alt_L || *code == XK_Alt_R) nk_input_key(ctx, NK_KEY_ALT, down);
else if (*code == XK_Delete) nk_input_key(ctx, NK_KEY_DEL, down);
else if (*code == XK_Return) nk_input_key(ctx, NK_KEY_ENTER, down);
else if (*code == XK_Tab) nk_input_key(ctx, NK_KEY_TAB, down);
else if (*code == XK_Left) nk_input_key(ctx, NK_KEY_LEFT, down);
else if (*code == XK_Right) nk_input_key(ctx, NK_KEY_RIGHT, down);
else if (*code == XK_Up) nk_input_key(ctx, NK_KEY_UP, down);
else if (*code == XK_Down) nk_input_key(ctx, NK_KEY_DOWN, down);
else if (*code == XK_BackSpace) nk_input_key(ctx, NK_KEY_BACKSPACE, down);
else if (*code == XK_Escape) nk_input_key(ctx, NK_KEY_ESCAPE, down);
// else if (*code == XK_space && !down) nk_input_char(ctx, ' ');
else if (*code == XK_Page_Up) nk_input_key(ctx, NK_KEY_SCROLL_UP, down);
else if (*code == XK_Page_Down) nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down);
else if (*code == XK_Home) {
nk_input_key(ctx, NK_KEY_TEXT_START, down);
nk_input_key(ctx, NK_KEY_SCROLL_START, down);
} else if (*code == XK_End) {
nk_input_key(ctx, NK_KEY_TEXT_END, down);
nk_input_key(ctx, NK_KEY_SCROLL_END, down);
} else {
#if 0 // MII wants these!!
if (*code == 'c' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_COPY, down);
else if (*code == 'v' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_PASTE, down);
else if (*code == 'x' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_CUT, down);
else if (*code == 'z' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_UNDO, down);
else if (*code == 'r' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_REDO, down);
else if (*code == XK_Left && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down);
else if (*code == XK_Right && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down);
else if (*code == 'b' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down);
else if (*code == 'e' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down);
else {
if (*code == 'i')
nk_input_key(ctx, NK_KEY_TEXT_INSERT_MODE, down);
else if (*code == 'r')
nk_input_key(ctx, NK_KEY_TEXT_REPLACE_MODE, down);
#endif
{
char buf[32];
KeySym keysym = 0;
if (XLookupString((XKeyEvent*)evt, buf, 32, &keysym, NULL) != NoSymbol) {
if (down)
nk_input_glyph(ctx, buf);
} else {
// printf("Unknown key %lx, pass on RAW %d\n", *code,
// ctx->input.keyboard.text_len);
uint32_t *d = (uint32_t *) ctx->input.keyboard.text;
d[ctx->input.keyboard.text_len / 4] =
(*code << 16) | !!down;
ctx->input.keyboard.text_len += 4;
}
}
}
XFree(code);
return 1;
} else if (evt->type == ButtonPress || evt->type == ButtonRelease) {
/* Button handler */
int down = (evt->type == ButtonPress);
const int x = evt->xbutton.x, y = evt->xbutton.y;
if (evt->xbutton.button == Button1) {
if (down) { /* Double-Click Button handler */
long dt = nk_timestamp() - x11.last_button_click;
if (dt > NK_X11_DOUBLE_CLICK_LO && dt < NK_X11_DOUBLE_CLICK_HI)
nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, nk_true);
x11.last_button_click = nk_timestamp();
} else nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, nk_false);
nk_input_button(ctx, NK_BUTTON_LEFT, x, y, down);
} else if (evt->xbutton.button == Button2)
nk_input_button(ctx, NK_BUTTON_MIDDLE, x, y, down);
else if (evt->xbutton.button == Button3)
nk_input_button(ctx, NK_BUTTON_RIGHT, x, y, down);
else if (evt->xbutton.button == Button4)
nk_input_scroll(ctx, nk_vec2(0,1.0f));
else if (evt->xbutton.button == Button5)
nk_input_scroll(ctx, nk_vec2(0,-1.0f));
else return 0;
return 1;
} else if (evt->type == MotionNotify) {
/* Mouse motion handler */
const int x = evt->xmotion.x, y = evt->xmotion.y;
nk_input_motion(ctx, x, y);
if (ctx->input.mouse.grabbed) {
ctx->input.mouse.pos.x = ctx->input.mouse.prev.x;
ctx->input.mouse.pos.y = ctx->input.mouse.prev.y;
XWarpPointer(x11.dpy, None, x11.win, 0, 0, 0, 0, (int)ctx->input.mouse.pos.x, (int)ctx->input.mouse.pos.y);
}
return 1;
} else if (evt->type == KeymapNotify) {
XRefreshKeyboardMapping(&evt->xmapping);
return 1;
}
return 0;
}
NK_API struct nk_context*
nk_x11_init(Display *dpy, Window win)
{
if (!setlocale(LC_ALL,"")) return 0;
if (!XSupportsLocale()) return 0;
if (!XSetLocaleModifiers("@im=none")) return 0;
if (!nk_x11_device_create()) return 0;
x11.dpy = dpy;
x11.win = win;
/* create invisible cursor */
{static XColor dummy; char data[1] = {0};
Pixmap blank = XCreateBitmapFromData(dpy, win, data, 1, 1);
if (blank == None) return 0;
x11.cursor = XCreatePixmapCursor(dpy, blank, blank, &dummy, &dummy, 0, 0);
XFreePixmap(dpy, blank);}
nk_init_default(&x11.ctx, 0);
return &x11.ctx;
}
NK_API void
nk_x11_shutdown(void)
{
nk_font_atlas_clear(&x11.atlas);
nk_free(&x11.ctx);
nk_x11_device_destroy();
XFreeCursor(x11.dpy, x11.cursor);
memset(&x11, 0, sizeof(x11));
}
#endif

View File

@ -352,17 +352,15 @@ _mii_disk2_command(
*(int *)param = 2;
break;
case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + 2 - 1:
if (param) {
int drive = cmd - MII_SLOT_DRIVE_LOAD;
const char *filename = param;
if (c->disk[drive].privdat) {
c->disk[drive].eject(&c->disk[drive]);
}
printf("%s: drive %d loading %s\n", __func__, drive,
filename);
c->disk[drive] = disk_format_load(filename);
return 0;
int drive = cmd - MII_SLOT_DRIVE_LOAD;
if (c->disk[drive].privdat) {
c->disk[drive].eject(&c->disk[drive]);
}
const char *filename = param;
printf("%s: drive %d loading %s\n", __func__, drive,
filename);
c->disk[drive] = disk_format_load(
filename && *filename ? filename : NULL);
break;
}
return 0;

View File

@ -8,6 +8,7 @@
* This is a driver for these eprom/flash cards from
* Terence J. Boldt and the likes
*/
#define _GNU_SOURCE // for asprintf
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
@ -21,37 +22,55 @@
#include "mii.h"
#include "mii_bank.h"
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mii_
#include "incbin.h"
INCBIN(1mb_rom, "disks/GamesWithFirmware.po");
typedef struct mii_card_ee_t {
uint8_t * file;
uint16_t latch;
mii_dd_t drive[1];
uint8_t * file;
uint16_t latch;
} mii_card_ee_t;
int mmapfile(const char *fname, uint8_t **buf, size_t *sz, int flags);
static int
_mii_ee_init(
mii_t * mii,
struct mii_slot_t *slot )
{
uint8_t * file;
size_t sz;
const char *fname = "disks/Apple IIe Diagnostic 2.1.po";
//const char *fname = "disks/GamesWithFirmware.po";
if (mmapfile(fname, &file, &sz, O_RDONLY) != 0) {
printf("Failed to load %s\n", fname);
return -1;
}
mii_card_ee_t *c = calloc(1, sizeof(*c));
slot->drv_priv = c;
c->file = file;
printf("%s loading in slot %d\n", __func__, slot->id);
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, c->file + 0x300, 256);
printf("%s loading in slot %d\n", __func__, slot->id + 1);
for (int i = 0; i < 1; i++) {
mii_dd_t *dd = &c->drive[i];
dd->slot_id = slot->id + 1;
dd->drive = i + 1;
dd->slot = slot;
dd->ro = 1; dd->wp = 1;
asprintf((char **)&dd->name, "EE1MB S:%d D:%d",
dd->slot_id, dd->drive);
}
mii_dd_register_drives(&mii->dd, c->drive, 1);
#if 1
c->file = (uint8_t*)mii_1mb_rom_data;
#else
const char *fname = "disks/GamesWithFirmware.po";
mii_dd_file_t *file = mii_dd_file_load(&mii->dd, fname, 0);
mii_dd_drive_load(&c->drive[0], file);
c->file = file->map;
#endif
if (c->file) {
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, c->file + 0x300, 256);
}
return 0;
}
@ -75,7 +94,35 @@ _mii_ee_access(
break;
}
} else {
return c->file[(c->latch << 4) + psw];
return c->file ? c->file[(c->latch << 4) + psw] : 0xff;
}
return 0;
}
static int
_mii_ee_command(
mii_t * mii,
struct mii_slot_t *slot,
uint8_t cmd,
void * param)
{
mii_card_ee_t *c = slot->drv_priv;
switch (cmd) {
case MII_SLOT_DRIVE_COUNT:
if (param)
*(int *)param = 1;
break;
case MII_SLOT_DRIVE_LOAD:
const char *filename = param;
mii_dd_file_t *file = NULL;
if (filename && *filename) {
file = mii_dd_file_load(&mii->dd, filename, 0);
if (!file)
return -1;
}
mii_dd_drive_load(&c->drive[0], file);
c->file = file ? file->map : (uint8_t*)mii_1mb_rom_data;
break;
}
return 0;
}
@ -85,5 +132,6 @@ static mii_slot_drv_t _driver = {
.desc = "EEPROM 1MB card",
.init = _mii_ee_init,
.access = _mii_ee_access,
.command = _mii_ee_command,
};
MI_DRIVER_REGISTER(_driver);

View File

@ -1,4 +1,11 @@
/*
* mii_mouse.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*
*/
#include "mii.h"
@ -16,6 +23,48 @@
#include "mii.h"
#include "mii_bank.h"
/*
* Coded against this information
* http://stason.org/TULARC/pc/apple2/programmer/012-How-do-I-write-programs-which-use-the-mouse.html
*/
/*
* Screen holes
* $0478 + slot Low byte of absolute X position
* $04F8 + slot Low byte of absolute Y position
* $0578 + slot High byte of absolute X position
* $05F8 + slot High byte of absolute Y position
* $0678 + slot Reserved and used by the firmware
* $06F8 + slot Reserved and used by the firmware
* $0778 + slot Button 0/1 interrupt status byte
* $07F8 + slot Mode byte
*
* Interrupt status byte:
* Set by READMOUSE
* Bit 7 6 5 4 3 2 1 0
* | | | | | | | |
* | | | | | | | `--- Previously, button 1 was up (0) or down (1)
* | | | | | | `----- Movement interrupt
* | | | | | `------- Button 0/1 interrupt
* | | | | `--------- VBL interrupt
* | | | `----------- Currently, button 1 is up (0) or down (1)
* | | `------------- X/Y moved since last READMOUSE
* | `--------------- Previously, button 0 was up (0) or down (1)
* `----------------- Currently, button 0 is up (0) or down (1)
*
* Mode byte
* Valid after calling SERVEMOUSE, cleared with READMOUSE
* Bit 7 6 5 4 3 2 1 0
* | | | | | | | |
* | | | | | | | `--- Mouse off (0) or on (1)
* | | | | | | `----- Interrupt if mouse is moved
* | | | | | `------- Interrupt if button is pressed
* | | | | `--------- Interrupt on VBL
* | | | `----------- Reserved
* | | `------------- Reserved
* | `--------------- Reserved
* `----------------- Reserved
*/
enum {
CLAMP_MIN_LO = 0x478,
@ -42,6 +91,8 @@ enum {
typedef struct mii_card_mouse_t {
struct mii_slot_t * slot;
mii_t * mii;
uint8_t timer_id; // 60hz timer
uint8_t slot_offset;
uint8_t mode; // cached mode byte
struct {
@ -50,6 +101,44 @@ typedef struct mii_card_mouse_t {
} last;
} mii_card_mouse_t;
static uint64_t
_mii_mouse_vbl_handler(
mii_t * mii,
void *param)
{
mii_card_mouse_t *c = param;
/* this is not exact, the VBL interrupt should still work when
* the mouse is disabled, but it's not really important -- for the moment
*/
if (!mii->mouse.enabled)
return 1000000 / 60;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
uint8_t status = mii_bank_peek(main, MOUSE_STATUS + c->slot_offset);
uint8_t old = status;
if (c->mode & mouseIntMoveEnabled) {
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y)) {
mii->cpu_state.irq = 1;
status |= 1 << 1;
}
}
if (c->mode & mouseIntButtonEnabled) {
if (mii->mouse.button && !c->last.button) {
mii->cpu_state.irq = 1;
status |= 1 << 2;
}
}
if (c->mode & mouseIntVBlankEnabled) {
mii->cpu_state.irq = 1;
status |= 1 << 3;
}
// if (mii->cpu_state.irq) mii->trace_cpu = true;
if (status != old)
mii_bank_poke(main, MOUSE_STATUS + c->slot_offset, status);
return 1000000 / 60;
}
static int
_mii_mouse_init(
mii_t * mii,
@ -58,12 +147,16 @@ _mii_mouse_init(
mii_card_mouse_t *c = calloc(1, sizeof(*c));
c->slot = slot;
slot->drv_priv = c;
c->mii = mii;
printf("%s loading in slot %d\n", __func__, slot->id + 1);
c->slot_offset = slot->id + 1 + 0xc0;
uint8_t data[256] = {};
c->timer_id = mii_timer_register(mii,
_mii_mouse_vbl_handler, c,
1000000 / 60, __func__);
// Identification as a mouse card
// From Technical Note Misc #8, "Pascal 1.1 Firmware Protocol ID Bytes":
data[0x05] = 0x38;
@ -84,12 +177,10 @@ _mii_mouse_init(
data[base+3] = 0x18; // CLC ;no error
data[base+4] = 0x60; // RTS
}
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, data, 256);
return 0;
}
@ -127,21 +218,19 @@ _mii_mouse_access(
printf(" Button: %s\n", byte & mouseIntButtonEnabled ? "enabled" : "disabled");
printf(" VBlank: %s\n", byte & mouseIntVBlankEnabled ? "enabled" : "disabled");
c->mode = byte;
mii->video.vbl_irq = !!(byte & mouseIntVBlankEnabled);
}
} break;
case 3: // service mouse
// no need to handle that, the VBL handler does it
break;
case 4: {// read mouse
if (!mii->mouse.enabled)
break;
uint8_t status = 0;
if (mii->mouse.button)
status |= 1 << 7;
if (c->last.button) {
status |= 1 << 6;
}
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y)) {
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y))
status |= 1 << 5;
}
status |= c->last.button ? 1 << 6 : 0;
status |= mii->mouse.button ? 1 << 7 : 0;
mii_bank_poke(main, MOUSE_X_HI + c->slot_offset, mii->mouse.x >> 8);
mii_bank_poke(main, MOUSE_Y_HI + c->slot_offset, mii->mouse.y >> 8);
mii_bank_poke(main, MOUSE_X_LO + c->slot_offset, mii->mouse.x);

View File

@ -105,7 +105,7 @@ _mii_nsc_access(
bool write)
{
if (!bank) {
printf("%s: disposing of NSC\n", __func__);
// printf("%s: disposing of NSC\n", __func__);
free(param);
return false;
}
@ -158,7 +158,7 @@ _mii_nsc_probe(
mii_t *mii,
uint32_t flags)
{
printf("%s %s\n", __func__, flags & MII_INIT_NSC ? "enabled" : "disabled");
// printf("%s %s\n", __func__, flags & MII_INIT_NSC ? "enabled" : "disabled");
if (!(flags & MII_INIT_NSC))
return 0;
mii_nsc_t * nsc = calloc(1, sizeof(*nsc));

View File

@ -36,7 +36,7 @@ extern const unsigned char mii_smartport_rom_data[];
#define MII_SM_DRIVE_COUNT 2
typedef struct mii_card_sm_t {
struct mii_card_sm_t *next;
// struct mii_card_sm_t *next;
mii_dd_t drive[MII_SM_DRIVE_COUNT];
struct mii_slot_t *slot;
} mii_card_sm_t;
@ -244,7 +244,7 @@ _mii_sm_init(
c->slot = slot;
slot->drv_priv = c;
printf("%s loading in slot %d\n", __func__, slot->id);
printf("%s loading in slot %d\n", __func__, slot->id + 1);
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
@ -252,7 +252,7 @@ _mii_sm_init(
uint8_t trap_hd = mii_register_trap(mii, _mii_hd_callback);
uint8_t trap_sm = mii_register_trap(mii, _mii_sm_callback);
printf("%s: traps %02x %02x\n", __func__, trap_hd, trap_sm);
// printf("%s: traps %02x %02x\n", __func__, trap_hd, trap_sm);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr + 0xd2, &trap_hd, 1);
@ -302,18 +302,15 @@ _mii_sm_command(
*(int *)param = MII_SM_DRIVE_COUNT;
break;
case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + MII_SM_DRIVE_COUNT - 1:
if (param) {
int drive = cmd - MII_SLOT_DRIVE_LOAD;
const char *filename = param;
mii_dd_file_t *file = NULL;
if (filename) {
file = mii_dd_file_load(&mii->dd, filename, 0);
if (!file)
return -1;
}
mii_dd_drive_load(&c->drive[drive], file);
return 0;
int drive = cmd - MII_SLOT_DRIVE_LOAD;
const char *filename = param;
mii_dd_file_t *file = NULL;
if (filename && *filename) {
file = mii_dd_file_load(&mii->dd, filename, 0);
if (!file)
return -1;
}
mii_dd_drive_load(&c->drive[drive], file);
break;
}
return 0;
@ -324,25 +321,6 @@ _mii_sm_access(
mii_t * mii, struct mii_slot_t *slot,
uint16_t addr, uint8_t byte, bool write)
{
#if 0
mii_card_sm_t *c = slot->drv_priv;
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
mii->cpu.PC, addr, byte, write);
int psw = addr & 0x0F;
if (write) {
switch (psw) {
case 0:
// c->latch = (c->latch & 0xff00) | byte;
break;
case 1:
// c->latch = (c->latch & 0x00ff) | (byte << 8);
break;
}
} else {
// return c->file[(c->latch << 4) + psw];
}
#endif
return 0;
}

143
src/drivers/mii_ssc.c Normal file
View File

@ -0,0 +1,143 @@
/*
* mii_ssc.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*
*/
/*
THIS IS A PLACEHOLDER DO NOT USE
*/
#include "mii.h"
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_sw.h"
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mii_
#include "incbin.h"
INCBIN(ssc_rom, "roms/mii_rom_scc_3410065a.bin");
typedef struct mii_card_ssc_t {
struct mii_slot_t * slot;
struct mii_bank_t * rom;
mii_t * mii;
uint8_t slot_offset;
} mii_card_ssc_t;
static bool
_mii_ssc_select(
struct mii_bank_t *bank,
void *param,
uint16_t addr,
uint8_t * byte,
bool write)
{
mii_card_ssc_t *c = param;
printf("%s selected:%d\n", __func__, c->slot->aux_rom_selected);
if (c->slot->aux_rom_selected)
return false;
mii_bank_write(c->rom, 0xc800, mii_ssc_rom_data, 2048);
c->slot->aux_rom_selected = true;
SW_SETSTATE(c->mii, SLOTAUXROM, 1);
c->mii->mem_dirty = true;
return false;
}
static int
_mii_ssc_init(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_ssc_t *c = calloc(1, sizeof(*c));
c->slot = slot;
slot->drv_priv = c;
c->mii = mii;
printf("%s: THIS IS A PLACEHOLDER DO NOT USE\n", __func__);
printf("%s loading in slot %d\n", __func__, slot->id + 1);
c->slot_offset = slot->id + 1 + 0xc0;
uint16_t addr = 0xc100 + (slot->id * 0x100);
c->rom = &mii->bank[MII_BANK_CARD_ROM];
mii_bank_write(c->rom, addr, mii_ssc_rom_data + 7*256, 256);
/*
* install a callback that will be called for every access to the
* ROM area, we need this to re-install the secondary part of the ROM
* when the card 'slot' rom is accessed.
*/
mii_bank_install_access_cb(c->rom,
_mii_ssc_select, c, addr >> 8, addr >> 8);
return 0;
}
static void
_mii_ssc_dispose(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_ssc_t *c = slot->drv_priv;
free(c);
slot->drv_priv = NULL;
}
static uint8_t
_mii_ssc_access(
mii_t * mii,
struct mii_slot_t *slot,
uint16_t addr,
uint8_t byte,
bool write)
{
// mii_card_ssc_t *c = slot->drv_priv;
int psw = addr & 0x0F;
// mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
switch (psw) {
default:
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
mii->cpu.PC, addr, byte, write);
break;
}
return 0;
}
static int
_mii_ssc_command(
mii_t * mii,
struct mii_slot_t *slot,
uint8_t cmd,
void * param)
{
// mii_card_ssc_t *c = slot->drv_priv;
switch (cmd) {
case MII_SLOT_SSC_SET_TTY: {
const char * tty = param;
printf("%s: set tty %s\n", __func__, tty);
} break;
}
return -1;
}
static mii_slot_drv_t _driver = {
.name = "ssc",
.desc = "Super Serial card",
.init = _mii_ssc_init,
.dispose = _mii_ssc_dispose,
.access = _mii_ssc_access,
.command = _mii_ssc_command,
};
MI_DRIVER_REGISTER(_driver);

View File

@ -60,7 +60,7 @@ _mii_titan_probe(
mii_t *mii,
uint32_t flags)
{
printf("%s %s\n", __func__, flags & MII_INIT_TITAN ? "enabled" : "disabled");
// printf("%s %s\n", __func__, flags & MII_INIT_TITAN ? "enabled" : "disabled");
if (!(flags & MII_INIT_TITAN))
return 0;
// this override a read-only soft switch, but we only handle writes

View File

@ -196,7 +196,7 @@ header:
continue;
}
const int data_field_sz = 0x15A; //counts prologue, not epilogue
// const int data_field_sz = 0x15A; //counts prologue, not epilogue
for (;;) {
if (rd >= (end - 0x15A)) goto bail;
if (rd[0] == 0xD5 && rd[1] == 0xAA) {

View File

@ -36,6 +36,7 @@ mii_dd_system_init(
struct mii_t *mii,
mii_dd_system_t *dd )
{
// printf("*** %s: %p\n", __func__, dd);
dd->drive = NULL;
dd->file = NULL;
}
@ -44,6 +45,7 @@ void
mii_dd_system_dispose(
mii_dd_system_t *dd )
{
// printf("*** %s: %p\n", __func__, dd);
while (dd->file)
mii_dd_file_dispose(dd, dd->file);
dd->file = NULL;
@ -56,6 +58,7 @@ mii_dd_register_drives(
mii_dd_t * drives,
uint8_t count )
{
// printf("%s: registering %d drives\n", __func__, count);
for (int i = 0; i < count; i++) {
mii_dd_t *d = &drives[i];
d->dd = dd;
@ -121,6 +124,8 @@ mii_dd_drive_load(
dd->file = file;
printf("%s: %s loading %s\n", __func__,
dd->name, file->pathname);
if (dd->ro || dd->wp)
return 0;
if (mii_dd_overlay_load(dd) < 0) {
printf("%s: No overlay to load, we're fine for now\n", __func__);
// no overlay.. what to do?
@ -371,6 +376,8 @@ mii_dd_write(
{
if (!dd || !dd->file || !dd->file->map)
return -1;
if (dd->ro || dd->wp)
return -1;
// printf("%s: %s write %d blocks at %d\n",
// __func__, dd->name, blockcount, blk);
mii_dd_overlay_prepare(dd);

View File

@ -13,8 +13,9 @@
struct mii_dd_t;
enum {
MII_DD_FILE_OVERLAY = 1,
// MII_DD_FILE_OVERLAY = 1,
MII_DD_FILE_RAM,
MII_DD_FILE_ROM,
MII_DD_FILE_2MG = 5,
};
@ -52,9 +53,9 @@ typedef union mii_dd_overlay_header_t {
typedef struct mii_dd_overlay_t {
mii_dd_overlay_header_t *header; // points to the file mapped in memory
uint64_t * bitmap; // usage bitmap
uint8_t * blocks; // raw block data
mii_dd_file_t *file; // overlay file mapping
uint64_t * bitmap; // usage bitmap
uint8_t * blocks; // raw block data
mii_dd_file_t * file; // overlay file mapping
} mii_dd_overlay_t;
struct mii_slot_t;
@ -62,19 +63,19 @@ struct mii_dd_system_t;
// a disk drive, with a slot, a drive number, and a file
typedef struct mii_dd_t {
struct mii_dd_t *next;
struct mii_dd_t * next;
struct mii_dd_system_t *dd;
const char * name; // ie "Disk ][ D:2"
uint8_t slot_id : 4, drive : 4;
struct mii_slot_t *slot;
unsigned int ro : 1, wp : 1, can_eject : 1;
mii_dd_file_t * file;
mii_dd_overlay_t overlay;
const char * name; // ie "Disk ][ D:2"
uint8_t slot_id : 4, drive : 4;
struct mii_slot_t * slot;
unsigned int ro : 1, wp : 1, can_eject : 1;
mii_dd_file_t * file;
mii_dd_overlay_t overlay;
} mii_dd_t;
typedef struct mii_dd_system_t {
mii_dd_t * drive; // list of all drives on all slots
mii_dd_file_t * file; // list of all open files (inc overlays)
mii_dd_t * drive; // list of all drives on all slots
mii_dd_file_t * file; // list of all open files (inc overlays)
} mii_dd_system_t;
struct mii_t;

245
src/mii.c
View File

@ -83,7 +83,7 @@ mii_dump_trace_state(
// display the S flags
static const char *s_flags = "CZIDBRVN";
for (int i = 0; i < 8; i++)
printf("%c", cpu->P.P[7-i] ? s_flags[7-i] : tolower(s_flags[7-i]));
printf("%c", MII_GET_P_BIT(cpu, i) ? s_flags[i] : tolower(s_flags[i]));
if (s.sync) {
uint8_t op[16];
for (int i = 0; i < 4; i++) {
@ -96,7 +96,7 @@ mii_dump_trace_state(
MII_DUMP_DIS_DUMP_HEX);
printf(": %s", dis);
if (d.desc.branch) {
if (cpu->P.P[d.desc.s_bit] == d.desc.s_bit_value)
if (MII_GET_P_BIT(cpu, d.desc.s_bit) == d.desc.s_bit_value)
printf(" ; taken");
}
printf("\n");
@ -109,7 +109,7 @@ mii_dump_run_trace(
mii_t *mii)
{
// walk all the previous PC values in mii->trace, and display a line
// of disassebly for all of them
// of disassembly for all of them
for (int li = 0; li < MII_PC_LOG_SIZE; li++) {
int idx = (mii->trace.idx + li) & (MII_PC_LOG_SIZE - 1);
uint16_t pc = mii->trace.log[idx];
@ -154,7 +154,7 @@ static void
mii_page_table_update(
mii_t *mii)
{
if (!mii->mem_dirty)
if (likely(!mii->mem_dirty))
return;
mii->mem_dirty = 0;
bool altzp = SW_GETSTATE(mii, SWALTPZ);
@ -165,9 +165,10 @@ mii_page_table_update(
bool ramwrt = SW_GETSTATE(mii, SWRAMWRT);
bool intcxrom = SW_GETSTATE(mii, SWINTCXROM);
bool slotc3rom = SW_GETSTATE(mii, SWSLOTC3ROM);
bool slotauxrom = SW_GETSTATE(mii, SLOTAUXROM);
if (mii->trace_cpu)
printf("%04x: page table update altzp:%d page2:%d store80:%d "
if (unlikely(mii->trace_cpu))
printf("%04x: MEM update altzp:%d page2:%d store80:%d "
"hires:%d ramrd:%d ramwrt:%d intcxrom:%d "
"slotc3rom:%d\n", mii->cpu.PC,
altzp, page2, store80, hires, ramrd, ramwrt, intcxrom, slotc3rom);
@ -188,8 +189,11 @@ mii_page_table_update(
page2 ? MII_BANK_AUX : MII_BANK_MAIN,
page2 ? MII_BANK_AUX : MII_BANK_MAIN, 0x20, 0x3f);
}
if (!intcxrom)
if (!intcxrom) {
mii_page_set(mii, MII_BANK_CARD_ROM, _SAME, 0xc1, 0xc7);
if (slotauxrom)
mii_page_set(mii, MII_BANK_CARD_ROM, _SAME, 0xc8, 0xcf);
}
mii_page_set(mii,
slotc3rom ? MII_BANK_CARD_ROM : MII_BANK_ROM, _SAME, 0xc3, 0xc3);
bool bsrread = SW_GETSTATE(mii, BSRREAD);
@ -229,6 +233,55 @@ mii_set_sw_override(
mii->soft_switches_override[sw_addr].param = param;
}
/*
* This watches for any write to 0xcfff -- if a card had it's aux rom
* selected, it will deselect it.
*/
static bool
_mii_deselect_auxrom(
struct mii_bank_t *bank,
void *param,
uint16_t addr,
uint8_t * byte,
bool write)
{
if (addr != 0xcfff)
return false;
mii_t * mii = param;
// printf("%s AUXROM:%d\n", __func__, !!(mii->sw_state & M_SLOTAUXROM));
if (!(mii->sw_state & M_SLOTAUXROM))
return false;
for (int i = 0; i < 7; i++) {
mii_slot_t * slot = &mii->slot[i];
if (slot->aux_rom_selected) {
printf("%s %d: %s\n", __func__,
i, slot->drv ? slot->drv->name : "(none?)");
slot->aux_rom_selected = false;
}
}
mii->sw_state &= ~M_SLOTAUXROM;
mii->mem_dirty = true;
return false;
}
static bool
_mii_select_c3rom(
struct mii_bank_t *bank,
void *param,
uint16_t addr,
uint8_t * byte,
bool write)
{
mii_t * mii = param;
printf("%s\n", __func__);
if (mii->sw_state & M_SLOTAUXROM) {
// printf("%s: C3 aux rom re-selected\n", __func__);
mii->sw_state &= ~M_SLOTAUXROM;
}
mii->mem_dirty = true;
return false;
}
static bool
mii_access_soft_switches(
mii_t *mii,
@ -236,7 +289,7 @@ mii_access_soft_switches(
uint8_t * byte,
bool write)
{
if (!(addr >= 0xc000 && addr <= 0xc0ff) || addr == 0xcfff)
if (!(addr >= 0xc000 && addr <= 0xc0ff))
return false;
bool res = false;
uint8_t on = 0;
@ -280,7 +333,9 @@ mii_access_soft_switches(
SW_SETSTATE(mii, BSRREAD, rd);
SW_SETSTATE(mii, BSRPAGE2, !(mode & 0x08));
mii->mem_dirty = 1;
if (mii->trace_cpu)
// mii->trace_cpu = 1;
// mii->state = MII_STOPPED;
if (unlikely(mii->trace_cpu))
printf("%04x: BSR mode addr %04x:%02x read:%s write:%s %s altzp:%02x\n",
mii->cpu.PC, addr,
mode,
@ -289,11 +344,6 @@ mii_access_soft_switches(
SW_GETSTATE(mii, BSRPAGE2) ? "page2" : "page1",
mii_sw(mii, SWALTPZ));
} break;
case 0xcfff:
res = true;
mii->mem_dirty = 1;
printf("%s TODO reset SLOT roms\n", __func__);
break;
case SWPAGE2OFF:
case SWPAGE2ON:
res = true;
@ -390,15 +440,22 @@ mii_access_soft_switches(
*byte = mii_bank_peek(main, addr);
break;
case 0xc020: // toggle TAPE output ?!?!
case 0xc064: // Analog Input 0 (paddle 0)
case 0xc065: // Analog Input 1 (paddle 1)
case 0xc079: // Analog Input Reset
res = true;
break;
case 0xc068:
res = true;
// IIgs register, read by prodos tho
break;
default:
res = true;
/*
* this is moderately important, return some random value
* as it is supposed to represent what's on the bus at the time,
* typically video being decoded etc.
*/
*byte = mii->random[mii->random_index++];
mii->random_index &= 0xff;
break;
}
}
if (!res) {
@ -460,15 +517,35 @@ mii_init(
mii_t *mii )
{
memset(mii, 0, sizeof(*mii));
mii->speed = 1.0;
mii->speed = 1.023;
mii->timer.map = 0;
for (int i = 0; i < MII_BANK_COUNT; i++)
mii->bank[i] = _mii_banks_init[i];
mii->bank[MII_BANK_ROM].mem = (uint8_t*)&iie_enhanced_rom_bin[0];
for (int i = 0; i < MII_BANK_COUNT; i++)
mii_bank_init(&mii->bank[i]);
mii->cpu.trap = MII_TRAP;
// these are called once, regardless of reset
mii_dd_system_init(mii, &mii->dd);
mii_analog_init(mii, &mii->analog);
mii_video_init(mii);
mii_speaker_init(mii, &mii->speaker);
mii_reset(mii, true);
mii->cpu_state = mii_cpu_init(&mii->cpu);
for (int i = 0; i < 7; i++)
mii->slot[i].id = i;
// srandom(time(NULL));
for (int i = 0; i < 256; i++)
mii->random[i] = random();
mii_bank_install_access_cb(&mii->bank[MII_BANK_CARD_ROM],
_mii_deselect_auxrom, mii, 0xcf, 0xcf);
mii_bank_install_access_cb(&mii->bank[MII_BANK_ROM],
_mii_deselect_auxrom, mii, 0xcf, 0xcf);
mii_bank_install_access_cb(&mii->bank[MII_BANK_ROM],
_mii_select_c3rom, mii, 0xc3, 0xc3);
}
void
@ -476,14 +553,12 @@ mii_prepare(
mii_t *mii,
uint32_t flags )
{
mii_dd_system_init(mii, &mii->dd);
mii_speaker_init(mii, &mii->speaker);
printf("%s driver table\n", __func__);
// printf("%s driver table\n", __func__);
mii_slot_drv_t * drv = mii_slot_drv_list;
while (drv) {
printf("%s driver: %s\n", __func__, drv->name);
if (drv->probe && drv->probe(mii, flags)) {
printf("%s %s probe done\n", __func__, drv->name);
// printf("%s %s probe done\n", __func__, drv->name);
}
drv = drv->next;
}
@ -501,6 +576,7 @@ mii_dispose(
mii_bank_dispose(&mii->bank[i]);
mii_speaker_dispose(&mii->speaker);
mii_dd_system_dispose(&mii->dd);
mii->state = MII_INIT;
}
void
@ -509,6 +585,7 @@ mii_reset(
bool cold)
{
// printf("%s cold %d\n", __func__, cold);
mii->state = MII_RUNNING;
mii->cpu_state.reset = 1;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii->sw_state = M_BSRWRITE | M_BSRPAGE2;
@ -547,27 +624,27 @@ mii_mem_access(
mii_access_keyboard(mii, addr, d, wr) ||
mii_access_video(mii, addr, d, wr) ||
mii_access_soft_switches(mii, addr, d, wr);
if (!done) {
uint8_t page = addr >> 8;
if (wr) {
uint8_t m = mii->mem[page].write;
mii_bank_t * b = &mii->bank[m];
if (b->ro) {
// printf("%s write to RO bank %s %04x:%02x\n",
// __func__, b->name, addr, *d);
} else
mii_bank_write(b, addr, d, 1);
} else {
uint8_t m = mii->mem[page].read;
mii_bank_t * b = &mii->bank[m];
*d = mii_bank_peek(b, addr);
}
if (done)
return;
uint8_t page = addr >> 8;
if (wr) {
uint8_t m = mii->mem[page].write;
mii_bank_t * b = &mii->bank[m];
if (b->ro) {
// printf("%s write to RO bank %s %04x:%02x\n",
// __func__, b->name, addr, *d);
} else
mii_bank_write(b, addr, d, 1);
} else {
uint8_t m = mii->mem[page].read;
mii_bank_t * b = &mii->bank[m];
*d = mii_bank_peek(b, addr);
}
}
static void
_mii_handle_trap(
mii_t *mii)
mii_t *mii)
{
// printf("%s TRAP hit PC: %04x\n", __func__, mii->cpu.PC);
mii->cpu_state.sync = 1;
@ -588,8 +665,8 @@ _mii_handle_trap(
uint8_t
mii_register_trap(
mii_t *mii,
mii_trap_handler_cb cb)
mii_t *mii,
mii_trap_handler_cb cb)
{
if (mii->trap.map == 0xffff) {
printf("%s no more traps!!\n", __func__);
@ -605,23 +682,91 @@ mii_register_trap(
return 0xff;
}
uint8_t
mii_timer_register(
mii_t *mii,
mii_timer_p cb,
void *param,
int64_t when,
const char *name)
{
if (mii->timer.map == (uint64_t)-1) {
printf("%s no more timers!!\n", __func__);
return 0xff;
}
int i = ffsll(~mii->timer.map) - 1;
mii->timer.map |= 1ull << i;
mii->timer.timers[i].cb = cb;
mii->timer.timers[i].param = param;
mii->timer.timers[i].when = when;
mii->timer.timers[i].name = name;
return i;
}
int64_t
mii_timer_get(
mii_t *mii,
uint8_t timer_id)
{
if (timer_id >= (int)sizeof(mii->timer.map) * 8)
return 0;
return mii->timer.timers[timer_id].when;
}
int
mii_timer_set(
mii_t *mii,
uint8_t timer_id,
int64_t when)
{
if (timer_id >= (int)sizeof(mii->timer.map) * 8)
return -1;
mii->timer.timers[timer_id].when = when;
return 0;
}
static void
mii_timer_run(
mii_t *mii,
uint64_t cycles)
{
uint64_t timer = mii->timer.map;
while (timer) {
int i = ffsll(timer) - 1;
timer &= ~(1ull << i);
if (mii->timer.timers[i].when > 0) {
mii->timer.timers[i].when -= cycles;
if (mii->timer.timers[i].when <= 0) {
if (mii->timer.timers[i].cb)
mii->timer.timers[i].when += mii->timer.timers[i].cb(mii,
mii->timer.timers[i].param);
}
}
}
}
void
mii_run(
mii_t *mii)
{
/* this runs all cycles for one instruction */
uint16_t cycle = mii->cpu.cycle;
do {
if (mii->trace_cpu)
if (unlikely(mii->trace_cpu > 1))
mii_dump_trace_state(mii);
mii->cpu_state = mii_cpu_run(&mii->cpu, mii->cpu_state);
mii_video_run(mii);
mii_speaker_run(&mii->speaker);
mii_timer_run(mii,
mii->cpu.cycle > cycle ? mii->cpu.cycle - cycle :
mii->cpu.cycle);
cycle = mii->cpu.cycle;
// extract 16-bit address from pin mask
const uint16_t addr = mii->cpu_state.addr;
const uint8_t data = mii->cpu_state.data;
// const uint8_t data = mii->cpu_state.data;
int wr = mii->cpu_state.w;
uint8_t d = data;
if (mii->debug.bp_map) {
// uint8_t d = data;
if (unlikely(mii->debug.bp_map)) {
for (int i = 0; i < (int)sizeof(mii->debug.bp_map) * 8; i++) {
if (!(mii->debug.bp_map & (1 << i)))
continue;
@ -644,14 +789,12 @@ mii_run(
}
}
}
mii_mem_access(mii, addr, &d, wr, true);
if (!wr)
mii->cpu_state.data = d;
if (mii->cpu_state.trap) {
mii_mem_access(mii, addr, &mii->cpu_state.data, wr, true);
if (unlikely(mii->cpu_state.trap)) {
_mii_handle_trap(mii);
}
} while (!(mii->cpu_state.sync));
mii->cycles += mii->cpu.cycle;
// log PC for the running disassembler display
mii->trace.log[mii->trace.idx] = mii->cpu.PC;
mii->trace.idx = (mii->trace.idx + 1) & (MII_PC_LOG_SIZE - 1);

View File

@ -10,7 +10,6 @@
#include <stdint.h>
#include <stdbool.h>
#include "mii_types.h"
#include "mii_65c02.h"
#include "mii_dd.h"
#include "mii_bank.h"
@ -20,6 +19,9 @@
#include "mii_mouse.h"
#include "mii_analog.h"
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
enum {
MII_BANK_MAIN = 0, // main 48K address space
MII_BANK_BSR, // 0xd000 - 0xffff bank switched RAM 16KB
@ -50,7 +52,8 @@ typedef struct mii_trap_t {
// state of the emulator
enum {
MII_RUNNING = 0, // default
MII_INIT = 0,
MII_RUNNING, // default
MII_STOPPED,
MII_STEP,
MII_TERMINATE,
@ -78,16 +81,32 @@ typedef struct mii_trace_t {
uint32_t step_inst;
} mii_trace_t;
typedef uint64_t (*mii_timer_p)(
mii_t * mii,
void * param );
/*
* principal emulator state, for a faceless emulation
*/
typedef struct mii_t {
unsigned int state;
mii_cycles_t cycles;
/*
* These are 'cycle timers' -- they count down from a set value,
* and stop at 0 (or possiblu -1 or -2, depending on the instructions)
* and call the callback (if present).
* The callback returns the number of cycles to wait until the next
* call.
*/
struct {
uint64_t map;
struct {
mii_timer_p cb;
void * param;
int64_t when;
const char * name; // debug
} timers[64];
} timer;
/* this is the video frame/VBL rate vs 60hz, default to 1.0 */
float speed;
float speed_current; // calculated speed
mii_cpu_t cpu;
mii_cpu_state_t cpu_state;
/*
@ -125,6 +144,9 @@ typedef struct mii_t {
mii_mouse_t mouse;
mii_dd_system_t dd;
mii_analog_t analog;
uint8_t random[256];
uint8_t random_index;
} mii_t;
enum {
@ -235,7 +257,6 @@ mii_write_word(
mii_t *mii,
uint16_t addr,
uint16_t w);
/* lower level call to access memory -- this one can trigger softswitches
* if specified. Otherwise behaves as the previous ones, one byte at a time
*/
@ -256,12 +277,33 @@ mii_set_sw_override(
mii_bank_access_cb cb,
void *param);
/* register a cycle timer. cb will be called when (at least) when
* cycles have been spent -- the callback returns how many it should
* spend until the next call */
uint8_t
mii_timer_register(
mii_t *mii,
mii_timer_p cb, // this is optional, can be NULL
void *param,
int64_t when,
const char *name);
/* return the cycles left for timer_id (can be negative !)*/
int64_t
mii_timer_get(
mii_t *mii,
uint8_t timer_id);
int
mii_timer_set(
mii_t *mii,
uint8_t timer_id,
int64_t when);
void
mii_dump_trace_state(
mii_t *mii);
mii_t *mii);
void
mii_dump_run_trace(
mii_t *mii);
mii_t *mii);
extern mii_slot_drv_t * mii_slot_drv_list;

View File

@ -8,6 +8,9 @@
#define MII_CPU_65C02_IMPL
#include "mii_65c02_ops.h"
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
mii_cpu_state_t
mii_cpu_init(
mii_cpu_t *cpu )
@ -23,31 +26,27 @@ mii_cpu_init(
#define _FETCH(_val) { \
s.addr = _val; s.w = 0; cpu->cycle++; \
pt_yield(cpu->state); \
} while (0);
}
#define _STORE(_addr, _val) { \
s.addr = _addr; s.data = _val; s.w = 1; cpu->cycle++; \
pt_yield(cpu->state); \
} while (0);
}
#define _SET_P(_byte) { \
for (int _pi = 0; _pi < 8; _pi++) \
cpu->P.P[_pi] = _pi == B_B || _pi == B_X || \
((_byte) & (1 << _pi)); \
}
#define _NZC(_val) { \
uint16_t v = (_val); \
cpu->P.N = !!(v & 0x80); \
cpu->P.Z = (v & 0xff) == 0; \
cpu->P.C = !!(v & 0xff00); \
} while (0)
}
#define _NZ(_val) { \
uint16_t v = (_val); \
cpu->P.N = !!(v & 0x80); \
cpu->P.Z = (v & 0xff) == 0; \
} while (0)
}
#define _C(_val) { \
cpu->P.C = !!(_val); \
} while (0)
}
mii_cpu_state_t
mii_cpu_run(
@ -57,28 +56,30 @@ mii_cpu_run(
mii_op_desc_t d = mii_cpu_op[cpu->IR].desc;
pt_start(cpu->state);
next_instruction:
if (s.reset) {
if (unlikely(s.reset)) {
s.reset = 0;
_FETCH(0xfffc); cpu->_P = s.data;
_FETCH(0xfffd); cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
cpu->S = 0xFF;
_SET_P(0);
MII_SET_P(cpu, 0);
}
if (s.irq) {
if (unlikely(s.irq) && cpu->P.I == 0) {
if (!cpu->IRQ)
cpu->IRQ = 1;
}
if (unlikely(cpu->IRQ)) {
s.irq = 0;
cpu->P.P[B_B] = cpu->IRQ == 2;
cpu->IRQ = 1;
cpu->_D = cpu->PC + 1;
cpu->P.B = cpu->IRQ == 2;
cpu->_D = cpu->PC;
_STORE(0x0100 | cpu->S--, cpu->_D >> 8);
_STORE(0x0100 | cpu->S--, cpu->_D & 0xff);
uint8_t p = 0;
for (int i = 0; i < 8; i++)
p |= (i == B_X || cpu->P.P[i]) << i;
MII_GET_P(cpu, p);
_STORE(0x0100 | cpu->S--, p);
cpu->P.P[B_I] = 1;
}
if (cpu->IRQ) {
cpu->P.I = 1;
if (cpu->IRQ == 2)
cpu->P.D = 0;
cpu->IRQ = 0;
_FETCH(0xfffe); cpu->_P = s.data;
_FETCH(0xffff); cpu->_P |= s.data << 8;
@ -95,7 +96,7 @@ next_instruction:
d = mii_cpu_op[cpu->IR].desc;
cpu->ir_log = (cpu->ir_log << 8) | cpu->IR;
s.trap = cpu->trap && (cpu->ir_log & 0xffff) == cpu->trap;
if (s.trap)
if (unlikely(s.trap))
cpu->ir_log = 0;
switch (d.mode) {
case IMM:
@ -173,17 +174,20 @@ next_instruction:
case 0x79: case 0x61: case 0x71: case 0x72:
{ // ADC
// Handle adding in BCD with bit D
if (cpu->P.D) {
uint8_t lo = (cpu->A & 0x0f) + (cpu->_D & 0x0f) + !!cpu->P.C;
if (unlikely(cpu->P.D)) {
uint8_t D = cpu->_D;
uint8_t lo = (cpu->A & 0x0f) + (D & 0x0f) + !!cpu->P.C;
if (lo > 9) lo += 6;
uint8_t hi = (cpu->A >> 4) + (cpu->_D >> 4) + (lo > 0x0f);
cpu->P.Z = ((uint8_t)(cpu->A + cpu->_D + cpu->P.C)) == 0;
uint8_t hi = (cpu->A >> 4) + (D >> 4) + (lo > 0x0f);
cpu->P.Z = ((uint8_t)(cpu->A + D + cpu->P.C)) == 0;
// that is 6502 behaviour
// cpu->P.N = !!(hi & 0xf8);
cpu->P.V = !!((!((cpu->A ^ cpu->_D) & 0x80) &&
cpu->P.V = !!((!((cpu->A ^ D) & 0x80) &&
((cpu->A ^ (hi << 4))) & 0x80));
if (hi > 9) hi += 6;
cpu->P.C = hi > 15;
// printf("ADC %02x %02x C:%d %x%x\n",
// cpu->A, D, !!cpu->P.C, hi & 0xf, lo & 0xf);
cpu->A = (hi << 4) | (lo & 0x0f);
// THAT is 65c02 behaviour
cpu->P.N = !!(cpu->A & 0x80);
@ -219,9 +223,11 @@ next_instruction:
case 0xaf: case 0xbf: case 0xcf: case 0xdf: case 0xef:
case 0xff:
{ // BBR/BBS
// printf(" BB%c%d vs %02x\n", d.s_bit_value ? 'S' : 'R',
// d.s_bit, cpu->_D);
_FETCH(cpu->PC++); // relative branch
if (((cpu->_D >> d.s_bit) & 1) == d.s_bit_value) {
cpu->_P = cpu->PC + (int8_t)cpu->_P;
cpu->_P = cpu->PC + (int8_t)s.data;
cpu->cycle++;
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
cpu->cycle++;
@ -231,7 +237,7 @@ next_instruction:
case 0x90: case 0xB0: case 0xF0: case 0x30:
case 0xD0: case 0x10: case 0x50: case 0x70:
{ // BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS
if (d.s_bit_value == cpu->P.P[d.s_bit]) {
if (d.s_bit_value == MII_GET_P_BIT(cpu, d.s_bit)) {
cpu->_P = cpu->PC + (int8_t)cpu->_P;
cpu->cycle++;
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
@ -259,14 +265,16 @@ next_instruction:
} break;
case 0x00:
{ // BRK
// Turns out BRK is a 2 byte opcode, who knew? well that guy did:
// https://www.nesdev.org/the%20'B'%20flag%20&%20BRK%20instruction.txt#:~:text=A%20note%20on%20the%20BRK,opcode%2C%20and%20not%20just%201.
_FETCH(cpu->PC++); // cpu->cycle++;
s.irq = 1;
cpu->IRQ = 2; // IRQ interrupt
// cpu->P.P[B_D] = 0; // 65c02 clears this
cpu->IRQ = 2; // BRK sort of IRQ interrupt
} break;
case 0x18: case 0xD8: case 0x58: case 0xB8:
{ // CLC, CLD, CLI, CLV
_FETCH(cpu->PC);
cpu->P.P[d.s_bit] = 0;
MII_SET_P_BIT(cpu, d.s_bit, 0);
} break;
case 0xC9: case 0xC5: case 0xD5: case 0xCD: case 0xDD:
case 0xD9: case 0xC1: case 0xD1: case 0xD2:
@ -338,10 +346,13 @@ next_instruction:
cpu->PC = cpu->_P;
} break;
case 0x20:
// https://github.com/AppleWin/AppleWin/issues/1257
{ // JSR
cpu->_D = cpu->PC - 1;
_STORE(0x0100 | cpu->S--, cpu->_D >> 8);
_STORE(0x0100 | cpu->S--, cpu->_D & 0xff);
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(0x0100 | cpu->S);
_STORE(0x0100 | cpu->S--, cpu->PC >> 8);
_STORE(0x0100 | cpu->S--, cpu->PC & 0xff);
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
} break;
case 0xA9: case 0xA5: case 0xB5: case 0xAD: case 0xBD:
@ -389,9 +400,8 @@ next_instruction:
case 0x08:
{ // PHP
uint8_t p = 0;
for (int i = 0; i < 8; i++)
p |= (i == B_X || cpu->P.P[i]) << i;
p |= (1 << B_B);
MII_GET_P(cpu, p);
p |= (1 << B_B) | (1 << B_X);
_STORE(0x0100 | cpu->S--, p); cpu->cycle++;
} break;
case 0xDA:
@ -411,7 +421,7 @@ next_instruction:
case 0x28:
{ // PLP
_FETCH(0x0100 | ++cpu->S);cpu->cycle++;
_SET_P(s.data);cpu->cycle++;
MII_SET_P(cpu, s.data);cpu->cycle++;
} break;
case 0xFA:
{ // PLX
@ -459,14 +469,15 @@ next_instruction:
} break;
case 0x40:
{ // RTI
_FETCH(0x0100 | ((++cpu->S) & 0xff));
_FETCH(cpu->PC); // dummy write
cpu->S++; _FETCH(0x0100 | cpu->S);
for (int i = 0; i < 8; i++)
cpu->P.P[i] = i == B_B || (s.data & (1 << i));
MII_SET_P_BIT(cpu, i, i == B_B || (s.data & (1 << i)));
cpu->P._R = 1;
_FETCH(0x0100 | ((++cpu->S) & 0xff));
cpu->S++; _FETCH(0x0100 | cpu->S);
cpu->_P = s.data;
_FETCH(0x0100 | ((++cpu->S) & 0xff));
cpu->_P = (s.data << 8) | (cpu->_P );
cpu->S++; _FETCH(0x0100 | cpu->S);
cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
} break;
case 0x60:
@ -481,17 +492,24 @@ next_instruction:
case 0xF9: case 0xE1: case 0xF1: case 0xF2:
{ // SBC
// Handle subbing in BCD with bit D
if (cpu->P.D) {
uint8_t lo = (cpu->A & 0x0f) - (cpu->_D & 0x0f) - !cpu->P.C;
if (lo & 0x10) lo -= 6;
uint8_t hi = (cpu->A >> 4) - (cpu->_D >> 4) - (lo & 0x10);
if (hi & 0x10) hi -= 6;
cpu->P.Z = ((uint8_t)(cpu->A - cpu->_D - !cpu->P.C)) == 0;
cpu->P.N = !!(hi & 0x8);
cpu->P.V = !!(((cpu->A ^ cpu->_D) &
if (unlikely(cpu->P.D)) {
uint8_t D = 0x99 - cpu->_D;
// verbatim ADC code here
uint8_t lo = (cpu->A & 0x0f) + (D & 0x0f) + !!cpu->P.C;
if (lo > 9) lo += 6;
uint8_t hi = (cpu->A >> 4) + (D >> 4) + (lo > 0x0f);
cpu->P.Z = ((uint8_t)(cpu->A + D + cpu->P.C)) == 0;
// that is 6502 behaviour
// cpu->P.N = !!(hi & 0xf8);
cpu->P.V = !!((!((cpu->A ^ D) & 0x80) &&
((cpu->A ^ (hi << 4))) & 0x80));
cpu->P.C = !(hi & 0x10);
if (hi > 9) hi += 6;
cpu->P.C = hi > 15;
// printf("SBC %02x %02x C:%d %x%x\n",
// cpu->A, D, !!cpu->P.C, hi & 0xf, lo & 0xf);
cpu->A = (hi << 4) | (lo & 0x0f);
// THAT is 65c02 behaviour
cpu->P.N = !!(cpu->A & 0x80);
} else {
cpu->_D = (~cpu->_D) & 0xff;
uint16_t sum = cpu->A + cpu->_D + !!cpu->P.C;
@ -504,7 +522,7 @@ next_instruction:
} break;
case 0x38: case 0xF8: case 0x78:
{ // SEC, SED, SEI
cpu->P.P[d.s_bit] = 1;
MII_SET_P_BIT(cpu, d.s_bit, 1);
} break;
case 0x85: case 0x95: case 0x8D: case 0x9D:
case 0x99: case 0x81: case 0x91: case 0x92:

View File

@ -2,6 +2,13 @@
#include <stdint.h>
/*
* This is pretty heavily dependant on the way bitfields are packed in
* bytes, so it's not technically portable; if you have problems using a
* strange compiler, undefine this and use the 'discrete' version below.
*/
#define MII_PACK_P
/*
* State structure used to 'talk' to the CPU emulator.
* It works like this:
@ -27,9 +34,9 @@
*/
typedef union mii_cpu_state_t {
struct {
uint32_t addr : 16,
data : 8,
w : 1,
uint16_t addr;
uint8_t data;
uint8_t w : 1,
sync : 1,
reset : 1,
irq : 1,
@ -49,20 +56,30 @@ typedef struct mii_cpu_t {
* are 'indirect' and load from memory will set _P and read in _D
* so the opcode doesn't have to handle s.data at all */
uint16_t _D, _P;
#ifdef MII_PACK_P
union {
struct {
uint8_t C:1, Z:1, I:1, D:1, B:1, _R:1, V:1, N:1;
};
uint8_t P;
} P;
#else
/* My experience with simavr shows that maintaining a 8 bits bitfield
* for a status register is a lot slower than having discrete flags
* and 'constructing' the matching 8 biots register when needed */
* and 'constructing' the matching 8 bits register when needed */
union {
struct {
uint8_t C, Z, I, D, B, _R, V, N;
};
uint8_t P[8];
} P;
#endif
uint16_t PC;
uint8_t IR;
uint8_t IRQ; // IRQ (0) or NMI (1) or BRK (2)
uint8_t cycle; // for current instruction
/* State of the CPU state machine */
/* State of the protothread for the CPU state machine (minipt.h) */
void * state;
/* sequence of instruction that will trigger a trap flag.
@ -73,8 +90,7 @@ typedef struct mii_cpu_t {
// last 4 instructions, as a shift register, used for traps or debug
uint32_t ir_log;
/* Debug only; the callback is called every cycles, with the current
* state of the cpu. */
/* Debug only; Only used by the test units. */
uint8_t * ram; // DEBUG
} mii_cpu_t;
@ -86,3 +102,35 @@ mii_cpu_state_t
mii_cpu_run(
mii_cpu_t *cpu,
mii_cpu_state_t s);
#ifdef MII_PACK_P
#define MII_SET_P(_cpu, _byte) { \
(_cpu)->P.P = _byte | 0x30; \
}
#define MII_GET_P(_cpu, _res) \
(_res) = (_cpu)->P.P
#define MII_SET_P_BIT(_cpu, _bit, _val) { \
const int __bit = _bit; \
(_cpu)->P.P = ((_cpu)->P.P & ~(1 << __bit)) | (!!(_val) << __bit); \
}
#define MII_GET_P_BIT(_cpu, _bit) \
!!(((_cpu)->P.P & (1 << (_bit))))
#else
#define MII_SET_P(_cpu, _byte) { \
const int __byte = _byte; \
for (int _pi = 0; _pi < 8; _pi++) \
(_cpu)->P.P[_pi] = _pi == B_B || _pi == B_X || \
((__byte) & (1 << _pi)); \
}
#define MII_GET_P(_cpu, _res) { \
(_res) = 0; \
for (int _pi = 0; _pi < 8; _pi++) \
(_res) |= (_cpu)->P.P[_pi] << _pi; \
}
#define MII_SET_P_BIT(_cpu, _bit, _val) { \
(_cpu)->P.P[_bit] = _val; \
}
#define MII_GET_P_BIT(_cpu, _bit) \
((_cpu)->P.P[_bit])
#endif

View File

@ -150,7 +150,7 @@ mii_cpu_asm_load(
* that can be resolved immediately */
char *kw = position;
while (*kw == ' ' || *kw == '\t') kw++;
char *cur = kw;
char *cur = NULL;
while ((cur = strsep(&kw, ",")) != NULL) {
while (*cur == ' ' || *cur == '\t') cur++;
char *ke = cur + strlen(cur);
@ -339,7 +339,7 @@ int
mii_cpu_asm_assemble(
mii_cpu_asm_program_t *p )
{
mii_cpu_asm_line_t *l = p->prog;
mii_cpu_asm_line_t *l = NULL;
int error = 0;
// fix symbols
@ -386,6 +386,12 @@ mii_cpu_asm_assemble(
} else if (l->mode == mii_cpu_op[i].desc.mode) {
found = i;
break;
} else if (mii_cpu_op[i].desc.op == 0x20 &&
l->mode == ABS) {
// this is JSR -- TECHNICALLY it's ABS mode, but
// it has to do a 'special' fetch so it's marked as
// implied in the table
found = i;
}
}
}
@ -433,7 +439,7 @@ mii_cpu_asm_assemble(
while (l2) {
int32_t value = 0;
if (!strcasecmp(l->op_name, l2->label)) {
value = l2->op_value;
// value = l2->op_value;
if (!l2->symbol)
value = l2->addr;
else

View File

@ -21,6 +21,10 @@ mii_cpu_disasm_one(
mii_op_desc_t d = mii_cpu_op[op].desc;
if (!d.pc)
d.pc = 1;
// special case for JSR, it is marked as IMPLIED for execution, but is
// in fact ABSOLUTE for PC calculation
if (op == 0x20)
d.mode = ABS;
*out = 0;
int len = out_len;
if (flags & MII_DUMP_DIS_PC)

View File

@ -189,7 +189,7 @@ const mii_op_t mii_cpu_op[256] = {
PCODE_PC(JMP, ABS, 0x4C, 3)
PCODE_PC(JMP, IND, 0x6C, 3)
PCODE_PC(JMP, IND_AX, 0x7C, 3)
PCODE_PC(JSR, ABS, 0x20, 3)
PCODE___(JSR, IMPLIED, 0x20, 3)
PCODE___(LDA, IMM, 0xA9, 2)
PCODE_R_(LDA, ZP_REL, 0xA5, 2)
PCODE_R_(LDA, ZP_X, 0xB5, 2)

View File

@ -14,6 +14,15 @@
#include "mii.h"
#include "mii_analog.h"
/*
* Analog joystick
* This is fairly easy, as long as the 65c02 respects the proper cycle
* count for all the instruction involved in reading, as it's very cycle
* sensitive.
* the UI fills up the analog values in mii_t, and here we just simulate
* the capacitor decay.
*/
void
mii_analog_init(
struct mii_t *mii,
@ -37,17 +46,35 @@ mii_analog_access(
return;
switch (addr) {
case 0xc070: {
// multiplying by mii->speed allows reading joystick in 'fast' mode,
// this basically simulate slowing down just for the joystick reading
/*
* No need starting the cycle timers when nobody cares about
* the analog values aka joysticks
*/
if (!a->enabled) {
a->enabled = true;
/*
* No need for a function pointer here for the timer, the
* decrementing value is just what we need, and we're quite
* happy to stop at ~0 as well.
*/
for (int i = 0; i < 4; i++)
a->v[i].timer_id = mii_timer_register(mii,
NULL, NULL, 0, __func__);
}
/*
* Multiplying by mii->speed allows reading joystick in
* 'fast' emulation mode, this basically simulate slowing down
* just for the joystick reading
*/
for (int i = 0; i < 4; i++) {
a->v[i].decay = mii->cycles +
((a->v[i].value * 11) * mii->speed);
mii_timer_set(mii, a->v[i].timer_id,
((a->v[i].value * 11) * mii->speed));
// printf("joystick %d: %d\n", i, a->v[i].value);
}
} break;
case 0xc064 ... 0xc067: {
addr -= 0xc064;
*byte = mii->cycles <= a->v[addr].decay ? 0x80 : 0x00;
*byte = mii_timer_get(mii, a->v[addr].timer_id) > 0 ? 0x80 : 0x00;
} break;
}
}

View File

@ -7,14 +7,15 @@
*/
#pragma once
#include "mii_types.h"
#include <stdint.h>
typedef struct mii_analog_t {
struct {
uint8_t value;
mii_cycles_t decay;
// mii_cycles_t decay;
uint8_t timer_id;
} v[4];
bool enabled;
} mii_analog_t;
struct mii_t;

View File

@ -150,7 +150,7 @@ mii_argv_parse(
} else if (!strcmp(arg, "-speed") || !strcmp(arg, "--speed")) {
if (i < argc-1) {
mii->speed = atof(argv[++i]);
if (mii->speed < 0.0)
if (mii->speed <= 0.0)
mii->speed = 1.0;
} else {
printf("mii: missing speed value\n");
@ -201,4 +201,4 @@ mii_argv_parse(
}
*index = argc;
return 1;
}
}

View File

@ -14,6 +14,16 @@
#include "mii.h"
#include "mii_bank.h"
void
mii_bank_init(
mii_bank_t *bank)
{
if (bank->mem)
return;
bank->mem = calloc(1, bank->size * 256);
bank->alloc = 1;
}
void
mii_bank_dispose(
mii_bank_t *bank)
@ -39,8 +49,8 @@ mii_bank_write(
const uint8_t *data,
uint16_t len)
{
if ((addr < bank->base) ||
((addr + len) > (uint32_t)(bank->base + (bank->size * 256)))) {
uint32_t end = bank->base + (bank->size << 8);
if (unlikely(addr < bank->base) || unlikely((addr + len) > end)) {
printf("%s %s INVALID write addr %04x len %d %04x:%04x\n",
__func__, bank->name, addr, (int)len,
bank->base, bank->base + (bank->size * 256));
@ -52,14 +62,10 @@ mii_bank_write(
addr, (uint8_t *)data, true))
return;
}
if (!bank->mem) {
bank->alloc = 1;
bank->mem = calloc(1, bank->size * 256);
}
addr -= bank->base;
for (uint16_t i = 0; i < len; i++, addr++) {
bank->mem[addr] = data[i];
}
do {
bank->mem[addr++] = *data++;
} while (unlikely(--len));
}
void
@ -69,27 +75,25 @@ mii_bank_read(
uint8_t *data,
uint16_t len)
{
if (addr < bank->base ||
(addr + len) > (uint32_t)(bank->base + (bank->size * 256))) {
#if 0 // rather expensive test when profiling!
uint32_t end = bank->base + (bank->size << 8);
if (unlikely(addr < bank->base) || unlikely((addr + len) > end)) {
printf("%s %s INVALID read addr %04x len %d %04x-%04x\n",
__func__, bank->name, addr, (int)len,
bank->base, bank->base + (bank->size * 256));
return;
}
#endif
uint8_t page_index = (addr - bank->base) >> 8;
if (bank->access && bank->access[page_index].cb) {
if (bank->access[page_index].cb(bank, bank->access[page_index].param,
addr, data, false))
return;
}
if (!bank->mem) {
bank->alloc = 1;
bank->mem = calloc(1, bank->size * 256);
}
addr -= bank->base;
for (uint16_t i = 0; i < len; i++, addr++) {
data[i] = bank->mem[addr];
}
do {
*data++ = bank->mem[addr++];
} while (unlikely(--len));
}
@ -113,8 +117,8 @@ mii_bank_install_access_cb(
if (!bank->access) {
bank->access = calloc(1, bank->size * sizeof(bank->access[0]));
}
printf("%s %s install access cb page %02x:%02x\n",
__func__, bank->name, page, end);
// printf("%s %s install access cb page %02x:%02x\n",
// __func__, bank->name, page, end);
for (int i = page; i <= end; i++) {
bank->access[i].cb = cb;
bank->access[i].param = param;

View File

@ -45,6 +45,9 @@ typedef struct mii_bank_t {
uint8_t *mem;
} mii_bank_t;
void
mii_bank_init(
mii_bank_t *bank);
void
mii_bank_dispose(
mii_bank_t *bank);

View File

@ -55,21 +55,18 @@ _mii_mish_cmd(
mii_t * mii = param;
if (!argv[1]) {
show_state:
printf("mii: %s Target speed: %.3fMHz Current: %.3fMHz\n",
state[mii->state], mii->speed, mii->speed_current);
printf("mii: %s Target speed: %.3fMHz\n",
state[mii->state], mii->speed);
mii_dump_run_trace(mii);
mii_dump_trace_state(mii);
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
bool text = !!mii_bank_peek(main, SWTEXT);
bool page2 = !!mii_bank_peek(main, SWPAGE2);
bool col80 = !!mii_bank_peek(main, SW80COL);
bool mixed = !!mii_bank_peek(main, SWMIXED);
bool hires = !!mii_bank_peek(main, SWHIRES);
bool dhires = !!mii_bank_peek(main, SWRDDHIRES);
printf("text:%d page2:%d col80:%d mixed:%d hires:%d dhires:%d\n",
text, page2, col80, mixed, hires, dhires);
for (int i = 0; mii_sw_names[i]; i++) {
char buf[32];
sprintf(buf, "%s:%d", mii_sw_names[i],
!!(mii->sw_state & (1 << i)));
printf("%-13.13s%s", buf, !(i % 6) ? "\n" : " ");
}
printf("\n");
return;
}
if (!strcmp(argv[1], "reset")) {
@ -77,6 +74,10 @@ show_state:
return;
}
if (!strcmp(argv[1], "mem")) {
printf("mii: memory map: ");
for (int i = 0; i < MII_BANK_COUNT; i++)
printf("%0d:%s ", i, mii->bank[i].name);
printf("\n");
for (int i = 0; i < 16; i++) {
printf("%02x: ", i * 16);
for (int j = 0; j < 16; j++)
@ -104,7 +105,7 @@ show_state:
}
uint16_t addr = strtol(argv[2], NULL, 16);
uint8_t val = strtol(argv[3], NULL, 16);
mii_mem_access(mii, addr, &val, false, true);
mii_mem_access(mii, addr, &val, true, true);
return;
}
if (!strcmp(argv[1], "peek")) {
@ -114,7 +115,7 @@ show_state:
}
uint16_t addr = strtol(argv[2], NULL, 16);
uint8_t val;
mii_mem_access(mii, addr, &val, false, false);
mii_mem_access(mii, addr, &val, false, true);
printf("%04x: %02x\n", addr, val);
return;
}
@ -141,6 +142,17 @@ show_state:
printf("mii: terminating\n");
return;
}
if (!strcmp(argv[1], "timers")) {
uint64_t timer = mii->timer.map;
printf("mii: %d cycle timers\n", __builtin_popcountll(timer));
while (timer) {
int i = ffsll(timer) - 1;
timer &= ~(1ull << i);
printf("%2d: %8ld %s\n", i, mii->timer.timers[i].when,
mii->timer.timers[i].name);
}
return;
}
printf("mii: unknown command %s\n", argv[1]);
}
@ -277,6 +289,10 @@ _mii_mish_step(
{
if (argv[0][0] == 's') {
mii_t * mii = param;
if (mii->state != MII_STOPPED) {
printf("mii: can't step/next, not stopped\n");
return;
}
if (argv[1]) {
int n = strtol(argv[1], NULL, 10);
mii->trace.step_inst = n;
@ -287,13 +303,17 @@ _mii_mish_step(
}
if (argv[0][0] == 'n') {
mii_t * mii = param;
if (mii->state != MII_STOPPED) {
printf("mii: can't step/next, not stopped\n");
return;
}
// read current opcode, find how how many bytes it take,
// then put a temporary breakpoint to the next PC.
// all of that if this is not a relative branch of course, in
// which case we use a normal 'step' behaviour
uint8_t op;
mii_mem_access(mii, mii->cpu.PC, &op, false, false);
if (op == 0x20) {
if (op == 0x20) { // JSR here?
// set a temp breakpoint on reading 3 bytes from PC
for (int i = 0; i < (int)sizeof(mii->debug.bp_map) * 8; i++) {
if ((mii->debug.bp_map & (1 << i)))
@ -337,7 +357,9 @@ _mii_mish_audio(
{
mii_t * mii = param;
if (argc < 2) {
printf("audio: missing argument\n");
printf("audio volume: %.3f multiplier:%.3f muted:%d\n",
mii->speaker.volume, mii->speaker.vol_multiplier,
mii->speaker.muted);
return;
}
if (!strcmp(argv[1], "record")) {

View File

@ -23,6 +23,7 @@ _mii_mish_dd(
mii_t * mii = param;
if (!argv[1] || !strcmp(argv[1], "list")) {
mii_dd_t *d = mii->dd.drive;
printf("dd %p %p drives\n", &mii->dd, mii->dd.drive);
printf(" ID %-16s %-20s\n", "Card", "Name");
while (d) {
printf("%d:%d %-16s %-20s : %s\n",
@ -42,4 +43,4 @@ MISH_CMD_HELP(dd,
"mii: disk commands",
" <default>|list: list all disk drives"
);
MII_MISH(dd, _mii_mish_dd);
MII_MISH(dd, _mii_mish_dd);

View File

@ -12,8 +12,8 @@
typedef struct mii_slot_drv_t mii_slot_drv_t;
typedef struct mii_slot_t {
uint8_t id;
void * drv_priv; // for driver use
uint8_t aux_rom_selected: 1, id;
void * drv_priv; // for driver use
const mii_slot_drv_t * drv;
} mii_slot_t;
@ -27,11 +27,24 @@ typedef struct mii_slot_drv_t {
uint32_t enable_flag; // if there is a MII_INIT_xxx flag
const char * name;
const char * desc;
int (*probe)(mii_t * mii, uint32_t flags);
int (*init)(mii_t * mii, struct mii_slot_t *slot);
void (*dispose)(mii_t * mii, struct mii_slot_t *slot); /* optional */
void (*reset)(mii_t * mii, struct mii_slot_t *slot); /* optional */
void (*run)(mii_t * mii, struct mii_slot_t *slot); /* optional */
int (*probe)(
mii_t * mii,
uint32_t flags);
int (*init)(
mii_t * mii,
struct mii_slot_t *slot);
/* optional */
void (*dispose)(
mii_t * mii,
struct mii_slot_t *slot);
/* optional */
void (*reset)(
mii_t * mii,
struct mii_slot_t *slot);
void (*run)(
mii_t * mii,
struct mii_slot_t *slot);
// access to the slot's soft switches.
uint8_t (*access)(
mii_t * mii,
struct mii_slot_t *slot,
@ -64,6 +77,8 @@ mii_slot_drv_find(
enum {
MII_SLOT_DRIVE_COUNT = 0x01,
MII_SLOT_DRIVE_LOAD = 0x20, // + drive index 0...n
MII_SLOT_SSC_SET_TTY = 0x10, // param is a pathname, or NULL for a pty
};
// send a command to a slot/driver. Return >=0 if ok, -1 if error

View File

@ -81,6 +81,11 @@ _alsa_init(
}
#endif
static uint64_t
_mii_speaker_timer_cb(
mii_t * mii,
void * param );
// Initialize the speaker with the frame size in samples
void
mii_speaker_init(
@ -90,32 +95,86 @@ mii_speaker_init(
s->mii = mii;
s->debug_fd = -1;
s->fsize = MII_SPEAKER_FRAME_SIZE;
// disabled at start...
s->timer_id = mii_timer_register(mii,
_mii_speaker_timer_cb, s, 0, __func__);
#ifdef HAS_ALSA
if (!s->off)
_alsa_init(s); // this can/will change fsize
#endif
s->vol_multiplier = 0.2;
mii_speaker_volume(s, 1);
s->sample = 0x8000;
s->findex = 0;
for (int i = 0; i < MII_SPEAKER_FRAME_COUNT; i++)
s->frame[i].audio = calloc(sizeof(s->frame[i].audio[0]), s->fsize);
s->frame[0].start = mii->cycles;
// s->frame[0].start = mii->cycles;
}
void
mii_speaker_dispose(
mii_speaker_t *speaker)
mii_speaker_t *s)
{
s->fsize = 0;
mii_timer_set(s->mii, s->timer_id, 0);
#ifdef HAS_ALSA
if (speaker->alsa_pcm)
snd_pcm_close(speaker->alsa_pcm);
if (s->alsa_pcm)
snd_pcm_close(s->alsa_pcm);
#endif
for (int i = 0; i < MII_SPEAKER_FRAME_COUNT; i++) {
free(speaker->frame[i].audio);
speaker->frame[i].audio = NULL;
free(s->frame[i].audio);
s->frame[i].audio = NULL;
}
}
// Check to see if there's a new frame to send, send it
// this timer is always running; it keeps checking for non-empty frames
static uint64_t
_mii_speaker_timer_cb(
mii_t * mii,
void * param )
{
mii_speaker_t *s = (mii_speaker_t *)param;
if (s->muted || s->off)
goto done;
mii_audio_frame_t *f = &s->frame[s->fplay];
// if the frame is empty, we mark the fact we are in underrun,
// so we can restart the audio later on.
if (!f->fill) {
if (s->under < 10)
s->under++;
goto done;
}
s->under = 0;
// Here we got a frame to play, so we play it, and move on to the next
// There's also the case were we stopped playing and the last frame
// wasn't complete, in which case we pad it, and flush it as well
// printf("%s: fplay %d findex %d fsize %d fill %d\n",
// __func__, s->fplay, s->findex, s->fsize, f->fill);
uint16_t sample = f->audio[f->fill - 1] ^ 0xffff;
while (f->fill < s->fsize)
f->audio[f->fill++] = sample;
s->fplay = (s->fplay + 1) % MII_SPEAKER_FRAME_COUNT;
s->frame[s->fplay].fill = 0;
if (!s->muted) {
if (s->debug_fd != -1)
write(s->debug_fd, f->audio,
f->fill * sizeof(s->frame[0].audio[0]));
#ifdef HAS_ALSA
if (s->alsa_pcm) {
int pcm;
if ((pcm = snd_pcm_writei(s->alsa_pcm,
f->audio, f->fill)) == -EPIPE) {
printf("%s Underrun.\n", __func__);
snd_pcm_recover(s->alsa_pcm, pcm, 1);
}
}
#endif
}
done:
return s->fsize * s->clk_per_sample;
}
// Called when $c030 is touched, place a sample at the 'appropriate' time
void
mii_speaker_click(
@ -126,18 +185,20 @@ mii_speaker_click(
s->cpu_speed = s->mii->speed;
s->clk_per_sample = ((1000000.0 /* / s->mii->speed */) /
(float)MII_SPEAKER_FREQ) + 0.5f;
printf("%s: %d cycles per sample\n", __func__, s->clk_per_sample);
printf("%s: %.2f cycles per sample\n", __func__, s->clk_per_sample);
mii_timer_set(s->mii, s->timer_id, s->fsize * s->clk_per_sample);
}
int64_t remains = mii_timer_get(s->mii, s->timer_id);
mii_audio_frame_t *f = &s->frame[s->findex];
// if we had stopped playing for 2 frames, restart
if (f->start == 0 ||
(s->mii->cycles - f->start) > (2 * s->fsize * s->clk_per_sample)) {
if (s->under > 1) {
s->under = 0;
// printf("Restarting playback\n");
#ifdef HAS_ALSA
if (s->alsa_pcm)
snd_pcm_prepare(s->alsa_pcm);
#endif
f->start = s->mii->cycles - (s->clk_per_sample * 8);
f->fill = 0;
// add a small attack to the start of the frame to soften the beeps
// we are going to flip the sample, so we need to preemptively
@ -146,20 +207,24 @@ mii_speaker_click(
for (int i = 8; i >= 1; i--)
f->audio[f->fill++] = (attack / i) * s->vol_multiplier;
s->fplay = s->findex; // restart here
mii_timer_set(s->mii, s->timer_id, (s->fsize - 16) * s->clk_per_sample);
remains = mii_timer_get(s->mii, s->timer_id);
}
long sample_index = (s->mii->cycles - f->start) / s->clk_per_sample;
// calculate the sample index we are going to fill -- this is relative
// to the frame we are waiting to play
long sample_index = // (s->fsize / 2) +
(((s->fsize * s->clk_per_sample) - remains) /
s->clk_per_sample);
// fill from last sample to here with the current sample
for (; f->fill < sample_index && f->fill < s->fsize; f->fill++)
f->audio[f->fill] = s->sample * s->vol_multiplier;
// printf("%s: findex %d fsize %d fill %d sample_index %ld\n",
// __func__, s->findex, s->fsize, f->fill, sample_index);
// if we've gone past the end of the frame, switch to the next one
if (sample_index >= s->fsize) {
if (sample_index >= s->fsize || sample_index < f->fill) {
sample_index = sample_index % s->fsize;
mii_cycles_t newstart = s->mii->cycles - (sample_index * s->clk_per_sample);
s->findex = (s->findex + 1) % MII_SPEAKER_FRAME_COUNT;
f = &s->frame[s->findex];
f->start = newstart;
f->fill = 0;
// fill from start of this frame to newly calculated sample_index
for (; f->fill < sample_index && f->fill < s->fsize; f->fill++)
@ -168,50 +233,22 @@ mii_speaker_click(
}
s->sample ^= 0xffff;
// if we are touching a new sample, make sure the next one is clear too.
#if 1
int32_t mix = s->sample * s->vol_multiplier;
#else
/* Mixing code; in case theres multiple clicks within a sample period.
* This is not used, because it's not really needed, as 2 clicks
* would cancel each others anyway. */
if (!f->audio[sample_index] && sample_index < s->fsize)
f->audio[sample_index + 1] = 0;
int32_t mix = f->audio[sample_index] + (s->sample * s->vol_multiplier);
if (mix > 0x7fff) mix = 0x7fff;
else if (mix < -0x8000) mix = -0x8000;
#endif
f->audio[sample_index] = mix;
}
// Check to see if there's a new frame to send, send it
void
mii_speaker_run(
mii_speaker_t *s)
{
mii_audio_frame_t *f = &s->frame[s->fplay];
// here we check if the frame we want to play is filled, and if it's
// been long enough since we started filling it to be sure we have
// enough samples to play.
// There's also the case were we stopped playing and the last frame
// wasn't complete, in which case we flush it as well
if (f->fill && ((s->mii->cycles - f->start) >
(s->fsize * s->clk_per_sample * 2))) {
s->fplaying = s->fplay;
s->fplay = (s->fplay + 1) % MII_SPEAKER_FRAME_COUNT;
f = &s->frame[s->fplaying];
if (!s->muted) {
if (s->debug_fd != -1)
write(s->debug_fd, f->audio,
f->fill * sizeof(s->frame[0].audio[0]));
#ifdef HAS_ALSA
if (s->alsa_pcm) {
int pcm;
if ((pcm = snd_pcm_writei(s->alsa_pcm,
f->audio, f->fill)) == -EPIPE) {
printf("%s Underrun.\n", __func__);
snd_pcm_recover(s->alsa_pcm, pcm, 1);
}
}
#endif
}
f->fill = 0;
}
}
// this is here so we dont' have to drag in libm math library.
double fastPow(double a, double b) {
@ -234,4 +271,4 @@ mii_speaker_volume(
s->volume = volume;
// printf("audio: speaker volume set to %.3f (%.4f)\n", volume, mul);
}
}

View File

@ -17,13 +17,14 @@ struct snd_pcm_t;
typedef int16_t mii_audio_sample_t;
typedef struct mii_audio_frame_t {
mii_cycles_t start;
// mii_cycles_t start;
uint16_t fill;
mii_audio_sample_t * audio;
} mii_audio_frame_t;
typedef struct mii_speaker_t {
struct mii_t * mii;
uint8_t timer_id; // timer id for the audio thread
int debug_fd; // if > 0, dump audio to this fd
void * alsa_pcm; // alsa pcm handle
bool muted; // if true, don't play anything
@ -31,11 +32,11 @@ typedef struct mii_speaker_t {
float volume; // volume, 0.0 to 10.0
float vol_multiplier; // sample multiplier, 0.0 to 1.0
float cpu_speed; // CPU speed in MHz, to calculate clk_per_sample
uint8_t under; // number of frames we've skipped
uint8_t fplay; // frame we want to play
uint8_t fplaying; // index of the current playing frame
uint16_t fsize; // size in samples of a frame
uint8_t findex; // frame we are currently filling
uint16_t clk_per_sample; // number of cycles per sample (at current CPU speed)
float clk_per_sample; // number of cycles per sample (at current CPU speed)
mii_audio_sample_t sample; // current value for the speaker output
mii_audio_frame_t frame[MII_SPEAKER_FRAME_COUNT];
} mii_speaker_t;
@ -53,9 +54,9 @@ void
mii_speaker_click(
mii_speaker_t *speaker);
// Check to see if there's a new frame to send, send it
void
mii_speaker_run(
mii_speaker_t *speaker);
//void
//mii_speaker_run(
// mii_speaker_t *speaker);
// volume from 0 to 10, sets the audio sample multiplier.
void
mii_speaker_volume(

Some files were not shown because too many files have changed in this diff Show More