Initial commit
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
build/
|
||||||
|
Kekatsu.elf
|
||||||
|
Kekatsu.nds
|
||||||
|
release/
|
||||||
|
Kekatsu-DS.zip
|
||||||
|
version.txt
|
||||||
|
.vscode/
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Cavv
|
||||||
|
|
||||||
|
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.
|
93
Makefile
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
.SUFFIXES:
|
||||||
|
|
||||||
|
ifeq ($(strip $(DEVKITARM)),)
|
||||||
|
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
|
||||||
|
endif
|
||||||
|
|
||||||
|
include $(DEVKITARM)/ds_rules
|
||||||
|
|
||||||
|
TARGET := Kekatsu
|
||||||
|
BUILD := build
|
||||||
|
RELEASE := release
|
||||||
|
SOURCES := source source/utils source/gui
|
||||||
|
DATA := data
|
||||||
|
INCLUDES := include
|
||||||
|
GRAPHICS := source/gfx source/gui/gfx
|
||||||
|
LANGUAGES := source/lang
|
||||||
|
VERSION := 1.0.0
|
||||||
|
|
||||||
|
ARCH := -march=armv5te -mtune=arm946e-s -mthumb
|
||||||
|
CFLAGS := -g -Wall -O2 -ffunction-sections -fdata-sections $(ARCH) $(INCLUDE) -DARM9
|
||||||
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||||
|
ASFLAGS := -g $(ARCH)
|
||||||
|
LDFLAGS := -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
|
LIBS := -lminizip -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lpng -lm -lz -ldswifi9 -lfat -lnds9
|
||||||
|
LIBDIRS := $(PORTLIBS) $(LIBNDS)
|
||||||
|
|
||||||
|
ifneq ($(BUILDDIR), $(CURDIR))
|
||||||
|
|
||||||
|
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||||
|
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(LANGUAGES),$(CURDIR)/$(dir))
|
||||||
|
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||||
|
|
||||||
|
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||||
|
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||||
|
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||||
|
PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png)))
|
||||||
|
LANGFILES := $(foreach dir,$(LANGUAGES),$(notdir $(wildcard $(dir)/*.lang*)))
|
||||||
|
|
||||||
|
ifeq ($(strip $(CPPFILES)),)
|
||||||
|
export LD := $(CC)
|
||||||
|
else
|
||||||
|
export LD := $(CXX)
|
||||||
|
endif
|
||||||
|
|
||||||
|
export OFILES_LANG := $(addsuffix .o,$(LANGFILES))
|
||||||
|
export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||||
|
export OFILES := $(PNGFILES:.png=.o) $(OFILES_LANG) $(OFILES_SOURCES)
|
||||||
|
export HFILES := $(PNGFILES:.png=.h) $(addsuffix .h,$(subst .,_,$(LANGFILES)))
|
||||||
|
export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||||
|
-I$(CURDIR)/$(BUILD)
|
||||||
|
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||||
|
|
||||||
|
export GAME_TITLE := Kekatsu
|
||||||
|
export GAME_SUBTITLE1 := DS(i) content downloader
|
||||||
|
export GAME_SUBTITLE2 := Cavv
|
||||||
|
export GAME_ICON := $(CURDIR)/icon.bmp
|
||||||
|
|
||||||
|
.PHONY: $(BUILD) clean
|
||||||
|
|
||||||
|
$(BUILD):
|
||||||
|
@[ -d $@ ] || mkdir -p $@
|
||||||
|
@make BUILDDIR=`cd $(BUILD) && pwd` --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||||
|
|
||||||
|
$(RELEASE): $(BUILD)
|
||||||
|
@[ -d $@ ] || mkdir -p $@
|
||||||
|
@mv $(TARGET).nds $(RELEASE)/$(TARGET).nds
|
||||||
|
@echo $(VERSION) > $(RELEASE)/version.txt
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo clean ...
|
||||||
|
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(RELEASE)
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
DEPENDS := $(OFILES:.o=.d)
|
||||||
|
|
||||||
|
$(OUTPUT).nds: $(OUTPUT).elf
|
||||||
|
$(OUTPUT).elf: $(OFILES)
|
||||||
|
|
||||||
|
%.s %.h: %.png %.grit
|
||||||
|
grit $< -fts -o$*
|
||||||
|
|
||||||
|
%.lang.o %_lang.h : %.lang
|
||||||
|
@echo $(notdir $<)
|
||||||
|
@$(bin2o)
|
||||||
|
|
||||||
|
-include $(DEPENDS)
|
||||||
|
|
||||||
|
endif
|
93
README.md
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Kekatsu DS
|
||||||
|
Easy-to-use content downloader for Nintendo DS(i) consoles
|
||||||
|
|
||||||
|
  
|
||||||
|
|
||||||
|
The main scope of this project is to have a standalone and easy way to download apps and games on the fly, given a database by the user.
|
||||||
|
|
||||||
|
Concept inspired by [pkgi-psp](https://github.com/bucanero/pkgi-psp) and [Universal-Updater](https://github.com/Universal-Team/Universal-Updater).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Multi-platform content download
|
||||||
|
- Database loading via URL or file
|
||||||
|
- Support for multiple databases
|
||||||
|
- ZIP file extraction
|
||||||
|
- Automatic updates check
|
||||||
|
- Customizable color scheme
|
||||||
|
- Localization support
|
||||||
|
|
||||||
|
## Databases setup
|
||||||
|
Kekatsu expects a `databases.txt` file to load as the list of available databases. This file has to be located in the `Kekatsu` directory on the root of the SD card.
|
||||||
|
|
||||||
|
Each line of the `databases.txt` file has to contain a name and a value separated by a tab character.
|
||||||
|
|
||||||
|
The name should be the display name of the database. The value must be either:
|
||||||
|
- an HTTP(S) URL which returns a database in text response
|
||||||
|
- a path to a database file on the SD card itself
|
||||||
|
|
||||||
|
### Example databases.txt
|
||||||
|
```
|
||||||
|
test-db https://example.com/database.txt
|
||||||
|
test-db2 /databases/db2.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Community databases
|
||||||
|
Collection of useful known databases. Contact me to if you want to contribute to this list.
|
||||||
|
|
||||||
|
| Name | Description | URL |
|
||||||
|
|---|---|---|
|
||||||
|
| [UDB-Kekatsu-DS](https://github.com/cavv-dev/UDB-Kekatsu-DS) | Updated selection of DS and DSi apps from Universal-DB | `https://gist.githubusercontent.com/cavv-dev/3c0cbc1b63ac8ca0c1d9f549403afbf1/raw/` |
|
||||||
|
|
||||||
|
## Database creation instructions
|
||||||
|
A database file expected by Kekatsu is a text file that follows this precise structure:
|
||||||
|
|
||||||
|
- **Line 1**: Database version - The database version to be used by the parser. Follow the next instructions that match the chosen database version.
|
||||||
|
|
||||||
|
<details><summary>Version 1</summary>
|
||||||
|
|
||||||
|
- **Line 2**: Delimiter character - The character to be used to separate fields in the next lines
|
||||||
|
- **Line 3 and above**: Fields separated by the delimiter character. They must follow this order:
|
||||||
|
- **Title** - Display title of the content
|
||||||
|
- **Platform** - Target platform of the content. *Should* be in lowercase and in its abbreviated form as it will be used as the name of the platform directory. E.g. `nds` instead of `Nintendo DS`.
|
||||||
|
- **Region** - Target region of the content. Could be `NTSC-U`, `PAL` and similar for contents which target a specific region or `ANY` for contents made for any region.
|
||||||
|
- **Version** - Release version of the content
|
||||||
|
- **Author** - Author or publisher of the content
|
||||||
|
- **Download URL** - The HTTP(S) URL to download the content. Must be a direct link to the file of the content. This file can be an executable or an archive in ZIP format.
|
||||||
|
- **File name** - The name under which the downloaded file will be saved
|
||||||
|
- **Size** - The size in bytes of the downloaded file
|
||||||
|
- **Box art URL** - The HTTP(S) URL of the displayed box art for the content. A box art is expected to be in PNG format.
|
||||||
|
- **Extract items** - The items to be extracted from the downloaded archive in couples of fields separated by the delimiter character. Each couple is composed of:
|
||||||
|
- **In-path** - The path of the file or directory in the archive to be extracted. Directories should have `/` as the last character.
|
||||||
|
- **Out-path** - The destination path of the extracted file or directory
|
||||||
|
|
||||||
|
If no extract items are specified, all the files and directories will be extracted following the structure in the archive.
|
||||||
|
|
||||||
|
They are not going to be checked if the downloaded file is not an archive.
|
||||||
|
|
||||||
|
### Example database file
|
||||||
|
```
|
||||||
|
1
|
||||||
|
,
|
||||||
|
test-app,nds,ANY,1.0,Author1,https://example.com/test-app-v1.0.nds,test-app.nds,1048576,https://example.com/test-app-boxart.png
|
||||||
|
test-app2,gba,NTSC-U,1.1,Author2,https://example.com/test-app2.zip,test-app2.zip,2097152,https://example.com/test-app2-boxart.png,release/gba/test-app2-v1.1.gba,test-app2.gba
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Building
|
||||||
|
### Requirements
|
||||||
|
- devkitARM toolchain (`nds-dev` package group) by [devkitPro](https://github.com/devkitPro)
|
||||||
|
- [curl](https://github.com/ds-sloth/pacman-packages/tree/nds-curl-mbedtls/nds/curl) and [mbedtls](https://github.com/ds-sloth/pacman-packages/tree/nds-curl-mbedtls/nds/mbedtls) by [ds-sloth](https://github.com/ds-sloth)
|
||||||
|
- nds-zlib, nds-libpng from devkitPro pacman repository
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/cavv-dev/Kekatsu-DS.git
|
||||||
|
cd Kekatsu-DS
|
||||||
|
make release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
- [Cavv](https://github.com/cavv-dev): Main developer
|
||||||
|
- [devkitPro](https://github.com/devkitPro): devkitARM and relative libraries
|
||||||
|
- [ds-sloth](https://github.com/ds-sloth): curl and mbedtls libraries
|
||||||
|
- [Flaticon](https://www.flaticon.com/): Icons
|
BIN
resources/screenshots/Kekatsu-DS_1.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
resources/screenshots/Kekatsu-DS_2.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
resources/screenshots/Kekatsu-DS_3.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
158
source/archives.c
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
#include "archives.h"
|
||||||
|
|
||||||
|
#include "utils/filesystem.h"
|
||||||
|
#include "utils/strings.h"
|
||||||
|
#include <calico/types.h>
|
||||||
|
#include <minizip/unzip.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
bool fileIsZip(const char* filePath)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(filePath, "rb");
|
||||||
|
if (!fp)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
unsigned char buffer[4];
|
||||||
|
size_t bytesRead = fread(buffer, 1, 4, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return (bytesRead == 4 && buffer[0] == 0x50 && buffer[1] == 0x4b && buffer[2] == 0x03 && buffer[3] == 0x04);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtractStatus extractFile(unzFile zipFile, const char* outFilePath)
|
||||||
|
{
|
||||||
|
char zipFilePath[PATH_MAX];
|
||||||
|
if (unzGetCurrentFileInfo(zipFile, NULL, zipFilePath, sizeof(zipFilePath), NULL, 0, NULL, 0) != UNZ_OK)
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
|
||||||
|
createDirStructure(outFilePath);
|
||||||
|
|
||||||
|
if (zipFilePath[strlen(zipFilePath) - 1] == '/') {
|
||||||
|
if (createDir(outFilePath))
|
||||||
|
return EXTRACT_SUCCESS;
|
||||||
|
else
|
||||||
|
return EXTRACT_ERR_FILE_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* fp = fopen(outFilePath, "wb");
|
||||||
|
if (!fp)
|
||||||
|
return EXTRACT_ERR_FILE_WRITE;
|
||||||
|
|
||||||
|
char buffer[128];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = unzReadCurrentFile(zipFile, buffer, sizeof(buffer))) > 0) {
|
||||||
|
if (fwrite(buffer, bytesRead, 1, fp) != 1) {
|
||||||
|
fclose(fp);
|
||||||
|
return EXTRACT_ERR_FILE_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
return (bytesRead < 0) ? EXTRACT_ERR_FILE_READ : EXTRACT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtractStatus extractZip(const char* filePath, const char* inPath, const char* outPath)
|
||||||
|
{
|
||||||
|
unzFile zipFile = unzOpen(filePath);
|
||||||
|
if (!zipFile)
|
||||||
|
return EXTRACT_ERR_FILE_OPEN;
|
||||||
|
|
||||||
|
if (inPath[strlen(inPath) - 1] == '/') {
|
||||||
|
if (unzGoToFirstFile(zipFile) != UNZ_OK) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
char zipFilePath[PATH_MAX];
|
||||||
|
if (unzGetCurrentFileInfo(zipFile, NULL, zipFilePath, sizeof(zipFilePath), NULL, 0, NULL, 0) != UNZ_OK) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(zipFilePath, inPath, strlen(inPath)) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char* outDirName = zipFilePath + strlen(inPath);
|
||||||
|
|
||||||
|
if (strlen(outPath) + strlen(outDirName) > PATH_MAX - 1)
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
|
||||||
|
char newOutPath[PATH_MAX];
|
||||||
|
joinPath(newOutPath, outPath, outDirName);
|
||||||
|
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtractStatus result = extractFile(zipFile, newOutPath);
|
||||||
|
unzCloseCurrentFile(zipFile);
|
||||||
|
if (result != EXTRACT_SUCCESS) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} while (unzGoToNextFile(zipFile) == UNZ_OK);
|
||||||
|
} else {
|
||||||
|
if (unzLocateFile(zipFile, inPath, 0) != UNZ_OK) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_ERR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtractStatus result = extractFile(zipFile, outPath);
|
||||||
|
unzCloseCurrentFile(zipFile);
|
||||||
|
if (result != EXTRACT_SUCCESS) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtractStatus extractAllZip(const char* filePath, const char* outDir)
|
||||||
|
{
|
||||||
|
unzFile zipFile = unzOpen(filePath);
|
||||||
|
if (!zipFile)
|
||||||
|
return EXTRACT_ERR_FILE_OPEN;
|
||||||
|
|
||||||
|
if (unzGoToFirstFile(zipFile) != UNZ_OK) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
char zipFilePath[PATH_MAX];
|
||||||
|
if (unzGetCurrentFileInfo(zipFile, NULL, zipFilePath, sizeof(zipFilePath), NULL, 0, NULL, 0) != UNZ_OK) {
|
||||||
|
unzCloseCurrentFile(zipFile);
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(outDir) + strlen(zipFilePath) > PATH_MAX - 1)
|
||||||
|
return EXTRACT_ERR_FILE_READ;
|
||||||
|
|
||||||
|
char outFilePath[PATH_MAX];
|
||||||
|
joinPath(outFilePath, outDir, zipFilePath);
|
||||||
|
|
||||||
|
ExtractStatus result = extractFile(zipFile, outFilePath);
|
||||||
|
unzCloseCurrentFile(zipFile);
|
||||||
|
if (result != EXTRACT_SUCCESS) {
|
||||||
|
unzClose(zipFile);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} while (unzGoToNextFile(zipFile) == UNZ_OK);
|
||||||
|
|
||||||
|
unzClose(zipFile);
|
||||||
|
return EXTRACT_SUCCESS;
|
||||||
|
}
|
14
source/archives.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EXTRACT_SUCCESS,
|
||||||
|
EXTRACT_ERR_FILE_OPEN,
|
||||||
|
EXTRACT_ERR_FILE_NOT_FOUND,
|
||||||
|
EXTRACT_ERR_FILE_READ,
|
||||||
|
EXTRACT_ERR_FILE_WRITE
|
||||||
|
} ExtractStatus;
|
||||||
|
|
||||||
|
bool fileIsZip(const char* filePath);
|
||||||
|
ExtractStatus extractZip(const char* filePath, const char* inFilePath, const char* outFilePath);
|
||||||
|
ExtractStatus extractAllZip(const char* filePath, const char* outDir);
|
54
source/colors.c
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#include "colors.h"
|
||||||
|
|
||||||
|
u16 colorSchemes[COLOR_SCHEMES_COUNT][COLORS_COUNT];
|
||||||
|
|
||||||
|
void initColorSchemes(void)
|
||||||
|
{
|
||||||
|
colorSchemes[COLOR_SCHEME_1][COLOR_BG] = RGB15_8BIT(9, 9, 15);
|
||||||
|
colorSchemes[COLOR_SCHEME_1][COLOR_BG_2] = RGB15_8BIT(20, 20, 33);
|
||||||
|
colorSchemes[COLOR_SCHEME_1][COLOR_TEXT] = RGB15_8BIT(168, 168, 237);
|
||||||
|
colorSchemes[COLOR_SCHEME_1][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
|
||||||
|
colorSchemes[COLOR_SCHEME_1][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
|
||||||
|
colorSchemes[COLOR_SCHEME_1][COLOR_PRIMARY] = RGB15_8BIT(110, 110, 186);
|
||||||
|
colorSchemes[COLOR_SCHEME_1][COLOR_SECONDARY] = RGB15_8BIT(55, 55, 105);
|
||||||
|
|
||||||
|
colorSchemes[COLOR_SCHEME_2][COLOR_BG] = RGB15_8BIT(9, 12, 15);
|
||||||
|
colorSchemes[COLOR_SCHEME_2][COLOR_BG_2] = RGB15_8BIT(20, 26, 33);
|
||||||
|
colorSchemes[COLOR_SCHEME_2][COLOR_TEXT] = RGB15_8BIT(168, 197, 237);
|
||||||
|
colorSchemes[COLOR_SCHEME_2][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
|
||||||
|
colorSchemes[COLOR_SCHEME_2][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
|
||||||
|
colorSchemes[COLOR_SCHEME_2][COLOR_PRIMARY] = RGB15_8BIT(110, 142, 186);
|
||||||
|
colorSchemes[COLOR_SCHEME_2][COLOR_SECONDARY] = RGB15_8BIT(54, 75, 105);
|
||||||
|
|
||||||
|
colorSchemes[COLOR_SCHEME_3][COLOR_BG] = RGB15_8BIT(15, 14, 9);
|
||||||
|
colorSchemes[COLOR_SCHEME_3][COLOR_BG_2] = RGB15_8BIT(33, 30, 20);
|
||||||
|
colorSchemes[COLOR_SCHEME_3][COLOR_TEXT] = RGB15_8BIT(237, 219, 168);
|
||||||
|
colorSchemes[COLOR_SCHEME_3][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
|
||||||
|
colorSchemes[COLOR_SCHEME_3][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
|
||||||
|
colorSchemes[COLOR_SCHEME_3][COLOR_PRIMARY] = RGB15_8BIT(186, 166, 110);
|
||||||
|
colorSchemes[COLOR_SCHEME_3][COLOR_SECONDARY] = RGB15_8BIT(105, 91, 54);
|
||||||
|
|
||||||
|
colorSchemes[COLOR_SCHEME_4][COLOR_BG] = RGB15_8BIT(15, 9, 9);
|
||||||
|
colorSchemes[COLOR_SCHEME_4][COLOR_BG_2] = RGB15_8BIT(33, 20, 20);
|
||||||
|
colorSchemes[COLOR_SCHEME_4][COLOR_TEXT] = RGB15_8BIT(237, 168, 170);
|
||||||
|
colorSchemes[COLOR_SCHEME_4][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
|
||||||
|
colorSchemes[COLOR_SCHEME_4][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
|
||||||
|
colorSchemes[COLOR_SCHEME_4][COLOR_PRIMARY] = RGB15_8BIT(186, 110, 111);
|
||||||
|
colorSchemes[COLOR_SCHEME_4][COLOR_SECONDARY] = RGB15_8BIT(105, 54, 55);
|
||||||
|
|
||||||
|
colorSchemes[COLOR_SCHEME_5][COLOR_BG] = RGB15_8BIT(9, 15, 12);
|
||||||
|
colorSchemes[COLOR_SCHEME_5][COLOR_BG_2] = RGB15_8BIT(20, 33, 25);
|
||||||
|
colorSchemes[COLOR_SCHEME_5][COLOR_TEXT] = RGB15_8BIT(168, 237, 196);
|
||||||
|
colorSchemes[COLOR_SCHEME_5][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
|
||||||
|
colorSchemes[COLOR_SCHEME_5][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
|
||||||
|
colorSchemes[COLOR_SCHEME_5][COLOR_PRIMARY] = RGB15_8BIT(110, 186, 140);
|
||||||
|
colorSchemes[COLOR_SCHEME_5][COLOR_SECONDARY] = RGB15_8BIT(54, 105, 74);
|
||||||
|
|
||||||
|
colorSchemes[COLOR_SCHEME_6][COLOR_BG] = RGB15_8BIT(15, 15, 15);
|
||||||
|
colorSchemes[COLOR_SCHEME_6][COLOR_BG_2] = RGB15_8BIT(33, 33, 33);
|
||||||
|
colorSchemes[COLOR_SCHEME_6][COLOR_TEXT] = RGB15_8BIT(237, 237, 237);
|
||||||
|
colorSchemes[COLOR_SCHEME_6][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
|
||||||
|
colorSchemes[COLOR_SCHEME_6][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
|
||||||
|
colorSchemes[COLOR_SCHEME_6][COLOR_PRIMARY] = RGB15_8BIT(186, 186, 186);
|
||||||
|
colorSchemes[COLOR_SCHEME_6][COLOR_SECONDARY] = RGB15_8BIT(105, 105, 105);
|
||||||
|
}
|
29
source/colors.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <calico/types.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
COLOR_SCHEME_1,
|
||||||
|
COLOR_SCHEME_2,
|
||||||
|
COLOR_SCHEME_3,
|
||||||
|
COLOR_SCHEME_4,
|
||||||
|
COLOR_SCHEME_5,
|
||||||
|
COLOR_SCHEME_6,
|
||||||
|
COLOR_SCHEMES_COUNT
|
||||||
|
} ColorSchemeEnum;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
COLOR_BG,
|
||||||
|
COLOR_BG_2,
|
||||||
|
COLOR_TEXT,
|
||||||
|
COLOR_TEXT_2,
|
||||||
|
COLOR_TEXT_3,
|
||||||
|
COLOR_PRIMARY,
|
||||||
|
COLOR_SECONDARY,
|
||||||
|
COLORS_COUNT
|
||||||
|
} ColorEnum;
|
||||||
|
|
||||||
|
#define RGB15_8BIT(r, g, b) (((r) >> 3) | (((g) >> 3) << 5) | (((b) >> 3) << 10))
|
||||||
|
|
||||||
|
extern u16 colorSchemes[COLOR_SCHEMES_COUNT][COLORS_COUNT];
|
||||||
|
|
||||||
|
void initColorSchemes(void);
|
9
source/config.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define APP_NAME "Kekatsu"
|
||||||
|
#define APP_VERSION "1.0.0"
|
||||||
|
#define APPDATA_DIR "/" APP_NAME
|
||||||
|
#define CACHE_DIR APPDATA_DIR "/cache"
|
||||||
|
|
||||||
|
#define UPDATE_URL_APP "https://github.com/cavv-dev/Kekatsu-DS/releases/latest/download/Kekatsu.nds"
|
||||||
|
#define UPDATE_URL_VERSION "https://github.com/cavv-dev/Kekatsu-DS/releases/latest/download/version.txt"
|
376
source/database.c
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
#include "database.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "networking.h"
|
||||||
|
#include "utils/filesystem.h"
|
||||||
|
#include "utils/strings.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define TEMP_DB_FILENAME "db.txt"
|
||||||
|
#define TEMP_DB_PATH CACHE_DIR "/" TEMP_DB_FILENAME
|
||||||
|
|
||||||
|
#define LAST_OPENED_DB_FILENAME "lastOpenedDb.txt"
|
||||||
|
#define LAST_OPENED_DB_PATH APPDATA_DIR "/" LAST_OPENED_DB_FILENAME
|
||||||
|
|
||||||
|
#define DATABASE_LIST_FILENAME "databases.txt"
|
||||||
|
#define DATABASE_LIST_PATH APPDATA_DIR "/" DATABASE_LIST_FILENAME
|
||||||
|
|
||||||
|
struct Database {
|
||||||
|
char* name;
|
||||||
|
char* value;
|
||||||
|
DatabaseType type;
|
||||||
|
char* path;
|
||||||
|
size_t size;
|
||||||
|
bool isInited;
|
||||||
|
FILE* fp;
|
||||||
|
char delimiter;
|
||||||
|
};
|
||||||
|
|
||||||
|
Database newDatabase(const char* name, const char* value)
|
||||||
|
{
|
||||||
|
Database d = malloc(sizeof(struct Database));
|
||||||
|
d->name = strdup(name);
|
||||||
|
d->value = strdup(value);
|
||||||
|
d->path = NULL;
|
||||||
|
d->size = 0;
|
||||||
|
d->isInited = false;
|
||||||
|
d->fp = NULL;
|
||||||
|
d->delimiter = '\0';
|
||||||
|
|
||||||
|
if ((strncmp(value, "http://", 7) == 0) || (strncmp(value, "https://", 8) == 0))
|
||||||
|
d->type = DATABASE_TYPE_HTTP;
|
||||||
|
else
|
||||||
|
d->type = DATABASE_TYPE_LOCAL;
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeDatabase(Database d)
|
||||||
|
{
|
||||||
|
free(d->value);
|
||||||
|
free(d->path);
|
||||||
|
|
||||||
|
if (d->fp)
|
||||||
|
closeDatabase(d);
|
||||||
|
|
||||||
|
free(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getDatabaseName(Database d)
|
||||||
|
{
|
||||||
|
return d->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getDatabaseValue(Database d)
|
||||||
|
{
|
||||||
|
return d->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getDatabasePath(Database d)
|
||||||
|
{
|
||||||
|
return d->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getDatabaseSize(Database d)
|
||||||
|
{
|
||||||
|
return d->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getDatabaseIsInited(Database d)
|
||||||
|
{
|
||||||
|
return d->isInited;
|
||||||
|
}
|
||||||
|
|
||||||
|
char** parseLine(char* line, char delimiter, size_t* fieldsCount)
|
||||||
|
{
|
||||||
|
if (line[0] == '\0') {
|
||||||
|
*fieldsCount = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count fields in line
|
||||||
|
*fieldsCount = 1;
|
||||||
|
for (char* ptr = line; *ptr != '\0'; ++ptr) {
|
||||||
|
if (*ptr == delimiter)
|
||||||
|
(*fieldsCount)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate space for fields array
|
||||||
|
char** fields = (char**)malloc(*fieldsCount * sizeof(char*));
|
||||||
|
if (fields == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// Populate fields array with pointers to strings in line
|
||||||
|
size_t fieldIndex = 0;
|
||||||
|
char* start = line;
|
||||||
|
for (char* ptr = line;; ++ptr) {
|
||||||
|
if (*ptr == delimiter || *ptr == '\0') {
|
||||||
|
fields[fieldIndex] = start;
|
||||||
|
fieldIndex++;
|
||||||
|
if (*ptr == '\0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*ptr = '\0'; // Replace delimiter with null terminator
|
||||||
|
start = ptr + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
Database getLastOpenedDatabase(void)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(LAST_OPENED_DB_PATH, "r");
|
||||||
|
if (!fp)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char line[2048];
|
||||||
|
char name[1024];
|
||||||
|
char value[1024];
|
||||||
|
|
||||||
|
if (!fgets(line, sizeof(line), fp) || sscanf(line, "%[^\t]\t%s", name, value) != 2) {
|
||||||
|
fclose(fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return newDatabase(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveLastOpenedDatabase(Database d)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(LAST_OPENED_DB_PATH, "w");
|
||||||
|
if (!fp)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fprintf(fp, "%s\t%s\n", d->name, d->value);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseInitStatus openDatabase(Database d)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(d->path, "r");
|
||||||
|
if (!fp)
|
||||||
|
return DATABASE_OPEN_ERR_FILE_OPEN;
|
||||||
|
|
||||||
|
size_t dbSize = 0;
|
||||||
|
|
||||||
|
char line[1024];
|
||||||
|
if (!fgets(line, sizeof(line), fp)) {
|
||||||
|
fclose(fp);
|
||||||
|
return DATABASE_OPEN_ERR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dbVersion = atoi(line);
|
||||||
|
if (dbVersion != 1) { // 1 is the only supported version for now
|
||||||
|
fclose(fp);
|
||||||
|
return DATABASE_OPEN_ERR_INVALID_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fgets(line, sizeof(line), fp)) {
|
||||||
|
fclose(fp);
|
||||||
|
return DATABASE_OPEN_ERR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
char delimiter = line[0];
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), fp)) {
|
||||||
|
size_t fieldsCount;
|
||||||
|
char** fields = parseLine(line, delimiter, &fieldsCount);
|
||||||
|
|
||||||
|
if (!fields || fieldsCount < 9) {
|
||||||
|
fclose(fp);
|
||||||
|
return DATABASE_OPEN_ERR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the fields count of items to extract
|
||||||
|
// Items are couple of fields so the count has to be an even number
|
||||||
|
size_t extractItemsFieldsCount = fieldsCount - 9;
|
||||||
|
if (fieldsCount > 9 && (extractItemsFieldsCount & 1) != 0) {
|
||||||
|
free(fields);
|
||||||
|
fclose(fp);
|
||||||
|
return DATABASE_OPEN_ERR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
d->size = dbSize;
|
||||||
|
d->fp = fp;
|
||||||
|
d->delimiter = delimiter;
|
||||||
|
|
||||||
|
saveLastOpenedDatabase(d);
|
||||||
|
|
||||||
|
return DATABASE_OPEN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool closeDatabase(Database d)
|
||||||
|
{
|
||||||
|
if (fclose(d->fp) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseInitStatus initDatabase(Database d)
|
||||||
|
{
|
||||||
|
bool usingCachedDb = false;
|
||||||
|
|
||||||
|
switch (d->type) {
|
||||||
|
case DATABASE_TYPE_HTTP:
|
||||||
|
char* dbFileName = strdup(d->value);
|
||||||
|
safeStr(dbFileName);
|
||||||
|
|
||||||
|
char dbFilePath[PATH_MAX];
|
||||||
|
snprintf(dbFilePath, sizeof(dbFilePath), "%s/%s", CACHE_DIR, dbFileName);
|
||||||
|
free(dbFileName);
|
||||||
|
|
||||||
|
d->path = strdup(dbFilePath);
|
||||||
|
|
||||||
|
if (downloadFile(TEMP_DB_PATH, d->value, NULL) == DOWNLOAD_SUCCESS) {
|
||||||
|
if (!renamePath(TEMP_DB_PATH, dbFilePath))
|
||||||
|
return DATABASE_INIT_ERR_DOWNLOAD;
|
||||||
|
} else {
|
||||||
|
if (fileExists(dbFilePath))
|
||||||
|
usingCachedDb = true;
|
||||||
|
else
|
||||||
|
return DATABASE_INIT_ERR_DOWNLOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case DATABASE_TYPE_LOCAL:
|
||||||
|
d->path = strdup(d->value);
|
||||||
|
|
||||||
|
if (!pathExists(d->value))
|
||||||
|
return DATABASE_INIT_ERR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
d->isInited = true;
|
||||||
|
|
||||||
|
return (usingCachedDb ? DATABASE_INIT_SUCCESS_CACHE : DATABASE_INIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void alignDatabase(Database d)
|
||||||
|
{
|
||||||
|
fseek(d->fp, 0, SEEK_SET);
|
||||||
|
|
||||||
|
// Advance 2 lines
|
||||||
|
char ch;
|
||||||
|
u8 lines = 2;
|
||||||
|
while (lines > 0 && (ch = fgetc(d->fp)) != EOF) {
|
||||||
|
if (ch == '\n') {
|
||||||
|
lines--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry* searchDatabase(Database d, const char* searchTitle, size_t pageSize, size_t page, size_t* resultsCount)
|
||||||
|
{
|
||||||
|
*resultsCount = 0;
|
||||||
|
size_t count = 0;
|
||||||
|
size_t capacity = 10;
|
||||||
|
size_t startIndex = (page - 1) * pageSize;
|
||||||
|
size_t endIndex = startIndex + pageSize;
|
||||||
|
size_t currIndex = 0;
|
||||||
|
char line[1024];
|
||||||
|
Entry* results = malloc(capacity * sizeof(Entry));
|
||||||
|
|
||||||
|
alignDatabase(d);
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), d->fp)) {
|
||||||
|
// Trim trailing newline
|
||||||
|
line[strcspn(line, "\r\n")] = '\0';
|
||||||
|
|
||||||
|
size_t fieldsCount;
|
||||||
|
char** fields = parseLine(line, d->delimiter, &fieldsCount);
|
||||||
|
|
||||||
|
if (searchTitle[0] != '\0') {
|
||||||
|
// Format titles in a comparable way
|
||||||
|
char tempEntryTitle[128];
|
||||||
|
char tempInputTitle[128];
|
||||||
|
|
||||||
|
strncpy(tempEntryTitle, fields[0], sizeof(tempEntryTitle) - 1);
|
||||||
|
removeAccentsStr(tempEntryTitle);
|
||||||
|
lowerStr(tempEntryTitle);
|
||||||
|
|
||||||
|
strncpy(tempInputTitle, searchTitle, sizeof(tempInputTitle) - 1);
|
||||||
|
lowerStr(tempInputTitle);
|
||||||
|
|
||||||
|
if (!strstr(tempEntryTitle, tempInputTitle)) {
|
||||||
|
free(fields);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currIndex < startIndex || currIndex >= endIndex) {
|
||||||
|
currIndex++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char** extractItemsFields = &fields[9]; // Items to extract start from the ninth field
|
||||||
|
|
||||||
|
size_t extractItemsCount = (fieldsCount - 9) / 2;
|
||||||
|
struct EntryExtractItem extractItems[extractItemsCount];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < extractItemsCount; i++) {
|
||||||
|
extractItems[i].outPath = extractItemsFields[i * 2];
|
||||||
|
extractItems[i].inPath = extractItemsFields[i * 2 + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry e = newEntry(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], strtoull(fields[7], NULL, 10), fields[8], extractItems, extractItemsCount);
|
||||||
|
|
||||||
|
// Increase the capacity if needed
|
||||||
|
if (count >= capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
results = realloc(results, capacity * sizeof(Entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
results[count] = e;
|
||||||
|
count++;
|
||||||
|
|
||||||
|
currIndex++;
|
||||||
|
free(fields);
|
||||||
|
|
||||||
|
if (currIndex >= endIndex)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*resultsCount = count;
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
Database* getDatabaseList(size_t* databasesCount)
|
||||||
|
{
|
||||||
|
*databasesCount = 0;
|
||||||
|
|
||||||
|
FILE* fp = fopen(DATABASE_LIST_PATH, "r");
|
||||||
|
if (!fp)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
size_t count = 0;
|
||||||
|
size_t capacity = 8;
|
||||||
|
Database* databases = malloc(capacity * sizeof(Database));
|
||||||
|
|
||||||
|
char line[2048];
|
||||||
|
char name[1024];
|
||||||
|
char value[1024];
|
||||||
|
while (fgets(line, sizeof(line), fp)) {
|
||||||
|
if (sscanf(line, "%[^\t]\t%s", name, value) != 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (count >= capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
databases = realloc(databases, capacity * sizeof(Database));
|
||||||
|
}
|
||||||
|
|
||||||
|
databases[count++] = newDatabase(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
*databasesCount = count;
|
||||||
|
return databases;
|
||||||
|
}
|
39
source/database.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "entries.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct Database* Database;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DATABASE_TYPE_HTTP,
|
||||||
|
DATABASE_TYPE_LOCAL
|
||||||
|
} DatabaseType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DATABASE_INIT_SUCCESS,
|
||||||
|
DATABASE_INIT_SUCCESS_CACHE,
|
||||||
|
DATABASE_INIT_ERR_DOWNLOAD,
|
||||||
|
DATABASE_INIT_ERR_FILE_NOT_FOUND
|
||||||
|
} DatabaseInitStatus;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DATABASE_OPEN_SUCCESS,
|
||||||
|
DATABASE_OPEN_ERR_FILE_OPEN,
|
||||||
|
DATABASE_OPEN_ERR_INVALID_VERSION,
|
||||||
|
DATABASE_OPEN_ERR_INVALID_FORMAT
|
||||||
|
} DatabaseOpenStatus;
|
||||||
|
|
||||||
|
Database newDatabase(const char* name, const char* value);
|
||||||
|
void freeDatabase(Database);
|
||||||
|
char* getDatabaseName(Database);
|
||||||
|
char* getDatabaseValue(Database);
|
||||||
|
char* getDatabasePath(Database);
|
||||||
|
size_t getDatabaseSize(Database);
|
||||||
|
bool getDatabaseIsInited(Database);
|
||||||
|
Database getLastOpenedDatabase(void);
|
||||||
|
DatabaseInitStatus openDatabase(Database);
|
||||||
|
bool closeDatabase(Database d);
|
||||||
|
DatabaseInitStatus initDatabase(Database);
|
||||||
|
Entry* searchDatabase(Database, const char* searchTitle, size_t pageSize, size_t page, size_t* resultsCount);
|
||||||
|
Database* getDatabaseList(size_t* databasesCount);
|
120
source/entries.c
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
#include "entries.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
char* title;
|
||||||
|
char* platform;
|
||||||
|
char* region;
|
||||||
|
char* version;
|
||||||
|
char* author;
|
||||||
|
char* url;
|
||||||
|
char* fileName;
|
||||||
|
u64 size;
|
||||||
|
char* boxartUrl;
|
||||||
|
struct EntryExtractItem* extractItems;
|
||||||
|
size_t extractItemsCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
Entry newEntry(const char* title, const char* platform, char* region, const char* version, const char* author, const char* url, const char* fileName, u64 size, const char* boxartUrl, struct EntryExtractItem* extractItems, size_t extractItemsCount)
|
||||||
|
{
|
||||||
|
Entry e = malloc(sizeof(struct Entry));
|
||||||
|
e->title = strdup(title);
|
||||||
|
e->platform = strdup(platform);
|
||||||
|
e->region = strdup(region);
|
||||||
|
e->version = strdup(version);
|
||||||
|
e->author = strdup(author);
|
||||||
|
e->url = strdup(url);
|
||||||
|
e->fileName = strdup(fileName);
|
||||||
|
e->size = size;
|
||||||
|
e->boxartUrl = strdup(boxartUrl);
|
||||||
|
|
||||||
|
e->extractItems = malloc(sizeof(struct EntryExtractItem) * extractItemsCount);
|
||||||
|
for (int i = 0; i < extractItemsCount; i++) {
|
||||||
|
e->extractItems[i].outPath = strdup(extractItems[i].outPath);
|
||||||
|
e->extractItems[i].inPath = strdup(extractItems[i].outPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
e->extractItemsCount = extractItemsCount;
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeEntry(Entry e)
|
||||||
|
{
|
||||||
|
free(e->title);
|
||||||
|
free(e->platform);
|
||||||
|
free(e->version);
|
||||||
|
free(e->author);
|
||||||
|
free(e->url);
|
||||||
|
free(e->boxartUrl);
|
||||||
|
|
||||||
|
for (int i = 0; i < e->extractItemsCount; i++) {
|
||||||
|
free(e->extractItems[i].outPath);
|
||||||
|
free(e->extractItems[i].inPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(e->extractItems);
|
||||||
|
free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getEntryTitle(Entry e)
|
||||||
|
{
|
||||||
|
return e->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getEntryPlatform(Entry e)
|
||||||
|
{
|
||||||
|
return e->platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getEntryRegion(Entry e)
|
||||||
|
{
|
||||||
|
return e->region;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getEntryVersion(Entry e)
|
||||||
|
{
|
||||||
|
return e->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getEntryAuthor(Entry e)
|
||||||
|
{
|
||||||
|
return e->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getEntryUrl(Entry e)
|
||||||
|
{
|
||||||
|
return e->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getEntryFileName(Entry e)
|
||||||
|
{
|
||||||
|
return e->fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 getEntrySize(Entry e)
|
||||||
|
{
|
||||||
|
return e->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* getEntryBoxartUrl(Entry e)
|
||||||
|
{
|
||||||
|
return e->boxartUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry cloneEntry(Entry e)
|
||||||
|
{
|
||||||
|
return newEntry(e->title, e->platform, e->region, e->version, e->author, e->url, e->fileName, e->size, e->boxartUrl, e->extractItems, e->extractItemsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EntryExtractItem* getEntryExtractItems(Entry e)
|
||||||
|
{
|
||||||
|
return e->extractItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getEntryExtractItemsCount(Entry e)
|
||||||
|
{
|
||||||
|
return e->extractItemsCount;
|
||||||
|
}
|
24
source/entries.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <calico/types.h>
|
||||||
|
|
||||||
|
typedef struct Entry* Entry;
|
||||||
|
|
||||||
|
struct EntryExtractItem {
|
||||||
|
char* outPath;
|
||||||
|
char* inPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
Entry newEntry(const char* title, const char* platform, char* region, const char* version, const char* author, const char* url, const char* fileName, u64 size, const char* boxartUrl, struct EntryExtractItem* extractItems, size_t extractItemsCount);
|
||||||
|
void freeEntry(Entry);
|
||||||
|
char* getEntryTitle(Entry);
|
||||||
|
char* getEntryPlatform(Entry);
|
||||||
|
char* getEntryRegion(Entry);
|
||||||
|
char* getEntryVersion(Entry);
|
||||||
|
char* getEntryAuthor(Entry);
|
||||||
|
char* getEntryUrl(Entry);
|
||||||
|
char* getEntryFileName(Entry);
|
||||||
|
u64 getEntrySize(Entry);
|
||||||
|
char* getEntryBoxartUrl(Entry);
|
||||||
|
Entry cloneEntry(Entry);
|
||||||
|
struct EntryExtractItem* getEntryExtractItems(Entry);
|
||||||
|
size_t getEntryExtractItemsCount(Entry);
|
249
source/gettext.c
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
// Based off https://github.com/dborth/libwiigui/blob/master/source/gettext.cpp
|
||||||
|
|
||||||
|
#include "gettext.h"
|
||||||
|
|
||||||
|
#include "en_lang.h"
|
||||||
|
#include "it_lang.h"
|
||||||
|
#include <calico/types.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef struct _MSG {
|
||||||
|
unsigned long id;
|
||||||
|
char* msgstr;
|
||||||
|
struct _MSG* next;
|
||||||
|
} MSG;
|
||||||
|
|
||||||
|
static MSG* baseMSG = 0;
|
||||||
|
|
||||||
|
#define HASHWORDBITS 32
|
||||||
|
|
||||||
|
static inline unsigned long hashString(const char* strParam)
|
||||||
|
{
|
||||||
|
unsigned long hval, g;
|
||||||
|
const char* str = strParam;
|
||||||
|
|
||||||
|
hval = 0;
|
||||||
|
while (*str != '\0') {
|
||||||
|
hval <<= 4;
|
||||||
|
hval += (unsigned char)*str++;
|
||||||
|
g = hval & ((unsigned long)0xf << (HASHWORDBITS - 4));
|
||||||
|
if (g != 0) {
|
||||||
|
hval ^= g >> (HASHWORDBITS - 8);
|
||||||
|
hval ^= g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* expandEscape(const char* str)
|
||||||
|
{
|
||||||
|
char *retval, *rp;
|
||||||
|
const char* cp = str;
|
||||||
|
|
||||||
|
retval = (char*)malloc(strlen(str) + 1);
|
||||||
|
if (retval == NULL)
|
||||||
|
return NULL;
|
||||||
|
rp = retval;
|
||||||
|
|
||||||
|
while (cp[0] != '\0' && cp[0] != '\\')
|
||||||
|
*rp++ = *cp++;
|
||||||
|
if (cp[0] != '\0') {
|
||||||
|
do {
|
||||||
|
switch (*++cp) {
|
||||||
|
case '\"':
|
||||||
|
*rp++ = '\"';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
*rp++ = '\a';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
*rp++ = '\b';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
*rp++ = '\f';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
*rp++ = '\n';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
*rp++ = '\r';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
*rp++ = '\t';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
*rp++ = '\v';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
*rp = '\\';
|
||||||
|
++cp;
|
||||||
|
break;
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7': {
|
||||||
|
size_t ch = *cp++ - '0';
|
||||||
|
if (*cp >= '0' && *cp <= '7') {
|
||||||
|
ch *= 8;
|
||||||
|
ch += *cp++ - '0';
|
||||||
|
if (*cp >= '0' && *cp <= '7') {
|
||||||
|
ch *= 8;
|
||||||
|
ch += *cp++ - '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*rp = ch;
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
*rp = '\\';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while (cp[0] != '\0' && cp[0] != '\\')
|
||||||
|
*rp++ = *cp++;
|
||||||
|
} while (cp[0] != '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
*rp = '\0';
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MSG* findMSG(unsigned long id)
|
||||||
|
{
|
||||||
|
MSG* msg;
|
||||||
|
for (msg = baseMSG; msg; msg = msg->next) {
|
||||||
|
if (msg->id == id)
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MSG* setMSG(const char* msgid, const char* msgstr)
|
||||||
|
{
|
||||||
|
unsigned long id = hashString(msgid);
|
||||||
|
MSG* msg = findMSG(id);
|
||||||
|
if (!msg) {
|
||||||
|
msg = (MSG*)malloc(sizeof(MSG));
|
||||||
|
msg->id = id;
|
||||||
|
msg->msgstr = NULL;
|
||||||
|
msg->next = baseMSG;
|
||||||
|
baseMSG = msg;
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
if (msgstr) {
|
||||||
|
if (msg->msgstr)
|
||||||
|
free(msg->msgstr);
|
||||||
|
msg->msgstr = expandEscape(msgstr);
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gettextCleanUp(void)
|
||||||
|
{
|
||||||
|
while (baseMSG) {
|
||||||
|
MSG* nextMsg = baseMSG->next;
|
||||||
|
free(baseMSG->msgstr);
|
||||||
|
free(baseMSG);
|
||||||
|
baseMSG = nextMsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* memfgets(char* dst, size_t maxlen, char* src)
|
||||||
|
{
|
||||||
|
if (!src || !dst || maxlen <= 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char* newline = strchr(src, '\n');
|
||||||
|
if (newline == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memcpy(dst, src, (newline - src));
|
||||||
|
dst[(newline - src)] = 0;
|
||||||
|
return ++newline;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool loadLanguageFromMemory(const u8* lang_data, size_t lang_size)
|
||||||
|
{
|
||||||
|
char line[256];
|
||||||
|
char* lastID = NULL;
|
||||||
|
|
||||||
|
const char *file, *eof;
|
||||||
|
|
||||||
|
file = (const char*)lang_data;
|
||||||
|
eof = file + lang_size;
|
||||||
|
|
||||||
|
gettextCleanUp();
|
||||||
|
|
||||||
|
while (file && file < eof) {
|
||||||
|
file = memfgets(line, sizeof(line), (char*)file);
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (line[0] == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strncmp(line, "msgid \"", 7) == 0) {
|
||||||
|
char *msgid, *end;
|
||||||
|
if (lastID) {
|
||||||
|
free(lastID);
|
||||||
|
lastID = NULL;
|
||||||
|
}
|
||||||
|
msgid = &line[7];
|
||||||
|
end = strrchr(msgid, '"');
|
||||||
|
if (end && end - msgid > 1) {
|
||||||
|
*end = 0;
|
||||||
|
lastID = strdup(msgid);
|
||||||
|
}
|
||||||
|
} else if (strncmp(line, "msgstr \"", 8) == 0) {
|
||||||
|
char *msgstr, *end;
|
||||||
|
|
||||||
|
if (lastID == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
msgstr = &line[8];
|
||||||
|
end = strrchr(msgstr, '"');
|
||||||
|
if (end && end - msgstr > 1) {
|
||||||
|
*end = 0;
|
||||||
|
setMSG(lastID, msgstr);
|
||||||
|
}
|
||||||
|
free(lastID);
|
||||||
|
lastID = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadLanguage(LanguageEnum lang)
|
||||||
|
{
|
||||||
|
switch (lang) {
|
||||||
|
case LANG_IT:
|
||||||
|
loadLanguageFromMemory(it_lang, it_lang_size);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
loadLanguageFromMemory(en_lang, en_lang_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* gettext(const char* msgid)
|
||||||
|
{
|
||||||
|
MSG* msg = findMSG(hashString(msgid));
|
||||||
|
if (msg && msg->msgstr)
|
||||||
|
return msg->msgstr;
|
||||||
|
|
||||||
|
return msgid;
|
||||||
|
}
|
10
source/gettext.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LANG_EN,
|
||||||
|
LANG_IT,
|
||||||
|
LANGS_COUNT
|
||||||
|
} LanguageEnum;
|
||||||
|
|
||||||
|
void loadLanguage(LanguageEnum lang);
|
||||||
|
const char* gettext(const char* msg);
|
1
source/gfx/brickColor1.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/brickColor1.png
Normal file
After Width: | Height: | Size: 957 B |
1
source/gfx/brickColor2.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/brickColor2.png
Normal file
After Width: | Height: | Size: 979 B |
1
source/gfx/lButton.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/lButton.png
Normal file
After Width: | Height: | Size: 373 B |
1
source/gfx/navbarBrowseIcon.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/navbarBrowseIcon.png
Normal file
After Width: | Height: | Size: 239 B |
1
source/gfx/navbarDatabasesIcon.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/navbarDatabasesIcon.png
Normal file
After Width: | Height: | Size: 210 B |
1
source/gfx/navbarInfoIcon.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/navbarInfoIcon.png
Normal file
After Width: | Height: | Size: 207 B |
1
source/gfx/navbarSettingsIcon.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/navbarSettingsIcon.png
Normal file
After Width: | Height: | Size: 255 B |
1
source/gfx/rButton.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/rButton.png
Normal file
After Width: | Height: | Size: 528 B |
93
source/gui/box.c
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#include "box.h"
|
||||||
|
|
||||||
|
#include <gl2d.h>
|
||||||
|
|
||||||
|
struct GuiBox {
|
||||||
|
size_t width;
|
||||||
|
size_t height;
|
||||||
|
size_t posX;
|
||||||
|
size_t posY;
|
||||||
|
u16 color;
|
||||||
|
size_t borderSize;
|
||||||
|
u16 borderColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
GuiBox newGuiBox(size_t width, size_t height, u16 color)
|
||||||
|
{
|
||||||
|
GuiBox gb = malloc(sizeof(struct GuiBox));
|
||||||
|
gb->width = width;
|
||||||
|
gb->height = height;
|
||||||
|
gb->posX = 0;
|
||||||
|
gb->posY = 0;
|
||||||
|
gb->color = color;
|
||||||
|
gb->borderSize = 0;
|
||||||
|
gb->borderColor = 0;
|
||||||
|
|
||||||
|
return gb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeGuiBox(GuiBox gb)
|
||||||
|
{
|
||||||
|
free(gb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiBoxWidth(GuiBox gb, size_t width)
|
||||||
|
{
|
||||||
|
gb->width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiBoxHeight(GuiBox gb, size_t height)
|
||||||
|
{
|
||||||
|
gb->width = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiBoxPos(GuiBox gb, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
gb->posX = posX;
|
||||||
|
gb->posY = posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiBoxBorder(GuiBox gb, size_t borderSize, u16 borderColor)
|
||||||
|
{
|
||||||
|
gb->borderSize = borderSize;
|
||||||
|
gb->borderColor = borderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiBoxPos(GuiBox gb, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
if (gb->borderSize && gb->borderColor && gb->color) {
|
||||||
|
glBoxFilled(
|
||||||
|
posX,
|
||||||
|
posY,
|
||||||
|
posX + gb->width - 1,
|
||||||
|
posY + gb->height - 1,
|
||||||
|
gb->borderColor);
|
||||||
|
glBoxFilled(
|
||||||
|
posX + gb->borderSize,
|
||||||
|
posY + gb->borderSize,
|
||||||
|
posX + gb->width - gb->borderSize - 1,
|
||||||
|
posY + gb->height - gb->borderSize - 1,
|
||||||
|
gb->color);
|
||||||
|
} else if (gb->color) {
|
||||||
|
glBoxFilled(
|
||||||
|
posX,
|
||||||
|
posY,
|
||||||
|
posX + gb->width - 1,
|
||||||
|
posY + gb->height - 1,
|
||||||
|
gb->color);
|
||||||
|
} else if (gb->borderSize && gb->borderColor) {
|
||||||
|
for (size_t i = 0; i < gb->borderSize; i++) {
|
||||||
|
glBox(
|
||||||
|
posX + i,
|
||||||
|
posY + i,
|
||||||
|
posX + gb->width - i - 2,
|
||||||
|
posY + gb->height - i - 2,
|
||||||
|
gb->borderColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiBox(GuiBox gb)
|
||||||
|
{
|
||||||
|
drawGuiBoxPos(gb, gb->posX, gb->posY);
|
||||||
|
}
|
13
source/gui/box.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <calico/types.h>
|
||||||
|
|
||||||
|
typedef struct GuiBox* GuiBox;
|
||||||
|
|
||||||
|
GuiBox newGuiBox(size_t width, size_t height, u16 color);
|
||||||
|
void freeGuiBox(GuiBox gb);
|
||||||
|
void setGuiBoxWidth(GuiBox, size_t);
|
||||||
|
void setGuiBoxHeight(GuiBox, size_t);
|
||||||
|
void setGuiBoxPos(GuiBox, size_t posX, size_t posY);
|
||||||
|
void setGuiBoxBorder(GuiBox, size_t borderSize, u16 borderColor);
|
||||||
|
void drawGuiBoxPos(GuiBox, size_t posX, size_t posY);
|
||||||
|
void drawGuiBox(GuiBox);
|
161
source/gui/button.c
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#include "button.h"
|
||||||
|
|
||||||
|
#include "input.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
struct GuiButton {
|
||||||
|
size_t width;
|
||||||
|
size_t height;
|
||||||
|
size_t posX;
|
||||||
|
size_t posY;
|
||||||
|
GuiButtonState state;
|
||||||
|
GuiBox bg;
|
||||||
|
GuiBox bgHover;
|
||||||
|
GuiText label;
|
||||||
|
GuiImage icon;
|
||||||
|
GuiImage iconHover;
|
||||||
|
};
|
||||||
|
|
||||||
|
GuiButton newGuiButton(size_t width, size_t height)
|
||||||
|
{
|
||||||
|
GuiButton gb = malloc(sizeof(struct GuiButton));
|
||||||
|
gb->width = width;
|
||||||
|
gb->height = height;
|
||||||
|
gb->posX = 0;
|
||||||
|
gb->posY = 0;
|
||||||
|
gb->state = GUI_BUTTON_STATE_DEFAULT;
|
||||||
|
gb->bg = NULL;
|
||||||
|
gb->bgHover = NULL;
|
||||||
|
gb->label = NULL;
|
||||||
|
gb->icon = NULL;
|
||||||
|
gb->iconHover = NULL;
|
||||||
|
|
||||||
|
return gb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeGuiButton(GuiButton gb)
|
||||||
|
{
|
||||||
|
free(gb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiButtonWidth(GuiButton gb, size_t width)
|
||||||
|
{
|
||||||
|
gb->width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiButtonHeight(GuiButton gb, size_t height)
|
||||||
|
{
|
||||||
|
gb->height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiButtonPos(GuiButton gb, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
gb->posX = posX;
|
||||||
|
gb->posY = posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiButtonState(GuiButton gb, GuiButtonState state)
|
||||||
|
{
|
||||||
|
gb->state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetGuiButtonState(GuiButton gb)
|
||||||
|
{
|
||||||
|
gb->state = GUI_BUTTON_STATE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiButtonBg(GuiButton gb, GuiBox bg, GuiBox bgHover)
|
||||||
|
{
|
||||||
|
gb->bg = bg;
|
||||||
|
gb->bgHover = bgHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiButtonLabel(GuiButton gb, GuiText label)
|
||||||
|
{
|
||||||
|
gb->label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiButtonIcon(GuiButton gb, GuiImage icon, GuiImage iconHover)
|
||||||
|
{
|
||||||
|
gb->icon = icon;
|
||||||
|
gb->iconHover = iconHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getGuiButtonPosX(GuiButton gb)
|
||||||
|
{
|
||||||
|
return gb->posX;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getGuiButtonPosY(GuiButton gb)
|
||||||
|
{
|
||||||
|
return gb->posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiButtonState getGuiButtonState(GuiButton gb)
|
||||||
|
{
|
||||||
|
return gb->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleTouchGuiButton(GuiButton gb)
|
||||||
|
{
|
||||||
|
if ((gb->state == GUI_BUTTON_STATE_HELD) && (touch.px == 0) && (touch.py == 0))
|
||||||
|
gb->state = GUI_BUTTON_STATE_CLICKED;
|
||||||
|
|
||||||
|
if (!touch.px & !touch.py)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (((touch.px >= gb->posX) && (touch.px <= (gb->posX + gb->width))) && ((touch.py >= gb->posY) && (touch.py <= (gb->posY + gb->height))))
|
||||||
|
gb->state = GUI_BUTTON_STATE_HELD;
|
||||||
|
else if (gb->state == GUI_BUTTON_STATE_HELD)
|
||||||
|
gb->state = GUI_BUTTON_STATE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiButtonPos(GuiButton gb, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
if (gb->bg && (gb->state == GUI_BUTTON_STATE_DEFAULT || gb->state == GUI_BUTTON_STATE_CLICKED))
|
||||||
|
drawGuiBoxPos(gb->bg, posX, posY);
|
||||||
|
else if (gb->bgHover && (gb->state == GUI_BUTTON_STATE_HELD || gb->state == GUI_BUTTON_STATE_SELECTED))
|
||||||
|
drawGuiBoxPos(gb->bgHover, posX, posY);
|
||||||
|
|
||||||
|
if (gb->icon && (gb->state == GUI_BUTTON_STATE_DEFAULT || gb->state == GUI_BUTTON_STATE_CLICKED))
|
||||||
|
drawGuiImagePos(gb->icon, posX + (gb->width - getGuiImageWidth(gb->icon)) / 2, posY + (gb->height - getGuiImageHeight(gb->icon)) / 2);
|
||||||
|
|
||||||
|
if (gb->iconHover && (gb->state == GUI_BUTTON_STATE_HELD || gb->state == GUI_BUTTON_STATE_SELECTED))
|
||||||
|
drawGuiImagePos(gb->iconHover, posX + (gb->width - getGuiImageWidth(gb->iconHover)) / 2, posY + (gb->height - getGuiImageHeight(gb->iconHover)) / 2);
|
||||||
|
|
||||||
|
if (gb->label) {
|
||||||
|
size_t labelPosX;
|
||||||
|
size_t labelPosY;
|
||||||
|
|
||||||
|
switch (getGuiTextHAlignment(gb->label)) {
|
||||||
|
case GUI_TEXT_H_ALIGN_CENTER:
|
||||||
|
labelPosX = getGuiTextPosX(gb->label) + posX + gb->width / 2;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_RIGHT:
|
||||||
|
labelPosX = getGuiTextPosX(gb->label) + posX + gb->width;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
labelPosX = getGuiTextPosX(gb->label) + posX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (getGuiTextVAlignment(gb->label)) {
|
||||||
|
case GUI_TEXT_V_ALIGN_MIDDLE:
|
||||||
|
labelPosY = getGuiTextPosY(gb->label) + posY + gb->height / 2;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_V_ALIGN_BOTTOM:
|
||||||
|
labelPosY = getGuiTextPosY(gb->label) + posY + getGuiTextHeight(gb->label);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
labelPosY = getGuiTextPosY(gb->label) + posY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawGuiTextPos(gb->label, labelPosX, labelPosY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiButton(GuiButton gb)
|
||||||
|
{
|
||||||
|
drawGuiButtonPos(gb, gb->posX, gb->posY);
|
||||||
|
}
|
29
source/gui/button.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "box.h"
|
||||||
|
#include "image.h"
|
||||||
|
#include "text.h"
|
||||||
|
|
||||||
|
typedef struct GuiButton* GuiButton;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_BUTTON_STATE_DEFAULT,
|
||||||
|
GUI_BUTTON_STATE_HELD,
|
||||||
|
GUI_BUTTON_STATE_SELECTED,
|
||||||
|
GUI_BUTTON_STATE_CLICKED
|
||||||
|
} GuiButtonState;
|
||||||
|
|
||||||
|
GuiButton newGuiButton(size_t width, size_t height);
|
||||||
|
void freeGuiButton(GuiButton);
|
||||||
|
void setGuiButtonWidth(GuiButton, size_t);
|
||||||
|
void setGuiButtonPos(GuiButton, size_t posX, size_t posY);
|
||||||
|
void setGuiButtonState(GuiButton, GuiButtonState);
|
||||||
|
void resetGuiButtonState(GuiButton);
|
||||||
|
void setGuiButtonBg(GuiButton, GuiBox bg, GuiBox bgHover);
|
||||||
|
void setGuiButtonLabel(GuiButton, GuiText);
|
||||||
|
void setGuiButtonIcon(GuiButton, GuiImage icon, GuiImage iconHover);
|
||||||
|
size_t getGuiButtonPosX(GuiButton);
|
||||||
|
size_t getGuiButtonPosY(GuiButton);
|
||||||
|
GuiButtonState getGuiButtonState(GuiButton);
|
||||||
|
void handleTouchGuiButton(GuiButton);
|
||||||
|
void drawGuiButtonPos(GuiButton, size_t posX, size_t posY);
|
||||||
|
void drawGuiButton(GuiButton);
|
1
source/gui/gfx/backspaceKey.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gui/gfx/backspaceKey.png
Normal file
After Width: | Height: | Size: 158 B |
265
source/gui/gfx/fontBigUVCoords.h
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
// Generated by bmfont-to-spriteset (https://github.com/cavv-dev/bmfont-to-spriteset)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#define FONTBIG_BITMAP_WIDTH 256
|
||||||
|
#define FONTBIG_BITMAP_HEIGHT 256
|
||||||
|
#define FONTBIG_NUM_IMAGES 256
|
||||||
|
|
||||||
|
const unsigned int fontBigTexCoords[] = {
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
16, 168, 3, 20, //
|
||||||
|
4, 168, 3, 20, // !
|
||||||
|
24, 147, 7, 20, // "
|
||||||
|
68, 63, 10, 20, // #
|
||||||
|
120, 84, 9, 20, // $
|
||||||
|
140, 0, 15, 20, // %
|
||||||
|
101, 42, 11, 20, // &
|
||||||
|
252, 126, 3, 20, // '
|
||||||
|
147, 147, 5, 20, // (
|
||||||
|
195, 147, 5, 20, // )
|
||||||
|
40, 147, 7, 20, // *
|
||||||
|
208, 126, 8, 20, // +
|
||||||
|
48, 168, 3, 20, // ,
|
||||||
|
236, 147, 4, 20, // -
|
||||||
|
40, 168, 3, 20, // .
|
||||||
|
171, 147, 5, 20, // /
|
||||||
|
190, 126, 8, 20, // 0
|
||||||
|
150, 84, 9, 20, // 1
|
||||||
|
181, 126, 8, 20, // 2
|
||||||
|
172, 126, 8, 20, // 3
|
||||||
|
101, 63, 10, 20, // 4
|
||||||
|
163, 126, 8, 20, // 5
|
||||||
|
154, 126, 8, 20, // 6
|
||||||
|
145, 126, 8, 20, // 7
|
||||||
|
136, 126, 8, 20, // 8
|
||||||
|
118, 126, 8, 20, // 9
|
||||||
|
32, 168, 3, 20, // :
|
||||||
|
36, 168, 3, 20, // ;
|
||||||
|
180, 84, 9, 20, // <
|
||||||
|
190, 84, 9, 20, // =
|
||||||
|
200, 84, 9, 20, // >
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
74, 0, 16, 20, // @
|
||||||
|
70, 21, 13, 20, // A
|
||||||
|
137, 42, 11, 20, // B
|
||||||
|
173, 42, 11, 20, // C
|
||||||
|
161, 42, 11, 20, // D
|
||||||
|
134, 63, 10, 20, // E
|
||||||
|
220, 84, 9, 20, // F
|
||||||
|
152, 21, 12, 20, // G
|
||||||
|
125, 42, 11, 20, // H
|
||||||
|
56, 168, 3, 20, // I
|
||||||
|
240, 84, 9, 20, // J
|
||||||
|
165, 21, 12, 20, // K
|
||||||
|
0, 105, 9, 20, // L
|
||||||
|
14, 21, 13, 20, // M
|
||||||
|
185, 42, 11, 20, // N
|
||||||
|
217, 21, 12, 20, // O
|
||||||
|
57, 63, 10, 20, // P
|
||||||
|
139, 21, 12, 20, // Q
|
||||||
|
191, 21, 12, 20, // R
|
||||||
|
90, 63, 10, 20, // S
|
||||||
|
60, 105, 9, 20, // T
|
||||||
|
89, 42, 11, 20, // U
|
||||||
|
149, 42, 11, 20, // V
|
||||||
|
56, 0, 17, 20, // W
|
||||||
|
243, 21, 12, 20, // X
|
||||||
|
77, 42, 11, 20, // Y
|
||||||
|
100, 126, 8, 20, // Z
|
||||||
|
165, 147, 5, 20, // [
|
||||||
|
153, 147, 5, 20, // Backslash
|
||||||
|
183, 147, 5, 20, // ]
|
||||||
|
40, 84, 9, 20, // ^
|
||||||
|
35, 63, 10, 20, // _
|
||||||
|
211, 147, 4, 20, // `
|
||||||
|
178, 63, 9, 20, // a
|
||||||
|
188, 63, 9, 20, // b
|
||||||
|
37, 126, 8, 20, // c
|
||||||
|
198, 63, 9, 20, // d
|
||||||
|
208, 63, 9, 20, // e
|
||||||
|
91, 147, 6, 20, // f
|
||||||
|
218, 63, 9, 20, // g
|
||||||
|
228, 63, 9, 20, // h
|
||||||
|
20, 168, 3, 20, // i
|
||||||
|
250, 105, 5, 20, // j
|
||||||
|
238, 63, 9, 20, // k
|
||||||
|
12, 168, 3, 20, // l
|
||||||
|
156, 0, 15, 20, // m
|
||||||
|
0, 84, 9, 20, // n
|
||||||
|
10, 84, 9, 20, // o
|
||||||
|
20, 84, 9, 20, // p
|
||||||
|
30, 84, 9, 20, // q
|
||||||
|
126, 147, 6, 20, // r
|
||||||
|
247, 0, 8, 20, // s
|
||||||
|
98, 147, 6, 20, // t
|
||||||
|
50, 84, 9, 20, // u
|
||||||
|
60, 84, 9, 20, // v
|
||||||
|
28, 21, 13, 20, // w
|
||||||
|
70, 84, 9, 20, // x
|
||||||
|
80, 84, 9, 20, // y
|
||||||
|
48, 147, 7, 20, // z
|
||||||
|
16, 147, 7, 20, // {
|
||||||
|
60, 168, 2, 20, // |
|
||||||
|
32, 147, 7, 20, // }
|
||||||
|
90, 84, 9, 20, // ~
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
100, 84, 9, 20, //
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
52, 168, 3, 20, //
|
||||||
|
19, 126, 8, 20, //
|
||||||
|
248, 63, 7, 20, //
|
||||||
|
124, 0, 15, 20, //
|
||||||
|
46, 126, 8, 20, //
|
||||||
|
55, 126, 8, 20, //
|
||||||
|
105, 147, 6, 20, //
|
||||||
|
0, 0, 18, 20, //
|
||||||
|
112, 63, 10, 20, //
|
||||||
|
216, 147, 4, 20, //
|
||||||
|
91, 0, 16, 20, //
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
127, 126, 8, 20, //
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
8, 168, 3, 20, //
|
||||||
|
44, 168, 3, 20, //
|
||||||
|
0, 147, 7, 20, //
|
||||||
|
8, 147, 7, 20, //
|
||||||
|
189, 147, 5, 20, //
|
||||||
|
10, 126, 8, 20, //
|
||||||
|
19, 0, 18, 20, //
|
||||||
|
133, 147, 6, 20, //
|
||||||
|
108, 0, 15, 20, //
|
||||||
|
64, 126, 8, 20, //
|
||||||
|
206, 147, 4, 20, //
|
||||||
|
172, 0, 15, 20, //
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
244, 126, 7, 20, //
|
||||||
|
197, 42, 11, 20, //
|
||||||
|
24, 168, 3, 20, //
|
||||||
|
28, 168, 3, 20, // ¡
|
||||||
|
73, 126, 8, 20, // ¢
|
||||||
|
82, 126, 8, 20, // £
|
||||||
|
167, 63, 10, 20, // ¤
|
||||||
|
230, 84, 9, 20, // ¥
|
||||||
|
63, 168, 2, 20, // ¦
|
||||||
|
109, 126, 8, 20, // §
|
||||||
|
140, 147, 6, 20, // ¨
|
||||||
|
204, 21, 12, 20, // ©
|
||||||
|
56, 147, 6, 20, // ª
|
||||||
|
199, 126, 8, 20, // «
|
||||||
|
140, 84, 9, 20, // ¬
|
||||||
|
241, 147, 4, 20, //
|
||||||
|
178, 21, 12, 20, // ®
|
||||||
|
123, 63, 10, 20, // ¯
|
||||||
|
77, 147, 6, 20, // °
|
||||||
|
217, 126, 8, 20, // ±
|
||||||
|
159, 147, 5, 20, // ²
|
||||||
|
250, 84, 5, 20, // ³
|
||||||
|
201, 147, 4, 20, // ´
|
||||||
|
226, 126, 8, 20, // µ
|
||||||
|
130, 84, 9, 20, // ¶
|
||||||
|
251, 147, 3, 20, // ·
|
||||||
|
0, 168, 3, 20, // ¸
|
||||||
|
84, 147, 6, 20, // ¹
|
||||||
|
177, 147, 5, 20, // º
|
||||||
|
28, 126, 8, 20, // »
|
||||||
|
188, 0, 14, 20, // ¼
|
||||||
|
218, 0, 14, 20, // ½
|
||||||
|
0, 21, 13, 20, // ¾
|
||||||
|
110, 84, 9, 20, // ¿
|
||||||
|
233, 0, 13, 20, // À
|
||||||
|
112, 21, 13, 20, // Á
|
||||||
|
98, 21, 13, 20, // Â
|
||||||
|
84, 21, 13, 20, // Ã
|
||||||
|
56, 21, 13, 20, // Ä
|
||||||
|
42, 21, 13, 20, // Å
|
||||||
|
38, 0, 17, 20, // Æ
|
||||||
|
113, 42, 11, 20, // Ç
|
||||||
|
245, 42, 10, 20, // È
|
||||||
|
24, 63, 10, 20, // É
|
||||||
|
46, 63, 10, 20, // Ê
|
||||||
|
79, 63, 10, 20, // Ë
|
||||||
|
221, 147, 4, 20, // Ì
|
||||||
|
226, 147, 4, 20, // Í
|
||||||
|
63, 147, 6, 20, // Î
|
||||||
|
70, 147, 6, 20, // Ï
|
||||||
|
126, 21, 12, 20, // Ð
|
||||||
|
65, 42, 11, 20, // Ñ
|
||||||
|
0, 42, 12, 20, // Ò
|
||||||
|
13, 42, 12, 20, // Ó
|
||||||
|
26, 42, 12, 20, // Ô
|
||||||
|
39, 42, 12, 20, // Õ
|
||||||
|
52, 42, 12, 20, // Ö
|
||||||
|
170, 84, 9, 20, // ×
|
||||||
|
230, 21, 12, 20, // Ø
|
||||||
|
209, 42, 11, 20, // Ù
|
||||||
|
221, 42, 11, 20, // Ú
|
||||||
|
233, 42, 11, 20, // Û
|
||||||
|
0, 63, 11, 20, // Ü
|
||||||
|
12, 63, 11, 20, // Ý
|
||||||
|
145, 63, 10, 20, // Þ
|
||||||
|
156, 63, 10, 20, // ß
|
||||||
|
10, 105, 9, 20, // à
|
||||||
|
20, 105, 9, 20, // á
|
||||||
|
30, 105, 9, 20, // â
|
||||||
|
40, 105, 9, 20, // ã
|
||||||
|
160, 84, 9, 20, // ä
|
||||||
|
50, 105, 9, 20, // å
|
||||||
|
203, 0, 14, 20, // æ
|
||||||
|
91, 126, 8, 20, // ç
|
||||||
|
80, 105, 9, 20, // è
|
||||||
|
90, 105, 9, 20, // é
|
||||||
|
100, 105, 9, 20, // ê
|
||||||
|
110, 105, 9, 20, // ë
|
||||||
|
246, 147, 4, 20, // ì
|
||||||
|
231, 147, 4, 20, // í
|
||||||
|
112, 147, 6, 20, // î
|
||||||
|
119, 147, 6, 20, // ï
|
||||||
|
120, 105, 9, 20, // ð
|
||||||
|
130, 105, 9, 20, // ñ
|
||||||
|
140, 105, 9, 20, // ò
|
||||||
|
150, 105, 9, 20, // ó
|
||||||
|
160, 105, 9, 20, // ô
|
||||||
|
170, 105, 9, 20, // õ
|
||||||
|
180, 105, 9, 20, // ö
|
||||||
|
235, 126, 8, 20, // ÷
|
||||||
|
190, 105, 9, 20, // ø
|
||||||
|
200, 105, 9, 20, // ù
|
||||||
|
210, 105, 9, 20, // ú
|
||||||
|
220, 105, 9, 20, // û
|
||||||
|
230, 105, 9, 20, // ü
|
||||||
|
240, 105, 9, 20, // ý
|
||||||
|
0, 126, 9, 20, // þ
|
||||||
|
70, 105, 9, 20, // ÿ
|
||||||
|
210, 84, 9, 20, // ?
|
||||||
|
};
|
1
source/gui/gfx/fontBig_0.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8
|
BIN
source/gui/gfx/fontBig_0.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
265
source/gui/gfx/fontMediumUVCoords.h
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
// Generated by bmfont-to-spriteset (https://github.com/cavv-dev/bmfont-to-spriteset)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#define FONTMEDIUM_BITMAP_WIDTH 256
|
||||||
|
#define FONTMEDIUM_BITMAP_HEIGHT 256
|
||||||
|
#define FONTMEDIUM_NUM_IMAGES 256
|
||||||
|
|
||||||
|
const unsigned int fontMediumTexCoords[] = {
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
252, 68, 3, 16, //
|
||||||
|
64, 102, 2, 16, // !
|
||||||
|
187, 85, 5, 16, // "
|
||||||
|
135, 34, 8, 16, // #
|
||||||
|
249, 34, 6, 16, // $
|
||||||
|
179, 0, 10, 16, // %
|
||||||
|
45, 34, 8, 16, // &
|
||||||
|
100, 102, 2, 16, // '
|
||||||
|
221, 85, 4, 16, // (
|
||||||
|
241, 85, 4, 16, // )
|
||||||
|
193, 85, 5, 16, // *
|
||||||
|
36, 34, 8, 16, // +
|
||||||
|
85, 102, 2, 16, // ,
|
||||||
|
252, 17, 3, 16, // -
|
||||||
|
82, 102, 2, 16, // .
|
||||||
|
211, 85, 4, 16, // /
|
||||||
|
49, 85, 6, 16, // 0
|
||||||
|
42, 85, 6, 16, // 1
|
||||||
|
35, 85, 6, 16, // 2
|
||||||
|
168, 68, 6, 16, // 3
|
||||||
|
152, 51, 7, 16, // 4
|
||||||
|
28, 85, 6, 16, // 5
|
||||||
|
245, 68, 6, 16, // 6
|
||||||
|
238, 68, 6, 16, // 7
|
||||||
|
231, 68, 6, 16, // 8
|
||||||
|
224, 68, 6, 16, // 9
|
||||||
|
97, 102, 2, 16, // :
|
||||||
|
88, 102, 2, 16, // ;
|
||||||
|
210, 68, 6, 16, // <
|
||||||
|
189, 68, 6, 16, // =
|
||||||
|
182, 68, 6, 16, // >
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
84, 0, 12, 16, // @
|
||||||
|
120, 17, 9, 16, // A
|
||||||
|
81, 34, 8, 16, // B
|
||||||
|
216, 17, 8, 16, // C
|
||||||
|
18, 34, 8, 16, // D
|
||||||
|
153, 34, 7, 16, // E
|
||||||
|
144, 51, 7, 16, // F
|
||||||
|
130, 17, 9, 16, // G
|
||||||
|
189, 17, 8, 16, // H
|
||||||
|
94, 102, 2, 16, // I
|
||||||
|
169, 34, 7, 16, // J
|
||||||
|
140, 17, 9, 16, // K
|
||||||
|
140, 68, 6, 16, // L
|
||||||
|
121, 0, 11, 16, // M
|
||||||
|
243, 17, 8, 16, // N
|
||||||
|
170, 17, 9, 16, // O
|
||||||
|
177, 34, 7, 16, // P
|
||||||
|
150, 17, 9, 16, // Q
|
||||||
|
27, 34, 8, 16, // R
|
||||||
|
185, 34, 7, 16, // S
|
||||||
|
119, 68, 6, 16, // T
|
||||||
|
54, 34, 8, 16, // U
|
||||||
|
222, 0, 9, 16, // V
|
||||||
|
44, 0, 13, 16, // W
|
||||||
|
212, 0, 9, 16, // X
|
||||||
|
90, 34, 8, 16, // Y
|
||||||
|
21, 85, 6, 16, // Z
|
||||||
|
0, 102, 4, 16, // [
|
||||||
|
251, 85, 4, 16, // Backslash
|
||||||
|
246, 85, 4, 16, // ]
|
||||||
|
0, 85, 6, 16, // ^
|
||||||
|
63, 34, 8, 16, // _
|
||||||
|
38, 102, 3, 16, // `
|
||||||
|
201, 34, 7, 16, // a
|
||||||
|
209, 34, 7, 16, // b
|
||||||
|
7, 85, 6, 16, // c
|
||||||
|
217, 34, 7, 16, // d
|
||||||
|
225, 34, 7, 16, // e
|
||||||
|
199, 85, 5, 16, // f
|
||||||
|
241, 34, 7, 16, // g
|
||||||
|
0, 51, 7, 16, // h
|
||||||
|
61, 102, 2, 16, // i
|
||||||
|
30, 102, 3, 16, // j
|
||||||
|
8, 51, 7, 16, // k
|
||||||
|
76, 102, 2, 16, // l
|
||||||
|
201, 0, 10, 16, // m
|
||||||
|
193, 34, 7, 16, // n
|
||||||
|
24, 51, 7, 16, // o
|
||||||
|
32, 51, 7, 16, // p
|
||||||
|
40, 51, 7, 16, // q
|
||||||
|
133, 85, 5, 16, // r
|
||||||
|
133, 68, 6, 16, // s
|
||||||
|
205, 85, 5, 16, // t
|
||||||
|
48, 51, 7, 16, // u
|
||||||
|
56, 51, 7, 16, // v
|
||||||
|
145, 0, 11, 16, // w
|
||||||
|
72, 34, 8, 16, // x
|
||||||
|
64, 51, 7, 16, // y
|
||||||
|
91, 85, 5, 16, // z
|
||||||
|
97, 85, 5, 16, // {
|
||||||
|
70, 102, 2, 16, // |
|
||||||
|
115, 85, 5, 16, // }
|
||||||
|
126, 68, 6, 16, // ~
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
80, 51, 7, 16, //
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
67, 102, 2, 16, //
|
||||||
|
154, 68, 6, 16, //
|
||||||
|
121, 85, 5, 16, //
|
||||||
|
168, 0, 10, 16, //
|
||||||
|
112, 68, 6, 16, //
|
||||||
|
84, 85, 6, 16, //
|
||||||
|
127, 85, 5, 16, //
|
||||||
|
71, 0, 12, 16, //
|
||||||
|
72, 51, 7, 16, //
|
||||||
|
50, 102, 3, 16, //
|
||||||
|
30, 0, 13, 16, //
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
161, 68, 6, 16, //
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
79, 102, 2, 16, //
|
||||||
|
103, 102, 2, 16, //
|
||||||
|
139, 85, 5, 16, //
|
||||||
|
145, 85, 5, 16, //
|
||||||
|
5, 102, 4, 16, //
|
||||||
|
233, 34, 7, 16, //
|
||||||
|
0, 0, 14, 16, //
|
||||||
|
151, 85, 5, 16, //
|
||||||
|
58, 0, 12, 16, //
|
||||||
|
77, 85, 6, 16, //
|
||||||
|
10, 102, 3, 16, //
|
||||||
|
133, 0, 11, 16, //
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
169, 85, 5, 16, //
|
||||||
|
198, 17, 8, 16, //
|
||||||
|
34, 102, 3, 16, //
|
||||||
|
58, 102, 2, 16, // ¡
|
||||||
|
14, 85, 6, 16, // ¢
|
||||||
|
175, 68, 6, 16, // £
|
||||||
|
225, 17, 8, 16, // ¤
|
||||||
|
234, 17, 8, 16, // ¥
|
||||||
|
73, 102, 2, 16, // ¦
|
||||||
|
203, 68, 6, 16, // §
|
||||||
|
175, 85, 5, 16, // ¨
|
||||||
|
160, 17, 9, 16, // ©
|
||||||
|
181, 85, 5, 16, // ª
|
||||||
|
161, 34, 7, 16, // «
|
||||||
|
217, 68, 6, 16, // ¬
|
||||||
|
54, 102, 3, 16, //
|
||||||
|
232, 0, 9, 16, // ®
|
||||||
|
9, 34, 8, 16, // ¯
|
||||||
|
216, 85, 4, 16, // °
|
||||||
|
56, 85, 6, 16, // ±
|
||||||
|
226, 85, 4, 16, // ²
|
||||||
|
231, 85, 4, 16, // ³
|
||||||
|
252, 0, 3, 16, // ´
|
||||||
|
63, 85, 6, 16, // µ
|
||||||
|
70, 85, 6, 16, // ¶
|
||||||
|
91, 102, 2, 16, // ·
|
||||||
|
14, 102, 3, 16, // ¸
|
||||||
|
18, 102, 3, 16, // ¹
|
||||||
|
236, 85, 4, 16, // º
|
||||||
|
136, 51, 7, 16, // »
|
||||||
|
157, 0, 10, 16, // ¼
|
||||||
|
190, 0, 10, 16, // ½
|
||||||
|
97, 0, 11, 16, // ¾
|
||||||
|
88, 51, 7, 16, // ¿
|
||||||
|
100, 17, 9, 16, // À
|
||||||
|
90, 17, 9, 16, // Á
|
||||||
|
80, 17, 9, 16, // Â
|
||||||
|
70, 17, 9, 16, // Ã
|
||||||
|
60, 17, 9, 16, // Ä
|
||||||
|
110, 17, 9, 16, // Å
|
||||||
|
15, 0, 14, 16, // Æ
|
||||||
|
207, 17, 8, 16, // Ç
|
||||||
|
96, 51, 7, 16, // È
|
||||||
|
104, 51, 7, 16, // É
|
||||||
|
112, 51, 7, 16, // Ê
|
||||||
|
120, 51, 7, 16, // Ë
|
||||||
|
42, 102, 3, 16, // Ì
|
||||||
|
46, 102, 3, 16, // Í
|
||||||
|
157, 85, 5, 16, // Î
|
||||||
|
163, 85, 5, 16, // Ï
|
||||||
|
50, 17, 9, 16, // Ð
|
||||||
|
0, 34, 8, 16, // Ñ
|
||||||
|
40, 17, 9, 16, // Ò
|
||||||
|
30, 17, 9, 16, // Ó
|
||||||
|
20, 17, 9, 16, // Ô
|
||||||
|
10, 17, 9, 16, // Õ
|
||||||
|
0, 17, 9, 16, // Ö
|
||||||
|
128, 51, 7, 16, // ×
|
||||||
|
242, 0, 9, 16, // Ø
|
||||||
|
99, 34, 8, 16, // Ù
|
||||||
|
180, 17, 8, 16, // Ú
|
||||||
|
108, 34, 8, 16, // Û
|
||||||
|
117, 34, 8, 16, // Ü
|
||||||
|
126, 34, 8, 16, // Ý
|
||||||
|
160, 51, 7, 16, // Þ
|
||||||
|
147, 68, 6, 16, // ß
|
||||||
|
168, 51, 7, 16, // à
|
||||||
|
176, 51, 7, 16, // á
|
||||||
|
184, 51, 7, 16, // â
|
||||||
|
192, 51, 7, 16, // ã
|
||||||
|
208, 51, 7, 16, // ä
|
||||||
|
200, 51, 7, 16, // å
|
||||||
|
109, 0, 11, 16, // æ
|
||||||
|
196, 68, 6, 16, // ç
|
||||||
|
16, 51, 7, 16, // è
|
||||||
|
216, 51, 7, 16, // é
|
||||||
|
224, 51, 7, 16, // ê
|
||||||
|
232, 51, 7, 16, // ë
|
||||||
|
22, 102, 3, 16, // ì
|
||||||
|
26, 102, 3, 16, // í
|
||||||
|
103, 85, 5, 16, // î
|
||||||
|
109, 85, 5, 16, // ï
|
||||||
|
240, 51, 7, 16, // ð
|
||||||
|
248, 51, 7, 16, // ñ
|
||||||
|
0, 68, 7, 16, // ò
|
||||||
|
8, 68, 7, 16, // ó
|
||||||
|
16, 68, 7, 16, // ô
|
||||||
|
24, 68, 7, 16, // õ
|
||||||
|
32, 68, 7, 16, // ö
|
||||||
|
144, 34, 8, 16, // ÷
|
||||||
|
48, 68, 7, 16, // ø
|
||||||
|
56, 68, 7, 16, // ù
|
||||||
|
64, 68, 7, 16, // ú
|
||||||
|
72, 68, 7, 16, // û
|
||||||
|
80, 68, 7, 16, // ü
|
||||||
|
88, 68, 7, 16, // ý
|
||||||
|
96, 68, 7, 16, // þ
|
||||||
|
104, 68, 7, 16, // ÿ
|
||||||
|
40, 68, 7, 16, // ?
|
||||||
|
};
|
1
source/gui/gfx/fontMedium_0.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8
|
BIN
source/gui/gfx/fontMedium_0.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
265
source/gui/gfx/fontSmallUVCoords.h
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
// Generated by bmfont-to-spriteset (https://github.com/cavv-dev/bmfont-to-spriteset)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#define FONTSMALL_BITMAP_WIDTH 256
|
||||||
|
#define FONTSMALL_BITMAP_HEIGHT 256
|
||||||
|
#define FONTSMALL_NUM_IMAGES 256
|
||||||
|
|
||||||
|
const unsigned int fontSmallTexCoords[] = {
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
8, 65, 3, 12, //
|
||||||
|
55, 65, 2, 12, // !
|
||||||
|
195, 39, 5, 12, // "
|
||||||
|
201, 39, 5, 12, // #
|
||||||
|
207, 39, 5, 12, // $
|
||||||
|
147, 0, 8, 12, // %
|
||||||
|
230, 0, 7, 12, // &
|
||||||
|
46, 65, 2, 12, // '
|
||||||
|
236, 52, 3, 12, // (
|
||||||
|
220, 52, 3, 12, // )
|
||||||
|
194, 52, 4, 12, // *
|
||||||
|
6, 52, 5, 12, // +
|
||||||
|
43, 65, 2, 12, // ,
|
||||||
|
252, 26, 3, 12, // -
|
||||||
|
79, 65, 2, 12, // .
|
||||||
|
24, 65, 3, 12, // /
|
||||||
|
36, 52, 5, 12, // 0
|
||||||
|
70, 26, 6, 12, // 1
|
||||||
|
48, 52, 5, 12, // 2
|
||||||
|
66, 52, 5, 12, // 3
|
||||||
|
105, 26, 6, 12, // 4
|
||||||
|
72, 52, 5, 12, // 5
|
||||||
|
78, 52, 5, 12, // 6
|
||||||
|
84, 52, 5, 12, // 7
|
||||||
|
96, 52, 5, 12, // 8
|
||||||
|
102, 52, 5, 12, // 9
|
||||||
|
82, 65, 2, 12, // :
|
||||||
|
67, 65, 2, 12, // ;
|
||||||
|
120, 52, 5, 12, // <
|
||||||
|
132, 52, 5, 12, // =
|
||||||
|
105, 39, 5, 12, // >
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
34, 0, 10, 12, // @
|
||||||
|
96, 13, 7, 12, // A
|
||||||
|
125, 13, 6, 12, // B
|
||||||
|
214, 0, 7, 12, // C
|
||||||
|
132, 13, 6, 12, // D
|
||||||
|
111, 39, 5, 12, // E
|
||||||
|
123, 39, 5, 12, // F
|
||||||
|
24, 13, 7, 12, // G
|
||||||
|
146, 13, 6, 12, // H
|
||||||
|
70, 65, 2, 12, // I
|
||||||
|
141, 39, 5, 12, // J
|
||||||
|
160, 13, 6, 12, // K
|
||||||
|
167, 13, 6, 12, // L
|
||||||
|
98, 0, 9, 12, // M
|
||||||
|
174, 13, 6, 12, // N
|
||||||
|
190, 0, 7, 12, // O
|
||||||
|
181, 13, 6, 12, // P
|
||||||
|
206, 0, 7, 12, // Q
|
||||||
|
244, 13, 6, 12, // R
|
||||||
|
0, 26, 6, 12, // S
|
||||||
|
7, 26, 6, 12, // T
|
||||||
|
28, 26, 6, 12, // U
|
||||||
|
198, 0, 7, 12, // V
|
||||||
|
118, 0, 9, 12, // W
|
||||||
|
42, 26, 6, 12, // X
|
||||||
|
56, 26, 6, 12, // Y
|
||||||
|
216, 13, 6, 12, // Z
|
||||||
|
16, 65, 3, 12, // [
|
||||||
|
4, 65, 3, 12, // Backslash
|
||||||
|
244, 52, 3, 12, // ]
|
||||||
|
159, 39, 5, 12, // ^
|
||||||
|
112, 26, 6, 12, // _
|
||||||
|
240, 52, 3, 12, // `
|
||||||
|
126, 26, 6, 12, // a
|
||||||
|
133, 26, 6, 12, // b
|
||||||
|
171, 39, 5, 12, // c
|
||||||
|
231, 26, 6, 12, // d
|
||||||
|
104, 13, 6, 12, // e
|
||||||
|
199, 52, 4, 12, // f
|
||||||
|
111, 13, 6, 12, // g
|
||||||
|
139, 13, 6, 12, // h
|
||||||
|
91, 65, 2, 12, // i
|
||||||
|
88, 65, 2, 12, // j
|
||||||
|
35, 26, 6, 12, // k
|
||||||
|
64, 65, 2, 12, // l
|
||||||
|
12, 0, 10, 12, // m
|
||||||
|
119, 26, 6, 12, // n
|
||||||
|
140, 26, 6, 12, // o
|
||||||
|
77, 26, 6, 12, // p
|
||||||
|
188, 13, 6, 12, // q
|
||||||
|
174, 52, 4, 12, // r
|
||||||
|
195, 13, 6, 12, // s
|
||||||
|
251, 13, 4, 12, // t
|
||||||
|
209, 13, 6, 12, // u
|
||||||
|
219, 39, 5, 12, // v
|
||||||
|
88, 0, 9, 12, // w
|
||||||
|
231, 39, 5, 12, // x
|
||||||
|
237, 39, 5, 12, // y
|
||||||
|
243, 39, 5, 12, // z
|
||||||
|
144, 52, 4, 12, // {
|
||||||
|
254, 0, 1, 12, // |
|
||||||
|
149, 52, 4, 12, // }
|
||||||
|
12, 52, 5, 12, // ~
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
223, 13, 6, 12, //
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
58, 65, 2, 12, //
|
||||||
|
154, 52, 4, 12, //
|
||||||
|
42, 52, 5, 12, //
|
||||||
|
56, 0, 10, 12, //
|
||||||
|
54, 52, 5, 12, //
|
||||||
|
60, 52, 5, 12, //
|
||||||
|
169, 52, 4, 12, //
|
||||||
|
23, 0, 10, 12, //
|
||||||
|
202, 13, 6, 12, //
|
||||||
|
28, 65, 3, 12, //
|
||||||
|
45, 0, 10, 12, //
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
237, 13, 6, 12, //
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
61, 65, 2, 12, //
|
||||||
|
40, 65, 2, 12, //
|
||||||
|
0, 52, 5, 12, //
|
||||||
|
249, 39, 5, 12, //
|
||||||
|
12, 65, 3, 12, //
|
||||||
|
230, 13, 6, 12, //
|
||||||
|
0, 0, 11, 12, //
|
||||||
|
179, 52, 4, 12, //
|
||||||
|
78, 0, 9, 12, //
|
||||||
|
63, 26, 6, 12, //
|
||||||
|
36, 65, 3, 12, //
|
||||||
|
108, 0, 9, 12, //
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
165, 39, 5, 12, //
|
||||||
|
91, 26, 6, 12, //
|
||||||
|
248, 52, 3, 12, //
|
||||||
|
85, 65, 2, 12, // ¡
|
||||||
|
177, 39, 5, 12, // ¢
|
||||||
|
153, 39, 5, 12, // £
|
||||||
|
147, 39, 5, 12, // ¤
|
||||||
|
153, 13, 6, 12, // ¥
|
||||||
|
94, 65, 1, 12, // ¦
|
||||||
|
108, 52, 5, 12, // §
|
||||||
|
20, 65, 3, 12, // ¨
|
||||||
|
182, 0, 7, 12, // ©
|
||||||
|
189, 52, 4, 12, // ª
|
||||||
|
30, 52, 5, 12, // «
|
||||||
|
24, 52, 5, 12, // ¬
|
||||||
|
204, 52, 3, 12, //
|
||||||
|
88, 13, 7, 12, // ®
|
||||||
|
49, 26, 6, 12, // ¯
|
||||||
|
216, 52, 3, 12, // °
|
||||||
|
225, 39, 5, 12, // ±
|
||||||
|
224, 52, 3, 12, // ²
|
||||||
|
228, 52, 3, 12, // ³
|
||||||
|
232, 52, 3, 12, // ´
|
||||||
|
213, 39, 5, 12, // µ
|
||||||
|
14, 26, 6, 12, // ¶
|
||||||
|
49, 65, 2, 12, // ·
|
||||||
|
52, 65, 2, 12, // ¸
|
||||||
|
252, 52, 3, 12, // ¹
|
||||||
|
0, 65, 3, 12, // º
|
||||||
|
189, 39, 5, 12, // »
|
||||||
|
156, 0, 8, 12, // ¼
|
||||||
|
222, 0, 7, 12, // ½
|
||||||
|
128, 0, 9, 12, // ¾
|
||||||
|
21, 26, 6, 12, // ¿
|
||||||
|
238, 0, 7, 12, // À
|
||||||
|
246, 0, 7, 12, // Á
|
||||||
|
0, 13, 7, 12, // Â
|
||||||
|
8, 13, 7, 12, // Ã
|
||||||
|
16, 13, 7, 12, // Ä
|
||||||
|
174, 0, 7, 12, // Å
|
||||||
|
67, 0, 10, 12, // Æ
|
||||||
|
32, 13, 7, 12, // Ç
|
||||||
|
117, 39, 5, 12, // È
|
||||||
|
114, 52, 5, 12, // É
|
||||||
|
129, 39, 5, 12, // Ê
|
||||||
|
135, 39, 5, 12, // Ë
|
||||||
|
73, 65, 2, 12, // Ì
|
||||||
|
76, 65, 2, 12, // Í
|
||||||
|
159, 52, 4, 12, // Î
|
||||||
|
164, 52, 4, 12, // Ï
|
||||||
|
40, 13, 7, 12, // Ð
|
||||||
|
98, 26, 6, 12, // Ñ
|
||||||
|
48, 13, 7, 12, // Ò
|
||||||
|
56, 13, 7, 12, // Ó
|
||||||
|
64, 13, 7, 12, // Ô
|
||||||
|
72, 13, 7, 12, // Õ
|
||||||
|
80, 13, 7, 12, // Ö
|
||||||
|
183, 39, 5, 12, // ×
|
||||||
|
165, 0, 8, 12, // Ø
|
||||||
|
147, 26, 6, 12, // Ù
|
||||||
|
154, 26, 6, 12, // Ú
|
||||||
|
161, 26, 6, 12, // Û
|
||||||
|
168, 26, 6, 12, // Ü
|
||||||
|
175, 26, 6, 12, // Ý
|
||||||
|
182, 26, 6, 12, // Þ
|
||||||
|
189, 26, 6, 12, // ß
|
||||||
|
196, 26, 6, 12, // à
|
||||||
|
203, 26, 6, 12, // á
|
||||||
|
210, 26, 6, 12, // â
|
||||||
|
217, 26, 6, 12, // ã
|
||||||
|
84, 26, 6, 12, // ä
|
||||||
|
224, 26, 6, 12, // å
|
||||||
|
138, 0, 8, 12, // æ
|
||||||
|
18, 52, 5, 12, // ç
|
||||||
|
238, 26, 6, 12, // è
|
||||||
|
245, 26, 6, 12, // é
|
||||||
|
0, 39, 6, 12, // ê
|
||||||
|
7, 39, 6, 12, // ë
|
||||||
|
208, 52, 3, 12, // ì
|
||||||
|
212, 52, 3, 12, // í
|
||||||
|
184, 52, 4, 12, // î
|
||||||
|
32, 65, 3, 12, // ï
|
||||||
|
14, 39, 6, 12, // ð
|
||||||
|
21, 39, 6, 12, // ñ
|
||||||
|
28, 39, 6, 12, // ò
|
||||||
|
35, 39, 6, 12, // ó
|
||||||
|
42, 39, 6, 12, // ô
|
||||||
|
49, 39, 6, 12, // õ
|
||||||
|
56, 39, 6, 12, // ö
|
||||||
|
90, 52, 5, 12, // ÷
|
||||||
|
63, 39, 6, 12, // ø
|
||||||
|
70, 39, 6, 12, // ù
|
||||||
|
77, 39, 6, 12, // ú
|
||||||
|
84, 39, 6, 12, // û
|
||||||
|
91, 39, 6, 12, // ü
|
||||||
|
126, 52, 5, 12, // ý
|
||||||
|
98, 39, 6, 12, // þ
|
||||||
|
138, 52, 5, 12, // ÿ
|
||||||
|
118, 13, 6, 12, // ?
|
||||||
|
};
|
1
source/gui/gfx/fontSmall_0.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8
|
BIN
source/gui/gfx/fontSmall_0.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
1
source/gui/gfx/shiftKey.grit
Normal file
@ -0,0 +1 @@
|
|||||||
|
-gb -gB8 -gTFF00FF
|
BIN
source/gui/gfx/shiftKey.png
Normal file
After Width: | Height: | Size: 144 B |
309
source/gui/image.c
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
#include "image.h"
|
||||||
|
|
||||||
|
#include <gl2d.h>
|
||||||
|
#include <png.h>
|
||||||
|
|
||||||
|
struct GuiImage {
|
||||||
|
size_t width;
|
||||||
|
size_t height;
|
||||||
|
size_t posX;
|
||||||
|
size_t posY;
|
||||||
|
int scale;
|
||||||
|
GuiImageHAlign hAlign;
|
||||||
|
GuiImageVAlign vAlign;
|
||||||
|
glImage* image;
|
||||||
|
int textureId;
|
||||||
|
int colorTintPalId;
|
||||||
|
};
|
||||||
|
|
||||||
|
void bitmapToRGB5A1(u8* bitmap, size_t width, size_t height, GuiImageTextureType textureType)
|
||||||
|
{
|
||||||
|
size_t pixelCount = width * height;
|
||||||
|
u16* rgb5a1Bitmap = (u16*)bitmap;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < pixelCount; i++) {
|
||||||
|
u8 r = bitmap[i * (textureType == GUI_IMAGE_TEXTURE_TYPE_RGB ? 3 : 4)];
|
||||||
|
u8 g = bitmap[i * (textureType == GUI_IMAGE_TEXTURE_TYPE_RGB ? 3 : 4) + 1];
|
||||||
|
u8 b = bitmap[i * (textureType == GUI_IMAGE_TEXTURE_TYPE_RGB ? 3 : 4) + 2];
|
||||||
|
u8 a = (textureType == GUI_IMAGE_TEXTURE_TYPE_RGB) ? 255 : bitmap[i * 4 + 3]; // Alpha is 255 if format is RGB
|
||||||
|
|
||||||
|
u16 rgb5a1 = 0;
|
||||||
|
rgb5a1 |= (r >> 3) & 0x1F; // 5 bits for red
|
||||||
|
rgb5a1 |= ((g >> 3) & 0x1F) << 5; // 5 bits for green
|
||||||
|
rgb5a1 |= ((b >> 3) & 0x1F) << 10; // 5 bits for blue
|
||||||
|
rgb5a1 |= (a >> 7) << 15; // 1 bit for alpha (most significant bit of alpha)
|
||||||
|
|
||||||
|
rgb5a1Bitmap[i] = rgb5a1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pngFileToBitmap(const char* filePath, u8** bitmap, size_t* width, size_t* height, GuiImageTextureType* textureType)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(filePath, "rb");
|
||||||
|
if (!fp)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||||
|
if (!png) {
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
if (!info) {
|
||||||
|
png_destroy_read_struct(&png, NULL, NULL);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(png))) {
|
||||||
|
png_destroy_read_struct(&png, &info, NULL);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_init_io(png, fp);
|
||||||
|
png_read_info(png, info);
|
||||||
|
|
||||||
|
*width = png_get_image_width(png, info);
|
||||||
|
*height = png_get_image_height(png, info);
|
||||||
|
|
||||||
|
// 1024 is the max texture size supported
|
||||||
|
if (*width > 1024 || *height > 1024) {
|
||||||
|
png_destroy_read_struct(&png, &info, NULL);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_byte color_type = png_get_color_type(png, info);
|
||||||
|
png_byte bit_depth = png_get_bit_depth(png, info);
|
||||||
|
|
||||||
|
// Convert to 8-bit depth if necessary
|
||||||
|
if (bit_depth == 16)
|
||||||
|
png_set_strip_16(png);
|
||||||
|
else if (bit_depth < 8)
|
||||||
|
png_set_packing(png);
|
||||||
|
|
||||||
|
// Convert palette images to RGB
|
||||||
|
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||||
|
png_set_palette_to_rgb(png);
|
||||||
|
|
||||||
|
// Convert grayscale images to RGB
|
||||||
|
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||||
|
png_set_gray_to_rgb(png);
|
||||||
|
|
||||||
|
// Add alpha channel if necessary
|
||||||
|
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
|
||||||
|
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
|
||||||
|
|
||||||
|
// Convert transparency to alpha
|
||||||
|
if (png_get_valid(png, info, PNG_INFO_tRNS))
|
||||||
|
png_set_tRNS_to_alpha(png);
|
||||||
|
|
||||||
|
png_read_update_info(png, info);
|
||||||
|
|
||||||
|
*bitmap = (u8*)malloc(png_get_rowbytes(png, info) * (*height));
|
||||||
|
png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * (*height));
|
||||||
|
for (size_t y = 0; y < *height; y++)
|
||||||
|
row_pointers[y] = *bitmap + y * png_get_rowbytes(png, info);
|
||||||
|
|
||||||
|
png_read_image(png, row_pointers);
|
||||||
|
|
||||||
|
*textureType = GUI_IMAGE_TEXTURE_TYPE_RGBA;
|
||||||
|
|
||||||
|
bitmapToRGB5A1(*bitmap, *width, *height, *textureType);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
png_destroy_read_struct(&png, &info, NULL);
|
||||||
|
free(row_pointers);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate closest GL_TEXTURE_SIZE_ENUM
|
||||||
|
#define calculateGlTextureSizeEnum(x) \
|
||||||
|
((x) <= 8 ? TEXTURE_SIZE_8 \
|
||||||
|
: (x) <= 16 ? TEXTURE_SIZE_16 \
|
||||||
|
: (x) <= 32 ? TEXTURE_SIZE_32 \
|
||||||
|
: (x) <= 64 ? TEXTURE_SIZE_64 \
|
||||||
|
: (x) <= 128 ? TEXTURE_SIZE_128 \
|
||||||
|
: (x) <= 256 ? TEXTURE_SIZE_256 \
|
||||||
|
: (x) <= 512 ? TEXTURE_SIZE_512 \
|
||||||
|
: (x) <= 1024 ? TEXTURE_SIZE_1024 \
|
||||||
|
: 0)
|
||||||
|
|
||||||
|
GuiImage newGuiImage(const unsigned* bitmap, const u16* pal, size_t width, size_t height, size_t bitmapWidth, size_t bitmapHeight, size_t resizeWidth, size_t resizeHeight, GuiImageTextureType textureType)
|
||||||
|
{
|
||||||
|
GuiImage gi = malloc(sizeof(struct GuiImage));
|
||||||
|
gi->width = width;
|
||||||
|
gi->height = height;
|
||||||
|
gi->scale = 1 << 12;
|
||||||
|
gi->hAlign = GUI_IMAGE_H_ALIGN_LEFT;
|
||||||
|
gi->vAlign = GUI_IMAGE_V_ALIGN_TOP;
|
||||||
|
gi->posX = 0;
|
||||||
|
gi->posY = 0;
|
||||||
|
gi->image = malloc(sizeof(glImage));
|
||||||
|
gi->colorTintPalId = 0;
|
||||||
|
|
||||||
|
size_t glTextureSizeWidth = calculateGlTextureSizeEnum(bitmapWidth);
|
||||||
|
size_t glTextureSizeHeight = calculateGlTextureSizeEnum(bitmapHeight);
|
||||||
|
|
||||||
|
GL_TEXTURE_TYPE_ENUM glTextureType;
|
||||||
|
switch (textureType) {
|
||||||
|
case GUI_IMAGE_TEXTURE_TYPE_RGB:
|
||||||
|
glTextureType = GL_RGB;
|
||||||
|
break;
|
||||||
|
case GUI_IMAGE_TEXTURE_TYPE_RGBA:
|
||||||
|
glTextureType = GL_RGBA;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
glTextureType = GL_RGB256;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
gi->textureId = glLoadTileSet(
|
||||||
|
gi->image,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bitmapWidth,
|
||||||
|
bitmapHeight,
|
||||||
|
glTextureType,
|
||||||
|
glTextureSizeWidth,
|
||||||
|
glTextureSizeHeight,
|
||||||
|
TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT,
|
||||||
|
(textureType == GUI_IMAGE_TEXTURE_TYPE_RGB256 ? 256 : 0),
|
||||||
|
pal,
|
||||||
|
(u8*)bitmap);
|
||||||
|
|
||||||
|
// Set scale to fit inside resized width and height
|
||||||
|
if (resizeWidth || resizeHeight) {
|
||||||
|
double scaleFactor = 1.0;
|
||||||
|
|
||||||
|
if (resizeWidth && !resizeHeight) {
|
||||||
|
scaleFactor = (double)resizeWidth / width;
|
||||||
|
gi->width = resizeWidth;
|
||||||
|
gi->height *= scaleFactor;
|
||||||
|
} else if (!resizeWidth && resizeHeight) {
|
||||||
|
scaleFactor = (double)resizeHeight / height;
|
||||||
|
gi->height = resizeHeight;
|
||||||
|
gi->width *= scaleFactor;
|
||||||
|
} else {
|
||||||
|
if (resizeWidth <= resizeHeight) {
|
||||||
|
scaleFactor = (double)resizeWidth / width;
|
||||||
|
gi->width = resizeWidth;
|
||||||
|
gi->height *= scaleFactor;
|
||||||
|
} else {
|
||||||
|
scaleFactor = (double)resizeHeight / height;
|
||||||
|
gi->height = resizeHeight;
|
||||||
|
gi->width *= scaleFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gi->scale *= scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gi;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiImage newGuiImagePNG(const char* filePath, size_t resizeWidth, size_t resizeHeight)
|
||||||
|
{
|
||||||
|
unsigned* bitmap = NULL;
|
||||||
|
GuiImageTextureType textureType;
|
||||||
|
size_t width, height;
|
||||||
|
|
||||||
|
if (!pngFileToBitmap(filePath, (u8**)&bitmap, &width, &height, &textureType))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
GuiImage gi = newGuiImage(bitmap, NULL, width, height, width, height, resizeWidth, resizeHeight, textureType);
|
||||||
|
free(bitmap);
|
||||||
|
|
||||||
|
return gi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeGuiImage(GuiImage gi)
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &gi->textureId);
|
||||||
|
|
||||||
|
if (gi->colorTintPalId)
|
||||||
|
glDeleteTextures(1, &gi->colorTintPalId);
|
||||||
|
|
||||||
|
free(gi->image);
|
||||||
|
free(gi);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiImagePos(GuiImage gi, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
gi->posX = posX;
|
||||||
|
gi->posY = posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiImageAlign(GuiImage gi, GuiImageHAlign hAlignment, GuiImageVAlign vAlignment)
|
||||||
|
{
|
||||||
|
gi->hAlign = hAlignment;
|
||||||
|
gi->vAlign = vAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiImageColorTint(GuiImage gi, u16 color)
|
||||||
|
{
|
||||||
|
u16 colorTintPal[256];
|
||||||
|
for (u16 i = 0; i < 256; i++)
|
||||||
|
colorTintPal[i] = color;
|
||||||
|
|
||||||
|
glGenTextures(1, &gi->colorTintPalId);
|
||||||
|
glBindTexture(0, gi->colorTintPalId);
|
||||||
|
glColorTableEXT(0, 0, 256, 0, 0, colorTintPal);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getGuiImageWidth(GuiImage gi)
|
||||||
|
{
|
||||||
|
return gi->width;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getGuiImageHeight(GuiImage gi)
|
||||||
|
{
|
||||||
|
return gi->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiImagePos(GuiImage gi, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
size_t x;
|
||||||
|
size_t y;
|
||||||
|
|
||||||
|
switch (gi->hAlign) {
|
||||||
|
case GUI_IMAGE_H_ALIGN_CENTER:
|
||||||
|
x = posX - gi->width / 2;
|
||||||
|
break;
|
||||||
|
case GUI_IMAGE_H_ALIGN_RIGHT:
|
||||||
|
x = posX - gi->width;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
x = posX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (gi->vAlign) {
|
||||||
|
case GUI_IMAGE_V_ALIGN_MIDDLE:
|
||||||
|
y = posY - gi->height / 2;
|
||||||
|
break;
|
||||||
|
case GUI_IMAGE_V_ALIGN_BOTTOM:
|
||||||
|
y = posY + gi->height;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
y = posY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
glSetActiveTexture(gi->textureId);
|
||||||
|
|
||||||
|
if (gi->colorTintPalId)
|
||||||
|
glAssignColorTable(0, gi->colorTintPalId);
|
||||||
|
|
||||||
|
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(1));
|
||||||
|
|
||||||
|
glSpriteScale(x, y, gi->scale, GL_FLIP_NONE, gi->image);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiImage(GuiImage gi)
|
||||||
|
{
|
||||||
|
drawGuiImagePos(gi, gi->posX, gi->posY);
|
||||||
|
}
|
33
source/gui/image.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <calico/types.h>
|
||||||
|
|
||||||
|
typedef struct GuiImage* GuiImage;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_IMAGE_TEXTURE_TYPE_RGB,
|
||||||
|
GUI_IMAGE_TEXTURE_TYPE_RGBA,
|
||||||
|
GUI_IMAGE_TEXTURE_TYPE_RGB256
|
||||||
|
} GuiImageTextureType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_IMAGE_H_ALIGN_LEFT,
|
||||||
|
GUI_IMAGE_H_ALIGN_CENTER,
|
||||||
|
GUI_IMAGE_H_ALIGN_RIGHT
|
||||||
|
} GuiImageHAlign;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_IMAGE_V_ALIGN_TOP,
|
||||||
|
GUI_IMAGE_V_ALIGN_MIDDLE,
|
||||||
|
GUI_IMAGE_V_ALIGN_BOTTOM
|
||||||
|
} GuiImageVAlign;
|
||||||
|
|
||||||
|
GuiImage newGuiImage(const unsigned* bitmap, const u16* pal, size_t width, size_t height, size_t bitmapWidth, size_t bitmapHeight, size_t resizeWidth, size_t resizeHeight, GuiImageTextureType textureType);
|
||||||
|
GuiImage newGuiImagePNG(const char* filePath, size_t resizeWidth, size_t resizeHeight);
|
||||||
|
void freeGuiImage(GuiImage);
|
||||||
|
void setGuiImagePos(GuiImage, size_t posX, size_t posY);
|
||||||
|
void setGuiImageAlign(GuiImage, GuiImageHAlign, GuiImageVAlign);
|
||||||
|
void setGuiImageColorTint(GuiImage, u16 color);
|
||||||
|
size_t getGuiImageWidth(GuiImage);
|
||||||
|
size_t getGuiImageHeight(GuiImage);
|
||||||
|
void drawGuiImagePos(GuiImage, size_t posX, size_t posY);
|
||||||
|
void drawGuiImage(GuiImage);
|
12
source/gui/input.c
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include "input.h"
|
||||||
|
|
||||||
|
u32 pressed;
|
||||||
|
touchPosition touch;
|
||||||
|
|
||||||
|
void updateInput(void)
|
||||||
|
{
|
||||||
|
scanKeys();
|
||||||
|
pressed = keysDown();
|
||||||
|
|
||||||
|
touchRead(&touch);
|
||||||
|
}
|
7
source/gui/input.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <nds/arm9/input.h>
|
||||||
|
|
||||||
|
extern u32 pressed;
|
||||||
|
extern touchPosition touch;
|
||||||
|
|
||||||
|
void updateInput(void);
|
156
source/gui/keyboard.c
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
#include "keyboard.h"
|
||||||
|
|
||||||
|
#include "backspaceKey.h"
|
||||||
|
#include "shiftKey.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
struct GuiKeyboard {
|
||||||
|
size_t posX;
|
||||||
|
size_t posY;
|
||||||
|
struct GuiKeyboardKey* keys;
|
||||||
|
bool shiftPressed;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define KEYS_COUNT 43
|
||||||
|
|
||||||
|
void setKeys(GuiKeyboard gk)
|
||||||
|
{
|
||||||
|
const char* chars;
|
||||||
|
|
||||||
|
if (!gk->shiftPressed)
|
||||||
|
chars = "1234567890\0qwertyuiop@asdfghjkl\0zxcvbnm-. \0";
|
||||||
|
else
|
||||||
|
chars = "1234567890\0QWERTYUIOP+ASDFGHJKL\0ZXCVBNM_/ \0";
|
||||||
|
|
||||||
|
for (size_t i = 0; i < KEYS_COUNT; i++) {
|
||||||
|
gk->keys[i].c = chars[i];
|
||||||
|
gk->keys[i].extraKey = GUI_KEYBOARD_EXTRA_KEY_NONE;
|
||||||
|
|
||||||
|
char text[2] = { chars[i], '\0' };
|
||||||
|
setGuiTextText(gk->keys[i].label, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
gk->keys[10].extraKey = GUI_KEYBOARD_EXTRA_KEY_BACKSPACE;
|
||||||
|
gk->keys[31].extraKey = GUI_KEYBOARD_EXTRA_KEY_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setKeysPos(GuiKeyboard gk)
|
||||||
|
{
|
||||||
|
// First row
|
||||||
|
for (size_t i = 0; i < 11; i++)
|
||||||
|
setGuiButtonPos(gk->keys[i].btn, gk->posX + i * 23, gk->posY);
|
||||||
|
|
||||||
|
// Second row
|
||||||
|
for (size_t i = 11; i < 22; i++)
|
||||||
|
setGuiButtonPos(gk->keys[i].btn, gk->posX + (i - 11) * 23 + 1, gk->posY + 23);
|
||||||
|
|
||||||
|
// Third row
|
||||||
|
for (size_t i = 22; i < 32; i++)
|
||||||
|
setGuiButtonPos(gk->keys[i].btn, gk->posX + (i - 22) * 23 + 11, gk->posY + 23 * 2);
|
||||||
|
|
||||||
|
// Fourth row
|
||||||
|
for (size_t i = 32; i < 41; i++)
|
||||||
|
setGuiButtonPos(gk->keys[i].btn, gk->posX + (i - 32) * 23 + 22, gk->posY + 23 * 3);
|
||||||
|
|
||||||
|
// Space
|
||||||
|
setGuiButtonPos(gk->keys[41].btn, gk->posX + 70, gk->posY + 23 * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setKeysShift(GuiKeyboard gk)
|
||||||
|
{
|
||||||
|
gk->shiftPressed = (gk->shiftPressed ? false : true);
|
||||||
|
setKeys(gk);
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiKeyboard newGuiKeyboard(u16 textColor, u16 hoverColor)
|
||||||
|
{
|
||||||
|
GuiKeyboard gk = malloc(sizeof(struct GuiKeyboard));
|
||||||
|
gk->posX = 0;
|
||||||
|
gk->posY = 0;
|
||||||
|
gk->keys = malloc(sizeof(struct GuiKeyboardKey) * KEYS_COUNT);
|
||||||
|
gk->shiftPressed = false;
|
||||||
|
|
||||||
|
// All keys
|
||||||
|
for (size_t i = 0; i < KEYS_COUNT; i++) {
|
||||||
|
gk->keys[i].bg = newGuiBox(23, 23, 0);
|
||||||
|
|
||||||
|
gk->keys[i].bgHover = newGuiBox(23, 23, 0);
|
||||||
|
setGuiBoxBorder(gk->keys[i].bgHover, 1, hoverColor);
|
||||||
|
|
||||||
|
gk->keys[i].label = newGuiText("", GUI_TEXT_SIZE_MEDIUM, textColor);
|
||||||
|
setGuiTextAlignment(gk->keys[i].label, GUI_TEXT_H_ALIGN_CENTER, GUI_TEXT_V_ALIGN_MIDDLE);
|
||||||
|
|
||||||
|
gk->keys[i].btn = newGuiButton(23, 23);
|
||||||
|
setGuiButtonBg(gk->keys[i].btn, gk->keys[i].bg, gk->keys[i].bgHover);
|
||||||
|
setGuiButtonLabel(gk->keys[i].btn, gk->keys[i].label);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Space
|
||||||
|
setGuiBoxWidth(gk->keys[41].bg, 115);
|
||||||
|
setGuiBoxBorder(gk->keys[41].bg, 1, textColor);
|
||||||
|
setGuiBoxWidth(gk->keys[41].bgHover, 115);
|
||||||
|
setGuiButtonWidth(gk->keys[41].btn, 115);
|
||||||
|
|
||||||
|
// Backspace
|
||||||
|
gk->keys[10].icon = newGuiImage(backspaceKeyBitmap, backspaceKeyPal, 15, 11, 16, 16, 0, 0, GUI_IMAGE_TEXTURE_TYPE_RGB256);
|
||||||
|
setGuiImageColorTint(gk->keys[10].icon, textColor);
|
||||||
|
setGuiButtonIcon(gk->keys[10].btn, gk->keys[10].icon, gk->keys[10].icon);
|
||||||
|
|
||||||
|
// Shift
|
||||||
|
gk->keys[31].icon = newGuiImage(shiftKeyBitmap, shiftKeyPal, 11, 13, 16, 16, 0, 0, GUI_IMAGE_TEXTURE_TYPE_RGB256);
|
||||||
|
setGuiImageColorTint(gk->keys[31].icon, textColor);
|
||||||
|
setGuiButtonIcon(gk->keys[31].btn, gk->keys[31].icon, gk->keys[31].icon);
|
||||||
|
|
||||||
|
setKeysPos(gk);
|
||||||
|
|
||||||
|
setKeys(gk);
|
||||||
|
|
||||||
|
return gk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeGuiKeyboard(GuiKeyboard gk)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < KEYS_COUNT; i++) {
|
||||||
|
freeGuiBox(gk->keys[i].bg);
|
||||||
|
freeGuiBox(gk->keys[i].bgHover);
|
||||||
|
freeGuiText(gk->keys[i].label);
|
||||||
|
freeGuiButton(gk->keys[i].btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
freeGuiImage(gk->keys[10].icon); // Backspace
|
||||||
|
freeGuiImage(gk->keys[31].icon); // Shift
|
||||||
|
|
||||||
|
free(gk->keys);
|
||||||
|
free(gk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiKeyboardPos(GuiKeyboard gk, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
gk->posX = posX;
|
||||||
|
gk->posY = posY;
|
||||||
|
setKeysPos(gk);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GuiKeyboardKey getGuiKeyboardPressed(GuiKeyboard gk)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < KEYS_COUNT; i++) {
|
||||||
|
if (getGuiButtonState(gk->keys[i].btn) != GUI_BUTTON_STATE_CLICKED)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i == 31) // Shift
|
||||||
|
setKeysShift(gk);
|
||||||
|
|
||||||
|
resetGuiButtonState(gk->keys[i].btn);
|
||||||
|
return gk->keys[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return gk->keys[KEYS_COUNT - 1]; // None key
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiKeyboard(GuiKeyboard gk)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < KEYS_COUNT; i++) {
|
||||||
|
handleTouchGuiButton(gk->keys[i].btn);
|
||||||
|
drawGuiButton(gk->keys[i].btn);
|
||||||
|
}
|
||||||
|
}
|
26
source/gui/keyboard.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "button.h"
|
||||||
|
|
||||||
|
typedef struct GuiKeyboard* GuiKeyboard;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_KEYBOARD_EXTRA_KEY_NONE,
|
||||||
|
GUI_KEYBOARD_EXTRA_KEY_BACKSPACE,
|
||||||
|
GUI_KEYBOARD_EXTRA_KEY_SHIFT
|
||||||
|
} GuiKeyboardExtraKey;
|
||||||
|
|
||||||
|
struct GuiKeyboardKey {
|
||||||
|
GuiBox bg;
|
||||||
|
GuiBox bgHover;
|
||||||
|
GuiText label;
|
||||||
|
GuiButton btn;
|
||||||
|
GuiImage icon;
|
||||||
|
char c;
|
||||||
|
GuiKeyboardExtraKey extraKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
GuiKeyboard newGuiKeyboard(u16 textColor, u16 hoverColor);
|
||||||
|
void freeGuiKeyboard(GuiKeyboard);
|
||||||
|
void setGuiKeyboardPos(GuiKeyboard, size_t posX, size_t posY);
|
||||||
|
struct GuiKeyboardKey getGuiKeyboardPressed(GuiKeyboard);
|
||||||
|
void drawGuiKeyboard(GuiKeyboard);
|
67
source/gui/progressbar.c
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "progressbar.h"
|
||||||
|
|
||||||
|
#include <gl2d.h>
|
||||||
|
|
||||||
|
struct GuiProgressBar {
|
||||||
|
size_t width;
|
||||||
|
size_t height;
|
||||||
|
size_t posX;
|
||||||
|
size_t posY;
|
||||||
|
u16 bgColor;
|
||||||
|
u16 progressColor;
|
||||||
|
u8 percent;
|
||||||
|
};
|
||||||
|
|
||||||
|
GuiProgressbar newGuiProgressbar(size_t width, size_t height, u16 bgColor, u16 progressColor)
|
||||||
|
{
|
||||||
|
GuiProgressbar gp = malloc(sizeof(struct GuiProgressBar));
|
||||||
|
gp->width = width;
|
||||||
|
gp->height = height;
|
||||||
|
gp->posX = 0;
|
||||||
|
gp->posY = 0;
|
||||||
|
gp->bgColor = bgColor;
|
||||||
|
gp->progressColor = progressColor;
|
||||||
|
gp->percent = 0;
|
||||||
|
|
||||||
|
return gp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeGuiProgressbar(GuiProgressbar gp)
|
||||||
|
{
|
||||||
|
free(gp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiProgressbarPos(GuiProgressbar gp, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
gp->posX = posX;
|
||||||
|
gp->posY = posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiProgressbarPercent(GuiProgressbar gp, u8 percent)
|
||||||
|
{
|
||||||
|
gp->percent = percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiProgressbarPos(GuiProgressbar gp, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
glBoxFilled(
|
||||||
|
posX,
|
||||||
|
posY,
|
||||||
|
posX + gp->width - 1,
|
||||||
|
posY + gp->height - 1,
|
||||||
|
gp->bgColor);
|
||||||
|
|
||||||
|
if (gp->percent) {
|
||||||
|
glBoxFilled(
|
||||||
|
posX,
|
||||||
|
posY,
|
||||||
|
posX + (gp->width * gp->percent / 100) - 1,
|
||||||
|
posY + gp->height - 1,
|
||||||
|
gp->progressColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiProgressbar(GuiProgressbar gp)
|
||||||
|
{
|
||||||
|
drawGuiProgressbarPos(gp, gp->posX, gp->posY);
|
||||||
|
}
|
11
source/gui/progressbar.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <calico/types.h>
|
||||||
|
|
||||||
|
typedef struct GuiProgressBar* GuiProgressbar;
|
||||||
|
|
||||||
|
GuiProgressbar newGuiProgressbar(size_t width, size_t height, u16 bgColor, u16 progressColor);
|
||||||
|
void freeGuiProgressbar(GuiProgressbar);
|
||||||
|
void setGuiProgressbarPos(GuiProgressbar, size_t posX, size_t posY);
|
||||||
|
void setGuiProgressbarPercent(GuiProgressbar, u8);
|
||||||
|
void drawGuiProgressbarPos(GuiProgressbar, size_t posX, size_t posY);
|
||||||
|
void drawGuiProgressbar(GuiProgressbar);
|
318
source/gui/screen.c
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
#include "screen.h"
|
||||||
|
|
||||||
|
#include "button.h"
|
||||||
|
#include "input.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "progressbar.h"
|
||||||
|
#include <gl2d.h>
|
||||||
|
#include <nds.h>
|
||||||
|
|
||||||
|
struct ElementNode {
|
||||||
|
void* value;
|
||||||
|
GuiElementType type;
|
||||||
|
struct ElementNode* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ElementList {
|
||||||
|
struct ElementNode* head;
|
||||||
|
struct ElementNode* tail;
|
||||||
|
struct ElementNode* selected;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GuiScreen {
|
||||||
|
GuiScreenLcd lcd;
|
||||||
|
bool dpadNavigate;
|
||||||
|
struct ElementList* elements;
|
||||||
|
};
|
||||||
|
|
||||||
|
GuiScreen activeTopScreen = NULL;
|
||||||
|
GuiScreen activeBottomScreen = NULL;
|
||||||
|
|
||||||
|
GuiScreenLcd targetLcd = GUI_SCREEN_LCD_TOP;
|
||||||
|
|
||||||
|
GuiScreen newGuiScreen(GuiScreenLcd lcd)
|
||||||
|
{
|
||||||
|
GuiScreen gs = malloc(sizeof(struct GuiScreen));
|
||||||
|
gs->lcd = lcd;
|
||||||
|
gs->dpadNavigate = true;
|
||||||
|
|
||||||
|
struct ElementList* el = malloc(sizeof(struct ElementList));
|
||||||
|
el->head = NULL;
|
||||||
|
el->tail = NULL;
|
||||||
|
el->selected = NULL;
|
||||||
|
el->size = 0;
|
||||||
|
gs->elements = el;
|
||||||
|
|
||||||
|
return gs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeGuiScreen(GuiScreen gs)
|
||||||
|
{
|
||||||
|
struct ElementNode* curr = gs->elements->head;
|
||||||
|
while (curr != NULL) {
|
||||||
|
struct ElementNode* next = curr->next;
|
||||||
|
free(curr);
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(gs->elements);
|
||||||
|
free(gs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiScreenDpadNavigate(GuiScreen gs, bool dpadNavigate)
|
||||||
|
{
|
||||||
|
gs->dpadNavigate = dpadNavigate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToGuiScreen(GuiScreen gs, void* element, GuiElementType type)
|
||||||
|
{
|
||||||
|
struct ElementNode* en = malloc(sizeof(struct ElementNode));
|
||||||
|
en->value = element;
|
||||||
|
en->type = type;
|
||||||
|
en->next = NULL;
|
||||||
|
|
||||||
|
if (gs->elements->head == NULL) {
|
||||||
|
gs->elements->head = en;
|
||||||
|
gs->elements->tail = en;
|
||||||
|
} else {
|
||||||
|
gs->elements->tail->next = en;
|
||||||
|
gs->elements->tail = en;
|
||||||
|
}
|
||||||
|
|
||||||
|
gs->elements->size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFromGuiScreen(GuiScreen gs, void* element)
|
||||||
|
{
|
||||||
|
struct ElementNode* prev = NULL;
|
||||||
|
struct ElementNode* curr = gs->elements->head;
|
||||||
|
|
||||||
|
while (curr != NULL) {
|
||||||
|
if (curr->value == element) {
|
||||||
|
if (prev == NULL)
|
||||||
|
gs->elements->head = curr->next;
|
||||||
|
else
|
||||||
|
prev->next = curr->next;
|
||||||
|
|
||||||
|
if (curr == gs->elements->tail)
|
||||||
|
gs->elements->tail = prev;
|
||||||
|
|
||||||
|
free(curr);
|
||||||
|
gs->elements->size--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = curr;
|
||||||
|
curr = curr->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiScreen(GuiScreen gs)
|
||||||
|
{
|
||||||
|
glBegin2D();
|
||||||
|
|
||||||
|
struct ElementNode* curr;
|
||||||
|
curr = gs->elements->head;
|
||||||
|
while (curr != NULL) {
|
||||||
|
switch (curr->type) {
|
||||||
|
case GUI_ELEMENT_TYPE_BOX:
|
||||||
|
drawGuiBox(curr->value);
|
||||||
|
break;
|
||||||
|
case GUI_ELEMENT_TYPE_IMAGE:
|
||||||
|
drawGuiImage(curr->value);
|
||||||
|
break;
|
||||||
|
case GUI_ELEMENT_TYPE_BUTTON:
|
||||||
|
if (gs->lcd == GUI_SCREEN_LCD_BOTTOM)
|
||||||
|
handleTouchGuiButton(curr->value);
|
||||||
|
|
||||||
|
drawGuiButton(curr->value);
|
||||||
|
break;
|
||||||
|
case GUI_ELEMENT_TYPE_TEXT:
|
||||||
|
drawGuiText(curr->value);
|
||||||
|
break;
|
||||||
|
case GUI_ELEMENT_TYPE_PROGRESSBAR:
|
||||||
|
drawGuiProgressbar(curr->value);
|
||||||
|
break;
|
||||||
|
case GUI_ELEMENT_TYPE_KEYBOARD:
|
||||||
|
drawGuiKeyboard(curr->value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
curr = curr->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
glEnd2D();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setActiveScreens(GuiScreen topScreen, GuiScreen bottomScreen)
|
||||||
|
{
|
||||||
|
activeTopScreen = topScreen;
|
||||||
|
activeBottomScreen = bottomScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiScreen getActiveTopScreen(void)
|
||||||
|
{
|
||||||
|
return activeTopScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiScreen getActiveBottomScreen(void)
|
||||||
|
{
|
||||||
|
return activeBottomScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleScreensDpadNavigate(void)
|
||||||
|
{
|
||||||
|
if ((!activeTopScreen || !activeTopScreen->dpadNavigate) && (!activeBottomScreen || !activeBottomScreen->dpadNavigate))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!(pressed & (KEY_DOWN | KEY_UP | KEY_LEFT | KEY_RIGHT | KEY_A)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
GuiButton selected = NULL;
|
||||||
|
size_t topScreenSize = activeTopScreen ? activeTopScreen->elements->size : 0;
|
||||||
|
size_t bottomScreenSize = activeBottomScreen ? activeBottomScreen->elements->size : 0;
|
||||||
|
GuiButton buttons[topScreenSize + bottomScreenSize];
|
||||||
|
size_t buttonsCount = 0;
|
||||||
|
|
||||||
|
struct ElementNode* curr;
|
||||||
|
|
||||||
|
// Collect buttons from the top screen
|
||||||
|
if (activeTopScreen && activeTopScreen->dpadNavigate) {
|
||||||
|
curr = activeTopScreen->elements->head;
|
||||||
|
for (; curr != NULL; curr = curr->next) {
|
||||||
|
if (curr->type != GUI_ELEMENT_TYPE_BUTTON)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (getGuiButtonState(curr->value) == GUI_BUTTON_STATE_SELECTED)
|
||||||
|
selected = curr->value;
|
||||||
|
|
||||||
|
buttons[buttonsCount++] = curr->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect buttons from the bottom screen
|
||||||
|
if (activeBottomScreen && activeBottomScreen->dpadNavigate) {
|
||||||
|
curr = activeBottomScreen->elements->head;
|
||||||
|
for (; curr != NULL; curr = curr->next) {
|
||||||
|
if (curr->type != GUI_ELEMENT_TYPE_BUTTON)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (getGuiButtonState(curr->value) == GUI_BUTTON_STATE_SELECTED)
|
||||||
|
selected = curr->value;
|
||||||
|
|
||||||
|
buttons[buttonsCount++] = curr->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonsCount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Find the highest button and select it if no button is selected
|
||||||
|
if (!selected) {
|
||||||
|
selected = buttons[0];
|
||||||
|
for (size_t i = 1; i < buttonsCount; i++) {
|
||||||
|
size_t selectedPosY = getGuiButtonPosY(selected) + (i < topScreenSize ? 192 : 0);
|
||||||
|
size_t buttonPosY = getGuiButtonPosY(buttons[i]) + (i < topScreenSize ? 192 : 0);
|
||||||
|
if (buttonPosY < selectedPosY)
|
||||||
|
selected = buttons[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
setGuiButtonState(selected, GUI_BUTTON_STATE_SELECTED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle button click
|
||||||
|
if (pressed & KEY_A) {
|
||||||
|
if (selected)
|
||||||
|
setGuiButtonState(selected, GUI_BUTTON_STATE_CLICKED);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the closest button according to the pressed key. Account for both screens
|
||||||
|
GuiButton closest = NULL;
|
||||||
|
for (size_t i = 0; i < buttonsCount; i++) {
|
||||||
|
size_t selectedPosY = getGuiButtonPosY(selected) + (i < topScreenSize ? 192 : 0);
|
||||||
|
size_t buttonPosY = getGuiButtonPosY(buttons[i]) + (i < topScreenSize ? 192 : 0);
|
||||||
|
size_t selectedPosX = getGuiButtonPosX(selected);
|
||||||
|
size_t buttonPosX = getGuiButtonPosX(buttons[i]);
|
||||||
|
|
||||||
|
if (pressed & KEY_DOWN) {
|
||||||
|
if (buttonPosY <= selectedPosY)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!closest || buttonPosY < getGuiButtonPosY(closest) + (i < topScreenSize ? 192 : 0))
|
||||||
|
closest = buttons[i];
|
||||||
|
} else if (pressed & KEY_UP) {
|
||||||
|
if (buttonPosY >= selectedPosY)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!closest || buttonPosY > getGuiButtonPosY(closest) + (i < topScreenSize ? 192 : 0))
|
||||||
|
closest = buttons[i];
|
||||||
|
} else if (pressed & KEY_LEFT) {
|
||||||
|
if (buttonPosX >= selectedPosX)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!closest || buttonPosX > getGuiButtonPosX(closest))
|
||||||
|
closest = buttons[i];
|
||||||
|
} else if (pressed & KEY_RIGHT) {
|
||||||
|
if (buttonPosX <= selectedPosX)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!closest || buttonPosX < getGuiButtonPosX(closest))
|
||||||
|
closest = buttons[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update button states
|
||||||
|
if (closest) {
|
||||||
|
setGuiButtonState(selected, GUI_BUTTON_STATE_DEFAULT);
|
||||||
|
setGuiButtonState(closest, GUI_BUTTON_STATE_SELECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawScreens(void)
|
||||||
|
{
|
||||||
|
updateInput();
|
||||||
|
|
||||||
|
handleScreensDpadNavigate();
|
||||||
|
|
||||||
|
// Wait for capture unit to be ready
|
||||||
|
while (REG_DISPCAPCNT & DCAP_ENABLE) { };
|
||||||
|
|
||||||
|
if (activeTopScreen && activeBottomScreen) {
|
||||||
|
if (targetLcd == GUI_SCREEN_LCD_TOP) {
|
||||||
|
lcdMainOnBottom();
|
||||||
|
vramSetBankC(VRAM_C_LCD);
|
||||||
|
vramSetBankD(VRAM_D_SUB_SPRITE);
|
||||||
|
REG_DISPCAPCNT = DCAP_BANK(2) | DCAP_ENABLE | DCAP_SIZE(3);
|
||||||
|
|
||||||
|
drawGuiScreen(activeTopScreen);
|
||||||
|
} else {
|
||||||
|
lcdMainOnTop();
|
||||||
|
vramSetBankD(VRAM_D_LCD);
|
||||||
|
vramSetBankC(VRAM_C_SUB_BG);
|
||||||
|
REG_DISPCAPCNT = DCAP_BANK(3) | DCAP_ENABLE | DCAP_SIZE(3);
|
||||||
|
|
||||||
|
drawGuiScreen(activeBottomScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap target lcd
|
||||||
|
targetLcd = targetLcd == GUI_SCREEN_LCD_TOP ? GUI_SCREEN_LCD_BOTTOM : GUI_SCREEN_LCD_TOP;
|
||||||
|
} else if (activeTopScreen) {
|
||||||
|
lcdMainOnTop();
|
||||||
|
vramSetBankD(VRAM_D_LCD);
|
||||||
|
vramSetBankC(VRAM_C_SUB_BG);
|
||||||
|
REG_DISPCAPCNT = DCAP_BANK(3) | DCAP_ENABLE | DCAP_SIZE(3);
|
||||||
|
|
||||||
|
drawGuiScreen(activeTopScreen);
|
||||||
|
} else {
|
||||||
|
lcdMainOnBottom();
|
||||||
|
vramSetBankC(VRAM_C_LCD);
|
||||||
|
vramSetBankD(VRAM_D_SUB_SPRITE);
|
||||||
|
REG_DISPCAPCNT = DCAP_BANK(2) | DCAP_ENABLE | DCAP_SIZE(3);
|
||||||
|
|
||||||
|
drawGuiScreen(activeBottomScreen);
|
||||||
|
}
|
||||||
|
}
|
28
source/gui/screen.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct GuiScreen* GuiScreen;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_SCREEN_LCD_TOP,
|
||||||
|
GUI_SCREEN_LCD_BOTTOM
|
||||||
|
} GuiScreenLcd;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_ELEMENT_TYPE_BOX,
|
||||||
|
GUI_ELEMENT_TYPE_IMAGE,
|
||||||
|
GUI_ELEMENT_TYPE_BUTTON,
|
||||||
|
GUI_ELEMENT_TYPE_TEXT,
|
||||||
|
GUI_ELEMENT_TYPE_PROGRESSBAR,
|
||||||
|
GUI_ELEMENT_TYPE_KEYBOARD
|
||||||
|
} GuiElementType;
|
||||||
|
|
||||||
|
GuiScreen newGuiScreen(GuiScreenLcd);
|
||||||
|
void freeGuiScreen(GuiScreen);
|
||||||
|
void addToGuiScreen(GuiScreen, void* element, GuiElementType);
|
||||||
|
void removeFromGuiScreen(GuiScreen, void* element);
|
||||||
|
void setGuiScreenDpadNavigate(GuiScreen, bool);
|
||||||
|
void setActiveScreens(GuiScreen topScreen, GuiScreen bottomScreen);
|
||||||
|
GuiScreen getActiveTopScreen(void);
|
||||||
|
GuiScreen getActiveBottomScreen(void);
|
||||||
|
void drawScreens(void);
|
377
source/gui/text.c
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
#include "text.h"
|
||||||
|
|
||||||
|
#include "fontBig_0.h"
|
||||||
|
#include "fontMedium_0.h"
|
||||||
|
#include "fontSmall_0.h"
|
||||||
|
#include "gfx/fontBigUVCoords.h"
|
||||||
|
#include "gfx/fontMediumUVCoords.h"
|
||||||
|
#include "gfx/fontSmallUVCoords.h"
|
||||||
|
#include <gl2d.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
glImage fontBig[FONTBIG_NUM_IMAGES];
|
||||||
|
glImage fontMedium[FONTBIG_NUM_IMAGES];
|
||||||
|
glImage fontSmall[FONTSMALL_NUM_IMAGES];
|
||||||
|
|
||||||
|
int fontBigTextureId;
|
||||||
|
int fontMediumTextureId;
|
||||||
|
int fontSmallTextureId;
|
||||||
|
|
||||||
|
struct GuiText {
|
||||||
|
size_t width;
|
||||||
|
size_t height;
|
||||||
|
size_t posX;
|
||||||
|
size_t posY;
|
||||||
|
char* text;
|
||||||
|
u16 color;
|
||||||
|
size_t length;
|
||||||
|
GuiTextHAlign hAlign;
|
||||||
|
GuiTextVAlign vAlign;
|
||||||
|
size_t maxWidth;
|
||||||
|
size_t maxHeight;
|
||||||
|
bool wrap;
|
||||||
|
int textureId;
|
||||||
|
glImage* font;
|
||||||
|
};
|
||||||
|
|
||||||
|
void initGuiFont(void)
|
||||||
|
{
|
||||||
|
fontBigTextureId = glLoadSpriteSet(
|
||||||
|
fontBig,
|
||||||
|
FONTBIG_NUM_IMAGES,
|
||||||
|
fontBigTexCoords,
|
||||||
|
GL_RGB256,
|
||||||
|
TEXTURE_SIZE_256,
|
||||||
|
TEXTURE_SIZE_256,
|
||||||
|
TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT,
|
||||||
|
256,
|
||||||
|
fontBig_0Pal,
|
||||||
|
(u8*)fontBig_0Bitmap);
|
||||||
|
|
||||||
|
fontMediumTextureId = glLoadSpriteSet(
|
||||||
|
fontMedium,
|
||||||
|
FONTMEDIUM_NUM_IMAGES,
|
||||||
|
fontMediumTexCoords,
|
||||||
|
GL_RGB256,
|
||||||
|
TEXTURE_SIZE_256,
|
||||||
|
TEXTURE_SIZE_256,
|
||||||
|
TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT,
|
||||||
|
256,
|
||||||
|
fontMedium_0Pal,
|
||||||
|
(u8*)fontMedium_0Bitmap);
|
||||||
|
|
||||||
|
fontSmallTextureId = glLoadSpriteSet(
|
||||||
|
fontSmall,
|
||||||
|
FONTSMALL_NUM_IMAGES,
|
||||||
|
fontSmallTexCoords,
|
||||||
|
GL_RGB256,
|
||||||
|
TEXTURE_SIZE_256,
|
||||||
|
TEXTURE_SIZE_256,
|
||||||
|
TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT,
|
||||||
|
256,
|
||||||
|
fontSmall_0Pal,
|
||||||
|
(u8*)fontSmall_0Bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiText newGuiText(const char* text, GuiTextSize size, u16 color)
|
||||||
|
{
|
||||||
|
GuiText gt = malloc(sizeof(struct GuiText));
|
||||||
|
gt->width = 0;
|
||||||
|
gt->height = 0;
|
||||||
|
gt->posX = 0;
|
||||||
|
gt->posY = 0;
|
||||||
|
gt->text = NULL;
|
||||||
|
gt->color = color;
|
||||||
|
gt->hAlign = GUI_TEXT_H_ALIGN_LEFT;
|
||||||
|
gt->vAlign = GUI_TEXT_V_ALIGN_TOP;
|
||||||
|
gt->maxWidth = 0;
|
||||||
|
gt->maxHeight = 0;
|
||||||
|
gt->wrap = false;
|
||||||
|
|
||||||
|
switch (size) {
|
||||||
|
case GUI_TEXT_SIZE_BIG:
|
||||||
|
gt->textureId = fontBigTextureId;
|
||||||
|
gt->font = fontBig;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_SIZE_MEDIUM:
|
||||||
|
gt->textureId = fontMediumTextureId;
|
||||||
|
gt->font = fontMedium;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_SIZE_SMALL:
|
||||||
|
gt->textureId = fontSmallTextureId;
|
||||||
|
gt->font = fontSmall;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGuiTextText(gt, text);
|
||||||
|
|
||||||
|
return gt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeGuiText(GuiText gt)
|
||||||
|
{
|
||||||
|
free(gt->text);
|
||||||
|
free(gt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateWH(GuiText gt)
|
||||||
|
{
|
||||||
|
size_t x = 0;
|
||||||
|
size_t y = 0;
|
||||||
|
|
||||||
|
size_t lineWidth = 0;
|
||||||
|
size_t lineStart = 0;
|
||||||
|
size_t lastSpace = -1;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < gt->length; i++) {
|
||||||
|
glImage currChar = gt->font[gt->text[i] - 1];
|
||||||
|
lineWidth += currChar.width + 1;
|
||||||
|
|
||||||
|
if (gt->text[i] == ' ')
|
||||||
|
lastSpace = i;
|
||||||
|
|
||||||
|
if (gt->maxWidth && (lineWidth > gt->maxWidth || gt->text[i] == '\n')) {
|
||||||
|
if (lineWidth > gt->maxWidth) {
|
||||||
|
if (lastSpace != -1 && lastSpace > lineStart) {
|
||||||
|
i = lastSpace;
|
||||||
|
lineWidth = 0;
|
||||||
|
|
||||||
|
for (size_t j = lineStart; j <= lastSpace; j++)
|
||||||
|
lineWidth += gt->font[gt->text[j] - 1].width + 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
lineWidth -= currChar.width + 1;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (gt->hAlign) {
|
||||||
|
case GUI_TEXT_H_ALIGN_LEFT:
|
||||||
|
x = 0;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_CENTER:
|
||||||
|
x = 0 - lineWidth / 2;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_RIGHT:
|
||||||
|
x = 0 - lineWidth;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = lineStart; j <= i; j++) {
|
||||||
|
currChar = gt->font[gt->text[j] - 1];
|
||||||
|
|
||||||
|
if (gt->text[j] != '\n')
|
||||||
|
x += currChar.width + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lineStart = i + 1;
|
||||||
|
lineWidth = 0;
|
||||||
|
y += currChar.height + 1;
|
||||||
|
lastSpace = -1;
|
||||||
|
|
||||||
|
if (!gt->wrap || (gt->maxHeight && y + currChar.height > gt->maxHeight)) {
|
||||||
|
gt->width = x;
|
||||||
|
gt->height = y;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineStart < gt->length) {
|
||||||
|
switch (gt->hAlign) {
|
||||||
|
case GUI_TEXT_H_ALIGN_LEFT:
|
||||||
|
x = 0;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_CENTER:
|
||||||
|
x = 0 - lineWidth / 2;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_RIGHT:
|
||||||
|
x = 0 - lineWidth;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = lineStart; j < gt->length; j++) {
|
||||||
|
glImage currChar = gt->font[gt->text[j] - 1];
|
||||||
|
x += currChar.width + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gt->width = x;
|
||||||
|
gt->height = y + gt->font[0].height + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiTextPos(GuiText gt, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
gt->posX = posX;
|
||||||
|
gt->posY = posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiTextText(GuiText gt, const char* text)
|
||||||
|
{
|
||||||
|
free(gt->text);
|
||||||
|
gt->text = strdup(text);
|
||||||
|
gt->length = strlen(text);
|
||||||
|
updateWH(gt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiTextAlignment(GuiText gt, GuiTextHAlign hAlignment, GuiTextVAlign vAlignment)
|
||||||
|
{
|
||||||
|
gt->hAlign = hAlignment;
|
||||||
|
gt->vAlign = vAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiTextMaxWidth(GuiText gt, size_t maxWidth)
|
||||||
|
{
|
||||||
|
gt->maxWidth = maxWidth;
|
||||||
|
updateWH(gt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiTextMaxHeight(GuiText gt, size_t maxHeight)
|
||||||
|
{
|
||||||
|
gt->maxHeight = maxHeight;
|
||||||
|
updateWH(gt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGuiTextWrap(GuiText gt, bool wrap)
|
||||||
|
{
|
||||||
|
gt->wrap = wrap;
|
||||||
|
updateWH(gt);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getGuiTextWidth(GuiText gt)
|
||||||
|
{
|
||||||
|
return gt->width;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getGuiTextHeight(GuiText gt)
|
||||||
|
{
|
||||||
|
return gt->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getGuiTextPosX(GuiText gt)
|
||||||
|
{
|
||||||
|
return gt->posX;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getGuiTextPosY(GuiText gt)
|
||||||
|
{
|
||||||
|
return gt->posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiTextHAlign getGuiTextHAlignment(GuiText gt)
|
||||||
|
{
|
||||||
|
return gt->hAlign;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiTextVAlign getGuiTextVAlignment(GuiText gt)
|
||||||
|
{
|
||||||
|
return gt->vAlign;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiTextPos(GuiText gt, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
size_t x = posX;
|
||||||
|
size_t y = posY;
|
||||||
|
|
||||||
|
size_t lineWidth = 0;
|
||||||
|
size_t lineStart = 0;
|
||||||
|
size_t lastSpace = -1;
|
||||||
|
|
||||||
|
switch (gt->vAlign) {
|
||||||
|
case GUI_TEXT_V_ALIGN_TOP:
|
||||||
|
y = posY;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_V_ALIGN_MIDDLE:
|
||||||
|
y = posY - gt->height / 2;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_V_ALIGN_BOTTOM:
|
||||||
|
y = posY - gt->height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t yStart = y;
|
||||||
|
|
||||||
|
glSetActiveTexture(gt->textureId);
|
||||||
|
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(1));
|
||||||
|
glColor(gt->color);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < gt->length; i++) {
|
||||||
|
glImage currChar = gt->font[gt->text[i] - 1];
|
||||||
|
lineWidth += currChar.width + 1;
|
||||||
|
|
||||||
|
if (gt->text[i] == ' ')
|
||||||
|
lastSpace = i;
|
||||||
|
|
||||||
|
if (gt->maxWidth && (lineWidth > gt->maxWidth || gt->text[i] == '\n')) {
|
||||||
|
if (lineWidth > gt->maxWidth) {
|
||||||
|
if (lastSpace != -1 && lastSpace > lineStart) {
|
||||||
|
i = lastSpace;
|
||||||
|
lineWidth = 0;
|
||||||
|
|
||||||
|
for (size_t j = lineStart; j <= lastSpace; j++)
|
||||||
|
lineWidth += gt->font[gt->text[j] - 1].width + 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
lineWidth -= currChar.width + 1;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (gt->hAlign) {
|
||||||
|
case GUI_TEXT_H_ALIGN_LEFT:
|
||||||
|
x = posX;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_CENTER:
|
||||||
|
x = posX - lineWidth / 2;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_RIGHT:
|
||||||
|
x = posX - lineWidth;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = lineStart; j <= i; j++) {
|
||||||
|
currChar = gt->font[gt->text[j] - 1];
|
||||||
|
|
||||||
|
if (gt->text[j] != '\n') {
|
||||||
|
glSprite(x, y, GL_FLIP_NONE, &currChar);
|
||||||
|
x += currChar.width + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lineStart = i + 1;
|
||||||
|
lineWidth = 0;
|
||||||
|
y += currChar.height + 1;
|
||||||
|
lastSpace = -1;
|
||||||
|
|
||||||
|
if (!gt->wrap || (gt->maxHeight && (y - yStart) + currChar.height > gt->maxHeight))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineStart < gt->length) {
|
||||||
|
switch (gt->hAlign) {
|
||||||
|
case GUI_TEXT_H_ALIGN_LEFT:
|
||||||
|
x = posX;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_CENTER:
|
||||||
|
x = posX - lineWidth / 2;
|
||||||
|
break;
|
||||||
|
case GUI_TEXT_H_ALIGN_RIGHT:
|
||||||
|
x = posX - lineWidth;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = lineStart; j < gt->length; j++) {
|
||||||
|
glImage currChar = gt->font[gt->text[j] - 1];
|
||||||
|
glSprite(x, y, GL_FLIP_NONE, &currChar);
|
||||||
|
x += currChar.width + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glColor(RGB15(31, 31, 31));
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGuiText(GuiText gt)
|
||||||
|
{
|
||||||
|
drawGuiTextPos(gt, gt->posX, gt->posY);
|
||||||
|
}
|
40
source/gui/text.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <calico/types.h>
|
||||||
|
|
||||||
|
typedef struct GuiText* GuiText;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_TEXT_SIZE_BIG,
|
||||||
|
GUI_TEXT_SIZE_MEDIUM,
|
||||||
|
GUI_TEXT_SIZE_SMALL
|
||||||
|
} GuiTextSize;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_TEXT_H_ALIGN_LEFT,
|
||||||
|
GUI_TEXT_H_ALIGN_CENTER,
|
||||||
|
GUI_TEXT_H_ALIGN_RIGHT
|
||||||
|
} GuiTextHAlign;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GUI_TEXT_V_ALIGN_TOP,
|
||||||
|
GUI_TEXT_V_ALIGN_MIDDLE,
|
||||||
|
GUI_TEXT_V_ALIGN_BOTTOM
|
||||||
|
} GuiTextVAlign;
|
||||||
|
|
||||||
|
void initGuiFont(void);
|
||||||
|
GuiText newGuiText(const char* text, GuiTextSize, u16 color);
|
||||||
|
void freeGuiText(GuiText);
|
||||||
|
void setGuiTextText(GuiText, const char*);
|
||||||
|
void setGuiTextPos(GuiText, size_t posX, size_t posY);
|
||||||
|
void setGuiTextAlignment(GuiText, GuiTextHAlign, GuiTextVAlign);
|
||||||
|
void setGuiTextMaxWidth(GuiText, size_t);
|
||||||
|
void setGuiTextMaxHeight(GuiText, size_t);
|
||||||
|
void setGuiTextWrap(GuiText, bool);
|
||||||
|
size_t getGuiTextWidth(GuiText);
|
||||||
|
size_t getGuiTextHeight(GuiText);
|
||||||
|
size_t getGuiTextPosX(GuiText);
|
||||||
|
size_t getGuiTextPosY(GuiText);
|
||||||
|
GuiTextHAlign getGuiTextHAlignment(GuiText);
|
||||||
|
GuiTextVAlign getGuiTextVAlignment(GuiText);
|
||||||
|
void drawGuiTextPos(GuiText, size_t posX, size_t posY);
|
||||||
|
void drawGuiText(GuiText);
|
43
source/gui/video.c
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "video.h"
|
||||||
|
|
||||||
|
#include <gl2d.h>
|
||||||
|
#include <nds.h>
|
||||||
|
|
||||||
|
void initSubSprites(void)
|
||||||
|
{
|
||||||
|
oamInit(&oamSub, SpriteMapping_Bmp_2D_256, false);
|
||||||
|
|
||||||
|
// Set up a 4x3 grid of 64x64 sprites to cover the screen
|
||||||
|
u8 id = 0;
|
||||||
|
for (u8 y = 0; y < 3; y++) {
|
||||||
|
for (u8 x = 0; x < 4; x++, id++) {
|
||||||
|
oamSub.oamMemory[id].attribute[0] = ATTR0_BMP | ATTR0_SQUARE | (64 * y);
|
||||||
|
oamSub.oamMemory[id].attribute[1] = ATTR1_SIZE_64 | (64 * x);
|
||||||
|
oamSub.oamMemory[id].attribute[2] = ATTR2_ALPHA(1) | (8 * 32 * y) | (8 * x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swiWaitForVBlank();
|
||||||
|
oamUpdate(&oamSub);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initGuiVideo(void)
|
||||||
|
{
|
||||||
|
videoSetMode(MODE_0_3D);
|
||||||
|
videoSetModeSub(MODE_5_2D);
|
||||||
|
|
||||||
|
initSubSprites();
|
||||||
|
bgInitSub(3, BgType_Bmp16, BgSize_B16_256x256, 0, 0);
|
||||||
|
|
||||||
|
glScreen2D();
|
||||||
|
|
||||||
|
vramSetBankA(VRAM_A_TEXTURE);
|
||||||
|
vramSetBankB(VRAM_B_TEXTURE);
|
||||||
|
vramSetBankF(VRAM_F_TEX_PALETTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void guiLoop(void)
|
||||||
|
{
|
||||||
|
glFlush(0);
|
||||||
|
swiWaitForVBlank();
|
||||||
|
}
|
4
source/gui/video.h
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void initGuiVideo(void);
|
||||||
|
void guiLoop(void);
|
191
source/lang/en.lang
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
msgid "Ok"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Browse"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Search by title"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Browse all"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No database loaded or initialized"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Page"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Previous"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Platform"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Region"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Author"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Version"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Download"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Error"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Info"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Download failed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "could not create download directory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "bad response status"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "could not initialize download"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "could not create file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "could not perform download"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Download completed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Extracting content..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Extraction failed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "could not open file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "could not locate file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "could not read file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "could not write file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Do you wish to extract the content in a separate directory?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Failed to initialize database"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "download failed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "file not found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Database open failed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "invalid version"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "invalid format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Database loaded successfully"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "entries"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Databases"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No databases found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Loading database..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Downloads directory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Use platform-specific directories (E.g. nds, gba)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Color scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Check for updates on start"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Path can not be empty"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Directory does not exist. Do you want to create it?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Failed to create directory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "English"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Italian"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Check for updates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Could not download update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Extracting update files..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Could not extract update files"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update downloaded successfully. Do you want to reboot now?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Checking for updates..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "New version found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Ignore"
|
||||||
|
msgstr ""
|
188
source/lang/it.lang
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
msgid "Ok"
|
||||||
|
msgstr "Ok"
|
||||||
|
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Annulla"
|
||||||
|
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Si"
|
||||||
|
|
||||||
|
msgid "No"
|
||||||
|
msgstr "No"
|
||||||
|
|
||||||
|
msgid "Browse"
|
||||||
|
msgstr "Sfoglia"
|
||||||
|
|
||||||
|
msgid "Search by title"
|
||||||
|
msgstr "Cerca per titolo"
|
||||||
|
|
||||||
|
msgid "Browse all"
|
||||||
|
msgstr "Sfoglia tutto"
|
||||||
|
|
||||||
|
msgid "No database loaded or initialized"
|
||||||
|
msgstr "Nessun database caricato o inizializzato"
|
||||||
|
|
||||||
|
msgid "Page"
|
||||||
|
msgstr "Pagina"
|
||||||
|
|
||||||
|
msgid "Previous"
|
||||||
|
msgstr "Indietro"
|
||||||
|
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Avanti"
|
||||||
|
|
||||||
|
msgid "Platform"
|
||||||
|
msgstr "Piattaforma"
|
||||||
|
|
||||||
|
msgid "Region"
|
||||||
|
msgstr "Regione"
|
||||||
|
|
||||||
|
msgid "Author"
|
||||||
|
msgstr "Autore"
|
||||||
|
|
||||||
|
msgid "Version"
|
||||||
|
msgstr "Versione"
|
||||||
|
|
||||||
|
msgid "Download"
|
||||||
|
msgstr "Scarica"
|
||||||
|
|
||||||
|
msgid "Error"
|
||||||
|
msgstr "Errore"
|
||||||
|
|
||||||
|
msgid "Info"
|
||||||
|
msgstr "Info"
|
||||||
|
|
||||||
|
msgid "Prompt"
|
||||||
|
msgstr "Richiesta"
|
||||||
|
|
||||||
|
msgid "Download failed"
|
||||||
|
msgstr "Download fallito"
|
||||||
|
|
||||||
|
msgid "could not create download directory"
|
||||||
|
msgstr "impossibile creare la directory di download"
|
||||||
|
|
||||||
|
msgid "bad response status"
|
||||||
|
msgstr "stato di risposta non valido"
|
||||||
|
|
||||||
|
msgid "could not initialize download"
|
||||||
|
msgstr "impossibile inizializzare il download"
|
||||||
|
|
||||||
|
msgid "could not create file"
|
||||||
|
msgstr "impossibile creare il file"
|
||||||
|
|
||||||
|
msgid "could not perform download"
|
||||||
|
msgstr "impossibile eseguire il download"
|
||||||
|
|
||||||
|
msgid "Download completed"
|
||||||
|
msgstr "Download completato"
|
||||||
|
|
||||||
|
msgid "Extracting content..."
|
||||||
|
msgstr "Estraendo il contenuto..."
|
||||||
|
|
||||||
|
msgid "Extraction failed"
|
||||||
|
msgstr "Estrazione fallita"
|
||||||
|
|
||||||
|
msgid "could not open file"
|
||||||
|
msgstr "impossibile aprire il file"
|
||||||
|
|
||||||
|
msgid "could not locate file"
|
||||||
|
msgstr "impossibile trovare il file"
|
||||||
|
|
||||||
|
msgid "could not read file"
|
||||||
|
msgstr "impossibile leggere il file"
|
||||||
|
|
||||||
|
msgid "could not write file"
|
||||||
|
msgstr "impossibile scrivere il file"
|
||||||
|
|
||||||
|
msgid "Do you wish to extract the content in a separate directory?"
|
||||||
|
msgstr "Vuoi estrarre il contenuto in una cartella separata?"
|
||||||
|
|
||||||
|
msgid "Failed to initialize database"
|
||||||
|
msgstr "Inizializzazione database fallita"
|
||||||
|
|
||||||
|
msgid "download failed"
|
||||||
|
msgstr "download fallito"
|
||||||
|
|
||||||
|
msgid "file not found"
|
||||||
|
msgstr "file non trovato"
|
||||||
|
|
||||||
|
msgid "Database open failed"
|
||||||
|
msgstr "Apertura database fallita"
|
||||||
|
|
||||||
|
msgid "invalid version"
|
||||||
|
msgstr "versione non valida"
|
||||||
|
|
||||||
|
msgid "invalid format"
|
||||||
|
msgstr "formato non valido"
|
||||||
|
|
||||||
|
msgid "Database loaded successfully"
|
||||||
|
msgstr "Database caricato correttamente"
|
||||||
|
|
||||||
|
msgid "entries"
|
||||||
|
msgstr "entry"
|
||||||
|
|
||||||
|
msgid "Databases"
|
||||||
|
msgstr "Database"
|
||||||
|
|
||||||
|
msgid "No databases found"
|
||||||
|
msgstr "Nessun database trovato"
|
||||||
|
|
||||||
|
msgid "Loading database..."
|
||||||
|
msgstr "Caricando database..."
|
||||||
|
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Impostazioni"
|
||||||
|
|
||||||
|
msgid "Downloads directory"
|
||||||
|
msgstr "Cartella download"
|
||||||
|
|
||||||
|
msgid "Use platform-specific directories (E.g. nds, gba)"
|
||||||
|
msgstr "Usa cartelle specifiche per piattaforma (Es. nds, gba)"
|
||||||
|
|
||||||
|
msgid "Color scheme"
|
||||||
|
msgstr "Schema colore"
|
||||||
|
|
||||||
|
msgid "Check for updates on start"
|
||||||
|
msgstr "Controlla aggiornamenti all'avvio"
|
||||||
|
|
||||||
|
msgid "Language"
|
||||||
|
msgstr "Lingua"
|
||||||
|
|
||||||
|
msgid "Path can not be empty"
|
||||||
|
msgstr "Il percorso non puo' essere vuoto"
|
||||||
|
|
||||||
|
msgid "Directory does not exist. Do you want to create it?"
|
||||||
|
msgstr "La cartella non esiste. Vuoi crearla?"
|
||||||
|
|
||||||
|
msgid "Failed to create directory"
|
||||||
|
msgstr "Impossibile creare la cartella"
|
||||||
|
|
||||||
|
msgid "Italian"
|
||||||
|
msgstr "Italiano"
|
||||||
|
|
||||||
|
msgid "Check for updates"
|
||||||
|
msgstr "Controlla aggiornamenti"
|
||||||
|
|
||||||
|
msgid "Could not download update"
|
||||||
|
msgstr "Impossibile scaricare aggiornamento"
|
||||||
|
|
||||||
|
msgid "Extracting update files..."
|
||||||
|
msgstr "Estraendo file di aggiornamento..."
|
||||||
|
|
||||||
|
msgid "Could not extract update files"
|
||||||
|
msgstr "Impossibile estrarre file di aggiornamento"
|
||||||
|
|
||||||
|
msgid "Update downloaded successfully. Do you want to reboot now?"
|
||||||
|
msgstr "Aggiornamento scaricato con successo. Vuoi riavviare adesso?"
|
||||||
|
|
||||||
|
msgid "Checking for updates..."
|
||||||
|
msgstr "Controllando aggiornamenti..."
|
||||||
|
|
||||||
|
msgid "New version found"
|
||||||
|
msgstr "Nuova versione trovata"
|
||||||
|
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Aggiorna"
|
||||||
|
|
||||||
|
msgid "Ignore"
|
||||||
|
msgstr "Ignora"
|
50
source/main.c
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "gui/text.h"
|
||||||
|
#include "gui/video.h"
|
||||||
|
#include "menu.h"
|
||||||
|
#include "networking.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "utils/filesystem.h"
|
||||||
|
#include <fat.h>
|
||||||
|
#include <nds.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
Database db = NULL;
|
||||||
|
|
||||||
|
void handleInit(bool result, const char* infoMessage, const char* errorMessage, u8 sleepTime, bool exitOnError)
|
||||||
|
{
|
||||||
|
iprintf(infoMessage);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
iprintf("Ok!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
iprintf(errorMessage);
|
||||||
|
sleep(sleepTime);
|
||||||
|
|
||||||
|
if (exitOnError)
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
consoleDemoInit();
|
||||||
|
|
||||||
|
iprintf("\n\t\t\t" APP_NAME " " APP_VERSION "\n\n");
|
||||||
|
iprintf("Initializing\n\n");
|
||||||
|
|
||||||
|
handleInit(fatInitDefault(), "Filesystem...", "\n\nFailed to initialize filesystem\n", 5, true);
|
||||||
|
handleInit(initNetworking() == NETWORKING_INIT_SUCCESS, "Networking...", "\n\nFailed to initialize networking:\nwifi connection failed\n", 5, true);
|
||||||
|
handleInit((createDir(APPDATA_DIR) && createDir(CACHE_DIR)), "Directories...", "\n\nFailed to initialize directories\n", 5, true);
|
||||||
|
handleInit((defaultSettings() && loadSettings()), "Settings...", "\n\nLoaded default settings\n", 3, false);
|
||||||
|
|
||||||
|
initGuiVideo();
|
||||||
|
initGuiFont();
|
||||||
|
|
||||||
|
menuBegin(MENU_BROWSE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
4
source/main.h
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "database.h"
|
||||||
|
|
||||||
|
extern Database db;
|
1835
source/menu.c
Normal file
15
source/menu.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MENU_NONE,
|
||||||
|
MENU_BROWSE,
|
||||||
|
MENU_RESULTS,
|
||||||
|
MENU_ENTRY,
|
||||||
|
MENU_DOWNLOAD,
|
||||||
|
MENU_DATABASES,
|
||||||
|
MENU_SETTINGS,
|
||||||
|
MENU_INFO,
|
||||||
|
MENU_EXIT
|
||||||
|
} MenuEnum;
|
||||||
|
|
||||||
|
void menuBegin(MenuEnum);
|
179
source/networking.c
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
#include "networking.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include <dswifi9.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define BUFFER_SIZE 1024 * 1024 // 1 MiB
|
||||||
|
#define USER_AGENT APP_NAME "/" APP_VERSION " (DS)"
|
||||||
|
|
||||||
|
NetworkingInitStatus initNetworking(void)
|
||||||
|
{
|
||||||
|
if (!Wifi_InitDefault(WFC_CONNECT))
|
||||||
|
return NETWORKING_INIT_ERR_WIFI_CONNECT;
|
||||||
|
|
||||||
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
|
|
||||||
|
return NETWORKING_INIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stopDownloadSignal = false;
|
||||||
|
|
||||||
|
struct WriteData {
|
||||||
|
FILE* fp;
|
||||||
|
char* buffer;
|
||||||
|
size_t bufferPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t writeDataCallback(void* ptr, size_t size, size_t nmemb, void* userdata)
|
||||||
|
{
|
||||||
|
struct WriteData* writeData = (struct WriteData*)userdata;
|
||||||
|
if (stopDownloadSignal)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
size_t total_size = size * nmemb;
|
||||||
|
if (writeData->bufferPos + total_size > BUFFER_SIZE) {
|
||||||
|
fwrite(writeData->buffer, 1, writeData->bufferPos, writeData->fp);
|
||||||
|
writeData->bufferPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_size > BUFFER_SIZE) {
|
||||||
|
fwrite(ptr, 1, total_size, writeData->fp);
|
||||||
|
} else {
|
||||||
|
memcpy(writeData->buffer + writeData->bufferPos, ptr, total_size);
|
||||||
|
writeData->bufferPos += total_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadStatus downloadFile(const char* path, const char* url, size_t (*downloadProgressCallback)(void*, curl_off_t, curl_off_t, curl_off_t, curl_off_t))
|
||||||
|
{
|
||||||
|
stopDownloadSignal = false;
|
||||||
|
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
if (!curl)
|
||||||
|
return DOWNLOAD_ERR_INIT_FAILED;
|
||||||
|
|
||||||
|
FILE* fp = fopen(path, "wb");
|
||||||
|
if (!fp) {
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
return DOWNLOAD_ERR_FILE_OPEN_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WriteData writeData;
|
||||||
|
writeData.fp = fp;
|
||||||
|
writeData.buffer = (char*)malloc(BUFFER_SIZE);
|
||||||
|
writeData.bufferPos = 0;
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeDataCallback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeData);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 7L);
|
||||||
|
|
||||||
|
if (downloadProgressCallback) {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, NULL);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgressCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
if (writeData.bufferPos > 0)
|
||||||
|
fwrite(writeData.buffer, 1, writeData.bufferPos, writeData.fp);
|
||||||
|
|
||||||
|
free(writeData.buffer);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
if (stopDownloadSignal) {
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
unlink(path);
|
||||||
|
return DOWNLOAD_STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
unlink(path);
|
||||||
|
return DOWNLOAD_ERR_PERFORM_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
long httpCode = 0;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
if (httpCode != 200) {
|
||||||
|
unlink(path);
|
||||||
|
return DOWNLOAD_ERR_NOT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DOWNLOAD_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopDownload(void)
|
||||||
|
{
|
||||||
|
stopDownloadSignal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MemoryWriteData {
|
||||||
|
char* result;
|
||||||
|
size_t bufferSize;
|
||||||
|
size_t currentPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t memoryWriteCallback(void* ptr, size_t size, size_t nmemb, void* userdata)
|
||||||
|
{
|
||||||
|
struct MemoryWriteData* writeData = (struct MemoryWriteData*)userdata;
|
||||||
|
size_t total_size = size * nmemb;
|
||||||
|
|
||||||
|
if (writeData->currentPos + total_size >= writeData->bufferSize) {
|
||||||
|
total_size = writeData->bufferSize - writeData->currentPos - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(writeData->result + writeData->currentPos, ptr, total_size);
|
||||||
|
writeData->currentPos += total_size;
|
||||||
|
writeData->result[writeData->currentPos] = '\0';
|
||||||
|
|
||||||
|
return total_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadStatus downloadToString(char* result, size_t bufferSize, const char* url)
|
||||||
|
{
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
if (!curl)
|
||||||
|
return DOWNLOAD_ERR_INIT_FAILED;
|
||||||
|
|
||||||
|
struct MemoryWriteData writeData;
|
||||||
|
writeData.result = result;
|
||||||
|
writeData.bufferSize = bufferSize;
|
||||||
|
writeData.currentPos = 0;
|
||||||
|
result[0] = '\0';
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memoryWriteCallback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeData);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 7L);
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
return DOWNLOAD_ERR_PERFORM_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
long httpCode = 0;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
if (httpCode != 200)
|
||||||
|
return DOWNLOAD_ERR_NOT_OK;
|
||||||
|
|
||||||
|
return DOWNLOAD_SUCCESS;
|
||||||
|
}
|
21
source/networking.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NETWORKING_INIT_SUCCESS,
|
||||||
|
NETWORKING_INIT_ERR_WIFI_CONNECT
|
||||||
|
} NetworkingInitStatus;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DOWNLOAD_SUCCESS,
|
||||||
|
DOWNLOAD_STOPPED,
|
||||||
|
DOWNLOAD_ERR_NOT_OK,
|
||||||
|
DOWNLOAD_ERR_INIT_FAILED,
|
||||||
|
DOWNLOAD_ERR_FILE_OPEN_FAILED,
|
||||||
|
DOWNLOAD_ERR_PERFORM_FAILED
|
||||||
|
} DownloadStatus;
|
||||||
|
|
||||||
|
NetworkingInitStatus initNetworking(void);
|
||||||
|
DownloadStatus downloadFile(const char* path, const char* url, size_t (*downloadProgressCallback)(void*, curl_off_t, curl_off_t, curl_off_t, curl_off_t));
|
||||||
|
void stopDownload(void);
|
||||||
|
DownloadStatus downloadToString(char* result, size_t bufferSize, const char* url);
|
80
source/settings.c
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "utils/filesystem.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define SETTINGS_FILENAME "settings.cfg"
|
||||||
|
|
||||||
|
struct Settings settings;
|
||||||
|
|
||||||
|
bool defaultSettings(void)
|
||||||
|
{
|
||||||
|
if (dirExists("/roms")) {
|
||||||
|
sprintf(settings.dlPath, "/roms");
|
||||||
|
settings.dlUseDirs = true; // The user probably wants to use platform-specific directories if has a roms directory
|
||||||
|
} else {
|
||||||
|
sprintf(settings.dlPath, "/");
|
||||||
|
settings.dlUseDirs = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.colorScheme = COLOR_SCHEME_1;
|
||||||
|
settings.lang = LANG_EN;
|
||||||
|
settings.checkUpdateOnStart = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadSettings(void)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(APPDATA_DIR "/" SETTINGS_FILENAME, "r");
|
||||||
|
if (!fp)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char line[2048];
|
||||||
|
char name[1024];
|
||||||
|
char value[1024];
|
||||||
|
while (fgets(line, sizeof(line), fp)) {
|
||||||
|
line[strcspn(line, "\r\n")] = 0;
|
||||||
|
|
||||||
|
if (sscanf(line, "%[^=]=%s", name, value) != 2) {
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int valueInt = atoi(value);
|
||||||
|
|
||||||
|
if (strcmp(name, "dlPath") == 0 && dirExists(value))
|
||||||
|
snprintf(settings.dlPath, sizeof(settings.dlPath), value);
|
||||||
|
else if (strcmp(name, "useDirs") == 0 && (valueInt == 0 || valueInt == 1))
|
||||||
|
settings.dlUseDirs = valueInt;
|
||||||
|
else if (strcmp(name, "colorScheme") == 0 && (valueInt > 0 && valueInt < COLOR_SCHEMES_COUNT + 1))
|
||||||
|
settings.colorScheme = valueInt - 1;
|
||||||
|
else if (strcmp(name, "lang") == 0 && (valueInt >= 0 && valueInt < LANGS_COUNT))
|
||||||
|
settings.lang = valueInt;
|
||||||
|
else if (strcmp(name, "checkUpdateOnStart") == 0 && (valueInt == 0 || valueInt == 1))
|
||||||
|
settings.checkUpdateOnStart = valueInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool saveSettings(void)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(APPDATA_DIR "/" SETTINGS_FILENAME, "w");
|
||||||
|
if (!fp)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fprintf(fp, "dlPath=%s\n", settings.dlPath);
|
||||||
|
fprintf(fp, "useDirs=%d\n", settings.dlUseDirs);
|
||||||
|
fprintf(fp, "colorScheme=%d\n", settings.colorScheme + 1);
|
||||||
|
fprintf(fp, "lang=%d\n", settings.lang);
|
||||||
|
fprintf(fp, "checkUpdateOnStart=%d\n", settings.checkUpdateOnStart);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
return true;
|
||||||
|
}
|
42
source/settings.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "colors.h"
|
||||||
|
#include "gettext.h"
|
||||||
|
#include <sys/syslimits.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SETTING_DLPATH,
|
||||||
|
SETTING_DLUSEDIRS,
|
||||||
|
SETTING_COLORSCHEME,
|
||||||
|
SETTING_CHECKUPDATEONSTART,
|
||||||
|
SETTING_LANG,
|
||||||
|
SETTINGS_COUNT
|
||||||
|
} SettingEnum;
|
||||||
|
|
||||||
|
struct Settings {
|
||||||
|
char dlPath[PATH_MAX];
|
||||||
|
bool dlUseDirs;
|
||||||
|
ColorSchemeEnum colorScheme;
|
||||||
|
LanguageEnum lang;
|
||||||
|
bool checkUpdateOnStart;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct Settings settings;
|
||||||
|
|
||||||
|
#define dlUseDirsStr(x) \
|
||||||
|
((x) == false ? "No" \
|
||||||
|
: (x) == true ? "Yes" \
|
||||||
|
: "")
|
||||||
|
|
||||||
|
#define langStr(x) \
|
||||||
|
((x) == LANG_EN ? "English" \
|
||||||
|
: (x) == LANG_IT ? "Italian" \
|
||||||
|
: "")
|
||||||
|
|
||||||
|
#define checkUpdateOnStartStr(x) \
|
||||||
|
((x) == false ? "No" \
|
||||||
|
: (x) == true ? "Yes" \
|
||||||
|
: "")
|
||||||
|
|
||||||
|
bool defaultSettings(void);
|
||||||
|
bool loadSettings(void);
|
||||||
|
bool saveSettings(void);
|
164
source/utils/filesystem.c
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#include "filesystem.h"
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
bool fileExists(const char* filePath)
|
||||||
|
{
|
||||||
|
struct stat statbuf;
|
||||||
|
if (stat(filePath, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dirExists(const char* dirPath)
|
||||||
|
{
|
||||||
|
struct stat statbuf;
|
||||||
|
if (stat(dirPath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pathExists(const char* path)
|
||||||
|
{
|
||||||
|
return fileExists(path) || dirExists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleteFile(const char* filePath)
|
||||||
|
{
|
||||||
|
if (unlink(filePath) != 0)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleteDir(const char* dirPath)
|
||||||
|
{
|
||||||
|
DIR* dp = opendir(dirPath);
|
||||||
|
if (dp == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
struct dirent* entry;
|
||||||
|
while ((entry = readdir(dp)) != NULL) {
|
||||||
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char fullPath[PATH_MAX];
|
||||||
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, entry->d_name);
|
||||||
|
|
||||||
|
struct stat statbuf;
|
||||||
|
if (stat(fullPath, &statbuf) != 0) {
|
||||||
|
closedir(dp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S_ISDIR(statbuf.st_mode)) {
|
||||||
|
if (!deleteDir(fullPath)) {
|
||||||
|
closedir(dp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!deleteFile(fullPath)) {
|
||||||
|
closedir(dp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dp);
|
||||||
|
|
||||||
|
if (rmdir(dirPath) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deletePath(const char* path)
|
||||||
|
{
|
||||||
|
return deleteFile(path) || deleteDir(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool renamePath(const char* oldPath, const char* newPath)
|
||||||
|
{
|
||||||
|
if (pathExists(newPath)) {
|
||||||
|
if (!deletePath(newPath))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rename(oldPath, newPath) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool createDir(const char* dirPath)
|
||||||
|
{
|
||||||
|
if (mkdir(dirPath, 0755) == 0 || errno == EEXIST)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool createDirR(const char* dirPath)
|
||||||
|
{
|
||||||
|
char tempPath[PATH_MAX];
|
||||||
|
char* p = NULL;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
strncpy(tempPath, dirPath, sizeof(tempPath) - 1);
|
||||||
|
len = strlen(tempPath);
|
||||||
|
|
||||||
|
// Remove trailing slash if present
|
||||||
|
if (tempPath[len - 1] == '/')
|
||||||
|
tempPath[len - 1] = '\0';
|
||||||
|
|
||||||
|
// Skip device name if present (e.g., "fat:")
|
||||||
|
p = strchr(tempPath, ':');
|
||||||
|
if (p != NULL)
|
||||||
|
p++; // Move past the ':'
|
||||||
|
else
|
||||||
|
p = tempPath;
|
||||||
|
|
||||||
|
// Iterate through the path and create directories as needed
|
||||||
|
for (; *p; p++) {
|
||||||
|
if (*p != '/')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
*p = '\0';
|
||||||
|
|
||||||
|
if (!createDir(tempPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the final directory
|
||||||
|
return createDir(tempPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getPathDir(const char* path, char* dir)
|
||||||
|
{
|
||||||
|
const char* lastSlash = strrchr(path, '/');
|
||||||
|
|
||||||
|
if (!lastSlash) {
|
||||||
|
dir[0] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t dirLen = lastSlash - path + 1;
|
||||||
|
strncpy(dir, path, dirLen);
|
||||||
|
dir[dirLen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool createDirStructure(const char* path)
|
||||||
|
{
|
||||||
|
char dirPath[PATH_MAX];
|
||||||
|
getPathDir(path, dirPath);
|
||||||
|
|
||||||
|
return createDirR(dirPath);
|
||||||
|
}
|
13
source/utils/filesystem.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
bool fileExists(const char* filePath);
|
||||||
|
bool dirExists(const char* dirPath);
|
||||||
|
bool pathExists(const char* path);
|
||||||
|
bool deleteFile(const char* filePath);
|
||||||
|
bool deleteDir(const char* dirPath);
|
||||||
|
bool deletePath(const char* path);
|
||||||
|
bool renamePath(const char* oldPath, const char* newPath);
|
||||||
|
bool createDir(const char* dirPath);
|
||||||
|
bool createDirR(const char* dirPath);
|
||||||
|
bool createDirStructure(const char* path);
|
4
source/utils/math.h
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
||||||
|
#define min(a, b) (((a) < (b)) ? (a) : (b))
|
76
source/utils/strings.c
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#include "strings.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void humanizeSize(char* sizeStr, size_t bufferSize, u64 sizeInBytes)
|
||||||
|
{
|
||||||
|
const char* suffix[] = { "B", "KiB", "MiB", "GiB" };
|
||||||
|
u8 suffixesCount = 4;
|
||||||
|
|
||||||
|
u8 i = 0;
|
||||||
|
double dblBytes = sizeInBytes;
|
||||||
|
if (sizeInBytes > 1024) {
|
||||||
|
for (i = 0; (sizeInBytes / 1024) > 0 && i < suffixesCount - 1; i++, sizeInBytes /= 1024)
|
||||||
|
dblBytes = (double)sizeInBytes / 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(sizeStr, bufferSize, "%.01lf %s", dblBytes, suffix[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lowerStr(char* str)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; str[i] != '\0'; i++)
|
||||||
|
str[i] = tolower(str[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAccentsStr(char* str)
|
||||||
|
{
|
||||||
|
const char normalizedCharMap[] = {
|
||||||
|
(char)0, (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, (char)32, (char)33, (char)34, (char)35, (char)36, (char)37, (char)38, (char)39, (char)40, (char)41, (char)42, (char)43, (char)44, (char)45, (char)46, (char)47, (char)48, (char)49, (char)50, (char)51, (char)52, (char)53, (char)54, (char)55, (char)56, (char)57, (char)58, (char)59, (char)60, (char)61, (char)62, (char)63, (char)64, (char)65, (char)66, (char)67, (char)68, (char)69, (char)70, (char)71, (char)72, (char)73, (char)74, (char)75, (char)76, (char)77, (char)78, (char)79, (char)80, (char)81, (char)82, (char)83, (char)84, (char)85, (char)86, (char)87, (char)88, (char)89, (char)90, (char)91, (char)92, (char)93, (char)94, (char)95, (char)96, (char)97, (char)98, (char)99, (char)100, (char)101, (char)102, (char)103, (char)104, (char)105, (char)106, (char)107, (char)108, (char)109, (char)110, (char)111, (char)112, (char)113, (char)114, (char)115, (char)116, (char)117, (char)118, (char)119, (char)120, (char)121, (char)122, (char)123, (char)124, (char)125, (char)126, (char)127, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)32, (char)33, (char)63, (char)63, (char)63, (char)63, (char)124, (char)63, (char)34, (char)63, (char)97, (char)63, (char)33, (char)63, (char)63, (char)45, (char)63, (char)63, (char)50, (char)51, (char)39, (char)117, (char)80, (char)42, (char)44, (char)49, (char)111, (char)63, (char)63, (char)63, (char)63, (char)63, (char)65, (char)65, (char)65, (char)65, (char)65, (char)65, (char)63, (char)67, (char)69, (char)69, (char)69, (char)69, (char)73, (char)73, (char)73, (char)73, (char)68, (char)78, (char)79, (char)79, (char)79, (char)79, (char)79, (char)120, (char)79, (char)85, (char)85, (char)85, (char)85, (char)89, (char)63, (char)63, (char)97, (char)97, (char)97, (char)97, (char)97, (char)97, (char)63, (char)99, (char)101, (char)101, (char)101, (char)101, (char)105, (char)105, (char)105, (char)105, (char)100, (char)110, (char)111, (char)111, (char)111, (char)111, (char)111, (char)47, (char)111, (char)117, (char)117, (char)117, (char)117, (char)121, (char)63, (char)121
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace each non-ASCII character with its corresponding in map
|
||||||
|
size_t i = 0, j = 0;
|
||||||
|
while (str[i] != '\0') {
|
||||||
|
u8 c = (u8)str[i];
|
||||||
|
if (c < 128)
|
||||||
|
str[j++] = str[i];
|
||||||
|
else
|
||||||
|
str[j++] = normalizedCharMap[c];
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
str[j] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void safeStr(char* str)
|
||||||
|
{
|
||||||
|
removeAccentsStr(str);
|
||||||
|
|
||||||
|
// Replace characters not in 'a-zA-Z0-9 ' with '_'
|
||||||
|
size_t i, j;
|
||||||
|
for (i = 0, j = 0; str[i]; i++) {
|
||||||
|
if ((str[i] >= 'a' && str[i] <= 'z') || (str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= '0' && str[i] <= '9') || (str[i] == ' '))
|
||||||
|
str[j++] = str[i];
|
||||||
|
else
|
||||||
|
str[j++] = '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
str[j] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void joinPath(char* joinedPath, const char* path1, const char* path2)
|
||||||
|
{
|
||||||
|
strcpy(joinedPath, path1);
|
||||||
|
|
||||||
|
if (joinedPath[strlen(joinedPath) - 1] != '/')
|
||||||
|
strcat(joinedPath, "/");
|
||||||
|
|
||||||
|
if (path2[0] == '/')
|
||||||
|
strcat(joinedPath, path2 + 1);
|
||||||
|
else
|
||||||
|
strcat(joinedPath, path2);
|
||||||
|
}
|
8
source/utils/strings.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <calico/types.h>
|
||||||
|
|
||||||
|
void humanizeSize(char* sizeStr, size_t bufferSize, u64 sizeInBytes);
|
||||||
|
void lowerStr(char*);
|
||||||
|
void removeAccentsStr(char*);
|
||||||
|
void safeStr(char*);
|
||||||
|
void joinPath(char* joinedPath, const char* path1, const char* path2);
|