From 2914f2c8e523ae427879a3aec4595f23c5aaaf36 Mon Sep 17 00:00:00 2001 From: tobid7 Date: Wed, 29 Jan 2025 03:14:29 +0100 Subject: [PATCH] # Stage 1.7 - Added File to Memory and FastHashMomory - Add Protection that only one app can exist - Add a Trace exist Variable as GetTraceRef automatically creates a trace - Outsource the LI::Rect to its own header - Add a CurrentScreen func - Use Rect for uv (to manually set all corners) - Rect still supports to use vec4 for uv - Add tex3ds Spritesheet support - Add T3X Loader to Texture (if single tex) - Integrate an autounload into Texture as in case of spritesheet the Tex needs to be unloaded manually - Safe some performance in texture loading by combining the Loops (best thing ive ever found) - Use the Momory Hash to only render one error icon into the TTF Texture - Also Try loading the whole 16-Bit range - Use GPU_A8 format for TTF rendering to save 24Bits per pixel and use the same Rendermode as System Font - Simplify Quad Command by using modern vec api - Integrate Text aligning - Fix FPS displayed twice in Performance overlay - UI7 DrawList now has its own AST system - TODO: do the same layering for the objects as Text uses - Map Drawcommands with a bool that declares either bottom or top screen was active - Add first basic Manu functions - Fix Typos in Theme - Add a basic UI7 Context Handler ## Extra - Added JetBrainsMono font in Test app ## Bugs: - Performance Overlay Freezes 3ds hardware and crashes Citra with Vulkan when System Font is used - UI7 Menu scrolling is as scruffed as back in RenderD7 0.9.5 --- CMakeLists.txt | 3 + include/pd.hpp | 8 + include/pd/common/app.hpp | 16 +- include/pd/common/io.hpp | 10 + include/pd/common/sys.hpp | 1 + include/pd/graphics/lithium.hpp | 39 +-- include/pd/graphics/rect.hpp | 82 ++++++ include/pd/graphics/spritesheet.hpp | 26 ++ include/pd/graphics/texture.hpp | 33 ++- include/pd/overlays/message_mgr.hpp | 20 +- include/pd/ui7/drawlist.hpp | 17 +- include/pd/ui7/id.hpp | 12 +- include/pd/ui7/menu.hpp | 81 +++++- include/pd/ui7/theme.hpp | 20 +- include/pd/ui7/ui7.hpp | 29 +- source/common/app.cpp | 5 + source/common/io.cpp | 26 ++ source/common/sys.cpp | 3 + source/graphics/lithium.cpp | 127 +++++---- source/graphics/spritesheet.cpp | 44 +++ source/graphics/texture.cpp | 71 ++--- source/overlays/keyboard.cpp | 2 - source/overlays/message_mgr.cpp | 2 - source/overlays/performance.cpp | 3 +- source/ui7/drawlist.cpp | 71 ++++- source/ui7/menu.cpp | 331 ++++++++++++++++++++++ source/ui7/theme.cpp | 4 +- source/ui7/ui7.cpp | 52 ++++ test/main.cpp | 85 ++++-- test/romfs/fonts/ComicNeue.md | 93 ++++++ test/romfs/{ => fonts}/ComicNeue.ttf | Bin test/romfs/fonts/JetBrainsMono-Medium.ttf | Bin 0 -> 114920 bytes test/romfs/fonts/JetBrainsMono.txt | 93 ++++++ 33 files changed, 1198 insertions(+), 211 deletions(-) create mode 100644 include/pd/common/io.hpp create mode 100644 include/pd/graphics/rect.hpp create mode 100644 include/pd/graphics/spritesheet.hpp create mode 100644 source/common/io.cpp create mode 100644 source/graphics/spritesheet.cpp create mode 100644 source/ui7/menu.cpp create mode 100644 test/romfs/fonts/ComicNeue.md rename test/romfs/{ => fonts}/ComicNeue.ttf (100%) create mode 100644 test/romfs/fonts/JetBrainsMono-Medium.ttf create mode 100644 test/romfs/fonts/JetBrainsMono.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 175480e..b4477e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ set(SRC_FILES source/common/sys.cpp source/common/lang.cpp source/common/error.cpp + source/common/io.cpp # Controls source/controls/hid.cpp # Maths @@ -40,6 +41,7 @@ set(SRC_FILES source/maths/img_convert.cpp # Graphics source/graphics/texture.cpp + source/graphics/spritesheet.cpp source/graphics/li7_shader.cpp source/graphics/lithium.cpp # Overlays @@ -51,6 +53,7 @@ set(SRC_FILES source/tools/gamepad_icons.cpp # UI7 source/ui7/drawlist.cpp + source/ui7/menu.cpp source/ui7/theme.cpp source/ui7/ui7.cpp # External diff --git a/include/pd.hpp b/include/pd.hpp index db68839..d5d2f2b 100644 --- a/include/pd.hpp +++ b/include/pd.hpp @@ -32,6 +32,7 @@ SOFTWARE. #include // Graphics #include +#include #include // Maths #include @@ -46,4 +47,11 @@ SOFTWARE. // UI7 #include +/// Setup these as non Namespaced access by default +#ifndef PD_MATH_NAMESPACED +using vec2 = PD::vec2; +using vec3 = PD::vec3; +using vec4 = PD::vec4; +#endif + // namespace Palladium = PD; \ No newline at end of file diff --git a/include/pd/common/app.hpp b/include/pd/common/app.hpp index f883999..f50f841 100644 --- a/include/pd/common/app.hpp +++ b/include/pd/common/app.hpp @@ -32,9 +32,14 @@ SOFTWARE. namespace PD { /// @brief Template Class for User Application -class App : public SmartCtor { +class App { public: - App() = default; + App() { + if (too) { + Error("Only one App can be created at the same time!"); + } + too++; + } ~App() = default; /// @brief Templete function where the user can Init his stuff @@ -50,8 +55,8 @@ class App : public SmartCtor { /// @brief Function to run the App /// (int main() { - /// auto app = PD::New(); - /// app->Run(); + /// UserApp app; + /// app.Run(); /// return 0; /// }) void Run(); @@ -71,5 +76,8 @@ class App : public SmartCtor { u64 last_time; float app_time; float fps; + + /// The Only One + static int too; }; } // namespace PD \ No newline at end of file diff --git a/include/pd/common/io.hpp b/include/pd/common/io.hpp new file mode 100644 index 0000000..3cdb208 --- /dev/null +++ b/include/pd/common/io.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace PD { +namespace IO { +std::vector LoadFile2Mem(const std::string& path); +u32 HashMemory(const std::vector& data); +} // namespace IO +} // namespace PD \ No newline at end of file diff --git a/include/pd/common/sys.hpp b/include/pd/common/sys.hpp index 59c5956..15db41f 100644 --- a/include/pd/common/sys.hpp +++ b/include/pd/common/sys.hpp @@ -32,6 +32,7 @@ using TraceMap = std::map; u64 GetTime(); u64 GetNanoTime(); TT::Res::Ref& GetTraceRef(const std::string& id); +bool TraceExist(const std::string& id); TraceMap& GetTraceMap(); } // namespace Sys } // namespace PD diff --git a/include/pd/graphics/lithium.hpp b/include/pd/graphics/lithium.hpp index d3f2a00..8747868 100644 --- a/include/pd/graphics/lithium.hpp +++ b/include/pd/graphics/lithium.hpp @@ -26,6 +26,7 @@ SOFTWARE. #include #include +#include #include #include #include @@ -46,32 +47,6 @@ enum LITextFlags_ { namespace PD { namespace LI { -/// @brief Container that holds top and bottom corners of a quad -class Rect { - public: - Rect() = default; - Rect(const vec4& t, const vec4& b) { - top = t; - bot = b; - } - Rect(const vec2& tl, const vec2& tr, const vec2& bl, const vec2& br) { - top = vec4(tl, tr); - bot = vec4(bl, br); - } - ~Rect() = default; - - vec4 Top() const { return top; } - vec4 Bot() const { return bot; } - - vec2 TopLeft() const { return vec2(top[0], top[1]); } - vec2 TopRight() const { return vec2(top[2], top[3]); } - vec2 BotLeft() const { return vec2(bot[0], bot[1]); } - vec2 BotRight() const { return vec2(bot[2], bot[3]); } - - private: - vec4 top; - vec4 bot; -}; class Font : public SmartCtor { public: class Codepoint { @@ -172,7 +147,7 @@ class Vertex { /// @brief Required to Set the TexENV enum RenderMode { RenderMode_RGBA, - RenderMode_SysFont, + RenderMode_Font, }; /// @brief Reform the Drawcommand by generating the Vertexbuffer into it class Command : public SmartCtor { @@ -362,6 +337,7 @@ class Renderer : public SmartCtor { void SetColor(u32 col); void SetPos(const vec2& pos); + void SetLayer(int l); void SetUnused() { used = false; } bool Used() const { return used; } @@ -397,6 +373,10 @@ class Renderer : public SmartCtor { area_size = bottom ? bot->GetSize() : top->GetSize(); } + Screen::Screen_ CurrentScreen() const { + return bottom ? Screen::Bottom : Screen::Top; + } + void Rotation(float v) { rot = v; } float Rotation() const { return rot; } void TextScale(float v) { text_size = v; } @@ -404,6 +384,7 @@ class Renderer : public SmartCtor { float TextScale() const { return text_size; } void Layer(int v) { current_layer = v; } int Layer() const { return current_layer; } + RenderFlags& GetFlags() { return flags; } void Font(Font::Ref v) { font = v; font_update = true; @@ -424,7 +405,7 @@ class Renderer : public SmartCtor { /// @param color Color /// @param uv UV Map void DrawRect(const vec2& pos, const vec2& size, u32 color, - const vec4& uv = vec4(0.f, 1.f, 1.f, 0.f)); + const Rect& uv = vec4(0.f, 1.f, 1.f, 0.f)); /// @brief Draw a Solid Rect (uses white tex) /// @note acts as a simplified Draw rect Wrapper /// @param pos Position @@ -488,7 +469,7 @@ class Renderer : public SmartCtor { /// @brief Automatically sets up a command void SetupCommand(Command::Ref cmd); /// @brief Creates a default Quad Render Command - void QuadCommand(Command::Ref cmd, const Rect& quad, const vec4& uv, u32 col); + void QuadCommand(Command::Ref cmd, const Rect& quad, const Rect& uv, u32 col); /// @brief Create a Default Triangle void TriangleCommand(Command::Ref cmd, const vec2& a, const vec2& b, const vec2& c, u32 col); diff --git a/include/pd/graphics/rect.hpp b/include/pd/graphics/rect.hpp new file mode 100644 index 0000000..0467e2f --- /dev/null +++ b/include/pd/graphics/rect.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +namespace PD { +namespace LI { +/// @brief Container that holds top and bottom corners of a quad +class Rect { + public: + Rect() = default; + Rect(const vec4& t, const vec4& b) { + top = t; + bot = b; + } + Rect(const vec2& tl, const vec2& tr, const vec2& bl, const vec2& br) { + top = vec4(tl, tr); + bot = vec4(bl, br); + } + /// This Constructor Fixes the issue of rewriting some Stuff in the Text + /// Renderer + Rect(const vec4& uv) { + top = vec4(uv.x(), uv.y(), uv.z(), uv.y()); + bot = vec4(uv.x(), uv.w(), uv.z(), uv.w()); + } + ~Rect() = default; + + vec4 Top() const { return top; } + vec4 Bot() const { return bot; } + + Rect& Top(const vec4& v) { + top = v; + return *this; + } + Rect& Bot(const vec4& v) { + bot = v; + return *this; + } + + vec2 TopLeft() const { return vec2(top[0], top[1]); } + vec2 TopRight() const { return vec2(top[2], top[3]); } + vec2 BotLeft() const { return vec2(bot[0], bot[1]); } + vec2 BotRight() const { return vec2(bot[2], bot[3]); } + + Rect& TopLeft(const vec2& v) { + top[0] = v[0]; + top[1] = v[1]; + return *this; + } + Rect& TopRight(const vec2& v) { + top[2] = v[0]; + top[3] = v[1]; + return *this; + } + Rect& BotLeft(const vec2& v) { + bot[0] = v[0]; + bot[1] = v[1]; + return *this; + } + Rect& BotRight(const vec2& v) { + bot[2] = v[0]; + bot[3] = v[1]; + return *this; + } + + void SwapVec2XY() { + for (int i = 0; i < 4; i += 2) { + float t = top[i]; + top[i] = top[i + 1]; + top[i + 1] = t; + t = bot[i]; + bot[i] = bot[i + 1]; + bot[i + 1] = t; + } + } + + private: + vec4 top; + vec4 bot; +}; +} // namespace LI +} // namespace PD \ No newline at end of file diff --git a/include/pd/graphics/spritesheet.hpp b/include/pd/graphics/spritesheet.hpp new file mode 100644 index 0000000..964ddac --- /dev/null +++ b/include/pd/graphics/spritesheet.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace PD { +class SpriteSheet { + public: + SpriteSheet() {} + SpriteSheet(const std::string& path) { this->LoadFile(path); } + ~SpriteSheet(); + + void LoadFile(const std::string& path); + Texture::Ref Get(int idx); + int NumTextures() const; + + Texture::Ref operator[](int idx) { return Get(idx); } + + private: + std::vector textures; +}; +} // namespace PD \ No newline at end of file diff --git a/include/pd/graphics/texture.hpp b/include/pd/graphics/texture.hpp index 85ab153..f6bd1a6 100644 --- a/include/pd/graphics/texture.hpp +++ b/include/pd/graphics/texture.hpp @@ -27,6 +27,7 @@ SOFTWARE. #include #include +#include #include namespace PD { @@ -46,8 +47,12 @@ class Texture : public SmartCtor { Texture() : uv(0.f, 1.f, 1.f, 0.f) {} /// @brief Load file Constructor /// @param path path to file - Texture(const std::string& path) : uv(0.f, 1.f, 1.f, 0.f) { - this->LoadFile(path); + Texture(const std::string& path, bool t3x = false) : uv(0.f, 1.f, 1.f, 0.f) { + if (t3x) { + this->LoadT3X(path); + } else { + this->LoadFile(path); + } } /// @brief Load Memory constructor /// @param data File Data reference @@ -66,7 +71,11 @@ class Texture : public SmartCtor { this->LoadPixels(data, w, h, type, filter); } /// @brief Deconstructor (aka auto delete) - ~Texture() { Delete(); } + ~Texture() { + if (autounload) { + Delete(); + } + } /// @brief Deletes image (if not already unloaded) void Delete(); @@ -86,11 +95,17 @@ class Texture : public SmartCtor { void LoadPixels(const std::vector& data, int w, int h, Type type = RGBA32, Filter filter = NEAREST); + /// @brief Load a texture of a T3X File + /// @note This is used for single texture T3X + /// Not for SpriteSheets + /// @param path path to .t3x file + void LoadT3X(const std::string& path); + /// @brief Input a Texture that you had set up on your own /// @param tex Texture reference (deletes itself) /// @param rszs The size of the source image /// @param uvs Your uv Setup - void LoadExternal(C3D_Tex* tex, vec2 rszs, vec4 uvs) { + void LoadExternal(C3D_Tex* tex, vec2 rszs, LI::Rect uvs) { this->Delete(); this->tex = tex; this->size = rszs; @@ -105,19 +120,23 @@ class Texture : public SmartCtor { } vec2 GetSize() const { return size; } C3D_Tex* GetTex() const { return tex; }; - vec4 GetUV() const { return uv; } + LI::Rect GetUV() const { return uv; } bool IsValid() const { return tex != 0; } + bool AutoUnLoad() const { return autounload; } + void AutoUnLoad(bool v) { autounload = v; } + operator C3D_Tex*() const { return tex; } operator vec2() const { return size; } - operator vec4() const { return uv; } + operator LI::Rect() const { return uv; } operator bool() const { return tex != 0; } private: void MakeTex(std::vector& buf, int w, int h, Type type = RGBA32, Filter filter = NEAREST); vec2 size; - vec4 uv; + LI::Rect uv; C3D_Tex* tex = nullptr; + bool autounload = true; }; } // namespace PD \ No newline at end of file diff --git a/include/pd/overlays/message_mgr.hpp b/include/pd/overlays/message_mgr.hpp index de7885e..ec4e34a 100644 --- a/include/pd/overlays/message_mgr.hpp +++ b/include/pd/overlays/message_mgr.hpp @@ -21,16 +21,16 @@ class MessageMgr : public PD::SmartCtor { bool ShouldBeRemoved() const { return (tbr && pos.IsFinished()) || kill; } private: - PD::Color col_bg; // Background Color - PD::Color col_text; // Text Color - float lifetime = 0.f; // LifeTime - PD::Tween pos; // Position effect - std::string title; // Title - std::string msg; // Message - vec2 size; // Size of the Background - bool tbr = false; // To be Removed ? - bool kill = false; // Instant Kill - int s = 0; // Slot + PD::Color col_bg; // Background Color + PD::Color col_text; // Text Color + float lifetime = 0.f; // LifeTime + PD::Tween pos; // Position effect + std::string title; // Title + std::string msg; // Message + vec2 size; // Size of the Background + bool tbr = false; // To be Removed ? + bool kill = false; // Instant Kill + int s = 0; // Slot }; MessageMgr(PD::LI::Renderer::Ref r) { ren = r; } ~MessageMgr() {} diff --git a/include/pd/ui7/drawlist.hpp b/include/pd/ui7/drawlist.hpp index 3ad4ec7..ca01312 100644 --- a/include/pd/ui7/drawlist.hpp +++ b/include/pd/ui7/drawlist.hpp @@ -38,7 +38,7 @@ class DrawList : public SmartCtor { void AddTriangle(vec2 pos0, vec2 pos1, vec2 pos2, const UI7Color& clr); void AddText(vec2 pos, const std::string& text, const UI7Color& clr, LITextFlags flags = 0, vec2 box = vec2()); - void AddImage(vec2 pos, Texture::Ref img); + void AddImage(vec2 pos, Texture::Ref img, vec2 size = 0.f); void Clear(); void Process(); @@ -47,9 +47,22 @@ class DrawList : public SmartCtor { void Layer(int v) { layer = v; } private: + /// @brief Base Layer offset (Internal Used) + int BaseLayer() const { return base; } + /// @brief Base Layer offset (Internal Used) + void BaseLayer(int v) { base = v; } + + /// @brief Exopose Renderer here for Menus [DONT KNOW IF THIUS GETS REMOVED] + LI::Renderer::Ref GetRenderer() { return ren; } + + friend class Menu; + friend class Context; + int layer; + int base; LI::Renderer::Ref ren; - std::vector commands; + std::unordered_map static_text; + std::vector> commands; }; } // namespace UI7 } // namespace PD \ No newline at end of file diff --git a/include/pd/ui7/id.hpp b/include/pd/ui7/id.hpp index cbc9559..21de6f1 100644 --- a/include/pd/ui7/id.hpp +++ b/include/pd/ui7/id.hpp @@ -7,13 +7,23 @@ namespace PD { namespace UI7 { class ID { public: - ID(const std::string& text) { id = PD::Strings::FastHash(text); } + ID(const std::string& text) { + id = PD::Strings::FastHash(text); + name = text; + } + ID(const char* text) { + id = PD::Strings::FastHash(text); + name = text; + } ~ID() {} + std::string GetName() const { return name; } + operator u32() const { return id; } private: u32 id; + std::string name; }; } // namespace UI7 } // namespace PD \ No newline at end of file diff --git a/include/pd/ui7/menu.hpp b/include/pd/ui7/menu.hpp index 1d86fd7..203d550 100644 --- a/include/pd/ui7/menu.hpp +++ b/include/pd/ui7/menu.hpp @@ -23,14 +23,20 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include +#include +#include namespace PD { namespace UI7 { class Menu : public SmartCtor { public: - Menu(u32 id) { + Menu(ID id, Theme* tl, Hid::Ref h) { + linked_theme = tl; + this->inp = h; this->id = id; + this->name = id.GetName(); scrolling[0] = false; scrolling[1] = false; scrollbar[0] = false; @@ -38,10 +44,75 @@ class Menu : public SmartCtor { scroll_allowed[0] = false; scroll_allowed[1] = false; }; - ~Menu() {}; + ~Menu() {} + + /// Objects + void Label(const std::string& label); + bool Button(const std::string& label); + void Checkbox(const std::string& label, bool& v); + void Image(Texture::Ref img, vec2 size = 0.f); + + /// Basic API + void SameLine(); + void Separator(); + void SeparatorText(const std::string& label); + + /// API for Custom Objects + bool HandleScrolling(vec2& pos, const vec2& size); + vec2 Cursor() const { return cursor; } + void Cursor(const vec2& v) { + bcursor = cursor; + cursor = v; + } + void RestoreCursor() { + cursor = bcursor; + bcursor = vec2(); + } + + /// Draw Lists + DrawList::Ref BackList() { return back; } + void BackList(DrawList::Ref v) { back = v; } + DrawList::Ref MainList() { return main; } + void MainList(DrawList::Ref v) { main = v; } + DrawList::Ref FrontList() { return front; } + void FrontList(DrawList::Ref v) { front = v; } + + /// Advanced + void DebugLabels(); + + /// Uneditable Stuff + std::string GetName() const { return name; } + u32 GetID() const { return id; } private: + /// Advanced Handlers + void PreHandler(UI7MenuFlags flags); + void PostHandler(); + /// Basic Settings + vec2 BackupCursor() const { return bcursor; } + void BackupCursor(const vec2& v) { bcursor = v; } + vec2 SameLineCursor() const { return slcursor; } + void SameLineCursor(const vec2& v) { slcursor = v; } + vec4 ViewArea() const { return view_area; } + void ViewArea(const vec4& v) { view_area = v; } + vec2 ScrollOffset() const { return scrolling_off; } + void ScrollOffset(const vec2& v) { scrolling_off = v; } + vec2 ScrollMod() const { return scroll_mod; } + void ScrollMod(const vec2& v) { scroll_mod = v; } + + /// Advanced + void CursorMove(const vec2& szs); + + /// Internal Processing + void Update(float delta); + + /// This ability is crazy useful + friend class Context; + + /// Data + UI7MenuFlags flags = 0; u32 id; + std::string name; vec2 cursor; vec2 bcursor; vec2 slcursor; @@ -65,6 +136,12 @@ class Menu : public SmartCtor { vec2 mouse; vec2 bslpos; vec2 last_size; + + // Theme + Theme* linked_theme; + + // Input Reference + Hid::Ref inp; }; } // namespace UI7 } // namespace PD \ No newline at end of file diff --git a/include/pd/ui7/theme.hpp b/include/pd/ui7/theme.hpp index e1a0d67..ab91a8d 100644 --- a/include/pd/ui7/theme.hpp +++ b/include/pd/ui7/theme.hpp @@ -32,14 +32,14 @@ enum UI7Color_ { UI7Color_Button, UI7Color_ButtonDead, UI7Color_ButtonActive, - UI7Color_ButtonDisabled, + UI7Color_ButtonHovered, UI7Color_Text, UI7Color_TextDead, UI7Color_Header, UI7Color_Selector, UI7Color_Checkmark, UI7Color_FrameBackground, - UI7Color_FragmeBackgroundHovered, + UI7Color_FrameBackgroundHovered, UI7Color_Progressbar, UI7Color_ListEven, UI7Color_ListOdd, @@ -115,20 +115,4 @@ class Theme { std::vector> changes; }; } // namespace UI7 -/// Using UI7Color as a Class to be able to -/// define it as struct as well as using it as enum -class UI7Color { - public: - UI7Color() { - /// No Color - } - UI7Color(unsigned int c) { color = c; } - UI7Color(UI7Color_ c) {} - ~UI7Color() {} - - operator u32() const { return color; } - - private: - u32 color; -}; } // namespace PD \ No newline at end of file diff --git a/include/pd/ui7/ui7.hpp b/include/pd/ui7/ui7.hpp index b2d7338..c8ebcd0 100644 --- a/include/pd/ui7/ui7.hpp +++ b/include/pd/ui7/ui7.hpp @@ -23,23 +23,45 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include //// WOW A NON UI/ Header #include #include #include #include #include -#include namespace PD { namespace UI7 { class Context : public SmartCtor { public: - Context() {} + Context(LI::Renderer::Ref ren, Hid::Ref hid) { + this->ren = ren; + this->inp = hid; + Theme::Default(theme); + back = DrawList::New(ren); + front = DrawList::New(ren); + } ~Context() {} + bool BeginMenu(const ID& id, UI7MenuFlags flags = 0); + Menu::Ref GetCurrentMenu(); + void EndMenu(); + + /// Theme Management + Theme& GetTheme() { return theme; } + + /// @brief Update Context (Render menus) + /// @param delta deltatime void Update(float delta); + /// Expose DrawLists + DrawList::Ref BackList() { return back; } + DrawList::Ref FrontList() { return front; } + private: + // Linked Renderer / Hid + LI::Renderer::Ref ren; + Hid::Ref inp; // Timing float delta; float time; @@ -50,11 +72,14 @@ class Context : public SmartCtor { bool debugging; // Menu Handlers std::unordered_map menus; + std::vector amenus; // Active ones Menu::Ref current; // Context DrawList DrawList::Ref debug; DrawList::Ref front; DrawList::Ref back; + // Theme + Theme theme; // Promt Handler }; } // namespace UI7 diff --git a/source/common/app.cpp b/source/common/app.cpp index 3f1024c..0f7cfdd 100644 --- a/source/common/app.cpp +++ b/source/common/app.cpp @@ -28,6 +28,8 @@ SOFTWARE. #include namespace PD { +int App::too; + void App::Run() { this->PreInit(); this->Init(); @@ -45,7 +47,10 @@ void App::Run() { } PD::TT::End("App_MainLoop"); PD::TT::Beg("Ovl_Update"); + renderer->Layer(90); overlay_mgr->Update(dt); + /// Messages have their own special Layer + renderer->Layer(93); msg_mgr->Update(dt); PD::TT::End("Ovl_Update"); renderer->Render(); diff --git a/source/common/io.cpp b/source/common/io.cpp new file mode 100644 index 0000000..e9c20b1 --- /dev/null +++ b/source/common/io.cpp @@ -0,0 +1,26 @@ +#include + +namespace PD { +namespace IO { +std::vector LoadFile2Mem(const std::string& path) { + std::ifstream iff(path, std::ios::binary); + if (!iff) { + return std::vector(); + } + iff.seekg(0, std::ios::end); + size_t szs = iff.tellg(); + iff.seekg(0, std::ios::beg); + std::vector res(szs, 0); + iff.read(reinterpret_cast(res.data()), res.size()); + iff.close(); + return res; +} +u32 HashMemory(const std::vector& data) { + u32 hash = 4477; + for (auto& it : data) { + hash = (hash * 33) + it; + } + return hash; +} +} // namespace IO +} // namespace PD \ No newline at end of file diff --git a/source/common/sys.cpp b/source/common/sys.cpp index d7d35ac..c726684 100644 --- a/source/common/sys.cpp +++ b/source/common/sys.cpp @@ -44,5 +44,8 @@ TT::Res::Ref& GetTraceRef(const std::string& id) { } return pd_sys_tm[id]; } +bool TraceExist(const std::string& id) { + return pd_sys_tm.find(id) != pd_sys_tm.end(); +} TraceMap& GetTraceMap() { return pd_sys_tm; } } // namespace PD::Sys \ No newline at end of file diff --git a/source/graphics/lithium.cpp b/source/graphics/lithium.cpp index 3469d4c..90be6d4 100644 --- a/source/graphics/lithium.cpp +++ b/source/graphics/lithium.cpp @@ -23,6 +23,7 @@ SOFTWARE. #include +#include #include #include #include @@ -45,31 +46,44 @@ void Font::LoadTTF(const std::string& path, int height) { loader.read(reinterpret_cast(buffer), len); loader.close(); stbtt_InitFont(&inf, buffer, 0); - std::vector font_tex(quad * quad * 4); + std::vector font_tex(quad * quad); float scale = stbtt_ScaleForPixelHeight(&inf, pixel_height); int ascent, descent, lineGap; stbtt_GetFontVMetrics(&inf, &ascent, &descent, &lineGap); int baseline = static_cast(ascent * scale); + std::map buf_cache; + auto tex = Texture::New(); vec2 off; - for (int i = 0; i < 255; i++) { - Codepoint c; + for (u32 ii = 0x0000; ii < 0xFFFF; ii++) { + int i = stbtt_FindGlyphIndex(&inf, ii); + if (i == 0) { + continue; + } if (stbtt_IsGlyphEmpty(&inf, i)) { - c.cp(i); - c.tex(tex); - c.invalid(true); - cpmap[i] = c; continue; } + Codepoint c; int w = 0, h = 0, xo = 0, yo = 0; unsigned char* bitmap = stbtt_GetCodepointBitmap(&inf, scale, scale, i, &w, &h, &xo, &yo); int x0, y0, x1, y1; stbtt_GetCodepointBitmapBox(&inf, i, scale, scale, &x0, &y0, &x1, &y1); + u32 hashed_map = IO::HashMemory(std::vector(bitmap, bitmap + (w * h))); + if (buf_cache.find(hashed_map) != buf_cache.end()) { + c = GetCodepoint(buf_cache[hashed_map]); + c.cp(i); + cpmap[i] = c; + free(bitmap); + continue; + } else { + buf_cache[hashed_map] = i; + } + if (off[0] + w > quad) { off[1] += pixel_height; off[0] = 0; @@ -88,24 +102,25 @@ void Font::LoadTTF(const std::string& path, int height) { for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - int map_pos = ((off[1] + y) * quad + (off[0] + x)) * 4; - - font_tex[map_pos + 0] = 255; - font_tex[map_pos + 1] = 255; - font_tex[map_pos + 2] = 255; - font_tex[map_pos + 3] = bitmap[x + y * w]; + int map_pos = ((off[1] + y) * quad + (off[0] + x)); + font_tex[map_pos] = bitmap[x + y * w]; } } + + free(bitmap); + cpmap[i] = c; + // Small Patch to avoid some possible artifacts off[0] += w + 1; if (off[0] + w > quad) { off[1] += pixel_height; + if (off[1] + pixel_height > quad) { + break; + } off[0] = 0; } - free(bitmap); - cpmap[i] = c; } - tex->LoadPixels(font_tex, quad, quad, Texture::RGBA32, Texture::LINEAR); + tex->LoadPixels(font_tex, quad, quad, Texture::A8, Texture::LINEAR); textures.push_back(tex); } @@ -143,6 +158,7 @@ void Font::LoadSystemFont() { tx->border = 0xffffffff; tx->lodParam = 0; stex->LoadExternal(tx, vec2(tx->width, tx->height), vec4(0, 1, 1, 0)); + stex->AutoUnLoad(false); textures[i] = stex; } std::vector charSet; @@ -192,8 +208,10 @@ void Font::LoadSystemFont() { codepoint.uv(vec4(dat.texcoord.left, dat.texcoord.top, dat.texcoord.right, dat.texcoord.bottom)); - if (textures.at(dat.sheetIndex) != nullptr) { + if (dat.sheetIndex < (int)textures.size()) { codepoint.tex(textures[dat.sheetIndex]); + } else { + codepoint.invalid(true); } codepoint.size(vec2(dat.vtxcoord.right, dat.vtxcoord.bottom)); codepoint.off(0); @@ -250,7 +268,9 @@ void Renderer::StaticText::Setup(Renderer* ren, const vec2& pos, u32 clr, this->pos = pos; this->ren = ren; this->text = StaticObject::New(); - ren->TextCommand(this->text->List(), pos, clr, text, flags, box); + /// Ensure that it also renders Out of Screen i guess + ren->TextCommand(this->text->List(), pos, clr, text, + flags | LITextFlags_RenderOOS, box); OptiCommandList(this->text->List()); } @@ -267,6 +287,8 @@ void Renderer::StaticText::SetPos(const vec2& pos) { text->MoveIt(pos - this->pos); } +void Renderer::StaticText::SetLayer(int layer) { text->ReLayer(layer); } + bool Renderer::InBox(const vec2& pos, const vec2& szs, const vec4& rect) { return (pos[0] < rect[2] || pos[1] < rect[3] || pos[0] + szs[0] > rect[0] || pos[1] + szs[1] > rect[1]); @@ -341,18 +363,14 @@ void Renderer::SetupCommand(Command::Ref cmd) { cmd->Index(cmd_idx++).Layer(current_layer).Tex(current_tex); } -void Renderer::QuadCommand(Command::Ref cmd, const Rect& quad, const vec4& uv, +void Renderer::QuadCommand(Command::Ref cmd, const Rect& quad, const Rect& uv, u32 col) { cmd->PushIndex(0).PushIndex(1).PushIndex(2); cmd->PushIndex(0).PushIndex(2).PushIndex(3); - cmd->PushVertex( - Vertex(vec2(quad.Bot().z(), quad.Bot().w()), vec2(uv.z(), uv.w()), col)); - cmd->PushVertex( - Vertex(vec2(quad.Top().z(), quad.Top().w()), vec2(uv.z(), uv.y()), col)); - cmd->PushVertex( - Vertex(vec2(quad.Top().x(), quad.Top().y()), vec2(uv.x(), uv.y()), col)); - cmd->PushVertex( - Vertex(vec2(quad.Bot().x(), quad.Bot().y()), vec2(uv.x(), uv.w()), col)); + cmd->PushVertex(Vertex(quad.BotRight(), uv.BotRight(), col)); + cmd->PushVertex(Vertex(quad.TopRight(), uv.TopRight(), col)); + cmd->PushVertex(Vertex(quad.TopLeft(), uv.TopLeft(), col)); + cmd->PushVertex(Vertex(quad.BotLeft(), uv.BotLeft(), col)); } void Renderer::TriangleCommand(Command::Ref cmd, const vec2& a, const vec2& b, @@ -364,7 +382,6 @@ void Renderer::TriangleCommand(Command::Ref cmd, const vec2& a, const vec2& b, cmd->PushVertex(Vertex(c, vec2(1.f, 0.f), col)); } -/// TO BE REWRITTEN void Renderer::TextCommand(std::vector& cmds, const vec2& pos, u32 color, const std::string& text, LITextFlags flags, const vec2& box) { @@ -375,6 +392,23 @@ void Renderer::TextCommand(std::vector& cmds, const vec2& pos, float cfs = (default_font_h * text_size) / (float)font->PixelHeight(); float lh = (float)font->PixelHeight() * cfs; vec2 td; + vec2 rpos = pos; + vec2 rbox = box; + if (flags & (LITextFlags_AlignMid | LITextFlags_AlignRight)) { + td = GetTextDimensions(text); + if (rbox[0] == 0.f) { + rbox[0] = area_size.x(); + } + if (rbox[1] == 0.f) { + rbox[1] = area_size.y(); + } + } + if (flags & LITextFlags_AlignMid) { + rpos = rbox * 0.5 - td * 0.5 + pos; + } + if (flags & LITextFlags_AlignRight) { + rpos[0] = rbox[0] - td[0]; + } std::vector lines; std::istringstream iss(text); @@ -384,10 +418,10 @@ void Renderer::TextCommand(std::vector& cmds, const vec2& pos, } for (auto& it : lines) { - if (pos[1] + off[1] + lh < 0) { + if (rpos[1] + off[1] + lh < 0) { off[1] += lh; continue; - } else if (pos[1] + off[1] > GetViewport().w() && + } else if (rpos[1] + off[1] > GetViewport().w() && !(flags & LITextFlags_RenderOOS)) { // Break cause next lines would be out of screen break; @@ -396,9 +430,7 @@ void Renderer::TextCommand(std::vector& cmds, const vec2& pos, auto cmd = Command::New(); current_tex = font->GetCodepoint(wline[0]).tex(); SetupCommand(cmd); - if (font->SystemFont()) { - cmd->Rendermode(RenderMode_SysFont); - } + cmd->Rendermode(RenderMode_Font); for (auto& jt : wline) { auto cp = font->GetCodepoint(jt); if (cp.invalid() && jt != '\n' && jt != '\t') { @@ -409,9 +441,7 @@ void Renderer::TextCommand(std::vector& cmds, const vec2& pos, cmd = Command::New(); current_tex = cp.tex(); SetupCommand(cmd); - if (font->SystemFont()) { - cmd->Rendermode(RenderMode_SysFont); - } + cmd->Rendermode(RenderMode_Font); } if (jt == '\t') { off[0] += 16 * cfs; @@ -421,13 +451,13 @@ void Renderer::TextCommand(std::vector& cmds, const vec2& pos, if (flags & LITextFlags_Shaddow) { // Draw Rect rec = CreateRect( - pos + vec2(off[0] + 1, off[1] + (cp.off() * cfs)) + 1, + rpos + vec2(off[0] + 1, off[1] + (cp.off() * cfs)) + 1, cp.size() * cfs, 0.f); QuadCommand(cmd, rec, cp.uv(), 0xff111111); current_layer++; } // Draw - Rect rec = CreateRect(pos + off + vec2(0, (cp.off() * cfs)), + Rect rec = CreateRect(rpos + off + vec2(0, (cp.off() * cfs)), cp.size() * cfs, 0.f); QuadCommand(cmd, rec, cp.uv(), color); current_layer = lr; @@ -450,7 +480,6 @@ vec4 Renderer::GetViewport() { return vec4(0, 0, screen[0], screen[1]); } -/// TO BE REWRITTEN vec2 Renderer::GetTextDimensions(const std::string& text) { if (!font) { // No font no size (oder so) @@ -516,7 +545,8 @@ vec2 Renderer::GetTextDimensions(const std::string& text) { void Renderer::UpdateRenderMode(const RenderMode& mode) { C3D_TexEnv* env = C3D_GetTexEnv(0); switch (mode) { - case RenderMode_SysFont: + case RenderMode_Font: + /// Sets Only Alpha Using the Color and Replase RGB with vertex color C3D_TexEnvInit(env); C3D_TexEnvSrc(env, C3D_RGB, GPU_PRIMARY_COLOR); C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); @@ -526,6 +556,7 @@ void Renderer::UpdateRenderMode(const RenderMode& mode) { // Fall trough instead of defining twice case RenderMode_RGBA: default: + /// Use Texture for RGBA and vertexcolor for visibility C3D_TexEnvInit(env); C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0); C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); @@ -536,23 +567,17 @@ void Renderer::UpdateRenderMode(const RenderMode& mode) { void Renderer::RenderOn(bool bot) { C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, (bot ? &bot_proj : &top_proj)); - C3D_DepthTest(false, GPU_GREATER, GPU_WRITE_ALL); - UpdateRenderMode(RenderMode_RGBA); - int total_vertices = 0; int total_indices = 0; drawcalls = 0; auto& cmds = draw_list[bot]; - commands = cmds.size(); size_t index = 0; - if (flags & RenderFlags_LRS) { OptiCommandList(cmds); } - while (index < cmds.size()) { C3D_Tex* tex = cmds[index]->Tex()->GetTex(); auto mode = cmds[index]->Rendermode(); @@ -572,6 +597,7 @@ void Renderer::RenderOn(bool bot) { } index++; } + C3D_TexBind(0, tex); auto bufInfo = C3D_GetBufInfo(); @@ -580,6 +606,7 @@ void Renderer::RenderOn(bool bot) { C3D_DrawElements(GPU_TRIANGLES, index_idx - start_idx, C3D_UNSIGNED_SHORT, index_buf.data() + start_idx); + drawcalls++; total_vertices += vertex_idx - start_vtx; total_indices += index_idx - start_idx; @@ -629,6 +656,8 @@ void Renderer::Render() { if (Sys::GetTime() - it.second.TimeCreated() > 5) rem.push_back(it.first); } for (auto it : rem) tms.erase(it); + } else { + tms.clear(); } if (flags & RenderFlags_AST) { std::vector rem; @@ -641,11 +670,13 @@ void Renderer::Render() { for (auto& it : rem) { ast.erase(it); } + } else { + ast.clear(); } } void Renderer::DrawRect(const vec2& pos, const vec2& size, u32 color, - const vec4& uv) { + const Rect& uv) { if (!InBox(pos, size, GetViewport())) { // Instand abort as it is out of screen return; @@ -659,7 +690,7 @@ void Renderer::DrawRect(const vec2& pos, const vec2& size, u32 color, void Renderer::DrawRectSolid(const vec2& pos, const vec2& size, u32 color) { UseTex(); - DrawRect(pos, size, color); + DrawRect(pos, size, color, vec4(0.f, 1.f, 1.f, 0.f)); } void Renderer::DrawTriangle(const vec2& a, const vec2& b, const vec2& c, diff --git a/source/graphics/spritesheet.cpp b/source/graphics/spritesheet.cpp new file mode 100644 index 0000000..5205ac7 --- /dev/null +++ b/source/graphics/spritesheet.cpp @@ -0,0 +1,44 @@ +#include +#include + +namespace PD { +SpriteSheet::~SpriteSheet() { textures.clear(); } + +void SpriteSheet::LoadFile(const std::string& path) { + auto file = IO::LoadFile2Mem(path); + if (file.size() == 0) { + Error("Unable to load file:\n" + path); + } + C3D_Tex* tex = new C3D_Tex; + auto t3x = + Tex3DS_TextureImport(file.data(), file.size(), tex, nullptr, false); + if (!t3x) { + Error("Unable to import:\n" + path); + } + tex->border = 0; + C3D_TexSetWrap(tex, GPU_CLAMP_TO_BORDER, GPU_CLAMP_TO_BORDER); + C3D_TexSetFilter(tex, GPU_LINEAR, GPU_NEAREST); + textures.reserve(Tex3DS_GetNumSubTextures(t3x) + 1); + for (int i = 0; i < (int)Tex3DS_GetNumSubTextures(t3x); i++) { + auto t = Texture::New(); + auto st = Tex3DS_GetSubTexture(t3x, i); + LI::Rect uv(vec2(st->left, st->top), vec2(st->right, st->top), + vec2(st->left, st->bottom), vec2(st->right, st->bottom)); + if (st->top < st->bottom) { + uv.SwapVec2XY(); + } + t->LoadExternal(tex, vec2(st->width, st->height), uv); + textures.push_back(t); + } +} + +Texture::Ref SpriteSheet::Get(int idx) { + if (idx >= (int)textures.size()) { + Error("Trying to Access Texture " + std::to_string(idx + 1) + " of " + + std::to_string(NumTextures())); + } + return textures[idx]; +} + +int SpriteSheet::NumTextures() const { return textures.size(); } +} // namespace PD \ No newline at end of file diff --git a/source/graphics/texture.cpp b/source/graphics/texture.cpp index e0742e4..21dff2b 100644 --- a/source/graphics/texture.cpp +++ b/source/graphics/texture.cpp @@ -24,12 +24,14 @@ SOFTWARE. #include <3ds.h> #include +#include +#include +#include #include #include #include #include -#include namespace PD { GPU_TEXCOLOR GetTexFmt(Texture::Type type) { @@ -54,36 +56,6 @@ void Texture::MakeTex(std::vector& buf, int w, int h, Texture::Type type, Filter filter) { // Don't check here as check done before int bpp = GetBPP(type); - if (bpp == 4) { - // RGBA -> Abgr - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int pos = (x + y * w) * bpp; - auto r = buf[pos + 0]; - auto g = buf[pos + 1]; - auto b = buf[pos + 2]; - auto a = buf[pos + 3]; - buf[pos + 0] = a; - buf[pos + 1] = b; - buf[pos + 2] = g; - buf[pos + 3] = r; - } - } - } else if (bpp == 3) { - // RGBA -> Abgr - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int pos = (x + y * w) * bpp; - auto r = buf[pos + 0]; - auto g = buf[pos + 1]; - auto b = buf[pos + 2]; - buf[pos + 0] = b; - buf[pos + 1] = g; - buf[pos + 2] = r; - } - } - } - vec2 tex_size(w, h); // Pow2 if (!PD::BitUtil::IsSingleBit(w)) { @@ -95,10 +67,8 @@ void Texture::MakeTex(std::vector& buf, int w, int h, Texture::Type type, this->size.x() = (u16)w; this->size.y() = (u16)h; - this->uv.x() = 0.0f; - this->uv.y() = 1.0f; - this->uv.z() = ((float)w / (float)tex_size.x()); - this->uv.w() = 1.0 - ((float)h / (float)tex_size.y()); + this->uv = vec4(0.f, 1.f, ((float)w / (float)tex_size.x()), + 1.0 - ((float)h / (float)tex_size.y())); // Texture Setup auto fltr = (filter == NEAREST ? GPU_NEAREST : GPU_LINEAR); @@ -109,7 +79,9 @@ void Texture::MakeTex(std::vector& buf, int w, int h, Texture::Type type, memset(tex->data, 0, tex->size); - if (bpp == 3 || bpp == 4) { + /// Probably Remove this if statement in future + /// This are the things confirmed as working + if (bpp == 3 || bpp == 4 || bpp == 1) { for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { int dst_pos = ((((y >> 3) * ((int)tex_size.x() >> 3) + (x >> 3)) << 6) + @@ -117,13 +89,13 @@ void Texture::MakeTex(std::vector& buf, int w, int h, Texture::Type type, ((y & 2) << 2) | ((x & 4) << 2) | ((y & 4) << 3))) * bpp; int src_pos = (y * w + x) * bpp; - - memcpy(&((u8*)tex->data)[dst_pos], &buf[src_pos], bpp); + /// Best idea i had + for (int i = 0; i < bpp; i++) { + ((u8*)tex->data)[dst_pos + bpp - 1 - i] = buf[src_pos + i]; + } } } C3D_TexFlush(tex); - } else if (bpp == 1) { - C3D_TexLoadImage(tex, buf.data(), GPU_TEXFACE_2D, 0); } tex->border = 0x00000000; @@ -200,4 +172,23 @@ void Texture::LoadPixels(const std::vector& pixels, int w, int h, Type type, std::vector cpy(pixels); MakeTex(cpy, w, h, type, filter); } + +void Texture::LoadT3X(const std::string& path) { + this->Delete(); + auto file = IO::LoadFile2Mem(path); + if (file.size() == 0) { + Error("Unable to load file:\n" + path); + } + this->tex = new C3D_Tex; + auto t3x = + Tex3DS_TextureImport(file.data(), file.size(), this->tex, nullptr, false); + if (!t3x) { + Error("Unable to import:\n" + path); + } + auto st = Tex3DS_GetSubTexture(t3x, 0); + this->uv = vec4(st->left, st->top, st->right, st->bottom); + this->size[0] = st->width; + this->size[1] = st->height; + Tex3DS_TextureFree(t3x); +} } // namespace PD \ No newline at end of file diff --git a/source/overlays/keyboard.cpp b/source/overlays/keyboard.cpp index 3080bfb..167eca2 100644 --- a/source/overlays/keyboard.cpp +++ b/source/overlays/keyboard.cpp @@ -484,8 +484,6 @@ void Keyboard::Update(float delta, LI::Renderer::Ref ren, Hid::Ref inp) { /// Process Controller Movement Movement(inp); - /// Declare RenderLayer (10 above the latest) - ren->Layer(ren->Layer() + 10); /// Update animations flymgr.Update(delta); selector.Update(delta); diff --git a/source/overlays/message_mgr.cpp b/source/overlays/message_mgr.cpp index 7e33958..a9b3949 100644 --- a/source/overlays/message_mgr.cpp +++ b/source/overlays/message_mgr.cpp @@ -104,8 +104,6 @@ void MessageMgr::Push(const std::string& title, const std::string& text) { } void MessageMgr::Update(float delta) { - // Go two layers up and Render on Top - ren->Layer(ren->Layer() + 2); ren->OnScreen(Screen::Top); for (size_t i = 0; i < msgs.size(); i++) { // Update the Animation Handlers and Move older diff --git a/source/overlays/performance.cpp b/source/overlays/performance.cpp index 693e217..7a794d2 100644 --- a/source/overlays/performance.cpp +++ b/source/overlays/performance.cpp @@ -12,8 +12,7 @@ void Performance::Update(float delta, LI::Renderer::Ref ren, Hid::Ref inp) { ren->OnScreen(Screen::Top); ren->TextScale(0.6); vec2 pos; - Line(pos, std::format("FPS {:.1f} FPS / {:.2f}ms", 1000.f / delta, delta), - ren); + Line(pos, std::format("{:.1f} FPS / {:.2f}ms", 1000.f / delta, delta), ren); Line(pos, "Ren [AVG]: " + TSA("LI_RenderAll"), ren); Line(pos, "App [AVG]: " + TSA("App_MainLoop"), ren); Line(pos, "Ovl [AVG]: " + TSA("Ovl_Update"), ren); diff --git a/source/ui7/drawlist.cpp b/source/ui7/drawlist.cpp index bcc6175..87bc9a6 100644 --- a/source/ui7/drawlist.cpp +++ b/source/ui7/drawlist.cpp @@ -21,6 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include namespace PD { @@ -31,11 +32,12 @@ void DrawList::AddRectangle(vec2 pos, vec2 szs, const UI7Color& clr) { } auto rect = ren->CreateRect(pos, szs, 0.f); auto cmd = LI::Command::New(); - ren->SetupCommand(cmd); ren->UseTex(); + ren->SetupCommand(cmd); cmd->Layer(layer); ren->QuadCommand(cmd, rect, vec4(0.f, 1.f, 1.f, 0.f), clr); - commands.push_back(cmd); + commands.push_back( + std::make_pair(ren->CurrentScreen() == Screen::Bottom, cmd)); } void DrawList::AddTriangle(vec2 pos0, vec2 pos1, vec2 pos2, @@ -44,41 +46,82 @@ void DrawList::AddTriangle(vec2 pos0, vec2 pos1, vec2 pos2, return; } auto cmd = LI::Command::New(); - ren->SetupCommand(cmd); ren->UseTex(); + ren->SetupCommand(cmd); cmd->Layer(layer); ren->TriangleCommand(cmd, pos0, pos1, pos2, clr); - commands.push_back(cmd); + commands.push_back( + std::make_pair(ren->CurrentScreen() == Screen::Bottom, cmd)); } void DrawList::AddText(vec2 pos, const std::string& text, const UI7Color& clr, LITextFlags flags, vec2 box) { + u32 id = Strings::FastHash(text); + auto e = static_text.find(id); + if (e == static_text.end()) { + static_text[id] = LI::Renderer::StaticText::New(); + e = static_text.find(id); + } + if (!e->second->IsSetup()) { + e->second->Setup(&(*ren), pos, clr, text, flags, box); + } + e->second->SetPos(pos); + e->second->SetColor(clr); + e->second->SetLayer(base + layer); + e->second->Draw(); + + ////// STILL LEAVING THE OLD CODE BELOW AS IT IS MAYBE NEEDED ////// + ////// IF STATIC TEXT SYSTEM SHOULD HAVE AN DISABLE OPTION ////// + // Dont create a Command here as TextCommand has autosetup // cause it needs to generate multiple commands if // Font uses multiple textures - ren->TextCommand(commands, pos, clr, text, flags, box); + // Oh and Handle Layer management here as well + // int l = ren->Layer(); + // ren->Layer(layer); + // std::vector cmds; + // ren->TextCommand(cmds, pos, clr, text, flags, box); + // ren->Layer(l); + // for (auto c : cmds) { + // commands.push_back( + // std::make_pair(ren->CurrentScreen() == Screen::Bottom, c)); + // } } -void DrawList::AddImage(vec2 pos, Texture::Ref img) { - if (!ren->InBox(pos, img->GetSize(), ren->GetViewport())) { +void DrawList::AddImage(vec2 pos, Texture::Ref img, vec2 size) { + size = size == 0.f ? img->GetSize() : size; + if (!ren->InBox(pos, size, ren->GetViewport())) { return; } - auto rect = ren->CreateRect(pos, img->GetSize(), 0.f); + auto rect = ren->CreateRect(pos, size, 0.f); auto cmd = LI::Command::New(); - ren->SetupCommand(cmd); ren->UseTex(img); + ren->SetupCommand(cmd); cmd->Layer(layer); - ren->QuadCommand(cmd, rect, vec4(0.f, 1.f, 1.f, 0.f), 0xffffffff); - commands.push_back(cmd); + ren->QuadCommand(cmd, rect, img->GetUV(), 0xffffffff); + commands.push_back( + std::make_pair(ren->CurrentScreen() == Screen::Bottom, cmd)); } void DrawList::Clear() { commands.clear(); } void DrawList::Process() { - // UI7 Commands Use LI7 as default feature - ren->OptiCommandList(commands); for (auto command : commands) { - ren->PushCommand(command); + command.second->Layer(command.second->Layer() + base); + ren->OnScreen(command.first ? Screen::Bottom : Screen::Top); + ren->PushCommand(command.second); + } + commands.clear(); + layer = 0; + std::vector rem; + for (auto it : static_text) { + if (!it.second->Used()) { + rem.push_back(it.first); + } + it.second->SetUnused(); + } + for (auto& it : rem) { + static_text.erase(it); } } } // namespace UI7 diff --git a/source/ui7/menu.cpp b/source/ui7/menu.cpp new file mode 100644 index 0000000..78e869f --- /dev/null +++ b/source/ui7/menu.cpp @@ -0,0 +1,331 @@ +#include +#include +#include + +////////////////////////////// +//////// OBJECT SETUP //////// +////// Setup Variables /////// +///////// Move Cursor //////// +//// Check Scrolling State /// +/////// Handle Controls ////// +////////// Render //////////// +////////////////////////////// + +namespace PD { +namespace UI7 { +void UI7::Menu::Label(const std::string& label) { + vec2 size = this->back->GetRenderer()->GetTextDimensions(label); + vec2 pos = Cursor(); + CursorMove(size - vec2(0, 4)); // Fix to make gap not to large + + if (HandleScrolling(pos, size)) { + return; + } + + /// To Draw a Label above the head bar you should + /// use the m->GetFrontList() instead + + main->AddText(pos, label, linked_theme->Get(UI7Color_Text), 0, + vec2(view_area.z(), 20)); +} + +bool UI7::Menu::Button(const std::string& label) { + bool ret = false; + auto tszs = this->back->GetRenderer()->GetTextDimensions(label); + vec2 size = tszs + vec2(8, 4); + vec2 pos = Cursor(); + UI7Color clr = UI7Color_Button; + CursorMove(size); + /////// SCROLLING HANDLER HERE //////// + if (HandleScrolling(pos, size)) { + return false; + } + /// CONTROLS /// + if (has_touch) { + if (inp->IsHeld(inp->Touch) && + LI::Renderer::InBox(inp->TouchPos(), vec4(pos, size))) { + clr = UI7Color_ButtonHovered; + } + if (inp->IsUp(inp->Touch) && + LI::Renderer::InBox(inp->TouchPosLast(), vec4(pos, size))) { + clr = UI7Color_ButtonActive; + ret = true; + } + } + /// Rendering /// + main->AddRectangle(pos, size, linked_theme->Get(clr)); + main->AddText(pos + size * 0.5 - tszs * 0.5, label, + linked_theme->Get(UI7Color_Text)); + return ret; +} + +void UI7::Menu::Checkbox(const std::string& label, bool& v) { + vec2 pos = Cursor(); + vec2 tdim = front->ren->GetTextDimensions(label); + vec2 cbs(18); + vec2 size = cbs + vec2(tdim.x() + 5, 0); + CursorMove(size); + + if (HandleScrolling(pos, size)) { + return; + } + + UI7Color cbbg = UI7Color_FrameBackground; + + if (has_touch) { + if (inp->IsHeld(inp->Touch) && + LI::Renderer::InBox(inp->TouchPos(), vec4(pos, size))) { + cbbg = UI7Color_FrameBackgroundHovered; + } + if (inp->IsUp(inp->Touch) && + LI::Renderer::InBox(inp->TouchPosLast(), vec4(pos, size))) { + cbbg = UI7Color_FrameBackgroundHovered; + v = !v; + } + } + + main->AddRectangle(pos, cbs, linked_theme->Get(cbbg)); + if (v) { + main->AddRectangle(pos + 2, cbs - 4, linked_theme->Get(UI7Color_Checkmark)); + } + main->AddText(pos + vec2(cbs.x() + 5, cbs.y() * 0.5 - tdim.y() * 0.5), label, + linked_theme->Get(UI7Color_Text)); +} + +void UI7::Menu::Image(Texture::Ref img, vec2 size) { + /// Variable Setup Stage /// + size = size == 0.f ? img->GetSize() : size; + vec2 pos = Cursor(); + CursorMove(size); + /// Scrolling Handler /// + if (HandleScrolling(pos, size)) { + return; + } + /// Rendering Stage /// + main->AddImage(pos, img, size); +} + +void UI7::Menu::DebugLabels() { + std::stringstream s; + s << "Name: " << name << " ["; + s << std::hex << std::setw(8) << std::setfill('0') << id; + s << std::dec << "]"; + this->Label(s.str()); + this->Label( + "Pre: " + + Strings::FormatNanos( + Sys::GetTraceRef("MPRE_" + name)->GetProtocol()->GetAverage())); + this->Label( + "Post: " + + Strings::FormatNanos( + Sys::GetTraceRef("MPOS_" + name)->GetProtocol()->GetAverage())); + this->Label( + "Update: " + + Strings::FormatNanos( + Sys::GetTraceRef("MUPT_" + name)->GetProtocol()->GetAverage())); + this->Label( + "MUser: " + + Strings::FormatNanos( + Sys::GetTraceRef("MUSR_" + name)->GetProtocol()->GetAverage())); +} + +void UI7::Menu::Update(float delta) { + TT::Scope st("MUPT_" + name); + this->back->BaseLayer(30); + this->back->Process(); + this->main->BaseLayer(40); + this->main->Process(); + this->front->BaseLayer(50); + this->front->Process(); +} + +void UI7::Menu::CursorMove(const vec2& size) { + last_size = size; + slcursor = cursor + vec2(size[0] + 5, 0); + if (bslpos[1]) { + cursor = vec2(5, cursor[1] + bslpos[1] + 5); + bslpos = vec2(); + } else { + cursor = vec2(5, cursor[1] + size[1] + 5); + } + max = vec2(slcursor[0], cursor[1]); +} + +void UI7::Menu::PreHandler(UI7MenuFlags flags) { + TT::Scope st("MPRE_" + name); + TT::Beg("MUSR_" + name); + Cursor(vec2(5, 5)); + this->flags = flags; + this->scrolling[0] = flags & UI7MenuFlags_HzScrolling; + this->scrolling[1] = flags & UI7MenuFlags_VtScrolling; + has_touch = main->GetRenderer()->CurrentScreen() == Screen::Bottom; + if (!(flags & UI7MenuFlags_NoBackground)) { + back->AddRectangle(0, view_area.zw(), UI7Color_Background); + } + if (!(flags & UI7MenuFlags_NoTitlebar)) { + tbh = front->GetRenderer()->TextScale() * 30.f; + front->AddRectangle(0, vec2(view_area.z(), tbh), + linked_theme->Get(UI7Color_Header)); + vec2 tpos(5, tbh * 0.5 - front->ren->GetTextDimensions(name).y() * 0.5); + LITextFlags tflags = LITextFlags_None; + if (flags & UI7MenuFlags_CenterTitle) { + tpos = 0; + tflags = LITextFlags_AlignMid; + } + front->AddText(tpos, this->name, linked_theme->Get(UI7Color_Text), tflags, + vec2(view_area.z(), tbh)); + Cursor(vec2(5, tbh + 5)); + } +} + +void UI7::Menu::PostHandler() { + TT::Scope st("MPOS_" + name); + if (scrolling[1]) { + scroll_allowed[1] = (max[1] > 235); + scrollbar[1] = scroll_allowed[1]; + + if (scrollbar[1]) { + /// Setup Some Variables hare [they are self described] + int screen_w = view_area.z(); + int tsp = 5 + tbh; + int slider_w = 4; + int szs = view_area.w() - tsp - 5; + /// Actually dont have a Horizontal bar yet + if (scrollbar[0]) szs -= slider_w - 2; + int lslider_h = 20; // Dont go less heigt for the drag + float slider_h = (szs - 4) * (float(szs - 4) / max[1]); + /// Visual Slider Height (How it looks in the end) + int vslider_h = std::clamp(slider_h, float(lslider_h), float(szs - 4)); + + /// Check if we overscroll to the bottom and Auto scroll back... + /// Probably schould use Tween ENgine here + if (scrolling_off[1] > max[1] - view_area[3] && max[1] != 0.f && + max[1] >= view_area[3] - 5) { + scrolling_off[1] -= 3.f; + if (scrolling_off[1] < max[1] - view_area[3]) { + scrolling_off[1] = max[1] - view_area[3]; + } + } + + /// Do the Same as above just for Overscroll back to the top + if (scrolling_off[1] < 0) { + scrolling_off[1] += 3.f; + if (scrolling_off[1] > 0) { + scrolling_off[1] = 0; + } + } + + /// Dont overscroll to much + if (scrolling_off[1] < -40 || + scrolling_off[1] > max[1] - view_area[3] + 40) { + scroll_mod[1] = 0.f; + } + + /// The pain :( + if (has_touch) { + vec2 tpos = inp->TouchPos(); + if (inp->IsDown(inp->Touch)) { + mouse = tpos; + } else if (inp->IsUp(inp->Touch)) { + mouse = vec2(); + } + if (inp->IsHeld(inp->Touch)) { + if (!front->ren->InBox(tpos, vec4(view_area[2] - 13, tbh + 5, 8, + view_area[3] - tbh - 10))) { + if (scrolling_off[1] < max[1] - view_area[3] + 40 && + scrolling_off[1] > -40) { + /// Cursor Mod + float cm = mouse[1] - tpos[1]; + if (scroll_mod[1] <= 4.f && scroll_mod[1] >= -4.f && cm != 0) { + scroll_mod[1] = cm; + } + } + mouse = tpos; + } + } + } + + /// Effect + if (scroll_mod[1] != 0) { + scrolling_off[1] += scroll_mod[1]; + } + if (scroll_mod[1] < 0.f) { + scroll_mod[1] += 0.4f; + if (scroll_mod[1] > 0.f) { + scroll_mod[1] = 0; + } + } + if (scroll_mod[1] > 0.f) { + scroll_mod[1] -= 0.4f; + if (scroll_mod[1] < 0.f) { + scroll_mod[1] = 0; + } + } + + int srpos = + tsp + std::clamp(float(szs - vslider_h - 4) * + (scrolling_off[1] / (max[1] - view_area[3])), + 0.f, float(szs - vslider_h - 4)); + + /// Rendering Stage + front->AddRectangle(vec2(screen_w - 12, tsp), vec2(slider_w * 2, szs), + linked_theme->Get(UI7Color_FrameBackground)); + front->AddRectangle(vec2(screen_w - 10, tsp + 2), vec2(slider_w, szs - 4), + linked_theme->Get(UI7Color_FrameBackgroundHovered)); + front->AddRectangle(vec2(screen_w - 10, srpos + 2), + vec2(slider_w, vslider_h), + linked_theme->Get(UI7Color_Button)); + } + } + TT::End("MUSR_" + name); +} + +void UI7::Menu::SameLine() { + bslpos = last_size; + cursor = slcursor; +} + +void UI7::Menu::Separator() { + vec2 pos = Cursor(); + vec2 size = vec2(view_area.z() - (scrollbar[1] ? 24 : 10), 1); + CursorMove(size); + if (HandleScrolling(pos, size)) { + return; + } + main->AddRectangle(pos, size, linked_theme->Get(UI7Color_TextDead)); +} + +void UI7::Menu::SeparatorText(const std::string& label) { + vec2 size = vec2(view_area.z() - (scrollbar[1] ? 24 : 10), 1); + vec2 tdim = this->back->GetRenderer()->GetTextDimensions(label); + vec2 pos = Cursor(); + CursorMove(vec2(size.x(), tdim.y() - 4)); // Fix to make gap not to large + + if (HandleScrolling(pos, size)) { + return; + } + /// Label pos for better overview + vec2 lpos = pos + vec2((view_area.z() - 10) * 0.5 - tdim.x() * 0.5, 0); + main->AddRectangle(pos + vec2(0, tdim.y() * 0.5), + vec2(lpos.x() - pos.x() - 5, size.y()), + linked_theme->Get(UI7Color_TextDead)); + main->AddRectangle(pos + vec2(lpos.x() + tdim.x(), tdim.y() * 0.5), + vec2(size.x() - (lpos.x() + tdim.x()), size.y()), + linked_theme->Get(UI7Color_TextDead)); + main->AddText(lpos, label, linked_theme->Get(UI7Color_Text), 0, + vec2(view_area.z(), 20)); +} + +bool UI7::Menu::HandleScrolling(vec2& pos, const vec2& size) { + if (scrolling[1]) { + vec2 p = pos; + pos -= vec2(0, scrolling_off.y()); + if (pos.y() > view_area.w() || + (pos.y() + size.y() < tbh - 5 && p.y() > tbh)) { + return true; + } + } + return false; +} +} // namespace UI7 +} // namespace PD \ No newline at end of file diff --git a/source/ui7/theme.cpp b/source/ui7/theme.cpp index ac18e6e..965e71f 100644 --- a/source/ui7/theme.cpp +++ b/source/ui7/theme.cpp @@ -10,12 +10,12 @@ void Theme::Default(Theme& theme) { theme.Set(UI7Color_Button, Color("#111111FF")); theme.Set(UI7Color_ButtonDead, Color("#080808FF")); theme.Set(UI7Color_ButtonActive, Color("#2A2A2AFF")); - theme.Set(UI7Color_ButtonDisabled, Color("#222222FF")); + theme.Set(UI7Color_ButtonHovered, Color("#222222FF")); theme.Set(UI7Color_Header, Color("#111111FF")); theme.Set(UI7Color_Selector, Color("#222222FF")); theme.Set(UI7Color_Checkmark, Color("#2A2A2AFF")); theme.Set(UI7Color_FrameBackground, Color("#555555FF")); - theme.Set(UI7Color_FragmeBackgroundHovered, Color("#777777FF")); + theme.Set(UI7Color_FrameBackgroundHovered, Color("#777777FF")); theme.Set(UI7Color_Progressbar, Color("#00FF00FF")); theme.Set(UI7Color_ListEven, Color("#CCCCCCFF")); theme.Set(UI7Color_ListOdd, Color("#BBBBBBFF")); diff --git a/source/ui7/ui7.cpp b/source/ui7/ui7.cpp index e69de29..90f7a68 100644 --- a/source/ui7/ui7.cpp +++ b/source/ui7/ui7.cpp @@ -0,0 +1,52 @@ +#include +#include + +namespace PD { +bool UI7::Context::BeginMenu(const ID& id, UI7MenuFlags flags) { + Assert(!this->current, "You are already in another Menu!"); + Assert(std::find(amenus.begin(), amenus.end(), (u32)id) == amenus.end(), + "Menu Name Already used or\nContext::Update not called!"); + auto menu = this->menus.find(id); + if (menu == this->menus.end()) { + this->menus[id] = Menu::New(id, &theme, inp); + menu = this->menus.find(id); + } + this->current = menu->second; + if (!this->current->BackList()) { + this->current->BackList(DrawList::New(ren)); + } + if (!this->current->MainList()) { + this->current->MainList(DrawList::New(ren)); + } + if (!this->current->FrontList()) { + this->current->FrontList(DrawList::New(ren)); + } + this->current->ViewArea(this->ren->GetViewport()); + this->current->PreHandler(flags); + amenus.push_back(this->current->GetID()); + return true; +} + +UI7::Menu::Ref UI7::Context::GetCurrentMenu() { + Assert(current != nullptr, "Not in a Menu!"); + return current; +} + +void UI7::Context::EndMenu() { + this->current->PostHandler(); + this->current = nullptr; +} + +void UI7::Context::Update(float delta) { + TT::Scope st("UI7_Update"); + Assert(current == nullptr, "Still in a Menu!"); + this->back->BaseLayer(10); + this->back->Process(); + for (auto it : amenus) { + menus[it]->Update(delta); + } + this->front->BaseLayer(60); + this->front->Process(); + this->amenus.clear(); +} +} // namespace PD \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp index 4305922..09d9d90 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -27,54 +27,82 @@ SOFTWARE. #include #include -using vec2 = PD::vec2; -using vec3 = PD::vec3; -using vec4 = PD::vec4; - class Test : public PD::App { public: Test() = default; ~Test() = default; void Init() override { + ren = Renderer(); + inp = Input(); test = PD::Texture::New("romfs:/icon.png"); - Overlays()->Push(PD::New(dbg, dbg_screen)); + // Performance Overlay freezes N3DS + // Overlays()->Push(PD::New(dbg, dbg_screen)); font = PD::LI::Font::New(); - // font->LoadSystemFont(); - font->LoadTTF("romfs:/ComicNeue.ttf", 32); - Renderer()->Font(font); - ui7 = PD::UI7::Context::New(); + // font->LoadTTF("romfs:/fonts/ComicNeue.ttf", 32); + font->LoadTTF("romfs:/fonts/JetBrainsMono-Medium.ttf", 32); + ren->Font(font); + ui7 = PD::UI7::Context::New(ren, inp); } bool MainLoop(float delta, float time) override { DrawFancyBG(time); - Renderer()->OnScreen(PD::Screen::Bottom); - Renderer()->DrawRectSolid(0, vec2(320, 240), PD::Color("#222222")); - Renderer()->UseTex(test); - Renderer()->Layer(Renderer()->Layer() + 1); - Renderer()->DrawImage( - Renderer()->GetViewport().zw() * 0.5 - test->GetSize() * 0.5, test); - Renderer()->DrawText(5, 0xffffffff, "Hello World!", LITextFlags_None); - if (Input()->IsDown(PD::Hid::Start)) { + ren->OnScreen(PD::Screen::Bottom); + // ren->DrawRectSolid(0, vec2(320, 240), PD::Color("#222222")); + // ren->Layer(ren->Layer() + 1); + // ren->DrawImage(ren->GetViewport().zw() * 0.5 - test->GetSize() * 0.5, + // test); ren->DrawText(5, 0xffffffff, "Hello World!", LITextFlags_None); + if (ui7->BeginMenu("Test", + UI7MenuFlags_Scrolling | UI7MenuFlags_CenterTitle)) { + auto m = ui7->GetCurrentMenu(); + m->SeparatorText("Menu Timings"); + m->DebugLabels(); + m->SeparatorText("Lithium Settings"); + FlagBox(m, "LI AST", PD::LI::RenderFlags_AST); + FlagBox(m, "LI LRS", PD::LI::RenderFlags_LRS); + FlagBox(m, "LI TMS", PD::LI::RenderFlags_TMS); + m->SeparatorText("UI7 Tests"); + m->Label("This seems to be a label"); + m->Separator(); + m->Button("Button?"); + m->SeparatorText("SeparatorText"); + m->Checkbox("Test", cbtest); + for (int i = 0; i < 10; i++) { + m->Label("Label: " + std::to_string(i)); + } + ui7->EndMenu(); + } + ui7->Update(delta); + if (inp->IsDown(PD::Hid::Start)) { return false; } - if (Input()->IsDown(Input()->A)) { + if (inp->IsDown(inp->A)) { Overlays()->Push(PD::New(dbg, dbg_screen)); Messages()->Push("Test", "Oder SO"); - // what.To(vec2(5, 200)).From(vec2(-100, - // 200)).In(0.5).As(what.EaseInQuad); } - if (Input()->IsUp(Input()->B)) { + if (inp->IsUp(inp->B)) { Overlays()->Push(PD::New(text, state)); - // what.To(vec2(5, 180)).From(vec2(5, 200)).In(0.5).As(what.EaseOutQuad); } return true; } void Deinit() override {} + void FlagBox(PD::UI7::Menu::Ref m, const std::string& label, + PD::LI::RenderFlags flag) { + bool has_flag = ren->GetFlags() & flag; + m->Checkbox(label, has_flag); + if (has_flag != (ren->GetFlags() & flag)) { + if (has_flag) { + ren->GetFlags() |= flag; + } else { + ren->GetFlags() &= ~flag; + } + } + } + void DrawFancyBG(float time) { - Renderer()->DrawRect(vec2(0, 0), vec2(400, 240), 0xff64c9fd); + ren->DrawRect(vec2(0, 0), vec2(400, 240), 0xff64c9fd); for (int i = 0; i < 44; i++) Append(i, vec2(0, 0), vec2(400, 240), time); } @@ -90,7 +118,7 @@ class Test : public PD::App { sin(offset + time) * 10 + 30; float color_effect = 1 - exp(-(index / 11) / 3.0f); - Renderer()->DrawTriangle( + ren->DrawTriangle( vec2(x_position, y_position), vec2(x_position + 300, y_position + (90)), vec2(x_position - 300, y_position + (90)), PD::Color(.94f - .17f * color_effect, .61f - .25f * color_effect, @@ -98,8 +126,13 @@ class Test : public PD::App { } private: + /// Shorter Acess to Renderer / Input + PD::LI::Renderer::Ref ren; + PD::Hid::Ref inp; + /// Other Data PD::Texture::Ref test; bool dbg = false, dbg_screen = false; + bool cbtest = true; std::string text; PD::Keyboard::State state; PD::UI7::Context::Ref ui7; @@ -108,7 +141,7 @@ class Test : public PD::App { }; int main() { - auto app = PD::New(); - app->Run(); + Test app; + app.Run(); return 0; } \ No newline at end of file diff --git a/test/romfs/fonts/ComicNeue.md b/test/romfs/fonts/ComicNeue.md new file mode 100644 index 0000000..80062cf --- /dev/null +++ b/test/romfs/fonts/ComicNeue.md @@ -0,0 +1,93 @@ +Copyright 2014 The Comic Neue Project Authors (https://github.com/crozynski/comicneue) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/test/romfs/ComicNeue.ttf b/test/romfs/fonts/ComicNeue.ttf similarity index 100% rename from test/romfs/ComicNeue.ttf rename to test/romfs/fonts/ComicNeue.ttf diff --git a/test/romfs/fonts/JetBrainsMono-Medium.ttf b/test/romfs/fonts/JetBrainsMono-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ad71d92bb1c4dd516e10bc0f5d7c446ea7df6023 GIT binary patch literal 114920 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