From 2409270b379e8d4f41bbd7dae99dd9503a1226c3 Mon Sep 17 00:00:00 2001 From: mrdude2478 Date: Tue, 26 Apr 2022 01:27:14 +0100 Subject: [PATCH] Add files via upload --- include/Plutonium/Plutonium/Makefile | 128 + .../Plutonium/Plutonium/include/pu/Plutonium | 39 + .../include/pu/audio/audio_Music.hpp | 36 + .../Plutonium/include/pu/audio/audio_Sfx.hpp | 26 + .../Plutonium/include/pu/pu_Include.hpp | 30 + .../include/pu/sdl2/sdl2_CustomTtf.h | 276 ++ .../Plutonium/include/pu/sdl2/sdl2_Types.hpp | 17 + .../Plutonium/include/pu/ttf/ttf_Font.hpp | 87 + .../include/pu/ui/elm/elm_Button.hpp | 131 + .../include/pu/ui/elm/elm_Element.hpp | 88 + .../Plutonium/include/pu/ui/elm/elm_Image.hpp | 86 + .../Plutonium/include/pu/ui/elm/elm_Menu.hpp | 291 +++ .../include/pu/ui/elm/elm_ProgressBar.hpp | 124 + .../include/pu/ui/elm/elm_Rectangle.hpp | 86 + .../include/pu/ui/elm/elm_TextBlock.hpp | 71 + .../include/pu/ui/elm/elm_Toggle.hpp | 94 + .../include/pu/ui/extras/extras_Toast.hpp | 41 + .../include/pu/ui/render/render_Renderer.hpp | 199 ++ .../include/pu/ui/render/render_SDL2.hpp | 27 + .../include/pu/ui/ui_Application.hpp | 142 ++ .../Plutonium/include/pu/ui/ui_Container.hpp | 88 + .../Plutonium/include/pu/ui/ui_Dialog.hpp | 128 + .../Plutonium/include/pu/ui/ui_Layout.hpp | 82 + .../Plutonium/include/pu/ui/ui_Overlay.hpp | 53 + .../Plutonium/include/pu/ui/ui_Types.hpp | 82 + .../Plutonium/source/pu/audio/audio_Music.cpp | 58 + .../Plutonium/source/pu/audio/audio_Sfx.cpp | 18 + .../Plutonium/source/pu/sdl2/sdl2_CustomTtf.c | 2254 +++++++++++++++++ .../Plutonium/source/pu/ttf/ttf_Font.cpp | 142 ++ .../Plutonium/source/pu/ui/elm/elm_Button.cpp | 89 + .../source/pu/ui/elm/elm_Element.cpp | 38 + .../Plutonium/source/pu/ui/elm/elm_Image.cpp | 34 + .../Plutonium/source/pu/ui/elm/elm_Menu.cpp | 310 +++ .../source/pu/ui/elm/elm_ProgressBar.cpp | 22 + .../source/pu/ui/elm/elm_Rectangle.cpp | 9 + .../source/pu/ui/elm/elm_TextBlock.cpp | 46 + .../Plutonium/source/pu/ui/elm/elm_Toggle.cpp | 80 + .../source/pu/ui/extras/extras_Toast.cpp | 38 + .../source/pu/ui/render/render_Renderer.cpp | 349 +++ .../source/pu/ui/render/render_SDL2.cpp | 56 + .../Plutonium/source/pu/ui/ui_Application.cpp | 212 ++ .../Plutonium/source/pu/ui/ui_Container.cpp | 11 + .../Plutonium/source/pu/ui/ui_Dialog.cpp | 250 ++ .../Plutonium/source/pu/ui/ui_Layout.cpp | 27 + .../Plutonium/source/pu/ui/ui_Overlay.cpp | 46 + .../Plutonium/source/pu/ui/ui_Types.cpp | 30 + 46 files changed, 6571 insertions(+) create mode 100644 include/Plutonium/Plutonium/Makefile create mode 100644 include/Plutonium/Plutonium/include/pu/Plutonium create mode 100644 include/Plutonium/Plutonium/include/pu/audio/audio_Music.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/audio/audio_Sfx.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/pu_Include.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/sdl2/sdl2_CustomTtf.h create mode 100644 include/Plutonium/Plutonium/include/pu/sdl2/sdl2_Types.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ttf/ttf_Font.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/elm/elm_Button.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/elm/elm_Element.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/elm/elm_Image.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/elm/elm_Menu.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/elm/elm_ProgressBar.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/elm/elm_Rectangle.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/elm/elm_TextBlock.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/elm/elm_Toggle.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/extras/extras_Toast.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/render/render_Renderer.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/render/render_SDL2.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/ui_Application.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/ui_Container.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/ui_Dialog.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/ui_Layout.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/ui_Overlay.hpp create mode 100644 include/Plutonium/Plutonium/include/pu/ui/ui_Types.hpp create mode 100644 include/Plutonium/Plutonium/source/pu/audio/audio_Music.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/audio/audio_Sfx.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/sdl2/sdl2_CustomTtf.c create mode 100644 include/Plutonium/Plutonium/source/pu/ttf/ttf_Font.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/elm/elm_Button.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/elm/elm_Element.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/elm/elm_Image.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/elm/elm_Menu.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/elm/elm_ProgressBar.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/elm/elm_Rectangle.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/elm/elm_TextBlock.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/elm/elm_Toggle.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/extras/extras_Toast.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/render/render_Renderer.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/render/render_SDL2.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/ui_Application.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/ui_Container.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/ui_Dialog.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/ui_Layout.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/ui_Overlay.cpp create mode 100644 include/Plutonium/Plutonium/source/pu/ui/ui_Types.cpp diff --git a/include/Plutonium/Plutonium/Makefile b/include/Plutonium/Plutonium/Makefile new file mode 100644 index 0000000..e1151a4 --- /dev/null +++ b/include/Plutonium/Plutonium/Makefile @@ -0,0 +1,128 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +include $(DEVKITPRO)/devkitA64/base_rules +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +BUILD := build +TARGET := pu +SOURCES := source source/pu source/pu/audio source/pu/ttf source/pu/sdl2 source/pu/ui source/pu/ui/elm source/pu/ui/extras source/pu/ui/render +INCLUDES := include +OUT_LIB := lib + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec + +CFLAGS := -g -Wall -D__SWITCH__ -ffunction-sections -fdata-sections $(ARCH) + +CFLAGS += $(INCLUDE) -I$(PORTLIBS)/include/freetype2 -DPU_MAJOR=$(PU_MAJOR) -DPU_MINOR=$(PU_MINOR) -DPU_MICRO=$(PU_MICRO) -DPU_VERSION=\"$(PU_MAJOR).$(PU_MINOR).$(PU_MICRO)\" + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=${DEVKITPRO}/libnx/switch.specs -g $(ARCH) -Wl,-r,-Map,$(notdir $*.map) + +SDL_IMAGE_LIBS := -lSDL2_image -lpng -ljpeg +SDL_GFX_LIBS := -lSDL2_gfx +SDL_MIXER_LIBS := -lSDL2_mixer -lmodplug -lmpg123 -lvorbisidec -logg + +LIBS := $(SDL_IMAGE_LIBS) $(SDL_GFX_LIBS) $(SDL_MIXER_LIBS) -lEGL -lGLESv2 -lglapi `sdl2-config --libs` `freetype-config --libs` + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- +export OUTPUT := $(CURDIR)/$(OUT_LIB)/lib$(TARGET).a +export DEPSDIR := $(CURDIR)/$(BUILD) +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @[ -d $(OUT_LIB) ] || mkdir -p $(OUT_LIB) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(CURDIR)/$(BUILD) + @rm -fr $(CURDIR)/$(OUT_LIB) + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES_SRC) : $(HFILES) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/Plutonium b/include/Plutonium/Plutonium/include/pu/Plutonium new file mode 100644 index 0000000..3257152 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/Plutonium @@ -0,0 +1,39 @@ + +/* + + Plutonium library + + @file Plutonium + @brief Plutonium library's main header. (include this to use this library properly) + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/audio/audio_Music.hpp b/include/Plutonium/Plutonium/include/pu/audio/audio_Music.hpp new file mode 100644 index 0000000..799ba00 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/audio/audio_Music.hpp @@ -0,0 +1,36 @@ + +/* + + Plutonium library + + @file audio_Music.hpp + @brief Music (BGM) support + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include + +namespace pu::audio { + + using Music = Mix_Music*; + + Music OpenMusic(const std::string &path); + void PlayMusic(Music mus, const int loops); + void PlayMusicWithFadeIn(Music mus, const i32 llops, const i32 ms); + bool IsPlayingMusic(); + void PauseMusic(); + void ResumeMusic(); + void SetMusicVolume(const i32 vol); + i32 GetMusicVolume(); + void FadeOutMusic(const i32 ms); + void RewindMusic(); + void StopMusic(); + void SetMusicPosition(const double sec); + void DestroyMusic(Music &mus); + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/audio/audio_Sfx.hpp b/include/Plutonium/Plutonium/include/pu/audio/audio_Sfx.hpp new file mode 100644 index 0000000..143186b --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/audio/audio_Sfx.hpp @@ -0,0 +1,26 @@ + +/* + + Plutonium library + + @file audio_Sfx.hpp + @brief Sfx (sound effects) support + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include + +namespace pu::audio { + + using Sfx = Mix_Chunk*; + + Sfx LoadSfx(const std::string &path); + void PlaySfx(Sfx sfx); + void DestroySfx(Sfx &sfx); + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/pu_Include.hpp b/include/Plutonium/Plutonium/include/pu/pu_Include.hpp new file mode 100644 index 0000000..53e4dd9 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/pu_Include.hpp @@ -0,0 +1,30 @@ + +/* + + Plutonium library + + @file pu_Include.hpp + @brief Basic includes and definitions for the library + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include +#include + +// Defines a static function (::New(...)) as a constructor for smart ptrs, also defines a custom type (::Ref) to simplify it +#define PU_SMART_CTOR(type) \ +using Ref = std::shared_ptr; \ +template \ +inline static Ref New(Args &&...ctor_args) { \ + return std::move(std::make_shared(std::forward(ctor_args)...)); \ +} + +namespace pu { + + using i32 = s32; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/sdl2/sdl2_CustomTtf.h b/include/Plutonium/Plutonium/include/pu/sdl2/sdl2_CustomTtf.h new file mode 100644 index 0000000..fe8d9e9 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/sdl2/sdl2_CustomTtf.h @@ -0,0 +1,276 @@ +/* + SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts + Copyright (C) 2001-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* This library is a wrapper around the excellent FreeType 2.0 library, + available at: + http://www.freetype.org/ +*/ + +#ifndef _SDL_TTF_H +#define _SDL_TTF_H + +#include +#include + +#include + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL +*/ +#define SDL_TTF_MAJOR_VERSION 2 +#define SDL_TTF_MINOR_VERSION 0 +#define SDL_TTF_PATCHLEVEL 12 + +/* This macro can be used to fill a version structure with the compile-time + * version of the SDL_ttf library. + */ +#define SDL_TTF_VERSION(X) \ +{ \ + (X)->major = SDL_TTF_MAJOR_VERSION; \ + (X)->minor = SDL_TTF_MINOR_VERSION; \ + (X)->patch = SDL_TTF_PATCHLEVEL; \ +} + +/* Backwards compatibility */ +#define TTF_MAJOR_VERSION SDL_TTF_MAJOR_VERSION +#define TTF_MINOR_VERSION SDL_TTF_MINOR_VERSION +#define TTF_PATCHLEVEL SDL_TTF_PATCHLEVEL +#define TTF_VERSION(X) SDL_TTF_VERSION(X) + +/* This function gets the version of the dynamically linked SDL_ttf library. + it should NOT be used to fill a version structure, instead you should + use the SDL_TTF_VERSION() macro. + */ +extern DECLSPEC const SDL_version * SDLCALL TTF_Linked_Version(void); + +/* ZERO WIDTH NO-BREAKSPACE (Unicode byte order mark) */ +#define UNICODE_BOM_NATIVE 0xFEFF +#define UNICODE_BOM_SWAPPED 0xFFFE + +/* This function tells the library whether UNICODE text is generally + byteswapped. A UNICODE BOM character in a string will override + this setting for the remainder of that string. +*/ +extern DECLSPEC void SDLCALL TTF_ByteSwappedUNICODE(int swapped); + +/* The internal structure containing font information */ +typedef struct _TTF_Font TTF_Font; + +/* Initialize the TTF engine - returns 0 if successful, -1 on error */ +extern DECLSPEC int SDLCALL TTF_Init(void); + +/* Open a font file and create a font of the specified point size. + * Some .fon fonts will have several sizes embedded in the file, so the + * point size becomes the index of choosing which size. If the value + * is too high, the last indexed size will be the default. */ +extern DECLSPEC TTF_Font * SDLCALL TTF_OpenFont(const char *file, int ptsize); +extern DECLSPEC TTF_Font * SDLCALL TTF_OpenFontIndex(const char *file, int ptsize, long index); +extern DECLSPEC TTF_Font * SDLCALL TTF_OpenFontRW(SDL_RWops *src, int freesrc, int ptsize); +extern DECLSPEC TTF_Font * SDLCALL TTF_OpenFontIndexRW(SDL_RWops *src, int freesrc, int ptsize, long index); + +/* Set and retrieve the font style */ +#define TTF_STYLE_NORMAL 0x00 +#define TTF_STYLE_BOLD 0x01 +#define TTF_STYLE_ITALIC 0x02 +#define TTF_STYLE_UNDERLINE 0x04 +#define TTF_STYLE_STRIKETHROUGH 0x08 +extern DECLSPEC int SDLCALL TTF_GetFontStyle(const TTF_Font *font); +extern DECLSPEC void SDLCALL TTF_SetFontStyle(TTF_Font *font, int style); +extern DECLSPEC int SDLCALL TTF_GetFontOutline(const TTF_Font *font); +extern DECLSPEC void SDLCALL TTF_SetFontOutline(TTF_Font *font, int outline); + +/* Set and retrieve FreeType hinter settings */ +#define TTF_HINTING_NORMAL 0 +#define TTF_HINTING_LIGHT 1 +#define TTF_HINTING_MONO 2 +#define TTF_HINTING_NONE 3 +extern DECLSPEC int SDLCALL TTF_GetFontHinting(const TTF_Font *font); +extern DECLSPEC void SDLCALL TTF_SetFontHinting(TTF_Font *font, int hinting); + +/* Get the total height of the font - usually equal to point size */ +extern DECLSPEC int SDLCALL TTF_FontHeight(const TTF_Font *font); + +/* Get the offset from the baseline to the top of the font + This is a positive value, relative to the baseline. + */ +extern DECLSPEC int SDLCALL TTF_FontAscent(const TTF_Font *font); + +/* Get the offset from the baseline to the bottom of the font + This is a negative value, relative to the baseline. + */ +extern DECLSPEC int SDLCALL TTF_FontDescent(const TTF_Font *font); + +/* Get the recommended spacing between lines of text for this font */ +extern DECLSPEC int SDLCALL TTF_FontLineSkip(const TTF_Font *font); + +/* Get/Set whether or not kerning is allowed for this font */ +extern DECLSPEC int SDLCALL TTF_GetFontKerning(const TTF_Font *font); +extern DECLSPEC void SDLCALL TTF_SetFontKerning(TTF_Font *font, int allowed); + +/* Get the number of faces of the font */ +extern DECLSPEC long SDLCALL TTF_FontFaces(const TTF_Font *font); + +/* Get the font face attributes, if any */ +extern DECLSPEC int SDLCALL TTF_FontFaceIsFixedWidth(const TTF_Font *font); +extern DECLSPEC char * SDLCALL TTF_FontFaceFamilyName(const TTF_Font *font); +extern DECLSPEC char * SDLCALL TTF_FontFaceStyleName(const TTF_Font *font); + +/* Check wether a glyph is provided by the font or not */ +extern DECLSPEC int SDLCALL TTF_GlyphIsProvided(const TTF_Font *font, Uint16 ch); + +/* Get the metrics (dimensions) of a glyph + To understand what these metrics mean, here is a useful link: + http://freetype.sourceforge.net/freetype2/docs/tutorial/step2.html + */ +extern DECLSPEC int SDLCALL TTF_GlyphMetrics(TTF_Font *font, Uint16 ch, + int *minx, int *maxx, + int *miny, int *maxy, int *advance); + +/* Get the dimensions of a rendered string of text */ +extern DECLSPEC int SDLCALL TTF_SizeText(TTF_Font *font, const char *text, int *w, int *h); +extern DECLSPEC int SDLCALL TTF_SizeUTF8(TTF_Font *font, const char *text, int *w, int *h); +extern DECLSPEC int SDLCALL TTF_SizeUNICODE(TTF_Font *font, const Uint16 *text, int *w, int *h); + +/* Create an 8-bit palettized surface and render the given text at + fast quality with the given font and color. The 0 pixel is the + colorkey, giving a transparent background, and the 1 pixel is set + to the text color. + This function returns the new surface, or NULL if there was an error. +*/ +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Solid(TTF_Font *font, + const char *text, SDL_Color fg); +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderUTF8_Solid(TTF_Font *font, + const char *text, SDL_Color fg); +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderUNICODE_Solid(TTF_Font *font, + const Uint16 *text, SDL_Color fg); + +/* Create an 8-bit palettized surface and render the given glyph at + fast quality with the given font and color. The 0 pixel is the + colorkey, giving a transparent background, and the 1 pixel is set + to the text color. The glyph is rendered without any padding or + centering in the X direction, and aligned normally in the Y direction. + This function returns the new surface, or NULL if there was an error. +*/ +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Solid(TTF_Font *font, + Uint16 ch, SDL_Color fg); + +/* Create an 8-bit palettized surface and render the given text at + high quality with the given font and colors. The 0 pixel is background, + while other pixels have varying degrees of the foreground color. + This function returns the new surface, or NULL if there was an error. +*/ +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Shaded(TTF_Font *font, + const char *text, SDL_Color fg, SDL_Color bg); +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderUTF8_Shaded(TTF_Font *font, + const char *text, SDL_Color fg, SDL_Color bg); +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderUNICODE_Shaded(TTF_Font *font, + const Uint16 *text, SDL_Color fg, SDL_Color bg); + +/* Create an 8-bit palettized surface and render the given glyph at + high quality with the given font and colors. The 0 pixel is background, + while other pixels have varying degrees of the foreground color. + The glyph is rendered without any padding or centering in the X + direction, and aligned normally in the Y direction. + This function returns the new surface, or NULL if there was an error. +*/ +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Shaded(TTF_Font *font, + Uint16 ch, SDL_Color fg, SDL_Color bg); + +/* Create a 32-bit ARGB surface and render the given text at high quality, + using alpha blending to dither the font with the given color. + This function returns the new surface, or NULL if there was an error. +*/ +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Blended(TTF_Font *font, + const char *text, SDL_Color fg); +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderUTF8_Blended(TTF_Font *font, + const char *text, SDL_Color fg); +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderUNICODE_Blended(TTF_Font *font, + const Uint16 *text, SDL_Color fg); + + +/* Create a 32-bit ARGB surface and render the given text at high quality, + using alpha blending to dither the font with the given color. + Text is wrapped to multiple lines on line endings and on word boundaries + if it extends beyond wrapLength in pixels. + This function returns the new surface, or NULL if there was an error. +*/ +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Blended_Wrapped(TTF_Font *font, + const char *text, SDL_Color fg, Uint32 wrapLength); +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderUTF8_Blended_Wrapped(TTF_Font *font, + const char *text, SDL_Color fg, Uint32 wrapLength); +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderUNICODE_Blended_Wrapped(TTF_Font *font, + const Uint16 *text, SDL_Color fg, Uint32 wrapLength); + +/* Create a 32-bit ARGB surface and render the given glyph at high quality, + using alpha blending to dither the font with the given color. + The glyph is rendered without any padding or centering in the X + direction, and aligned normally in the Y direction. + This function returns the new surface, or NULL if there was an error. +*/ +extern DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Blended(TTF_Font *font, + Uint16 ch, SDL_Color fg); + +/* For compatibility with previous versions, here are the old functions */ +#define TTF_RenderText(font, text, fg, bg) \ + TTF_RenderText_Shaded(font, text, fg, bg) +#define TTF_RenderUTF8(font, text, fg, bg) \ + TTF_RenderUTF8_Shaded(font, text, fg, bg) +#define TTF_RenderUNICODE(font, text, fg, bg) \ + TTF_RenderUNICODE_Shaded(font, text, fg, bg) + +/* Close an opened font file */ +extern DECLSPEC void SDLCALL TTF_CloseFont(TTF_Font *font); + +/* De-initialize the TTF engine */ +extern DECLSPEC void SDLCALL TTF_Quit(void); + +/* Check if the TTF engine is initialized */ +extern DECLSPEC int SDLCALL TTF_WasInit(void); + +/* Get the kerning size of two glyphs */ +extern DECLSPEC int TTF_GetFontKerningSize(TTF_Font *font, int prev_index, int index); + +/* Code present in C++ code */ +TTF_Font *TTF_CppWrap_FindValidFont(TTF_Font *font, Uint16 ch); + +/* Get the pointer to the C++ data */ +void *TTF_CppWrap_GetCppPtrRef(TTF_Font *font); + +/* Set the pointer to the C++ data */ +void TTF_CppWrap_SetCppPtrRef(TTF_Font *font, void *cpp_ptr_ref); + +/* We'll use SDL for reporting errors */ +#define TTF_SetError SDL_SetError +#define TTF_GetError SDL_GetError + +#define TMP_LOG(str) { const char *cstr = str; svcOutputDebugString(cstr, strlen(cstr)); } + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* _SDL_TTF_H */ diff --git a/include/Plutonium/Plutonium/include/pu/sdl2/sdl2_Types.hpp b/include/Plutonium/Plutonium/include/pu/sdl2/sdl2_Types.hpp new file mode 100644 index 0000000..213067a --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/sdl2/sdl2_Types.hpp @@ -0,0 +1,17 @@ + +#pragma once +#include +#include +#include +#include +#include + +namespace pu::sdl2 { + + using Texture = SDL_Texture*; + using Window = SDL_Window*; + using Renderer = SDL_Renderer*; + using Font = TTF_Font*; + using Surface = SDL_Surface*; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ttf/ttf_Font.hpp b/include/Plutonium/Plutonium/include/pu/ttf/ttf_Font.hpp new file mode 100644 index 0000000..a733bb6 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ttf/ttf_Font.hpp @@ -0,0 +1,87 @@ + +#pragma once +#include +#include +#include + +namespace pu::ttf { + + class Font { + private: + using FontFaceDisposingFunction = void(*)(void*); + + struct FontFace { + sdl2::Font font; + void *ptr; + size_t ptr_sz; + FontFaceDisposingFunction dispose_fn; + + FontFace(void *buf, const size_t buf_size, FontFaceDisposingFunction disp_fn, const u32 font_sz, void *font_class_ptr) : font(nullptr), ptr(buf), ptr_sz(buf_size), dispose_fn(disp_fn) { + this->font = TTF_OpenFontRW(SDL_RWFromMem(this->ptr, this->ptr_sz), 1, font_sz); + if(this->font != nullptr) { + TTF_CppWrap_SetCppPtrRef(this->font, font_class_ptr); + } + } + + FontFace() : font(nullptr), ptr(nullptr), ptr_sz(0), dispose_fn(EmptyFontFaceDisposingFunction) {} + + inline bool IsSourceValid() { + // AKA - is the base ptr and size valid? + return (this->ptr != nullptr) && (this->ptr_sz > 0); + } + + void DisposeFont() { + if(this->font != nullptr) { + TTF_CloseFont(this->font); + this->font = nullptr; + } + } + + void Dispose() { + this->DisposeFont(); + if(this->IsSourceValid()) { + (this->dispose_fn)(this->ptr); + this->ptr = nullptr; + this->ptr_sz = 0; + } + } + + }; + + std::vector>> font_faces; + u32 font_size; + + inline sdl2::Font TryGetFirstFont() { + if(!this->font_faces.empty()) { + return this->font_faces.begin()->second->font; + } + return nullptr; + } + + public: + static constexpr i32 InvalidFontFaceIndex = -1; + static constexpr u32 DefaultFontSize = 25; + + static void EmptyFontFaceDisposingFunction(void*) {} + + static inline constexpr bool IsValidFontFaceIndex(const i32 index) { + return index != InvalidFontFaceIndex; + } + + Font(const u32 font_sz) : font_size(font_sz) {} + ~Font(); + + i32 LoadFromMemory(void *ptr, const size_t size, FontFaceDisposingFunction disp_fn); + i32 LoadFromFile(const std::string &path); + void Unload(const i32 font_idx); + + inline u32 GetFontSize() { + return this->font_size; + } + + sdl2::Font FindValidFontFor(const Uint16 ch); + std::pair GetTextDimensions(const std::string &str); + sdl2::Texture RenderText(const std::string &str, const ui::Color clr); + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Button.hpp b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Button.hpp new file mode 100644 index 0000000..121899d --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Button.hpp @@ -0,0 +1,131 @@ + +/* + + Plutonium library + + @file Button.hpp + @brief A Button is an Element for option selecting. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include + +namespace pu::ui::elm { + + class Button : public Element { + public: + using OnClickCallback = std::function; + + static constexpr u8 DarkerColorFactor = 70; + + static constexpr u8 HoverAlphaIncrement = 48; + + private: + i32 x; + i32 y; + i32 w; + i32 h; + std::string fnt_name; + Color bg_clr; + Color cnt_clr; + std::string cnt; + sdl2::Texture cnt_tex; + OnClickCallback on_click_cb; + bool hover; + i32 hover_alpha; + + inline Color MakeHoverBackgroundColor(const i32 alpha) { + i32 base_r = this->bg_clr.r - DarkerColorFactor; + if(base_r < 0) { + base_r = 0; + } + i32 base_g = this->bg_clr.g - DarkerColorFactor; + if(base_g < 0) { + base_g = 0; + } + i32 base_b = this->bg_clr.b - DarkerColorFactor; + if(base_b < 0) { + base_b = 0; + } + + auto base_a = this->bg_clr.a; + if(alpha >= 0) { + base_a = static_cast(alpha); + } + + return { static_cast(base_r), static_cast(base_g), static_cast(base_b), base_a }; + } + + public: + Button(const i32 x, const i32 y, const i32 width, const i32 height, const std::string &content, const Color content_clr, const Color bg_clr); + PU_SMART_CTOR(Button) + ~Button(); + + inline i32 GetX() override { + return this->x; + } + + inline void SetX(const i32 x) { + this->x = x; + } + + inline i32 GetY() override { + return this->y; + } + + inline void SetY(const i32 y) { + this->y = y; + } + + inline i32 GetWidth() override { + return this->w; + } + + inline void SetWidth(const i32 width) { + this->w = width; + } + + inline i32 GetHeight() override { + return this->h; + } + + inline void SetHeight(const i32 height) { + this->h = height; + } + + inline std::string GetContent() { + return this->cnt; + } + + void SetContent(const std::string &content); + + inline Color GetContentColor() { + return this->cnt_clr; + } + + void SetContentColor(const Color content_clr); + + inline Color GetBackgroundColor() { + return this->bg_clr; + } + + inline void SetBackgroundColor(const Color bg_clr) { + this->bg_clr = bg_clr; + } + + void SetContentFont(const std::string &font_name); + + inline void SetOnClick(OnClickCallback on_click_cb) { + this->on_click_cb = on_click_cb; + } + + void OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) override; + void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) override; + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Element.hpp b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Element.hpp new file mode 100644 index 0000000..5f0b217 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Element.hpp @@ -0,0 +1,88 @@ + +/* + + Plutonium library + + @file Element.hpp + @brief An Element is the base of Plutonium UI's content. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +namespace pu::ui { + + class Container; + +} + +namespace pu::ui::elm { + + enum class HorizontalAlign { + Left, + Center, + Right + }; + + enum class VerticalAlign { + Up, + Center, + Down + }; + + class Element { + protected: + bool visible; + HorizontalAlign h_align; + VerticalAlign v_align; + Container *parent_container; + + public: + Element() : visible(true), h_align(HorizontalAlign::Left), v_align(VerticalAlign::Up), parent_container(nullptr) {} + PU_SMART_CTOR(Element) + virtual ~Element() {} + + virtual i32 GetX() = 0; + virtual i32 GetY() = 0; + virtual i32 GetWidth() = 0; + virtual i32 GetHeight() = 0; + virtual void OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) = 0; + virtual void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) = 0; + + inline bool IsVisible() { + return this->visible; + } + + inline void SetVisible(const bool visible) { + this->visible = visible; + } + + inline void SetHorizontalAlign(const HorizontalAlign align) { + this->h_align = align; + } + + inline HorizontalAlign GetHorizontalAlign() { + return this->h_align; + } + + inline void SetVerticalAlign(const VerticalAlign align) { + this->v_align = align; + } + + inline VerticalAlign GetVerticalAlign() { + return this->v_align; + } + + inline void SetParentContainer(Container *parent_container) { + this->parent_container = parent_container; + } + + i32 GetProcessedX(); + i32 GetProcessedY(); + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Image.hpp b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Image.hpp new file mode 100644 index 0000000..135b025 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Image.hpp @@ -0,0 +1,86 @@ + +/* + + Plutonium library + + @file Image.hpp + @brief An Image is an Element showing a picture. (JPEG, PNG, TGA, BMP) + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +namespace pu::ui::elm { + + class Image : public Element { + private: + std::string img_path; + sdl2::Texture img_tex; + render::TextureRenderOptions rend_opts; + i32 x; + i32 y; + + public: + Image(const i32 x, const i32 y, const std::string &image_path); + PU_SMART_CTOR(Image) + ~Image(); + + inline i32 GetX() override { + return this->x; + } + + inline void SetX(const i32 x) { + this->x = x; + } + + inline i32 GetY() override { + return this->y; + } + + inline void SetY(const i32 y) { + this->y = y; + } + + inline i32 GetWidth() override { + return this->rend_opts.width; + } + + inline void SetWidth(const i32 width) { + this->rend_opts.width = width; + } + + inline i32 GetHeight() override { + return this->rend_opts.height; + } + + inline void SetHeight(const i32 height) { + this->rend_opts.height = height; + } + + inline float GetRotationAngle() { + return this->rend_opts.rot_angle; + } + + inline void SetRotationAngle(const float angle) { + this->rend_opts.rot_angle = angle; + } + + inline std::string GetImagePath() { + return this->img_path; + } + + void SetImage(const std::string &image_path); + + inline bool IsImageValid() { + return this->img_tex != nullptr; + } + + void OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) override; + void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) override {} + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Menu.hpp b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Menu.hpp new file mode 100644 index 0000000..261ff9a --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Menu.hpp @@ -0,0 +1,291 @@ + +/* + + Plutonium library + + @file Menu.hpp + @brief A Menu is a very useful Element for option browsing or selecting. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include +#include + +namespace pu::ui::elm { + + class MenuItem { + public: + using OnKeyCallback = std::function; + static constexpr Color DefaultColor = { 10, 10, 10, 0xFF }; + + private: + std::string name; + Color items_clr; + std::string icon_path; + std::vector on_key_cbs; + std::vector on_key_cb_keys; + + public: + MenuItem(const std::string &name) : name(name), items_clr(DefaultColor) {} + PU_SMART_CTOR(MenuItem) + + inline std::string GetName() { + return this->name; + } + + inline void SetName(const std::string &name) { + this->name = name; + } + + inline Color GetColor() { + return this->items_clr; + } + + inline void SetColor(const Color items_clr) { + this->items_clr = items_clr; + } + + void AddOnKey(OnKeyCallback on_key_cb, const u64 key = HidNpadButton_A); + + inline u32 GetOnKeyCallbackCount() { + return this->on_key_cbs.size(); + } + + inline OnKeyCallback GetOnKeyCallback(const u32 idx) { + if(idx < this->on_key_cbs.size()) { + return this->on_key_cbs.at(idx); + } + else { + return {}; + } + } + + inline u64 GetOnKeyCallbackKey(const u32 idx) { + if(idx < this->on_key_cb_keys.size()) { + return this->on_key_cb_keys.at(idx); + } + else { + return {}; + } + } + + inline std::string GetIconPath() { + return this->icon_path; + } + + void SetIcon(const std::string &icon_path); + + inline bool HasIcon() { + return !this->icon_path.empty(); + } + }; + + class Menu : public Element { + public: + static constexpr Color DefaultScrollbarColor = { 110, 110, 110, 0xFF }; + + static constexpr u8 ItemAlphaIncrement = 48; + + static constexpr float IconItemSizesFactor = 0.8f; + + static constexpr u32 IconMargin = 25; + static constexpr u32 TextMargin = 25; + + static constexpr u8 LightScrollbarColorFactor = 30; + + static constexpr u32 ScrollbarWidth = 20; + + static constexpr u32 ShadowHeight = 5; + static constexpr u8 ShadowBaseAlpha = 160; + + using OnSelectionChangedCallback = std::function; + + private: + i32 x; + i32 y; + i32 w; + i32 items_h; + u32 items_to_show; + u32 selected_item_idx; + i32 selected_item_alpha; + i32 prev_selected_item_idx; + i32 prev_selected_item_alpha; + u32 advanced_item_count; + Color scrollbar_clr; + Color items_clr; + Color items_focus_clr; + bool cooldown_enabled; + bool item_touched; + u8 move_mode; + std::chrono::time_point move_start_time; + OnSelectionChangedCallback on_selection_changed_cb; + std::vector items; + std::string font_name; + std::vector loaded_name_texs; + std::vector loaded_icon_texs; + + void ReloadItemRenders(); + + inline Color MakeItemsFocusColor(const u8 alpha) { + return { this->items_focus_clr.r, this->items_focus_clr.g, this->items_focus_clr.b, alpha }; + } + + inline constexpr Color MakeLighterScrollbarColor() { + i32 base_r = this->scrollbar_clr.r - LightScrollbarColorFactor; + if(base_r < 0) { + base_r = 0; + } + i32 base_g = this->scrollbar_clr.g - LightScrollbarColorFactor; + if(base_g < 0) { + base_g = 0; + } + i32 base_b = this->scrollbar_clr.b - LightScrollbarColorFactor; + if(base_b < 0) { + base_b = 0; + } + + return { static_cast(base_r), static_cast(base_g), static_cast(base_b), this->scrollbar_clr.a }; + } + + inline void HandleOnSelectionChanged() { + if(this->on_selection_changed_cb) { + (this->on_selection_changed_cb)(); + } + } + + inline void RunSelectedItemCallback(const u64 keys) { + auto item = this->items.at(this->selected_item_idx); + const auto cb_count = item->GetOnKeyCallbackCount(); + for(u32 i = 0; i < cb_count; i++) { + if(keys & item->GetOnKeyCallbackKey(i)) { + if(!this->cooldown_enabled) { + auto cb = item->GetOnKeyCallback(i); + if(cb) { + cb(); + } + } + } + } + this->cooldown_enabled = false; + } + + inline u32 GetItemCount() { + auto item_count = this->items_to_show; + if(item_count > this->items.size()) { + item_count = this->items.size(); + } + if((item_count + this->advanced_item_count) > this->items.size()) { + item_count = this->items.size() - this->advanced_item_count; + } + return item_count; + } + + public: + Menu(const i32 x, const i32 y, const i32 width, const Color items_clr, const Color items_focus_clr, const i32 items_height, const u32 items_to_show); + PU_SMART_CTOR(Menu) + + inline i32 GetX() override { + return this->x; + } + + inline void SetX(const i32 x) { + this->x = x; + } + + inline i32 GetY() override { + return this->y; + } + + inline void SetY(const i32 y) { + this->y = y; + } + + inline i32 GetWidth() override { + return this->w; + } + + inline void SetWidth(const i32 width) { + this->w = width; + } + + inline i32 GetHeight() override { + return this->items_h * this->items_to_show; + } + + inline i32 GetItemsHeight() { + return this->items_h; + } + + inline void SetItemsHeight(const i32 items_height) { + this->items_h = items_height; + } + + inline i32 GetNumberOfItemsToShow() { + return this->items_to_show; + } + + inline void SetNumberOfItemsToShow(const i32 items_to_show) { + this->items_to_show = items_to_show; + } + + inline Color GetItemsColor() { + return this->items_clr; + } + + inline void SetItemsColor(const Color items_clr) { + this->items_clr = items_clr; + } + + inline Color GetItemsFocusColor() { + return this->items_focus_clr; + } + + inline void SetItemsFocusColor(const Color items_focus_clr) { + this->items_focus_clr = items_focus_clr; + } + + inline Color GetScrollbarColor() { + return this->scrollbar_clr; + } + + inline void SetScrollbarColor(const Color scrollbar_clr) { + this->scrollbar_clr = scrollbar_clr; + } + + inline void SetOnSelectionChanged(OnSelectionChangedCallback on_selection_changed_cb) { + this->on_selection_changed_cb = on_selection_changed_cb; + } + + inline void AddItem(MenuItem::Ref &item) { + this->items.push_back(item); + } + + void ClearItems(); + + inline void SetCooldownEnabled(const bool enabled) { + this->cooldown_enabled = enabled; + } + + inline MenuItem::Ref &GetSelectedItem() { + return this->items.at(this->selected_item_idx); + } + + inline std::vector &GetItems() { + return this->items; + } + + inline i32 GetSelectedIndex() { + return this->selected_item_idx; + } + + void SetSelectedIndex(const u32 idx); + + void OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) override; + void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) override; + }; +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/elm/elm_ProgressBar.hpp b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_ProgressBar.hpp new file mode 100644 index 0000000..d610e49 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_ProgressBar.hpp @@ -0,0 +1,124 @@ + +/* + + Plutonium library + + @file ProgressBar.hpp + @brief A ProgressBar is an Element which represents a progress (a percentage) by filling a bar. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +namespace pu::ui::elm { + + class ProgressBar : public Element { + public: + static constexpr Color DefaultProgressColor = { 139, 195, 74, 255 }; + static constexpr Color DefaultBackgroundColor = { 140, 140, 140, 255 }; + + private: + i32 x; + i32 y; + i32 w; + i32 h; + double val; + double max_val; + Color progress_clr; + Color bg_clr; + + public: + ProgressBar(const i32 x, const i32 y, const i32 width, const i32 height, const double max_val) : Element(), x(x), y(y), w(width), h(height), val(0), max_val(max_val), progress_clr(DefaultProgressColor), bg_clr(DefaultBackgroundColor) {} + PU_SMART_CTOR(ProgressBar) + + inline i32 GetX() override { + return this->x; + } + + inline void SetX(const i32 x) { + this->x = x; + } + + inline i32 GetY() override { + return this->y; + } + + inline void SetY(const i32 y) { + this->y = y; + } + + inline i32 GetWidth() override { + return this->w; + } + + inline void SetWidth(const i32 width) { + this->w = width; + } + + inline i32 GetHeight() override { + return this->h; + } + + inline void SetHeight(const i32 height) { + this->h = height; + } + + inline Color GetProgressColor() { + return this->progress_clr; + } + + inline void SetProgressColor(const Color progress_clr) { + this->progress_clr = progress_clr; + } + + inline Color GetBackgroundColor() { + return this->bg_clr; + } + + inline void SetBackgroundColor(const Color bg_clr) { + this->bg_clr = bg_clr; + } + + inline double GetProgress() { + return this->val; + } + + void SetProgress(const double progress); + + inline void IncrementProgress(const double extra_progress) { + this->SetProgress(this->val + extra_progress); + } + + inline void DecrementProgress(const double extra_progress) { + this->SetProgress(this->val - extra_progress); + } + + inline void SetMaxProgress(const double max_progress) { + this->max_val = max_progress; + } + + inline double GetMaxProgress() { + return this->max_val; + } + + inline void FillProgress() { + this->SetProgress(this->max_val); + } + + inline void ClearProgress() { + this->SetProgress(0); + } + + inline bool IsCompleted() { + return this->val == this->max_val; + } + + void OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) override; + void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) override {} + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Rectangle.hpp b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Rectangle.hpp new file mode 100644 index 0000000..b0f5dcd --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Rectangle.hpp @@ -0,0 +1,86 @@ + +/* + + Plutonium library + + @file Rectangle.hpp + @brief A Rectangle is an Element which simply draws a filled rectangle. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +namespace pu::ui::elm { + + class Rectangle : public Element + { + private: + i32 x; + i32 y; + i32 w; + i32 h; + Color clr; + i32 border_radius; + + public: + Rectangle(const i32 x, const i32 y, const i32 width, const i32 height, const Color clr, const i32 border_radius = 0) : Element(), x(x), y(y), w(width), h(height), clr(clr), border_radius(border_radius) {} + PU_SMART_CTOR(Rectangle) + + inline i32 GetX() override { + return this->x; + } + + inline void SetX(const i32 x) { + this->x = x; + } + + inline i32 GetY() override { + return this->y; + } + + inline void SetY(const i32 y) { + this->y = y; + } + + inline i32 GetWidth() override { + return this->w; + } + + inline void SetWidth(const i32 width) { + this->w = width; + } + + inline i32 GetHeight() override { + return this->h; + } + + inline void SetHeight(const i32 height) { + this->h = height; + } + + inline i32 GetBorderRadius() { + return this->border_radius; + } + + inline void SetBorderRadius(const i32 border_radius) { + this->border_radius = border_radius; + } + + + inline Color GetColor() { + return this->clr; + } + + inline void SetColor(const Color clr) { + this->clr = clr; + } + + void OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) override; + void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) override {} + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/elm/elm_TextBlock.hpp b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_TextBlock.hpp new file mode 100644 index 0000000..65242a7 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_TextBlock.hpp @@ -0,0 +1,71 @@ + +/* + + Plutonium library + + @file TextBlock.hpp + @brief A TextBlock is a very useful Element which is used to draw text on the screen. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +namespace pu::ui::elm { + + class TextBlock : public Element { + public: + static constexpr Color DefaultColor = { 0, 0, 0, 0xFF }; + + private: + i32 x; + i32 y; + Color clr; + std::string text; + sdl2::Texture text_tex; + std::string fnt_name; + + public: + TextBlock(const i32 x, const i32 y, const std::string &text); + PU_SMART_CTOR(TextBlock) + ~TextBlock(); + + inline i32 GetX() override { + return this->x; + } + + inline void SetX(const i32 x) { + this->x = x; + } + + inline i32 GetY() override { + return this->y; + } + + inline void SetY(const i32 y) { + this->y = y; + } + + i32 GetWidth() override; + i32 GetHeight() override; + + inline std::string GetText() { + return this->text; + } + + void SetText(const std::string &text); + void SetFont(const std::string &font_name); + + inline Color GetColor() { + return this->clr; + } + + void SetColor(const Color clr); + void OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) override; + void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) override {} + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Toggle.hpp b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Toggle.hpp new file mode 100644 index 0000000..7701a49 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/elm/elm_Toggle.hpp @@ -0,0 +1,94 @@ + +/* + + Plutonium library + + @file Toggle.hpp + @brief A Toggle is an Element used to switch between two options by toggling the item. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +namespace pu::ui::elm { + + class Toggle : public Element { + public: + static constexpr u32 ContentHorizontalMargin = 30; + static constexpr u32 ContentVerticalMargin = 20; + + static constexpr u8 ToggleAlphaIncrement = 48; + + static constexpr Color MakeBackgroundColor(const u8 alpha) { + return { 130, 130, 130, alpha }; + } + + private: + i32 x; + i32 y; + u64 key; + bool checked; + Color clr; + std::string fnt_name; + i32 toggle_alpha; + std::string cnt; + sdl2::Texture cnt_tex; + + public: + Toggle(const i32 x, const i32 y, const std::string &content, const u64 toggle_key, const Color clr); + PU_SMART_CTOR(Toggle) + ~Toggle(); + + inline i32 GetX() override { + return this->x; + } + + inline void SetX(const i32 x) { + this->x = x; + } + + inline i32 GetY() override { + return this->y; + } + + inline void SetY(const i32 y) { + this->y = y; + } + + i32 GetWidth() override; + i32 GetHeight() override; + + inline std::string GetContent() { + return this->cnt; + } + + void SetContent(const std::string &content); + void SetFont(const std::string &font_name); + + inline Color GetColor() { + return this->clr; + } + + void SetColor(const Color clr); + + inline u64 GetKey() { + return this->key; + } + + inline void SetKey(const u64 toggle_key) { + this->key = toggle_key; + } + + inline bool IsChecked() { + return this->checked; + } + + void OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) override; + void OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) override; + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/extras/extras_Toast.hpp b/include/Plutonium/Plutonium/include/pu/ui/extras/extras_Toast.hpp new file mode 100644 index 0000000..bb2a9b9 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/extras/extras_Toast.hpp @@ -0,0 +1,41 @@ + +/* + + Plutonium library + + @file extras_Toast.hpp + @brief An Overlay similar to Android's toast notifications + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include + +namespace pu::ui::extras { + + class Toast final : public Overlay { + public: + static constexpr i32 DefaultY = 550; + static constexpr i32 HeightAndTextHeightFactor = 3; + static constexpr i32 HorizontalMargin = 50; + static constexpr u8 BaseAlpha = 200; + + private: + pu::ui::elm::TextBlock::Ref text; + + void AdjustDimensions(); + + public: + Toast(const std::string &text, const std::string &font_name, const Color text_clr, const Color bg_clr); + PU_SMART_CTOR(Toast) + + void SetText(const std::string &text); + void OnPreRender(render::Renderer::Ref &drawer) override; + void OnPostRender(render::Renderer::Ref &drawer) override; + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/render/render_Renderer.hpp b/include/Plutonium/Plutonium/include/pu/ui/render/render_Renderer.hpp new file mode 100644 index 0000000..6478eb3 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/render/render_Renderer.hpp @@ -0,0 +1,199 @@ + +/* + + Plutonium library + + @file render_Renderer.hpp + @brief A Renderer is the object performing basic rendering. (simply, a SDL2 wrapper) + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include +#include + +namespace pu::ui::render { + + constexpr u32 ScreenWidth = 1280; + constexpr u32 ScreenHeight = 720; + + struct RendererInitOptions { + u32 sdl_flags; + u32 sdl_render_flags; + u32 width; + u32 height; + bool init_ttf; + std::vector extra_default_font_sizes; + std::string default_font_path; + bool init_mixer; + u32 audio_mixer_flags; + bool init_img; + u32 sdl_img_flags; + bool init_pl; + bool init_romfs; + + RendererInitOptions(const u32 sdl_flags, const u32 sdl_render_flags, const u32 w = ScreenWidth, const u32 h = ScreenHeight) : sdl_flags(sdl_flags), sdl_render_flags(sdl_render_flags), width(w), height(h), init_ttf(false), extra_default_font_sizes(), default_font_path(), init_mixer(false), audio_mixer_flags(0), init_img(false), sdl_img_flags(0), init_pl(false), init_romfs(false) {} + + inline void UseTTF(const std::string &default_font_path = "") { + this->init_ttf = true; + + // Empty font path = using shared font + if(!default_font_path.empty()) { + this->default_font_path = default_font_path; + } + else { + this->init_pl = true; + } + } + + inline void SetExtraDefaultFontSize(const u32 font_size) { + this->extra_default_font_sizes.push_back(font_size); + } + + inline void UseAudio(const u32 audio_mixer_flags) { + this->init_mixer = true; + this->audio_mixer_flags = audio_mixer_flags; + } + + inline void UseImage(const u32 sdl_img_flags) { + this->init_img = true; + this->sdl_img_flags = sdl_img_flags; + } + + inline void UseRomfs() { + this->init_romfs = true; + } + }; + + constexpr u32 MixerAllFlags = MIX_INIT_FLAC | MIX_INIT_MOD | MIX_INIT_MP3 | MIX_INIT_OGG; + constexpr u32 IMGAllFlags = IMG_INIT_PNG | IMG_INIT_JPG | IMG_INIT_TIF | IMG_INIT_WEBP; + constexpr u32 RendererSoftwareFlags = SDL_RENDERER_SOFTWARE; + constexpr u32 RendererHardwareFlags = SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED; + + struct TextureRenderOptions { + i32 alpha_mod; + i32 width; + i32 height; + float rot_angle; + + static constexpr i32 NoAlpha = -1; + static constexpr i32 NoWidth = -1; + static constexpr i32 NoHeight = -1; + static constexpr float NoRotation = -1.0f; + + static constexpr TextureRenderOptions Default() { + return { NoAlpha, NoWidth, NoHeight, NoRotation }; + } + + static constexpr TextureRenderOptions WithCustomAlpha(const u8 alpha) { + return { alpha, NoWidth, NoHeight, NoRotation }; + } + + static constexpr TextureRenderOptions WithCustomDimensions(const i32 width, const i32 height) { + return { NoAlpha, width, height, NoRotation }; + } + + static constexpr TextureRenderOptions WithCustomAlphaAndDimensions(const u8 alpha, const i32 width, const i32 height) { + return { alpha, width, height, NoRotation }; + } + }; + + class Renderer { + private: + RendererInitOptions init_opts; + bool ok_romfs; + bool ok_pl; + bool initialized; + i32 base_x; + i32 base_y; + i32 base_a; + + inline u8 GetActualAlpha(const u8 input_a) { + if(this->base_a >= 0) { + return static_cast(this->base_a); + } + else { + return input_a; + } + } + + public: + Renderer(const RendererInitOptions init_opts) : init_opts(init_opts), ok_romfs(false), ok_pl(false), initialized(false), base_x(0), base_y(0), base_a(0) {} + PU_SMART_CTOR(Renderer) + + void Initialize(); + void Finalize(); + + inline bool HasInitialized() { + return this->initialized; + } + + inline bool HasRomFs() { + return this->ok_romfs; + } + + void InitializeRender(const Color clr); + void FinalizeRender(); + void RenderTexture(sdl2::Texture texture, const i32 x, const i32 y, const TextureRenderOptions opts = TextureRenderOptions::Default()); + void RenderRectangle(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height); + void RenderRectangleFill(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height); + + inline void RenderRectangleOutline(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height, const i32 border_width) { + this->RenderRectangleFill(clr, x - border_width, y - border_width, width + (border_width * 2), height + (border_width * 2)); + } + + void RenderRoundedRectangle(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height, const i32 radius); + void RenderRoundedRectangleFill(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height, const i32 radius); + void RenderCircle(const Color clr, const i32 x, const i32 y, const i32 radius); + void RenderCircleFill(const Color clr, const i32 x, const i32 y, const i32 radius); + void RenderShadowSimple(const i32 x, const i32 y, const i32 width, const i32 height, const i32 base_alpha, const u8 main_alpha = 0xFF); + + inline void SetBaseRenderPosition(const i32 x, const i32 y) { + this->base_x = x; + this->base_y = y; + } + + inline void ResetBaseRenderPosition() { + this->SetBaseRenderPosition(0, 0); + } + + inline void SetBaseRenderAlpha(const u8 alpha) { + this->base_a = alpha; + } + + inline void ResetBaseRenderAlpha() { + this->base_a = -1; + } + }; + + // Global rendering + + sdl2::Renderer GetMainRenderer(); + sdl2::Window GetMainWindow(); + sdl2::Surface GetMainSurface(); + + std::pair GetDimensions(); + + // Text rendering + + bool AddSharedFont(const std::string &font_name, const u32 font_size, const PlSharedFontType type); + bool AddAllSharedFonts(const std::string &font_name, const u32 font_size); + bool AddFontFile(const std::string &font_name, const u32 font_size, const std::string &path); + + inline void AddDefaultFontFromShared(const u32 font_size) { + AddAllSharedFonts(MakeDefaultFontName(font_size), font_size); + } + + inline void AddDefaultFontFromFile(const u32 font_size, const std::string &path) { + AddFontFile(MakeDefaultFontName(font_size), font_size, path); + } + + sdl2::Texture RenderText(const std::string &font_name, const std::string &text, const Color clr); + i32 GetTextWidth(const std::string &font_name, const std::string &text); + i32 GetTextHeight(const std::string &font_name, const std::string &text); + +} diff --git a/include/Plutonium/Plutonium/include/pu/ui/render/render_SDL2.hpp b/include/Plutonium/Plutonium/include/pu/ui/render/render_SDL2.hpp new file mode 100644 index 0000000..c26e9f1 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/render/render_SDL2.hpp @@ -0,0 +1,27 @@ + +/* + + Plutonium library + + @file render_SDL2.hpp + @brief Wrapper code to simplify SDL2 usage + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include + +namespace pu::ui::render { + + sdl2::Texture ConvertToTexture(sdl2::Surface surface); + sdl2::Texture LoadImage(const std::string &path); + i32 GetTextureWidth(sdl2::Texture texture); + i32 GetTextureHeight(sdl2::Texture texture); + void SetAlphaValue(sdl2::Texture texture, const u8 alpha); + void DeleteTexture(sdl2::Texture &texture); + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/ui_Application.hpp b/include/Plutonium/Plutonium/include/pu/ui/ui_Application.hpp new file mode 100644 index 0000000..35240a1 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/ui_Application.hpp @@ -0,0 +1,142 @@ + +/* + + Plutonium library + + @file ui_Application.hpp + @brief An Application is the base to use the UI system of this library. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include +#include +#include + +namespace pu::ui { + + class Application { + public: + using OnInputCallback = std::function; + using RenderCallback = std::function; + using RenderOverFunction = std::function; + + static constexpr u8 DefaultFadeAlphaIncrement = 35; + + protected: + bool loaded; + bool in_render_over; + RenderOverFunction render_over_fn; + bool is_shown; + u8 fade_alpha_increment; + i32 fade_alpha; + Layout::Ref lyt; + Overlay::Ref ovl; + u64 ovl_timeout_ms; + std::chrono::steady_clock::time_point ovl_start_time; + std::vector render_cbs; + OnInputCallback on_ipt_cb; + render::Renderer::Ref renderer; + PadState input_pad; + + public: + Application(render::Renderer::Ref renderer); + PU_SMART_CTOR(Application) + + inline void LoadLayout(Layout::Ref lyt) { + this->lyt = lyt; + } + + template + inline std::shared_ptr GetLayout() { + static_assert(std::is_base_of_v); + return std::static_pointer_cast(this->lyt); + } + + void Prepare(); + + // Force create a derived Application class which initializes everything here + virtual void OnLoad() = 0; + + inline void AddRenderCallback(RenderCallback render_cb) { + this->render_cbs.push_back(render_cb); + } + + inline void SetOnInput(OnInputCallback on_ipt_cb) { + this->on_ipt_cb = on_ipt_cb; + } + + inline i32 ShowDialog(Dialog::Ref &dialog) { + return dialog->Show(this); + } + + i32 CreateShowDialog(const std::string &title, const std::string &content, const std::vector &opts, const bool use_last_opt_as_cancel, const std::string &icon_path = ""); + + inline void StartOverlay(Overlay::Ref ovl) { + if(this->ovl == nullptr) { + this->ovl = ovl; + } + } + + void StartOverlayWithTimeout(Overlay::Ref ovl, const u64 ms); + void EndOverlay(); + void Show(); + + inline void ShowWithFadeIn() { + this->FadeIn(); + this->Show(); + } + + inline bool IsShown() { + return this->is_shown; + } + + inline bool CanBeShown() { + return this->loaded && (this->lyt != nullptr); + } + + bool CallForRender(); + bool CallForRenderWithRenderOver(RenderOverFunction render_over_fn); + void FadeIn(); + void FadeOut(); + + inline bool IsFadedIn() { + return this->fade_alpha > 0; + } + + inline void SetFadeAlphaIncrement(const u8 fade_alpha_increment) { + this->fade_alpha_increment = fade_alpha_increment; + } + + void OnRender(); + void Close(); + + inline void CloseWithFadeOut() { + this->FadeOut(); + this->Close(); + } + + inline u64 GetButtonsDown() { + return padGetButtonsDown(&this->input_pad); + } + + inline u64 GetButtonsUp() { + return padGetButtonsUp(&this->input_pad); + } + + inline u64 GetButtonsHeld() { + return padGetButtons(&this->input_pad); + } + + inline HidTouchScreenState GetTouchState() { + HidTouchScreenState state = {}; + hidGetTouchScreenStates(&state, 1); + return state; + } + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/ui_Container.hpp b/include/Plutonium/Plutonium/include/pu/ui/ui_Container.hpp new file mode 100644 index 0000000..6687589 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/ui_Container.hpp @@ -0,0 +1,88 @@ + +/* + + Plutonium library + + @file ui_Container.hpp + @brief A Container is a basic object which contains a bunch of Elements. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include +#include + +namespace pu::ui { + + class Container { + protected: + i32 x; + i32 y; + i32 w; + i32 h; + std::vector elems; + + public: + Container(const i32 x, const i32 y, const i32 width, const i32 height) : x(x), y(y), w(width), h(height), elems() {} + PU_SMART_CTOR(Container) + + inline void Add(elm::Element::Ref elem) { + this->elems.push_back(elem); + } + + inline elm::Element::Ref &At(const i32 idx) { + return this->elems.at(idx); + } + + inline bool Has(elm::Element::Ref &elem) { + return std::find(this->elems.begin(), this->elems.end(), elem) != this->elems.end(); + } + + inline void Clear() { + this->elems.clear(); + } + + inline size_t GetCount() { + return this->elems.size(); + } + + inline void SetX(const i32 x) { + this->x = x; + } + + inline i32 GetX() { + return this->x; + } + + inline void SetY(const i32 y) { + this->y = y; + } + + inline i32 GetY() { + return this->y; + } + + inline void SetWidth(const i32 width) { + this->w = width; + } + + inline i32 GetWidth() { + return this->w; + } + + inline void SetHeight(const i32 height) { + this->h = height; + } + + inline i32 GetHeight() { + return this->h; + } + + void PreRender(); + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/ui_Dialog.hpp b/include/Plutonium/Plutonium/include/pu/ui/ui_Dialog.hpp new file mode 100644 index 0000000..ec30a95 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/ui_Dialog.hpp @@ -0,0 +1,128 @@ + +/* + + Plutonium library + + @file ui_Dialog.hpp + @brief A Dialog is an easy way to ask the user to choose between several options. + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include + +namespace pu::ui { + + class Application; + + class Dialog { + public: + static constexpr Color DefaultTitleColor = { 0xA, 0xA, 0xA, 0xFF }; + static constexpr Color DefaultContentColor = { 0x14, 0x14, 0x14, 0xFF }; + static constexpr Color DefaultOptionColor = { 0xA, 0xA, 0xA, 0xFF }; + + static constexpr u32 DialogExtraBaseWidth = 250; + static constexpr u32 DialogBorderRadius = 35; + + static constexpr u32 SpaceBetweenOptions = 20; + + static constexpr u32 TitleExtraWidth = 90; + static constexpr u32 ContentExtraWidth = 90; + static constexpr u32 SpaceBetweenContentAndOptions = 140; + static constexpr u32 TitleTopMargin = 20; + + static constexpr u32 TitleX = 45; + static constexpr u32 TitleY = 55; + static constexpr u32 ContentX = 45; + static constexpr u32 ContentY = 140; + + static constexpr u32 IconExtraHeight = 25; + + static constexpr u32 OptionsBaseHorizontalMargin = 45; + static constexpr u32 OptionHeight = 60; + static constexpr u32 OptionHorizontalMargin = 30; + static constexpr u32 OptionBorderRadius = OptionHeight / 3; + static constexpr u32 OptionBottomMargin = 25; + + static constexpr u8 MaxScreenFadeAlpha = 125; + + static constexpr u32 IconMargin = 30; + + static inline constexpr Color MakeDialogColor(const u8 alpha) { + return { 0xE1, 0xE1, 0xE1, alpha }; + } + + static inline constexpr Color MakeOverColor(const u8 alpha) { + return { 0xB4, 0xB4, 0xC8, alpha }; + } + + static constexpr u8 OverAlphaIncrement = 48; + static constexpr u8 FadeAlphaIncrement = 25; + + private: + std::string title_font_name; + std::string cnt_font_name; + std::string opt_font_name; + std::string title; + std::string cnt; + sdl2::Texture title_tex; + sdl2::Texture cnt_tex; + std::vector opts; + std::vector opt_texs; + std::string cancel_opt; + u32 selected_opt_idx; + i32 selected_opt_over_alpha; + i32 prev_selected_opt_idx; + i32 prev_selected_opt_over_alpha; + bool user_cancelled; + sdl2::Texture icon_tex; + + public: + Dialog(const std::string &title, const std::string &content); + PU_SMART_CTOR(Dialog) + ~Dialog(); + + void AddOption(const std::string &opt_name); + + inline void SetCancelOption(const std::string &opt_name) { + this->cancel_opt = opt_name; + } + + inline void RemoveCancelOption() { + this->SetCancelOption(""); + } + + inline bool HasCancelOption() { + return !this->cancel_opt.empty(); + } + + void SetIcon(const std::string &icon_path); + + inline constexpr bool HasIcon() { + return this->icon_tex != nullptr; + } + + i32 Show(Application *app_ref); + + inline constexpr bool UserCancelled() { + return this->user_cancelled; + } + + inline bool IsOk() { + if(this->user_cancelled) { + return false; + } + + if(this->HasCancelOption() && (this->selected_opt_idx == (this->opt_texs.size() - 1))) { + return false; + } + + return true; + } + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/ui_Layout.hpp b/include/Plutonium/Plutonium/include/pu/ui/ui_Layout.hpp new file mode 100644 index 0000000..d804f6f --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/ui_Layout.hpp @@ -0,0 +1,82 @@ + +/* + + Plutonium library + + @file ui_Layout.hpp + @brief Contains pu::Layout class, the object used to render within applications + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include +#include + +namespace pu::ui { + + class Layout : public Container { + public: + using OnInputCallback = std::function; + using RenderCallback = std::function; + + static constexpr Color DefaultBackgroundColor = { 0xE1, 0xE1, 0xE1, 0xFF }; + + private: + bool has_image; + Color over_bg_color; + TouchPoint sim_touch_pos; + sdl2::Texture over_bg_tex; + OnInputCallback on_ipt; + std::vector render_cbs; + + public: + Layout() : Container(0, 0, render::ScreenWidth, render::ScreenHeight), has_image(false), over_bg_color(DefaultBackgroundColor), sim_touch_pos(), over_bg_tex(), on_ipt(), render_cbs() {} + PU_SMART_CTOR(Layout) + ~Layout(); + + inline bool HasChildren() { + return !this->elems.empty(); + } + + inline void SetOnInput(OnInputCallback on_ipt_cb) { + this->on_ipt = on_ipt_cb; + } + + inline OnInputCallback GetOnInput() { + return this->on_ipt; + } + + inline void AddRenderCallback(RenderCallback render_cb) { + this->render_cbs.push_back(render_cb); + } + + inline std::vector &GetRenderCallbacks() { + return this->render_cbs; + } + + inline bool HasBackgroundImage() { + return this->has_image; + } + + inline sdl2::Texture GetBackgroundImageTexture() { + return this->over_bg_tex; + } + + inline Color GetBackgroundColor() { + return this->over_bg_color; + } + + void SetBackgroundImage(const std::string &path); + void SetBackgroundColor(const Color clr); + + inline void SimulateTouchPosition(const TouchPoint sim_touch_pos) { + this->sim_touch_pos = sim_touch_pos; + } + + TouchPoint ConsumeSimulatedTouchPosition(); + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/ui_Overlay.hpp b/include/Plutonium/Plutonium/include/pu/ui/ui_Overlay.hpp new file mode 100644 index 0000000..e6cb302 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/ui_Overlay.hpp @@ -0,0 +1,53 @@ + +/* + + Plutonium library + + @file ui_Overlay.hpp + @brief An overlay is some kind of "pop-up", like notification messages or similar items + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +namespace pu::ui { + + class Overlay : public Container { + public: + static constexpr i32 DefaultRadius = 25; + static constexpr i32 MaxFadeAlpha = 200; + static constexpr i32 FadeAlphaVariation = 25; + + private: + i32 fade_a; + Color bg_clr; + i32 rad; + bool is_ending; + bool round; + + public: + Overlay(const i32 x, const i32 y, const i32 width, const i32 height, const Color bg_clr, const bool round = true, const i32 radius = DefaultRadius) : Container(x, y, width, height), fade_a(0), bg_clr(bg_clr), rad(radius), is_ending(false), round(round) {} + PU_SMART_CTOR(Overlay) + + inline void SetRadius(const i32 radius) { + this->rad = radius; + } + + inline i32 GetRadius() { + return this->rad; + } + + virtual void OnPreRender(render::Renderer::Ref &drawer) {} + virtual void OnPostRender(render::Renderer::Ref &drawer) {} + bool Render(render::Renderer::Ref &drawer); + + inline void NotifyEnding(const bool ending) { + this->is_ending = ending; + } + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/include/pu/ui/ui_Types.hpp b/include/Plutonium/Plutonium/include/pu/ui/ui_Types.hpp new file mode 100644 index 0000000..c289cd8 --- /dev/null +++ b/include/Plutonium/Plutonium/include/pu/ui/ui_Types.hpp @@ -0,0 +1,82 @@ + +/* + + Plutonium library + + @file ui_Types.hpp + @brief Several basic types helpful for UI and rendering, such as Color + @author XorTroll + + @copyright Plutonium project - an easy-to-use UI framework for Nintendo Switch homebrew + +*/ + +#pragma once +#include + +namespace pu::ui { + + // Font sizes Plutonium components use by default + + enum class DefaultFontSize : u32 { + Small, + Medium, + MediumLarge, + Large, + + Count + }; + + static inline constexpr std::array(DefaultFontSize::Count)> DefaultFontSizes = { 18, 20, 25, 30 }; + + inline std::string MakeDefaultFontName(const u32 font_size) { + return "DefaultFont@" + std::to_string(font_size); + } + + inline constexpr u32 GetDefaultFontSize(const DefaultFontSize kind) { + return DefaultFontSizes[static_cast(kind)]; + } + + inline std::string GetDefaultFont(const DefaultFontSize kind) { + return MakeDefaultFontName(GetDefaultFontSize(kind)); + } + + struct Color { + u8 r; + u8 g; + u8 b; + u8 a; + + constexpr Color() : r(0), g(0), b(0), a(0xFF) {} + constexpr Color(const u8 r, const u8 g, const u8 b, const u8 a) : r(r), g(g), b(b), a(a) {} + + static Color FromHex(const std::string &str_clr); + }; + + static inline constexpr bool TouchHitsRegion(const i32 touch_x, const i32 touch_y, const i32 region_x, const i32 region_y, const i32 region_w, const i32 region_h) { + return (touch_x >= region_x) && (touch_x < (region_x + region_w)) && (touch_y >= region_y) && (touch_y < (region_y + region_h)); + } + + constexpr u64 TouchPseudoKey = HidNpadButton_29; + + struct TouchPoint { + i32 x; + i32 y; + + constexpr TouchPoint() : x(-1), y(-1) {} + constexpr TouchPoint(const u32 x, const u32 y) : x(x), y(y) {} + + inline constexpr bool IsEmpty() const { + return (this->x < 0) && (this->y < 0); + } + + inline constexpr bool HitsRegion(const i32 region_x, const i32 region_y, const i32 region_w, const i32 region_h) const { + if(this->IsEmpty()) { + return false; + } + + return TouchHitsRegion(this->x, this->y, region_x, region_y, region_w, region_h); + } + }; + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/audio/audio_Music.cpp b/include/Plutonium/Plutonium/source/pu/audio/audio_Music.cpp new file mode 100644 index 0000000..2bb79d6 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/audio/audio_Music.cpp @@ -0,0 +1,58 @@ +#include + +namespace pu::audio { + + Music OpenMusic(const std::string &path) { + return Mix_LoadMUS(path.c_str()); + } + + void PlayMusic(Music mus, const i32 loops) { + Mix_PlayMusic(mus, loops); + } + + void PlayMusicWithFadeIn(Music mus, const i32 loops, const i32 ms) { + Mix_FadeInMusic(mus, loops, ms); + } + + bool IsPlayingMusic() { + return Mix_PlayingMusic(); + } + + void PauseMusic() { + Mix_PauseMusic(); + } + + void ResumeMusic() { + Mix_ResumeMusic(); + } + + void SetMusicVolume(const i32 vol) { + Mix_VolumeMusic(vol); + } + + i32 GetMusicVolume() { + return Mix_VolumeMusic(-1); + } + + void FadeOutMusic(const i32 ms) { + Mix_FadeOutMusic(ms); + } + + void RewindMusic() { + Mix_RewindMusic(); + } + + void StopMusic() { + Mix_HaltMusic(); + } + + void SetMusicPosition(const double sec) { + Mix_SetMusicPosition(sec); + } + + void DestroyMusic(Music &mus) { + Mix_FreeMusic(mus); + mus = nullptr; + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/audio/audio_Sfx.cpp b/include/Plutonium/Plutonium/source/pu/audio/audio_Sfx.cpp new file mode 100644 index 0000000..8d88153 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/audio/audio_Sfx.cpp @@ -0,0 +1,18 @@ +#include + +namespace pu::audio { + + Sfx LoadSfx(const std::string &path) { + return Mix_LoadWAV(path.c_str()); + } + + void PlaySfx(Sfx sfx) { + Mix_PlayChannel(-1, sfx, 0); + } + + void DestroySfx(Sfx &sfx) { + Mix_FreeChunk(sfx); + sfx = nullptr; + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/sdl2/sdl2_CustomTtf.c b/include/Plutonium/Plutonium/source/pu/sdl2/sdl2_CustomTtf.c new file mode 100644 index 0000000..5e84b13 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/sdl2/sdl2_CustomTtf.c @@ -0,0 +1,2254 @@ +/* + SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts + Copyright (C) 2001-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H +#include FT_OUTLINE_H +#include FT_STROKER_H +#include FT_GLYPH_H +#include FT_TRUETYPE_IDS_H + +#include + +/* FIXME: Right now we assume the gray-scale renderer Freetype is using + supports 256 shades of gray, but we should instead key off of num_grays + in the result FT_Bitmap after the FT_Render_Glyph() call. */ +#define NUM_GRAYS 256 + +/* Handy routines for converting from fixed point */ +#define FT_FLOOR(X) ((X & -64) / 64) +#define FT_CEIL(X) (((X + 63) & -64) / 64) + +#define CACHED_METRICS 0x10 +#define CACHED_BITMAP 0x01 +#define CACHED_PIXMAP 0x02 + +/* Cached glyph information */ +typedef struct cached_glyph { + int stored; + FT_UInt index; + FT_Bitmap bitmap; + FT_Bitmap pixmap; + int minx; + int maxx; + int miny; + int maxy; + int yoffset; + int advance; + Uint16 cached; +} c_glyph; + +/* The structure used to hold internal font information */ +struct _TTF_Font { + /* Freetype2 maintains all sorts of useful info itself */ + FT_Face face; + + /* We'll cache these ourselves */ + int height; + int ascent; + int descent; + int lineskip; + + /* The font style */ + int face_style; + int style; + int outline; + + /* Whether kerning is desired */ + int kerning; + + /* Extra width in glyph bounds for text styles */ + int glyph_overhang; + float glyph_italics; + + /* Information in the font for underlining */ + int underline_offset; + int underline_height; + + /* Cache for style-transformed glyphs */ + c_glyph *current; + c_glyph cache[257]; /* 257 is a prime */ + + /* We are responsible for closing the font stream */ + SDL_RWops *src; + int freesrc; + FT_Open_Args args; + + /* For non-scalable formats, we must remember which font index size */ + int font_size_family; + + /* really just flags passed into FT_Load_Glyph */ + int hinting; + + /* custom - pointer to the C++ class with the font maps, etc... */ + void *cpp_font_ref_ptr; +}; + +/* Handle a style only if the font does not already handle it */ +#define TTF_HANDLE_STYLE_BOLD(font) (((font)->style & TTF_STYLE_BOLD) && \ + !((font)->face_style & TTF_STYLE_BOLD)) +#define TTF_HANDLE_STYLE_ITALIC(font) (((font)->style & TTF_STYLE_ITALIC) && \ + !((font)->face_style & TTF_STYLE_ITALIC)) +#define TTF_HANDLE_STYLE_UNDERLINE(font) ((font)->style & TTF_STYLE_UNDERLINE) +#define TTF_HANDLE_STYLE_STRIKETHROUGH(font) ((font)->style & TTF_STYLE_STRIKETHROUGH) + +/* Font styles that does not impact glyph drawing */ +#define TTF_STYLE_NO_GLYPH_CHANGE (TTF_STYLE_UNDERLINE | TTF_STYLE_STRIKETHROUGH) + +/* The FreeType font engine/library */ +static FT_Library library; +static int TTF_initialized = 0; +static int TTF_byteswapped = 0; + +#define TTF_CHECKPOINTER(p, errval) \ + if ( !TTF_initialized ) { \ + TTF_SetError("Library not initialized"); \ + return errval; \ + } \ + if ( !p ) { \ + TTF_SetError("Passed a NULL pointer"); \ + return errval; \ + } + +/* Gets the top row of the underline. The outline + is taken into account. +*/ +static __inline__ int TTF_underline_top_row(TTF_Font *font) +{ + /* With outline, the underline_offset is underline_offset+outline. */ + /* So, we don't have to remove the top part of the outline height. */ + return font->ascent - font->underline_offset - 1; +} + +/* Gets the top row of the underline. for a given glyph. The outline + is taken into account. + Need to update row according to height difference between font and glyph: + font_value - font->ascent + glyph->maxy +*/ +static __inline__ int TTF_Glyph_underline_top_row(TTF_Font *font, c_glyph *glyph) +{ + return glyph->maxy - font->underline_offset - 1; +} + +/* Gets the bottom row of the underline. The outline + is taken into account. +*/ +static __inline__ int TTF_underline_bottom_row(TTF_Font *font) +{ + int row = TTF_underline_top_row(font) + font->underline_height; + if ( font->outline > 0 ) { + /* Add underline_offset outline offset and */ + /* the bottom part of the outline. */ + row += font->outline * 2; + } + return row; +} + +/* Gets the bottom row of the underline. for a given glyph. The outline + is taken into account. + Need to update row according to height difference between font and glyph: + font_value - font->ascent + glyph->maxy +*/ +static __inline__ int TTF_Glyph_underline_bottom_row(TTF_Font *font, c_glyph *glyph) +{ + return TTF_underline_bottom_row(font) - font->ascent + glyph->maxy; +} + +/* Gets the top row of the strikethrough. The outline + is taken into account. +*/ +static __inline__ int TTF_strikethrough_top_row(TTF_Font *font) +{ + /* With outline, the first text row is 'outline'. */ + /* So, we don't have to remove the top part of the outline height. */ + return font->height / 2; +} + +/* Gets the top row of the strikethrough for a given glyph. The outline + is taken into account. + Need to update row according to height difference between font and glyph: + font_value - font->ascent + glyph->maxy +*/ +static __inline__ int TTF_Glyph_strikethrough_top_row(TTF_Font *font, c_glyph *glyph) +{ + return TTF_strikethrough_top_row(font) - font->ascent + glyph->maxy; +} + +static void TTF_initLineMectrics(const TTF_Font *font, const SDL_Surface *textbuf, const int row, Uint8 **pdst, int *pheight) +{ + Uint8 *dst; + int height; + + dst = (Uint8 *)textbuf->pixels; + if ( row > 0 ) { + dst += row * textbuf->pitch; + } + + height = font->underline_height; + /* Take outline into account */ + if ( font->outline > 0 ) { + height += font->outline * 2; + } + *pdst = dst; + *pheight = height; +} + +/* Draw a solid line of underline_height (+ optional outline) + at the given row. The row value must take the + outline into account. +*/ +static void TTF_drawLine_Solid(const TTF_Font *font, const SDL_Surface *textbuf, const int row) +{ + int line; + Uint8 *dst_check = (Uint8*)textbuf->pixels + textbuf->pitch * textbuf->h; + Uint8 *dst; + int height; + + TTF_initLineMectrics(font, textbuf, row, &dst, &height); + + /* Draw line */ + for ( line=height; line>0 && dst < dst_check; --line ) { + /* 1 because 0 is the bg color */ + memset( dst, 1, textbuf->w ); + dst += textbuf->pitch; + } +} + +/* Draw a shaded line of underline_height (+ optional outline) + at the given row. The row value must take the + outline into account. +*/ +static void TTF_drawLine_Shaded(const TTF_Font *font, const SDL_Surface *textbuf, const int row) +{ + int line; + Uint8 *dst_check = (Uint8*)textbuf->pixels + textbuf->pitch * textbuf->h; + Uint8 *dst; + int height; + + TTF_initLineMectrics(font, textbuf, row, &dst, &height); + + /* Draw line */ + for ( line=height; line>0 && dst < dst_check; --line ) { + memset( dst, NUM_GRAYS - 1, textbuf->w ); + dst += textbuf->pitch; + } +} + +/* Draw a blended line of underline_height (+ optional outline) + at the given row. The row value must take the + outline into account. +*/ +static void TTF_drawLine_Blended(const TTF_Font *font, const SDL_Surface *textbuf, const int row, const Uint32 color) +{ + int line; + Uint32 *dst_check = (Uint32*)textbuf->pixels + textbuf->pitch/4 * textbuf->h; + Uint8 *dst8; /* destination, byte version */ + Uint32 *dst; + int height; + int col; + Uint32 pixel = color | 0xFF000000; /* Amask */ + + TTF_initLineMectrics(font, textbuf, row, &dst8, &height); + dst = (Uint32 *) dst8; + + /* Draw line */ + for ( line=height; line>0 && dst < dst_check; --line ) { + for ( col=0; col < textbuf->w; ++col ) { + dst[col] = pixel; + } + dst += textbuf->pitch/4; + } +} + +/* rcg06192001 get linked library's version. */ +const SDL_version *TTF_Linked_Version(void) +{ + static SDL_version linked_version; + SDL_TTF_VERSION(&linked_version); + return(&linked_version); +} + +/* This function tells the library whether UNICODE text is generally + byteswapped. A UNICODE BOM character at the beginning of a string + will override this setting for that string. + */ +void TTF_ByteSwappedUNICODE(int swapped) +{ + TTF_byteswapped = swapped; +} + +static void TTF_SetFTError(const char *msg, FT_Error error) +{ +#ifdef USE_FREETYPE_ERRORS +#undef FTERRORS_H +#define FT_ERRORDEF( e, v, s ) { e, s }, + static const struct + { + int err_code; + const char* err_msg; + } ft_errors[] = { +#include + }; + int i; + const char *err_msg; + char buffer[1024]; + + err_msg = NULL; + for ( i=0; i<((sizeof ft_errors)/(sizeof ft_errors[0])); ++i ) { + if ( error == ft_errors[i].err_code ) { + err_msg = ft_errors[i].err_msg; + break; + } + } + if ( ! err_msg ) { + err_msg = "unknown FreeType error"; + } + sprintf(buffer, "%s: %s", msg, err_msg); + TTF_SetError(buffer); +#else + TTF_SetError(msg); +#endif /* USE_FREETYPE_ERRORS */ +} + +int TTF_Init( void ) +{ + int status = 0; + + if ( ! TTF_initialized ) { + FT_Error error = FT_Init_FreeType( &library ); + if ( error ) { + TTF_SetFTError("Couldn't init FreeType engine", error); + status = -1; + } + } + if ( status == 0 ) { + ++TTF_initialized; + } + return status; +} + +static unsigned long RWread( + FT_Stream stream, + unsigned long offset, + unsigned char* buffer, + unsigned long count +) +{ + SDL_RWops *src; + + src = (SDL_RWops *)stream->descriptor.pointer; + SDL_RWseek( src, (int)offset, RW_SEEK_SET ); + if ( count == 0 ) { + return 0; + } + return (unsigned long)SDL_RWread( src, buffer, 1, (int)count ); +} + +TTF_Font* TTF_OpenFontIndexRW( SDL_RWops *src, int freesrc, int ptsize, long index ) +{ + TTF_Font* font; + FT_Error error; + FT_Face face; + FT_Fixed scale; + FT_Stream stream; + FT_CharMap found; + Sint64 position; + int i; + + if ( ! TTF_initialized ) { + TTF_SetError( "Library not initialized" ); + if ( src && freesrc ) { + SDL_RWclose( src ); + } + return NULL; + } + + if ( ! src ) { + TTF_SetError( "Passed a NULL font source" ); + return NULL; + } + + /* Check to make sure we can seek in this stream */ + position = SDL_RWtell(src); + if ( position < 0 ) { + TTF_SetError( "Can't seek in stream" ); + if ( freesrc ) { + SDL_RWclose( src ); + } + return NULL; + } + + font = (TTF_Font*) malloc(sizeof *font); + if ( font == NULL ) { + TTF_SetError( "Out of memory" ); + if ( freesrc ) { + SDL_RWclose( src ); + } + return NULL; + } + memset(font, 0, sizeof(*font)); + + font->src = src; + font->freesrc = freesrc; + + stream = (FT_Stream)malloc(sizeof(*stream)); + if ( stream == NULL ) { + TTF_SetError( "Out of memory" ); + TTF_CloseFont( font ); + return NULL; + } + memset(stream, 0, sizeof(*stream)); + + stream->read = RWread; + stream->descriptor.pointer = src; + stream->pos = (unsigned long)position; + stream->size = (unsigned long)(SDL_RWsize(src) - position); + + font->args.flags = FT_OPEN_STREAM; + font->args.stream = stream; + + error = FT_Open_Face( library, &font->args, index, &font->face ); + if ( error ) { + TTF_SetFTError( "Couldn't load font file", error ); + TTF_CloseFont( font ); + return NULL; + } + face = font->face; + + /* Set charmap for loaded font */ + found = 0; + for (i = 0; i < face->num_charmaps; i++) { + FT_CharMap charmap = face->charmaps[i]; + if ((charmap->platform_id == 3 && charmap->encoding_id == 1) /* Windows Unicode */ + || (charmap->platform_id == 3 && charmap->encoding_id == 0) /* Windows Symbol */ + || (charmap->platform_id == 2 && charmap->encoding_id == 1) /* ISO Unicode */ + || (charmap->platform_id == 0)) { /* Apple Unicode */ + found = charmap; + break; + } + } + if ( found ) { + /* If this fails, continue using the default charmap */ + FT_Set_Charmap(face, found); + } + + /* Make sure that our font face is scalable (global metrics) */ + if ( FT_IS_SCALABLE(face) ) { + /* Set the character size and use default DPI (72) */ + error = FT_Set_Char_Size( font->face, 0, ptsize * 64, 0, 0 ); + if ( error ) { + TTF_SetFTError( "Couldn't set font size", error ); + TTF_CloseFont( font ); + return NULL; + } + + /* Get the scalable font metrics for this font */ + scale = face->size->metrics.y_scale; + font->ascent = FT_CEIL(FT_MulFix(face->ascender, scale)); + font->descent = FT_CEIL(FT_MulFix(face->descender, scale)); + font->height = font->ascent - font->descent + /* baseline */ 1; + font->lineskip = FT_CEIL(FT_MulFix(face->height, scale)); + font->underline_offset = FT_FLOOR(FT_MulFix(face->underline_position, scale)); + font->underline_height = FT_FLOOR(FT_MulFix(face->underline_thickness, scale)); + + } else { + /* Non-scalable font case. ptsize determines which family + * or series of fonts to grab from the non-scalable format. + * It is not the point size of the font. + * */ + if ( ptsize >= font->face->num_fixed_sizes ) + ptsize = font->face->num_fixed_sizes - 1; + font->font_size_family = ptsize; + error = FT_Set_Pixel_Sizes( face, + face->available_sizes[ptsize].width, + face->available_sizes[ptsize].height ); + if ( error ) { + TTF_SetFTError( "Couldn't set font size", error ); + TTF_CloseFont( font ); + return NULL; + } + + /* With non-scalale fonts, Freetype2 likes to fill many of the + * font metrics with the value of 0. The size of the + * non-scalable fonts must be determined differently + * or sometimes cannot be determined. + * */ + font->ascent = face->available_sizes[ptsize].height; + font->descent = 0; + font->height = face->available_sizes[ptsize].height; + font->lineskip = FT_CEIL(font->ascent); + font->underline_offset = FT_FLOOR(face->underline_position); + font->underline_height = FT_FLOOR(face->underline_thickness); + } + + if ( font->underline_height < 1 ) { + font->underline_height = 1; + } + +#ifdef DEBUG_FONTS + printf("Font metrics:\n"); + printf("\tascent = %d, descent = %d\n", + font->ascent, font->descent); + printf("\theight = %d, lineskip = %d\n", + font->height, font->lineskip); + printf("\tunderline_offset = %d, underline_height = %d\n", + font->underline_offset, font->underline_height); + printf("\tunderline_top_row = %d, strikethrough_top_row = %d\n", + TTF_underline_top_row(font), TTF_strikethrough_top_row(font)); +#endif + + /* Initialize the font face style */ + font->face_style = TTF_STYLE_NORMAL; + if ( font->face->style_flags & FT_STYLE_FLAG_BOLD ) { + font->face_style |= TTF_STYLE_BOLD; + } + if ( font->face->style_flags & FT_STYLE_FLAG_ITALIC ) { + font->face_style |= TTF_STYLE_ITALIC; + } + + /* Set the default font style */ + font->style = font->face_style; + font->outline = 0; + font->kerning = 1; + font->glyph_overhang = face->size->metrics.y_ppem / 10; + /* x offset = cos(((90.0-12)/360)*2*M_PI), or 12 degree angle */ + font->glyph_italics = 0.207f; + font->glyph_italics *= font->height; + + return font; +} + +TTF_Font* TTF_OpenFontRW( SDL_RWops *src, int freesrc, int ptsize ) +{ + return TTF_OpenFontIndexRW(src, freesrc, ptsize, 0); +} + +TTF_Font* TTF_OpenFontIndex( const char *file, int ptsize, long index ) +{ + SDL_RWops *rw = SDL_RWFromFile(file, "rb"); + if ( rw == NULL ) { + TTF_SetError(SDL_GetError()); + return NULL; + } + return TTF_OpenFontIndexRW(rw, 1, ptsize, index); +} + +TTF_Font* TTF_OpenFont( const char *file, int ptsize ) +{ + return TTF_OpenFontIndex(file, ptsize, 0); +} + +static void Flush_Glyph( c_glyph* glyph ) +{ + glyph->stored = 0; + glyph->index = 0; + if ( glyph->bitmap.buffer ) { + free( glyph->bitmap.buffer ); + glyph->bitmap.buffer = 0; + } + if ( glyph->pixmap.buffer ) { + free( glyph->pixmap.buffer ); + glyph->pixmap.buffer = 0; + } + glyph->cached = 0; +} + +static void Flush_Cache( TTF_Font* font ) +{ + int i; + int size = sizeof( font->cache ) / sizeof( font->cache[0] ); + + for ( i = 0; i < size; ++i ) { + if ( font->cache[i].cached ) { + Flush_Glyph( &font->cache[i] ); + } + + } +} + +static FT_Error Load_Glyph( TTF_Font* font, Uint16 ch, c_glyph* cached, int want ) +{ + FT_Face face; + FT_Error error; + FT_GlyphSlot glyph; + FT_Glyph_Metrics* metrics; + FT_Outline* outline; + + if ( !font || !font->face ) { + return FT_Err_Invalid_Handle; + } + + face = font->face; + + /* Load the glyph */ + if ( ! cached->index ) { + cached->index = FT_Get_Char_Index( face, ch ); + } + error = FT_Load_Glyph( face, cached->index, FT_LOAD_DEFAULT | font->hinting); + if ( error ) { + return error; + } + + /* Get our glyph shortcuts */ + glyph = face->glyph; + metrics = &glyph->metrics; + outline = &glyph->outline; + + /* Get the glyph metrics if desired */ + if ( (want & CACHED_METRICS) && !(cached->stored & CACHED_METRICS) ) { + if ( FT_IS_SCALABLE( face ) ) { + /* Get the bounding box */ + cached->minx = FT_FLOOR(metrics->horiBearingX); + cached->maxx = FT_CEIL(metrics->horiBearingX + metrics->width); + cached->maxy = FT_FLOOR(metrics->horiBearingY); + cached->miny = cached->maxy - FT_CEIL(metrics->height); + cached->yoffset = font->ascent - cached->maxy; + cached->advance = FT_CEIL(metrics->horiAdvance); + } else { + /* Get the bounding box for non-scalable format. + * Again, freetype2 fills in many of the font metrics + * with the value of 0, so some of the values we + * need must be calculated differently with certain + * assumptions about non-scalable formats. + * */ + cached->minx = FT_FLOOR(metrics->horiBearingX); + cached->maxx = FT_CEIL(metrics->horiBearingX + metrics->width); + cached->maxy = FT_FLOOR(metrics->horiBearingY); + cached->miny = cached->maxy - FT_CEIL(face->available_sizes[font->font_size_family].height); + cached->yoffset = 0; + cached->advance = FT_CEIL(metrics->horiAdvance); + } + + /* Adjust for bold and italic text */ + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + cached->maxx += font->glyph_overhang; + } + if ( TTF_HANDLE_STYLE_ITALIC(font) ) { + cached->maxx += (int)ceil(font->glyph_italics); + } + cached->stored |= CACHED_METRICS; + } + + if ( ((want & CACHED_BITMAP) && !(cached->stored & CACHED_BITMAP)) || + ((want & CACHED_PIXMAP) && !(cached->stored & CACHED_PIXMAP)) ) { + int mono = (want & CACHED_BITMAP); + int i; + FT_Bitmap* src; + FT_Bitmap* dst; + FT_Glyph bitmap_glyph = NULL; + + /* Handle the italic style */ + if ( TTF_HANDLE_STYLE_ITALIC(font) ) { + FT_Matrix shear; + + shear.xx = 1 << 16; + shear.xy = (int) ( font->glyph_italics * ( 1 << 16 ) ) / font->height; + shear.yx = 0; + shear.yy = 1 << 16; + + FT_Outline_Transform( outline, &shear ); + } + + /* Render as outline */ + if ( (font->outline > 0) && glyph->format != FT_GLYPH_FORMAT_BITMAP ) { + FT_Stroker stroker; + FT_Get_Glyph( glyph, &bitmap_glyph ); + error = FT_Stroker_New( library, &stroker ); + if ( error ) { + return error; + } + FT_Stroker_Set( stroker, font->outline * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0 ); + FT_Glyph_Stroke( &bitmap_glyph, stroker, 1 /* delete the original glyph */ ); + FT_Stroker_Done( stroker ); + /* Render the glyph */ + error = FT_Glyph_To_Bitmap( &bitmap_glyph, mono ? ft_render_mode_mono : ft_render_mode_normal, 0, 1 ); + if ( error ) { + FT_Done_Glyph( bitmap_glyph ); + return error; + } + src = &((FT_BitmapGlyph)bitmap_glyph)->bitmap; + } else { + /* Render the glyph */ + error = FT_Render_Glyph( glyph, mono ? ft_render_mode_mono : ft_render_mode_normal ); + if ( error ) { + return error; + } + src = &glyph->bitmap; + } + /* Copy over information to cache */ + if ( mono ) { + dst = &cached->bitmap; + } else { + dst = &cached->pixmap; + } + memcpy( dst, src, sizeof( *dst ) ); + + /* FT_Render_Glyph() and .fon fonts always generate a + * two-color (black and white) glyphslot surface, even + * when rendered in ft_render_mode_normal. */ + /* FT_IS_SCALABLE() means that the font is in outline format, + * but does not imply that outline is rendered as 8-bit + * grayscale, because embedded bitmap/graymap is preferred + * (see FT_LOAD_DEFAULT section of FreeType2 API Reference). + * FT_Render_Glyph() canreturn two-color bitmap or 4/16/256- + * color graymap according to the format of embedded bitmap/ + * graymap. */ + if ( src->pixel_mode == FT_PIXEL_MODE_MONO ) { + dst->pitch *= 8; + } else if ( src->pixel_mode == FT_PIXEL_MODE_GRAY2 ) { + dst->pitch *= 4; + } else if ( src->pixel_mode == FT_PIXEL_MODE_GRAY4 ) { + dst->pitch *= 2; + } + + /* Adjust for bold and italic text */ + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + int bump = font->glyph_overhang; + dst->pitch += bump; + dst->width += bump; + } + if ( TTF_HANDLE_STYLE_ITALIC(font) ) { + int bump = (int)ceil(font->glyph_italics); + dst->pitch += bump; + dst->width += bump; + } + + if (dst->rows != 0) { + dst->buffer = (unsigned char *)malloc( dst->pitch * dst->rows ); + if ( !dst->buffer ) { + return FT_Err_Out_Of_Memory; + } + memset( dst->buffer, 0, dst->pitch * dst->rows ); + + for ( i = 0; i < src->rows; i++ ) { + int soffset = i * src->pitch; + int doffset = i * dst->pitch; + if ( mono ) { + unsigned char *srcp = src->buffer + soffset; + unsigned char *dstp = dst->buffer + doffset; + int j; + if ( src->pixel_mode == FT_PIXEL_MODE_MONO ) { + for ( j = 0; j < src->width; j += 8 ) { + unsigned char c = *srcp++; + *dstp++ = (c&0x80) >> 7; + c <<= 1; + *dstp++ = (c&0x80) >> 7; + c <<= 1; + *dstp++ = (c&0x80) >> 7; + c <<= 1; + *dstp++ = (c&0x80) >> 7; + c <<= 1; + *dstp++ = (c&0x80) >> 7; + c <<= 1; + *dstp++ = (c&0x80) >> 7; + c <<= 1; + *dstp++ = (c&0x80) >> 7; + c <<= 1; + *dstp++ = (c&0x80) >> 7; + } + } else if ( src->pixel_mode == FT_PIXEL_MODE_GRAY2 ) { + for ( j = 0; j < src->width; j += 4 ) { + unsigned char c = *srcp++; + *dstp++ = (((c&0xA0) >> 6) >= 0x2) ? 1 : 0; + c <<= 2; + *dstp++ = (((c&0xA0) >> 6) >= 0x2) ? 1 : 0; + c <<= 2; + *dstp++ = (((c&0xA0) >> 6) >= 0x2) ? 1 : 0; + c <<= 2; + *dstp++ = (((c&0xA0) >> 6) >= 0x2) ? 1 : 0; + } + } else if ( src->pixel_mode == FT_PIXEL_MODE_GRAY4 ) { + for ( j = 0; j < src->width; j += 2 ) { + unsigned char c = *srcp++; + *dstp++ = (((c&0xF0) >> 4) >= 0x8) ? 1 : 0; + c <<= 4; + *dstp++ = (((c&0xF0) >> 4) >= 0x8) ? 1 : 0; + } + } else { + for ( j = 0; j < src->width; j++ ) { + unsigned char c = *srcp++; + *dstp++ = (c >= 0x80) ? 1 : 0; + } + } + } else if ( src->pixel_mode == FT_PIXEL_MODE_MONO ) { + /* This special case wouldn't + * be here if the FT_Render_Glyph() + * function wasn't buggy when it tried + * to render a .fon font with 256 + * shades of gray. Instead, it + * returns a black and white surface + * and we have to translate it back + * to a 256 gray shaded surface. + * */ + unsigned char *srcp = src->buffer + soffset; + unsigned char *dstp = dst->buffer + doffset; + unsigned char c; + int j, k; + for ( j = 0; j < src->width; j += 8) { + c = *srcp++; + for (k = 0; k < 8; ++k) { + if ((c&0x80) >> 7) { + *dstp++ = NUM_GRAYS - 1; + } else { + *dstp++ = 0x00; + } + c <<= 1; + } + } + } else if ( src->pixel_mode == FT_PIXEL_MODE_GRAY2 ) { + unsigned char *srcp = src->buffer + soffset; + unsigned char *dstp = dst->buffer + doffset; + unsigned char c; + int j, k; + for ( j = 0; j < src->width; j += 4 ) { + c = *srcp++; + for ( k = 0; k < 4; ++k ) { + if ((c&0xA0) >> 6) { + *dstp++ = NUM_GRAYS * ((c&0xA0) >> 6) / 3 - 1; + } else { + *dstp++ = 0x00; + } + c <<= 2; + } + } + } else if ( src->pixel_mode == FT_PIXEL_MODE_GRAY4 ) { + unsigned char *srcp = src->buffer + soffset; + unsigned char *dstp = dst->buffer + doffset; + unsigned char c; + int j, k; + for ( j = 0; j < src->width; j += 2 ) { + c = *srcp++; + for ( k = 0; k < 2; ++k ) { + if ((c&0xF0) >> 4) { + *dstp++ = NUM_GRAYS * ((c&0xF0) >> 4) / 15 - 1; + } else { + *dstp++ = 0x00; + } + c <<= 4; + } + } + } else { + memcpy(dst->buffer+doffset, + src->buffer+soffset, src->pitch); + } + } + } + + /* Handle the bold style */ + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + int row; + int col; + int offset; + int pixel; + Uint8* pixmap; + + /* The pixmap is a little hard, we have to add and clamp */ + for ( row = dst->rows - 1; row >= 0; --row ) { + pixmap = (Uint8*) dst->buffer + row * dst->pitch; + for ( offset=1; offset <= font->glyph_overhang; ++offset ) { + for ( col = dst->width - 1; col > 0; --col ) { + if ( mono ) { + pixmap[col] |= pixmap[col-1]; + } else { + pixel = (pixmap[col] + pixmap[col-1]); + if ( pixel > NUM_GRAYS - 1 ) { + pixel = NUM_GRAYS - 1; + } + pixmap[col] = (Uint8) pixel; + } + } + } + } + } + + /* Mark that we rendered this format */ + if ( mono ) { + cached->stored |= CACHED_BITMAP; + } else { + cached->stored |= CACHED_PIXMAP; + } + + /* Free outlined glyph */ + if ( bitmap_glyph ) { + FT_Done_Glyph( bitmap_glyph ); + } + } + + /* We're done, mark this glyph cached */ + cached->cached = ch; + + return 0; +} + +static FT_Error Find_Glyph( TTF_Font* font, Uint16 ch, int want ) +{ + int retval = 0; + int hsize = sizeof( font->cache ) / sizeof( font->cache[0] ); + + int h = ch % hsize; + font->current = &font->cache[h]; + + if (font->current->cached != ch) + Flush_Glyph( font->current ); + + if ( (font->current->stored & want) != want ) { + retval = Load_Glyph( font, ch, font->current, want ); + } + return retval; +} + +void TTF_CloseFont( TTF_Font* font ) +{ + if ( font ) { + Flush_Cache( font ); + if ( font->face ) { + FT_Done_Face( font->face ); + } + if ( font->args.stream ) { + free( font->args.stream ); + } + if ( font->freesrc ) { + SDL_RWclose( font->src ); + } + free( font ); + } +} + +/* Gets the number of bytes used by a null terminated UCS2 string */ +static __inline__ size_t UCS2_len(const Uint16 *text) +{ + size_t count = 0; + while (*text++) { + ++count; + } + return count * sizeof(*text); +} + +/* Convert a Latin-1 string to a UTF-8 string */ +static void LATIN1_to_UTF8(const char *src, Uint8 *dst) +{ + while (*src) { + Uint8 ch = *(Uint8*)src++; + if (ch <= 0x7F) { + *dst++ = ch; + } else { + *dst++ = 0xC0 | ((ch >> 6) & 0x1F); + *dst++ = 0x80 | (ch & 0x3F); + } + } + *dst = '\0'; +} + +/* Convert a UCS-2 string to a UTF-8 string */ +static void UCS2_to_UTF8(const Uint16 *src, Uint8 *dst) +{ + int swapped = TTF_byteswapped; + + while (*src) { + Uint16 ch = *(Uint16*)src++; + if (ch == UNICODE_BOM_NATIVE) { + swapped = 0; + continue; + } + if (ch == UNICODE_BOM_SWAPPED) { + swapped = 1; + continue; + } + if (swapped) { + ch = SDL_Swap16(ch); + } + if (ch <= 0x7F) { + *dst++ = (Uint8) ch; + } else if (ch <= 0x7FF) { + *dst++ = 0xC0 | (Uint8) ((ch >> 6) & 0x1F); + *dst++ = 0x80 | (Uint8) (ch & 0x3F); + } else { + *dst++ = 0xE0 | (Uint8) ((ch >> 12) & 0x0F); + *dst++ = 0x80 | (Uint8) ((ch >> 6) & 0x3F); + *dst++ = 0x80 | (Uint8) (ch & 0x3F); + } + } + *dst = '\0'; +} + +/* Gets a unicode value from a UTF-8 encoded string and advance the string */ +#define UNKNOWN_UNICODE 0xFFFD +static Uint32 UTF8_getch(const char **src, size_t *srclen) +{ + const Uint8 *p = *(const Uint8**)src; + size_t left = 0; + // SDL_bool overlong = SDL_FALSE; + SDL_bool underflow = SDL_FALSE; + Uint32 ch = UNKNOWN_UNICODE; + + if (*srclen == 0) { + return UNKNOWN_UNICODE; + } + if (p[0] >= 0xFC) { + if ((p[0] & 0xFE) == 0xFC) { + if (p[0] == 0xFC && (p[1] & 0xFC) == 0x80) { + // overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x01); + left = 5; + } + } else if (p[0] >= 0xF8) { + if ((p[0] & 0xFC) == 0xF8) { + if (p[0] == 0xF8 && (p[1] & 0xF8) == 0x80) { + // overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x03); + left = 4; + } + } else if (p[0] >= 0xF0) { + if ((p[0] & 0xF8) == 0xF0) { + if (p[0] == 0xF0 && (p[1] & 0xF0) == 0x80) { + // overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x07); + left = 3; + } + } else if (p[0] >= 0xE0) { + if ((p[0] & 0xF0) == 0xE0) { + if (p[0] == 0xE0 && (p[1] & 0xE0) == 0x80) { + // overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x0F); + left = 2; + } + } else if (p[0] >= 0xC0) { + if ((p[0] & 0xE0) == 0xC0) { + if ((p[0] & 0xDE) == 0xC0) { + // overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x1F); + left = 1; + } + } else { + if ((p[0] & 0x80) == 0x00) { + ch = (Uint32) p[0]; + } + } + ++*src; + --*srclen; + while (left > 0 && *srclen > 0) { + ++p; + if ((p[0] & 0xC0) != 0x80) { + ch = UNKNOWN_UNICODE; + break; + } + ch <<= 6; + ch |= (p[0] & 0x3F); + ++*src; + --*srclen; + --left; + } + if (left > 0) { + underflow = SDL_TRUE; + } + /* Technically overlong sequences are invalid and should not be interpreted. + However, it doesn't cause a security risk here and I don't see any harm in + displaying them. The application is responsible for any other side effects + of allowing overlong sequences (e.g. string compares failing, etc.) + See bug 1931 for sample input that triggers this. + */ + /*if (overlong) return UNKNOWN_UNICODE;*/ + if (underflow || + (ch >= 0xD800 && ch <= 0xDFFF) || + (ch == 0xFFFE || ch == 0xFFFF) || ch > 0x10FFFF) { + ch = UNKNOWN_UNICODE; + } + return ch; +} + +int TTF_FontHeight(const TTF_Font *font) +{ + return(font->height); +} + +int TTF_FontAscent(const TTF_Font *font) +{ + return(font->ascent); +} + +int TTF_FontDescent(const TTF_Font *font) +{ + return(font->descent); +} + +int TTF_FontLineSkip(const TTF_Font *font) +{ + return(font->lineskip); +} + +int TTF_GetFontKerning(const TTF_Font *font) +{ + return(font->kerning); +} + +void TTF_SetFontKerning(TTF_Font *font, int allowed) +{ + font->kerning = allowed; +} + +long TTF_FontFaces(const TTF_Font *font) +{ + return(font->face->num_faces); +} + +int TTF_FontFaceIsFixedWidth(const TTF_Font *font) +{ + return(FT_IS_FIXED_WIDTH(font->face)); +} + +char *TTF_FontFaceFamilyName(const TTF_Font *font) +{ + return(font->face->family_name); +} + +char *TTF_FontFaceStyleName(const TTF_Font *font) +{ + return(font->face->style_name); +} + +int TTF_GlyphIsProvided(const TTF_Font *font, Uint16 ch) +{ + return(FT_Get_Char_Index(font->face, ch)); +} + +int TTF_GlyphMetrics(TTF_Font *font, Uint16 ch, + int* minx, int* maxx, int* miny, int* maxy, int* advance) +{ + FT_Error error; + + error = Find_Glyph(font, ch, CACHED_METRICS); + if ( error ) { + TTF_SetFTError("Couldn't find glyph", error); + return -1; + } + + if ( minx ) { + *minx = font->current->minx; + } + if ( maxx ) { + *maxx = font->current->maxx; + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + *maxx += font->glyph_overhang; + } + } + if ( miny ) { + *miny = font->current->miny; + } + if ( maxy ) { + *maxy = font->current->maxy; + } + if ( advance ) { + *advance = font->current->advance; + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + *advance += font->glyph_overhang; + } + } + return 0; +} + +int TTF_SizeText(TTF_Font *font, const char *text, int *w, int *h) +{ + int status = -1; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, -1); + + utf8 = SDL_stack_alloc(Uint8, SDL_strlen(text)*2+1); + if ( utf8 ) { + LATIN1_to_UTF8(text, utf8); + status = TTF_SizeUTF8(font, (char *)utf8, w, h); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return status; +} + +int TTF_SizeUTF8(TTF_Font *ttf_font, const char *text, int *w, int *h) +{ + int status; + int x, z; + int minx, maxx; + int miny, maxy; + c_glyph *glyph; + FT_Error error; + FT_Long use_kerning; + FT_UInt prev_index = 0; + int outline_delta = 0; + size_t textlen; + + TTF_CHECKPOINTER(text, -1); + + /* Initialize everything to 0 */ + status = 0; + minx = maxx = 0; + miny = maxy = 0; + + TTF_Font *orig_font = ttf_font; + TTF_Font *font = orig_font; + + /* Load each character and sum it's bounding box */ + textlen = SDL_strlen(text); + x= 0; + while ( textlen > 0 ) { + Uint16 c = UTF8_getch(&text, &textlen); + + /* custom */ + font = TTF_CppWrap_FindValidFont(orig_font, c); + + /* check kerning */ + use_kerning = FT_HAS_KERNING( font->face ) && font->kerning; + + /* Init outline handling */ + if ( font->outline > 0 ) { + outline_delta = font->outline * 2; + } + + if ( c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED ) { + continue; + } + + error = Find_Glyph(font, c, CACHED_METRICS); + if ( error ) { + TTF_SetFTError("Couldn't find glyph", error); + return -1; + } + glyph = font->current; + + /* handle kerning */ + if ( use_kerning && prev_index && glyph->index ) { + FT_Vector delta; + FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); + x += delta.x >> 6; + } + +#if 0 + if ( (ch == text) && (glyph->minx < 0) ) { + /* Fixes the texture wrapping bug when the first letter + * has a negative minx value or horibearing value. The entire + * bounding box must be adjusted to be bigger so the entire + * letter can fit without any texture corruption or wrapping. + * + * Effects: First enlarges bounding box. + * Second, xstart has to start ahead of its normal spot in the + * negative direction of the negative minx value. + * (pushes everything to the right). + * + * This will make the memory copy of the glyph bitmap data + * work out correctly. + * */ + z -= glyph->minx; + } +#endif + + z = x + glyph->minx; + if ( minx > z ) { + minx = z; + } + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + x += font->glyph_overhang; + } + if ( glyph->advance > glyph->maxx ) { + z = x + glyph->advance; + } else { + z = x + glyph->maxx; + } + if ( maxx < z ) { + maxx = z; + } + x += glyph->advance; + + if ( glyph->miny < miny ) { + miny = glyph->miny; + } + if ( glyph->maxy > maxy ) { + maxy = glyph->maxy; + } + prev_index = glyph->index; + } + + /* Fill the bounds rectangle */ + if ( w ) { + /* Add outline extra width */ + *w = (maxx - minx) + outline_delta; + } + if ( h ) { + /* Some fonts descend below font height (FletcherGothicFLF) */ + /* Add outline extra height */ + *h = (font->ascent - miny) + outline_delta; + if ( *h < font->height ) { + *h = font->height; + } + /* Update height according to the needs of the underline style */ + if ( TTF_HANDLE_STYLE_UNDERLINE(font) ) { + int bottom_row = TTF_underline_bottom_row(font); + if ( *h < bottom_row ) { + *h = bottom_row; + } + } + } + return status; +} + +int TTF_SizeUNICODE(TTF_Font *font, const Uint16 *text, int *w, int *h) +{ + int status = -1; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, -1); + + utf8 = SDL_stack_alloc(Uint8, UCS2_len(text)*3+1); + if ( utf8 ) { + UCS2_to_UTF8(text, utf8); + status = TTF_SizeUTF8(font, (char *)utf8, w, h); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return status; +} + +SDL_Surface *TTF_RenderText_Solid(TTF_Font *font, + const char *text, SDL_Color fg) +{ + SDL_Surface *surface = NULL; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, NULL); + + utf8 = SDL_stack_alloc(Uint8, SDL_strlen(text)*2+1); + if ( utf8 ) { + LATIN1_to_UTF8(text, utf8); + surface = TTF_RenderUTF8_Solid(font, (char *)utf8, fg); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return surface; +} + +SDL_Surface *TTF_RenderUTF8_Solid(TTF_Font *ttf_font, + const char *text, SDL_Color fg) +{ + SDL_bool first; + int xstart; + int width; + int height; + SDL_Surface* textbuf; + SDL_Palette* palette; + Uint8* src; + Uint8* dst; + Uint8 *dst_check; + int row, col; + c_glyph *glyph; + + FT_Bitmap *current; + FT_Error error; + FT_Long use_kerning; + FT_UInt prev_index = 0; + size_t textlen; + + TTF_CHECKPOINTER(text, NULL); + + /* Get the dimensions of the text surface */ + if ( ( TTF_SizeUTF8(ttf_font, text, &width, &height) < 0 ) || !width ) { + TTF_SetError( "Text has zero width" ); + return NULL; + } + + /* Create the target surface */ + textbuf = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 8, 0, 0, 0, 0); + if ( textbuf == NULL ) { + return NULL; + } + + /* Adding bound checking to avoid all kinds of memory corruption errors + that may occur. */ + dst_check = (Uint8*)textbuf->pixels + textbuf->pitch * textbuf->h; + + /* Fill the palette with the foreground color */ + palette = textbuf->format->palette; + palette->colors[0].r = 255 - fg.r; + palette->colors[0].g = 255 - fg.g; + palette->colors[0].b = 255 - fg.b; + palette->colors[1].r = fg.r; + palette->colors[1].g = fg.g; + palette->colors[1].b = fg.b; + SDL_SetColorKey( textbuf, SDL_TRUE, 0 ); + + TTF_Font *orig_font = ttf_font; + TTF_Font *font = orig_font; + + /* Load and render each character */ + textlen = SDL_strlen(text); + first = SDL_TRUE; + xstart = 0; + while ( textlen > 0 ) { + Uint16 c = UTF8_getch(&text, &textlen); + if ( c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED ) { + continue; + } + + font = TTF_CppWrap_FindValidFont(orig_font, c); + + /* check kerning */ + use_kerning = FT_HAS_KERNING( font->face ) && font->kerning; + + error = Find_Glyph(font, c, CACHED_METRICS|CACHED_BITMAP); + if ( error ) { + TTF_SetFTError("Couldn't find glyph", error); + SDL_FreeSurface( textbuf ); + return NULL; + } + glyph = font->current; + current = &glyph->bitmap; + /* Ensure the width of the pixmap is correct. On some cases, + * freetype may report a larger pixmap than possible.*/ + width = current->width; + if (font->outline <= 0 && width > glyph->maxx - glyph->minx) { + width = glyph->maxx - glyph->minx; + } + /* do kerning, if possible AC-Patch */ + if ( use_kerning && prev_index && glyph->index ) { + FT_Vector delta; + FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); + xstart += delta.x >> 6; + } + /* Compensate for wrap around bug with negative minx's */ + if ( first && (glyph->minx < 0) ) { + xstart -= glyph->minx; + } + first = SDL_FALSE; + + for ( row = 0; row < current->rows; ++row ) { + /* Make sure we don't go either over, or under the + * limit */ + if ( row+glyph->yoffset < 0 ) { + continue; + } + if ( row+glyph->yoffset >= textbuf->h ) { + continue; + } + dst = (Uint8*) textbuf->pixels + + (row+glyph->yoffset) * textbuf->pitch + + xstart + glyph->minx; + src = current->buffer + row * current->pitch; + + for ( col=width; col>0 && dst < dst_check; --col ) { + *dst++ |= *src++; + } + } + + xstart += glyph->advance; + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + xstart += font->glyph_overhang; + } + prev_index = glyph->index; + } + + /* Handle the underline style */ + if ( TTF_HANDLE_STYLE_UNDERLINE(font) ) { + row = TTF_underline_top_row(font); + TTF_drawLine_Solid(font, textbuf, row); + } + + /* Handle the strikethrough style */ + if ( TTF_HANDLE_STYLE_STRIKETHROUGH(font) ) { + row = TTF_strikethrough_top_row(font); + TTF_drawLine_Solid(font, textbuf, row); + } + return textbuf; +} + +SDL_Surface *TTF_RenderUNICODE_Solid(TTF_Font *font, + const Uint16 *text, SDL_Color fg) +{ + SDL_Surface *surface = NULL; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, NULL); + + utf8 = SDL_stack_alloc(Uint8, UCS2_len(text)*3+1); + if ( utf8 ) { + UCS2_to_UTF8(text, utf8); + surface = TTF_RenderUTF8_Solid(font, (char *)utf8, fg); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return surface; +} + +SDL_Surface *TTF_RenderGlyph_Solid(TTF_Font *font, Uint16 ch, SDL_Color fg) +{ + Uint16 ucs2[2] = { ch, 0 }; + Uint8 utf8[4]; + + UCS2_to_UTF8(ucs2, utf8); + return TTF_RenderUTF8_Solid(font, (char *)utf8, fg); +} + +SDL_Surface *TTF_RenderText_Shaded(TTF_Font *font, + const char *text, SDL_Color fg, SDL_Color bg) +{ + SDL_Surface *surface = NULL; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, NULL); + + utf8 = SDL_stack_alloc(Uint8, SDL_strlen(text)*2+1); + if ( utf8 ) { + LATIN1_to_UTF8(text, utf8); + surface = TTF_RenderUTF8_Shaded(font, (char *)utf8, fg, bg); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return surface; +} + +/* Convert the UTF-8 text to UNICODE and render it +*/ +SDL_Surface *TTF_RenderUTF8_Shaded(TTF_Font *ttf_font, + const char *text, SDL_Color fg, SDL_Color bg) +{ + SDL_bool first; + int xstart; + int width; + int height; + SDL_Surface* textbuf; + SDL_Palette* palette; + int index; + int rdiff; + int gdiff; + int bdiff; + Uint8* src; + Uint8* dst; + Uint8* dst_check; + int row, col; + FT_Bitmap* current; + c_glyph *glyph; + FT_Error error; + FT_Long use_kerning; + FT_UInt prev_index = 0; + size_t textlen; + + TTF_CHECKPOINTER(text, NULL); + + /* Get the dimensions of the text surface */ + if ( ( TTF_SizeUTF8(ttf_font, text, &width, &height) < 0 ) || !width ) { + TTF_SetError("Text has zero width"); + return NULL; + } + + /* Create the target surface */ + textbuf = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 8, 0, 0, 0, 0); + if ( textbuf == NULL ) { + return NULL; + } + + /* Adding bound checking to avoid all kinds of memory corruption errors + that may occur. */ + dst_check = (Uint8*)textbuf->pixels + textbuf->pitch * textbuf->h; + + /* Fill the palette with NUM_GRAYS levels of shading from bg to fg */ + palette = textbuf->format->palette; + rdiff = fg.r - bg.r; + gdiff = fg.g - bg.g; + bdiff = fg.b - bg.b; + + for ( index = 0; index < NUM_GRAYS; ++index ) { + palette->colors[index].r = bg.r + (index*rdiff) / (NUM_GRAYS-1); + palette->colors[index].g = bg.g + (index*gdiff) / (NUM_GRAYS-1); + palette->colors[index].b = bg.b + (index*bdiff) / (NUM_GRAYS-1); + } + + + + TTF_Font *orig_font = ttf_font; + TTF_Font *font = orig_font; + + /* Load and render each character */ + textlen = SDL_strlen(text); + first = SDL_FALSE; + xstart = 0; + while ( textlen > 0 ) { + Uint16 c = UTF8_getch(&text, &textlen); + if ( c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED ) { + continue; + } + + font = TTF_CppWrap_FindValidFont(orig_font, c); + + /* check kerning */ + use_kerning = FT_HAS_KERNING( font->face ) && font->kerning; + + error = Find_Glyph(font, c, CACHED_METRICS|CACHED_PIXMAP); + if ( error ) { + TTF_SetFTError("Couldn't find glyph", error); + SDL_FreeSurface( textbuf ); + return NULL; + } + glyph = font->current; + /* Ensure the width of the pixmap is correct. On some cases, + * freetype may report a larger pixmap than possible.*/ + width = glyph->pixmap.width; + if (font->outline <= 0 && width > glyph->maxx - glyph->minx) { + width = glyph->maxx - glyph->minx; + } + /* do kerning, if possible AC-Patch */ + if ( use_kerning && prev_index && glyph->index ) { + FT_Vector delta; + FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); + xstart += delta.x >> 6; + } + /* Compensate for the wrap around with negative minx's */ + if ( first && (glyph->minx < 0) ) { + xstart -= glyph->minx; + } + first = SDL_FALSE; + + current = &glyph->pixmap; + for ( row = 0; row < current->rows; ++row ) { + /* Make sure we don't go either over, or under the + * limit */ + if ( row+glyph->yoffset < 0 ) { + continue; + } + if ( row+glyph->yoffset >= textbuf->h ) { + continue; + } + dst = (Uint8*) textbuf->pixels + + (row+glyph->yoffset) * textbuf->pitch + + xstart + glyph->minx; + src = current->buffer + row * current->pitch; + for ( col=width; col>0 && dst < dst_check; --col ) { + *dst++ |= *src++; + } + } + + xstart += glyph->advance; + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + xstart += font->glyph_overhang; + } + prev_index = glyph->index; + } + + /* Handle the underline style */ + if ( TTF_HANDLE_STYLE_UNDERLINE(font) ) { + row = TTF_underline_top_row(font); + TTF_drawLine_Shaded(font, textbuf, row); + } + + /* Handle the strikethrough style */ + if ( TTF_HANDLE_STYLE_STRIKETHROUGH(font) ) { + row = TTF_strikethrough_top_row(font); + TTF_drawLine_Shaded(font, textbuf, row); + } + return textbuf; +} + +SDL_Surface* TTF_RenderUNICODE_Shaded( TTF_Font* font, + const Uint16* text, + SDL_Color fg, + SDL_Color bg ) +{ + SDL_Surface *surface = NULL; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, NULL); + + utf8 = SDL_stack_alloc(Uint8, UCS2_len(text)*3+1); + if ( utf8 ) { + UCS2_to_UTF8(text, utf8); + surface = TTF_RenderUTF8_Shaded(font, (char *)utf8, fg, bg); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return surface; +} + +SDL_Surface* TTF_RenderGlyph_Shaded( TTF_Font* font, + Uint16 ch, + SDL_Color fg, + SDL_Color bg ) +{ + Uint16 ucs2[2] = { ch, 0 }; + Uint8 utf8[4]; + + UCS2_to_UTF8(ucs2, utf8); + return TTF_RenderUTF8_Shaded(font, (char *)utf8, fg, bg); +} + +SDL_Surface *TTF_RenderText_Blended(TTF_Font *font, + const char *text, SDL_Color fg) +{ + SDL_Surface *surface = NULL; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, NULL); + + utf8 = SDL_stack_alloc(Uint8, SDL_strlen(text)*2+1); + if ( utf8 ) { + LATIN1_to_UTF8(text, utf8); + surface = TTF_RenderUTF8_Blended(font, (char *)utf8, fg); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return surface; +} + +SDL_Surface *TTF_RenderUTF8_Blended(TTF_Font *ttf_font, + const char *text, SDL_Color fg) +{ + SDL_bool first; + int xstart; + int width, height; + SDL_Surface *textbuf; + Uint32 alpha; + Uint32 pixel; + Uint8 *src; + Uint32 *dst; + Uint32 *dst_check; + int row, col; + c_glyph *glyph; + FT_Error error; + FT_Long use_kerning; + FT_UInt prev_index = 0; + size_t textlen; + + TTF_CHECKPOINTER(text, NULL); + + /* Get the dimensions of the text surface */ + if ( ( TTF_SizeUTF8(ttf_font, text, &width, &height) < 0 ) || !width ) { + TTF_SetError("Text has zero width"); + return(NULL); + } + + /* Create the target surface */ + textbuf = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, + 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + if ( textbuf == NULL ) { + return(NULL); + } + + /* Adding bound checking to avoid all kinds of memory corruption errors + that may occur. */ + dst_check = (Uint32*)textbuf->pixels + textbuf->pitch/4 * textbuf->h; + + TTF_Font *orig_font = ttf_font; + TTF_Font *font = font; + + /* Load and render each character */ + textlen = SDL_strlen(text); + first = SDL_TRUE; + xstart = 0; + pixel = (fg.r<<16)|(fg.g<<8)|fg.b; + SDL_FillRect(textbuf, NULL, pixel); /* Initialize with fg and 0 alpha */ + while ( textlen > 0 ) { + Uint16 c = UTF8_getch(&text, &textlen); + if ( c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED ) { + continue; + } + + font = TTF_CppWrap_FindValidFont(orig_font, c); + + /* check kerning */ + use_kerning = FT_HAS_KERNING( font->face ) && font->kerning; + + error = Find_Glyph(font, c, CACHED_METRICS|CACHED_PIXMAP); + if ( error ) { + TTF_SetFTError("Couldn't find glyph", error); + SDL_FreeSurface( textbuf ); + return NULL; + } + glyph = font->current; + /* Ensure the width of the pixmap is correct. On some cases, + * freetype may report a larger pixmap than possible.*/ + width = glyph->pixmap.width; + if (font->outline <= 0 && width > glyph->maxx - glyph->minx) { + width = glyph->maxx - glyph->minx; + } + /* do kerning, if possible AC-Patch */ + if ( use_kerning && prev_index && glyph->index ) { + FT_Vector delta; + FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); + xstart += delta.x >> 6; + } + + /* Compensate for the wrap around bug with negative minx's */ + if ( first && (glyph->minx < 0) ) { + xstart -= glyph->minx; + } + first = SDL_FALSE; + + for ( row = 0; row < glyph->pixmap.rows; ++row ) { + /* Make sure we don't go either over, or under the + * limit */ + if ( row+glyph->yoffset < 0 ) { + continue; + } + if ( row+glyph->yoffset >= textbuf->h ) { + continue; + } + dst = (Uint32*) textbuf->pixels + + (row+glyph->yoffset) * textbuf->pitch/4 + + xstart + glyph->minx; + + /* Added code to adjust src pointer for pixmaps to + * account for pitch. + * */ + src = (Uint8*) (glyph->pixmap.buffer + glyph->pixmap.pitch * row); + for ( col = width; col>0 && dst < dst_check; --col) { + alpha = *src++; + *dst++ |= pixel | (alpha << 24); + } + } + + xstart += glyph->advance; + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + xstart += font->glyph_overhang; + } + prev_index = glyph->index; + } + + /* Handle the underline style */ + if ( TTF_HANDLE_STYLE_UNDERLINE(font) ) { + row = TTF_underline_top_row(font); + TTF_drawLine_Blended(font, textbuf, row, pixel); + } + + /* Handle the strikethrough style */ + if ( TTF_HANDLE_STYLE_STRIKETHROUGH(font) ) { + row = TTF_strikethrough_top_row(font); + TTF_drawLine_Blended(font, textbuf, row, pixel); + } + return(textbuf); +} + +SDL_Surface *TTF_RenderUNICODE_Blended(TTF_Font *font, + const Uint16 *text, SDL_Color fg) +{ + SDL_Surface *surface = NULL; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, NULL); + + utf8 = SDL_stack_alloc(Uint8, UCS2_len(text)*3+1); + if ( utf8 ) { + UCS2_to_UTF8(text, utf8); + surface = TTF_RenderUTF8_Blended(font, (char *)utf8, fg); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return surface; +} + + +SDL_Surface *TTF_RenderText_Blended_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, Uint32 wrapLength) +{ + SDL_Surface *surface = NULL; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, NULL); + + utf8 = SDL_stack_alloc(Uint8, SDL_strlen(text)*2+1); + if ( utf8 ) { + LATIN1_to_UTF8(text, utf8); + surface = TTF_RenderUTF8_Blended_Wrapped(font, (char *)utf8, fg, wrapLength); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return surface; +} + +static SDL_bool CharacterIsDelimiter(char c, const char *delimiters) +{ + while (*delimiters) { + if (c == *delimiters) { + return SDL_TRUE; + } + ++delimiters; + } + return SDL_FALSE; +} + +SDL_Surface *TTF_RenderUTF8_Blended_Wrapped(TTF_Font *ttf_font, + const char *text, SDL_Color fg, Uint32 wrapLength) +{ + SDL_bool first; + int xstart; + int width, height; + SDL_Surface *textbuf; + Uint32 alpha; + Uint32 pixel; + Uint8 *src; + Uint32 *dst; + Uint32 *dst_check; + int row, col; + c_glyph *glyph; + FT_Error error; + FT_Long use_kerning; + FT_UInt prev_index = 0; + const int lineSpace = 2; + int line, numLines, rowSize; + char *str, **strLines; + size_t textlen; + int max_width; + + TTF_CHECKPOINTER(text, NULL); + + /* Get the dimensions of the text surface */ + if ( (TTF_SizeUTF8(ttf_font, text, &width, &height) < 0) || !width ) { + TTF_SetError("Text has zero width"); + return(NULL); + } + + max_width = 0; + numLines = 1; + str = NULL; + strLines = NULL; + if ( wrapLength > 0 && *text ) { + const char *wrapDelims = " \t\r\n"; + int w, h; + char *spot, *tok, *next_tok, *end; + char delim; + size_t str_len = SDL_strlen(text); + + numLines = 0; + + str = SDL_stack_alloc(char, str_len+1); + if ( str == NULL ) { + TTF_SetError("Out of memory"); + return(NULL); + } + + SDL_strlcpy(str, text, str_len+1); + tok = str; + end = str + str_len; + do { + strLines = (char **)SDL_realloc(strLines, (numLines+1)*sizeof(*strLines)); + if (!strLines) { + TTF_SetError("Out of memory"); + return(NULL); + } + strLines[numLines++] = tok; + + /* Look for the end of the line */ + if ((spot = SDL_strchr(tok, '\r')) != NULL || + (spot = SDL_strchr(tok, '\n')) != NULL) { + if (*spot == '\r') { + ++spot; + } + if (*spot == '\n') { + ++spot; + } + } else { + spot = end; + } + next_tok = spot; + + /* Get the longest string that will fit in the desired space */ + for ( ; ; ) { + /* Strip trailing whitespace */ + while ( spot > tok && + CharacterIsDelimiter(spot[-1], wrapDelims) ) { + --spot; + } + if ( spot == tok ) { + if (CharacterIsDelimiter(*spot, wrapDelims)) { + *spot = '\0'; + } + break; + } + delim = *spot; + *spot = '\0'; + + TTF_SizeUTF8(ttf_font, tok, &w, &h); + if ((Uint32)w <= wrapLength) { + if (w > max_width) + max_width = w; + break; + } else { + /* Back up and try again... */ + *spot = delim; + } + + while ( spot > tok && + !CharacterIsDelimiter(spot[-1], wrapDelims) ) { + --spot; + } + if ( spot > tok ) { + next_tok = spot; + } + } + tok = next_tok; + } while (tok < end); + } + + /* Create the target surface */ + textbuf = SDL_CreateRGBSurface(SDL_SWSURFACE, + (numLines > 1) ? max_width : width, + height * numLines + (lineSpace * (numLines - 1)), + 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + if ( textbuf == NULL ) { + if ( strLines ) { + SDL_free(strLines); + SDL_stack_free(str); + } + return(NULL); + } + + rowSize = textbuf->pitch/4 * height; + + /* Adding bound checking to avoid all kinds of memory corruption errors + that may occur. */ + dst_check = (Uint32*)textbuf->pixels + textbuf->pitch/4 * textbuf->h; + + TTF_Font *orig_font = ttf_font; + TTF_Font *font = orig_font; + + /* Load and render each character */ + pixel = (fg.r<<16)|(fg.g<<8)|fg.b; + SDL_FillRect(textbuf, NULL, pixel); /* Initialize with fg and 0 alpha */ + + for ( line = 0; line < numLines; line++ ) { + if ( strLines ) { + text = strLines[line]; + } + textlen = SDL_strlen(text); + first = SDL_TRUE; + xstart = 0; + while ( textlen > 0 ) { + Uint16 c = UTF8_getch(&text, &textlen); + if ( c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED ) { + continue; + } + + font = TTF_CppWrap_FindValidFont(orig_font, c); + + /* check kerning */ + use_kerning = FT_HAS_KERNING( font->face ) && font->kerning; + + error = Find_Glyph(font, c, CACHED_METRICS|CACHED_PIXMAP); + if ( error ) { + TTF_SetFTError("Couldn't find glyph", error); + SDL_FreeSurface( textbuf ); + return NULL; + } + glyph = font->current; + /* Ensure the width of the pixmap is correct. On some cases, + * freetype may report a larger pixmap than possible.*/ + width = glyph->pixmap.width; + if ( font->outline <= 0 && width > glyph->maxx - glyph->minx ) { + width = glyph->maxx - glyph->minx; + } + /* do kerning, if possible AC-Patch */ + if ( use_kerning && prev_index && glyph->index ) { + FT_Vector delta; + FT_Get_Kerning( font->face, prev_index, glyph->index, ft_kerning_default, &delta ); + xstart += delta.x >> 6; + } + + /* Compensate for the wrap around bug with negative minx's */ + if ( first && (glyph->minx < 0) ) { + xstart -= glyph->minx; + } + first = SDL_FALSE; + + for ( row = 0; row < glyph->pixmap.rows; ++row ) { + /* Make sure we don't go either over, or under the + * limit */ + if ( row+glyph->yoffset < 0 ) { + continue; + } + if ( row+glyph->yoffset >= textbuf->h ) { + continue; + } + dst = ((Uint32*)textbuf->pixels + rowSize * line) + + (row+glyph->yoffset) * textbuf->pitch/4 + + xstart + glyph->minx; + + /* Added code to adjust src pointer for pixmaps to + * account for pitch. + * */ + src = (Uint8*) (glyph->pixmap.buffer + glyph->pixmap.pitch * row); + for ( col = width; col>0 && dst < dst_check; --col) { + alpha = *src++; + *dst++ |= pixel | (alpha << 24); + } + } + + xstart += glyph->advance; + if ( TTF_HANDLE_STYLE_BOLD(font) ) { + xstart += font->glyph_overhang; + } + prev_index = glyph->index; + } + + /* Handle the underline style * + if ( TTF_HANDLE_STYLE_UNDERLINE(font) ) { + row = TTF_underline_top_row(font); + TTF_drawLine_Blended(font, textbuf, row, pixel); + } + */ + + /* Handle the strikethrough style * + if ( TTF_HANDLE_STYLE_STRIKETHROUGH(font) ) { + row = TTF_strikethrough_top_row(font); + TTF_drawLine_Blended(font, textbuf, row, pixel); + } + */ + } + + if ( strLines ) { + SDL_free(strLines); + SDL_stack_free(str); + } + return(textbuf); +} + +SDL_Surface *TTF_RenderUNICODE_Blended_Wrapped(TTF_Font *font, const Uint16* text, + SDL_Color fg, Uint32 wrapLength) +{ + SDL_Surface *surface = NULL; + Uint8 *utf8; + + TTF_CHECKPOINTER(text, NULL); + + utf8 = SDL_stack_alloc(Uint8, UCS2_len(text)*3+1); + if ( utf8 ) { + UCS2_to_UTF8(text, utf8); + surface = TTF_RenderUTF8_Blended_Wrapped(font, (char *)utf8, fg, wrapLength); + SDL_stack_free(utf8); + } else { + SDL_OutOfMemory(); + } + return surface; +} + +SDL_Surface *TTF_RenderGlyph_Blended(TTF_Font *font, Uint16 ch, SDL_Color fg) +{ + Uint16 ucs2[2] = { ch, 0 }; + Uint8 utf8[4]; + + UCS2_to_UTF8(ucs2, utf8); + return TTF_RenderUTF8_Blended(font, (char *)utf8, fg); +} + +void TTF_SetFontStyle( TTF_Font* font, int style ) +{ + int prev_style = font->style; + font->style = style | font->face_style; + + /* Flush the cache if the style has changed. + * Ignore UNDERLINE which does not impact glyph drawning. + * */ + if ( (font->style | TTF_STYLE_NO_GLYPH_CHANGE ) != ( prev_style | TTF_STYLE_NO_GLYPH_CHANGE )) { + Flush_Cache( font ); + } +} + +int TTF_GetFontStyle( const TTF_Font* font ) +{ + return font->style; +} + +void TTF_SetFontOutline( TTF_Font* font, int outline ) +{ + font->outline = outline; + Flush_Cache( font ); +} + +int TTF_GetFontOutline( const TTF_Font* font ) +{ + return font->outline; +} + +void TTF_SetFontHinting( TTF_Font* font, int hinting ) +{ + if (hinting == TTF_HINTING_LIGHT) + font->hinting = FT_LOAD_TARGET_LIGHT; + else if (hinting == TTF_HINTING_MONO) + font->hinting = FT_LOAD_TARGET_MONO; + else if (hinting == TTF_HINTING_NONE) + font->hinting = FT_LOAD_NO_HINTING; + else + font->hinting = 0; + + Flush_Cache( font ); +} + +int TTF_GetFontHinting( const TTF_Font* font ) +{ + if (font->hinting == FT_LOAD_TARGET_LIGHT) + return TTF_HINTING_LIGHT; + else if (font->hinting == FT_LOAD_TARGET_MONO) + return TTF_HINTING_MONO; + else if (font->hinting == FT_LOAD_NO_HINTING) + return TTF_HINTING_NONE; + return 0; +} + +void TTF_Quit( void ) +{ + if ( TTF_initialized ) { + if ( --TTF_initialized == 0 ) { + FT_Done_FreeType( library ); + } + } +} + +int TTF_WasInit( void ) +{ + return TTF_initialized; +} + +int TTF_GetFontKerningSize(TTF_Font* font, int prev_index, int index) +{ + FT_Vector delta; + FT_Get_Kerning( font->face, prev_index, index, ft_kerning_default, &delta ); + return (delta.x >> 6); +} + +void *TTF_CppWrap_GetCppPtrRef(TTF_Font *font) +{ + return font->cpp_font_ref_ptr; +} + +void TTF_CppWrap_SetCppPtrRef(TTF_Font *font, void *cpp_ptr_ref) +{ + font->cpp_font_ref_ptr = cpp_ptr_ref; +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ttf/ttf_Font.cpp b/include/Plutonium/Plutonium/source/pu/ttf/ttf_Font.cpp new file mode 100644 index 0000000..d550547 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ttf/ttf_Font.cpp @@ -0,0 +1,142 @@ +#include +#include +#include + +namespace pu::ttf { + + namespace { + + void FileBufferFontFaceDisposingFunction(void *ptr) { + if(ptr != nullptr) { + auto file_buf = reinterpret_cast(ptr); + delete[] file_buf; + } + } + + } + + Font::~Font() { + for(auto &[idx, font] : this->font_faces) { + font->Dispose(); + } + } + + i32 Font::LoadFromMemory(void *ptr, const size_t size, FontFaceDisposingFunction disp_fn) { + const auto idx = rand(); + auto font = std::make_unique(ptr, size, disp_fn, this->font_size, reinterpret_cast(this)); + this->font_faces.push_back({ idx, std::move(font) }); + return idx; + } + + i32 Font::LoadFromFile(const std::string &path) { + auto f = fopen(path.c_str(), "rb"); + if(f) { + fseek(f, 0, SEEK_END); + const auto f_size = ftell(f); + rewind(f); + if(f_size > 0) { + auto font_buf = new u8[f_size](); + fread(font_buf, 1, f_size, f); + fclose(f); + return this->LoadFromMemory(font_buf, f_size, FileBufferFontFaceDisposingFunction); + } + fclose(f); + } + + return InvalidFontFaceIndex; + } + + void Font::Unload(const i32 font_idx) { + u32 i = 0; + for(auto &[idx, font]: this->font_faces) { + if(idx == font_idx) { + this->font_faces.erase(this->font_faces.begin() + i); + break; + } + i++; + } + } + + sdl2::Font Font::FindValidFontFor(const Uint16 ch) { + for(const auto &[idx, font] : this->font_faces) { + if(TTF_GlyphIsProvided(font->font, ch)) { + return font->font; + } + } + + return nullptr; + } + + namespace { + + inline void ProcessLineDimensionsImpl(sdl2::Font font, std::string &str, u32 &w, u32 &h) { + i32 str_w = 0; + i32 str_h = 0; + TTF_SizeUTF8(font, str.c_str(), &str_w, &str_h); + + const auto str_w_32 = static_cast(str_w); + const auto str_h_32 = static_cast(str_h); + if(str_w_32 > w) { + w = str_w_32; + } + h += str_h_32; + str = ""; + } + + } + + std::pair Font::GetTextDimensions(const std::string &str) { + u32 w = 0; + u32 h = 0; + auto font = this->TryGetFirstFont(); + if(font != nullptr) { + std::string tmp_line; + for(const auto &ch: str) { + if(ch == '\n') { + ProcessLineDimensionsImpl(font, tmp_line, w, h); + } + else { + tmp_line += ch; + } + } + if(!tmp_line.empty()) { + ProcessLineDimensionsImpl(font, tmp_line, w, h); + } + } + return { w, h }; + } + + sdl2::Texture Font::RenderText(const std::string &str, const ui::Color clr) { + auto font = this->TryGetFirstFont(); + if(font != nullptr) { + const auto [w, _] = ui::render::GetDimensions(); + auto srf = TTF_RenderUTF8_Blended_Wrapped(font, str.c_str(), { clr.r, clr.g, clr.b, clr.a }, w); + return ui::render::ConvertToTexture(srf); + } + else { + return nullptr; + } + } + +} + +extern "C" { + + pu::sdl2::Font TTF_CppWrap_FindValidFont(pu::sdl2::Font font, Uint16 ch) { + if(font != nullptr) { + auto raw_font_ptr = TTF_CppWrap_GetCppPtrRef(font); + if(raw_font_ptr != nullptr) { + auto font_ptr = reinterpret_cast(raw_font_ptr); + auto find_font = font_ptr->FindValidFontFor(ch); + if(find_font == nullptr) { + return font; + } + else { + return find_font; + } + } + } + return font; + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Button.cpp b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Button.cpp new file mode 100644 index 0000000..0dfff9a --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Button.cpp @@ -0,0 +1,89 @@ +#include + +namespace pu::ui::elm { + + Button::Button(const i32 x, const i32 y, const i32 width, const i32 height, const std::string &content, const Color content_clr, const Color bg_clr) : Element::Element() { + this->x = x; + this->y = y; + this->w = width; + this->h = height; + this->cnt = content; + this->cnt_clr = content_clr; + this->bg_clr = bg_clr; + this->hover = false; + this->hover_alpha = 0xFF; + this->fnt_name = GetDefaultFont(DefaultFontSize::MediumLarge); + this->cnt_tex = nullptr; + this->SetContent(content); + this->on_click_cb = {}; + } + + Button::~Button() { + render::DeleteTexture(this->cnt_tex); + } + + void Button::SetContent(const std::string &content) { + this->cnt = content; + render::DeleteTexture(this->cnt_tex); + this->cnt_tex = render::RenderText(this->fnt_name, content, this->cnt_clr); + } + + void Button::SetContentColor(const Color content_clr) { + this->cnt_clr = content_clr; + this->SetContent(this->cnt); + } + + void Button::SetContentFont(const std::string &font_name) { + this->fnt_name = font_name; + this->SetContent(this->cnt); + } + + void Button::OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) { + drawer->RenderRectangleFill(this->bg_clr, x, y, this->w, this->h); + if(this->hover) { + if(this->hover_alpha < 0xFF) { + const auto hover_bg_clr = this->MakeHoverBackgroundColor(this->hover_alpha); + drawer->RenderRectangleFill(hover_bg_clr, x, y, this->w, this->h); + this->hover_alpha += HoverAlphaIncrement; + } + else { + this->hover_alpha = 0xFF; + const auto darker_bg_clr = this->MakeHoverBackgroundColor(-1); + drawer->RenderRectangleFill(darker_bg_clr, x, y, this->w, this->h); + } + } + else { + if(this->hover_alpha > 0) { + const auto hover_bg_clr = this->MakeHoverBackgroundColor(this->hover_alpha); + drawer->RenderRectangleFill(hover_bg_clr, x, y, this->w, this->h); + this->hover_alpha -= HoverAlphaIncrement; + } + else { + this->hover_alpha = 0; + drawer->RenderRectangleFill(this->bg_clr, x, y, this->w, this->h); + } + } + + const auto cnt_width = render::GetTextureWidth(this->cnt_tex); + const auto cnt_height = render::GetTextureHeight(this->cnt_tex); + const auto cnt_x = x + ((this->w - cnt_width) / 2); + const auto cnt_y = y + ((this->h - cnt_height) / 2); + drawer->RenderTexture(this->cnt_tex, cnt_x, cnt_y); + } + + void Button::OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) { + if(this->hover) { + if(touch_pos.IsEmpty()) { + (this->on_click_cb)(); + this->hover = false; + this->hover_alpha = 0xFF; + } + } + else { + if(touch_pos.HitsRegion(this->x, this->y, this->w, this->h)) { + this->hover = true; + this->hover_alpha = 0; + } + } + } +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Element.cpp b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Element.cpp new file mode 100644 index 0000000..a904443 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Element.cpp @@ -0,0 +1,38 @@ +#include +#include + +namespace pu::ui::elm { + + i32 Element::GetProcessedX() { + auto x = this->GetX(); + if(this->parent_container != nullptr) { + auto container = reinterpret_cast(this->parent_container); + x += container->GetX(); + + if(this->h_align == HorizontalAlign::Center) { + x = container->GetX() + ((container->GetWidth() - this->GetWidth()) / 2); + } + else if(this->h_align == HorizontalAlign::Right) { + x = container->GetX() + (container->GetWidth() - this->GetWidth()); + } + } + return x; + } + + i32 Element::GetProcessedY() { + auto y = this->GetY(); + if(this->parent_container != nullptr) { + auto container = reinterpret_cast(this->parent_container); + y += container->GetY(); + + if(this->v_align == VerticalAlign::Center) { + y = container->GetY() + ((container->GetHeight() - this->GetHeight()) / 2); + } + else if(this->v_align == VerticalAlign::Down) { + y = container->GetY() + (container->GetHeight() - this->GetHeight()); + } + } + return y; + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Image.cpp b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Image.cpp new file mode 100644 index 0000000..9b00753 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Image.cpp @@ -0,0 +1,34 @@ +#include +#include + +namespace pu::ui::elm { + + Image::Image(const i32 x, const i32 y, const std::string &image_path) : Element::Element() { + this->x = x; + this->y = y; + this->img_tex = nullptr; + this->rend_opts = render::TextureRenderOptions::Default(); + this->SetImage(image_path); + } + + Image::~Image() { + render::DeleteTexture(this->img_tex); + } + + void Image::SetImage(const std::string &image_path) { + render::DeleteTexture(this->img_tex); + + struct stat st; + if(stat(image_path.c_str(), &st) == 0) { + this->img_path = image_path; + this->img_tex = render::LoadImage(image_path); + this->rend_opts.width = render::GetTextureWidth(this->img_tex); + this->rend_opts.height = render::GetTextureHeight(this->img_tex); + } + } + + void Image::OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) { + drawer->RenderTexture(this->img_tex, x, y, this->rend_opts); + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Menu.cpp b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Menu.cpp new file mode 100644 index 0000000..a51e22d --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Menu.cpp @@ -0,0 +1,310 @@ +#include +#include + +namespace pu::ui::elm { + + void MenuItem::AddOnKey(OnKeyCallback on_key_cb, const u64 key) { + this->on_key_cbs.push_back(on_key_cb); + this->on_key_cb_keys.push_back(key); + } + + void MenuItem::SetIcon(const std::string &icon_path) { + struct stat st; + if(stat(icon_path.c_str(), &st) == 0) { + this->icon_path = icon_path; + } + else { + this->icon_path = ""; + } + } + + void Menu::ReloadItemRenders() { + for(auto &name_tex : this->loaded_name_texs) { + render::DeleteTexture(name_tex); + } + this->loaded_name_texs.clear(); + for(auto &icon_tex : this->loaded_icon_texs) { + render::DeleteTexture(icon_tex); + } + this->loaded_icon_texs.clear(); + + const auto item_count = this->GetItemCount(); + for(u32 i = this->advanced_item_count; i < (this->advanced_item_count + item_count); i++) { + auto &item = this->items.at(i); + auto name_tex = render::RenderText(this->font_name, item->GetName(), item->GetColor()); + this->loaded_name_texs.push_back(name_tex); + + if(item->HasIcon()) { + auto icon_tex = render::LoadImage(item->GetIconPath()); + this->loaded_icon_texs.push_back(icon_tex); + } + else { + this->loaded_icon_texs.push_back(nullptr); + } + } + } + + Menu::Menu(const i32 x, const i32 y, const i32 width, const Color items_clr, const Color items_focus_clr, const i32 items_height, const u32 items_to_show) : Element::Element() { + this->x = x; + this->y = y; + this->w = width; + this->items_clr = items_clr; + this->scrollbar_clr = DefaultScrollbarColor; + this->items_h = items_height; + this->items_to_show = items_to_show; + this->prev_selected_item_idx = 0; + this->selected_item_idx = 0; + this->advanced_item_count = 0; + this->selected_item_alpha = 0xFF; + this->prev_selected_item_alpha = 0; + this->on_selection_changed_cb = {}; + this->cooldown_enabled = false; + this->item_touched = false; + this->items_focus_clr = items_focus_clr; + this->move_mode = 0; + this->font_name = GetDefaultFont(DefaultFontSize::MediumLarge); + } + + void Menu::ClearItems() { + this->items.clear(); + for(auto &name_tex : this->loaded_name_texs) { + render::DeleteTexture(name_tex); + } + this->loaded_name_texs.clear(); + for(auto &icon_tex : this->loaded_icon_texs) { + render::DeleteTexture(icon_tex); + } + this->loaded_icon_texs.clear(); + this->selected_item_idx = 0; + this->prev_selected_item_idx = 0; + this->advanced_item_count = 0; + } + + void Menu::SetSelectedIndex(const u32 idx) { + if(idx < this->items.size()) { + this->selected_item_idx = idx; + this->advanced_item_count = 0; + if(this->selected_item_idx >= (this->items.size() - this->items_to_show)) { + this->advanced_item_count = this->items.size() - this->items_to_show; + } + else if(this->selected_item_idx < this->items_to_show) { + this->advanced_item_count = 0; + } + else { + this->advanced_item_count = this->selected_item_idx; + } + + this->ReloadItemRenders(); + this->selected_item_alpha = 0xFF; + this->prev_selected_item_alpha = 0; + } + } + + void Menu::OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) { + if(!this->items.empty()) { + const auto item_count = this->GetItemCount(); + + if(this->loaded_name_texs.empty()) { + this->ReloadItemRenders(); + } + + auto cur_item_y = y; + for(u32 i = this->advanced_item_count; i < (this->advanced_item_count + item_count); i++) { + const auto loaded_tex_idx = i - this->advanced_item_count; + auto name_tex = this->loaded_name_texs.at(loaded_tex_idx); + auto icon_tex = this->loaded_icon_texs.at(loaded_tex_idx); + if(this->selected_item_idx == i) { + drawer->RenderRectangleFill(this->items_clr, x, cur_item_y, this->w, this->items_h); + if(this->selected_item_alpha < 0xFF) { + const auto focus_clr = this->MakeItemsFocusColor(this->selected_item_alpha); + drawer->RenderRectangleFill(focus_clr, x, cur_item_y, this->w, this->items_h); + this->selected_item_alpha += ItemAlphaIncrement; + } + else { + drawer->RenderRectangleFill(this->items_focus_clr, x, cur_item_y, this->w, this->items_h); + } + } + else if(this->prev_selected_item_idx == static_cast(i)) { + drawer->RenderRectangleFill(this->items_clr, x, cur_item_y, this->w, this->items_h); + if(this->prev_selected_item_alpha > 0) { + const auto focus_clr = this->MakeItemsFocusColor(this->prev_selected_item_alpha); + drawer->RenderRectangleFill(focus_clr, x, cur_item_y, this->w, this->items_h); + this->prev_selected_item_alpha -= ItemAlphaIncrement; + } + else { + drawer->RenderRectangleFill(this->items_clr, x, cur_item_y, this->w, this->items_h); + } + } + else { + drawer->RenderRectangleFill(this->items_clr, x, cur_item_y, this->w, this->items_h); + } + + auto &item = this->items.at(i); + const auto name_height = render::GetTextureHeight(name_tex); + auto name_x = x + TextMargin; + const auto name_y = cur_item_y + ((this->items_h - name_height) / 2); + if(item->HasIcon()) { + const auto factor = (float)render::GetTextureHeight(icon_tex) / (float)render::GetTextureWidth(icon_tex); + auto icon_width = (i32)(this->items_h * IconItemSizesFactor); + auto icon_height = icon_width; + if(factor < 1) { + icon_height = (i32)(icon_width * factor); + } + else { + icon_width = (i32)(icon_height * factor); + } + + const auto icon_x = x + IconMargin; + const auto icon_y = cur_item_y + (this->items_h - icon_height) / 2; + name_x = icon_x + icon_width + TextMargin; + drawer->RenderTexture(icon_tex, icon_x, icon_y, render::TextureRenderOptions::WithCustomDimensions(icon_width, icon_height)); + } + drawer->RenderTexture(name_tex, name_x, name_y); + cur_item_y += this->items_h; + } + + if(this->items_to_show < this->items.size()) { + const auto scrollbar_x = x + (this->w - ScrollbarWidth); + const auto scrollbar_height = this->GetHeight(); + drawer->RenderRectangleFill(this->scrollbar_clr, scrollbar_x, y, ScrollbarWidth, scrollbar_height); + + const auto light_scrollbar_clr = this->MakeLighterScrollbarColor(); + const auto scrollbar_front_height = (this->items_to_show * scrollbar_height) / this->items.size(); + const auto scrollbar_front_y = y + (this->advanced_item_count * (scrollbar_height / this->items.size())); + drawer->RenderRectangleFill(light_scrollbar_clr, scrollbar_x, scrollbar_front_y, ScrollbarWidth, scrollbar_front_height); + } + drawer->RenderShadowSimple(x, cur_item_y, this->w, ShadowHeight, ShadowBaseAlpha); + } + } + + void Menu::OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) { + if(this->items.empty()) { + return; + } + + if(this->move_mode == 1) { + const auto cur_time = std::chrono::steady_clock::now(); + const auto time_diff_ms = std::chrono::duration_cast(cur_time - this->move_start_time).count(); + if(time_diff_ms >= 150) { + this->move_mode = 2; + } + } + if(!touch_pos.IsEmpty()) { + const auto x = this->GetProcessedX(); + auto cur_item_y = this->GetProcessedY(); + const auto item_count = this->GetItemCount(); + for(u32 i = this->advanced_item_count; i < (this->advanced_item_count + item_count); i++) { + if(touch_pos.HitsRegion(x, cur_item_y, this->w, this->items_h)) { + this->item_touched = true; + this->prev_selected_item_idx = this->selected_item_idx; + this->selected_item_idx = i; + this->HandleOnSelectionChanged(); + if(i == this->selected_item_idx) { + this->selected_item_alpha = 0xFF; + } + else if(static_cast(i) == this->prev_selected_item_idx) { + this->prev_selected_item_alpha = 0; + } + break; + } + cur_item_y += this->items_h; + } + } + else if(this->item_touched) { + if((this->selected_item_alpha >= 0xFF) && (this->prev_selected_item_alpha <= 0)) { + if(this->cooldown_enabled) { + this->cooldown_enabled = false; + } + else { + this->RunSelectedItemCallback(TouchPseudoKey); + } + this->item_touched = false; + } + } + else { + if(keys_down & HidNpadButton_AnyDown) { + auto move = true; + if(keys_held & HidNpadButton_StickRDown) { + move = false; + if(this->move_mode == 0) { + this->move_start_time = std::chrono::steady_clock::now(); + this->move_mode = 1; + } + else if(move_mode == 2) { + this->move_mode = 0; + move = true; + } + } + if(move) { + if(!this->items.empty() && (this->selected_item_idx < (this->items.size() - 1))) { + if((this->selected_item_idx - this->advanced_item_count) == (this->items_to_show - 1)) { + this->advanced_item_count++; + this->selected_item_idx++; + this->HandleOnSelectionChanged(); + this->ReloadItemRenders(); + } + else { + this->prev_selected_item_idx = this->selected_item_idx; + this->selected_item_idx++; + this->HandleOnSelectionChanged(); + + this->selected_item_alpha = 0; + this->prev_selected_item_alpha = 0xFF; + } + } + else { + this->selected_item_idx = 0; + this->advanced_item_count = 0; + if(this->items.size() >= this->items_to_show) { + this->ReloadItemRenders(); + } + } + } + } + else if(keys_down & HidNpadButton_AnyUp) { + auto move = true; + if(keys_held & HidNpadButton_StickRUp) { + move = false; + if(this->move_mode == 0) { + this->move_start_time = std::chrono::steady_clock::now(); + this->move_mode = 1; + } + else if(this->move_mode == 2) { + this->move_mode = 0; + move = true; + } + } + if(move) { + if(this->selected_item_idx > 0) { + if(this->selected_item_idx == this->advanced_item_count) { + this->advanced_item_count--; + this->selected_item_idx--; + this->HandleOnSelectionChanged(); + this->ReloadItemRenders(); + } + else { + this->prev_selected_item_idx = this->selected_item_idx; + this->selected_item_idx--; + this->HandleOnSelectionChanged(); + + this->selected_item_alpha = 0; + this->prev_selected_item_alpha = 0xFF; + } + } + else { + this->selected_item_idx = this->items.size() - 1; + this->advanced_item_count = 0; + if(this->items.size() >= this->items_to_show) { + this->advanced_item_count = this->items.size() - this->items_to_show; + this->ReloadItemRenders(); + } + } + } + } + else { + this->RunSelectedItemCallback(keys_down); + } + } + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/elm/elm_ProgressBar.cpp b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_ProgressBar.cpp new file mode 100644 index 0000000..cbda408 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_ProgressBar.cpp @@ -0,0 +1,22 @@ +#include + +namespace pu::ui::elm { + + void ProgressBar::SetProgress(const double progress) { + if(progress >= this->max_val) { + this->val = this->max_val; + } + else { + this->val = progress; + } + } + + void ProgressBar::OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) { + const auto progress_width = (i32)((this->val / this->max_val) * (double)this->w); + // TODO: set radius? + const auto radius = (this->h / 3); + drawer->RenderRoundedRectangleFill(this->bg_clr, x, y, this->w, this->h, radius); + drawer->RenderRoundedRectangleFill(this->progress_clr, x, y, progress_width, this->h, radius); + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Rectangle.cpp b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Rectangle.cpp new file mode 100644 index 0000000..84c4a13 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Rectangle.cpp @@ -0,0 +1,9 @@ +#include + +namespace pu::ui::elm { + + void Rectangle::OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) { + drawer->RenderRectangleFill(this->clr, x, y, this->w, this->h); + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/elm/elm_TextBlock.cpp b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_TextBlock.cpp new file mode 100644 index 0000000..3f4a668 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_TextBlock.cpp @@ -0,0 +1,46 @@ +#include + +namespace pu::ui::elm { + + TextBlock::TextBlock(const i32 x, const i32 y, const std::string &text) : Element::Element() { + this->x = x; + this->y = y; + this->clr = DefaultColor; + this->text_tex = nullptr; + this->fnt_name = GetDefaultFont(DefaultFontSize::MediumLarge); + this->SetText(text); + } + + TextBlock::~TextBlock() { + render::DeleteTexture(this->text_tex); + } + + i32 TextBlock::GetWidth() { + return render::GetTextureWidth(this->text_tex); + } + + i32 TextBlock::GetHeight() { + return render::GetTextureHeight(this->text_tex); + } + + void TextBlock::SetText(const std::string &text) { + this->text = text; + render::DeleteTexture(this->text_tex); + this->text_tex = render::RenderText(this->fnt_name, text, this->clr); + } + + void TextBlock::SetFont(const std::string &font_name) { + this->fnt_name = font_name; + this->SetText(this->text); + } + + void TextBlock::SetColor(const Color clr) { + this->clr = clr; + this->SetText(this->text); + } + + void TextBlock::OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) { + drawer->RenderTexture(this->text_tex, x, y); + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Toggle.cpp b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Toggle.cpp new file mode 100644 index 0000000..3e6ef67 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/elm/elm_Toggle.cpp @@ -0,0 +1,80 @@ +#include + +namespace pu::ui::elm { + + Toggle::Toggle(const i32 x, const i32 y, const std::string &content, const u64 toggle_key, const Color clr) : Element::Element() { + this->x = x; + this->y = y; + this->key = toggle_key; + this->clr = clr; + this->cnt_tex = nullptr; + this->fnt_name = GetDefaultFont(DefaultFontSize::MediumLarge); + this->toggle_alpha = 0xFF; + this->checked = false; + this->SetContent(content); + } + + Toggle::~Toggle() { + render::DeleteTexture(this->cnt_tex); + } + + i32 Toggle::GetWidth() { + return render::GetTextureWidth(this->cnt_tex) + 2 * ContentHorizontalMargin; + } + + i32 Toggle::GetHeight() { + return render::GetTextureHeight(this->cnt_tex) + 2 * ContentVerticalMargin; + } + + void Toggle::SetContent(const std::string &content) { + this->cnt = content; + render::DeleteTexture(this->cnt_tex); + this->cnt_tex = render::RenderText(this->fnt_name, content, this->clr); + } + + void Toggle::SetFont(const std::string &font_name) { + this->fnt_name = font_name; + this->SetContent(this->cnt); + } + + void Toggle::SetColor(const Color clr) { + this->clr = clr; + this->SetContent(this->cnt); + } + + void Toggle::OnRender(render::Renderer::Ref &drawer, const i32 x, const i32 y) { + const auto bg_width = this->GetWidth(); + const auto bg_height = this->GetHeight(); + const auto cnt_x = x + ContentHorizontalMargin; + const auto cnt_y = y + ContentVerticalMargin; + if(this->checked) { + drawer->RenderRectangleFill(MakeBackgroundColor(0xFF), x, y, bg_width, bg_height); + if(this->toggle_alpha < 0xFF) { + drawer->RenderRectangleFill(MakeBackgroundColor(0xFF - this->toggle_alpha), x, y, bg_width, bg_height); + this->toggle_alpha += ToggleAlphaIncrement; + } + else { + drawer->RenderRectangleFill(MakeBackgroundColor(0xFF), x, y, bg_width, bg_height); + } + } + else { + drawer->RenderRectangleFill(this->clr, x, y, bg_width, bg_height); + if(this->toggle_alpha > 0) + { + drawer->RenderRectangleFill(MakeBackgroundColor(this->toggle_alpha), x, y, bg_width, bg_height); + this->toggle_alpha -= ToggleAlphaIncrement; + } + else { + drawer->RenderRectangleFill(this->clr, x, y, bg_width, bg_height); + } + } + drawer->RenderTexture(this->cnt_tex, cnt_x, cnt_y); + } + + void Toggle::OnInput(const u64 keys_down, const u64 keys_up, const u64 keys_held, const TouchPoint touch_pos) { + if((keys_down & this->key) || ((this->key == TouchPseudoKey) && touch_pos.HitsRegion(this->x, this->y, this->GetWidth(), this->GetHeight()))) { + this->checked = !this->checked; + } + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/extras/extras_Toast.cpp b/include/Plutonium/Plutonium/source/pu/ui/extras/extras_Toast.cpp new file mode 100644 index 0000000..5c02076 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/extras/extras_Toast.cpp @@ -0,0 +1,38 @@ +#include + +namespace pu::ui::extras { + + Toast::Toast(const std::string &text, const std::string &font_name, const Color text_clr, const Color bg_clr) : Overlay(0, DefaultY, 0, 0, bg_clr) { + this->text = elm::TextBlock::New(0, 0, text); + this->text->SetFont(font_name); + this->text->SetColor(text_clr); + this->text->SetHorizontalAlign(elm::HorizontalAlign::Center); + this->text->SetVerticalAlign(elm::VerticalAlign::Center); + this->AdjustDimensions(); + this->Add(this->text); + } + + void Toast::AdjustDimensions() { + const auto text_width = this->text->GetWidth(); + const auto text_height = this->text->GetHeight(); + const auto toast_width = text_width + 2 * HorizontalMargin; + const auto toast_height = text_height * HeightAndTextHeightFactor; + this->SetX((render::ScreenWidth - toast_width) / 2); + this->SetWidth(toast_width); + this->SetHeight(toast_height); + } + + void Toast::SetText(const std::string &text) { + this->text->SetText(text); + this->AdjustDimensions(); + } + + void Toast::OnPreRender(render::Renderer::Ref &drawer) { + drawer->SetBaseRenderAlpha(BaseAlpha); + } + + void Toast::OnPostRender(render::Renderer::Ref &drawer) { + drawer->ResetBaseRenderAlpha(); + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/render/render_Renderer.cpp b/include/Plutonium/Plutonium/source/pu/ui/render/render_Renderer.cpp new file mode 100644 index 0000000..8dc40e5 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/render/render_Renderer.cpp @@ -0,0 +1,349 @@ +#include +#include + +namespace pu::ui::render { + + namespace { + + // Global rendering vars + sdl2::Renderer g_Renderer = nullptr; + sdl2::Window g_Window = nullptr; + sdl2::Surface g_WindowSurface = nullptr; + + // Global font object + std::vector>> g_FontTable; + + inline bool DoAddSharedFont(std::shared_ptr &font, const PlSharedFontType type) { + // Let's assume pl services are initialized, and return if anything unexpected happens + PlFontData data = {}; + if(R_FAILED(plGetSharedFontByType(&data, type))) { + return false; + } + if(!ttf::Font::IsValidFontFaceIndex(font->LoadFromMemory(data.address, data.size, ttf::Font::EmptyFontFaceDisposingFunction))) { + return false; + } + return true; + } + + inline bool ExistsFont(const std::string &font_name) { + for(const auto &[name, font]: g_FontTable) { + if(name == font_name) { + return true; + } + } + + return false; + } + + } + + void Renderer::Initialize() { + if(!this->initialized) { + if(this->init_opts.init_romfs) { + this->ok_romfs = R_SUCCEEDED(romfsInit()); + } + + if(this->init_opts.init_pl) { + // TODO: choose pl service type? + this->ok_pl = R_SUCCEEDED(plInitialize(PlServiceType_User)); + } + + // TODO: check sdl return errcodes! + + SDL_Init(this->init_opts.sdl_flags); + g_Window = SDL_CreateWindow("Plutonium-SDL2", 0, 0, this->init_opts.width, this->init_opts.height, 0); + g_Renderer = SDL_CreateRenderer(g_Window, -1, this->init_opts.sdl_render_flags); + g_WindowSurface = SDL_GetWindowSurface(g_Window); + SDL_SetRenderDrawBlendMode(g_Renderer, SDL_BLENDMODE_BLEND); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); + + if(this->init_opts.init_img) { + IMG_Init(this->init_opts.sdl_img_flags); + } + + if(this->init_opts.init_ttf) { + TTF_Init(); + if(!this->init_opts.default_font_path.empty()) { + for(const auto size: DefaultFontSizes) { + AddDefaultFontFromFile(size, this->init_opts.default_font_path); + } + for(const auto size: this->init_opts.extra_default_font_sizes) { + AddDefaultFontFromFile(size, this->init_opts.default_font_path); + } + } + else { + for(const auto size: DefaultFontSizes) { + AddDefaultFontFromShared(size); + } + for(const auto size: this->init_opts.extra_default_font_sizes) { + AddDefaultFontFromShared(size); + } + } + } + + if(this->init_opts.init_mixer) { + Mix_Init(this->init_opts.audio_mixer_flags); + Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 4096); + } + + this->initialized = true; + this->base_a = TextureRenderOptions::NoAlpha; + this->base_x = 0; + this->base_y = 0; + } + } + + void Renderer::Finalize() { + if(this->initialized) { + // Close all the fonts before closing TTF + g_FontTable.clear(); + + if(this->init_opts.init_ttf) { + TTF_Quit(); + } + if(this->init_opts.init_img) { + IMG_Quit(); + } + if(this->init_opts.init_mixer) { + Mix_CloseAudio(); + } + if(this->ok_pl) { + plExit(); + } + if(this->ok_romfs) { + romfsExit(); + } + SDL_DestroyRenderer(g_Renderer); + SDL_FreeSurface(g_WindowSurface); + SDL_DestroyWindow(g_Window); + SDL_Quit(); + this->initialized = false; + } + } + + void Renderer::InitializeRender(const Color clr) { + SDL_SetRenderDrawColor(g_Renderer, clr.r, clr.g, clr.b, clr.a); + SDL_RenderClear(g_Renderer); + } + + void Renderer::FinalizeRender() { + SDL_RenderPresent(g_Renderer); + } + + void Renderer::RenderTexture(sdl2::Texture texture, const i32 x, const i32 y, const TextureRenderOptions opts) { + if(texture == nullptr) { + return; + } + + SDL_Rect pos = { + .x = x + this->base_x, + .y = y + this->base_y + }; + if(opts.width != TextureRenderOptions::NoWidth) { + pos.w = opts.width; + } + else { + SDL_QueryTexture(texture, nullptr, nullptr, &pos.w, nullptr); + } + if(opts.height != TextureRenderOptions::NoHeight) { + pos.h = opts.height; + } + else { + SDL_QueryTexture(texture, nullptr, nullptr, nullptr, &pos.h); + } + + float angle = 0; + if(opts.rot_angle != TextureRenderOptions::NoRotation) { + angle = opts.rot_angle; + } + if(opts.alpha_mod != TextureRenderOptions::NoAlpha) { + SetAlphaValue(texture, static_cast(opts.alpha_mod)); + } + if(this->base_a >= 0) { + SetAlphaValue(texture, static_cast(this->base_a)); + } + SDL_RenderCopyEx(g_Renderer, texture, nullptr, &pos, angle, nullptr, SDL_FLIP_NONE); + } + + void Renderer::RenderRectangle(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height) { + const SDL_Rect rect = { + .x = x + this->base_x, + .y = y + this->base_y, + .w = width, + .h = height + }; + SDL_SetRenderDrawColor(g_Renderer, clr.r, clr.g, clr.b, this->GetActualAlpha(clr.a)); + SDL_RenderDrawRect(g_Renderer, &rect); + } + + void Renderer::RenderRectangleFill(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height) { + const SDL_Rect rect = { + .x = x + this->base_x, + .y = y + this->base_y, + .w = width, + .h = height + }; + SDL_SetRenderDrawColor(g_Renderer, clr.r, clr.g, clr.b, this->GetActualAlpha(clr.a)); + SDL_RenderFillRect(g_Renderer, &rect); + } + + void Renderer::RenderRoundedRectangle(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height, const i32 radius) { + auto proper_radius = radius; + if((2 * proper_radius) > width) { + proper_radius = width / 2; + } + if((2 * proper_radius) > height) { + proper_radius = height / 2; + } + + roundedRectangleRGBA(g_Renderer, x + this->base_x, y + this->base_y, x + this->base_x + width, y + this->base_y + height, proper_radius, clr.r, clr.g, clr.b, this->GetActualAlpha(clr.a)); + SDL_SetRenderDrawBlendMode(g_Renderer, SDL_BLENDMODE_BLEND); + } + + void Renderer::RenderRoundedRectangleFill(const Color clr, const i32 x, const i32 y, const i32 width, const i32 height, const i32 radius) { + auto proper_radius = radius; + if((2 * proper_radius) > width) { + proper_radius = width / 2; + } + if((2 * proper_radius) > height) { + proper_radius = height / 2; + } + + roundedBoxRGBA(g_Renderer, x + this->base_x, y + this->base_y, x + this->base_x + width, y + this->base_y + height, proper_radius, clr.r, clr.g, clr.b, this->GetActualAlpha(clr.a)); + SDL_SetRenderDrawBlendMode(g_Renderer, SDL_BLENDMODE_BLEND); + } + + void Renderer::RenderCircle(const Color clr, const i32 x, const i32 y, const i32 radius) { + circleRGBA(g_Renderer, x + this->base_x, y + this->base_y, radius - 1, clr.r, clr.g, clr.b, this->GetActualAlpha(clr.a)); + aacircleRGBA(g_Renderer, x + this->base_x, y + this->base_y, radius - 1, clr.r, clr.g, clr.b, this->GetActualAlpha(clr.a)); + } + + void Renderer::RenderCircleFill(const Color clr, const i32 x, const i32 y, const i32 radius) { + filledCircleRGBA(g_Renderer, x + this->base_x, y + this->base_y, radius - 1, clr.r, clr.g, clr.b, this->GetActualAlpha(clr.a)); + aacircleRGBA(g_Renderer, x + this->base_x, y + this->base_y, radius - 1, clr.r, clr.g, clr.b, this->GetActualAlpha(clr.a)); + } + + void Renderer::RenderShadowSimple(const i32 x, const i32 y, const i32 width, const i32 height, const i32 base_alpha, const u8 main_alpha) { + auto crop = false; + auto shadow_width = width; + auto shadow_x = x; + auto shadow_y = y; + for(auto cur_a = base_alpha; cur_a > 0; cur_a -= (180 / height)) { + const Color shadow_clr = { 130, 130, 130, static_cast(cur_a * (main_alpha / 0xFF)) }; + this->RenderRectangleFill(shadow_clr, shadow_x + this->base_x, shadow_y + this->base_y, shadow_width, 1); + if(crop) { + shadow_width -= 2; + shadow_x++; + } + crop = !crop; + shadow_y++; + } + } + + sdl2::Renderer GetMainRenderer() { + return g_Renderer; + } + + sdl2::Window GetMainWindow() { + return g_Window; + } + + sdl2::Surface GetMainSurface() { + return g_WindowSurface; + } + + std::pair GetDimensions() { + i32 w = 0; + i32 h = 0; + SDL_GetWindowSize(g_Window, &w, &h); + return { static_cast(w), static_cast(h) }; + } + + bool AddSharedFont(const std::string &font_name, const u32 font_size, const PlSharedFontType type) { + if(ExistsFont(font_name)) { + return false; + } + + auto font = std::make_shared(font_size); + if(!DoAddSharedFont(font, type)) { + return false; + } + + g_FontTable.push_back(std::make_pair(font_name, std::move(font))); + return true; + } + + bool AddAllSharedFonts(const std::string &font_name, const u32 font_size) { + if(ExistsFont(font_name)) { + return false; + } + + auto font = std::make_shared(font_size); + if(!DoAddSharedFont(font, PlSharedFontType_Standard)) { + return false; + } + if(!DoAddSharedFont(font, PlSharedFontType_NintendoExt)) { + return false; + } + if(!DoAddSharedFont(font, PlSharedFontType_ChineseSimplified)) { + return false; + } + if(!DoAddSharedFont(font, PlSharedFontType_ExtChineseSimplified)) { + return false; + } + if(!DoAddSharedFont(font, PlSharedFontType_ChineseTraditional)) { + return false; + } + if(!DoAddSharedFont(font, PlSharedFontType_KO)) { + return false; + } + + g_FontTable.push_back(std::make_pair(font_name, std::move(font))); + return true; + } + + bool AddFontFile(const std::string &font_name, const u32 font_size, const std::string &path) { + if(ExistsFont(font_name)) { + return false; + } + + auto font = std::make_shared(font_size); + if(!ttf::Font::IsValidFontFaceIndex(font->LoadFromFile(path))) { + return false; + } + + g_FontTable.push_back(std::make_pair(font_name, std::move(font))); + return true; + } + + sdl2::Texture RenderText(const std::string &font_name, const std::string &text, const Color clr) { + for(auto &[name, font]: g_FontTable) { + if(name == font_name) { + return font->RenderText(text, clr); + } + } + + return nullptr; + } + + i32 GetTextWidth(const std::string &font_name, const std::string &text) { + for(auto &[name, font]: g_FontTable) { + if(name == font_name) { + const auto [w, _] = font->GetTextDimensions(text); + return static_cast(w); + } + } + return 0; + } + + i32 GetTextHeight(const std::string &font_name, const std::string &text) { + for(auto &[name, font]: g_FontTable) { + if(name == font_name) { + const auto [_, h] = font->GetTextDimensions(text); + return static_cast(h); + } + } + return 0; + } + +} diff --git a/include/Plutonium/Plutonium/source/pu/ui/render/render_SDL2.cpp b/include/Plutonium/Plutonium/source/pu/ui/render/render_SDL2.cpp new file mode 100644 index 0000000..c8d3199 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/render/render_SDL2.cpp @@ -0,0 +1,56 @@ +#include +#include + +namespace pu::ui::render { + + sdl2::Texture ConvertToTexture(sdl2::Surface surface) { + if(surface == nullptr) { + return nullptr; + } + + auto tex = SDL_CreateTextureFromSurface(GetMainRenderer(), surface); + SDL_FreeSurface(surface); + return tex; + } + + sdl2::Texture LoadImage(const std::string &path) { + return ConvertToTexture(IMG_Load(path.c_str())); + } + + i32 GetTextureWidth(sdl2::Texture texture) { + if(texture == nullptr) { + return 0; + } + + i32 w = 0; + SDL_QueryTexture(texture, nullptr, nullptr, &w, nullptr); + return w; + } + + i32 GetTextureHeight(sdl2::Texture texture) { + if(texture == nullptr) { + return 0; + } + + i32 h = 0; + SDL_QueryTexture(texture, nullptr, nullptr, nullptr, &h); + return h; + } + + void SetAlphaValue(sdl2::Texture texture, const u8 alpha) { + if(texture == nullptr) { + return; + } + + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod(texture, alpha); + } + + void DeleteTexture(sdl2::Texture &texture) { + if(texture != nullptr) { + SDL_DestroyTexture(texture); + texture = nullptr; + } + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/ui_Application.cpp b/include/Plutonium/Plutonium/source/pu/ui/ui_Application.cpp new file mode 100644 index 0000000..799398a --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/ui_Application.cpp @@ -0,0 +1,212 @@ +#include + +namespace pu::ui { + + Application::Application(render::Renderer::Ref renderer) { + this->renderer = renderer; + // TODO: do it outside ctor, get result...? + this->renderer->Initialize(); + this->is_shown = false; + this->on_ipt_cb = {}; + this->in_render_over = false; + this->ovl = nullptr; + this->lyt = nullptr; + this->loaded = false; + this->render_over_fn = {}; + this->fade_alpha = 0xFF; + this->fade_alpha_increment = DefaultFadeAlphaIncrement; + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + padInitializeDefault(&this->input_pad); + } + + void Application::Prepare() { + if(!this->loaded) { + this->OnLoad(); + this->loaded = true; + } + } + + i32 Application::CreateShowDialog(const std::string &title, const std::string &content, const std::vector &opts, const bool use_last_opt_as_cancel, const std::string &icon_path) { + auto dialog = Dialog::New(title, content); + for(u32 i = 0; i < opts.size(); i++) { + const auto &opt = opts.at(i); + if(use_last_opt_as_cancel && (i == (opts.size() - 1))) { + dialog->SetCancelOption(opt); + } + else { + dialog->AddOption(opt); + } + } + + if(!icon_path.empty()) { + dialog->SetIcon(icon_path); + } + + const auto opt = this->ShowDialog(dialog); + if(dialog->UserCancelled()) { + return -1; + } + else if(!dialog->IsOk()) { + return -2; + } + else { + return opt; + } + } + + void Application::StartOverlayWithTimeout(Overlay::Ref ovl, const u64 ms) { + if(this->ovl == nullptr) { + this->ovl = ovl; + this->ovl_timeout_ms = ms; + this->ovl_start_time = std::chrono::steady_clock::now(); + } + } + + void Application::EndOverlay() { + if(this->ovl != nullptr) { + this->ovl->NotifyEnding(false); + this->ovl_timeout_ms = 0; + this->ovl = nullptr; + } + } + + void Application::Show() { + if(!this->CanBeShown()) { + return; + } + + this->is_shown = true; + while(this->is_shown) { + this->CallForRender(); + } + } + + bool Application::CallForRender() { + if(!this->CanBeShown()) { + return false; + } + + auto continue_render = true; + this->renderer->InitializeRender(this->lyt->GetBackgroundColor()); + this->OnRender(); + if(this->in_render_over) { + continue_render = (this->render_over_fn)(this->renderer); + this->in_render_over = false; + this->render_over_fn = {}; + } + this->renderer->FinalizeRender(); + return continue_render; + } + + bool Application::CallForRenderWithRenderOver(RenderOverFunction render_over_fn) { + this->in_render_over = true; + this->render_over_fn = render_over_fn; + return this->CallForRender(); + } + + void Application::FadeIn() { + this->fade_alpha = 0; + while(true) { + this->CallForRender(); + this->fade_alpha += this->fade_alpha_increment; + if(this->fade_alpha > 0xFF) { + this->fade_alpha = 0xFF; + this->CallForRender(); + break; + } + } + } + + void Application::FadeOut() { + this->fade_alpha = 0xFF; + while(true) { + this->CallForRender(); + this->fade_alpha -= this->fade_alpha_increment; + if(this->fade_alpha < 0) { + this->fade_alpha = 0; + this->CallForRender(); + break; + } + } + } + + void Application::OnRender() { + padUpdate(&this->input_pad); + const auto keys_down = this->GetButtonsDown(); + const auto keys_up = this->GetButtonsUp(); + const auto keys_held = this->GetButtonsHeld(); + + const auto tch_state = this->GetTouchState(); + TouchPoint tch_pos = {}; + if(tch_state.count > 0) { + tch_pos = { tch_state.touches[0].x, tch_state.touches[0].y }; + } + const auto sim_tch_pos = this->lyt->ConsumeSimulatedTouchPosition(); + if(!sim_tch_pos.IsEmpty()) { + tch_pos = sim_tch_pos; + } + + for(auto &render_cb: this->render_cbs) { + if(render_cb) { + render_cb(); + } + } + + this->lyt->PreRender(); + + for(auto &lyt_render_cb: this->lyt->GetRenderCallbacks()) { + if(lyt_render_cb) { + lyt_render_cb(); + } + } + + if(!this->in_render_over) { + if(this->on_ipt_cb) { + (this->on_ipt_cb)(keys_down, keys_up, keys_held, tch_pos); + } + } + + if(this->lyt->HasBackgroundImage()) { + this->renderer->RenderTexture(this->lyt->GetBackgroundImageTexture(), 0, 0); + } + + if(!this->in_render_over) { + auto lyt_on_ipt_cb = this->lyt->GetOnInput(); + if(lyt_on_ipt_cb) { + lyt_on_ipt_cb(keys_down, keys_up, keys_held, tch_pos); + } + } + + for(u32 i = 0; i < this->lyt->GetCount(); i++) { + auto elm = this->lyt->At(i); + if(elm->IsVisible()) { + elm->OnRender(this->renderer, elm->GetProcessedX(), elm->GetProcessedY()); + if(!this->in_render_over) { + elm->OnInput(keys_down, keys_up, keys_held, tch_pos); + } + } + } + + if(this->ovl != nullptr) { + const auto ovl_continue_render = this->ovl->Render(this->renderer); + if(this->ovl_timeout_ms > 0) { + const auto time_now = std::chrono::steady_clock::now(); + const u64 elapsed_time_ms = std::chrono::duration_cast(time_now - this->ovl_start_time).count(); + if(elapsed_time_ms >= this->ovl_timeout_ms) { + this->ovl->NotifyEnding(true); + } + } + if(!ovl_continue_render) { + this->EndOverlay(); + } + } + + this->renderer->RenderRectangleFill({ 0, 0, 0, static_cast(0xFF - this->fade_alpha) }, 0, 0, render::ScreenWidth, render::ScreenHeight); + } + + void Application::Close() { + this->is_shown = false; + this->renderer->Finalize(); + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/ui_Container.cpp b/include/Plutonium/Plutonium/source/pu/ui/ui_Container.cpp new file mode 100644 index 0000000..6c96884 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/ui_Container.cpp @@ -0,0 +1,11 @@ +#include + +namespace pu::ui { + + void Container::PreRender() { + for(auto &elm : this->elems) { + elm->SetParentContainer(this); + } + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/ui_Dialog.cpp b/include/Plutonium/Plutonium/source/pu/ui/ui_Dialog.cpp new file mode 100644 index 0000000..07d72b4 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/ui_Dialog.cpp @@ -0,0 +1,250 @@ +#include +#include + +namespace pu::ui { + + Dialog::Dialog(const std::string &title, const std::string &content) { + this->title_font_name = GetDefaultFont(DefaultFontSize::Large); + this->cnt_font_name = GetDefaultFont(DefaultFontSize::Medium); + this->opt_font_name = GetDefaultFont(DefaultFontSize::Small); + this->title = title; + this->cnt = content; + this->title_tex = render::RenderText(this->title_font_name, title, DefaultTitleColor); + this->cnt_tex = render::RenderText(this->cnt_font_name, content, DefaultContentColor); + this->icon_tex = nullptr; + this->selected_opt_idx = 0; + this->prev_selected_opt_idx = 0; + this->selected_opt_over_alpha = 0xFF; + this->prev_selected_opt_over_alpha = 0; + this->user_cancelled = false; + } + + Dialog::~Dialog() { + render::DeleteTexture(this->title_tex); + render::DeleteTexture(this->cnt_tex); + render::DeleteTexture(this->icon_tex); + for(auto &opt_tex: this->opt_texs) { + render::DeleteTexture(opt_tex); + } + } + + void Dialog::AddOption(const std::string &opt_name) { + this->opts.push_back(opt_name); + this->opt_texs.push_back(render::RenderText(this->opt_font_name, opt_name, DefaultOptionColor)); + } + + void Dialog::SetIcon(const std::string &icon_path) { + render::DeleteTexture(this->icon_tex); + this->icon_tex = render::LoadImage(icon_path); + } + + i32 Dialog::Show(Application *app_ref) { + if(this->HasCancelOption()) { + this->AddOption(this->cancel_opt); + } + + if(this->opt_texs.empty()) { + return 0; + } + + auto opts_width = (SpaceBetweenOptions * (this->opt_texs.size() - 1)) + 2 * OptionsBaseHorizontalMargin; + for(const auto &opt_tex : this->opt_texs) { + const auto opt_width = render::GetTextureWidth(opt_tex) + 2 * OptionHorizontalMargin; + opts_width += opt_width; + } + auto dialog_width = opts_width; + + const auto cnt_width = render::GetTextureWidth(this->cnt_tex) + ContentExtraWidth; + if(cnt_width > dialog_width) { + dialog_width = cnt_width; + } + + const auto title_width = render::GetTextureWidth(this->title_tex) + TitleExtraWidth; + if(title_width > dialog_width) { + dialog_width = title_width; + } + const auto title_cnt_height = TitleTopMargin + render::GetTextureHeight(this->title_tex) + render::GetTextureHeight(this->cnt_tex) + SpaceBetweenContentAndOptions; + auto opt_base_y = title_cnt_height; + + if(this->HasIcon()) { + const auto icon_height = render::GetTextureHeight(this->icon_tex) + 2 * IconMargin; + if(icon_height > opt_base_y) { + opt_base_y = icon_height; + } + + const auto icon_width = render::GetTextureWidth(this->icon_tex) + 2 * IconMargin; + + const auto icon_title_width = title_width + icon_width; + if(icon_title_width > dialog_width) { + dialog_width = icon_title_width; + } + + const auto icon_cnt_width = cnt_width + icon_width; + if(icon_cnt_width > dialog_width) { + dialog_width = icon_cnt_width; + } + + const auto icon_opts_width = opts_width + icon_width; + if(icon_opts_width > dialog_width) { + dialog_width = icon_opts_width; + } + } + + if(dialog_width > render::ScreenWidth) { + dialog_width = render::ScreenWidth; + } + + auto dialog_height = opt_base_y + OptionHeight + OptionBottomMargin; + if(dialog_height > render::ScreenHeight) { + dialog_height = render::ScreenHeight; + } + + const auto dialog_x = (render::ScreenWidth - dialog_width) / 2; + const auto dialog_y = (render::ScreenHeight - dialog_height) / 2; + opt_base_y += dialog_y; + + auto is_finishing = false; + i32 initial_fade_alpha = 0; + while(true) { + const auto ok = app_ref->CallForRenderWithRenderOver([&](render::Renderer::Ref &drawer) -> bool { + const auto keys_down = app_ref->GetButtonsDown(); + const auto tch_state = app_ref->GetTouchState(); + const TouchPoint tch_pos = { tch_state.touches[0].x, tch_state.touches[0].y }; + if(keys_down & HidNpadButton_AnyLeft) { + if(this->selected_opt_idx > 0) { + this->prev_selected_opt_idx = this->selected_opt_idx; + this->selected_opt_idx--; + + this->selected_opt_over_alpha = 0; + this->prev_selected_opt_over_alpha = 0xFF; + } + } + else if(keys_down & HidNpadButton_AnyRight) { + if(this->selected_opt_idx < (this->opt_texs.size() - 1)) { + this->prev_selected_opt_idx = this->selected_opt_idx; + this->selected_opt_idx++; + + this->selected_opt_over_alpha = 0; + this->prev_selected_opt_over_alpha = 0xFF; + } + } + else if(keys_down & HidNpadButton_A) { + this->user_cancelled = false; + is_finishing = true; + } + else if(keys_down & HidNpadButton_B) { + this->user_cancelled = true; + is_finishing = true; + } + if(tch_state.count > 0) { + auto cur_opt_x = dialog_x + OptionsBaseHorizontalMargin; + for(u32 i = 0; i < this->opts.size(); i++) { + auto &opt_tex = this->opt_texs.at(i); + const auto opt_name_width = render::GetTextureWidth(opt_tex); + const auto opt_width = opt_name_width + 2 * OptionHorizontalMargin; + + if(tch_pos.HitsRegion(cur_opt_x, opt_base_y, opt_width, OptionHeight)) { + this->selected_opt_idx = i; + this->user_cancelled = false; + is_finishing = true; + break; + } + + cur_opt_x += opt_width + SpaceBetweenOptions; + } + } + + const auto dialog_clr = MakeDialogColor(static_cast(initial_fade_alpha)); + auto screen_fade_alpha = initial_fade_alpha; + if(screen_fade_alpha < 0) { + screen_fade_alpha = 0; + } + if(screen_fade_alpha > MaxScreenFadeAlpha) { + screen_fade_alpha = MaxScreenFadeAlpha; + } + const Color screen_fade_clr = { 0, 0, 0, static_cast(screen_fade_alpha) }; + drawer->RenderRectangleFill(screen_fade_clr, 0, 0, render::ScreenWidth, render::ScreenHeight); + + drawer->RenderRoundedRectangleFill(dialog_clr, dialog_x, dialog_y, dialog_width, dialog_height, DialogBorderRadius); + + render::SetAlphaValue(this->title_tex, initial_fade_alpha); + render::SetAlphaValue(this->cnt_tex, initial_fade_alpha); + drawer->RenderTexture(this->title_tex, dialog_x + TitleX, dialog_y + TitleY); + drawer->RenderTexture(this->cnt_tex, dialog_x + ContentX, dialog_y + ContentY); + + if(this->HasIcon()) { + const auto icon_width = render::GetTextureWidth(this->icon_tex); + const auto icon_x = dialog_x + (dialog_width - (icon_width + 2 * IconMargin)); + const auto icon_y = dialog_y + IconMargin; + drawer->RenderTexture(this->icon_tex, icon_x, icon_y, render::TextureRenderOptions::WithCustomAlpha(static_cast(initial_fade_alpha))); + } + + auto cur_opt_x = dialog_x + OptionsBaseHorizontalMargin; + for(u32 i = 0; i < this->opt_texs.size(); i++) { + auto &opt_tex = this->opt_texs.at(i); + const auto opt_name_width = render::GetTextureWidth(opt_tex); + const auto opt_name_height = render::GetTextureHeight(opt_tex); + const auto opt_width = opt_name_width + 2 * OptionHorizontalMargin; + const auto opt_name_x = cur_opt_x + OptionHorizontalMargin; + const auto opt_name_y = opt_base_y + ((OptionHeight - opt_name_height) / 2); + if(this->selected_opt_idx == i) { + if(this->selected_opt_over_alpha < 0xFF) { + const auto over_clr = MakeOverColor(static_cast(this->selected_opt_over_alpha)); + drawer->RenderRoundedRectangleFill(over_clr, cur_opt_x, opt_base_y, opt_width, OptionHeight, OptionBorderRadius); + this->selected_opt_over_alpha += OverAlphaIncrement; + } + else { + this->selected_opt_over_alpha = 0xFF; + const auto over_clr = MakeOverColor(static_cast(initial_fade_alpha)); + drawer->RenderRoundedRectangleFill(over_clr, cur_opt_x, opt_base_y, opt_width, OptionHeight, OptionBorderRadius); + } + } + else if(this->prev_selected_opt_idx == static_cast(i)) { + if(this->prev_selected_opt_over_alpha > 0) { + const auto over_clr = MakeOverColor(static_cast(this->prev_selected_opt_over_alpha)); + drawer->RenderRoundedRectangleFill(over_clr, cur_opt_x, opt_base_y, opt_width, OptionHeight, OptionBorderRadius); + this->prev_selected_opt_over_alpha -= OverAlphaIncrement; + } + else { + this->prev_selected_opt_over_alpha = 0; + } + } + + render::SetAlphaValue(opt_tex, static_cast(initial_fade_alpha)); + drawer->RenderTexture(opt_tex, opt_name_x, opt_name_y); + cur_opt_x += opt_width + SpaceBetweenOptions; + } + + if(is_finishing) { + if(initial_fade_alpha == 0) { + return false; + } + + if(initial_fade_alpha > 0) { + initial_fade_alpha -= FadeAlphaIncrement; + } + if(initial_fade_alpha < 0) { + initial_fade_alpha = 0; + } + } + else { + if(initial_fade_alpha < 0xFF) { + initial_fade_alpha += FadeAlphaIncrement; + } + if(initial_fade_alpha > 0xFF) { + initial_fade_alpha = 0xFF; + } + } + return true; + }); + + if(!ok) { + app_ref->CallForRenderWithRenderOver([](render::Renderer::Ref&) -> bool { return false; }); + break; + } + } + + return this->selected_opt_idx; + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/ui_Layout.cpp b/include/Plutonium/Plutonium/source/pu/ui/ui_Layout.cpp new file mode 100644 index 0000000..7e7996c --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/ui_Layout.cpp @@ -0,0 +1,27 @@ +#include + +namespace pu::ui { + + Layout::~Layout() { + render::DeleteTexture(this->over_bg_tex); + } + + void Layout::SetBackgroundImage(const std::string &path) { + render::DeleteTexture(this->over_bg_tex); + this->has_image = true; + this->over_bg_tex = render::LoadImage(path); + } + + void Layout::SetBackgroundColor(const Color clr) { + render::DeleteTexture(this->over_bg_tex); + this->has_image = false; + this->over_bg_color = clr; + } + + TouchPoint Layout::ConsumeSimulatedTouchPosition() { + auto touch_pos_copy = this->sim_touch_pos; + this->sim_touch_pos = {}; + return touch_pos_copy; + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/ui_Overlay.cpp b/include/Plutonium/Plutonium/source/pu/ui/ui_Overlay.cpp new file mode 100644 index 0000000..34f88ef --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/ui_Overlay.cpp @@ -0,0 +1,46 @@ +#include + +namespace pu::ui { + + bool Overlay::Render(render::Renderer::Ref &drawer) { + this->OnPreRender(drawer); + drawer->SetBaseRenderAlpha(static_cast(this->fade_a)); + if(this->round) { + drawer->RenderRoundedRectangleFill(this->bg_clr, this->x, this->y, this->w, this->h, this->rad); + } + else { + drawer->RenderRectangleFill(this->bg_clr, this->x, this->y, this->w, this->h); + } + + this->PreRender(); + for(auto &elem: this->elems) { + if(elem->IsVisible()) { + elem->OnRender(drawer, elem->GetProcessedX(), elem->GetProcessedY()); + } + } + drawer->ResetBaseRenderAlpha(); + if(this->is_ending) { + if(this->fade_a > 0) { + this->fade_a -= FadeAlphaVariation; + } + else { + this->fade_a = 0; + } + } + else { + if(this->fade_a < MaxFadeAlpha) { + this->fade_a += FadeAlphaVariation; + } + else { + this->fade_a = MaxFadeAlpha; + } + } + this->OnPostRender(drawer); + const auto is_finished = this->is_ending && (this->fade_a == 0); + if(is_finished) { + this->is_ending = false; + } + return !is_finished; + } + +} \ No newline at end of file diff --git a/include/Plutonium/Plutonium/source/pu/ui/ui_Types.cpp b/include/Plutonium/Plutonium/source/pu/ui/ui_Types.cpp new file mode 100644 index 0000000..f7a79d2 --- /dev/null +++ b/include/Plutonium/Plutonium/source/pu/ui/ui_Types.cpp @@ -0,0 +1,30 @@ +#include + +namespace pu::ui { + + Color Color::FromHex(const std::string &str_clr) { + // Format: '#rrggbbaa' + + std::string r = "00"; + std::string g = "00"; + std::string b = "00"; + std::string a = "FF"; + + if(str_clr.length() >= 9) { + a = str_clr.substr(7, 2); + } + if(str_clr.length() >= 7) + { + r = str_clr.substr(1, 2); + g = str_clr.substr(3, 2); + b = str_clr.substr(5, 2); + } + + const auto r_val = static_cast(std::stoul(r, nullptr, 16)); + const auto g_val = static_cast(std::stoul(g, nullptr, 16)); + const auto b_val = static_cast(std::stoul(b, nullptr, 16)); + const auto a_val = static_cast(std::stoul(a, nullptr, 16)); + return { r_val, g_val, b_val, a_val }; + } + +} \ No newline at end of file