commit 25bcbf172741924436fafe117a2a1bd68ac89242 Author: Maschell Date: Fri Oct 2 19:57:53 2020 +0200 first commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7f27c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +build/* +release/* +libgui.cbp +lib/ +*.bz2 +libgui.layout +obj/ +CMakeLists.txt +cmake-build-debug/ +.idea/ diff --git a/Dockerfile.wiiu b/Dockerfile.wiiu new file mode 100644 index 0000000..b142c48 --- /dev/null +++ b/Dockerfile.wiiu @@ -0,0 +1,11 @@ +FROM wiiuenv/devkitppc:20200810 + +RUN dkp-pacman -Syu && dkp-pacman -S --noconfirm wiiu-sdl2-libs && dkp-pacman -Scc --noconfirm + +WORKDIR tmp_build +COPY . . +RUN make clean && make -j8 && mkdir -p /artifacts/wut/usr && cp -r lib /artifacts/wut/usr && cp -r include /artifacts/wut/usr +WORKDIR /artifacts + +FROM scratch +COPY --from=0 /artifacts /artifacts \ No newline at end of file diff --git a/Makefile.wiiu b/Makefile.wiiu new file mode 100644 index 0000000..9af6b72 --- /dev/null +++ b/Makefile.wiiu @@ -0,0 +1,160 @@ +#------------------------------------------------------------------------------- +.SUFFIXES: +#------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) + +include $(DEVKITPRO)/wut/share/wut_rules + +export VER_MAJOR := 1 +export VER_MINOR := 0 +export VER_PATCH := 0 + +VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_PATCH) + +#------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# 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 +#------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source \ + source/gui \ + source/gui/input \ + source/gui/system \ + source/gui/video +DATA := data +INCLUDES := source \ + include/gui-sdl \ + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +CFLAGS := -Wall -Werror -save-temps \ + -ffunction-sections -fdata-sections \ + $(MACHDEP) \ + $(BUILD_CFLAGS) + +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ + +CXXFLAGS := $(CFLAGS) -std=gnu++20 + +ASFLAGS := $(MACHDEP) + +LDFLAGS = $(ARCH) -Wl,--gc-sections + +LIBS := + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUT_ROOT) + +#--------------------------------------------------------------------------------- +# 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 TOPDIR := $(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))) +DEFFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.def))) +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 OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(DEFFILES:.def=.o) $(SFILES:.s=.o) $(CFILES:.c=.o) $(CPPFILES:.cpp=.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. -I$(PORTLIBS_PATH)/ppc/include/freetype2 + +.PHONY: all dist-bin dist-src dist install clean + +#--------------------------------------------------------------------------------- +all: lib/libgui-sdl.a + +dist-bin: all + @tar --exclude=*~ -cjf libgui-sdl-$(VERSION).tar.bz2 include lib + +dist-src: + @tar --exclude=*~ -cjf libgui-sdl-src-$(VERSION).tar.bz2 include source Makefile.wiiu + +dist: dist-src dist-bin + +install: dist-bin + mkdir -p $(DESTDIR)$(DEVKITPRO)/wut/usr + bzip2 -cd libgui-sdl-$(VERSION).tar.bz2 | tar -xf - -C $(DESTDIR)$(DEVKITPRO)/wut/usr + +lib: + @[ -d $@ ] || mkdir -p $@ + +release: + @[ -d $@ ] || mkdir -p $@ + +lib/libgui-sdl.a :$(SOURCES) $(INCLUDES) | lib release + @$(MAKE) -f $(CURDIR)/Makefile.wiiu BUILD=release OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DNDEBUG=1 -O2 -s" \ + DEPSDIR=$(CURDIR)/release \ + --no-print-directory -C release \ + -f $(CURDIR)/Makefile.wiiu + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -rf release lib + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES_SRC) : $(HFILES) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a529a7 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# libgui-sdl + + +## Dependencies +To be able to use libgui, you need to install the following dependencies: + +- [wut](https://github.com/devkitPro/wut/) +- Install the required portlibs and sdl2 via `(dkp-)pacman -Syu wiiu-portlibs wiiu-sdl2-libs` + +# Credits +- Orignally based on https://github.com/dborth/libwiigui +- Wii U port / modification / new functions / sound / much more by dimok. diff --git a/include/gui-sdl/gui/GuiButton.h b/include/gui-sdl/gui/GuiButton.h new file mode 100644 index 0000000..30b75ff --- /dev/null +++ b/include/gui-sdl/gui/GuiButton.h @@ -0,0 +1,130 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once +#include +#include +#include +#include +#include + +//!Display, manage, and manipulate buttons in the GUI. Buttons can have images, icons, text, and sound set (all of which are optional) +class GuiButton : public GuiElement { +public: + //!Constructor + //!\param w Width + //!\param h Height + GuiButton(float w, float h); + + //!Destructor + virtual ~GuiButton(); + + //!Sets the button's image + //!\param i Pointer to GuiImage object + void setImage(GuiImage *i); + + //!Sets the button's image on over + //!\param i Pointer to GuiImage object + void setImageOver(GuiImage *i); + + void setIcon(GuiImage *i); + + void setIconOver(GuiImage *i); + + //!Sets the button's image on hold + //!\param i Pointer to GuiImage object + void setImageHold(GuiImage *i); + + //!Sets the button's image on click + //!\param i Pointer to GuiImage object + void setImageClick(GuiImage *i); + + //!Sets the button's label + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void setLabel(GuiText *t, int32_t n = 0); + + //!Sets the button's label on over (eg: different colored text) + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void setLabelOver(GuiText *t, int32_t n = 0); + + //!Sets the button's label on hold + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void setLabelHold(GuiText *t, int32_t n = 0); + + //!Sets the button's label on click + //!\param t Pointer to GuiText object + //!\param n Index of label to set (optional, default is 0) + void setLabelClick(GuiText *t, int32_t n = 0); + + //!Sets the sound to play on over + //!\param s Pointer to GuiSound object + void setSoundOver(GuiSound *s); + + //!Sets the sound to play on hold + //!\param s Pointer to GuiSound object + void setSoundHold(GuiSound *s); + + //!Sets the sound to play on click + //!\param s Pointer to GuiSound object + void setSoundClick(GuiSound *s); + + //!Set a new GuiTrigger for the element + //!\param i Index of trigger array to set + //!\param t Pointer to GuiTrigger + void setTrigger(GuiTrigger *t, int32_t idx = -1); + + //! + void resetState(void) override; + + //!Constantly called to draw the GuiButton + void draw(Renderer *video) override; + + //!Constantly called to allow the GuiButton to respond to updated input data + //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD + void update(GuiController *c) override; + + void process() override; + + sigslot::signal2 selected; + sigslot::signal2 deSelected; + sigslot::signal2 pointedOn; + sigslot::signal2 pointedOff; + sigslot::signal3 clicked; + sigslot::signal3 held; + sigslot::signal3 released; +protected: + static const int32_t iMaxGuiTriggers = 10; + + GuiImage *image; //!< Button image (default) + GuiImage *imageOver; //!< Button image for STATE_SELECTED + GuiImage *imageHold; //!< Button image for STATE_HELD + GuiImage *imageClick; //!< Button image for STATE_CLICKED + GuiImage *icon; + GuiImage *iconOver; + GuiText *label[4]; //!< Label(s) to display (default) + GuiText *labelOver[4]; //!< Label(s) to display for STATE_SELECTED + GuiText *labelHold[4]; //!< Label(s) to display for STATE_HELD + GuiText *labelClick[4]; //!< Label(s) to display for STATE_CLICKED + GuiSound *soundOver; //!< Sound to play for STATE_SELECTED + GuiSound *soundHold; //!< Sound to play for STATE_HELD + GuiSound *soundClick; //!< Sound to play for STATE_CLICKED + GuiTrigger *trigger[iMaxGuiTriggers]; //!< GuiTriggers (input actions) that this element responds to + GuiTrigger *clickedTrigger; + GuiTrigger *heldTrigger; +}; diff --git a/include/gui-sdl/gui/GuiController.h b/include/gui-sdl/gui/GuiController.h new file mode 100644 index 0000000..f506eb2 --- /dev/null +++ b/include/gui-sdl/gui/GuiController.h @@ -0,0 +1,68 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include "GuiTrigger.h" + +class GuiController { +public: + //!Constructor + GuiController(int32_t channel) + : chan(channel) { + memset(&lastData, 0, sizeof(lastData)); + memset(&data, 0, sizeof(data)); + + switch (chan) { + default: + case GuiTrigger::CHANNEL_1: + chanIdx = 0; + break; + case GuiTrigger::CHANNEL_2: + chanIdx = 1; + break; + case GuiTrigger::CHANNEL_3: + chanIdx = 2; + break; + case GuiTrigger::CHANNEL_4: + chanIdx = 3; + break; + case GuiTrigger::CHANNEL_5: + chanIdx = 4; + break; + } + } + + //!Destructor + virtual ~GuiController() {} + + typedef struct { + uint32_t buttons_h; + uint32_t buttons_d; + uint32_t buttons_r; + bool validPointer; + bool touched; + float pointerAngle; + int32_t x; + int32_t y; + } PadData; + + uint32_t chan; + int32_t chanIdx; + PadData data; + PadData lastData; +}; diff --git a/include/gui-sdl/gui/GuiElement.h b/include/gui-sdl/gui/GuiElement.h new file mode 100644 index 0000000..2871b33 --- /dev/null +++ b/include/gui-sdl/gui/GuiElement.h @@ -0,0 +1,629 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sigslot.h" +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +enum { + EFFECT_NONE = 0x00, + EFFECT_SLIDE_TOP = 0x01, + EFFECT_SLIDE_BOTTOM = 0x02, + EFFECT_SLIDE_RIGHT = 0x04, + EFFECT_SLIDE_LEFT = 0x08, + EFFECT_SLIDE_IN = 0x10, + EFFECT_SLIDE_OUT = 0x20, + EFFECT_SLIDE_FROM = 0x40, + EFFECT_FADE = 0x80, + EFFECT_SCALE = 0x100, + EFFECT_COLOR_TRANSITION = 0x200 +}; + +enum { + ALIGN_LEFT = 0x01, + ALIGN_CENTER = 0x02, + ALIGN_RIGHT = 0x04, + ALIGN_TOP = 0x10, + ALIGN_MIDDLE = 0x20, + ALIGN_BOTTOM = 0x40, + ALIGN_TOP_LEFT = ALIGN_LEFT | ALIGN_TOP, + ALIGN_TOP_CENTER = ALIGN_CENTER | ALIGN_TOP, + ALIGN_TOP_RIGHT = ALIGN_RIGHT | ALIGN_TOP, + ALIGN_CENTERED = ALIGN_CENTER | ALIGN_MIDDLE, +}; + +//!Forward declaration +class GuiController; + +class SDLSystem; + +//!Primary GUI class. Most other classes inherit from this class. +class GuiElement { +public: + //!Constructor + GuiElement(); + + //!Destructor + virtual ~GuiElement() = default; + + //!Set the element's parent + //!\param e Pointer to parent element + virtual void setParent(GuiElement *e) { + parentElement = e; + } + + //!Gets the element's parent + //!\return Pointer to parent element + virtual GuiElement *getParent() { + return parentElement; + } + + //!Gets the current leftmost coordinate of the element + //!Considers horizontal alignment, x offset, width, and parent element's GetLeft() / GetWidth() values + //!\return left coordinate + virtual float getLeft(); + + //!Gets the current topmost coordinate of the element + //!Considers vertical alignment, y offset, height, and parent element's GetTop() / GetHeight() values + //!\return top coordinate + virtual float getTop(); + + //!Gets the current Z coordinate of the element + //!\return Z coordinate + virtual float getDepth() { + float zParent = 0.0f; + + if (parentElement) { + zParent = parentElement->getDepth(); + } + + return zParent + zoffset; + } + + virtual float getCenterX() { + float pCenterX = 0.0f; + + if (parentElement) { + pCenterX = parentElement->getCenterX(); + } + + pCenterX += xoffset + xoffsetDyn; + + if (alignment & ALIGN_LEFT) { + float pWidth = 0.0f; + float pScale = 0.0f; + + if (parentElement) { + pWidth = parentElement->getWidth(); + pScale = parentElement->getScaleX(); + } + + pCenterX -= pWidth * 0.5f * pScale - width * 0.5f * getScaleX(); + } else if (alignment & ALIGN_RIGHT) { + float pWidth = 0.0f; + float pScale = 0.0f; + + if (parentElement) { + pWidth = parentElement->getWidth(); + pScale = parentElement->getScaleX(); + } + + pCenterX += pWidth * 0.5f * pScale - width * 0.5f * getScaleX(); + } + return pCenterX; + } + + virtual float getCenterY() { + float pCenterY = 0.0f; + + if (parentElement) { + pCenterY = parentElement->getCenterY(); + } + + pCenterY += yoffset + yoffsetDyn; + + if (alignment & ALIGN_TOP) { + float pHeight = 0.0f; + float pScale = 0.0f; + + if (parentElement) { + pHeight = parentElement->getHeight(); + pScale = parentElement->getScaleY(); + } + + pCenterY += pHeight * 0.5f * pScale + getHeight() * 0.5f * getScaleY(); + } else if (alignment & ALIGN_BOTTOM) { + float pHeight = 0.0f; + float pScale = 0.0f; + + if (parentElement) { + pHeight = parentElement->getHeight(); + pScale = parentElement->getScaleY(); + } + + pCenterY -= pHeight * 0.5f * pScale - getHeight() * 0.5f * getScaleY(); + } + return pCenterY; + } + + //!Gets elements xoffset + virtual float getOffsetX() { + return xoffset; + } + + //!Gets elements yoffset + virtual float getOffsetY() { + return yoffset; + } + + //!Gets the current width of the element. Does not currently consider the scale + //!\return width + virtual float getWidth() { + return width; + }; + + //!Gets the height of the element. Does not currently consider the scale + //!\return height + virtual float getHeight() { + return height; + } + + //!Sets the size (width/height) of the element + //!\param w Width of element + //!\param h Height of element + virtual void setSize(float w, float h) { + width = w; + height = h; + } + + //!Sets the element's visibility + //!\param v Visibility (true = visible) + virtual void setVisible(bool v) { + visible = v; + visibleChanged(this, v); + } + + //!Checks whether or not the element is visible + //!\return true if visible, false otherwise + virtual bool isVisible() const { + return !isStateSet(STATE_HIDDEN) && visible; + }; + + //!Checks whether or not the element is selectable + //!\return true if selectable, false otherwise + virtual bool isSelectable() { + return !isStateSet(STATE_DISABLED) && selectable; + } + + virtual bool isDrawOverOnlyWhenSelected() { + return drawOverOnlyWhenSelected; + } + + virtual void setdrawOverOnlyWhenSelected(bool s) { + drawOverOnlyWhenSelected = s; + } + + //!Checks whether or not the element is clickable + //!\return true if clickable, false otherwise + virtual bool isClickable() { + return !isStateSet(STATE_DISABLED) && clickable; + } + + //!Checks whether or not the element is holdable + //!\return true if holdable, false otherwise + virtual bool isHoldable() { + return !isStateSet(STATE_DISABLED) && holdable; + } + + //!Sets whether or not the element is selectable + //!\param s Selectable + virtual void setSelectable(bool s) { + selectable = s; + } + + //!Sets whether or not the element is clickable + //!\param c Clickable + virtual void setClickable(bool c) { + clickable = c; + } + + //!Sets whether or not the element is holdable + //!\param c Holdable + virtual void setHoldable(bool d) { + holdable = d; + } + + //!Sets the element's state + //!\param s State (STATE_DEFAULT, STATE_SELECTED, STATE_CLICKED, STATE_DISABLED) + //!\param c Controller channel (0-3, -1 = none) + virtual void setState(uint32_t s, int32_t c) { + if (c >= 0 && c < 5) { + state[c] |= s; + } else { + for (int32_t i = 0; i < 5; i++) { + state[i] |= s; + } + } + stateChan = c; + stateChanged(this, s, c); + } + + virtual void clearState(uint32_t s, int32_t c) { + if (c >= 0 && c < 5) { + state[c] &= ~s; + } else { + for (unsigned int & i : state) { + i &= ~s; + } + } + stateChan = c; + stateChanged(this, s, c); + } + + virtual bool isStateSet(uint32_t s, int32_t c = -1) const { + if (c >= 0 && c < 5) { + return (state[c] & s) != 0; + } else { + for (unsigned int i : state) { + if ((i & s) != 0) { + return true; + } + } + + return false; + } + } + + //!Gets the element's current state + //!\return state + virtual int32_t getState(int32_t c) { + return state[c]; + }; + + //!Gets the controller channel that last changed the element's state + //!\return Channel number (0-3, -1 = no channel) + virtual int32_t getStateChan() { + return stateChan; + }; + + //!Resets the element's state to STATE_DEFAULT + virtual void resetState() { + for (unsigned int & i : state) { + i = STATE_DEFAULT; + } + stateChan = -1; + } + + //!Sets the element's alpha value + //!\param a alpha value + virtual void setAlpha(float a) { + alpha = a; + } + + //!Gets the element's alpha value + //!Considers alpha, alphaDyn, and the parent element's getAlpha() value + //!\return alpha + virtual float getAlpha() { + float a; + + if (alphaDyn >= 0) { + a = alphaDyn; + } else { + a = alpha; + } + + if (parentElement) { + a = (a * parentElement->getAlpha()); + } + + return a; + } + + //!Sets the element's scale + //!\param s scale (1 is 100%) + virtual void setScale(float s) { + scaleX = s; + scaleY = s; + scaleZ = s; + } + + //!Sets the element's scale + //!\param s scale (1 is 100%) + virtual void setScaleX(float s) { + scaleX = s; + } + + //!Sets the element's scale + //!\param s scale (1 is 100%) + virtual void setScaleY(float s) { + scaleY = s; + } + + //!Sets the element's scale + //!\param s scale (1 is 100%) + virtual void setScaleZ(float s) { + scaleZ = s; + } + + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's getScale() value + virtual float getScale() { + float s = 0.5f * (scaleX + scaleY) * scaleDyn; + + if (parentElement) { + s *= parentElement->getScale(); + } + + return s; + } + + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's getScale() value + virtual float getScaleX() { + float s = scaleX * scaleDyn; + + if (parentElement) { + s *= parentElement->getScaleX(); + } + + return s; + } + + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's getScale() value + virtual float getScaleY() { + float s = scaleY * scaleDyn; + + if (parentElement) { + s *= parentElement->getScaleY(); + } + + return s; + } + + //!Gets the element's current scale + //!Considers scale, scaleDyn, and the parent element's getScale() value + virtual float getScaleZ() { + float s = scaleZ; + + if (parentElement) { + s *= parentElement->getScaleZ(); + } + + return s; + } + + //!Checks whether rumble was requested by the element + //!\return true is rumble was requested, false otherwise + virtual bool isRumbleActive() { + return rumble; + } + + //!Sets whether or not the element is requesting a rumble event + //!\param r true if requesting rumble, false if not + virtual void setRumble(bool r) { + rumble = r; + } + + //!Set an effect for the element + //!\param e Effect to enable + //!\param a Amount of the effect (usage varies on effect) + //!\param t Target amount of the effect (usage varies on effect) + virtual void setEffect(uint32_t e, int32_t a, int32_t t); + + //!Sets an effect to be enabled on wiimote cursor over + //!\param e Effect to enable + //!\param a Amount of the effect (usage varies on effect) + //!\param t Target amount of the effect (usage varies on effect) + virtual void setEffectOnOver(uint32_t e, int32_t a, int32_t t); + + //!Shortcut to SetEffectOnOver(EFFECT_SCALE, 4, 110) + virtual void setEffectGrow() { + setEffectOnOver(EFFECT_SCALE, 4, 110); + } + + //!Reset all applied effects + virtual void resetEffects(); + + //!Gets the current element effects + //!\return element effects + virtual int32_t getEffect() const { + return effects; + } + + //!\return true if element animation is on going + virtual bool isAnimated() const { + return (parentElement != 0) && (getEffect() > 0); + } + + //!Checks whether the specified coordinates are within the element's boundaries + //!\param x X coordinate + //!\param y Y coordinate + //!\return true if contained within, false otherwise + virtual bool isInside(float x, float y) { + float rotatedX = x; + float rotatedY = y; + + if (getAngle() != 0.f) { + // translate input point + float tempX = x - getCenterX(); + float tempY = y - getCenterY(); + + // Conver to Rad + auto angleInRad = (float) ((getAngle() * -1.0f) * M_PI / 180.0f); + + // now apply rotation + rotatedX = tempX * cos((angleInRad)) - tempY * sin(angleInRad); + rotatedY = tempX * sin(angleInRad) + tempY * cos(angleInRad); + + // translate back + rotatedX = rotatedX + getCenterX(); + rotatedY = rotatedY + getCenterY(); + } + + return (rotatedX > (this->getCenterX() - getScaleX() * getWidth() * 0.5f) + && rotatedX < (this->getCenterX() + getScaleX() * getWidth() * 0.5f) + && rotatedY > (this->getCenterY() - getScaleY() * getHeight() * 0.5f) + && rotatedY < (this->getCenterY() + getScaleY() * getHeight() * 0.5f)); + } + + //!Sets the element's position + //!\param x X coordinate + //!\param y Y coordinate + virtual void setPosition(float x, float y) { + xoffset = x; + yoffset = y; + } + + //!Sets the element's position + //!\param x X coordinate + //!\param y Y coordinate + //!\param z Z coordinate + virtual void setPosition(float x, float y, float z) { + xoffset = x; + yoffset = y; + zoffset = z; + } + + //!Gets whether or not the element is in STATE_SELECTED + //!\return true if selected, false otherwise + virtual int32_t getSelected() { + return -1; + } + + //!Sets the element's alignment respective to its parent element + //!Bitwise ALIGN_LEFT | ALIGN_RIGHT | ALIGN_CENTRE, ALIGN_TOP, ALIGN_BOTTOM, ALIGN_MIDDLE) + //!\param align Alignment + virtual void setAlignment(int32_t a) { + alignment = a; + } + + //!Gets the element's alignment + virtual int32_t getAlignment() const { + return alignment; + } + + //!Angle of the object + virtual void setAngle(float a) { + angle = a; + } + + //!Angle of the object + virtual float getAngle() const { + float r_angle = angle; + if (parentElement) { r_angle += parentElement->getAngle(); } + return r_angle; + } + + //!Called constantly to allow the element to respond to the current input data + //!\param t Pointer to a GuiController, containing the current input data from PAD/WPAD/VPAD + virtual void update(GuiController *t) {} + + //!Called constantly to redraw the element + virtual void draw(Renderer * v) {} + + //!Called constantly to process stuff in the element + virtual void process() {} + + //!Updates the element's effects (dynamic values) + //!Called by Draw(), used for animation purposes + virtual void updateEffects(); + + typedef struct _POINT { + int32_t x; + int32_t y; + } POINT; + + enum { + STATE_DEFAULT = 0, + STATE_SELECTED = 0x01, + STATE_CLICKED = 0x02, + STATE_HELD = 0x04, + STATE_OVER = 0x08, + STATE_HIDDEN = 0x10, + STATE_DISABLE_INPUT = 0x20, + STATE_CLICKED_TOUCH = 0x40, + STATE_DISABLED = 0x80 + }; + + //! Switch pointer from control to screen position + POINT PtrToScreen(POINT p) { + //! TODO for 3D + //POINT r = { p.x + getLeft(), p.y + getTop() }; + return p; + } + + //! Switch pointer screen to control position + POINT PtrToControl(POINT p) { + //! TODO for 3D + //POINT r = { p.x - getLeft(), p.y - getTop() }; + return p; + } + + //! Signals + sigslot::signal2 visibleChanged; + sigslot::signal3 stateChanged; + sigslot::signal1 effectFinished; +protected: + bool rumble; //!< Wiimote rumble (on/off) - set to on when this element requests a rumble event + bool visible; //!< Visibility of the element. If false, Draw() is skipped + bool selectable; //!< Whether or not this element selectable (can change to SELECTED state) + bool clickable; //!< Whether or not this element is clickable (can change to CLICKED state) + bool holdable; //!< Whether or not this element is holdable (can change to HELD state) + bool drawOverOnlyWhenSelected; //!< Whether or not this element is holdable (can change to HELD state) + float width; //!< Element width + float height; //!< Element height + float xoffset; //!< Element X offset + float yoffset; //!< Element Y offset + float zoffset; //!< Element Z offset + float alpha; //!< Element alpha value (0-255) + float angle; //!< Angle of the object (0-360) + float scaleX; //!< Element scale (1 = 100%) + float scaleY; //!< Element scale (1 = 100%) + float scaleZ; //!< Element scale (1 = 100%) + uint32_t alignment; //!< Horizontal element alignment, respective to parent element + uint32_t state[5]{}; //!< Element state (DEFAULT, SELECTED, CLICKED, DISABLED) + int32_t stateChan; //!< Which controller channel is responsible for the last change in state + GuiElement *parentElement; //!< Parent element + + //! TODO: Move me to some Animator class + int32_t xoffsetDyn; //!< Element X offset, dynamic (added to xoffset value for animation effects) + int32_t yoffsetDyn; //!< Element Y offset, dynamic (added to yoffset value for animation effects) + float alphaDyn; //!< Element alpha, dynamic (multiplied by alpha value for blending/fading effects) + float scaleDyn; //!< Element scale, dynamic (multiplied by alpha value for blending/fading effects) + uint32_t effects; //!< Currently enabled effect(s). 0 when no effects are enabled + int32_t effectAmount; //!< Effect amount. Used by different effects for different purposes + int32_t effectTarget; //!< Effect target amount. Used by different effects for different purposes + uint32_t effectsOver; //!< Effects to enable when wiimote cursor is over this element. Copied to effects variable on over event + int32_t effectAmountOver; //!< EffectAmount to set when wiimote cursor is over this element + int32_t effectTargetOver; //!< EffectTarget to set when wiimote cursor is over this element +}; diff --git a/include/gui-sdl/gui/GuiFrame.h b/include/gui-sdl/gui/GuiFrame.h new file mode 100644 index 0000000..5f1d22e --- /dev/null +++ b/include/gui-sdl/gui/GuiFrame.h @@ -0,0 +1,109 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include + +//!Allows GuiElements to be grouped together into a "window" +class GuiFrame : public GuiElement { +public: + //!Constructor + GuiFrame(GuiFrame *parent = 0); + + //!\overload + //!\param w Width of window + //!\param h Height of window + GuiFrame(float w, float h, GuiFrame *parent = 0); + + //!Destructor + ~GuiFrame() override; + + //!Appends a GuiElement to the GuiFrame + //!\param e The GuiElement to append. If it is already in the GuiFrame, it is removed first + void append(GuiElement *e); + + //!Inserts a GuiElement into the GuiFrame at the specified index + //!\param e The GuiElement to insert. If it is already in the GuiFrame, it is removed first + //!\param i Index in which to insert the element + void insert(GuiElement *e, uint32_t i); + + //!Removes the specified GuiElement from the GuiFrame + //!\param e GuiElement to be removed + void remove(GuiElement *e); + + //!Removes all GuiElements + void removeAll(); + + //!Bring element to front of the window + void bringToFront(GuiElement *e) { + remove(e); + append(e); + } + + //!Returns the GuiElement at the specified index + //!\param index The index of the element + //!\return A pointer to the element at the index, NULL on error (eg: out of bounds) + GuiElement *getGuiElementAt(uint32_t index) const; + + //!Returns the size of the list of elements + //!\return The size of the current element list + uint32_t getSize(); + + //!Sets the visibility of the window + //!\param v visibility (true = visible) + void setVisible(bool v) override; + + //!Resets the window's state to STATE_DEFAULT + void resetState() override; + + //!Sets the window's state + //!\param s State + void setState(uint32_t s, int32_t c = -1) override; + + void clearState(uint32_t s, int32_t c = -1) override; + + //!Gets the index of the GuiElement inside the window that is currently selected + //!\return index of selected GuiElement + int32_t getSelected() override; + + //!Draws all the elements in this GuiFrame + void draw(Renderer *v) override; + + //!Updates the window and all elements contains within + //!Allows the GuiFrame and all elements to respond to the input data specified + //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD + void update(GuiController *t) override; + + //!virtual updateEffects which is called by the main loop + void updateEffects() override; + + //!virtual process which is called by the main loop + void process() override; + + //! Signals + //! On Closing + sigslot::signal1 closing; +protected: + bool dim; //! Enable/disable dim of a window only + GuiFrame *parent; //!< Parent Window + std::vector elements; //!< Contains all elements within the GuiFrame + std::recursive_mutex mutex; +}; diff --git a/include/gui-sdl/gui/GuiImage.h b/include/gui-sdl/gui/GuiImage.h new file mode 100644 index 0000000..8a4e54d --- /dev/null +++ b/include/gui-sdl/gui/GuiImage.h @@ -0,0 +1,50 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include +#include + +//!Display, manage, and manipulate images in the GUI +class GuiImage : public GuiElement { +public: + + GuiImage() = default; + + //! Draws an image from an existing texture + //!\param texture Pointer to GuiTextureData element + explicit GuiImage(GuiTextureData *texture); + + //! Draws a colored rectangle + //!\param texture Pointer to GuiTextureData element + explicit GuiImage(SDL_Color color, float width, float height); + + //!Destructor + ~GuiImage() override; + + void draw(Renderer *r) override; + + void setTexture(GuiTextureData *tex); + +private: + GuiTextureData *texture = nullptr; + bool freeTextureData = false; + + // Color of the rect that's drawn if the picture has no texture. + SDL_Color color = {0, 0, 0, 0}; +}; diff --git a/include/gui-sdl/gui/GuiSound.h b/include/gui-sdl/gui/GuiSound.h new file mode 100644 index 0000000..b6af163 --- /dev/null +++ b/include/gui-sdl/gui/GuiSound.h @@ -0,0 +1,67 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include + +//!Sound conversion and playback. A wrapper for other sound libraries - ASND, libmad, ltremor, etc +class GuiSound : public GuiElement { +public: + explicit GuiSound(const char *filepath); + + //!Constructor + //!\param sound Pointer to the sound data + //!\param filesize Length of sound data + GuiSound(void *buffer, uint32_t filesize, bool freeSrc = false); + + //!Destructor + ~GuiSound() override; + + //!Load a file and replace the old one + bool Load(const char *filepath); + + //!Start sound playback + void Play(); + + //!Stop sound playback + void Stop() const; + + //!Pause sound playback + void Pause() const; + + //!Resume sound playback + void Resume(); + + //!Checks if the sound is currently playing + //!\return true if sound is playing, false otherwise + [[nodiscard]] bool IsPlaying() const; + + //!Rewind the music + void Rewind() const; + + //!Set sound volume + //!\param v Sound volume (0-100) + void SetVolume(uint32_t v) const; + + //!\param l Loop (true to loop) + void SetLoop(bool l); + + Mix_Chunk *music = nullptr; + int32_t loops = 0; + int32_t playedOn = -1; +}; \ No newline at end of file diff --git a/include/gui-sdl/gui/GuiText.h b/include/gui-sdl/gui/GuiText.h new file mode 100644 index 0000000..93a85e2 --- /dev/null +++ b/include/gui-sdl/gui/GuiText.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include + +//!Display, manage, and manipulate text in the GUI +class GuiText : public GuiElement { +public: + + //!\param t Text + //!\param s Font size + //!\param c Font color + GuiText(const std::string &t, SDL_Color c, FC_Font *font); + ~GuiText() override; + + void draw(Renderer *pVideo) override; + + void process() override; + + void setMaxWidth(float width); + +protected: + GuiImage texture; + GuiTextureData* textureData = nullptr; + + std::string text; + SDL_Color color; + FC_Font *fc_font = nullptr; + bool doUpdateTexture = true; + + uint16_t maxWidth = 0xFFFF; + + void updateSize(); + + void updateTexture(Renderer *renderer); +}; diff --git a/include/gui-sdl/gui/GuiTextureData.h b/include/gui-sdl/gui/GuiTextureData.h new file mode 100644 index 0000000..0690b0c --- /dev/null +++ b/include/gui-sdl/gui/GuiTextureData.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +class GuiTextureData { +public: + GuiTextureData(void *buffer, uint32_t filesize, bool freesrc = false); + + explicit GuiTextureData(SDL_Texture *texture); + + explicit GuiTextureData(const std::string &path); + + //!Destructor + ~GuiTextureData(); + + void draw(Renderer *pVideo, const SDL_Rect &rect, float angle); + + int setBlendMode(SDL_BlendMode blendMode); + + [[nodiscard]] int32_t getWidth() const { + return width; + } + + [[nodiscard]] int32_t getHeight() const { + return height; + } + + +protected: + void loadSurface(SDL_Surface *pSurface); + + SDL_Surface *imgSurface = nullptr; + + void cleanUp(); + + SDL_Texture *texture = nullptr; + int32_t width = 0; + int32_t height = 0; + SDL_BlendMode blendMode; +}; diff --git a/include/gui-sdl/gui/GuiTrigger.h b/include/gui-sdl/gui/GuiTrigger.h new file mode 100644 index 0000000..6134d1d --- /dev/null +++ b/include/gui-sdl/gui/GuiTrigger.h @@ -0,0 +1,132 @@ +/*************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include + +class GuiController; + +//!Menu input trigger management. Determine if action is necessary based on input data by comparing controller input data to a specific trigger element. +class GuiTrigger { +public: + enum eClicked { + CLICKED_NONE = 0x00, + CLICKED_TOUCH = 0x01, + CLICKED_BUTTON = 0x02, + }; + enum eChannels { + CHANNEL_1 = 0x01, + CHANNEL_2 = 0x02, + CHANNEL_3 = 0x04, + CHANNEL_4 = 0x08, + CHANNEL_5 = 0x10, + CHANNEL_ALL = 0xFF + }; + enum eButtons { + BUTTON_NONE = 0x0000, + TOUCHED = 0x80000000, + VPAD_TOUCH = TOUCHED, + BUTTON_Z = 0x20000, + BUTTON_C = 0x10000, + BUTTON_A = 0x8000, + BUTTON_B = 0x4000, + BUTTON_X = 0x2000, + BUTTON_Y = 0x1000, + BUTTON_1 = BUTTON_Y, + BUTTON_2 = BUTTON_X, + BUTTON_LEFT = 0x0800, + BUTTON_RIGHT = 0x0400, + BUTTON_UP = 0x0200, + BUTTON_DOWN = 0x0100, + BUTTON_ZL = 0x0080, + BUTTON_ZR = 0x0040, + BUTTON_L = 0x0020, + BUTTON_R = 0x0010, + BUTTON_PLUS = 0x0008, + BUTTON_MINUS = 0x0004, + BUTTON_HOME = 0x0002, + BUTTON_SYNC = 0x0001, + STICK_R_LEFT = 0x04000000, + STICK_R_RIGHT = 0x02000000, + STICK_R_UP = 0x01000000, + STICK_R_DOWN = 0x00800000, + STICK_L_LEFT = 0x40000000, + STICK_L_RIGHT = 0x20000000, + STICK_L_UP = 0x10000000, + STICK_L_DOWN = 0x08000000, + BUTTON_STICK_L = BUTTON_Z, + BUTTON_STICK_R = BUTTON_C, + }; + + //!Constructor + GuiTrigger(); + + //!Constructor + GuiTrigger(uint32_t ch, uint32_t btns, bool clickEverywhere = false, bool holdEverywhere = false, bool selectionClickEverywhere = false); + + //!Destructor + virtual ~GuiTrigger(); + + //!Sets a simple trigger. Requires: element is selected, and trigger button is pressed + void setTrigger(uint32_t ch, uint32_t btns); + + void setClickEverywhere(bool b) { + bClickEverywhere = b; + } + + void setHoldOnly(bool b) { + bHoldEverywhere = b; + } + + void setSelectionClickEverywhere(bool b) { + bSelectionClickEverywhere = b; + } + + [[nodiscard]] bool isClickEverywhere() const { + return bClickEverywhere; + } + + [[nodiscard]] bool isHoldEverywhere() const { + return bHoldEverywhere; + } + + [[nodiscard]] bool isSelectionClickEverywhere() const { + return bSelectionClickEverywhere; + } + + bool left(const GuiController *controller) const; + + bool right(const GuiController *controller) const; + + bool up(const GuiController *controller) const; + + bool down(const GuiController *controller) const; + + int32_t clicked(const GuiController *controller) const; + + bool held(const GuiController *controller) const; + + bool released(const GuiController *controller) const; + +private: + uint32_t chan; + uint32_t btns; + bool bClickEverywhere; + bool bHoldEverywhere; + bool bSelectionClickEverywhere; + bool bLastTouched; +}; diff --git a/include/gui-sdl/gui/input/ControllerManager.h b/include/gui-sdl/gui/input/ControllerManager.h new file mode 100644 index 0000000..6537b6e --- /dev/null +++ b/include/gui-sdl/gui/input/ControllerManager.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include + +class ControllerManager { +public: + ControllerManager(int32_t screenWidth, int32_t screenHeight) : screenWidth(screenWidth), screenHeight(screenHeight){ + + } + + void attachController(GuiTrigger::eChannels channels, SDLController *controller); + + void prepare(); + + bool attachJoystick(int32_t deviceId); + + void detachJoystick(int32_t deviceId); + + void processEvent(SDL_JoystickID joystickId, int32_t channel, SDL_Event *event); + + void finish(); + + + void callPerController(std::function func); + +private: + GuiTrigger::eChannels increaseChannel(GuiTrigger::eChannels channel); + + std::map controllerList; + std::map joystickToChannel; + int32_t screenWidth; + int32_t screenHeight; +}; diff --git a/include/gui-sdl/gui/input/SDLController.h b/include/gui-sdl/gui/input/SDLController.h new file mode 100644 index 0000000..ae4661e --- /dev/null +++ b/include/gui-sdl/gui/input/SDLController.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define printButton(chan, x) if(data.buttons_d & x) LG_Log("Controller #%d %s", chan, #x) + +class SDLController : public GuiController { +public: + explicit SDLController(int32_t channel) : GuiController(channel) { + + } + + virtual bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) = 0; + + + virtual void before() { + lastData = data; + + data.buttons_d = 0; + data.buttons_r = 0; + } + + virtual void after() { + data.buttons_d |= (data.buttons_h & (~(lastData.buttons_h))); + data.buttons_r |= ((lastData.buttons_h) & (~data.buttons_h)); + if (data.buttons_h != 0 || data.buttons_d != 0 || data.buttons_r != 0) { + LG_Log("Controller #%d: h %08X d %08X r %08X", chanIdx, data.buttons_h, data.buttons_d, data.buttons_r); + + printButton(chanIdx, GuiTrigger::BUTTON_A); + printButton(chanIdx, GuiTrigger::BUTTON_B); + printButton(chanIdx, GuiTrigger::BUTTON_X); + printButton(chanIdx, GuiTrigger::BUTTON_Y); + printButton(chanIdx, GuiTrigger::BUTTON_STICK_L); + printButton(chanIdx, GuiTrigger::BUTTON_STICK_R); + printButton(chanIdx, GuiTrigger::BUTTON_L); + printButton(chanIdx, GuiTrigger::BUTTON_R); + printButton(chanIdx, GuiTrigger::BUTTON_ZL); + printButton(chanIdx, GuiTrigger::BUTTON_ZR); + printButton(chanIdx, GuiTrigger::BUTTON_PLUS); + printButton(chanIdx, GuiTrigger::BUTTON_MINUS); + printButton(chanIdx, GuiTrigger::BUTTON_LEFT); + printButton(chanIdx, GuiTrigger::BUTTON_UP); + printButton(chanIdx, GuiTrigger::BUTTON_RIGHT); + printButton(chanIdx, GuiTrigger::BUTTON_DOWN); + printButton(chanIdx, GuiTrigger::STICK_L_LEFT); + printButton(chanIdx, GuiTrigger::STICK_L_UP); + printButton(chanIdx, GuiTrigger::STICK_L_RIGHT); + printButton(chanIdx, GuiTrigger::STICK_L_DOWN); + printButton(chanIdx, GuiTrigger::STICK_R_LEFT); + printButton(chanIdx, GuiTrigger::STICK_R_UP); + printButton(chanIdx, GuiTrigger::STICK_R_RIGHT); + printButton(chanIdx, GuiTrigger::STICK_R_DOWN); + printButton(chanIdx, GuiTrigger::TOUCHED); + } + } +}; diff --git a/include/gui-sdl/gui/input/SDLControllerJoystick.h b/include/gui-sdl/gui/input/SDLControllerJoystick.h new file mode 100644 index 0000000..419171b --- /dev/null +++ b/include/gui-sdl/gui/input/SDLControllerJoystick.h @@ -0,0 +1,58 @@ +#pragma once + +class SDLControllerJoystick : public SDLController { +public: + SDLControllerJoystick(int32_t channel, SDL_JoystickID joystickId) : SDLController(channel) { + + } + + bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) override { + if (e->type == SDL_JOYBUTTONDOWN) { + data.buttons_h |= (1 << e->jbutton.button); + } else if (e->type == SDL_JOYBUTTONUP) { + data.buttons_h &= ~(1 << e->jbutton.button); + } else if (e->type == SDL_JOYHATMOTION) { + auto val = e->jhat.value; + + auto hatMask = (GuiTrigger::BUTTON_LEFT | GuiTrigger::BUTTON_UP | GuiTrigger::BUTTON_DOWN | GuiTrigger::BUTTON_RIGHT); + + // Remove hat values so we can add the new value. + data.buttons_h &= ~hatMask; + + switch (val) { + case SDL_HAT_LEFTUP: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_LEFT: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + break; + case SDL_HAT_LEFTDOWN: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + case SDL_HAT_UP: + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_DOWN: + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + case SDL_HAT_RIGHTUP: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_RIGHT: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + break; + case SDL_HAT_RIGHTDOWN: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + } + } else if (e->type == SDL_JOYAXISMOTION) { + // + } + return true; + } +}; + diff --git a/include/gui-sdl/gui/input/SDLControllerMouse.h b/include/gui-sdl/gui/input/SDLControllerMouse.h new file mode 100644 index 0000000..28bd558 --- /dev/null +++ b/include/gui-sdl/gui/input/SDLControllerMouse.h @@ -0,0 +1,29 @@ +#pragma once + +class SDLControllerMouse: public SDLController { +public: + explicit SDLControllerMouse(int32_t channel) : SDLController(channel) { + + } + + void before() override{ + SDLController::before(); + data.validPointer = true; + } + + virtual bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) override { + if (e->type == SDL_MOUSEMOTION) { + data.y = e->motion.y; + data.x = e->motion.x; + } else if (e->type == SDL_MOUSEBUTTONDOWN && e->button.button == SDL_BUTTON_LEFT) { + data.touched = true; + } else if (e->type == SDL_MOUSEBUTTONUP && e->button.button == SDL_BUTTON_LEFT) { + data.touched = false; + }else{ + LG_Log("Unknown event"); + return false; + } + return true; + } +}; + diff --git a/include/gui-sdl/gui/input/SDLControllerWiiUGamepad.h b/include/gui-sdl/gui/input/SDLControllerWiiUGamepad.h new file mode 100644 index 0000000..fb31402 --- /dev/null +++ b/include/gui-sdl/gui/input/SDLControllerWiiUGamepad.h @@ -0,0 +1,50 @@ +#pragma once + +#include "SDLController.h" + +static GuiTrigger::eButtons vpad_button_map[] = { + GuiTrigger::BUTTON_A, + GuiTrigger::BUTTON_B, + GuiTrigger::BUTTON_X, + GuiTrigger::BUTTON_Y, + GuiTrigger::BUTTON_STICK_L, GuiTrigger::BUTTON_STICK_R, + GuiTrigger::BUTTON_L, GuiTrigger::BUTTON_R, + GuiTrigger::BUTTON_ZL, GuiTrigger::BUTTON_ZR, + GuiTrigger::BUTTON_PLUS, GuiTrigger::BUTTON_MINUS, + GuiTrigger::BUTTON_LEFT, GuiTrigger::BUTTON_UP, GuiTrigger::BUTTON_RIGHT, GuiTrigger::BUTTON_DOWN, + GuiTrigger::STICK_L_LEFT, GuiTrigger::STICK_L_UP, GuiTrigger::STICK_L_RIGHT, GuiTrigger::STICK_L_DOWN, + GuiTrigger::STICK_R_LEFT, GuiTrigger::STICK_R_UP, GuiTrigger::STICK_R_RIGHT, GuiTrigger::STICK_R_DOWN, +}; + +class SDLControllerWiiUGamepad : public SDLController { +public: + explicit SDLControllerWiiUGamepad(int32_t channel) : SDLController(channel) { + + } + + bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) override { + if (e->type == SDL_FINGERMOTION || e->type == SDL_FINGERUP || e->type == SDL_FINGERDOWN) { + data.y = e->tfinger.y * screenHeight; + data.x = e->tfinger.x * screenWidth; + if (e->type == SDL_FINGERUP) { + data.touched = false; + } else if (e->type == SDL_FINGERDOWN) { + data.touched = true; + } + } else if (e->type == SDL_JOYBUTTONDOWN) { + data.buttons_h |= vpad_button_map[e->jbutton.button]; + } else if (e->type == SDL_JOYBUTTONUP) { + data.buttons_h &= ~vpad_button_map[e->jbutton.button]; + } else { + LG_Log("Unknown event"); + return false; + } + return true; + } + + void after() override { + data.validPointer = data.touched; + SDLController::after(); + } +}; + diff --git a/include/gui-sdl/gui/input/SDLControllerWiiUProContoller.h b/include/gui-sdl/gui/input/SDLControllerWiiUProContoller.h new file mode 100644 index 0000000..1fa5451 --- /dev/null +++ b/include/gui-sdl/gui/input/SDLControllerWiiUProContoller.h @@ -0,0 +1,11 @@ +#pragma once + +#include "SDLControllerWiiUGamepad.h" + +class SDLControllerWiiUProContoller : public SDLControllerWiiUGamepad { +public: + explicit SDLControllerWiiUProContoller(int32_t channel) : SDLControllerWiiUGamepad(channel){ + + } +}; + diff --git a/include/gui-sdl/gui/input/SDLControllerXboxOne.h b/include/gui-sdl/gui/input/SDLControllerXboxOne.h new file mode 100644 index 0000000..b86f708 --- /dev/null +++ b/include/gui-sdl/gui/input/SDLControllerXboxOne.h @@ -0,0 +1,106 @@ +#pragma once + +#include "SDLController.h" + +static GuiTrigger::eButtons xbox_button_map[] = + { + GuiTrigger::BUTTON_A, + GuiTrigger::BUTTON_B, + GuiTrigger::BUTTON_X, + GuiTrigger::BUTTON_Y, + GuiTrigger::BUTTON_L, + GuiTrigger::BUTTON_R, + GuiTrigger::BUTTON_MINUS, + GuiTrigger::BUTTON_PLUS, + GuiTrigger::BUTTON_STICK_L, + GuiTrigger::BUTTON_STICK_R, + }; + +#define getDigitalAxis(axis, targetAxis, value, hold, first, second) \ +if(axis == targetAxis){ \ + if (value < 0x4000 && value > -0x4000){ \ + hold &= ~first; \ + hold &= ~second; \ + }else if(value < -0x4000){ \ + hold |= first; \ + hold &= ~second; \ + }else if(value > 0x4000){ \ + hold |= second; \ + hold &= ~first; \ + } \ +} \ + +#define getDigitalTrigger(axis, targetAxis, value, hold, first) \ +if(axis == targetAxis){ \ + if(value > 0){ \ + hold |= first; \ + }else{ \ + hold &= ~first; \ + } \ +} \ + + +class SDLControllerXboxOne : public SDLController { +public: + explicit SDLControllerXboxOne(int32_t channel) : SDLController(channel) { + + } + + bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) override { + if (e->type == SDL_JOYBUTTONDOWN) { + data.buttons_h |= xbox_button_map[e->jbutton.button]; + } else if (e->type == SDL_JOYBUTTONUP) { + data.buttons_h &= ~xbox_button_map[e->jbutton.button]; + } else if (e->type == SDL_JOYHATMOTION) { + auto val = e->jhat.value; + + auto hatMask = (GuiTrigger::BUTTON_LEFT | GuiTrigger::BUTTON_UP | GuiTrigger::BUTTON_DOWN | GuiTrigger::BUTTON_RIGHT); + + // Remove hat values so we can add the new values. + data.buttons_h &= ~hatMask; + + switch (val) { + case SDL_HAT_LEFTUP: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_LEFT: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + break; + case SDL_HAT_LEFTDOWN: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + case SDL_HAT_UP: + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_DOWN: + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + case SDL_HAT_RIGHTUP: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_RIGHT: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + break; + case SDL_HAT_RIGHTDOWN: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + } + } else if (e->type == SDL_JOYAXISMOTION) { + getDigitalTrigger(e->jaxis.axis, 2, e->jaxis.value, data.buttons_h, GuiTrigger::BUTTON_ZL); + getDigitalTrigger(e->jaxis.axis, 5, e->jaxis.value, data.buttons_h, GuiTrigger::BUTTON_ZR); + getDigitalAxis(e->jaxis.axis, 0, e->jaxis.value, data.buttons_h, GuiTrigger::STICK_L_LEFT, GuiTrigger::STICK_L_RIGHT); + getDigitalAxis(e->jaxis.axis, 1, e->jaxis.value, data.buttons_h, GuiTrigger::STICK_L_UP, GuiTrigger::STICK_L_DOWN); + getDigitalAxis(e->jaxis.axis, 3, e->jaxis.value, data.buttons_h, GuiTrigger::STICK_R_LEFT, GuiTrigger::STICK_R_RIGHT); + getDigitalAxis(e->jaxis.axis, 4, e->jaxis.value, data.buttons_h, GuiTrigger::STICK_R_UP, GuiTrigger::STICK_R_DOWN); + } else { + LG_Log("Unknown event"); + return false; + } + return true; + } +}; + diff --git a/include/gui-sdl/gui/sigslot.h b/include/gui-sdl/gui/sigslot.h new file mode 100644 index 0000000..f578c47 --- /dev/null +++ b/include/gui-sdl/gui/sigslot.h @@ -0,0 +1,2433 @@ +// sigslot.h: Signal/Slot classes +// +// Written by Sarah Thompson (sarah@telergy.com) 2002. +// +// License: Public domain. You are free to use this code however you like, with the proviso that +// the author takes on no responsibility or liability for any use. +// +// QUICK DOCUMENTATION +// +// (see also the full documentation at http://sigslot.sourceforge.net/) +// +// #define switches +// SIGSLOT_PURE_ISO - Define this to force ISO C++ compliance. This also disables +// all of the thread safety support on platforms where it is +// available. +// +// SIGSLOT_USE_POSIX_THREADS - Force use of Posix threads when using a C++ compiler other than +// gcc on a platform that supports Posix threads. (When using gcc, +// this is the default - use SIGSLOT_PURE_ISO to disable this if +// necessary) +// +// SIGSLOT_DEFAULT_MT_POLICY - Where thread support is enabled, this defaults to multi_threaded_global. +// Otherwise, the default is single_threaded. #define this yourself to +// override the default. In pure ISO mode, anything other than +// single_threaded will cause a compiler error. +// +// PLATFORM NOTES +// +// Win32 - On Win32, the WIN32 symbol must be #defined. Most mainstream +// compilers do this by default, but you may need to define it +// yourself if your build environment is less standard. This causes +// the Win32 thread support to be compiled in and used automatically. +// +// Unix/Linux/BSD, etc. - If you're using gcc, it is assumed that you have Posix threads +// available, so they are used automatically. You can override this +// (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using +// something other than gcc but still want to use Posix threads, you +// need to #define SIGSLOT_USE_POSIX_THREADS. +// +// ISO C++ - If none of the supported platforms are detected, or if +// SIGSLOT_PURE_ISO is defined, all multithreading support is turned off, +// along with any code that might cause a pure ISO C++ environment to +// complain. Before you ask, gcc -ansi -pedantic won't compile this +// library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of +// errors that aren't really there. If you feel like investigating this, +// please contact the author. +// +// +// THREADING MODES +// +// single_threaded - Your program is assumed to be single threaded from the point of view +// of signal/slot usage (i.e. all objects using signals and slots are +// created and destroyed from a single thread). Behaviour if objects are +// destroyed concurrently is undefined (i.e. you'll get the occasional +// segmentation fault/memory exception). +// +// multi_threaded_global - Your program is assumed to be multi threaded. Objects using signals and +// slots can be safely created and destroyed from any thread, even when +// connections exist. In multi_threaded_global mode, this is achieved by a +// single global mutex (actually a critical section on Windows because they +// are faster). This option uses less OS resources, but results in more +// opportunities for contention, possibly resulting in more context switches +// than are strictly necessary. +// +// multi_threaded_local - Behaviour in this mode is essentially the same as multi_threaded_global, +// except that each signal, and each object that inherits has_slots, all +// have their own mutex/critical section. In practice, this means that +// mutex collisions (and hence context switches) only happen if they are +// absolutely essential. However, on some platforms, creating a lot of +// mutexes can slow down the whole OS, so use this option with care. +// +// USING THE LIBRARY +// +// See the full documentation at http://sigslot.sourceforge.net/ +// +// +#ifndef SIGSLOT_H__ +#define SIGSLOT_H__ + +#include +#include + +#define _SIGSLOT_SINGLE_THREADED + +#ifndef SIGSLOT_DEFAULT_MT_POLICY +# ifdef _SIGSLOT_SINGLE_THREADED +# define SIGSLOT_DEFAULT_MT_POLICY single_threaded +# else +# define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local +# endif +#endif + + +namespace sigslot { + + class single_threaded { + public: + single_threaded() { + ; + } + + virtual ~single_threaded() { + ; + } + + virtual void lock() { + ; + } + + virtual void unlock() { + ; + } + }; + +#ifdef _SIGSLOT_HAS_WIN32_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global { + public: + multi_threaded_global() { + static bool isinitialised = false; + + if(!isinitialised) { + InitializeCriticalSection(get_critsec()); + isinitialised = true; + } + } + + multi_threaded_global(const multi_threaded_global&) { + ; + } + + virtual ~multi_threaded_global() { + ; + } + + virtual void lock() { + EnterCriticalSection(get_critsec()); + } + + virtual void unlock() { + LeaveCriticalSection(get_critsec()); + } + + private: + CRITICAL_SECTION* get_critsec() { + static CRITICAL_SECTION g_critsec; + return &g_critsec; + } + }; + + class multi_threaded_local { + public: + multi_threaded_local() { + InitializeCriticalSection(&m_critsec); + } + + multi_threaded_local(const multi_threaded_local&) { + InitializeCriticalSection(&m_critsec); + } + + virtual ~multi_threaded_local() { + DeleteCriticalSection(&m_critsec); + } + + virtual void lock() { + EnterCriticalSection(&m_critsec); + } + + virtual void unlock() { + LeaveCriticalSection(&m_critsec); + } + + private: + CRITICAL_SECTION m_critsec; + }; +#endif // _SIGSLOT_HAS_WIN32_THREADS + +#ifdef _SIGSLOT_HAS_POSIX_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global { + public: + multi_threaded_global() { + pthread_mutex_init(get_mutex(), NULL); + } + + multi_threaded_global(const multi_threaded_global&) { + ; + } + + virtual ~multi_threaded_global() { + ; + } + + virtual void lock() { + pthread_mutex_lock(get_mutex()); + } + + virtual void unlock() { + pthread_mutex_unlock(get_mutex()); + } + + private: + pthread_mutex_t* get_mutex() { + static pthread_mutex_t g_mutex; + return &g_mutex; + } + }; + + class multi_threaded_local { + public: + multi_threaded_local() { + pthread_mutex_init(&m_mutex, NULL); + } + + multi_threaded_local(const multi_threaded_local&) { + pthread_mutex_init(&m_mutex, NULL); + } + + virtual ~multi_threaded_local() { + pthread_mutex_destroy(&m_mutex); + } + + virtual void lock() { + pthread_mutex_lock(&m_mutex); + } + + virtual void unlock() { + pthread_mutex_unlock(&m_mutex); + } + + private: + pthread_mutex_t m_mutex; + }; +#endif // _SIGSLOT_HAS_POSIX_THREADS + +#ifdef _SIGSLOT_HAS_LWP_THREADS + + class multi_threaded_global { + public: + multi_threaded_global() { + ; + } + + multi_threaded_global(const multi_threaded_global&) { + ; + } + + virtual ~multi_threaded_global() { + ; + } + + virtual void lock() { + ; + } + + virtual void unlock() { + ; + } + }; + + class multi_threaded_local { + public: + multi_threaded_local() { + ; + } + + multi_threaded_local(const multi_threaded_local&) { + ; + } + + virtual ~multi_threaded_local() { + } + + virtual void lock() { + ; + } + + virtual void unlock() { + ; + } + }; + +#endif // _SIGSLOT_HAS_LWP_THREADS + + template + class lock_block { + public: + mt_policy *m_mutex; + + lock_block(mt_policy *mtx) + : m_mutex(mtx) { + m_mutex->lock(); + } + + ~lock_block() { + m_mutex->unlock(); + } + }; + + template + class has_slots; + + template + class _connection_base0 { + public: + virtual ~_connection_base0() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit() = 0; + + virtual _connection_base0 *clone() = 0; + + virtual _connection_base0 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _connection_base1 { + public: + virtual ~_connection_base1() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit(arg1_type) = 0; + + virtual _connection_base1 *clone() = 0; + + virtual _connection_base1 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _connection_base2 { + public: + virtual ~_connection_base2() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit(arg1_type, arg2_type) = 0; + + virtual _connection_base2 *clone() = 0; + + virtual _connection_base2 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _connection_base3 { + public: + virtual ~_connection_base3() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit(arg1_type, arg2_type, arg3_type) = 0; + + virtual _connection_base3 *clone() = 0; + + virtual _connection_base3 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _connection_base4 { + public: + virtual ~_connection_base4() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0; + + virtual _connection_base4 *clone() = 0; + + virtual _connection_base4 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _connection_base5 { + public: + virtual ~_connection_base5() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type) = 0; + + virtual _connection_base5 *clone() = 0; + + virtual _connection_base5 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _connection_base6 { + public: + virtual ~_connection_base6() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type) = 0; + + virtual _connection_base6 *clone() = 0; + + virtual _connection_base6 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _connection_base7 { + public: + virtual ~_connection_base7() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type) = 0; + + virtual _connection_base7 *clone() = 0; + + virtual _connection_base7 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _connection_base8 { + public: + virtual ~_connection_base8() { + ; + } + + virtual has_slots *getdest() const = 0; + + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type, arg8_type) = 0; + + virtual _connection_base8 *clone() = 0; + + virtual _connection_base8 *duplicate(has_slots *pnewdest) = 0; + }; + + template + class _signal_base : public mt_policy { + public: + virtual void slot_disconnect(has_slots *pslot) = 0; + + virtual void slot_duplicate(const has_slots *poldslot, has_slots *pnewslot) = 0; + }; + + template + class has_slots : public mt_policy { + private: + typedef typename std::set<_signal_base *> sender_set; + typedef typename sender_set::const_iterator const_iterator; + + public: + has_slots() { + ; + } + + has_slots(const has_slots &hs) + : mt_policy(hs) { + lock_block lock(this); + const_iterator it = hs.m_senders.begin(); + const_iterator itEnd = hs.m_senders.end(); + + while (it != itEnd) { + (*it)->slot_duplicate(&hs, this); + m_senders.insert(*it); + ++it; + } + } + + void signal_connect(_signal_base *sender) { + lock_block lock(this); + m_senders.insert(sender); + } + + void signal_disconnect(_signal_base *sender) { + lock_block lock(this); + m_senders.erase(sender); + } + + virtual ~has_slots() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_senders.begin(); + const_iterator itEnd = m_senders.end(); + + while (it != itEnd) { + (*it)->slot_disconnect(this); + ++it; + } + + m_senders.erase(m_senders.begin(), m_senders.end()); + } + + private: + sender_set m_senders; + }; + + template + class _signal_base0 : public _signal_base { + public: + typedef typename std::list<_connection_base0 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base0() { + ; + } + + _signal_base0(const _signal_base0 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + ~_signal_base0() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base1 : public _signal_base { + public: + typedef typename std::list<_connection_base1 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base1() { + ; + } + + _signal_base1(const _signal_base1 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base1() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base2 : public _signal_base { + public: + typedef typename std::list<_connection_base2 *> + connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base2() { + ; + } + + _signal_base2(const _signal_base2 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base2() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base3 : public _signal_base { + public: + typedef std::list<_connection_base3 *> + connections_list; + + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base3() { + ; + } + + _signal_base3(const _signal_base3 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base3() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base4 : public _signal_base { + public: + typedef std::list<_connection_base4 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base4() { + ; + } + + _signal_base4(const _signal_base4 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base4() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + this->m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base5 : public _signal_base { + public: + typedef std::list<_connection_base5 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base5() { + ; + } + + _signal_base5(const _signal_base5 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base5() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base6 : public _signal_base { + public: + typedef std::list<_connection_base6 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base6() { + ; + } + + _signal_base6(const _signal_base6 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base6() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base7 : public _signal_base { + public: + typedef std::list<_connection_base7 *> connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base7() { + ; + } + + _signal_base7(const _signal_base7 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base7() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base8 : public _signal_base { + public: + typedef std::list<_connection_base8 *> + connections_list; + typedef typename connections_list::const_iterator const_iterator; + typedef typename connections_list::iterator iterator; + + _signal_base8() { + ; + } + + _signal_base8(const _signal_base8 &s) + : _signal_base(s) { + lock_block lock(this); + const_iterator it = s.m_connected_slots.begin(); + const_iterator itEnd = s.m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots *oldtarget, has_slots *newtarget) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == oldtarget) { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base8() { + disconnect_all(); + } + + void disconnect_all() { + lock_block lock(this); + const_iterator it = m_connected_slots.begin(); + const_iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + + void disconnect(has_slots *pclass) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + if ((*it)->getdest() == pclass) { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + bool connected() { + return m_connected_slots.size() != 0; + } + + void slot_disconnect(has_slots *pslot) { + lock_block lock(this); + iterator it = m_connected_slots.begin(); + iterator itEnd = m_connected_slots.end(); + + while (it != itEnd) { + iterator itNext = it; + ++itNext; + + if ((*it)->getdest() == pslot) { + delete *it; + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + + template + class _connection0 : public _connection_base0 { + public: + _connection0() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection0(dest_type *pobject, void (dest_type::*pmemfun)()) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection0() { + ; + } + + virtual _connection_base0 *clone() { + return new _connection0(*this); + } + + virtual _connection_base0 *duplicate(has_slots *pnewdest) { + return new _connection0((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit() { + (m_pobject->*m_pmemfun)(); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(); + }; + + template + class _connection1 : public _connection_base1 { + public: + _connection1() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection1(dest_type *pobject, void (dest_type::*pmemfun)(arg1_type)) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection1() { + ; + } + + virtual _connection_base1 *clone() { + return new _connection1(*this); + } + + virtual _connection_base1 *duplicate(has_slots *pnewdest) { + return new _connection1((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1) { + (m_pobject->*m_pmemfun)(a1); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(arg1_type); + }; + + template + class _connection2 : public _connection_base2 { + public: + _connection2() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection2(dest_type *pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type)) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection2() { + ; + } + + + virtual _connection_base2 *clone() { + return new _connection2(*this); + } + + virtual _connection_base2 *duplicate(has_slots *pnewdest) { + return new _connection2((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2) { + (m_pobject->*m_pmemfun)(a1, a2); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(arg1_type, arg2_type); + }; + + template + class _connection3 : public _connection_base3 { + public: + _connection3() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection3(dest_type *pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type)) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection3() { + ; + } + + + virtual _connection_base3 *clone() { + return new _connection3(*this); + } + + virtual _connection_base3 *duplicate(has_slots *pnewdest) { + return new _connection3((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3) { + (m_pobject->*m_pmemfun)(a1, a2, a3); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type); + }; + + template + class _connection4 : public _connection_base4 { + public: + _connection4() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection4(dest_type *pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection4() { + ; + } + + virtual _connection_base4 *clone() { + return new _connection4(*this); + } + + virtual _connection_base4 *duplicate(has_slots *pnewdest) { + return new _connection4((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, + arg4_type a4) { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, + arg4_type); + }; + + template + class _connection5 : public _connection_base5 { + public: + _connection5() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection5(dest_type *pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection5() { + ; + } + + virtual _connection_base5 *clone() { + return new _connection5(*this); + } + + virtual _connection_base5 *duplicate(has_slots *pnewdest) { + return new _connection5((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type); + }; + + template + class _connection6 : public _connection_base6 { + public: + _connection6() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection6(dest_type *pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection6() { + ; + } + + virtual _connection_base6 *clone() { + return new _connection6(*this); + } + + virtual _connection_base6 *duplicate(has_slots *pnewdest) { + return new _connection6((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type); + }; + + template + class _connection7 : public _connection_base7 { + public: + _connection7() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection7(dest_type *pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type)) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection7() { + ; + } + + virtual _connection_base7 *clone() { + return new _connection7(*this); + } + + virtual _connection_base7 *duplicate(has_slots *pnewdest) { + return new _connection7((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type); + }; + + template + class _connection8 : public _connection_base8 { + public: + _connection8() { + this->pobject = NULL; + this->pmemfun = NULL; + } + + _connection8(dest_type *pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection8() { + ; + } + + virtual _connection_base8 *clone() { + return new _connection8(*this); + } + + virtual _connection_base8 *duplicate(has_slots *pnewdest) { + return new _connection8((dest_type *) pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + virtual has_slots *getdest() const { + return m_pobject; + } + + private: + dest_type *m_pobject; + + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type, arg8_type); + }; + + template + class signal0 : public _signal_base0 { + public: + typedef typename _signal_base0::connections_list::const_iterator const_iterator; + + signal0() { + ; + } + + signal0(const signal0 &s) + : _signal_base0(s) { + ; + } + + virtual ~signal0() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)()) { + lock_block lock(this); + _connection0 *conn = + new _connection0(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit() { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + + void operator()() { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + }; + + template + class signal1 : public _signal_base1 { + public: + typedef typename _signal_base1::connections_list::const_iterator const_iterator; + + signal1() { + ; + } + + signal1(const signal1 &s) + : _signal_base1(s) { + ; + } + + virtual ~signal1() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)(arg1_type)) { + lock_block lock(this); + _connection1 *conn = + new _connection1(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + + void operator()(arg1_type a1) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + }; + + template + class signal2 : public _signal_base2 { + public: + typedef typename _signal_base2::connections_list::const_iterator const_iterator; + + signal2() { + ; + } + + signal2(const signal2 &s) + : _signal_base2(s) { + ; + } + + virtual ~signal2() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type)) { + lock_block lock(this); + _connection2 *conn = new + _connection2(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + }; + + template + class signal3 : public _signal_base3 { + public: + typedef typename _signal_base3::connections_list::const_iterator const_iterator; + + signal3() { + ; + } + + signal3(const signal3 &s) + : _signal_base3(s) { + ; + } + + virtual ~signal3() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type)) { + lock_block lock(this); + _connection3 *conn = + new _connection3(pclass, + pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + }; + + template + class signal4 : public _signal_base4 { + public: + typedef typename _signal_base4::connections_list::const_iterator const_iterator; + + signal4() { + ; + } + + signal4(const signal4 &s) + : _signal_base4(s) { + ; + } + + virtual ~signal4() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) { + lock_block lock(this); + _connection4 * + conn = new _connection4(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + }; + + template + class signal5 : public _signal_base5 { + public: + typedef typename _signal_base5::connections_list::const_iterator const_iterator; + + signal5() { + ; + } + + signal5(const signal5 &s) + : _signal_base5(s) { + ; + } + + virtual ~signal5() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) { + lock_block lock(this); + _connection5 *conn = new _connection5(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + }; + + + template + class signal6 : public _signal_base6 { + public: + typedef typename _signal_base6::connections_list::const_iterator const_iterator; + + signal6() { + ; + } + + signal6(const signal6 &s) + : _signal_base6(s) { + ; + } + + virtual ~signal6() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) { + lock_block lock(this); + _connection6 *conn = + new _connection6(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + }; + + template + class signal7 : public _signal_base7 { + public: + typedef typename _signal_base7::connections_list::const_iterator const_iterator; + + signal7() { + ; + } + + signal7(const signal7 &s) + : _signal_base7(s) { + ; + } + + virtual ~signal7() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type)) { + lock_block lock(this); + _connection7 *conn = + new _connection7(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + }; + + template + class signal8 : public _signal_base8 { + public: + typedef typename _signal_base8::connections_list::const_iterator const_iterator; + + signal8() { + ; + } + + signal8(const signal8 &s) + : _signal_base8(s) { + ; + } + + virtual ~signal8() { + ; + } + + template + void connect(desttype *pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) { + lock_block lock(this); + _connection8 *conn = + new _connection8(pclass, pmemfun); + this->m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) { + lock_block lock(this); + const_iterator itNext, it = this->m_connected_slots.begin(); + const_iterator itEnd = this->m_connected_slots.end(); + + while (it != itEnd) { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + }; + +}; // namespace sigslot + +#endif // SIGSLOT_H__ diff --git a/include/gui-sdl/gui/system/SDLSystem.h b/include/gui-sdl/gui/system/SDLSystem.h new file mode 100644 index 0000000..17c1261 --- /dev/null +++ b/include/gui-sdl/gui/system/SDLSystem.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once +#include +#include + +class SDLSystem { +public: + SDLSystem(); + + virtual ~SDLSystem(); + + Renderer *getRenderer(); + + float getHeight(); + float getWidth(); + +private: + SDL_Window *window = NULL; + Renderer *renderer = NULL; +}; \ No newline at end of file diff --git a/include/gui-sdl/gui/system/libgui_log.h b/include/gui-sdl/gui/system/libgui_log.h new file mode 100644 index 0000000..867691f --- /dev/null +++ b/include/gui-sdl/gui/system/libgui_log.h @@ -0,0 +1,8 @@ +#pragma once + +#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) + +#define LG_Log(FMT, ARGS...)do { \ + printf("[%23s]%30s@L%04d: " FMT "\n",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0) diff --git a/include/gui-sdl/gui/video/Renderer.h b/include/gui-sdl/gui/video/Renderer.h new file mode 100644 index 0000000..8b1d745 --- /dev/null +++ b/include/gui-sdl/gui/video/Renderer.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +class Renderer { +public: + Renderer(SDL_Renderer *renderer, uint32_t pixelFormat) : sdl_renderer(renderer), pixelFormat(pixelFormat){ + + } + + virtual ~Renderer() { + if(sdl_renderer){ + SDL_DestroyRenderer(sdl_renderer); + } + } + + SDL_Renderer *getRenderer(){ + return sdl_renderer; + } + + uint32_t getPixelFormat(){ + return pixelFormat; + } + +private: + SDL_Renderer *sdl_renderer = NULL; + uint32_t pixelFormat = SDL_PIXELFORMAT_RGBA8888; +}; \ No newline at end of file diff --git a/include/gui-sdl/gui/video/SDL_FontCache.h b/include/gui-sdl/gui/video/SDL_FontCache.h new file mode 100644 index 0000000..d6c8263 --- /dev/null +++ b/include/gui-sdl/gui/video/SDL_FontCache.h @@ -0,0 +1,320 @@ +/* +SDL_FontCache v0.10.0: A font cache for SDL and SDL_ttf +by Jonathan Dearborn +Dedicated to the memory of Florian Hufsky + +License: + The short: + Use it however you'd like, but keep the copyright and license notice + whenever these files or parts of them are distributed in uncompiled form. + + The long: +Copyright (c) 2019 Jonathan Dearborn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef _SDL_FONTCACHE_H__ +#define _SDL_FONTCACHE_H__ + +#include +#include +#include +#include + +#ifdef FC_USE_SDL_GPU + #include "SDL_gpu.h" +#endif + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Let's pretend this exists... +#define TTF_STYLE_OUTLINE 16 + + + +// Differences between SDL_Renderer and SDL_gpu +#ifdef FC_USE_SDL_GPU +#define FC_Rect GPU_Rect +#define FC_Target GPU_Target +#define FC_Image GPU_Image +#define FC_Log GPU_LogError +#else +#define FC_Rect SDL_Rect +#define FC_Target SDL_Renderer +#define FC_Image SDL_Texture +#define FC_Log LG_Log +#endif + + +// SDL_FontCache types + +typedef enum +{ + FC_ALIGN_LEFT, + FC_ALIGN_CENTER, + FC_ALIGN_RIGHT +} FC_AlignEnum; + +typedef enum +{ + FC_FILTER_NEAREST, + FC_FILTER_LINEAR +} FC_FilterEnum; + +typedef struct FC_Scale +{ + float x; + float y; + +} FC_Scale; + +typedef struct FC_Effect +{ + FC_AlignEnum alignment; + FC_Scale scale; + SDL_Color color; + +} FC_Effect; + +// Opaque type +typedef struct FC_Font FC_Font; + + +typedef struct FC_GlyphData +{ + SDL_Rect rect; + int cache_level; + +} FC_GlyphData; + +// Object creation +FC_Rect FC_MakeRect(float x, float y, float w, float h); + +FC_Scale FC_MakeScale(float x, float y); + +SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a); + +FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color); + +FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h); + +// Font object + +FC_Font* FC_CreateFont(void); + +#ifdef FC_USE_SDL_GPU +Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style); + +Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color); + +Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style); +#else +Uint8 FC_LoadFont(FC_Font* font, SDL_Renderer* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style); + +Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color); + +Uint8 FC_LoadFont_RW(FC_Font* font, SDL_Renderer* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style); +#endif + +#ifndef FC_USE_SDL_GPU +// note: handle SDL event types SDL_RENDER_TARGETS_RESET(>= SDL 2.0.2) and SDL_RENDER_DEVICE_RESET(>= SDL 2.0.4) +void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType); +#endif + +void FC_ClearFont(FC_Font* font); + +void FC_FreeFont(FC_Font* font); + + + +// Built-in loading strings + +char* FC_GetStringASCII(void); + +char* FC_GetStringLatin1(void); + +char* FC_GetStringASCII_Latin1(void); + + +// UTF-8 to SDL_FontCache codepoint conversion + +/*! +Returns the Uint32 codepoint (not UTF-32) parsed from the given UTF-8 string. +\param c A pointer to a string of proper UTF-8 character values. +\param advance_pointer If true, the source pointer will be incremented to skip the extra bytes from multibyte codepoints. +*/ +Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer); + +/*! +Parses the given codepoint and stores the UTF-8 bytes in 'result'. The result is NULL terminated. +\param result A memory buffer for the UTF-8 values. Must be at least 5 bytes long. +\param codepoint The Uint32 codepoint to parse (not UTF-32). +*/ +void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint); + + +// UTF-8 string operations + +/*! Allocates a new string of 'size' bytes that is already NULL-terminated. The NULL byte counts toward the size limit, as usual. Returns NULL if size is 0. */ +char* U8_alloc(unsigned int size); + +/*! Deallocates the given string. */ +void U8_free(char* string); + +/*! Allocates a copy of the given string. */ +char* U8_strdup(const char* string); + +/*! Returns the number of UTF-8 characters in the given string. */ +int U8_strlen(const char* string); + +/*! Returns the number of bytes in the UTF-8 multibyte character pointed at by 'character'. */ +int U8_charsize(const char* character); + +/*! Copies the source multibyte character into the given buffer without overrunning it. Returns 0 on failure. */ +int U8_charcpy(char* buffer, const char* source, int buffer_size); + +/*! Returns a pointer to the next UTF-8 character. */ +const char* U8_next(const char* string); + +/*! Inserts a UTF-8 string into 'string' at the given position. Use a position of -1 to append. Returns 0 when unable to insert the string. */ +int U8_strinsert(char* string, int position, const char* source, int max_bytes); + +/*! Erases the UTF-8 character at the given position, moving the subsequent characters down. */ +void U8_strdel(char* string, int position); + + +// Internal settings +/*! Sets the string from which to load the initial glyphs. Use this if you need upfront loading for any reason (such as lack of render-target support). */ +void FC_SetLoadingString(FC_Font* font, const char* string); + +/*! Returns the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */ +unsigned int FC_GetBufferSize(void); + +/*! Changes the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */ +void FC_SetBufferSize(unsigned int size); + +/*! Returns the width of a single horizontal tab in multiples of the width of a space (default: 4) */ +unsigned int FC_GetTabWidth(void); + +/*! Changes the width of a horizontal tab in multiples of the width of a space (default: 4) */ +void FC_SetTabWidth(unsigned int width_in_spaces); + +void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale)); + +FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale); + + +// Custom caching + +/*! Returns the number of cache levels that are active. */ +int FC_GetNumCacheLevels(FC_Font* font); + +/*! Returns the cache source texture at the given cache level. */ +FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level); + +// TODO: Specify ownership of the texture (should be shareable) +/*! Sets a cache source texture for rendering. New cache levels must be sequential. */ +Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture); + +/*! Copies the given surface to the given cache level as a texture. New cache levels must be sequential. */ +Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface); + +/*! Returns the number of codepoints that are stored in the font's glyph data map. */ +unsigned int FC_GetNumCodepoints(FC_Font* font); + +/*! Copies the stored codepoints into the given array. */ +void FC_GetCodepoints(FC_Font* font, Uint32* result); + +/*! Stores the glyph data for the given codepoint in 'result'. Returns 0 if the codepoint was not found in the cache. */ +Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint); + +/*! Sets the glyph data for the given codepoint. Duplicates are not checked. Returns a pointer to the stored data. */ +FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data); + +// Rendering +FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...); +FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...); +FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...); +FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...); +FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...); + +FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...); +FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...); +FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...); +FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...); +FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...); + +FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...); +FC_Rect FC_DrawColumnToTexture(FC_Font* font, SDL_Texture* target, float x, float y, Uint16 width, const char* text); +FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...); +FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...); +FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...); +FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...); + + +// Getters + +FC_FilterEnum FC_GetFilterMode(FC_Font* font); +Uint16 FC_GetLineHeight(FC_Font* font); +Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...); +Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...); + +// Returns a 1-pixel wide box in front of the character in the given position (index) +FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...); +Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...); + +int FC_GetAscent(FC_Font* font, const char* formatted_text, ...); +int FC_GetDescent(FC_Font* font, const char* formatted_text, ...); +int FC_GetBaseline(FC_Font* font); +int FC_GetSpacing(FC_Font* font); +int FC_GetLineSpacing(FC_Font* font); +Uint16 FC_GetMaxWidth(FC_Font* font); +SDL_Color FC_GetDefaultColor(FC_Font* font); + +FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...); + +Uint8 FC_InRect(float x, float y, FC_Rect input_rect); +// Given an offset (x,y) from the text draw position (the upper-left corner), returns the character position (UTF-8 index) +Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...); + +// Returns the number of characters in the new wrapped text written into `result`. +int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...); + +// Setters + +void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter); +void FC_SetSpacing(FC_Font* font, int LetterSpacing); +void FC_SetLineSpacing(FC_Font* font, int LineSpacing); +void FC_SetDefaultColor(FC_Font* font, SDL_Color color); + + +#ifdef __cplusplus +} +#endif + + + +#endif diff --git a/source/gui/GuiButton.cpp b/source/gui/GuiButton.cpp new file mode 100644 index 0000000..9792ea6 --- /dev/null +++ b/source/gui/GuiButton.cpp @@ -0,0 +1,295 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include + +/** + * Constructor for the GuiButton class. + */ + +GuiButton::GuiButton(float w, float h) { + width = w; + height = h; + image = nullptr; + imageOver = nullptr; + imageHold = nullptr; + imageClick = nullptr; + icon = nullptr; + iconOver = nullptr; + + for (int32_t i = 0; i < 4; i++) { + label[i] = nullptr; + labelOver[i] = nullptr; + labelHold[i] = nullptr; + labelClick[i] = nullptr; + } + for (auto &i : trigger) { + i = nullptr; + } + + soundOver = nullptr; + soundHold = nullptr; + soundClick = nullptr; + clickedTrigger = nullptr; + heldTrigger = nullptr; + selectable = true; + holdable = false; + clickable = true; +} + +/** + * Destructor for the GuiButton class. + */ +GuiButton::~GuiButton() = default; + +void GuiButton::setImage(GuiImage *img) { + image = img; + if (img) { img->setParent(this); } +} + +void GuiButton::setImageOver(GuiImage *img) { + imageOver = img; + if (img) { img->setParent(this); } +} + +void GuiButton::setImageHold(GuiImage *img) { + imageHold = img; + if (img) { img->setParent(this); } +} + +void GuiButton::setImageClick(GuiImage *img) { + imageClick = img; + if (img) { img->setParent(this); } +} + +void GuiButton::setIcon(GuiImage *img) { + icon = img; + if (img) { img->setParent(this); } +} + +void GuiButton::setIconOver(GuiImage *img) { + iconOver = img; + if (img) { img->setParent(this); } +} + +void GuiButton::setLabel(GuiText *txt, int32_t n) { + label[n] = txt; + if (txt) { txt->setParent(this); } +} + +void GuiButton::setLabelOver(GuiText *txt, int32_t n) { + labelOver[n] = txt; + if (txt) { txt->setParent(this); } +} + +void GuiButton::setLabelHold(GuiText *txt, int32_t n) { + labelHold[n] = txt; + if (txt) { txt->setParent(this); } +} + +void GuiButton::setLabelClick(GuiText *txt, int32_t n) { + labelClick[n] = txt; + if (txt) { txt->setParent(this); } +} + +void GuiButton::setSoundOver(GuiSound *snd) { + soundOver = snd; +} + +void GuiButton::setSoundHold(GuiSound *snd) { + soundHold = snd; +} + +void GuiButton::setSoundClick(GuiSound *snd) { + soundClick = snd; +} + +void GuiButton::setTrigger(GuiTrigger *t, int32_t idx) { + if (idx >= 0 && idx < iMaxGuiTriggers) { + trigger[idx] = t; + } else { + for (auto &i : trigger) { + if (!i) { + i = t; + break; + } + } + } +} + +void GuiButton::resetState() { + clickedTrigger = nullptr; + heldTrigger = nullptr; + GuiElement::resetState(); +} + +/** + * Draw the button on screen + */ +void GuiButton::draw(Renderer *v) { + if (!this->isVisible()) { + return; + } + + // draw image + if ((isDrawOverOnlyWhenSelected() && (isStateSet(STATE_SELECTED) && imageOver)) || + (!isDrawOverOnlyWhenSelected() && (isStateSet(STATE_OVER | STATE_SELECTED | STATE_CLICKED | STATE_HELD) && imageOver))) { + imageOver->draw(v); + } else if (image) { + image->draw(v); + } + + if ((isDrawOverOnlyWhenSelected() && (isStateSet(STATE_SELECTED) && iconOver)) || + (!isDrawOverOnlyWhenSelected() && (isStateSet(STATE_OVER | STATE_SELECTED | STATE_CLICKED | STATE_HELD) && iconOver))) { + iconOver->draw(v); + } else if (icon) { + icon->draw(v); + } + + // draw text + for (int32_t i = 0; i < 4; i++) { + if (isStateSet(STATE_OVER | STATE_SELECTED | STATE_CLICKED | STATE_HELD) && labelOver[i]) { + labelOver[i]->draw(v); + } else if (label[i]) { + label[i]->draw(v); + } + } +} + +void GuiButton::update(GuiController *c) { + if (!c || isStateSet(STATE_DISABLED | STATE_HIDDEN | STATE_DISABLE_INPUT, c->chanIdx)) { + return; + } else if (parentElement && (parentElement->isStateSet(STATE_DISABLED | STATE_HIDDEN | STATE_DISABLE_INPUT, c->chanIdx))) { + return; + } + + if (selectable) { + if (c->data.validPointer && this->isInside(c->data.x, c->data.y)) { + if (!isStateSet(STATE_OVER, c->chanIdx)) { + setState(STATE_OVER, c->chanIdx); + + //if(this->isRumbleActive()) + // this->rumble(t->chan); + + if (soundOver) { + soundOver->Play(); + } + + if (effectsOver && !effects) { + // initiate effects + effects = effectsOver; + effectAmount = effectAmountOver; + effectTarget = effectTargetOver; + } + + pointedOn(this, c); + } + } else if (isStateSet(STATE_OVER, c->chanIdx)) { + this->clearState(STATE_OVER, c->chanIdx); + pointedOff(this, c); + + if (effectTarget == effectTargetOver && effectAmount == effectAmountOver) { + // initiate effects (in reverse) + effects = effectsOver; + effectAmount = -effectAmountOver; + effectTarget = 100; + } + } + } + + for (auto & i : trigger) { + if (!i) { + continue; + } + + // button triggers + if (clickable) { + int32_t isClicked = i->clicked(c); + if (!clickedTrigger && (isClicked != GuiTrigger::CLICKED_NONE) + && (i->isClickEverywhere() || (isStateSet(STATE_SELECTED | STATE_OVER, c->chanIdx) && i->isSelectionClickEverywhere()) || this->isInside(c->data.x, c->data.y))) { + if (soundClick) { + soundClick->Play(); + } + + clickedTrigger = i; + + if (!isStateSet(STATE_CLICKED, c->chanIdx)) { + if (isClicked == GuiTrigger::CLICKED_TOUCH) { + setState(STATE_CLICKED_TOUCH, c->chanIdx); + } else { + setState(STATE_CLICKED, c->chanIdx); + } + } + + clicked(this, c, i); + } else if ((isStateSet(STATE_CLICKED, c->chanIdx) || isStateSet(STATE_CLICKED_TOUCH, c->chanIdx)) && (clickedTrigger == i) && !isStateSet(STATE_HELD, c->chanIdx) && !i->held(c) && + ((isClicked == GuiTrigger::CLICKED_NONE) || i->released(c))) { + if ((isStateSet(STATE_CLICKED_TOUCH, c->chanIdx) && this->isInside(c->data.x, c->data.y)) || (isStateSet(STATE_CLICKED, c->chanIdx))) { + clickedTrigger = nullptr; + clearState(STATE_CLICKED, c->chanIdx); + released(this, c, i); + } + } + } + + if (holdable) { + bool isHeld = i->held(c); + + if ((!heldTrigger || heldTrigger == i) && isHeld + && (i->isHoldEverywhere() || (isStateSet(STATE_SELECTED | STATE_OVER, c->chanIdx) && i->isSelectionClickEverywhere()) || this->isInside(c->data.x, c->data.y))) { + heldTrigger = i; + + if (!isStateSet(STATE_HELD, c->chanIdx)) { + setState(STATE_HELD, c->chanIdx); + } + + held(this, c, i); + } else if (isStateSet(STATE_HELD, c->chanIdx) && (heldTrigger == i) && (!isHeld || i->released(c))) { + //! click is removed at this point and converted to held + if (clickedTrigger == i) { + clickedTrigger = nullptr; + clearState(STATE_CLICKED, c->chanIdx); + } + heldTrigger = nullptr; + clearState(STATE_HELD, c->chanIdx); + released(this, c, i); + } + } + } +} + +void GuiButton::process() { + GuiElement::process(); + + if(image) { image->process(); } + if(imageOver) { imageOver->process(); } + if(imageHold) { imageHold->process(); } + if(imageClick) { imageClick->process(); } + if(icon) { icon->process(); } + if(iconOver) { iconOver->process(); } + if(soundOver) { soundOver->process(); } + if(soundHold) { soundHold->process(); } + if(soundClick) { soundClick->process(); } + + for(int i = 0;i<4;i++){ + if(label[i]) { label[i]->process(); } + if(labelOver[i]) { labelOver[i]->process(); } + if(labelHold[i]) { labelHold[i]->process(); } + if(labelClick[i]) { labelClick[i]->process(); } + } +} diff --git a/source/gui/GuiElement.cpp b/source/gui/GuiElement.cpp new file mode 100644 index 0000000..9c2e910 --- /dev/null +++ b/source/gui/GuiElement.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include + +//! TODO remove this! +static int32_t screenwidth = 1280; +static int32_t screenheight = 720; + +/** + * Constructor for the Object class. + */ +GuiElement::GuiElement() { + xoffset = 0.0f; + yoffset = 0.0f; + zoffset = 0.0f; + width = 0.0f; + height = 0.0f; + alpha = 1.0f; + scaleX = 1.0f; + scaleY = 1.0f; + scaleZ = 1.0f; + for (unsigned int & i : state) { + i = STATE_DEFAULT; + } + stateChan = -1; + parentElement = nullptr; + rumble = true; + selectable = false; + clickable = false; + holdable = false; + drawOverOnlyWhenSelected = false; + visible = true; + yoffsetDyn = 0; + xoffsetDyn = 0; + alphaDyn = -1; + scaleDyn = 1; + effects = EFFECT_NONE; + effectAmount = 0; + effectTarget = 0; + effectsOver = EFFECT_NONE; + effectAmountOver = 0; + effectTargetOver = 0; + angle = 0.0f; + + // default alignment - align to top left + alignment = (ALIGN_TOP_LEFT); +} + +/** + * Get the left position of the GuiElement. + * @see SetLeft() + * @return Left position in pixel. + */ +float GuiElement::getLeft() { + float pWidth = 0; + float pLeft = 0; + float pScaleX = 1.0f; + + if (parentElement) { + pWidth = parentElement->getWidth(); + pLeft = parentElement->getLeft(); + pScaleX = parentElement->getScaleX(); + } + + pLeft += xoffsetDyn; + + float x = pLeft; + + //! TODO: the conversion from int to float and back to int is bad for performance, change that + if (alignment & ALIGN_CENTER) { + x = pLeft + pWidth * 0.5f * pScaleX - width * 0.5f * getScaleX(); + } else if (alignment & ALIGN_RIGHT) { + x = pLeft + pWidth * pScaleX - width * getScaleX(); + } + + return x + xoffset; +} + +/** + * Get the top position of the GuiElement. + * @see SetTop() + * @return Top position in pixel. + */ +float GuiElement::getTop() { + float pHeight = 0; + float pTop = 0; + float pScaleY = 1.0f; + + if (parentElement) { + pHeight = parentElement->getHeight(); + pTop = parentElement->getTop(); + pScaleY = parentElement->getScaleY(); + } + + pTop += yoffsetDyn; + + float y = pTop; + + //! TODO: the conversion from int to float and back to int is bad for performance, change that + if (alignment & ALIGN_MIDDLE) { + y = pTop + pHeight * 0.5f * pScaleY - getHeight() * 0.5f * getScaleY(); + } else if (alignment & ALIGN_BOTTOM) { + y = pTop + pHeight * pScaleY - getHeight() * getScaleY(); + } + + return y + yoffset; +} + +void GuiElement::setEffect(uint32_t eff, int32_t amount, int32_t target) { + if (eff & EFFECT_SLIDE_IN) { + // these calculations overcompensate a little + if (eff & EFFECT_SLIDE_TOP) { + if (eff & EFFECT_SLIDE_FROM) { + yoffsetDyn = (int32_t) -getHeight() * scaleY; + } else { + yoffsetDyn = -screenheight; + } + } else if (eff & EFFECT_SLIDE_LEFT) { + if (eff & EFFECT_SLIDE_FROM) { + xoffsetDyn = (int32_t) -getWidth() * scaleX; + } else { + xoffsetDyn = -screenwidth; + } + } else if (eff & EFFECT_SLIDE_BOTTOM) { + if (eff & EFFECT_SLIDE_FROM) { + yoffsetDyn = (int32_t) getHeight() * scaleY; + } else { + yoffsetDyn = screenheight; + } + } else if (eff & EFFECT_SLIDE_RIGHT) { + if (eff & EFFECT_SLIDE_FROM) { + xoffsetDyn = (int32_t) getWidth() * scaleX; + } else { + xoffsetDyn = screenwidth; + } + } + } + if ((eff & EFFECT_FADE) && amount > 0) { + alphaDyn = 0; + } else if ((eff & EFFECT_FADE) && amount < 0) { + alphaDyn = alpha; + } + effects |= eff; + effectAmount = amount; + effectTarget = target; +} + +//!Sets an effect to be enabled on wiimote cursor over +//!\param e Effect to enable +//!\param a Amount of the effect (usage varies on effect) +//!\param t Target amount of the effect (usage varies on effect) +void GuiElement::setEffectOnOver(uint32_t e, int32_t a, int32_t t) { + effectsOver |= e; + effectAmountOver = a; + effectTargetOver = t; +} + +void GuiElement::resetEffects() { + yoffsetDyn = 0; + xoffsetDyn = 0; + alphaDyn = -1; + scaleDyn = 1; + effects = EFFECT_NONE; + effectAmount = 0; + effectTarget = 0; + effectsOver = EFFECT_NONE; + effectAmountOver = 0; + effectTargetOver = 0; +} + +void GuiElement::updateEffects() { + if (!this->isVisible() && parentElement) { + return; + } + + if (effects & (EFFECT_SLIDE_IN | EFFECT_SLIDE_OUT | EFFECT_SLIDE_FROM)) { + if (effects & EFFECT_SLIDE_IN) { + if (effects & EFFECT_SLIDE_LEFT) { + xoffsetDyn += effectAmount; + + if (xoffsetDyn >= 0) { + xoffsetDyn = 0; + effects = 0; + effectFinished(this); + } + } else if (effects & EFFECT_SLIDE_RIGHT) { + xoffsetDyn -= effectAmount; + + if (xoffsetDyn <= 0) { + xoffsetDyn = 0; + effects = 0; + effectFinished(this); + } + } else if (effects & EFFECT_SLIDE_TOP) { + yoffsetDyn += effectAmount; + + if (yoffsetDyn >= 0) { + yoffsetDyn = 0; + effects = 0; + effectFinished(this); + } + } else if (effects & EFFECT_SLIDE_BOTTOM) { + yoffsetDyn -= effectAmount; + + if (yoffsetDyn <= 0) { + yoffsetDyn = 0; + effects = 0; + effectFinished(this); + } + } + } else { + if (effects & EFFECT_SLIDE_LEFT) { + xoffsetDyn -= effectAmount; + + if (xoffsetDyn <= -screenwidth) { + effects = 0; // shut off effect + effectFinished(this); + } else if ((effects & EFFECT_SLIDE_FROM) && xoffsetDyn <= -getWidth()) { + effects = 0; // shut off effect + effectFinished(this); + } + } else if (effects & EFFECT_SLIDE_RIGHT) { + xoffsetDyn += effectAmount; + + if (xoffsetDyn >= screenwidth) { + effects = 0; // shut off effect + effectFinished(this); + } else if ((effects & EFFECT_SLIDE_FROM) && xoffsetDyn >= getWidth() * scaleX) { + effects = 0; // shut off effect + effectFinished(this); + } + } else if (effects & EFFECT_SLIDE_TOP) { + yoffsetDyn -= effectAmount; + + if (yoffsetDyn <= -screenheight) { + effects = 0; // shut off effect + effectFinished(this); + } else if ((effects & EFFECT_SLIDE_FROM) && yoffsetDyn <= -getHeight()) { + effects = 0; // shut off effect + effectFinished(this); + } + } else if (effects & EFFECT_SLIDE_BOTTOM) { + yoffsetDyn += effectAmount; + + if (yoffsetDyn >= screenheight) { + effects = 0; // shut off effect + effectFinished(this); + } else if ((effects & EFFECT_SLIDE_FROM) && yoffsetDyn >= getHeight()) { + effects = 0; // shut off effect + effectFinished(this); + } + } + } + } else if (effects & EFFECT_FADE) { + alphaDyn += effectAmount * (1.0f / 255.0f); + + if (effectAmount < 0 && alphaDyn <= 0) { + alphaDyn = 0; + effects = 0; // shut off effect + effectFinished(this); + } else if (effectAmount > 0 && alphaDyn >= alpha) { + alphaDyn = alpha; + effects = 0; // shut off effect + effectFinished(this); + } + } else if (effects & EFFECT_SCALE) { + scaleDyn += effectAmount * 0.01f; + + if ((effectAmount < 0 && scaleDyn <= (effectTarget * 0.01f)) + || (effectAmount > 0 && scaleDyn >= (effectTarget * 0.01f))) { + scaleDyn = effectTarget * 0.01f; + effects = 0; // shut off effect + effectFinished(this); + } + } +} diff --git a/source/gui/GuiFrame.cpp b/source/gui/GuiFrame.cpp new file mode 100644 index 0000000..2af38e6 --- /dev/null +++ b/source/gui/GuiFrame.cpp @@ -0,0 +1,226 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include + +GuiFrame::GuiFrame(GuiFrame *p) { + parent = p; + width = 0; + height = 0; + dim = false; + + if (parent) { + parent->append(this); + } +} + +GuiFrame::GuiFrame(float w, float h, GuiFrame *p) { + parent = p; + width = w; + height = h; + dim = false; + + if (parent) { + parent->append(this); + } +} + +GuiFrame::~GuiFrame() { + closing(this); + + if (parent) { + parent->remove(this); + } +} + +void GuiFrame::append(GuiElement *e) { + if (e == nullptr) { + return; + } + + remove(e); + mutex.lock(); + elements.push_back(e); + e->setParent(this); + mutex.unlock(); +} + +void GuiFrame::insert(GuiElement *e, uint32_t index) { + if (e == nullptr || (index >= elements.size())) { + return; + } + + remove(e); + mutex.lock(); + elements.insert(elements.begin() + index, e); + e->setParent(this); + mutex.unlock(); +} + +void GuiFrame::remove(GuiElement *e) { + if (e == NULL) { + return; + } + + mutex.lock(); + for (uint32_t i = 0; i < elements.size(); ++i) { + if (e == elements[i]) { + elements.erase(elements.begin() + i); + break; + } + } + mutex.unlock(); +} + +void GuiFrame::removeAll() { + mutex.lock(); + elements.clear(); + mutex.unlock(); +} + +GuiElement *GuiFrame::getGuiElementAt(uint32_t index) const { + if (index >= elements.size()) { + return nullptr; + } + + return elements[index]; +} + +uint32_t GuiFrame::getSize() { + return elements.size(); +} + +void GuiFrame::resetState() { + GuiElement::resetState(); + + mutex.lock(); + for (auto & element : elements) { + element->resetState(); + } + mutex.unlock(); +} + +void GuiFrame::setState(uint32_t s, int32_t c) { + GuiElement::setState(s, c); + mutex.lock(); + for (uint32_t i = 0; i < elements.size(); ++i) { + elements[i]->setState(s, c); + } + mutex.unlock(); +} + +void GuiFrame::clearState(uint32_t s, int32_t c) { + GuiElement::clearState(s, c); + + mutex.lock(); + for (uint32_t i = 0; i < elements.size(); ++i) { + elements[i]->clearState(s, c); + } + mutex.unlock(); +} + +void GuiFrame::setVisible(bool v) { + visible = v; + + mutex.lock(); + for (auto & element : elements) { + element->setVisible(v); + } + mutex.unlock(); +} + +int32_t GuiFrame::getSelected() { + // find selected element + int32_t found = -1; + mutex.lock(); + for (uint32_t i = 0; i < elements.size(); ++i) { + if (elements[i]->isStateSet(STATE_SELECTED | STATE_OVER)) { + found = i; + break; + } + } + mutex.unlock(); + return found; +} + +void GuiFrame::draw(Renderer *v) { + if (!this->isVisible() && parentElement) { + return; + } + + if (parentElement && dim) { + //GXColor dimColor = (GXColor){0, 0, 0, 0x70}; + //Menu_DrawRectangle(0, 0, GetZPosition(), screenwidth,screenheight, &dimColor, false, true); + } + + mutex.lock(); + //! render appended items next frame but allow stop of render if size is reached + uint32_t size = elements.size(); + + for (uint32_t i = 0; i < size && i < elements.size(); ++i) { + elements[i]->draw(v); + } + mutex.unlock(); +} + +void GuiFrame::updateEffects() { + if (!this->isVisible() && parentElement) { + return; + } + + GuiElement::updateEffects(); + + mutex.lock(); + //! render appended items next frame but allow stop of render if size is reached + uint32_t size = elements.size(); + + for (uint32_t i = 0; i < size && i < elements.size(); ++i) { + elements[i]->updateEffects(); + } + mutex.unlock(); +} + +void GuiFrame::process() { + if (!this->isVisible() && parentElement) { + return; + } + + GuiElement::process(); + + mutex.lock(); + //! render appended items next frame but allow stop of render if size is reached + uint32_t size = elements.size(); + + for (uint32_t i = 0; i < size && i < elements.size(); ++i) { + elements[i]->process(); + } + mutex.unlock(); +} + +void GuiFrame::update(GuiController *c) { + if (isStateSet(STATE_DISABLED) && parentElement) { + return; + } + + mutex.lock(); + //! update appended items next frame + uint32_t size = elements.size(); + + for (uint32_t i = 0; i < size && i < elements.size(); ++i) { + elements[i]->update(c); + } + mutex.unlock(); +} diff --git a/source/gui/GuiImage.cpp b/source/gui/GuiImage.cpp new file mode 100644 index 0000000..fad3231 --- /dev/null +++ b/source/gui/GuiImage.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include + +GuiImage::GuiImage(GuiTextureData *texture) { + setTexture(texture); +} + +GuiImage::GuiImage(SDL_Color color, float width, float height) { + this->color = color; + this->setSize(width, height); +} + +GuiImage::~GuiImage() { + if (this->texture && freeTextureData) { + delete this->texture; + } +} + +void GuiImage::draw(Renderer *renderer) { + if (!this->isVisible()) { + return; + } + + SDL_Rect rect; + rect.x = (int) getLeft(); + rect.y = (int) getTop(); + rect.w = (int) (getScaleX() * getWidth()); + rect.h = (int) (getScaleY() * getHeight()); + + if (texture) { + texture->draw(renderer, rect, getAngle()); + } else { + SDL_SetRenderDrawColor(renderer->getRenderer(), color.r, color.g, color.b, color.a); + SDL_RenderFillRect(renderer->getRenderer(), &rect); + if(getAngle() != 0.0f){ + LG_Log("Drawing a rotated rect is not supported yet"); + } + } +} + +void GuiImage::setTexture(GuiTextureData *tex) { + if (tex) { + if(this->texture && freeTextureData){ + delete this->texture; + } + this->texture = tex; + this->setSize(tex->getWidth(), tex->getHeight()); + } +} diff --git a/source/gui/GuiSound.cpp b/source/gui/GuiSound.cpp new file mode 100644 index 0000000..8c41ff7 --- /dev/null +++ b/source/gui/GuiSound.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include + +GuiSound::GuiSound(void *buffer, uint32_t filesize, bool freeSrc) { + SDL_RWops *rw = SDL_RWFromMem(buffer, filesize); + music = Mix_LoadWAV_RW(rw, freeSrc); +} + +GuiSound::GuiSound(const char *filepath) { + Load(filepath); +} + +GuiSound::~GuiSound() { + if (music) { + Mix_FreeChunk(music); + music = nullptr; + } +} + +bool GuiSound::Load(const char *filepath) { + music = Mix_LoadWAV(filepath); + return music != nullptr; +} + +void GuiSound::Play() { + if (music) { + playedOn = Mix_PlayChannel(-1, music, loops); + } +} + +void GuiSound::Stop() const { + Pause(); +} + +void GuiSound::Pause() const { + if (playedOn != -1) { + Mix_HaltChannel(playedOn); + } +} + +void GuiSound::Resume() { + Play(); +} + +bool GuiSound::IsPlaying() const { + if (playedOn == -1) { + return false; + } + return Mix_Playing(playedOn); +} + +void GuiSound::SetVolume(uint32_t vol) const { + if (music) { Mix_VolumeChunk(music, vol); } +} + +void GuiSound::SetLoop(bool l) { + // < 0 == infinitive loop + loops = l ? -1 : 1; +} + +void GuiSound::Rewind() const { + // TODO: how to rewind? + Stop(); +} diff --git a/source/gui/GuiText.cpp b/source/gui/GuiText.cpp new file mode 100644 index 0000000..05c71e8 --- /dev/null +++ b/source/gui/GuiText.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include +#include + +/** + * Constructor for the GuiText class. + */ + +GuiText::GuiText(const std::string& text, SDL_Color c, FC_Font* gFont) { + this->text = text; + this->color = c; + this->fc_font = gFont; + this->doUpdateTexture = true; + this->texture.setParent(this); +} + +GuiText::~GuiText() { + delete textureData; +} + +void GuiText::draw(Renderer *renderer) { + if (!this->isVisible()) { + return; + } + + updateTexture(renderer); + + texture.draw(renderer); +} + +void GuiText::process() { + GuiElement::process(); +} + +void GuiText::setMaxWidth(float width) { + this->maxWidth = width; + + // Rebuild the texture cache on next draw + doUpdateTexture = true; +} + +void GuiText::updateSize() { + auto height = FC_GetColumnHeight(fc_font, maxWidth, text.c_str()); + auto width = FC_GetWidth(fc_font, text.c_str()); + width = width > maxWidth ? maxWidth : width; + this->setSize(width, height); +} + +void GuiText::updateTexture(Renderer *renderer) { + if(doUpdateTexture) { + updateSize(); + int tex_width = width == 0 ? 1 : (int) width; + int tex_height = height == 0 ? 1 : (int)height; + + SDL_Texture *temp = SDL_CreateTexture(renderer->getRenderer(), renderer->getPixelFormat(), SDL_TEXTUREACCESS_TARGET, tex_width, tex_height); + if (temp) { + texture.setTexture(nullptr); + delete textureData; + textureData = new GuiTextureData(temp); + textureData->setBlendMode(SDL_BLENDMODE_BLEND); + texture.setTexture(textureData); + + // Set render target to texture + SDL_SetRenderTarget(renderer->getRenderer(), temp); + + // Clear texture. + SDL_SetRenderDrawColor(renderer->getRenderer(), 0, 0, 0, 0); + SDL_RenderClear(renderer->getRenderer()); + + // Draw text to texture + FC_DrawColumn(fc_font, renderer->getRenderer(), 0, 0, maxWidth, text.c_str()); + + // Restore render target + SDL_SetRenderTarget(renderer->getRenderer(), nullptr); + } else { + LG_Log("Failed to create texture"); + } + doUpdateTexture = false; + } +} diff --git a/source/gui/GuiTextureData.cpp b/source/gui/GuiTextureData.cpp new file mode 100644 index 0000000..cf72fb6 --- /dev/null +++ b/source/gui/GuiTextureData.cpp @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +GuiTextureData::GuiTextureData(const std::string& path) { + SDL_Surface *surface = IMG_Load_RW(SDL_RWFromFile(path.c_str(), "rb"), 1); + loadSurface(surface); +} + +GuiTextureData::GuiTextureData(void *buffer, const uint32_t filesize, bool freesrc) { + SDL_RWops *rw = SDL_RWFromMem(buffer, filesize); + SDL_Surface *surface = IMG_Load_RW(rw, freesrc); + loadSurface(surface); +} + +GuiTextureData::GuiTextureData(SDL_Texture *texture) { + if (this->texture) { + SDL_DestroyTexture(this->texture); + this->texture = nullptr; + } + this->texture = texture; + int w, h; + SDL_QueryTexture(this->texture, nullptr, nullptr, &w, &h); + this->width = w; + this->height = h; +} + +void GuiTextureData::loadSurface(SDL_Surface *pSurface) { + if(!pSurface){ + return; + } + + cleanUp(); + + imgSurface = pSurface; + + this->width = imgSurface->w; + this->height = imgSurface->h; +} + +void GuiTextureData::cleanUp() { + if (imgSurface) { + SDL_FreeSurface(imgSurface); + imgSurface = nullptr; + } + if (texture) { + SDL_DestroyTexture(texture); + texture = nullptr; + } +} + +/** + * Destructor for the GuiImage class. + */ +GuiTextureData::~GuiTextureData() { + cleanUp(); +} + +void GuiTextureData::draw(Renderer *renderer, const SDL_Rect& dest, float angle) { + if (texture == nullptr && imgSurface) { + texture = SDL_CreateTextureFromSurface(renderer->getRenderer(), imgSurface); + } + if (!texture) { + LG_Log("no texture!"); + return; + } + + // copy the texture to the rendering context + SDL_BlendMode mode; + SDL_GetRenderDrawBlendMode(renderer->getRenderer(), &mode); + + // adjust blend mode + if(blendMode != mode){ + SDL_SetRenderDrawBlendMode(renderer->getRenderer(), blendMode); + } + + if (angle == 0) { + SDL_RenderCopy(renderer->getRenderer(), texture, nullptr, &dest); + } else { + SDL_RenderCopyEx(renderer->getRenderer(), texture, nullptr, &dest, angle, nullptr, SDL_FLIP_NONE); + } + + if(blendMode != mode){ + SDL_SetRenderDrawBlendMode(renderer->getRenderer(), mode); + } +} + +int GuiTextureData::setBlendMode(SDL_BlendMode blendMode) { + this->blendMode = blendMode; + if(texture){ return SDL_SetTextureBlendMode(texture, blendMode); } + return SDL_BLENDMODE_INVALID; +} \ No newline at end of file diff --git a/source/gui/GuiTrigger.cpp b/source/gui/GuiTrigger.cpp new file mode 100644 index 0000000..04e1134 --- /dev/null +++ b/source/gui/GuiTrigger.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include + +/** + * Constructor for the GuiTrigger class. + */ +GuiTrigger::GuiTrigger() + : chan(CHANNEL_ALL), btns(BUTTON_NONE), bClickEverywhere(false), bHoldEverywhere(false), bSelectionClickEverywhere(false), bLastTouched(false) { +} + +GuiTrigger::GuiTrigger(uint32_t ch, uint32_t btn, bool clickEverywhere, bool holdEverywhere, bool selectionClickEverywhere) + : chan(ch), btns(btn), bClickEverywhere(clickEverywhere), bHoldEverywhere(holdEverywhere), bSelectionClickEverywhere(selectionClickEverywhere), bLastTouched(false) { +} + +/** + * Destructor for the GuiTrigger class. + */ +GuiTrigger::~GuiTrigger() = default; + +/** + * Sets a simple trigger. Requires: + * - Element is selected + * - Trigger button is pressed + */ +void GuiTrigger::setTrigger(uint32_t ch, uint32_t btn) { + chan = ch; + btns = btn; +} + +bool GuiTrigger::left(const GuiController *controller) const { + if ((controller->chan & chan) == 0) { + return false; + } + if ((controller->data.buttons_h | controller->data.buttons_d) & (BUTTON_LEFT | STICK_L_LEFT)) { + return true; + } + return false; +} + +bool GuiTrigger::right(const GuiController *controller) const { + if ((controller->chan & chan) == 0) { + return false; + } + if ((controller->data.buttons_h | controller->data.buttons_d) & (BUTTON_RIGHT | STICK_L_RIGHT)) { + return true; + } + return false; +} + +bool GuiTrigger::up(const GuiController *controller) const { + if ((controller->chan & chan) == 0) { + return false; + } + if ((controller->data.buttons_h | controller->data.buttons_d) & (BUTTON_UP | STICK_L_UP)) { + return true; + } + return false; +} + +bool GuiTrigger::down(const GuiController *controller) const { + if ((controller->chan & chan) == 0) { + return false; + } + if ((controller->data.buttons_h | controller->data.buttons_d) & (BUTTON_DOWN | STICK_L_DOWN)) { + return true; + } + return false; +} + +int32_t GuiTrigger::clicked(const GuiController *controller) const { + if ((controller->chan & chan) == 0) { + return CLICKED_NONE; + } + + int32_t bResult = CLICKED_NONE; + + + if (controller->data.touched && controller->data.validPointer && (btns & TOUCHED) && !controller->lastData.touched) { + bResult = CLICKED_TOUCH; + } + + if (controller->data.buttons_d & btns) { + bResult = CLICKED_BUTTON; + } + return bResult; +} + +bool GuiTrigger::held(const GuiController *controller) const { + if ((controller->chan & chan) == 0) { + return false; + } + + bool bResult = false; + + if (controller->data.touched && (btns & TOUCHED) && controller->data.validPointer && controller->lastData.touched && controller->lastData.validPointer) { + bResult = true; + } + + if (controller->data.buttons_h & btns) { + bResult = true; + } + + return bResult; +} + +bool GuiTrigger::released(const GuiController *controller) const { + if ((controller->chan & chan) == 0) { + return false; + } + + if (clicked(controller) || held(controller)) { + return false; + } + + bool bResult = false; + + if (!controller->data.touched && (btns & TOUCHED) && controller->lastData.touched && controller->lastData.validPointer) { + bResult = true; + } + + if (controller->data.buttons_r & btns) { + bResult = true; + } + + return bResult; +} + diff --git a/source/gui/input/ControllerManager.cpp b/source/gui/input/ControllerManager.cpp new file mode 100644 index 0000000..be34046 --- /dev/null +++ b/source/gui/input/ControllerManager.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include + +GuiTrigger::eChannels ControllerManager::increaseChannel(GuiTrigger::eChannels channel) { + switch (channel) { + case GuiTrigger::CHANNEL_1: + return GuiTrigger::CHANNEL_2; + case GuiTrigger::CHANNEL_2: + return GuiTrigger::CHANNEL_3; + case GuiTrigger::CHANNEL_3: + return GuiTrigger::CHANNEL_4; + case GuiTrigger::CHANNEL_4: + return GuiTrigger::CHANNEL_5; + case GuiTrigger::CHANNEL_5: + case GuiTrigger::CHANNEL_ALL: + return GuiTrigger::CHANNEL_ALL; + } + return GuiTrigger::CHANNEL_ALL; +} + +void ControllerManager::attachController(GuiTrigger::eChannels channel, SDLController *controller) { + controllerList[channel] = controller; +} + +void ControllerManager::prepare() { + //! Read out inputs + for (auto const&[channel, controller] : controllerList) { + controller->before(); + } +} + +bool ControllerManager::attachJoystick(int32_t deviceId) { + auto joystick = SDL_JoystickOpen(deviceId); + if (joystick == nullptr) { + LG_Log("SDL_JoystickOpen failed: %s\n", SDL_GetError()); + return false; + } + auto instanceId = SDL_JoystickInstanceID(joystick); + if (std::string("WiiU Gamepad") == SDL_JoystickName(joystick)) { + controllerList[GuiTrigger::CHANNEL_1] = new SDLControllerWiiUGamepad(GuiTrigger::CHANNEL_1); + joystickToChannel[instanceId] = GuiTrigger::CHANNEL_1; + } else { + bool successfully_added = false; + auto channel = GuiTrigger::CHANNEL_2; + while (channel != GuiTrigger::CHANNEL_ALL) { + if (controllerList.find(channel) == controllerList.end()) { + if (std::string(SDL_JoystickName(joystick)).find("Xbox") != std::string::npos + || std::string(SDL_JoystickName(joystick)).find("X-Box") != std::string::npos) { + controllerList[channel] = new SDLControllerXboxOne(channel); + } else if (std::string(SDL_JoystickName(joystick)).find("WiiU Pro Controller") != std::string::npos) { + controllerList[channel] = new SDLControllerWiiUProContoller(channel); + } else { + controllerList[channel] = new SDLControllerJoystick(channel, instanceId); + } + joystickToChannel[instanceId] = channel; + successfully_added = true; + break; + } + channel = increaseChannel(channel); + } + if (!successfully_added) { + LG_Log("Failed to add joystick. Closing it now"); + SDL_JoystickClose(joystick); + return false; + } + } + LG_Log("Added joystick %s", SDL_JoystickName(joystick)); + return true; +} + +void ControllerManager::detachJoystick(int32_t instanceId) { + auto channel = joystickToChannel[instanceId]; + delete controllerList[channel]; + controllerList.erase(channel); + joystickToChannel.erase(instanceId); + LG_Log("Removed joystick: %d", instanceId); +} + +void ControllerManager::processEvent(SDL_JoystickID joystickId, int32_t channel, SDL_Event *e) { + if (joystickId != -1) { + if (joystickToChannel.find(joystickId) != joystickToChannel.end()) { + channel = joystickToChannel[joystickId]; + } + } + if (channel != -1 && controllerList.count(static_cast(channel)) > 0) { + controllerList[static_cast(channel)]->update(e, screenWidth, screenHeight); + } +} + +void ControllerManager::finish() { + for (auto const&[joypad, controller] : controllerList) { + controller->after(); + } +} + +void ControllerManager::callPerController(std::function func) { + for (auto const&[joypad, controller] : controllerList) { + func(controller); + } +} diff --git a/source/gui/system/SDLSystem.cpp b/source/gui/system/SDLSystem.cpp new file mode 100644 index 0000000..23dfe95 --- /dev/null +++ b/source/gui/system/SDLSystem.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include +#include +#include +#include +#include + +SDLSystem::SDLSystem() { + SDL_Init(SDL_INIT_EVERYTHING); + + auto SDLFlags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC; + + //Setup window + window = SDL_CreateWindow(nullptr, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, 0); + if (!window) { + LG_Log("Failed to create window"); + return; + } + auto sdl_renderer = SDL_CreateRenderer(window, -1, SDLFlags); + if (!sdl_renderer) { + // DEBUG_FUNCTION_LINE("Failed to init sdl renderer"); + return; + } + SDL_SetRenderTarget(sdl_renderer, nullptr); + this->renderer = new Renderer(sdl_renderer, SDL_GetWindowPixelFormat(window)); + if (!renderer) { + LG_Log("Failed to init renderer"); + return; + } + + if (SDL_Init(SDL_INIT_AUDIO) != 0) { + LG_Log("SDL init error: %s\n", SDL_GetError()); + return; + } + + int flags = 0; + int result = 0; + if (flags != (result = Mix_Init(flags))) { + LG_Log("Could not initialize mixer (result: %d).\n", result); + LG_Log("Mix_Init: %s\n", Mix_GetError()); + } + + auto dev = Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 640); + SDL_PauseAudioDevice(dev, 0); + + TTF_Init(); +} + +SDLSystem::~SDLSystem() { + SDL_DestroyWindow(window); + delete renderer; + SDL_Quit(); +} + +float SDLSystem::getHeight() { + int h = 0; + SDL_GetWindowSize(window, nullptr, &h); + return h; +} + +float SDLSystem::getWidth() { + int w = 0; + SDL_GetWindowSize(window, &w, nullptr); + return w; +} + +Renderer *SDLSystem::getRenderer() { + return renderer; +} diff --git a/source/gui/video/SDL_FontCache.cpp b/source/gui/video/SDL_FontCache.cpp new file mode 100644 index 0000000..9a52d8f --- /dev/null +++ b/source/gui/video/SDL_FontCache.cpp @@ -0,0 +1,3033 @@ +/* +SDL_FontCache: A font cache for SDL and SDL_ttf +by Jonathan Dearborn + +See SDL_FontCache.h for license info. +*/ + +#include + +#include +#include +#include +#include + +// Visual C does not support static inline +#ifndef static_inline + #ifdef _MSC_VER + #define static_inline static + #else + #define static_inline static inline + #endif +#endif + +#if SDL_VERSION_ATLEAST(2,0,0) + #define FC_GET_ALPHA(sdl_color) ((sdl_color).a) +#else + #define FC_GET_ALPHA(sdl_color) ((sdl_color).unused) +#endif + +// Need SDL_RenderIsClipEnabled() for proper clipping support +#if SDL_VERSION_ATLEAST(2,0,4) + #define ENABLE_SDL_CLIPPING +#endif + +#define FC_MIN(a,b) ((a) < (b)? (a) : (b)) +#define FC_MAX(a,b) ((a) > (b)? (a) : (b)) + + +// vsnprintf replacement from Valentin Milea: +// http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +#if defined(_MSC_VER) && _MSC_VER < 1900 + +#define snprintf c99_snprintf +#define vsnprintf c99_vsnprintf + +__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + + return count; +} + +#endif + + +#define FC_EXTRACT_VARARGS(buffer, start_args) \ +{ \ + va_list lst; \ + va_start(lst, start_args); \ + vsnprintf(buffer, fc_buffer_size, start_args, lst); \ + va_end(lst); \ +} + +// Extra pixels of padding around each glyph to avoid linear filtering artifacts +#define FC_CACHE_PADDING 1 + + +static Uint8 has_clip(FC_Target* dest) +{ + #ifdef FC_USE_SDL_GPU + return dest->use_clip_rect; + #elif defined(ENABLE_SDL_CLIPPING) + return SDL_RenderIsClipEnabled(dest); + #else + return 0; + #endif +} + +static FC_Rect get_clip(FC_Target* dest) +{ + #ifdef FC_USE_SDL_GPU + return dest->clip_rect; + #elif defined(ENABLE_SDL_CLIPPING) + SDL_Rect r; + SDL_RenderGetClipRect(dest, &r); + return r; + #else + SDL_Rect r = {0, 0, 0, 0}; + return r; + #endif +} + +static void set_clip(FC_Target* dest, FC_Rect* rect) +{ + #ifdef FC_USE_SDL_GPU + if(rect != NULL) + GPU_SetClipRect(dest, *rect); + else + GPU_UnsetClip(dest); + #elif defined(ENABLE_SDL_CLIPPING) + SDL_RenderSetClipRect(dest, rect); + #endif +} + +static void set_color(FC_Image* src, Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + #ifdef FC_USE_SDL_GPU + GPU_SetRGBA(src, r, g, b, a); + #else + SDL_SetTextureColorMod(src, r, g, b); + SDL_SetTextureAlphaMod(src, a); + #endif +} + + + +static char* new_concat(const char* a, const char* b) +{ + // Create new buffer + unsigned int size = strlen(a) + strlen(b); + char* new_string = (char*)malloc(size+1); + + // Concatenate strings in the new buffer + strcpy(new_string, a); + strcat(new_string, b); + + return new_string; +} + +static char* replace_concat(char** a, const char* b) +{ + char* new_string = new_concat(*a, b); + free(*a); + *a = new_string; + return *a; +} + + +// Width of a tab in units of the space width (sorry, no tab alignment!) +static unsigned int fc_tab_width = 4; + +// Shared buffer for variadic text +static char* fc_buffer = NULL; +static unsigned int fc_buffer_size = 1024; + +static Uint8 fc_has_render_target_support = 0; + +// The number of fonts that has been created but not freed +static int NUM_EXISTING_FONTS = 0; + +// Globals for GetString functions +static char* ASCII_STRING = NULL; +static char* LATIN_1_STRING = NULL; +static char* ASCII_LATIN_1_STRING = NULL; + +char* FC_GetStringASCII(void) +{ + if(ASCII_STRING == NULL) + { + int i; + char c; + ASCII_STRING = (char*)malloc(512); + memset(ASCII_STRING, 0, 512); + i = 0; + c = 32; + while(1) + { + ASCII_STRING[i] = c; + if(c == 126) + break; + ++i; + ++c; + } + } + return U8_strdup(ASCII_STRING); +} + +char* FC_GetStringLatin1(void) +{ + if(LATIN_1_STRING == NULL) + { + int i; + unsigned char c; + LATIN_1_STRING = (char*)malloc(512); + memset(LATIN_1_STRING, 0, 512); + i = 0; + c = 0xA0; + while(1) + { + LATIN_1_STRING[i] = 0xC2; + LATIN_1_STRING[i+1] = c; + if(c == 0xBF) + break; + i += 2; + ++c; + } + i += 2; + c = 0x80; + while(1) + { + LATIN_1_STRING[i] = 0xC3; + LATIN_1_STRING[i+1] = c; + if(c == 0xBF) + break; + i += 2; + ++c; + } + } + return U8_strdup(LATIN_1_STRING); +} + +char* FC_GetStringASCII_Latin1(void) +{ + if(ASCII_LATIN_1_STRING == NULL) + ASCII_LATIN_1_STRING = new_concat(FC_GetStringASCII(), FC_GetStringLatin1()); + + return U8_strdup(ASCII_LATIN_1_STRING); +} + +FC_Rect FC_MakeRect(float x, float y, float w, float h) +{ + FC_Rect r = {(int) x, (int) y, (int) w, (int) h}; + return r; +} + +FC_Scale FC_MakeScale(float x, float y) +{ + FC_Scale s = {x, y}; + + return s; +} + +SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + SDL_Color c = {r, g, b, a}; + + return c; +} + +FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color) +{ + FC_Effect e; + + e.alignment = alignment; + e.scale = scale; + e.color = color; + + return e; +} + +FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h) +{ + FC_GlyphData gd; + + gd.rect.x = x; + gd.rect.y = y; + gd.rect.w = w; + gd.rect.h = h; + gd.cache_level = cache_level; + + return gd; +} + +// Enough to hold all of the ascii characters and some. +#define FC_DEFAULT_NUM_BUCKETS 300 + +typedef struct FC_MapNode +{ + Uint32 key; + FC_GlyphData value; + struct FC_MapNode* next; + +} FC_MapNode; + +typedef struct FC_Map +{ + int num_buckets; + FC_MapNode** buckets; +} FC_Map; + + + +static FC_Map* FC_MapCreate(int num_buckets) +{ + int i; + FC_Map* map = (FC_Map*)malloc(sizeof(FC_Map)); + + map->num_buckets = num_buckets; + map->buckets = (FC_MapNode**)malloc(num_buckets * sizeof(FC_MapNode*)); + + for(i = 0; i < num_buckets; ++i) + { + map->buckets[i] = NULL; + } + + return map; +} + +/*static void FC_MapClear(FC_Map* map) +{ + int i; + if(map == NULL) + return; + + // Go through each bucket + for(i = 0; i < map->num_buckets; ++i) + { + // Delete the nodes in order + FC_MapNode* node = map->buckets[i]; + while(node != NULL) + { + FC_MapNode* last = node; + node = node->next; + free(last); + } + // Set the bucket to empty + map->buckets[i] = NULL; + } +}*/ + +static void FC_MapFree(FC_Map* map) +{ + int i; + if(map == NULL) + return; + + // Go through each bucket + for(i = 0; i < map->num_buckets; ++i) + { + // Delete the nodes in order + FC_MapNode* node = map->buckets[i]; + while(node != NULL) + { + FC_MapNode* last = node; + node = node->next; + free(last); + } + } + + free(map->buckets); + free(map); +} + +// Note: Does not handle duplicates in any special way. +static FC_GlyphData* FC_MapInsert(FC_Map* map, Uint32 codepoint, FC_GlyphData glyph) +{ + Uint32 index; + FC_MapNode* node; + if(map == NULL) + return NULL; + + // Get index for bucket + index = codepoint % map->num_buckets; + + // If this bucket is empty, create a node and return its value + if(map->buckets[index] == NULL) + { + node = map->buckets[index] = (FC_MapNode*)malloc(sizeof(FC_MapNode)); + node->key = codepoint; + node->value = glyph; + node->next = NULL; + return &node->value; + } + + for(node = map->buckets[index]; node != NULL; node = node->next) + { + // Find empty node and add a new one on. + if(node->next == NULL) + { + node->next = (FC_MapNode*)malloc(sizeof(FC_MapNode)); + node = node->next; + + node->key = codepoint; + node->value = glyph; + node->next = NULL; + return &node->value; + } + } + + return NULL; +} + +static FC_GlyphData* FC_MapFind(FC_Map* map, Uint32 codepoint) +{ + Uint32 index; + FC_MapNode* node; + if(map == NULL) + return NULL; + + // Get index for bucket + index = codepoint % map->num_buckets; + + // Go through list until we find a match + for(node = map->buckets[index]; node != NULL; node = node->next) + { + if(node->key == codepoint) + return &node->value; + } + + return NULL; +} + + + +struct FC_Font +{ + #ifndef FC_USE_SDL_GPU + SDL_Renderer* renderer; + #endif + + TTF_Font* ttf_source; // TTF_Font source of characters + Uint8 owns_ttf_source; // Can we delete the TTF_Font ourselves? + + FC_FilterEnum filter; + + SDL_Color default_color; + Uint16 height; + + Uint16 maxWidth; + Uint16 baseline; + int ascent; + int descent; + + int lineSpacing; + int letterSpacing; + + // Uses 32-bit (4-byte) Unicode codepoints to refer to each glyph + // Codepoints are little endian (reversed from UTF-8) so that something like 0x00000005 is ASCII 5 and the map can be indexed by ASCII values + FC_Map* glyphs; + + FC_GlyphData last_glyph; // Texture packing cursor + int glyph_cache_size; + int glyph_cache_count; + FC_Image** glyph_cache; + + char* loading_string; + + std::recursive_mutex* mutex = nullptr; +}; + +// Private +static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight); + + +static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); +static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); +static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); + + +static_inline SDL_Surface* FC_CreateSurface32(Uint32 width, Uint32 height) +{ + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); + #else + return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); + #endif +} + + +char* U8_alloc(unsigned int size) +{ + char* result; + if(size == 0) + return NULL; + + result = (char*)malloc(size); + result[0] = '\0'; + + return result; +} + +void U8_free(char* string) +{ + free(string); +} + +char* U8_strdup(const char* string) +{ + char* result; + if(string == NULL) + return NULL; + + result = (char*)malloc(strlen(string)+1); + strcpy(result, string); + + return result; +} + +int U8_strlen(const char* string) +{ + int length = 0; + if(string == NULL) + return 0; + + while(*string != '\0') + { + string = U8_next(string); + ++length; + } + + return length; +} + +int U8_charsize(const char* character) +{ + if(character == NULL) + return 0; + + if((unsigned char)*character <= 0x7F) + return 1; + else if((unsigned char)*character < 0xE0) + return 2; + else if((unsigned char)*character < 0xF0) + return 3; + else + return 4; + return 1; +} + +int U8_charcpy(char* buffer, const char* source, int buffer_size) +{ + int charsize; + if(buffer == NULL || source == NULL || buffer_size < 1) + return 0; + + charsize = U8_charsize(source); + if(charsize > buffer_size) + return 0; + + memcpy(buffer, source, charsize); + return charsize; +} + +const char* U8_next(const char* string) +{ + return string + U8_charsize(string); +} + +int U8_strinsert(char* string, int position, const char* source, int max_bytes) +{ + int pos_u8char; + int len; + int add_len; + int ulen; + const char* string_start = string; + + if(string == NULL || source == NULL) + return 0; + + len = strlen(string); + add_len = strlen(source); + ulen = U8_strlen(string); + + if(position == -1) + position = ulen; + + if(position < 0 || position > ulen || len + add_len + 1 > max_bytes) + return 0; + + // Move string pointer to the proper position + pos_u8char = 0; + while(*string != '\0' && pos_u8char < position) + { + string = (char*)U8_next(string); + ++pos_u8char; + } + + // Move the rest of the string out of the way + memmove(string + add_len, string, len - (string - string_start) + 1); + + // Copy in the new characters + memcpy(string, source, add_len); + + return 1; +} + +void U8_strdel(char* string, int position) +{ + if(string == NULL || position < 0) + return; + + while(*string != '\0') + { + if(position == 0) + { + int chars_to_erase = U8_charsize(string); + int remaining_bytes = strlen(string) + 1; + memmove(string, string + chars_to_erase, remaining_bytes); + break; + } + + string = (char*)U8_next(string); + --position; + } +} + + + + + +static_inline FC_Rect FC_RectUnion(FC_Rect A, FC_Rect B) +{ + float x,x2,y,y2; + x = FC_MIN(A.x, B.x); + y = FC_MIN(A.y, B.y); + x2 = FC_MAX(A.x+A.w, B.x+B.w); + y2 = FC_MAX(A.y+A.h, B.y+B.h); + { + FC_Rect result = {(int) x, (int) y, (int) FC_MAX(0, x2 - x), (int) FC_MAX(0, y2 - y)}; + return result; + } +} + +// Adapted from SDL_IntersectRect +static_inline FC_Rect FC_RectIntersect(FC_Rect A, FC_Rect B) +{ + FC_Rect result; + float Amin, Amax, Bmin, Bmax; + + // Horizontal intersection + Amin = A.x; + Amax = Amin + A.w; + Bmin = B.x; + Bmax = Bmin + B.w; + if(Bmin > Amin) + Amin = Bmin; + result.x = Amin; + if(Bmax < Amax) + Amax = Bmax; + result.w = Amax - Amin > 0 ? Amax - Amin : 0; + + // Vertical intersection + Amin = A.y; + Amax = Amin + A.h; + Bmin = B.y; + Bmax = Bmin + B.h; + if(Bmin > Amin) + Amin = Bmin; + result.y = Amin; + if(Bmax < Amax) + Amax = Bmax; + result.h = Amax - Amin > 0 ? Amax - Amin : 0; + + return result; +} + + + + + + + + + + + + + + +FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) +{ + float w = srcrect->w * xscale; + float h = srcrect->h * yscale; + FC_Rect result; + + // FIXME: Why does the scaled offset look so wrong? + #ifdef FC_USE_SDL_GPU + { + GPU_Rect r = *srcrect; + GPU_BlitScale(src, &r, dest, x + xscale*r.w/2.0f, y + r.h/2.0f, xscale, yscale); + } + #else + { + SDL_RendererFlip flip = SDL_FLIP_NONE; + if(xscale < 0) + { + xscale = -xscale; + flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_HORIZONTAL); + } + if(yscale < 0) + { + yscale = -yscale; + flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_VERTICAL); + } + + SDL_Rect r = *srcrect; + SDL_Rect dr = {(int)x, (int)y, (int)(xscale*r.w), (int)(yscale*r.h)}; + SDL_RenderCopyEx(dest, src, &r, &dr, 0, NULL, flip); + } + #endif + + result.x = x; + result.y = y; + result.w = w; + result.h = h; + return result; +} + +static FC_Rect (*fc_render_callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) = &FC_DefaultRenderCallback; + +void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale)) +{ + if(callback == NULL) + fc_render_callback = &FC_DefaultRenderCallback; + else + fc_render_callback = callback; +} + +void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint) +{ + char a, b, c, d; + + if(result == NULL) + return; + + a = (codepoint >> 24) & 0xFF; + b = (codepoint >> 16) & 0xFF; + c = (codepoint >> 8) & 0xFF; + d = codepoint & 0xFF; + + if(a == 0) + { + if(b == 0) + { + if(c == 0) + { + result[0] = d; + result[1] = '\0'; + } + else + { + result[0] = c; + result[1] = d; + result[2] = '\0'; + } + } + else + { + result[0] = b; + result[1] = c; + result[2] = d; + result[3] = '\0'; + } + } + else + { + result[0] = a; + result[1] = b; + result[2] = c; + result[3] = d; + result[4] = '\0'; + } +} + +Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer) +{ + Uint32 result = 0; + const char* str; + if(c == NULL || *c == NULL) + return 0; + + str = *c; + if((unsigned char)*str <= 0x7F) + result = *str; + else if((unsigned char)*str < 0xE0) + { + result |= (unsigned char)(*str) << 8; + result |= (unsigned char)(*(str+1)); + if(advance_pointer) + *c += 1; + } + else if((unsigned char)*str < 0xF0) + { + result |= (unsigned char)(*str) << 16; + result |= (unsigned char)(*(str+1)) << 8; + result |= (unsigned char)(*(str+2)); + if(advance_pointer) + *c += 2; + } + else + { + result |= (unsigned char)(*str) << 24; + result |= (unsigned char)(*(str+1)) << 16; + result |= (unsigned char)(*(str+2)) << 8; + result |= (unsigned char)(*(str+3)); + if(advance_pointer) + *c += 3; + } + return result; +} + + +void FC_SetLoadingString(FC_Font* font, const char* string) +{ + if(font == NULL) + return; + + free(font->loading_string); + font->loading_string = U8_strdup(string); +} + + +unsigned int FC_GetBufferSize(void) +{ + return fc_buffer_size; +} + +void FC_SetBufferSize(unsigned int size) +{ + free(fc_buffer); + if(size > 0) + { + fc_buffer_size = size; + fc_buffer = (char*)malloc(fc_buffer_size); + } + else + fc_buffer = (char*)malloc(fc_buffer_size); +} + + +unsigned int FC_GetTabWidth(void) +{ + return fc_tab_width; +} + +void FC_SetTabWidth(unsigned int width_in_spaces) +{ + fc_tab_width = width_in_spaces; +} + +// Constructors +static void FC_Init(FC_Font* font) +{ + if(font == NULL) + return; + + #ifndef FC_USE_SDL_GPU + font->renderer = NULL; + #endif + + font->ttf_source = NULL; + font->owns_ttf_source = 0; + + font->filter = FC_FILTER_NEAREST; + + font->default_color.r = 0; + font->default_color.g = 0; + font->default_color.b = 0; + FC_GET_ALPHA(font->default_color) = 255; + + font->height = 0; // ascent+descent + + font->maxWidth = 0; + font->baseline = 0; + font->ascent = 0; + font->descent = 0; + + font->lineSpacing = 0; + font->letterSpacing = 0; + + // Give a little offset for when filtering/mipmaps are used. Depending on mipmap level, this will still not be enough. + font->last_glyph.rect.x = FC_CACHE_PADDING; + font->last_glyph.rect.y = FC_CACHE_PADDING; + font->last_glyph.rect.w = 0; + font->last_glyph.rect.h = 0; + font->last_glyph.cache_level = 0; + + if(font->glyphs != NULL) + FC_MapFree(font->glyphs); + + font->glyphs = FC_MapCreate(FC_DEFAULT_NUM_BUCKETS); + + font->glyph_cache_size = 3; + font->glyph_cache_count = 0; + + font->mutex = new std::recursive_mutex(); + + font->glyph_cache = (FC_Image**)malloc(font->glyph_cache_size * sizeof(FC_Image*)); + + if (font->loading_string == NULL) + font->loading_string = FC_GetStringASCII(); + + if(fc_buffer == NULL) + fc_buffer = (char*)malloc(fc_buffer_size); +} + +static Uint8 FC_GrowGlyphCache(FC_Font* font) +{ + if(font == NULL) + return 0; + #ifdef FC_USE_SDL_GPU + GPU_Image* new_level = GPU_CreateImage(font->height * 12, font->height * 12, GPU_FORMAT_RGBA); + GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different + #else + SDL_Texture* new_level = SDL_CreateTexture(font->renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, font->height * 12, font->height * 12); + #endif + if(new_level == NULL || !FC_SetGlyphCacheLevel(font, font->glyph_cache_count, new_level)) + { + FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n"); + #ifdef FC_USE_SDL_GPU + GPU_FreeImage(new_level); + #else + SDL_DestroyTexture(new_level); + #endif + return 0; + } + // bug: we do not have the correct color here, this might be the wrong color! + // , most functions use set_color_for_all_caches() + // - for evading this bug, you must use FC_SetDefaultColor(), before using any draw functions + set_color(new_level, font->default_color.r, font->default_color.g, font->default_color.b, FC_GET_ALPHA(font->default_color)); +#ifndef FC_USE_SDL_GPU + { + Uint8 r, g, b, a; + SDL_Texture* prev_target = SDL_GetRenderTarget(font->renderer); + SDL_Rect prev_clip, prev_viewport; + int prev_logicalw, prev_logicalh; + Uint8 prev_clip_enabled; + float prev_scalex, prev_scaley; + // only backup if previous target existed (SDL will preserve them for the default target) + if (prev_target) { + prev_clip_enabled = has_clip(font->renderer); + if (prev_clip_enabled) + prev_clip = get_clip(font->renderer); + SDL_RenderGetViewport(font->renderer, &prev_viewport); + SDL_RenderGetScale(font->renderer, &prev_scalex, &prev_scaley); + SDL_RenderGetLogicalSize(font->renderer, &prev_logicalw, &prev_logicalh); + } + SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); + SDL_SetRenderTarget(font->renderer, new_level); + SDL_GetRenderDrawColor(font->renderer, &r, &g, &b, &a); + SDL_SetRenderDrawColor(font->renderer, 0, 0, 0, 0); + SDL_RenderClear(font->renderer); + SDL_SetRenderDrawColor(font->renderer, r, g, b, a); + SDL_SetRenderTarget(font->renderer, prev_target); + if (prev_target) { + if (prev_clip_enabled) + set_clip(font->renderer, &prev_clip); + if (prev_logicalw && prev_logicalh) + SDL_RenderSetLogicalSize(font->renderer, prev_logicalw, prev_logicalh); + else { + SDL_RenderSetViewport(font->renderer, &prev_viewport); + SDL_RenderSetScale(font->renderer, prev_scalex, prev_scaley); + } + } + } +#endif + return 1; +} + +Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface) +{ + if(font == NULL || data_surface == NULL) + return 0; + #ifdef FC_USE_SDL_GPU + GPU_Image* new_level = GPU_CopyImageFromSurface(data_surface); + GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different + if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) + GPU_SetImageFilter(new_level, GPU_FILTER_LINEAR); + else + GPU_SetImageFilter(new_level, GPU_FILTER_NEAREST); + #else + SDL_Texture* new_level; + if(!fc_has_render_target_support) + new_level = SDL_CreateTextureFromSurface(font->renderer, data_surface); + else + { + // Must upload with render target enabled so we can put more glyphs on later + SDL_Renderer* renderer = font->renderer; + + // Set filter mode for new texture + char old_filter_mode[16]; // Save it so we can change the hint value in the meantime + const char* old_filter_hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY); + if(!old_filter_hint) + old_filter_hint = "nearest"; + snprintf(old_filter_mode, 16, "%s", old_filter_hint); + + if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); + else + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + + new_level = SDL_CreateTexture(renderer, data_surface->format->format, SDL_TEXTUREACCESS_TARGET, data_surface->w, data_surface->h); + SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); + + // Reset filter mode for the temp texture + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + + { + Uint8 r, g, b, a; + SDL_Texture* temp = SDL_CreateTextureFromSurface(renderer, data_surface); + SDL_Texture* prev_target = SDL_GetRenderTarget(renderer); + SDL_Rect prev_clip, prev_viewport; + int prev_logicalw, prev_logicalh; + Uint8 prev_clip_enabled; + float prev_scalex, prev_scaley; + // only backup if previous target existed (SDL will preserve them for the default target) + if (prev_target) { + prev_clip_enabled = has_clip(renderer); + if (prev_clip_enabled) + prev_clip = get_clip(renderer); + SDL_RenderGetViewport(renderer, &prev_viewport); + SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); + SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); + } + SDL_SetTextureBlendMode(temp, SDL_BLENDMODE_NONE); + SDL_SetRenderTarget(renderer, new_level); + + SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + SDL_SetRenderDrawColor(renderer, r, g, b, a); + + SDL_RenderCopy(renderer, temp, NULL, NULL); + SDL_SetRenderTarget(renderer, prev_target); + if (prev_target) { + if (prev_clip_enabled) + set_clip(renderer, &prev_clip); + if (prev_logicalw && prev_logicalh) + SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); + else { + SDL_RenderSetViewport(renderer, &prev_viewport); + SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); + } + } + + SDL_DestroyTexture(temp); + } + + // Reset to the old filter value + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, old_filter_mode); + + } + #endif + if(new_level == NULL || !FC_SetGlyphCacheLevel(font, cache_level, new_level)) + { + FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n"); + #ifdef FC_USE_SDL_GPU + GPU_FreeImage(new_level); + #else + SDL_DestroyTexture(new_level); + #endif + return 0; + } + return 1; +} + +static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight) +{ + FC_Map* glyphs = font->glyphs; + FC_GlyphData* last_glyph = &font->last_glyph; + Uint16 height = font->height + FC_CACHE_PADDING; + + // TAB is special! + if(codepoint == '\t') + { + FC_GlyphData spaceGlyph; + FC_GetGlyphData(font, &spaceGlyph, ' '); + width = fc_tab_width * spaceGlyph.rect.w; + } + + if(last_glyph->rect.x + last_glyph->rect.w + width >= maxWidth - FC_CACHE_PADDING) + { + if(last_glyph->rect.y + height + height >= maxHeight - FC_CACHE_PADDING) + { + // Get ready to pack on the next cache level when it is ready + last_glyph->cache_level = font->glyph_cache_count; + last_glyph->rect.x = FC_CACHE_PADDING; + last_glyph->rect.y = FC_CACHE_PADDING; + last_glyph->rect.w = 0; + return NULL; + } + else + { + // Go to next row + last_glyph->rect.x = FC_CACHE_PADDING; + last_glyph->rect.y += height; + last_glyph->rect.w = 0; + } + } + + // Move to next space + last_glyph->rect.x += last_glyph->rect.w + 1 + FC_CACHE_PADDING; + last_glyph->rect.w = width; + + return FC_MapInsert(glyphs, codepoint, FC_MakeGlyphData(last_glyph->cache_level, last_glyph->rect.x, last_glyph->rect.y, last_glyph->rect.w, last_glyph->rect.h)); +} + + +FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level) +{ + if(font == NULL || cache_level < 0 || cache_level > font->glyph_cache_count) + return NULL; + + return font->glyph_cache[cache_level]; +} + +Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture) +{ + if(font == NULL || cache_level < 0) + return 0; + + // Must be sequentially added + if(cache_level > font->glyph_cache_count + 1) + return 0; + + if(cache_level == font->glyph_cache_count) + { + font->glyph_cache_count++; + + // Grow cache? + if(font->glyph_cache_count > font->glyph_cache_size) + { + // Copy old cache to new one + int i; + FC_Image** new_cache; + new_cache = (FC_Image**)malloc(font->glyph_cache_count * sizeof(FC_Image*)); + for(i = 0; i < font->glyph_cache_size; ++i) + new_cache[i] = font->glyph_cache[i]; + + // Save new cache + free(font->glyph_cache); + font->glyph_cache_size = font->glyph_cache_count; + font->glyph_cache = new_cache; + } + } + + font->glyph_cache[cache_level] = cache_texture; + return 1; +} + + +FC_Font* FC_CreateFont(void) +{ + FC_Font* font; + + font = (FC_Font*)malloc(sizeof(FC_Font)); + memset((void*)font, 0, sizeof(FC_Font)); + + FC_Init(font); + ++NUM_EXISTING_FONTS; + + return font; +} + + +// Assume this many will be enough... +#define FC_LOAD_MAX_SURFACES 10 + +#ifdef FC_USE_SDL_GPU +Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color) +#else +Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color) +#endif +{ + if(font == NULL || ttf == NULL) + return 0; + #ifndef FC_USE_SDL_GPU + if(renderer == NULL) + return 0; + #endif + + FC_ClearFont(font); + std::scoped_lock lock(*font->mutex); + + // Might as well check render target support here + #ifdef FC_USE_SDL_GPU + fc_has_render_target_support = GPU_IsFeatureEnabled(GPU_FEATURE_RENDER_TARGETS); + #else + SDL_RendererInfo info; + SDL_GetRendererInfo(renderer, &info); + fc_has_render_target_support = (info.flags & SDL_RENDERER_TARGETTEXTURE); + + font->renderer = renderer; + #endif + + font->ttf_source = ttf; + + //font->line_height = TTF_FontLineSkip(ttf); + font->height = TTF_FontHeight(ttf); + font->ascent = TTF_FontAscent(ttf); + font->descent = -TTF_FontDescent(ttf); + + // Some bug for certain fonts can result in an incorrect height. + if(font->height < font->ascent - font->descent) + font->height = font->ascent - font->descent; + + font->baseline = font->height - font->descent; + + font->default_color = color; + + { + SDL_Color white = {255, 255, 255, 255}; + SDL_Surface* glyph_surf; + char buff[5]; + const char* buff_ptr = buff; + const char* source_string; + Uint8 packed = 0; + + // Copy glyphs from the surface to the font texture and store the position data + // Pack row by row into a square texture + // Try figuring out dimensions that make sense for the font size. + unsigned int w = font->height*12; + unsigned int h = font->height*12; + SDL_Surface* surfaces[FC_LOAD_MAX_SURFACES]; + int num_surfaces = 1; + surfaces[0] = FC_CreateSurface32(w, h); + font->last_glyph.rect.x = FC_CACHE_PADDING; + font->last_glyph.rect.y = FC_CACHE_PADDING; + font->last_glyph.rect.w = 0; + font->last_glyph.rect.h = font->height; + + source_string = font->loading_string; + for(; *source_string != '\0'; source_string = U8_next(source_string)) + { + memset(buff, 0, 5); + if(!U8_charcpy(buff, source_string, 5)) + continue; + glyph_surf = TTF_RenderUTF8_Blended(ttf, buff, white); + if(glyph_surf == NULL) + continue; + + // Try packing. If it fails, create a new surface for the next cache level. + packed = (FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL); + if(!packed) + { + int i = num_surfaces-1; + if(num_surfaces >= FC_LOAD_MAX_SURFACES) + { + // Can't do any more! + FC_Log("SDL_FontCache error: Could not create enough cache surfaces to fit all of the loading string!\n"); + SDL_FreeSurface(glyph_surf); + break; + } + + // Upload the current surface to the glyph cache now so we can keep the cache level packing cursor up to date as we go. + FC_UploadGlyphCache(font, i, surfaces[i]); + SDL_FreeSurface(surfaces[i]); + #ifndef FC_USE_SDL_GPU + SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND); + #endif + // Update the glyph cursor to the new cache level. We need to do this here because the actual cache lags behind our use of the packing above. + font->last_glyph.cache_level = num_surfaces; + + + surfaces[num_surfaces] = FC_CreateSurface32(w, h); + num_surfaces++; + } + + // Try packing for the new surface, then blit onto it. + if(packed || FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL) + { + SDL_SetSurfaceBlendMode(glyph_surf, SDL_BLENDMODE_NONE); + SDL_Rect srcRect = {0, 0, glyph_surf->w, glyph_surf->h}; + SDL_Rect destrect = font->last_glyph.rect; + SDL_BlitSurface(glyph_surf, &srcRect, surfaces[num_surfaces-1], &destrect); + } + + SDL_FreeSurface(glyph_surf); + } + + { + int i = num_surfaces-1; + FC_UploadGlyphCache(font, i, surfaces[i]); + SDL_FreeSurface(surfaces[i]); + #ifndef FC_USE_SDL_GPU + SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND); + #endif + } + } + + + + return 1; +} + + +#ifdef FC_USE_SDL_GPU +Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style) +#else +Uint8 FC_LoadFont(FC_Font* font, FC_Target* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style) +#endif +{ + SDL_RWops* rwops; + + if(font == NULL) + return 0; + + rwops = SDL_RWFromFile(filename_ttf, "rb"); + + if(rwops == NULL) + { + FC_Log("Unable to open file for reading: %s \n", SDL_GetError()); + return 0; + } + + #ifdef FC_USE_SDL_GPU + return FC_LoadFont_RW(font, rwops, 1, pointSize, color, style); + #else + return FC_LoadFont_RW(font, renderer, rwops, 1, pointSize, color, style); + #endif +} + +#ifdef FC_USE_SDL_GPU +Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style) +#else +Uint8 FC_LoadFont_RW(FC_Font* font, FC_Target* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style) +#endif +{ + Uint8 result; + TTF_Font* ttf; + Uint8 outline; + + if(font == NULL) + return 0; + + if(!TTF_WasInit() && TTF_Init() < 0) + { + FC_Log("Unable to initialize SDL_ttf: %s \n", TTF_GetError()); + if(own_rwops) + SDL_RWclose(file_rwops_ttf); + return 0; + } + + ttf = TTF_OpenFontRW(file_rwops_ttf, own_rwops, pointSize); + + if(ttf == NULL) + { + FC_Log("Unable to load TrueType font: %s \n", TTF_GetError()); + if(own_rwops) + SDL_RWclose(file_rwops_ttf); + return 0; + } + + outline = (style & TTF_STYLE_OUTLINE); + if(outline) + { + style &= ~TTF_STYLE_OUTLINE; + TTF_SetFontOutline(ttf, 1); + } + TTF_SetFontStyle(ttf, style); + + #ifdef FC_USE_SDL_GPU + result = FC_LoadFontFromTTF(font, ttf, color); + #else + result = FC_LoadFontFromTTF(font, renderer, ttf, color); + #endif + + // Can only load new (uncached) glyphs if we can keep the SDL_RWops open. + font->owns_ttf_source = own_rwops; + if(!own_rwops) + { + TTF_CloseFont(font->ttf_source); + font->ttf_source = NULL; + } + + return result; +} + + +#ifndef FC_USE_SDL_GPU +void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType) +{ + TTF_Font* ttf; + SDL_Color col; + Uint8 owns_ttf; + if (font == NULL) + return; + + // Destroy glyph cache + if (evType == SDL_RENDER_TARGETS_RESET) { + int i; + for (i = 0; i < font->glyph_cache_count; ++i) + SDL_DestroyTexture(font->glyph_cache[i]); + } + free(font->glyph_cache); + + ttf = font->ttf_source; + col = font->default_color; + owns_ttf = font->owns_ttf_source; + FC_Init(font); + + // Can only reload glyphs if we own the SDL_RWops. + if (owns_ttf) + FC_LoadFontFromTTF(font, renderer, ttf, col); + font->owns_ttf_source = owns_ttf; +} +#endif + +void FC_ClearFont(FC_Font* font) +{ + int i; + if(font == NULL) + return; + + // Release resources + if(font->owns_ttf_source) + TTF_CloseFont(font->ttf_source); + + font->owns_ttf_source = 0; + font->ttf_source = NULL; + + // Delete glyph map + FC_MapFree(font->glyphs); + font->glyphs = NULL; + + // Delete glyph cache + for(i = 0; i < font->glyph_cache_count; ++i) + { + #ifdef FC_USE_SDL_GPU + GPU_FreeImage(font->glyph_cache[i]); + #else + SDL_DestroyTexture(font->glyph_cache[i]); + #endif + } + free(font->glyph_cache); + font->glyph_cache = NULL; + delete font->mutex; + + font->mutex = nullptr; + + // Reset font + FC_Init(font); +} + + +void FC_FreeFont(FC_Font* font) +{ + int i; + if(font == NULL) + return; + + // Release resources + if(font->owns_ttf_source) + TTF_CloseFont(font->ttf_source); + + // Delete glyph map + FC_MapFree(font->glyphs); + + // Delete glyph cache + for(i = 0; i < font->glyph_cache_count; ++i) + { + #ifdef FC_USE_SDL_GPU + GPU_FreeImage(font->glyph_cache[i]); + #else + SDL_DestroyTexture(font->glyph_cache[i]); + #endif + } + free(font->glyph_cache); + + free(font->loading_string); + + free(font); + + // If the last font has been freed; assume shutdown and free the global variables + if (--NUM_EXISTING_FONTS <= 0) + { + free(ASCII_STRING); + ASCII_STRING = NULL; + + free(LATIN_1_STRING); + LATIN_1_STRING = NULL; + + free(ASCII_LATIN_1_STRING); + ASCII_LATIN_1_STRING = NULL; + + free(fc_buffer); + fc_buffer = NULL; + } +} + +int FC_GetNumCacheLevels(FC_Font* font) +{ + return font->glyph_cache_count; +} + +Uint8 FC_AddGlyphToCache(FC_Font* font, SDL_Surface* glyph_surface) +{ + if(font == NULL || glyph_surface == NULL) + return 0; + + SDL_SetSurfaceBlendMode(glyph_surface, SDL_BLENDMODE_NONE); + FC_Image* dest = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level); + if(dest == NULL) + return 0; + + #ifdef FC_USE_SDL_GPU + { + GPU_Target* target = GPU_LoadTarget(dest); + if(target == NULL) + return 0; + GPU_Image* img = GPU_CopyImageFromSurface(glyph_surface); + GPU_SetAnchor(img, 0.5f, 0.5f); // Just in case the default is different + GPU_SetImageFilter(img, GPU_FILTER_NEAREST); + GPU_SetBlendMode(img, GPU_BLEND_SET); + + SDL_Rect destrect = font->last_glyph.rect; + GPU_Blit(img, NULL, target, destrect.x + destrect.w/2, destrect.y + destrect.h/2); + + GPU_FreeImage(img); + GPU_FreeTarget(target); + } + #else + { + SDL_Renderer* renderer = font->renderer; + SDL_Texture* img; + SDL_Rect destrect; + SDL_Texture* prev_target = SDL_GetRenderTarget(renderer); + SDL_Rect prev_clip, prev_viewport; + int prev_logicalw, prev_logicalh; + Uint8 prev_clip_enabled; + float prev_scalex, prev_scaley; + // only backup if previous target existed (SDL will preserve them for the default target) + if (prev_target) { + prev_clip_enabled = has_clip(renderer); + if (prev_clip_enabled) + prev_clip = get_clip(renderer); + SDL_RenderGetViewport(renderer, &prev_viewport); + SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); + SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); + } + + img = SDL_CreateTextureFromSurface(renderer, glyph_surface); + + destrect = font->last_glyph.rect; + SDL_SetRenderTarget(renderer, dest); + SDL_RenderCopy(renderer, img, NULL, &destrect); + SDL_SetRenderTarget(renderer, prev_target); + if (prev_target) { + if (prev_clip_enabled) + set_clip(renderer, &prev_clip); + if (prev_logicalw && prev_logicalh) + SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); + else { + SDL_RenderSetViewport(renderer, &prev_viewport); + SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); + } + } + + SDL_DestroyTexture(img); + } + #endif + + return 1; +} + + +unsigned int FC_GetNumCodepoints(FC_Font* font) +{ + FC_Map* glyphs; + int i; + unsigned int result = 0; + if(font == NULL || font->glyphs == NULL) + return 0; + + glyphs = font->glyphs; + + for(i = 0; i < glyphs->num_buckets; ++i) + { + FC_MapNode* node; + for(node = glyphs->buckets[i]; node != NULL; node = node->next) + { + result++; + } + } + + return result; +} + +void FC_GetCodepoints(FC_Font* font, Uint32* result) +{ + FC_Map* glyphs; + int i; + unsigned int count = 0; + if(font == NULL || font->glyphs == NULL) + return; + + glyphs = font->glyphs; + + for(i = 0; i < glyphs->num_buckets; ++i) + { + FC_MapNode* node; + for(node = glyphs->buckets[i]; node != NULL; node = node->next) + { + result[count] = node->key; + count++; + } + } +} + +Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint) +{ + FC_GlyphData* e = FC_MapFind(font->glyphs, codepoint); + if(e == NULL) + { + char buff[5]; + int w, h; + SDL_Color white = {255, 255, 255, 255}; + SDL_Surface* surf; + FC_Image* cache_image; + + if(font->ttf_source == NULL) + return 0; + + FC_GetUTF8FromCodepoint(buff, codepoint); + + cache_image = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level); + if(cache_image == NULL) + { + FC_Log("SDL_FontCache: Failed to load cache image, so cannot add new glyphs!\n"); + return 0; + } + + #ifdef FC_USE_SDL_GPU + w = cache_image->w; + h = cache_image->h; + #else + SDL_QueryTexture(cache_image, NULL, NULL, &w, &h); + #endif + + surf = TTF_RenderUTF8_Blended(font->ttf_source, buff, white); + if(surf == NULL) + { + return 0; + } + + e = FC_PackGlyphData(font, codepoint, surf->w, w, h); + if(e == NULL) + { + // Grow the cache + FC_GrowGlyphCache(font); + + // Try packing again + e = FC_PackGlyphData(font, codepoint, surf->w, w, h); + if(e == NULL) + { + SDL_FreeSurface(surf); + return 0; + } + } + + // Render onto the cache texture + FC_AddGlyphToCache(font, surf); + + SDL_FreeSurface(surf); + } + + if(result != NULL && e != NULL) + *result = *e; + + return 1; +} + + +FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data) +{ + return FC_MapInsert(font->glyphs, codepoint, glyph_data); +} + + + +// Drawing +static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) +{ + const char* c = text; + FC_Rect srcRect; + FC_Rect dstRect; + FC_Rect dirtyRect = FC_MakeRect(x, y, 0, 0); + + FC_GlyphData glyph; + Uint32 codepoint; + + float destX = x; + float destY = y; + float destH; + float destLineSpacing; + float destLetterSpacing; + + if(font == NULL) + return dirtyRect; + + destH = font->height * scale.y; + destLineSpacing = font->lineSpacing*scale.y; + destLetterSpacing = font->letterSpacing*scale.x; + + if(c == NULL || font->glyph_cache_count == 0 || dest == NULL) + return dirtyRect; + + int newlineX = x; + + for(; *c != '\0'; c++) + { + if(*c == '\n') + { + destX = newlineX; + destY += destH + destLineSpacing; + continue; + } + + codepoint = FC_GetCodepointFromUTF8(&c, 1); // Increments 'c' to skip the extra UTF-8 bytes + if(!FC_GetGlyphData(font, &glyph, codepoint)) + { + codepoint = ' '; + if(!FC_GetGlyphData(font, &glyph, codepoint)) + continue; // Skip bad characters + } + + if (codepoint == ' ') + { + destX += glyph.rect.w*scale.x + destLetterSpacing; + continue; + } + /*if(destX >= dest->w) + continue; + if(destY >= dest->h) + continue;*/ + + #ifdef FC_USE_SDL_GPU + srcRect.x = glyph.rect.x; + srcRect.y = glyph.rect.y; + srcRect.w = glyph.rect.w; + srcRect.h = glyph.rect.h; + #else + srcRect = glyph.rect; + #endif + dstRect = fc_render_callback(FC_GetGlyphCacheLevel(font, glyph.cache_level), &srcRect, dest, destX, destY, scale.x, scale.y); + if(dirtyRect.w == 0 || dirtyRect.h == 0) + dirtyRect = dstRect; + else + dirtyRect = FC_RectUnion(dirtyRect, dstRect); + + destX += glyph.rect.w*scale.x + destLetterSpacing; + } + + return dirtyRect; +} + +static void set_color_for_all_caches(FC_Font* font, SDL_Color color) +{ + // TODO: How can I predict which glyph caches are to be used? + FC_Image* img; + int i; + int num_levels = FC_GetNumCacheLevels(font); + for(i = 0; i < num_levels; ++i) + { + img = FC_GetGlyphCacheLevel(font, i); + set_color(img, color.r, color.g, color.b, FC_GET_ALPHA(color)); + } +} + +FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + set_color_for_all_caches(font, font->default_color); + + auto res = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + + return res; +} + +typedef struct FC_StringList +{ + char* value; + struct FC_StringList* next; +} FC_StringList; + +void FC_StringListFree(FC_StringList* node) +{ + // Delete the nodes in order + while(node != NULL) + { + FC_StringList* last = node; + node = node->next; + + free(last->value); + free(last); + } +} + +FC_StringList** FC_StringListPushBack(FC_StringList** node, char* value, Uint8 copy) +{ + if(node == NULL) + { + return NULL; + } + + // Get to the last node + while(*node != NULL) + { + node = &(*node)->next; + } + + *node = (FC_StringList*)malloc(sizeof(FC_StringList)); + + (*node)->value = (copy? U8_strdup(value) : value); + (*node)->next = NULL; + + return node; +} + +FC_StringList** FC_StringListPushBackBytes(FC_StringList** node, const char* data, int num_bytes) +{ + if(node == NULL) + { + return node; + } + + // Get to the last node + while(*node != NULL) + { + node = &(*node)->next; + } + + *node = (FC_StringList*)malloc(sizeof(FC_StringList)); + + (*node)->value = (char*)malloc(num_bytes + 1); + memcpy((*node)->value, data, num_bytes); + (*node)->value[num_bytes] = '\0'; + (*node)->next = NULL; + + return node; +} + +static FC_StringList* FC_Explode(const char* text, char delimiter) +{ + FC_StringList* head; + FC_StringList* new_node; + FC_StringList** node; + const char* start; + const char* end; + unsigned int size; + if(text == NULL) + return NULL; + + head = NULL; + node = &head; + + // Doesn't technically support UTF-8, but it's probably fine, right? + size = 0; + start = end = text; + while(1) + { + if(*end == delimiter || *end == '\0') + { + *node = (FC_StringList*)malloc(sizeof(FC_StringList)); + new_node = *node; + + new_node->value = (char*)malloc(size + 1); + memcpy(new_node->value, start, size); + new_node->value[size] = '\0'; + + new_node->next = NULL; + + if(*end == '\0') + break; + + node = &((*node)->next); + start = end+1; + size = 0; + } + else + ++size; + + ++end; + } + + return head; +} + +static FC_StringList* FC_ExplodeBreakingSpace(const char* text, FC_StringList** spaces) +{ + FC_StringList* head; + FC_StringList** node; + const char* start; + const char* end; + unsigned int size; + if(text == NULL) + return NULL; + + head = NULL; + node = &head; + + // Warning: spaces must not be initialized before this function + *spaces = NULL; + + // Doesn't technically support UTF-8, but it's probably fine, right? + size = 0; + start = end = text; + while(1) + { + // Add any characters here that should make separate words (except for \n?) + if(*end == ' ' || *end == '\t' || *end == '\0') + { + FC_StringListPushBackBytes(node, start, size); + FC_StringListPushBackBytes(spaces, end, 1); + + if(*end == '\0') + break; + + node = &((*node)->next); + start = end+1; + size = 0; + } + else + ++size; + + ++end; + } + + return head; +} + +static FC_StringList* FC_ExplodeAndKeep(const char* text, char delimiter) +{ + FC_StringList* head; + FC_StringList** node; + const char* start; + const char* end; + unsigned int size; + if(text == NULL) + return NULL; + + head = NULL; + node = &head; + + // Doesn't technically support UTF-8, but it's probably fine, right? + size = 0; + start = end = text; + while(1) + { + if(*end == delimiter || *end == '\0') + { + FC_StringListPushBackBytes(node, start, size); + + if(*end == '\0') + break; + + node = &((*node)->next); + start = end; + size = 1; + } + else + ++size; + + ++end; + } + + return head; +} + +static void FC_RenderAlign(FC_Font* font, FC_Target* dest, float x, float y, int width, FC_Scale scale, FC_AlignEnum align, const char* text) +{ + switch(align) + { + case FC_ALIGN_LEFT: + FC_RenderLeft(font, dest, x, y, scale, text); + break; + case FC_ALIGN_CENTER: + FC_RenderCenter(font, dest, x + width/2, y, scale, text); + break; + case FC_ALIGN_RIGHT: + FC_RenderRight(font, dest, x + width, y, scale, text); + break; + } +} + +static FC_StringList* FC_GetBufferFitToColumn(FC_Font* font, int width, FC_Scale scale, Uint8 keep_newlines) +{ + FC_StringList* result = NULL; + FC_StringList** current = &result; + + FC_StringList *ls, *iter; + + ls = (keep_newlines? FC_ExplodeAndKeep(fc_buffer, '\n') : FC_Explode(fc_buffer, '\n')); + for(iter = ls; iter != NULL; iter = iter->next) + { + char* line = iter->value; + + // If line is too long, then add words one at a time until we go over. + if(width > 0 && FC_GetWidth(font, "%s", line) > width) + { + FC_StringList *words, *word_iter, *spaces, *spaces_iter; + + words = FC_ExplodeBreakingSpace(line, &spaces); + // Skip the first word for the iterator, so there will always be at least one word per line + line = new_concat(words->value, spaces->value); + for(word_iter = words->next, spaces_iter = spaces->next; word_iter != NULL && spaces_iter != NULL; word_iter = word_iter->next, spaces_iter = spaces_iter->next) + { + char* line_plus_word = new_concat(line, word_iter->value); + char* word_plus_space = new_concat(word_iter->value, spaces_iter->value); + if(FC_GetWidth(font, "%s", line_plus_word) > width) + { + current = FC_StringListPushBack(current, line, 0); + + line = word_plus_space; + } + else + { + replace_concat(&line, word_plus_space); + free(word_plus_space); + } + free(line_plus_word); + } + current = FC_StringListPushBack(current, line, 0); + FC_StringListFree(words); + FC_StringListFree(spaces); + } + else + { + current = FC_StringListPushBack(current, line, 0); + iter->value = NULL; + } + } + FC_StringListFree(ls); + + return result; +} + +static void FC_DrawColumnFromBuffer(FC_Font* font, FC_Target* dest, FC_Rect box, int* total_height, FC_Scale scale, FC_AlignEnum align) +{ + int y = box.y; + FC_StringList *ls, *iter; + + ls = FC_GetBufferFitToColumn(font, box.w, scale, 0); + int8_t onFirst = 1; + for(iter = ls; iter != NULL; iter = iter->next) + { + FC_RenderAlign(font, dest, box.x, y, box.w, scale, onFirst ? FC_ALIGN_LEFT : align, iter->value); + y += FC_GetLineHeight(font); + onFirst = 0; + } + FC_StringListFree(ls); + + if(total_height != NULL) + *total_height = y - box.y; +} + +FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + + set_clip(dest, &newclip); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + set_clip(dest, &newclip); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), align); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + set_clip(dest, &newclip); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, scale, FC_ALIGN_LEFT); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + set_clip(dest, &newclip); + + set_color_for_all_caches(font, color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + set_clip(dest, &newclip); + + set_color_for_all_caches(font, effect.color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, effect.scale, effect.alignment); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...) +{ + FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + return res; +} + +FC_Rect FC_DrawColumnToTexture(FC_Font* font, SDL_Texture* target, float x, float y, Uint16 width, const char* text) +{ + std::scoped_lock lock(*font->mutex); + + // Draw the text onto it + SDL_SetRenderTarget(font->renderer, target); + // make sure the texture is clean. + SDL_RenderClear(font->renderer); + FC_Rect res = FC_DrawColumn(font, font->renderer, x, y, width, text); + SDL_SetRenderTarget(font->renderer, NULL); + + return res; +} + +FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...) +{ + FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + switch(align) + { + case FC_ALIGN_CENTER: + box.x -= width/2; + break; + case FC_ALIGN_RIGHT: + box.x -= width; + break; + default: + break; + } + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), align); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + return res; +} + +FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...) +{ + FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, scale, FC_ALIGN_CENTER); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + + + return res; +} + +FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...) +{ + FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, color); + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + + + return res; +} + +FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...) +{ + FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, effect.color); + + switch(effect.alignment) + { + case FC_ALIGN_CENTER: + box.x -= width/2; + break; + case FC_ALIGN_RIGHT: + box.x -= width; + break; + default: + break; + } + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, effect.scale, effect.alignment); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + + + return res; +} + +static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) +{ + FC_Rect result = {(int) x, (int) y, (int) 0, (int) 0}; + if(text == NULL || font == NULL) + return result; + + char* str = U8_strdup(text); + char* del = str; + char* c; + + // Go through str, when you find a \n, replace it with \0 and print it + // then move down, back, and continue. + for(c = str; *c != '\0';) + { + if(*c == '\n') + { + *c = '\0'; + result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result); + *c = '\n'; + c++; + str = c; + y += scale.y*font->height; + } + else + c++; + } + + result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result); + + free(del); + return result; +} + +static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) +{ + FC_Rect result = {(int) x, (int) y, (int) 0, (int) 0}; + if(text == NULL || font == NULL) + return result; + + char* str = U8_strdup(text); + char* del = str; + char* c; + + for(c = str; *c != '\0';) + { + if(*c == '\n') + { + *c = '\0'; + result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result); + *c = '\n'; + c++; + str = c; + y += scale.y*font->height; + } + else + c++; + } + + result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result); + + free(del); + return result; +} + + + +FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + auto res = FC_RenderLeft(font, dest, x, y, scale, fc_buffer); + + + + return res; +} + +FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + FC_Rect result; + switch(align) + { + case FC_ALIGN_LEFT: + result = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + break; + case FC_ALIGN_CENTER: + result = FC_RenderCenter(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + break; + case FC_ALIGN_RIGHT: + result = FC_RenderRight(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + break; + default: + result = FC_MakeRect(x, y, 0, 0); + break; + } + + + + return result; +} + +FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, color); + + auto res = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + + + + return res; +} + + +FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, effect.color); + + FC_Rect result; + switch(effect.alignment) + { + case FC_ALIGN_LEFT: + result = FC_RenderLeft(font, dest, x, y, effect.scale, fc_buffer); + break; + case FC_ALIGN_CENTER: + result = FC_RenderCenter(font, dest, x, y, effect.scale, fc_buffer); + break; + case FC_ALIGN_RIGHT: + result = FC_RenderRight(font, dest, x, y, effect.scale, fc_buffer); + break; + default: + result = FC_MakeRect(x, y, 0, 0); + break; + } + + + + return result; +} + + + + +// Getters + + +FC_FilterEnum FC_GetFilterMode(FC_Font* font) +{ + if(font == NULL) + return FC_FILTER_NEAREST; + + return font->filter; +} + +Uint16 FC_GetLineHeight(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->height; +} + +Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return 0; + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + Uint16 numLines = 1; + const char* c; + + for (c = fc_buffer; *c != '\0'; c++) + { + if(*c == '\n') + numLines++; + } + + // Actual height of letter region + line spacing + auto res = font->height*numLines + font->lineSpacing*(numLines - 1); //height*numLines; + + + + return res; +} + +Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return 0; + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + const char* c; + Uint16 width = 0; + Uint16 bigWidth = 0; // Allows for multi-line strings + + for (c = fc_buffer; *c != '\0'; c++) + { + if(*c == '\n') + { + bigWidth = bigWidth >= width? bigWidth : width; + width = 0; + continue; + } + + FC_GlyphData glyph; + Uint32 codepoint = FC_GetCodepointFromUTF8(&c, 1); + if(FC_GetGlyphData(font, &glyph, codepoint) || FC_GetGlyphData(font, &glyph, ' ')) + width += glyph.rect.w; + } + bigWidth = bigWidth >= width? bigWidth : width; + + + + return bigWidth; +} + +// If width == -1, use no width limit +FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...) +{ + FC_Rect result = {0, 0, 1, FC_GetLineHeight(font)}; + FC_StringList *ls, *iter; + int num_lines = 0; + Uint8 done = 0; + + if(formatted_text == NULL || column_width == 0 || position_index == 0 || font == NULL) + return result; + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1); + for(iter = ls; iter != NULL;) + { + char* line; + int i = 0; + FC_StringList* next_iter = iter->next; + + ++num_lines; + for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line)) + { + ++i; + --position_index; + if(position_index == 0) + { + // FIXME: Doesn't handle box-wrapped newlines correctly + line = (char*)U8_next(line); + line[0] = '\0'; + result.x = FC_GetWidth(font, "%s", iter->value); + done = 1; + break; + } + } + if(done) + break; + + // Prevent line wrapping if there are no more lines + if(next_iter == NULL && !done) + result.x = FC_GetWidth(font, "%s", iter->value); + iter = next_iter; + } + FC_StringListFree(ls); + + if(num_lines > 1) + { + result.y = (num_lines - 1) * FC_GetLineHeight(font); + } + + + + return result; +} + + +Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...) +{ + int y = 0; + + FC_StringList *ls, *iter; + + if(font == NULL) + return 0; + + if(formatted_text == NULL || width == 0) + return font->height; + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); + for(iter = ls; iter != NULL; iter = iter->next) + { + y += FC_GetLineHeight(font); + } + FC_StringListFree(ls); + + + + return y; +} + +static int FC_GetAscentFromCodepoint(FC_Font* font, Uint32 codepoint) +{ + FC_GlyphData glyph; + + if(font == NULL) + return 0; + + // FIXME: Store ascent so we can return it here + FC_GetGlyphData(font, &glyph, codepoint); + return glyph.rect.h; +} + +static int FC_GetDescentFromCodepoint(FC_Font* font, Uint32 codepoint) +{ + FC_GlyphData glyph; + + if(font == NULL) + return 0; + + // FIXME: Store descent so we can return it here + FC_GetGlyphData(font, &glyph, codepoint); + return glyph.rect.h; +} + +int FC_GetAscent(FC_Font* font, const char* formatted_text, ...) +{ + Uint32 codepoint; + int max, ascent; + const char* c; + + if(font == NULL) + return 0; + + if(formatted_text == NULL) + return font->ascent; + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + max = 0; + c = fc_buffer; + + while(*c != '\0') + { + codepoint = FC_GetCodepointFromUTF8(&c, 1); + if(codepoint != 0) + { + ascent = FC_GetAscentFromCodepoint(font, codepoint); + if(ascent > max) + max = ascent; + } + ++c; + } + + + + return max; +} + +int FC_GetDescent(FC_Font* font, const char* formatted_text, ...) +{ + Uint32 codepoint; + int max, descent; + const char* c; + + if(font == NULL) + return 0; + + if(formatted_text == NULL) + return font->descent; + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + max = 0; + c = fc_buffer; + + while(*c != '\0') + { + codepoint = FC_GetCodepointFromUTF8(&c, 1); + if(codepoint != 0) + { + descent = FC_GetDescentFromCodepoint(font, codepoint); + if(descent > max) + max = descent; + } + ++c; + } + + + + return max; +} + +int FC_GetBaseline(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->baseline; +} + +int FC_GetSpacing(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->letterSpacing; +} + +int FC_GetLineSpacing(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->lineSpacing; +} + +Uint16 FC_GetMaxWidth(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->maxWidth; +} + +SDL_Color FC_GetDefaultColor(FC_Font* font) +{ + if(font == NULL) + { + SDL_Color c = {0,0,0,255}; + return c; + } + + return font->default_color; +} + +FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...) +{ + FC_Rect result = {(int) x, (int) y, (int) 0, (int) 0}; + + if(formatted_text == NULL) + return result; + + // Create a temp buffer while GetWidth and GetHeight use fc_buffer. + char* temp = (char*)malloc(fc_buffer_size); + FC_EXTRACT_VARARGS(temp, formatted_text); + + result.w = FC_GetWidth(font, "%s", temp) * scale.x; + result.h = FC_GetHeight(font, "%s", temp) * scale.y; + + switch(align) + { + case FC_ALIGN_LEFT: + break; + case FC_ALIGN_CENTER: + result.x -= result.w/2; + break; + case FC_ALIGN_RIGHT: + result.x -= result.w; + break; + default: + break; + } + + free(temp); + + return result; +} + +Uint8 FC_InRect(float x, float y, FC_Rect input_rect) +{ + return (input_rect.x <= x && x <= input_rect.x + input_rect.w && input_rect.y <= y && y <= input_rect.y + input_rect.h); +} + +// TODO: Make it work with alignment +Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...) +{ + FC_StringList *ls, *iter; + Uint8 done = 0; + int height = FC_GetLineHeight(font); + Uint16 position = 0; + int current_x = 0; + int current_y = 0; + FC_GlyphData glyph_data; + + if(formatted_text == NULL || column_width == 0 || font == NULL) + return 0; + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1); + for(iter = ls; iter != NULL; iter = iter->next) + { + char* line; + + for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line)) + { + if(FC_GetGlyphData(font, &glyph_data, FC_GetCodepointFromUTF8((const char**)&line, 0))) + { + if(FC_InRect(x, y, FC_MakeRect(current_x, current_y, glyph_data.rect.w, glyph_data.rect.h))) + { + done = 1; + break; + } + + current_x += glyph_data.rect.w; + } + position++; + } + if(done) + break; + + current_x = 0; + current_y += height; + if(y < current_y) + break; + } + FC_StringListFree(ls); + + + + return position; +} + +int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...) +{ + FC_StringList *ls, *iter; + + if(font == NULL) + return 0; + + if(formatted_text == NULL || width == 0) + return 0; + + std::scoped_lock lock(*font->mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); + int size_so_far = 0; + int size_remaining = max_result_size-1; // reserve for \0 + for(iter = ls; iter != NULL && size_remaining > 0; iter = iter->next) + { + // Copy as much of this line as we can + int len = strlen(iter->value); + int num_bytes = FC_MIN(len, size_remaining); + memcpy(&result[size_so_far], iter->value, num_bytes); + size_so_far += num_bytes; + + // If there's another line, add newline character + if(size_remaining > 0 && iter->next != NULL) + { + --size_remaining; + result[size_so_far] = '\n'; + ++size_so_far; + } + } + FC_StringListFree(ls); + + result[size_so_far] = '\0'; + + + + return size_so_far; +} + + + +// Setters +void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter) +{ + if(font == NULL) + return; + + if(font->filter != filter) + { + font->filter = filter; + + #ifdef FC_USE_SDL_GPU + // Update each texture to use this filter mode + { + int i; + GPU_FilterEnum gpu_filter = GPU_FILTER_NEAREST; + if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) + gpu_filter = GPU_FILTER_LINEAR; + + for(i = 0; i < font->glyph_cache_count; ++i) + { + GPU_SetImageFilter(font->glyph_cache[i], gpu_filter); + } + } + #endif + } +} + + +void FC_SetSpacing(FC_Font* font, int LetterSpacing) +{ + if(font == NULL) + return; + + font->letterSpacing = LetterSpacing; +} + +void FC_SetLineSpacing(FC_Font* font, int LineSpacing) +{ + if(font == NULL) + return; + + font->lineSpacing = LineSpacing; +} + +void FC_SetDefaultColor(FC_Font* font, SDL_Color color) +{ + if(font == NULL) + return; + + font->default_color = color; +}