diff --git a/external/rlguipp/CMakeLists.txt b/external/rlguipp/CMakeLists.txt index 96a2ebb..56a09f6 100644 --- a/external/rlguipp/CMakeLists.txt +++ b/external/rlguipp/CMakeLists.txt @@ -5,3 +5,4 @@ add_library(rlguipp STATIC rlguipp.cpp rlguipp.hpp raygui4.h rlunicode.h dm_prop target_include_directories(rlguipp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) target_link_libraries(rlguipp PUBLIC raylib_static) +add_subdirectory(test) \ No newline at end of file diff --git a/external/rlguipp/rlguipp.cpp b/external/rlguipp/rlguipp.cpp index fdcb65f..c4ad1f0 100644 --- a/external/rlguipp/rlguipp.cpp +++ b/external/rlguipp/rlguipp.cpp @@ -178,7 +178,7 @@ struct GuiContext bool horizontal{false}; bool bordered{false}; int level{0}; - float maxSize{0}; + Vector2 maxSize{}; float rowHeight{DEFAULT_ROW_HEIGHT}; float nextWidth{-1}; float nextHeight{-1}; @@ -201,13 +201,13 @@ struct GuiContext { auto x = currentPos.x; auto y = currentPos.y; + maxSize.x = std::max(maxSize.x, size.x); + maxSize.y = std::max(maxSize.y, size.y); if (horizontal) { currentPos.x += size.x + spacingH; - maxSize = std::max(size.y, maxSize); } else { currentPos.y += size.y + spacingV; - maxSize = std::max(size.x, maxSize); } nextWidth = -1; nextHeight = -1; @@ -217,10 +217,10 @@ struct GuiContext { if (horizontal) { currentPos.x = area.x; - currentPos.y += maxSize; + currentPos.y += maxSize.y; } else { - currentPos.x += maxSize; + currentPos.x += maxSize.x; currentPos.y = area.y; } } @@ -357,6 +357,7 @@ inline GuiContext& GuiContext::newContext(const std::string& key) const auto& ctx = g_contextStack.top(); ctx.childContextCount = 0; ctx.hash = detail::fnv_64a_str(key.c_str(), ctx.hash, ++g_contextStack.top().childContextCount); + ctx.maxSize = {}; return ctx; } @@ -593,21 +594,23 @@ void Begin() ctx.bordered = false; ctx.level++; ctx.nextWidth = ctx.nextHeight = -1; - ctx.maxSize = 0; } -static void EndImpl() +static void EndImpl(Vector2 size = {}) { auto ctxOld = g_contextStack.top(); g_contextStack.pop(); auto& ctx = detail::context(); - if (ctxOld.horizontal) { + if (size.x > 0 && size.y > 0) { + ctx.increment(size); + } + else if (ctxOld.horizontal) { // DrawLine(ctxOld.area.x, ctxOld.area.y, ctxOld.currentPos.x, ctxOld.initialPos.y + ctxOld.maxSize, RED); - ctx.increment({ctxOld.currentPos.x - ctxOld.area.x, ctxOld.maxSize}); + ctx.increment({ctxOld.currentPos.x - ctxOld.area.x + ctxOld.padding.x - ctxOld.spacingH, ctxOld.maxSize.y + ctxOld.padding.y*2}); } else { // DrawLine(ctxOld.area.x, ctxOld.area.y, ctxOld.initialPos.x + ctxOld.maxSize, ctxOld.currentPos.y, GREEN); - ctx.increment({ctxOld.maxSize, ctxOld.currentPos.y - ctxOld.area.y}); + ctx.increment({ctxOld.maxSize.x + ctxOld.padding.x*2, ctxOld.currentPos.y - ctxOld.area.y + ctxOld.padding.y - ctxOld.spacingV}); } } @@ -631,11 +634,12 @@ void BeginColumns() void EndColumns() { - if (!detail::context().horizontal) { + auto ctx = detail::context(); + if (!ctx.horizontal) { throw std::runtime_error("Unbalanced gui::BeginColumns/gui::EndColumns!"); } // detail::context().horizontal = false; - EndImpl(); + EndImpl({ctx.currentPos.x - ctx.area.x, ctx.maxSize.y}); } void BeginPanel(const char* text, Vector2 padding) @@ -660,7 +664,6 @@ void BeginPanel(const char* text, Vector2 padding) ctx.level++; ctx.groupName = text ? text : ""; ctx.nextWidth = ctx.nextHeight = -1; - ctx.maxSize = 0; ctx.padding = padding; } @@ -668,15 +671,14 @@ void EndPanel() { auto& ctx = detail::context(); if (ctx.level > 1) { - GuiDrawRectangle({ctx.area.x, ctx.area.y, ctx.area.width, ctx.currentPos.y - ctx.area.y + ctx.padding.y}, 1, Fade(GetColor(gui::GetStyle(DEFAULT, LINE_COLOR)), guiAlpha), {0, 0, 0, 0}); + GuiDrawRectangle({ctx.area.x, ctx.area.y, ctx.area.width, ctx.currentPos.y - ctx.area.y + ctx.padding.y - ctx.spacingV}, 1, Fade(GetColor(gui::GetStyle(DEFAULT, LINE_COLOR)), guiAlpha), {0, 0, 0, 0}); } else { GuiDrawRectangle({ctx.area.x, ctx.area.y, ctx.area.width, ctx.area.height}, 1, Fade(GetColor(gui::GetStyle(DEFAULT, LINE_COLOR)), guiAlpha), {0, 0, 0, 0}); } // ctx.increment({0, ctx.currentPos.y - ctx.area.y + (ctx.groupName.empty() ? 10 : RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + 10)}); //ctx.increment({1,2}); - ctx.maxSize = ctx.area.width; - End(); + EndImpl(); } void BeginTabView(int *activeTab) @@ -752,7 +754,6 @@ bool BeginTab(const char* text, Vector2 padding) ctx.level++; ctx.groupName = text ? text : ""; ctx.nextWidth = ctx.nextHeight = -1; - ctx.maxSize = 0; ctx.padding = padding; return true; } @@ -769,16 +770,16 @@ void EndTab() } g_contextStack.pop(); auto& ctxParent = detail::context(); - ctxOld.maxSize = ctxOld.area.width; + ctxOld.maxSize = {ctxOld.area.width, ctx.area.height}; auto& tvc = *std::get(ctxParent.contextData); if (ctxOld.horizontal) { // DrawLine(ctxOld.area.x, ctxOld.area.y, ctxOld.currentPos.x, ctxOld.initialPos.y + ctxOld.maxSize, RED); tvc.incX = ctxOld.currentPos.x - ctxOld.area.x; - tvc.incY = ctxOld.maxSize; + tvc.incY = ctxOld.maxSize.y; } else { // DrawLine(ctxOld.area.x, ctxOld.area.y, ctxOld.initialPos.x + ctxOld.maxSize, ctxOld.currentPos.y, GREEN); - tvc.incX = ctxOld.maxSize; + tvc.incX = ctxOld.maxSize.x; tvc.incY = ctxOld.currentPos.y - ctxOld.area.y; } } @@ -797,7 +798,6 @@ void BeginScrollPanel(float height, Rectangle content, Vector2 *scroll) ctx.bordered = false; ctx.level = 0; ctx.nextWidth = ctx.nextHeight = -1; - ctx.maxSize = 0; ctx.padding = {0, 0}; Rectangle view; @@ -813,7 +813,8 @@ void EndScrollPanel() throw std::runtime_error("Unbalanced gui::BeginScrollPanel/gui::EndScrollPanel!"); } EndScissorMode(); - EndPanel(); + auto& ctx = detail::context(); + EndImpl({ctx.area.width, ctx.area.height}); //End(); //auto ctxOld = g_contextStack.top(); //g_contextStack.pop(); @@ -915,7 +916,6 @@ void BeginGroupBox(const char* text) ctx.level++; ctx.groupName = text ? text : ""; ctx.nextWidth = ctx.nextHeight = -1; - ctx.maxSize = 0; ctx.padding = {0, 0}; } @@ -948,7 +948,6 @@ void BeginPopup(Rectangle area, bool* isOpen) ctx.bordered = false; ctx.level = 0; ctx.nextWidth = ctx.nextHeight = -1; - ctx.maxSize = 0; ctx.padding = {0, 0}; if (std::holds_alternative(ctxParent.contextData)) { @@ -1062,6 +1061,14 @@ void SetNextWidth(float width) ctx.nextWidth = width; } +void SetNextHeight(float height) +{ + auto& ctx = detail::context(); + if(height <= 1.0f) + height = std::floor(ctx.content.height * height); + ctx.nextHeight = height; +} + void SetRowHeight(float height) { detail::context().rowHeight = height; @@ -1637,7 +1644,6 @@ bool BeginMenuBar() ctx.level++; ctx.groupName = ""; ctx.nextWidth = ctx.nextHeight = -1; - ctx.maxSize = 0; ctx.padding = {5, 0}; if (GetState() == STATE_DISABLED) { return false; @@ -1690,7 +1696,6 @@ bool BeginMenu(const char* text) ctx.level++; ctx.groupName = ""; ctx.nextWidth = ctx.nextHeight = -1; - ctx.maxSize = 0; ctx.padding = {5, 0}; ctx.contextData = &mctx; return true; diff --git a/external/rlguipp/rlguipp.hpp b/external/rlguipp/rlguipp.hpp index 7070045..56d0627 100644 --- a/external/rlguipp/rlguipp.hpp +++ b/external/rlguipp/rlguipp.hpp @@ -127,6 +127,7 @@ RLGUIPP_API int GetStyle(int control, int property); RLGUIPP_API void SetIndent(float width); // Indents all following elements in this level RLGUIPP_API void SetReserve(float width); // Sets the space reserved for right side labels of bar widgets RLGUIPP_API void SetNextWidth(float width); // Set the width of the next element, default is the width of the parent +RLGUIPP_API void SetNextHeight(float height); // Set the height of the next element, default is the row height RLGUIPP_API void SetRowHeight(float height); // Set the height for elements that can be typically in a row, like buttons, edit fields, spinner... RLGUIPP_API void SetSpacing(float spacing); // Set the spacing, depending on the parent layout it will set the horizontal or vertical spacing RLGUIPP_API Vector2 GetCurrentPos(); // Get the position that will be used by the next widget diff --git a/external/rlguipp/test/CMakeLists.txt b/external/rlguipp/test/CMakeLists.txt new file mode 100644 index 0000000..3cddc08 --- /dev/null +++ b/external/rlguipp/test/CMakeLists.txt @@ -0,0 +1,4 @@ +if(NOT EMSCRIPTEN) + add_executable(rlguipp_test rlguipp_test.cpp stylemanager.cpp stylemanager.hpp) + target_link_libraries(rlguipp_test PUBLIC rlguipp fmt::fmt) +endif() diff --git a/external/rlguipp/test/rlguipp_test.cpp b/external/rlguipp/test/rlguipp_test.cpp new file mode 100644 index 0000000..b491a45 --- /dev/null +++ b/external/rlguipp/test/rlguipp_test.cpp @@ -0,0 +1,288 @@ +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2022, Steffen Schümann +// +// 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. +// +//--------------------------------------------------------------------------------------- +#include +#include + +class RlGuippApp +{ +public: + RlGuippApp(int width, int height, int minWidth = 640, int minHeight = 400) + : MIN_WIDTH(minWidth), MIN_HEIGHT(minHeight) + , _width(width), _height(height) + , _scale(2) + { + InitWindow(_width * _scale, _height * _scale, "rlGui++ Test"); + SetMouseScale(1.0f/_scale, 1.0f/_scale); + SetTargetFPS(60); + _renderTexture = LoadRenderTexture(_width, _height); + SetTextureFilter(_renderTexture.texture, TEXTURE_FILTER_POINT); + _styleManager.setTheme(0); + } + ~RlGuippApp() + { + UnloadRenderTexture(_renderTexture); + CloseWindow(); + } + + static void updateAndDrawFrame(void* self) + { + static_cast(self)->updateAndDraw(); + } + + void updateAndDraw() + { + updateResolution(); + + BeginTextureMode(_renderTexture); + drawGui(); + EndTextureMode(); + + BeginDrawing(); + { + ClearBackground(_backgroundColor); +#ifdef RESIZABLE_GUI + Vector2 guiOffset = {(GetScreenWidth() - _screenWidth*screenScale)/2.0f, (GetScreenHeight() - _screenHeight*screenScale)/2.0f}; + if(guiOffset.x < 0) guiOffset.x = 0; + if(guiOffset.y < 0) guiOffset.y = 0; + if (_scaleBy2) { + drawScreen({_screenOverlay.x * 2, _screenOverlay.y * 2, _screenOverlay.width * 2, _screenOverlay.height * 2}, _screenScale); + DrawTexturePro(_renderTexture.texture, (Rectangle){0, 0, (float)_renderTexture.texture.width, -(float)_renderTexture.texture.height}, (Rectangle){0, 0, (float)_renderTexture.texture.width * 2, (float)_renderTexture.texture.height * 2}, + (Vector2){0, 0}, 0.0f, WHITE); + } + else { + drawScreen(_screenOverlay, _screenScale); + DrawTextureRec(_renderTexture.texture, (Rectangle){0, 0, (float)_renderTexture.texture.width, -(float)_renderTexture.texture.height}, (Vector2){0, 0}, WHITE); + } + //DrawTexturePro(_renderTexture.texture, (Rectangle){0, 0, (float)_renderTexture.texture.width, -(float)_renderTexture.texture.height}, (Rectangle){guiOffset.x, guiOffset.y, (float)_renderTexture.texture.width * screenScale, (float)_renderTexture.texture.height * screenScale}, + // (Vector2){0, 0}, 0.0f, WHITE); +#else + //drawScreen({_screenOverlay.x * _scaleMode, _screenOverlay.y * _scaleMode, _screenOverlay.width * _scaleMode, _screenOverlay.height * _scaleMode}, _screenScale); + DrawTexturePro(_renderTexture.texture, (Rectangle){0, 0, (float)_renderTexture.texture.width, -(float)_renderTexture.texture.height}, (Rectangle){0, 0, (float)_renderTexture.texture.width * _scale, (float)_renderTexture.texture.height * _scale}, + (Vector2){0, 0}, 0.0f, WHITE); +#endif +#if 0 + int width{0}, height{0}; +#if defined(PLATFORM_WEB) + double devicePixelRatio = emscripten_get_device_pixel_ratio(); + width = GetScreenWidth() * devicePixelRatio; + height = GetScreenHeight() * devicePixelRatio; +#else + glfwGetFramebufferSize(glfwGetCurrentContext(), &width, &height); +#endif + //TraceLog(LOG_INFO, "Window resized: %dx%d, fb: %dx%d", GetScreenWidth(), GetScreenHeight(), width, height); + DrawText(TextFormat("Window resized: %dx%d, fb: %dx%d, rzc: %d", GetScreenWidth(), GetScreenHeight(), width, height, resizeCount), 10,30,10,GREEN); +#endif + // DrawText(TextFormat("Res: %dx%d", GetMonitorWidth(GetCurrentMonitor()), GetMonitorHeight(GetCurrentMonitor())), 10, 30, 10, GREEN); + // DrawFPS(10,45); + } + EndDrawing(); + } + + void updateResolution() + { +#ifdef RESIZABLE_GUI +#if 1 + static int resizeCount = 0; + if (IsWindowResized()) { + int width{0}, height{0}; + resizeCount++; +#if defined(PLATFORM_WEB) + double devicePixelRatio = emscripten_get_device_pixel_ratio(); + width = GetScreenWidth() * devicePixelRatio; + height = GetScreenHeight() * devicePixelRatio; +#else + // TODO: glfwGetFramebufferSize(glfwGetCurrentContext(), &width, &height); +#endif + TraceLog(LOG_INFO, "Window resized: %dx%d, fb: %dx%d", GetScreenWidth(), GetScreenHeight(), width, height); + } +#endif + auto screenScale = std::min(std::clamp(int(GetScreenWidth() / _screenWidth), 1, 8), std::clamp(int(GetScreenHeight() / _screenHeight), 1, 8)); + SetMouseScale(1.0f/screenScale, 1.0f/screenScale); +#else + if (!_scale || GetMonitorWidth(GetCurrentMonitor()) <= _width * _scale) { + _scale = 1; + } + if (GetScreenWidth() != _width * _scale) { + SetWindowSize(_width * _scale, _height * _scale); + //CenterWindow(_screenWidth * 2, _screenHeight * 2); + SetMouseScale(1.0f/_scale, 1.0f/_scale); + } +#endif + +#ifdef RESIZABLE_GUI + auto width = std::max(GetScreenWidth(), _width) / (_scaleBy2 ? 2 : 1); + auto height = std::max(GetScreenHeight(), _height) / (_scaleBy2 ? 2 : 1); + + if(GetScreenWidth() < width || GetScreenHeight() < height) + SetWindowSize(width, height); + if(width != _width || height != _height) { + UnloadRenderTexture(_renderTexture); + _width = width; + _height = height; + _renderTexture = LoadRenderTexture(_width, _height); + SetTextureFilter(_renderTexture.texture, TEXTURE_FILTER_POINT); + } +#else + if(_height < MIN_HEIGHT ||_width < MIN_WIDTH) { + UnloadRenderTexture(_renderTexture); + _width = MIN_WIDTH; + _height = MIN_HEIGHT; + _renderTexture = LoadRenderTexture(_width, _height); + SetTextureFilter(_renderTexture.texture, TEXTURE_FILTER_POINT); + SetWindowSize(_width * _scale, _height * _scale); + } +#endif + } + + bool windowShouldClose() const + { + return _shouldClose || WindowShouldClose(); + } + + Vector2 guiScaling() const { return {static_cast(_scale), static_cast(_scale)}; } + + static bool iconButton(int iconId, bool isPressed = false, Color color = {3, 127, 161}, Color foreground = {0x51, 0xbf, 0xd3, 0xff}) + { + using namespace gui; + StyleManager::Scope guard; + auto fg = guard.getStyle(Style::TEXT_COLOR_NORMAL); + auto bg = guard.getStyle(Style::BASE_COLOR_NORMAL); + if(isPressed) { + guard.setStyle(Style::BASE_COLOR_NORMAL, fg); + guard.setStyle(Style::TEXT_COLOR_NORMAL, bg); + } + //guard.setStyle(Style::TEXT_COLOR_NORMAL, foreground); + SetNextWidth(20); + auto result = Button(GuiIconText(iconId, "")); + return result; + } + + void drawGui() + { + using namespace gui; + ClearBackground(GetColor(GetStyle(DEFAULT, BACKGROUND_COLOR))); + BeginGui({}, &_renderTexture, {0, 0}, guiScaling()); + { + SetStyle(STATUSBAR, TEXT_PADDING, 4); + SetStyle(LISTVIEW, SCROLLBAR_WIDTH, 6); + SetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 0); + SetStyle(SPINNER, TEXT_PADDING, 4); + SetRowHeight(16); + SetSpacing(0); + + StatusBar({{0.3f, "Status"}, {0.7f, "Bar"}}); + + SetSpacing(0); + BeginColumns(); + { + auto start = GetContentAvailable().x; + auto width = GetContentAvailable().width; + SetSpacing(0); + SetRowHeight(20); + if (iconButton(ICON_BURGER_MENU, false)) {} + if (iconButton(ICON_ROM, false)) {} + Space(GetContentAvailable().width - 20); + auto r = GetLastWidgetRect(); + DrawRectangleRec(r, StyleManager::getStyleColor(Style::BASE_COLOR_NORMAL)); + if (iconButton(ICON_HIDPI, _scale != 1)) + _scale = _scale >= 3 ? 1 : _scale + 1; + SetTooltip("TOGGLE ZOOM "); + } + EndColumns(); + + Begin(); + BeginPanel("Library / Research"); + { + SetSpacing(5); + TextBox(_queryLine, 4096); + auto area = GetContentAvailable(); + BeginColumns(); + { + auto tagsWidth = area.width / 3 - 5; + auto listWidth = area.width - tagsWidth - 5; + SetSpacing(0); + SetNextWidth(tagsWidth); + SetNextHeight(area.height - 135); +#if 0 + BeginPanel("Panel1"); + Label("Some label"); + Label("Another one"); + EndPanel(); + SetNextWidth(tagsWidth); + BeginPanel("Panel2"); + Label("Some label"); + EndPanel(); +#else +#if 0 + static Vector2 scrollPos{}; + BeginScrollPanel(area.height - 135, {0,0,100,1000}, &scrollPos); + EndScrollPanel(); + BeginScrollPanel(area.height - 135, {0,0,100,1000}, &scrollPos); + EndScrollPanel(); +#else + BeginTableView(area.height - 135, 2, &_tagsScrollPos); + TableNextRow(22); + TableNextColumn(64); + Label("Table1"); + EndTableView(); + SetNextWidth(tagsWidth); + BeginTableView(area.height - 135, 2, &_tagsScrollPos); + TableNextRow(22); + TableNextColumn(64); + Label("Table2"); + EndTableView(); +#endif +#endif + } + EndColumns(); + TextBox(_queryLine, 4096); + } + //Space(_screenHeight - GetCurrentPos().y - 20 - 1); + EndPanel(); + End(); + } + EndGui(); + } + +private: + const int MIN_WIDTH = 640; + const int MIN_HEIGHT = 400; + int _width{}; + int _height{}; + std::string _queryLine; + Vector2 _tagsScrollPos{}; + RenderTexture _renderTexture; + Color _backgroundColor{BLACK}; + gui::StyleManager _styleManager{}; + bool _shouldClose{false}; + int _scale{1}; +}; + +int main() { + RlGuippApp app(640, 480); + while (!app.windowShouldClose()) { + app.updateAndDraw(); + } + return 0; +} \ No newline at end of file diff --git a/external/rlguipp/test/stylemanager.cpp b/external/rlguipp/test/stylemanager.cpp new file mode 100644 index 0000000..68d8dcd --- /dev/null +++ b/external/rlguipp/test/stylemanager.cpp @@ -0,0 +1,442 @@ +//--------------------------------------------------------------------------------------- +// src/emulation/stylemanager.hpp +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2023, Steffen Schümann +// +// 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. +// +//--------------------------------------------------------------------------------------- + +#include + +#include +#include + +#include + +namespace gui { +#if 0 +static const uint32_t cadmiumPalette[7] = { + 0x00222bff, //E = 0! + 0x134b5aff, //D = 1! + 0x2f7486ff, //F = 2! + 0x3299b4ff, //B = 3! + 0x51bfd3ff, //G = 4! + 0x82cde0ff, //A = 5! + 0xeff8ffff //C = 6! +}; +#else +static const uint32_t cadmiumPalette[7] = { + 0x00222bff, //E = 0! + 0x134b5aff, //D = 1! + 0x2f7486ff, //F = 2! + 0x3299b4ff, //B = 3! + 0x51bfd3ff, //G = 4! + 0x85d2e6ff, //A = 5! + 0xeff8ffff //C = 6! +}; +#endif +static float cadmiumAverageHue = 200.0f; + + +#define CHIP8_STYLE_PROPS_COUNT 16 +static const StyleManager::Entry chip8StyleProps[CHIP8_STYLE_PROPS_COUNT] = { + {0, 0, 2}, // F! DEFAULT_BORDER_COLOR_NORMAL + {0, 1, 1}, // D! DEFAULT_BASE_COLOR_NORMAL + {0, 2, 4}, // G! DEFAULT_TEXT_COLOR_NORMAL + {0, 3, 5}, // A! DEFAULT_BORDER_COLOR_FOCUSED + {0, 4, 3}, // B! DEFAULT_BASE_COLOR_FOCUSED + {0, 5, 6}, // C! DEFAULT_TEXT_COLOR_FOCUSED + {0, 6, 5}, // A! DEFAULT_BORDER_COLOR_PRESSED + {0, 7, 3}, // B! DEFAULT_BASE_COLOR_PRESSED + {0, 8, 6}, // C! DEFAULT_TEXT_COLOR_PRESSED + {0, 9, 1}, // D! DEFAULT_BORDER_COLOR_DISABLED + {0, 10, 0}, // E! DEFAULT_BASE_COLOR_DISABLED + {0, 11, 1}, // D! DEFAULT_TEXT_COLOR_DISABLED + {0, 18, 5}, // A! DEFAULT_LINE_COLOR + {0, 19, 0}, // E! DEFAULT_BACKGROUND_COLOR + {0, 16, 0x0000000a}, // DEFAULT_TEXT_SIZE + {0, 17, 0x00000001}, // DEFAULT_TEXT_SPACING +}; + +#if 0 +static const StyleManager::Entry chip8StyleProps[CHIP8_STYLE_PROPS_COUNT] = { + {0, 0, 0x2f7486ff}, // F! DEFAULT_BORDER_COLOR_NORMAL + //{0, 1, 0x024658ff}, // DEFAULT_BASE_COLOR_NORMAL + {0, 1, 0x134b5aff}, // D! DEFAULT_BASE_COLOR_NORMAL + {0, 2, 0x51bfd3ff}, // G! DEFAULT_TEXT_COLOR_NORMAL + {0, 3, 0x82cde0ff}, // A! DEFAULT_BORDER_COLOR_FOCUSED + {0, 4, 0x3299b4ff}, // B! DEFAULT_BASE_COLOR_FOCUSED + //{0, 5, 0xb6e1eaff}, // DEFAULT_TEXT_COLOR_FOCUSED + {0, 5, 0xeff8ffff}, // C! DEFAULT_TEXT_COLOR_FOCUSED + {0, 6, 0x82cde0ff}, // A! DEFAULT_BORDER_COLOR_PRESSED + {0, 7, 0x3299b4ff}, // B! DEFAULT_BASE_COLOR_PRESSED + {0, 8, 0xeff8ffff}, // C! DEFAULT_TEXT_COLOR_PRESSED + //{0, 9, 0x134b5aff}, // DEFAULT_BORDER_COLOR_DISABLED + {0, 9, 0x134b5aff}, // D! DEFAULT_BORDER_COLOR_DISABLED + {0, 10, 0x00222bff}, // E! DEFAULT_BASE_COLOR_DISABLED + //{0, 11, 0x17505fff}, // DEFAULT_TEXT_COLOR_DISABLED + {0, 11, 0x134b5aff}, // D! DEFAULT_TEXT_COLOR_DISABLED + {0, 18, 0x82cde0ff}, // A! DEFAULT_LINE_COLOR + {0, 19, 0x00222bff}, // E! DEFAULT_BACKGROUND_COLOR + {0, 16, 0x00000008}, // DEFAULT_TEXT_SIZE + {0, 17, 0x00000000}, // DEFAULT_TEXT_SPACING +}; + +static const StyleManager::Entry chip8DarkStyleProps[] = { + /* + { 0, 0, 0x878787ff }, // DEFAULT_BORDER_COLOR_NORMAL + //{ 0, 1, 0x2c2c2cff }, // DEFAULT_BASE_COLOR_NORMAL + { 0, 1, 0x3c3c40ff }, // DEFAULT_BASE_COLOR_NORMAL + { 0, 2, 0xc3c3d0ff }, // DEFAULT_TEXT_COLOR_NORMAL + { 0, 3, 0xe1e1f0ff }, // DEFAULT_BORDER_COLOR_FOCUSED + { 0, 4, 0x84848cff }, // DEFAULT_BASE_COLOR_FOCUSED + { 0, 5, 0x18181cff }, // DEFAULT_TEXT_COLOR_FOCUSED + { 0, 6, 0x000000ff }, // DEFAULT_BORDER_COLOR_PRESSED + { 0, 7, 0xefeffeff }, // DEFAULT_BASE_COLOR_PRESSED + { 0, 8, 0x202024ff }, // DEFAULT_TEXT_COLOR_PRESSED + { 0, 9, 0x6a6a70ff }, // DEFAULT_BORDER_COLOR_DISABLED + { 0, 10, 0x818188ff }, // DEFAULT_BASE_COLOR_DISABLED + { 0, 11, 0x606066ff }, // DEFAULT_TEXT_COLOR_DISABLED + { 0, 16, 0x0000000e }, // DEFAULT_TEXT_SIZE + { 0, 17, 0x00000000 }, // DEFAULT_TEXT_SPACING + { 0, 18, 0x9d9da4ff }, // DEFAULT_LINE_COLOR + //{ 0, 19, 0x3c3c3cff }, // DEFAULT_BACKGROUND_COLOR + { 0, 19, 0x2c2c30ff }, // DEFAULT_BACKGROUND_COLOR + + { 0, 20, 0x00000018 }, // DEFAULT_TEXT_LINE_SPACING + { 1, 5, 0xf7f7ffff }, // LABEL_TEXT_COLOR_FOCUSED + { 1, 8, 0x898990ff }, // LABEL_TEXT_COLOR_PRESSED + { 4, 5, 0xb0b0bbff }, // SLIDER_TEXT_COLOR_FOCUSED + { 5, 5, 0x84848cff }, // PROGRESSBAR_TEXT_COLOR_FOCUSED + { 9, 5, 0xf5f5ffff }, // TEXTBOX_TEXT_COLOR_FOCUSED + { 10, 5, 0xf6f6ffff }, // VALUEBOX_TEXT_COLOR_FOCUSED + */ + { 0, 0, 0x787c86ff }, // DEFAULT_BORDER_COLOR_NORMAL + //{ 0, 1, 0x27282bff }, // DEFAULT_BASE_COLOR_NORMAL + { 0, 1, 0x36383cff }, // DEFAULT_BASE_COLOR_NORMAL + { 0, 2, 0xafb4c3ff }, // DEFAULT_TEXT_COLOR_NORMAL + { 0, 3, 0xc9cfe0ff }, // DEFAULT_BORDER_COLOR_FOCUSED + { 0, 4, 0x7c808aff }, // DEFAULT_BASE_COLOR_FOCUSED + { 0, 5, 0x151617ff }, // DEFAULT_TEXT_COLOR_FOCUSED + { 0, 6, 0x000000ff }, // DEFAULT_BORDER_COLOR_PRESSED + { 0, 7, 0xd8deefff }, // DEFAULT_BASE_COLOR_PRESSED + { 0, 8, 0x1e1f21ff }, // DEFAULT_TEXT_COLOR_PRESSED + { 0, 9, 0x60636bff }, // DEFAULT_BORDER_COLOR_DISABLED + { 0, 10, 0x757882ff }, // DEFAULT_BASE_COLOR_DISABLED + { 0, 11, 0x55585eff }, // DEFAULT_TEXT_COLOR_DISABLED + { 0, 16, 0x00000010 }, // DEFAULT_TEXT_SIZE + { 0, 17, 0x00000000 }, // DEFAULT_TEXT_SPACING + { 0, 18, 0x8e929dff }, // DEFAULT_LINE_COLOR + //{ 0, 19, 0x36383cff }, // DEFAULT_BACKGROUND_COLOR + { 0, 19, 0x1c1d1eff }, // DEFAULT_BACKGROUND_COLOR + { 0, 20, 0x00000018 }, // DEFAULT_TEXT_LINE_SPACING + + { 9, 5, 0xf5f5ffff } // TEXTBOX_TEXT_COLOR_FOCUSED +}; + +static const StyleManager::Entry chip8BluishStyleProps[] = { + { 0, 0, 0x5ca6a6ff }, // DEFAULT_BORDER_COLOR_NORMAL + { 0, 1, 0xb4e8f3ff }, // DEFAULT_BASE_COLOR_NORMAL + { 0, 2, 0x447e77ff }, // DEFAULT_TEXT_COLOR_NORMAL + { 0, 3, 0x5f8792ff }, // DEFAULT_BORDER_COLOR_FOCUSED + { 0, 4, 0xcdeff7ff }, // DEFAULT_BASE_COLOR_FOCUSED + { 0, 5, 0x4c6c74ff }, // DEFAULT_TEXT_COLOR_FOCUSED + { 0, 6, 0x3b5b5fff }, // DEFAULT_BORDER_COLOR_PRESSED + { 0, 7, 0xeaffffff }, // DEFAULT_BASE_COLOR_PRESSED + { 0, 8, 0x275057ff }, // DEFAULT_TEXT_COLOR_PRESSED + { 0, 9, 0x96aaacff }, // DEFAULT_BORDER_COLOR_DISABLED + { 0, 10, 0xc8d7d9ff }, // DEFAULT_BASE_COLOR_DISABLED + { 0, 11, 0x8c9c9eff }, // DEFAULT_TEXT_COLOR_DISABLED + { 0, 16, 0x0000000e }, // DEFAULT_TEXT_SIZE + { 0, 17, 0x00000000 }, // DEFAULT_TEXT_SPACING + { 0, 18, 0x84adb7ff }, // DEFAULT_LINE_COLOR + { 0, 19, 0xe8eef1ff }, // DEFAULT_BACKGROUND_COLOR +}; +#endif + +/* + * {0, 0, 0x2f7486ff}, // DEFAULT_BORDER_COLOR_NORMAL +//{0, 1, 0x024658ff}, // DEFAULT_BASE_COLOR_NORMAL + {0, 1, 0x134b5aff}, // D! DEFAULT_BASE_COLOR_NORMAL +{0, 2, 0x51bfd3ff}, // DEFAULT_TEXT_COLOR_NORMAL + {0, 3, 0x82cde0ff}, // A! DEFAULT_BORDER_COLOR_FOCUSED + {0, 4, 0x3299b4ff}, // B! DEFAULT_BASE_COLOR_FOCUSED +//{0, 5, 0xb6e1eaff}, // DEFAULT_TEXT_COLOR_FOCUSED + {0, 5, 0xeff8ffff}, // C! DEFAULT_TEXT_COLOR_FOCUSED + {0, 6, 0x82cde0ff}, // A! DEFAULT_BORDER_COLOR_PRESSED + {0, 7, 0x3299b4ff}, // B! DEFAULT_BASE_COLOR_PRESSED + {0, 8, 0xeff8ffff}, // C! DEFAULT_TEXT_COLOR_PRESSED +//{0, 9, 0x134b5aff}, // DEFAULT_BORDER_COLOR_DISABLED + {0, 9, 0x134b5aff}, // D! DEFAULT_BORDER_COLOR_DISABLED +{0, 10, 0x0e273aff}, // DEFAULT_BASE_COLOR_DISABLED +//{0, 11, 0x17505fff}, // DEFAULT_TEXT_COLOR_DISABLED + {0, 11, 0x134b5aff}, // D! DEFAULT_TEXT_COLOR_DISABLED + {0, 18, 0x82cde0ff}, // A! DEFAULT_LINE_COLOR +{0, 19, 0x00222bff}, // DEFAULT_BACKGROUND_COLOR + */ + +static const std::pair styleMapping[] = { + {DEFAULT, BORDER_COLOR_NORMAL}, + {DEFAULT, BASE_COLOR_NORMAL}, + {DEFAULT, TEXT_COLOR_NORMAL}, + {DEFAULT, BORDER_COLOR_FOCUSED}, + {DEFAULT, BASE_COLOR_FOCUSED}, + {DEFAULT, TEXT_COLOR_FOCUSED}, + {DEFAULT, BORDER_COLOR_PRESSED}, + {DEFAULT, BASE_COLOR_PRESSED}, + {DEFAULT, TEXT_COLOR_PRESSED}, + {DEFAULT, BORDER_COLOR_DISABLED}, + {DEFAULT, BASE_COLOR_DISABLED}, + {DEFAULT, TEXT_COLOR_DISABLED}, + {DEFAULT, LINE_COLOR}, + {DEFAULT, BACKGROUND_COLOR}, + {DEFAULT, TEXT_SIZE}, + {DEFAULT, TEXT_SPACING}, + + {DEFAULT, TEXT_LINE_SPACING}, + {LABEL, TEXT_COLOR_FOCUSED}, + {LABEL, TEXT_COLOR_PRESSED}, + {SLIDER, TEXT_COLOR_FOCUSED}, + {PROGRESSBAR, TEXT_COLOR_FOCUSED}, + {TEXTBOX, TEXT_COLOR_FOCUSED}, + {VALUEBOX, TEXT_COLOR_FOCUSED} +}; + + +StyleManager::Scope::~Scope() +{ + for(const auto& [ctrl, prop, value] : styles) { + gui::SetStyle(ctrl, prop, value); + } +} +void StyleManager::Scope::setStyle(Style style, int value) +{ + const auto& pair = styleMapping[(int)style]; + const auto& control = pair.first; + const auto& property = pair.second; + auto iter = std::find_if(styles.begin(), styles.end(), [&](const Entry& e) { return control == e.ctrl && property == e.prop; }); + if(iter == styles.end()) + styles.push_back({control, property, (uint32_t)gui::GetStyle(control, property)}); + gui::SetStyle(control, property, value); +} + +void StyleManager::Scope::setStyle(Style style, const Color& color) +{ + setStyle(style, ColorToInt(color)); +} + +int StyleManager::Scope::getStyle(Style style) const +{ + const auto& [control, property] = styleMapping[(int)style]; + return gui::GetStyle(control, property); +} + +StyleManager* StyleManager::_instance = nullptr; + +static inline float diff(float a1, float a2) +{ + auto a = a1 - a2; + if(a > 180) a -= 360; + if(a < -180) a += 360; + return a; +} + +static inline uint32_t tintedColor(uint32_t color, float hue, float sat, bool invert) +{ + auto col = GetColor(color); + auto hsv = gui::HsvFromColor(col); + auto hueDelta = diff(hsv.x, cadmiumAverageHue); + hue += hueDelta; + if(hue >= 360) hue -= 360; + if(hue < 0) hue += 360; + hsv.x = hue; + hsv.y *= sat / 100.0f; + if (invert) + hsv.z = 1.0f - hsv.z; + return ColorToInt(gui::ColorFromHsv(hsv)); +} + +StyleManager::StyleManager() +{ + _instance = this; + _styleSets.push_back({"default", {}}); + double y_part = 0, x_part = 0; + int count = 0; + for(auto color : cadmiumPalette) { + auto hsv = gui::HsvFromColor(GetColor(color)); + x_part += cos (hsv.x * M_PI / 180); + y_part += sin (hsv.x * M_PI / 180); + count++; + _styleSets.front().palette.push_back(color); + } + cadmiumAverageHue = static_cast(std::atan2 (y_part / count, x_part / count) * 180 / M_PI); + /* + int idx = 0; + for (auto chip8StyleProp : chip8StyleProps) { + if(idx < (int)Style::COLOR_END) { + chip8StyleProp.val = cadmiumPalette[chip8StyleProp.val]; + } + _styleSets.front().styles.push_back(chip8StyleProp); + idx++; + } + */ +} + +StyleManager::~StyleManager() +{ + _instance = nullptr; +} + +void StyleManager::addTheme(const std::string& name, float hue, float sat, bool invert) +{ + _styleSets.push_back({name, invert, {}}); + for(auto color : cadmiumPalette) { + /* + auto col = GetColor(color); + auto hsv = gui::HsvFromColor(col); + hsv.x = hue; + hsv.y = sat; + if(invert) + hsv.z = 1.0f - hsv.z; + _styleSets.back().palette.push_back(ColorToInt(gui::ColorFromHsv(hsv))); + */ + _styleSets.back().palette.push_back(tintedColor(color, hue, sat, invert)); + } +} + + +void StyleManager::updateStyle(uint16_t hue, uint8_t sat, bool invert) +{ + int idx = 0; + _guiHue = hue; + _guiSaturation = sat; + for(auto& color : _currentStyle.palette) { + /* + color = cadmiumPalette[idx]; + auto col = GetColor(color); + auto hsv = gui::HsvFromColor(col); + hsv.x = hue; + hsv.y *= sat / 100.0f; + if (invert) + hsv.z = 1.0f - hsv.z; + color = ColorToInt(gui::ColorFromHsv(hsv)); + */ + color = tintedColor(cadmiumPalette[idx], hue, sat, invert); + idx++; + } + idx = 0; + for(auto [ctrl, prop, val] : chip8StyleProps) { + if(idx < (int)Style::COLOR_END) { + val = _currentStyle.palette[val]; + gui::SetStyle(ctrl, prop, val); + } + idx++; + } +} + +void StyleManager::setTheme(size_t themeIndex) +{ + if(themeIndex >= _styleSets.size()) themeIndex = 0; + _currentStyle = _styleSets[themeIndex]; + int idx = 0; + for(auto [ctrl, prop, val] : chip8StyleProps) { + if(idx < (int)Style::COLOR_END) { + val = _styleSets[themeIndex].palette[val]; + } + gui::SetStyle(ctrl, prop, val); + idx++; + } +} + +void StyleManager::setDefaultTheme() +{ + setTheme(0); +} + +Color StyleManager::getStyleColor(Style style) +{ + const auto& [control, property] = styleMapping[(int)style]; + return GetColor(gui::GetStyle(control, property)); +} + +Color StyleManager::mappedColor(const Color& col) +{ + using namespace gui; + if(_instance && _instance->_currentStyle.isInverted) { + auto hsv = gui::HsvFromColor(col); + if(hsv.z > 0.9f && hsv.y > 0.9f) { + hsv.y = 1.0f; + hsv.z = 0.7f; + } + else { + hsv.y = 1.0f; + hsv.z = 1.0f - hsv.z; + } + return ColorFromHsv(hsv); + } + return col; +} + +void StyleManager::renderAppearanceEditor() +{ + using namespace gui; + Space(4); + Begin(); + SetSpacing(2); + SetIndent(90); + SetNextWidth(150); + Spinner("UI-Tint ", &_guiHue, 0, 360); + SetNextWidth(150); + Spinner("UI-Saturation ", &_guiSaturation, 0, 100); + SetIndent(26); + StyleManager::Scope guard; + auto pos = GetCurrentPos(); + static Vector3 hsv{}; + Color col; + //SetNextWidth(52.0f + 16*18); + Label("UI Colors "); + int xoffset = 64; + updateStyle(_guiHue, _guiSaturation, false); + for (int i = 0; i < 7; ++i) { + col = GetColor(_currentStyle.palette[i]); + DrawRectangle(pos.x + xoffset + i * 18 + 2, pos.y + 2 , 12, 12,col); + bool hover = CheckCollisionPointRec(GetMousePosition(), {pos.x + xoffset + i * 18, pos.y, 16, 16}); + if(hover) { + hsv = gui::HsvFromColor(col); + } + //if(!GuiIsLocked() && IsMouseButtonReleased(0) && hover) { + /*_selectedColor = &_colorPalette[i]; + _previousColor = _colorPalette[i]; + _colorText = fmt::format("{:06x}", _colorPalette[i]>>8); + _colorSelectOpen = true; + */ + //} + DrawRectangleLines(pos.x + xoffset + i * 18, pos.y, 16, 16, GetColor(guard.getStyle(/*hover ? Style::BORDER_COLOR_FOCUSED :*/ Style::BORDER_COLOR_NORMAL))); + } + Label(fmt::format("H:{}, S:{}, V:{}", hsv.x, hsv.y, hsv.z).c_str()); + SetNextWidth(120); + if(Button("Reset to Default")) { + updateStyle(200, 80, false); // 192,90? + } + End(); +} + +} diff --git a/external/rlguipp/test/stylemanager.hpp b/external/rlguipp/test/stylemanager.hpp new file mode 100644 index 0000000..3cf3c6d --- /dev/null +++ b/external/rlguipp/test/stylemanager.hpp @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------------------------- +// src/emulation/stylemanager.hpp +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2023, Steffen Schümann +// +// 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. +// +//--------------------------------------------------------------------------------------- +#pragma once + +#include +#include +#include + +struct Color; + +namespace gui { +enum class Style { + BORDER_COLOR_NORMAL, + BASE_COLOR_NORMAL, + TEXT_COLOR_NORMAL, + BORDER_COLOR_FOCUSED, + BASE_COLOR_FOCUSED, + TEXT_COLOR_FOCUSED, + BORDER_COLOR_PRESSED, + BASE_COLOR_PRESSED, + TEXT_COLOR_PRESSED, + BORDER_COLOR_DISABLED, + BASE_COLOR_DISABLED, + TEXT_COLOR_DISABLED, + LINE_COLOR, + BACKGROUND_COLOR, + COLOR_END, + TEXT_SIZE = COLOR_END, + TEXT_SPACING, + TOOL_BUTTON_COLOR_NORMAL, + TOOL_BUTTON_COLOR_ACTIVE +}; + +class StyleManager +{ +public: + struct Entry { int ctrl; int prop; uint32_t val; }; + class Scope + { + public: + Scope() = default; + ~Scope(); + void setStyle(Style style, int value); + void setStyle(Style style, const Color& color); + int getStyle(Style style) const; + private: + std::vector styles; + }; + + StyleManager(); + ~StyleManager(); + + void setDefaultTheme(); + void addTheme(const std::string& name, float hue, float sat, bool invert = false); + void updateStyle(uint16_t hue, uint8_t sat, bool invert = false); + void setTheme(size_t idx); + void renderAppearanceEditor(); + uint16_t getGuiHue() const { return _guiHue; } + uint8_t getGuiSaturation() const { return _guiSaturation; } + + static StyleManager& instance() { return *_instance; } + static Color getStyleColor(Style style); + static Color mappedColor(const Color& col); + bool isInvertedTheme() const { return _currentStyle.isInverted; } + +private: + struct StyleSet { + std::string name; + bool isInverted{false}; + std::vector palette; + }; + int _guiHue{192}; + int _guiSaturation{90}; + StyleSet _currentStyle{}; + std::vector _styleSets; + + static StyleManager* _instance; +}; + +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f4e21d7..1c2887d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -50,9 +50,7 @@ if(WIN32) add_executable(cadmium WIN32 ${CADMIUM_SOURCE} cadmium.rc) endif() elseif(APPLE) - set(CADMIUM_ICON "${PROJECT_BINARY_DIR}/resources/cadmium.icns" - database.cpp - database.hpp) + set(CADMIUM_ICON "${PROJECT_BINARY_DIR}/resources/cadmium.icns") set_source_files_properties(${CADMIUM_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources" GENERATED 1) add_executable(cadmium MACOSX_BUNDLE ${CADMIUM_SOURCE} ${CADMIUM_ICON}) if(WITH_FLAG_COCOA_GRAPHICS_SWITCHING) @@ -84,7 +82,7 @@ if(WEB_WITH_FETCHING) target_compile_definitions(cadmium PUBLIC WEB_WITH_FETCHING) endif() set_source_files_properties(src/editor.cpp PROPERTIES COMPILE_FLAGS "-Wno-unused-value") -target_link_libraries(cadmium PRIVATE emulation rlguipp resourcedata jsct ghc_filesystem zxorm::zxorm crypto ssl httplib::httplib) +target_link_libraries(cadmium PRIVATE emulation rlguipp resourcedata jsct ghc_filesystem zxorm::zxorm ssl httplib::httplib) target_code_coverage(cadmium) if (${PLATFORM} MATCHES "Web") if(WEB_WITH_CLIPBOARD) diff --git a/src/c8db/database.hpp b/src/c8db/database.hpp index fb942e6..674221b 100644 --- a/src/c8db/database.hpp +++ b/src/c8db/database.hpp @@ -283,8 +283,8 @@ class Database explicit Database(std::string directory, const std::string& platformsFile_ = "platforms.json", const std::string& programsFile_ = "programs.json") : dbDir(std::move(directory)) - , platformsFile(platformsFile.front() != '[' ? platformsFile_ : "") - , programsFile(programsFile.front() != '[' ? programsFile_ : "") + , platformsFile(platformsFile_.front() != '[' ? platformsFile_ : "") + , programsFile(programsFile_.front() != '[' ? programsFile_ : "") { if (platformsFile.empty()) { std::istringstream platformsStream(platformsFile_); diff --git a/src/cadmium.cpp b/src/cadmium.cpp index 63f3695..e14080c 100644 --- a/src/cadmium.cpp +++ b/src/cadmium.cpp @@ -781,9 +781,10 @@ void main() _microFont = LoadImage("micro-font.png"); generateFont(); if(props) { - // TODO: Fix this - //_options = *chip8options; - setPalette({_defaultPalette.begin(), _defaultPalette.end()}); + if (props.palette().empty()) + setPalette(_defaultPalette); + else + setPalette(props.palette()); } else _mainView = eSETTINGS; @@ -1171,12 +1172,14 @@ void main() void updatePalette(const std::array& palette) override { + /* + * TODO: Fix this if(!_customPalette) { for (int i = 0; i < palette.size(); ++i) { _colorPalette[i] = ((rgb332To888(palette[i]) << 8) | 0xff); } _updateScreen = true; - } + }*/ } void updatePalette(const std::vector& palette, size_t offset) override @@ -1213,6 +1216,7 @@ void main() Color badgeColor, textColor; }; std::vector badges; + auto textColor = DARKGRAY; for(const auto& [name, info] : _cores) { //std::cout << toOptionName(name) << std::endl; //coresAvailable += fmt::format(" {} - {}\n", toOptionName(name), info->description); @@ -1223,12 +1227,12 @@ void main() presetName = toOptionName(info->variantName(i)); else presetName = toOptionName(info->prefix() + '-' + info->variantName(i)); - badges.push_back(BadgeInfo{presetName, GREEN, BLACK}); + badges.push_back(BadgeInfo{presetName, {0x00, 0xE0, 0x00, 0xFF}, textColor}); } } - badges.push_back(BadgeInfo{"GENERIC-CHIP-8", ORANGE, BLACK}); - badges.push_back(BadgeInfo{"???", RED, BLACK}); - badges.push_back(BadgeInfo{"NEW", SKYBLUE, BLACK}); + badges.push_back(BadgeInfo{"GENERIC-CHIP-8", {0xE0, 0xC0, 0x00, 0xFF}, textColor}); + badges.push_back(BadgeInfo{"???", {0xE0, 0x40, 0x40, 0xFF}, LIGHTGRAY}); + badges.push_back(BadgeInfo{"NEW", {0x00, 0xC0, 0xE0, 0xFF}, textColor}); for(uint16_t i = 0; i < badges.size(); ++i) { auto& [label, badgeCol, textCol] = badges[i]; float width = label.length() * 4 + 3; @@ -1491,6 +1495,7 @@ void main() static bool iconButton(int iconId, bool isPressed = false, Color color = {3, 127, 161}, Color foreground = {0x51, 0xbf, 0xd3, 0xff}) { + using namespace gui; StyleManager::Scope guard; auto fg = guard.getStyle(Style::TEXT_COLOR_NORMAL); auto bg = guard.getStyle(Style::BASE_COLOR_NORMAL); @@ -1938,7 +1943,14 @@ void main() SetSpacing(0); Begin(); BeginPanel("Library / Research"); - _database.render(_font); + if (_database.render(_font)) { + auto program = _database.getSelectedProgram(); + if (program) { + loadBinary(program->name, program->data, program->properties, true); + reloadRom(true); + _lastRunView = _mainView = eDEBUGGER; + } + } Space(_screenHeight - GetCurrentPos().y - 20 - 1); EndPanel(); End(); @@ -2257,6 +2269,7 @@ void main() pos.y = std::ceil(pos.y); SetNextWidth(52.0f + 16*18); Label("Colors:"); +#if 0 // TODO: fix this for (int i = 0; i < 16; ++i) { bool hover = CheckCollisionPointRec(GetMousePosition(), {pos.x + 52 + i * 18, pos.y, 16, 16}); DrawRectangle(pos.x + 52 + i * 18, pos.y, 16, 16, GetColor(guard.getStyle(hover ? Style::BORDER_COLOR_FOCUSED : Style::BORDER_COLOR_NORMAL))); @@ -2274,6 +2287,7 @@ void main() setPalette({_colorPalette.begin(), _colorPalette.end()}); prevPalette.assign(_colorPalette.begin(), _colorPalette.end()); } +#endif static int sel = 5; if(DropdownBox("Cadmium;Silicon-8;Pico-8;Octo Classic;LCD;Custom", &sel, true)) { switch(sel) { @@ -2730,7 +2744,7 @@ void main() private: std::mutex _audioMutex; ResourceManager _resources; - StyleManager _styleManager; + gui::StyleManager _styleManager; Image _fontImage{}; Image _microFont{}; Image _titleImage{}; diff --git a/src/database.cpp b/src/database.cpp index fe601dc..c6b4ea3 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -68,16 +68,25 @@ struct DBProgram std::vector binaries; }; -struct DBBinary +struct DBBinaryConfig { int id{0}; - int program_id{0}; + int binary_id{0}; std::string preset; std::string properties; +}; + +struct DBBinary +{ + int id{0}; + int program_id{0}; std::string sha1; + std::string release; + std::string description; std::vector data; std::vector filenames; std::vector tags; + std::vector configs; }; struct DBFilename @@ -116,10 +125,16 @@ using BinariesTable = Table<"binaries", DBBinary, Column<"id", &DBBinary::id, PrimaryKey<>>, Column<"program_id", &DBBinary::program_id, ForeignKey<"programs", "id", action_t::cascade, action_t::cascade>>, - Column<"preset", &DBBinary::preset>, - Column<"properties", &DBBinary::properties>, Column<"sha1", &DBBinary::sha1, Unique>, + Column<"release", &DBBinary::release>, + Column<"description", &DBBinary::description>, Column<"data", &DBBinary::data>>; +using BinaryConfigsTable = Table<"binary_configs", + DBBinaryConfig, + Column<"id", &DBBinaryConfig::id, PrimaryKey<>>, + Column<"binary_id", &DBBinaryConfig::binary_id, ForeignKey<"binaries", "id", action_t::cascade, action_t::cascade>>, + Column<"preset", &DBBinaryConfig::preset>, + Column<"properties", &DBBinaryConfig::properties>>; using FilenamesTable = Table<"filenames", DBFilename, Column<"id", &DBFilename::id, PrimaryKey<>>, @@ -141,7 +156,7 @@ using BinariesTagsTable = Table<"binaries_tags", Column<"binary_id", &DBBinaryTag::binary_id, ForeignKey<"binaries", "id", action_t::cascade, action_t::cascade>>, Column<"tag_id", &DBBinaryTag::tag_id, ForeignKey<"tags", "id", action_t::cascade, action_t::cascade>>>; -using DBConnection = Connection; +using DBConnection = Connection; struct Database::Private @@ -168,9 +183,12 @@ struct Database::Private if (!presetFilter.empty()) { bool matchesPreset = false; for (const auto& binid : program.binaries) { - if (const auto& binary = binaries[binid]; presetFilter.find(binary.preset) != std::string::npos) { - matchesPreset = true; - break; + const auto& binary = binaries[binid]; + for (const auto& bincfg : binary.configs) { + if (presetFilter.find(bincfg.preset) != std::string::npos) { + matchesPreset = true; + break; + } } } if (!matchesPreset) { @@ -254,6 +272,10 @@ void Database::fetchProgramInfo() for (const auto& binary : binaries) { auto [iter, added] = _pimpl->binaries.emplace(binary.id, binary); _digests.insert(Sha1::Digest(binary.sha1)); + auto configs = _pimpl->connection->select_query().where_many(BinaryConfigsTable::field_t<"binary_id">() == binary.id).exec(); + for (const auto& config : configs) { + iter->second.configs.push_back(config); + } auto filenames = _pimpl->connection->select_query>>().where_many(FilenamesTable::field_t<"binary_id">() == binary.id).exec(); for (const auto& filename : filenames) { iter->second.filenames.push_back(filename); @@ -270,6 +292,7 @@ int Database::scanLibrary() std::vector binarys; auto start = std::chrono::steady_clock::now(); auto extensions = _registry.getSupportedExtensions(); + std::vector foundRoms; int numFiles = 0; for (auto folders = split(_configuration.libraryPath, ';'); const auto& folder : folders) { try { @@ -286,25 +309,29 @@ int Database::scanLibrary() } } if (!digested) { - const char* preset = "unknown"; - std::string name = ""; - const auto* romInfo = Librarian::findKnownRom(info.digest); DBProgram program; DBBinary binary; - if (romInfo) { - preset = romInfo->preset; - name = romInfo->name ? fmt::format(" {} -", romInfo->name) : ""; + std::string name = ""; + std::string preset = "???"; + if (Librarian::findKnownRoms(info.digest, foundRoms)) { + name = foundRoms.front()->name ? fmt::format(" {} -", foundRoms.front()->name) : ""; + preset = foundRoms.front()->preset; try { - _pimpl->connection->transaction([this,romInfo, &info, &data, &de, &program, &binary]() { - program = DBProgram{.name = std::string(romInfo->name) }; + _pimpl->connection->transaction([this,&foundRoms, &info, &data, &de, &program, &binary]() { + program = DBProgram{.name = std::string(foundRoms.front()->name) }; _pimpl->connection->insert_record(program); - binary = DBBinary{.program_id = program.id, .preset = std::string(romInfo->preset), .sha1 = info.digest.to_hex(), .data = data}; + binary = DBBinary{.program_id = program.id, .sha1 = info.digest.to_hex(), .data = data}; _pimpl->connection->insert_record(binary); - //std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - auto filename = DBFilename{.binary_id = binary.id, .name = de.path().string()}; - //std::cout << "insert DBFilename: " << filename.binary_id << ", " << filename.name << std::endl; - _pimpl->connection->insert_record(filename); - _pimpl->connection->insert_record(DBProgramTag{.program_id = program.id, .tag_id = _pimpl->newTagId}); + for (const auto* romInfo : foundRoms) { + auto config = DBBinaryConfig{.binary_id = binary.id, .preset = std::string(romInfo->preset), .properties = std::string(romInfo->options ? romInfo->options : "")}; + binary.configs.push_back(config); + _pimpl->connection->insert_record(config); + //std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + auto filename = DBFilename{.binary_id = binary.id, .name = de.path().string()}; + //std::cout << "insert DBFilename: " << filename.binary_id << ", " << filename.name << std::endl; + _pimpl->connection->insert_record(filename); + _pimpl->connection->insert_record(DBProgramTag{.program_id = program.id, .tag_id = _pimpl->newTagId}); + } }); } catch (const SQLExecutionError& ex) { @@ -321,8 +348,13 @@ int Database::scanLibrary() } program = DBProgram{.name = de.path().filename().stem().string() }; _pimpl->connection->insert_record(program); - binary = DBBinary{.program_id = program.id, .preset = preset, .sha1 = info.digest.to_hex(), .data = data}; + binary = DBBinary{.program_id = program.id, .sha1 = info.digest.to_hex(), .data = data}; _pimpl->connection->insert_record(binary); + if (!preset.empty()) { + auto config = DBBinaryConfig{.binary_id = binary.id, .preset = preset}; + _pimpl->connection->insert_record(config); + binary.configs.push_back(config); + } _pimpl->connection->insert_record(DBFilename{.binary_id = binary.id, .name = de.path().string()}); _pimpl->connection->insert_record(DBProgramTag{.program_id = program.id, .tag_id = _pimpl->newTagId}); _pimpl->connection->insert_record(DBBinaryTag{.binary_id =binary.id, .tag_id = _pimpl->unclassifiedTagId}); @@ -371,13 +403,18 @@ int Database::scanLibrary() Database::FileInfo Database::scanFile(const std::string& filePath, std::vector* outData) { auto data = loadFile(filePath); - auto result = FileInfo{filePath,calculateSha1(data.data(), data.size())}; + auto result = FileInfo{filePath, calculateSha1(data.data(), data.size())}; if (outData) { std::swap(*outData, data); } return result; } +std::optional Database::getSelectedProgram() const +{ + return _selectedProgram; +} + const std::string& Database::getBadge(std::string badgeText) const { static const std::string none; @@ -385,11 +422,12 @@ const std::string& Database::getBadge(std::string badgeText) const return iter != _badges.end() ? iter->second : none; } -void Database::render(Font& font) +bool Database::render(Font& font) { using namespace gui; static bool first = true; static bool second = false; + bool binarySelected = false; if (first) { first = false; fetchProgramInfo(); @@ -403,23 +441,29 @@ void Database::render(Font& font) } { std:std::unique_lock lock(_pimpl->mutex); + SetSpacing(4); auto area = GetContentAvailable(); _pimpl->relayoutList(area.width); TextBox(_pimpl->queryLine, 4096); BeginColumns(); { - auto tagsWidth = area.width / 3 - 5; - auto listWidth = area.width - tagsWidth - 5; + SetSpacing(4); + auto tagsWidth = area.width / 4 - 5; SetNextWidth(tagsWidth); - BeginTableView(area.height - 135, 2, &_pimpl->tagsScrollPos); - TableNextRow(22); - TableNextColumn(64); - Label("Huhu"); + auto offset = GetCurrentPos(); + BeginTableView(GetContentAvailable().height - 135, 2, &_pimpl->tagsScrollPos); + for (const auto& [key, badge] : _badges) { + TableNextRow(10); + TableNextColumn(tagsWidth - 8); + auto pos = GetContentAvailable(); + DrawTextEx(font, badge.c_str(), {pos.x + offset.x, pos.y + offset.y + _pimpl->tagsScrollPos.y}, 8.0f, 2.0f, WHITE); + } EndTableView(); - auto listRect = Rectangle{area.x + tagsWidth + 5, area.y, area.width * 3 / 2, area.height - 135}; + auto tableArea = GetContentAvailable(); + auto listWidth = tableArea.width; + auto listRect = Rectangle{tableArea.x, tableArea.y, listWidth, tableArea.height - 135}; static Vector2 scrollPos{0,0}; auto [px, py] = GetCurrentPos(); - SetNextWidth(listWidth); BeginScrollPanel(listRect.height, Rectangle{0.0f,0.0f, listRect.width - 8, _pimpl->listContentHeight < listRect.height ? listRect.height : _pimpl->listContentHeight}, &scrollPos); auto [cx, cy] = GetCurrentPos(); auto [mx, my] = GetMousePosition(); @@ -433,33 +477,60 @@ void Database::render(Font& font) auto lightgrayCol = StyleManager::mappedColor(LIGHTGRAY); for (const int pid : _pimpl->shownIndices) { const auto& program = _pimpl->programs.at(pid); - Rectangle itemRect = {program.rect.x + px + cx + scrollPos.x, program.rect.y + py + cy + scrollPos.y, program.rect.width, program.rect.height}; + Rectangle itemRect = {program.rect.x + px + cx + scrollPos.x, program.rect.y + py + cy + scrollPos.y, program.rect.width, program.rect.height - 2}; if (CheckCollisionRecs(listRect, itemRect)) { ++disp; if (disp > maxDisp) { maxDisp = disp; } + if (CheckCollisionPointRec(GetMousePosition(), itemRect)) { + DrawRectangleRec({itemRect.x - 2, itemRect.y - 2, itemRect.width, itemRect.height}, StyleManager::getStyleColor(gui::Style::BASE_COLOR_NORMAL)); + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + if (program.binaries.size() == 1) { + auto preset = _pimpl->binaries[program.binaries.front()].configs.front().preset; + emu::Properties props; + if (!fuzzyCompare(preset, "generic-chip-8")) { + props = emu::CoreRegistry::propertiesForPreset(_pimpl->binaries[program.binaries.front()].configs.front().preset); + const auto& propString = _pimpl->binaries[program.binaries.front()].configs.front().properties; + if (!propString.empty()) { + props.applyDiff(nlohmann::json::parse(propString)); + } + } + _selectedProgram = {.name = program.name, .properties = props, .data = _pimpl->binaries[program.binaries.front()].data}; + binarySelected = true; + } + else { + _selectedProgram = {};//{.name = program.name, .properties = {}, .data = {}}; + } + } + } DrawTextEx(font, fmt::format("{}", program.name.c_str()).c_str(), {itemRect.x, itemRect.y}, 8, 0, lightgrayCol); for (size_t i = 0; i < program.binaries.size(); ++i) { const auto binary = _pimpl->binaries.at(program.binaries[i]); - std::string badges = getBadge( binary.preset); + std::string badges; + for (const auto& bincfg : binary.configs) { + badges += getBadge(bincfg.preset); + } if (badges.empty()) { badges = getBadge("???"); } - DrawTextEx(font, fmt::format("{} {}", _pimpl->binaries[program.binaries[i]].sha1, badges).c_str(), {itemRect.x, itemRect.y + (i+1) * 9}, 8, 0, grayCol); + DrawTextEx(font, fmt::format("{}", _pimpl->binaries[program.binaries[i]].sha1.substr(0,8)).c_str(), {itemRect.x, itemRect.y + (i+1) * 9}, 8, 0, grayCol); + DrawTextEx(font, fmt::format("{}", badges).c_str(), {itemRect.x + 9*6, itemRect.y + (i+1) * 9}, 8, 0, WHITE); } } ypos += program.rect.height; } //DrawRectangleLines(-scrollPos.x, py-scrollPos.y, listRect.width-2, listRect.height-2, BLACK); EndScrollPanel(); + //DrawRectangleLinesEx(listRect, 1, MAGENTA); } EndColumns(); //auto innerHeight = _pimpl->programs //BeginScrollPanel(listRect.height, {0,0,area.width-6, (float)(_core->memSize()/8 + 1) * lineSpacing}, &memScroll); auto pos = GetCurrentPos(); - DrawRectangleLines(pos.x + area.width - 130, pos.y, 130, 66, StyleManager::instance().getStyleColor(Style::BORDER_COLOR_NORMAL)); + DrawRectangleLines(pos.x + area.width - 131, pos.y, 130, 66, StyleManager::instance().getStyleColor(gui::Style::BORDER_COLOR_NORMAL)); //DrawText(fmt::format("{}x{}", mx, my).c_str(), area.x + 2, area.y + 2, 8, RED); + return binarySelected; } } diff --git a/src/database.hpp b/src/database.hpp index b931169..2221cf6 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -42,11 +42,18 @@ class Database std::string filePath; Sha1::Digest digest; }; + struct Program + { + std::string name; + emu::Properties properties; + std::span data; + }; explicit Database(const emu::CoreRegistry& registry, CadmiumConfiguration& configuration, ThreadPool& threadPool, const std::string& path, const std::unordered_map& badges); ~Database(); int scanLibrary(); FileInfo scanFile(const std::string& filePath, std::vector* outData = nullptr); - void render(Font& font); + std::optional getSelectedProgram() const; + bool render(Font& font); // returns true if a program was selected bool fetchC8PDB(); private: void fetchProgramInfo(); @@ -60,5 +67,6 @@ class Database std::chrono::steady_clock::duration durationOfLastJob{}; struct Private; std::unique_ptr _pimpl; + std::optional _selectedProgram; }; diff --git a/src/debugger.cpp b/src/debugger.cpp index d5003f4..811c087 100644 --- a/src/debugger.cpp +++ b/src/debugger.cpp @@ -331,6 +331,7 @@ void Debugger::showInstructions(emu::GenericCpu& cpu, Font& font, const int line void Debugger::showGenericRegs(emu::GenericCpu& cpu, const RegPack& regs, const RegPack& oldRegs, Font& font, const int lineSpacing, const Vector2& pos) const { + using namespace gui; auto lightgrayCol = StyleManager::getStyleColor(Style::TEXT_COLOR_FOCUSED);//StyleManager::mappedColor(LIGHTGRAY); auto yellowCol = StyleManager::mappedColor(YELLOW); int i, line = 0, lastSize = 0; diff --git a/src/editor.cpp b/src/editor.cpp index b46f22d..6b2ff0b 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -254,6 +254,7 @@ void Editor::ensureCursorVisibility() void Editor::update() { + using namespace gui; auto oldRepeat = _repeatTimer; ++_editId; _repeatTimer -= GetFrameTime(); diff --git a/src/emuhostex.cpp b/src/emuhostex.cpp index 84c2586..4150806 100644 --- a/src/emuhostex.cpp +++ b/src/emuhostex.cpp @@ -68,20 +68,27 @@ EmuHostEx::EmuHostEx(CadmiumConfiguration& cfg) void EmuHostEx::setPalette(const std::vector& colors, size_t offset) { - for(size_t i = 0; i < colors.size() && i + offset < _colorPalette.size(); ++i) { - _colorPalette[i + offset] = colors[i]; + if (_colorPalette.size() < colors.size() + offset) + _colorPalette.colors.resize(colors.size() + offset); + for (size_t i = 0; i < colors.size() && i + offset < _colorPalette.size(); ++i) { + _colorPalette.colors[i + offset] = Palette::Color(colors[i]); } - if(_chipEmu) + if (_chipEmu) _chipEmu->setPalette(_colorPalette); - std::vector pal(16, ""); - for(size_t i = 0; i < 16; ++i) { - pal[i] = fmt::format("#{:06x}", _colorPalette[i] >> 8); - } + //std::vector pal(16, ""); + //for (size_t i = 0; i < 16; ++i) { + // pal[i] = fmt::format("#{:06x}", _colorPalette[i] >> 8); + //} // TODO: Fix this //_options.advanced["palette"] = pal; //_options.updatedAdvanced(); } +void EmuHostEx::setPalette(const Palette& palette) +{ + _colorPalette = palette; + _properties->palette() = palette; +} std::unique_ptr EmuHostEx::create(Properties& properties, IEmulationCore* iother) { @@ -273,28 +280,27 @@ bool EmuHostEx::loadBinary(std::string_view filename, ghc::span b Sha1::Digest romSha1; std::vector romImage; std::string source; - //Properties knownProperties; + // Properties knownProperties; auto fileData = std::vector(binary.data(), binary.data() + binary.size()); auto isKnown = _librarian.isKnownFile(fileData.data(), fileData.size()); TraceLog(LOG_INFO, "Loading %s file with sha1: %s", isKnown ? "known" : "unknown", calculateSha1(fileData.data(), fileData.size()).to_hex().c_str()); auto knownProperties = _librarian.getPropertiesForFile(fileData.data(), fileData.size()); - if(endsWith(filename, ".8o")) { + if (endsWith(filename, ".8o")) { c8c = std::make_unique(); source.assign((const char*)fileData.data(), fileData.size()); - if(c8c->compile(filename).resultType == emu::CompileResult::eOK) - { + if (c8c->compile(filename).resultType == emu::CompileResult::eOK) { auto startAddress = _properties->get("startAddress"); auto loadAddress = startAddress ? startAddress->intValue : 0; - if(c8c->codeSize() < _chipEmu->memSize() - loadAddress) { + if (c8c->codeSize() < _chipEmu->memSize() - loadAddress) { romImage.assign(c8c->code(), c8c->code() + c8c->codeSize()); romSha1 = c8c->sha1(); valid = true; wasFromSource = true; - if((loadOpt & DontChangeOptions) == 0) { + if ((loadOpt & DontChangeOptions) == 0) { isKnown = _librarian.isKnownFile(romImage.data(), romImage.size()); - //knownOptions = _librarian.getOptionsForFile(romImage.data(), romImage.size()); - //if(knownOptions.behaviorBase != Chip8EmulatorOptions::ePORTABLE) - // updateEmulatorOptions(knownOptions); + // knownOptions = _librarian.getOptionsForFile(romImage.data(), romImage.size()); + // if(knownOptions.behaviorBase != Chip8EmulatorOptions::ePORTABLE) + // updateEmulatorOptions(knownOptions); } } } @@ -306,20 +312,20 @@ bool EmuHostEx::loadBinary(std::string_view filename, ghc::span b } else { std::optional loadAddress; - if(Librarian::isPrefixedTPDRom(binary.data(), binary.size())) + if (Librarian::isPrefixedTPDRom(binary.data(), binary.size())) loadAddress = 0x200; - if(loadOpt & DontChangeOptions) { + if (loadOpt & DontChangeOptions) { _chipEmu->reset(); - if(_chipEmu->loadData(binary, loadAddress)) { + if (_chipEmu->loadData(binary, loadAddress)) { romImage.assign(binary.data(), binary.data() + binary.size()); valid = true; } } - else{ + else { if (auto extensionProps = CoreRegistry::propertiesForExtension(fs::path(filename).extension().string())) { updateEmulatorOptions(extensionProps); _chipEmu->reset(); - if(_chipEmu->loadData(binary, loadAddress)) { + if (_chipEmu->loadData(binary, loadAddress)) { romImage.assign(binary.data(), binary.data() + binary.size()); valid = true; } @@ -327,31 +333,31 @@ bool EmuHostEx::loadBinary(std::string_view filename, ghc::span b } } if (valid) { - //TraceLog(LOG_INFO, "Found a valid rom."); + // TraceLog(LOG_INFO, "Found a valid rom."); _romImage = std::move(romImage); _romSha1 = romSha1 == Sha1::Digest{} ? calculateSha1(_romImage.data(), _romImage.size()) : romSha1; _romName = filename; _romIsWellKnown = isKnown; - //if(isKnown && knownOptions.behaviorBase != Chip8EmulatorOptions::ePORTABLE) - // _romWellKnownProperties = knownProperties; + // if(isKnown && knownOptions.behaviorBase != Chip8EmulatorOptions::ePORTABLE) + // _romWellKnownProperties = knownProperties; //_chipEmu->reset(); - if(!wasFromSource && _romImage.size() < 8192*1024) { - //TraceLog(LOG_INFO, "Setting up decompiler."); + if (!wasFromSource && _romImage.size() < 8192 * 1024) { + // TraceLog(LOG_INFO, "Setting up decompiler."); std::stringstream os; - //TraceLog(LOG_INFO, "Setting instance."); + // TraceLog(LOG_INFO, "Setting instance."); auto startAddress = _properties->get("startAddress"); auto loadAddress = startAddress ? startAddress->intValue : 0; emu::Chip8Decompiler decomp{_romImage, static_cast(loadAddress)}; - decomp.setVariant(Chip8Variant::CHIP_8/*_options.presetAsVariant()*/, true); - //TraceLog(LOG_INFO, "Setting variant."); - //decomp.setVariant(Chip8Variant::CHIP_8, true); - //TraceLog(LOG_INFO, "About to decompile..."); + decomp.setVariant(Chip8Variant::CHIP_8 /*_options.presetAsVariant()*/, true); + // TraceLog(LOG_INFO, "Setting variant."); + // decomp.setVariant(Chip8Variant::CHIP_8, true); + // TraceLog(LOG_INFO, "About to decompile..."); decomp.decompile(filename, loadAddress, &os, false, true); source = os.str(); } whenRomLoaded(fs::path(_romName).replace_extension(".8o").string(), loadOpt & LoadOptions::SetToRun, c8c.get(), source); } - // TODO: Fix this + // TODO: Fix this #if 0 std::unique_ptr c8c; std::string romSha1Hex; @@ -604,4 +610,22 @@ bool EmuHostEx::loadBinary(std::string_view filename, ghc::span b return valid; } +bool EmuHostEx::loadBinary(std::string_view filename, ghc::span binary, const Properties& props, const bool isKnown) +{ + _customPalette = false; + _colorPalette = _defaultPalette; + if (props) { + updateEmulatorOptions(props); + } + _chipEmu->reset(); + _romImage = std::vector(binary.data(), binary.data() + binary.size()); + _romSha1 = calculateSha1(_romImage.data(), _romImage.size()); + _romName = filename; + _romIsWellKnown = isKnown; + if (isKnown) { + _romWellKnownProperties = props; + } + return true; +} + } diff --git a/src/emuhostex.hpp b/src/emuhostex.hpp index 0103b7e..3444aaf 100644 --- a/src/emuhostex.hpp +++ b/src/emuhostex.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -79,8 +80,10 @@ class EmuHostEx : public EmulatorHost //virtual void updatePalette(const std::vector& palette, size_t offset) = 0; virtual bool loadRom(std::string_view filename, LoadOptions loadOpt); virtual bool loadBinary(std::string_view filename, ghc::span binary, LoadOptions loadOpt); + virtual bool loadBinary(std::string_view filename, ghc::span binary, const Properties& props, bool isKnown); void updateEmulatorOptions(const Properties& properties); void setPalette(const std::vector& colors, size_t offset = 0); + void setPalette(const Palette& palette); protected: std::unique_ptr create(Properties& properties, IEmulationCore* iother = nullptr); @@ -104,8 +107,10 @@ class EmuHostEx : public EmulatorHost Sha1::Digest _romSha1; bool _romIsWellKnown{false}; bool _customPalette{false}; - std::array _colorPalette{}; - std::array _defaultPalette{}; + Palette _colorPalette; + Palette _defaultPalette; + //std::array _colorPalette{}; + //std::array _defaultPalette{}; emu::Properties* _properties{nullptr}; std::map _propertiesByClass; std::string _variantName; @@ -135,4 +140,45 @@ class HeadlessHost : public EmuHostEx CadmiumConfiguration _cfg; }; +class ThreadedBackgroundHost : public EmuHostEx +{ +public: + ThreadedBackgroundHost() : EmuHostEx(_cfg) + { + _screen = GenImageColor(emu::SUPPORTED_SCREEN_WIDTH, emu::SUPPORTED_SCREEN_HEIGHT, BLACK); + _screenTexture = LoadTextureFromImage(_screen); + } + explicit ThreadedBackgroundHost(const Properties& options) : EmuHostEx(_cfg) { updateEmulatorOptions(options); } + ~ThreadedBackgroundHost() override = default; + Properties& getProperties() { return *_properties; } + IEmulationCore& emuCore() { return *_chipEmu; } + bool isHeadless() const override { return true; } + int getKeyPressed() override { return 0; } + bool isKeyDown(uint8_t key) override { return false; } + const std::array& getKeyStates() const override { static const std::array keys{}; return keys; } + void updateScreen() override + { + auto* pixel = static_cast(_screen.data); + if(pixel) { + if (const auto* screen = _chipEmu->getScreen()) { + screen->convert(pixel, _screen.width, 255, nullptr); + UpdateTexture(_screenTexture, _screen.data); + } + else { + if (const auto* screenRgb = _chipEmu->getScreenRGBA()) { + screenRgb->convert(pixel, _screen.width, _chipEmu->getScreenAlpha(), _chipEmu->getWorkRGBA()); + UpdateTexture(_screenTexture, _screen.data); + } + } + } + } + void vblank() override {} + void updatePalette(const std::array& palette) override {} + void updatePalette(const std::vector& palette, size_t offset) override {} +private: + CadmiumConfiguration _cfg; + Image _screen; + Texture2D _screenTexture; +}; + } diff --git a/src/emulation/chip8generic.cpp b/src/emulation/chip8generic.cpp index 79af2fc..0576d14 100644 --- a/src/emulation/chip8generic.cpp +++ b/src/emulation/chip8generic.cpp @@ -481,13 +481,18 @@ void Chip8GenericEmulator::reset() bool Chip8GenericEmulator::updateProperties(Properties& props, Property& changed) { - if(fuzzyAnyOf(changed.getName(), {"TraceLog", "InstructionsPerFrame", "FrameRate"})) { + if (fuzzyAnyOf(changed.getName(), {"TraceLog", "InstructionsPerFrame", "FrameRate"})) { _options = Chip8GenericOptions::fromProperties(props); return false; } return true; } +void Chip8GenericEmulator::setPalette(const Palette& palette) +{ + _screen.setPalette(palette); +} + bool Chip8GenericEmulator::loadData(std::span data, std::optional loadAddress) { return Chip8GenericBase::loadData(data, loadAddress ? loadAddress : _options.startAddress); diff --git a/src/emulation/chip8generic.hpp b/src/emulation/chip8generic.hpp index 75627c2..243e10a 100644 --- a/src/emulation/chip8generic.hpp +++ b/src/emulation/chip8generic.hpp @@ -125,6 +125,7 @@ class Chip8GenericEmulator : public Chip8GenericBase const VideoType* getScreen() const override { return _isMegaChipMode ? nullptr : &_screen; } const VideoRGBAType* getScreenRGBA() const override { return _isMegaChipMode ? _screenRGBA : nullptr; } uint8_t getScreenAlpha() const override { return _screenAlpha; } + void setPalette(const Palette& palette) override; bool isDoublePixel() const override { return _options.behaviorBase == Chip8GenericOptions::eMEGACHIP ? false : (_options.optAllowHires && !_isHires); } bool loadData(std::span data, std::optional loadAddress) override; unsigned stackSize() const override { return 16; } diff --git a/src/emulation/chip8genericbase.cpp b/src/emulation/chip8genericbase.cpp index 79a5d96..532783a 100644 --- a/src/emulation/chip8genericbase.cpp +++ b/src/emulation/chip8genericbase.cpp @@ -226,7 +226,8 @@ static uint8_t g_octoBigFont[] = { 0xFF, 0xFF, 0xC0, 0xC0, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0 // F }; -static uint8_t g_madsterBigFont[] = { +// Whith the kind permission of @Madster (EmuDev Discord) +static uint8_t g_auchipBigFont[] = { 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, // 0 0x18, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0xFF, // 1 0x7E, 0xFF, 0xC3, 0x03, 0x07, 0x1E, 0x78, 0xE0, 0xFF, 0xFF, // 2 @@ -283,7 +284,7 @@ std::pair Chip8GenericBase::bigFontData(Chip8BigFont fon case C8F10_MEGACHIP: return {g_megachip8BigFont, sizeof(g_megachip8BigFont)}; case C8F10_FISHNCHIPS: return {g_fishNChipBigFont, sizeof(g_fishNChipBigFont)}; case C8F10_XOCHIP: return {g_octoBigFont, sizeof(g_octoBigFont)}; - case C8F10_MADSTER: return {g_madsterBigFont, sizeof(g_madsterBigFont)}; + case C8F10_AUCHIP: return {g_auchipBigFont, sizeof(g_auchipBigFont)}; case C8F10_AKOUZ1: return {g_akouz1BigFont, sizeof(g_akouz1BigFont)}; default: return {g_ship11BigFont, sizeof(g_ship11BigFont)}; } diff --git a/src/emulation/chip8genericbase.hpp b/src/emulation/chip8genericbase.hpp index 06dd11e..065461e 100644 --- a/src/emulation/chip8genericbase.hpp +++ b/src/emulation/chip8genericbase.hpp @@ -36,7 +36,7 @@ class Chip8GenericBase : public IEmulationCore, public IChip8Emulator public: enum MegaChipBlendMode { eBLEND_NORMAL = 0, eBLEND_ALPHA_25 = 1, eBLEND_ALPHA_50 = 2, eBLEND_ALPHA_75 = 3, eBLEND_ADD = 4, eBLEND_MUL = 5 }; enum Chip8Font { C8F5_COSMAC, C8F5_ETI, C8F5_DREAM, C8F5_CHIP48, C8F5_FISHNCHIPS, C8F5_AKOUZ1 }; - enum Chip8BigFont { C8F10_NONE, C8F10_SCHIP10, C8F10_SCHIP11, C8F10_FISHNCHIPS, C8F10_MEGACHIP, C8F10_XOCHIP, C8F10_MADSTER, C8F10_AKOUZ1 }; + enum Chip8BigFont { C8F10_NONE, C8F10_SCHIP10, C8F10_SCHIP11, C8F10_FISHNCHIPS, C8F10_MEGACHIP, C8F10_XOCHIP, C8F10_AUCHIP, C8F10_AKOUZ1 }; explicit Chip8GenericBase(Chip8Variant variant, std::optional clockRate); bool inErrorState() const override { return _cpuState == eERROR; } bool isGenericEmulation() const override { return true; } diff --git a/src/emulation/chip8strict.cpp b/src/emulation/chip8strict.cpp index 38561f1..22355d4 100644 --- a/src/emulation/chip8strict.cpp +++ b/src/emulation/chip8strict.cpp @@ -100,4 +100,9 @@ GenericCpu::StackContent Chip8StrictEmulator::stack() const return {2, eBIG, eDOWNWARDS, std::span(_memory.data() + _options.ramSize - 0x160, 48)}; } +void Chip8StrictEmulator::setPalette(const Palette& palette) +{ + _screen.setPalette(palette); +} + } diff --git a/src/emulation/chip8strict.hpp b/src/emulation/chip8strict.hpp index 28b9004..038be9e 100644 --- a/src/emulation/chip8strict.hpp +++ b/src/emulation/chip8strict.hpp @@ -169,10 +169,30 @@ class Chip8StrictEmulator : public Chip8GenericBase switch (opcode >> 12) { case 0: if (opcode == 0x00E0) { // 00E0 - clear - _screen.setAll(0); - std::memset(_memory.data() + 0xF00, 0, 256); - ++_clearCounter; - addCycles(3078); + constexpr int eraseCycles = 3078; + auto cyclesLeftInFrame = cyclesLeftInCurrentFrame(); + if(_cpuState != eWAIT) { + _cpuState = eWAIT; + _rPC = uint16_t(_rPC - 2); + _instructionCycles = (eraseCycles > cyclesLeftInFrame) ? eraseCycles - cyclesLeftInFrame : 0; + addCycles(cyclesLeftInFrame); + } + else + { + if(_instructionCycles) { + _instructionCycles -= (_instructionCycles > cyclesLeftInFrame) ? cyclesLeftInFrame : _instructionCycles; + addCycles(cyclesLeftInFrame); + } + if (!_instructionCycles) { + _cpuState = eNORMAL; + _screen.setAll(0); + std::memset(_memory.data() + 0xF00, 0, 256); + ++_clearCounter; + } + else { + _rPC = uint16_t(_rPC - 2); + } + } } else if (opcode == 0x00EE) { // 00EE - return if(!_rSP) @@ -402,7 +422,7 @@ class Chip8StrictEmulator : public Chip8GenericBase else { _instructionCycles = 0; _cpuState = eNORMAL; - addCycles(8); + addCycles(10); } } else { @@ -556,6 +576,8 @@ class Chip8StrictEmulator : public Chip8GenericBase const VideoType* getScreen() const override { return &_screen; } + void setPalette(const Palette& palette) override; + protected: void wait(int instructionCycles = 0) { diff --git a/src/emulation/cosmacvip.cpp b/src/emulation/cosmacvip.cpp index cfe2414..2c7d956 100644 --- a/src/emulation/cosmacvip.cpp +++ b/src/emulation/cosmacvip.cpp @@ -1118,6 +1118,11 @@ const VideoType* CosmacVIP::getScreen() const return &_impl->_video.getScreen(); } +void CosmacVIP::setPalette(const Palette& palette) +{ + _impl->_video.setPalette(palette); +} + GenericCpu& CosmacVIP::getBackendCpu() { return _impl->_cpu; diff --git a/src/emulation/cosmacvip.hpp b/src/emulation/cosmacvip.hpp index 1a80c0c..eaedbcc 100644 --- a/src/emulation/cosmacvip.hpp +++ b/src/emulation/cosmacvip.hpp @@ -90,6 +90,7 @@ class CosmacVIP : public Chip8RealCoreBase, public Cdp1802Bus uint16_t getMaxScreenWidth() const override; uint16_t getMaxScreenHeight() const override; const VideoType* getScreen() const override; + void setPalette(const Palette& palette) override; bool isDisplayEnabled() const override; diff --git a/src/emulation/dream6800.cpp b/src/emulation/dream6800.cpp index 77a1310..5a9547e 100644 --- a/src/emulation/dream6800.cpp +++ b/src/emulation/dream6800.cpp @@ -740,6 +740,11 @@ const VideoType* Dream6800::getScreen() const return &_impl->_screen; } +void Dream6800::setPalette(const Palette& palette) +{ + _impl->_screen.setPalette(palette); +} + GenericCpu& Dream6800::getBackendCpu() { return _impl->_cpu; diff --git a/src/emulation/dream6800.hpp b/src/emulation/dream6800.hpp index 5ea5772..f27a13f 100644 --- a/src/emulation/dream6800.hpp +++ b/src/emulation/dream6800.hpp @@ -80,6 +80,7 @@ class Dream6800 : public Chip8RealCoreBase, public M6800Bus<> uint16_t getMaxScreenWidth() const override; uint16_t getMaxScreenHeight() const override; const VideoType* getScreen() const override; + void setPalette(const Palette& palette) override; bool isDisplayEnabled() const override; diff --git a/src/emulation/eti660.cpp b/src/emulation/eti660.cpp index 2c954a8..f252841 100644 --- a/src/emulation/eti660.cpp +++ b/src/emulation/eti660.cpp @@ -770,6 +770,11 @@ const VideoType* Eti660::getScreen() const return &_impl->_video.getScreen(); } +void Eti660::setPalette(const Palette& palette) +{ + _impl->_video.setPalette(palette); +} + GenericCpu& Eti660::getBackendCpu() { return _impl->_cpu; diff --git a/src/emulation/eti660.hpp b/src/emulation/eti660.hpp index b0da5b2..0af7ab4 100644 --- a/src/emulation/eti660.hpp +++ b/src/emulation/eti660.hpp @@ -79,6 +79,7 @@ class Eti660 : public Chip8RealCoreBase, public Cdp1802Bus uint16_t getMaxScreenWidth() const override; uint16_t getMaxScreenHeight() const override; const VideoType* getScreen() const override; + void setPalette(const Palette& palette) override; bool isDisplayEnabled() const override; diff --git a/src/emulation/hardware/cdp186x.cpp b/src/emulation/hardware/cdp186x.cpp index aedee61..42118fd 100644 --- a/src/emulation/hardware/cdp186x.cpp +++ b/src/emulation/hardware/cdp186x.cpp @@ -96,6 +96,11 @@ const Cdp186x::VideoType& Cdp186x::getScreen() const return _screen; } +void Cdp186x::setPalette(const Palette& palette) +{ + _screen.setPalette(palette); +} + std::pair Cdp186x::executeStep() { auto fc = static_cast((_cpu.cycles() >> 3) % VIDEO_CYCLES_PER_FRAME); diff --git a/src/emulation/hardware/cdp186x.hpp b/src/emulation/hardware/cdp186x.hpp index b67d4a7..1d86965 100644 --- a/src/emulation/hardware/cdp186x.hpp +++ b/src/emulation/hardware/cdp186x.hpp @@ -56,7 +56,7 @@ class Cdp186x int frames() const { return _frameCounter; } int cyclesPerFrame() const { return VIDEO_CYCLES_PER_FRAME; } const VideoType& getScreen() const; - + void setPalette(const Palette& palette); static int64_t machineCycle(cycles_t cycles) { return cycles >> 3; diff --git a/src/emulation/iemulationcore.hpp b/src/emulation/iemulationcore.hpp index 5e109c1..8ffbe5e 100644 --- a/src/emulation/iemulationcore.hpp +++ b/src/emulation/iemulationcore.hpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -111,7 +112,7 @@ class IEmulationCore { virtual const VideoRGBAType* getScreenRGBA() const { return nullptr; } virtual const VideoRGBAType* getWorkRGBA() const { return nullptr; } virtual uint8_t getScreenAlpha() const { return 255; } - virtual void setPalette(std::span palette) {} + virtual void setPalette(const Palette& palette) = 0; virtual void renderAudio(int16_t* samples, size_t frames, int sampleFrequency) { while (frames--) *samples++ = 0; } }; diff --git a/src/emulation/palette.hpp b/src/emulation/palette.hpp new file mode 100644 index 0000000..56a9110 --- /dev/null +++ b/src/emulation/palette.hpp @@ -0,0 +1,94 @@ +//--------------------------------------------------------------------------------------- +// src/emulation/palette.hpp +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2024, Steffen Schümann +// +// 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. +// +//--------------------------------------------------------------------------------------- +#pragma once + +#include +#include +#include +#include +#include + +namespace emu { +class Palette +{ +public: + struct Color { + Color() = default; + Color(const Color& other) = default; + Color(const uint8_t rval, const uint8_t gval, const uint8_t bval) : r(rval), g(gval), b(bval) {} + explicit Color(uint32_t val) : r(val >> 16), g(val >> 8), b(val) {} + explicit Color(std::string_view hex) + { + if(hex.length()>1 && hex[0] == '#') { + uint32_t colInt = std::strtoul(hex.data() + 1, nullptr, 16); + r = colInt>>16; + g = colInt>>8; + b = colInt; + } + else { + r = 0, g = 0, b = 0; + } + } + uint32_t toRGBInt() const + { + return (r << 16u) | (g << 8u) | b; + } + uint32_t toRGBAInt(const uint8_t alpha = 255) const + { + return (r << 24u) | (g << 16u) | (b << 8) | alpha; + } + bool operator==(const Color& other) const + { + return r == other.r && g == other.g && b == other.b; + } + std::string toString() const; + static Color fromRGBA(const uint32_t val) { return Color( val >> 24, val >> 16, val >> 8); } + uint8_t r, g, b; + }; + Palette() = default; + Palette(const std::initializer_list cols) + : colors(cols) + {} + Palette(const std::initializer_list cols) + { + colors.reserve(cols.size()); + for(const auto& col : cols) { + colors.emplace_back(col); + } + } + + bool empty() const { return colors.empty(); } + size_t size() const { return colors.size(); } + bool operator==(const Palette& other) const + { + return colors == other.colors && borderColor == other.borderColor && signalColor == other.signalColor; + } + std::vector colors; + std::optional borderColor{}; + std::optional signalColor{}; +}; + +} diff --git a/src/emulation/properties.cpp b/src/emulation/properties.cpp index 1183092..b480fdd 100644 --- a/src/emulation/properties.cpp +++ b/src/emulation/properties.cpp @@ -115,8 +115,8 @@ nlohmann::json Properties::createDiff(const Properties& other) const void Properties::applyDiff(const nlohmann::json& diff) { for (const auto& [key, value] : diff.items()) { - if (auto iter = _valueMap.find(key); iter != _valueMap.end()) { - auto& prop = iter->second; + if (auto* propPtr = find(key); propPtr != nullptr) { + auto& prop = *propPtr; std::visit(visitor{ [&](std::nullptr_t&) { }, [&](bool& val) { val = value.get(); }, diff --git a/src/emulation/properties.hpp b/src/emulation/properties.hpp index 2839247..08b0dc6 100644 --- a/src/emulation/properties.hpp +++ b/src/emulation/properties.hpp @@ -25,6 +25,7 @@ //--------------------------------------------------------------------------------------- #pragma once +#include #include #include @@ -52,63 +53,6 @@ visitor(Ts...) -> visitor; enum PropertyAccess { eReadOnly, eWritable, eInvisible }; -class Palette -{ -public: - struct Color { - Color(const uint8_t rval, const uint8_t gval, const uint8_t bval) : r(rval), g(gval), b(bval) {} - explicit Color(uint32_t val) : r(val >> 16), g(val >> 8), b(val) {} - explicit Color(std::string_view hex) - { - if(hex.length()>1 && hex[0] == '#') { - uint32_t colInt = std::strtoul(hex.data() + 1, nullptr, 16); - r = colInt>>16; - g = colInt>>8; - b = colInt; - } - else { - r = 0, g = 0, b = 0; - } - } - uint32_t toRGBInt() const - { - return (r << 16u) | (g << 8u) | b; - } - uint32_t toRGBAInt(const uint8_t alpha = 255) const - { - return (r << 24u) | (g << 16u) | (b << 8) | alpha; - } - bool operator==(const Color& other) const - { - return r == other.r && g == other.g && b == other.b; - } - std::string toString() const; - static Color fromRGBA(const uint32_t val) { return Color( val >> 24, val >> 16, val >> 8); } - uint8_t r, g, b; - }; - Palette() = default; - Palette(const std::initializer_list cols) - : colors(cols) - {} - Palette(const std::initializer_list cols) - { - colors.reserve(cols.size()); - for(const auto& col : cols) { - colors.emplace_back(col); - } - } - - bool empty() const { return colors.empty(); } - size_t size() const { return colors.size(); } - bool operator==(const Palette& other) const - { - return colors == other.colors && borderColor == other.borderColor && signalColor == other.signalColor; - } - std::vector colors; - std::optional borderColor{}; - std::optional signalColor{}; -}; - class Property { public: @@ -304,13 +248,21 @@ class Properties } return iter->second; } - Property& at(std::string_view key) + Property* find(std::string_view key) { for(auto& [mapKey, val] : _valueMap) { if(fuzzyCompare(mapKey, key)) { - return val; + return &val; } } + return nullptr; + } + Property& at(std::string_view key) + { + auto* prop = find(key); + if (prop) { + return *prop; + } throw std::runtime_error(fmt::format("No property named {}", key)); } const Property& at(std::string_view key) const diff --git a/src/knownfiles.ipp b/src/knownfiles.ipp index 2ba7908..4dc9b61 100644 --- a/src/knownfiles.ipp +++ b/src/knownfiles.ipp @@ -473,6 +473,7 @@ static KnownRomInfo g_knownRoms[] = { {"ce15f2f4281b1069d33e00c801d5ed4390049a76"_sha1, "xo-chip", "Mabe Village From Link'S Awakening (sound)",nullptr, nullptr}, {"ce33f148bfd5f1ca9edc68988b900a256905d057"_sha1, "chip-8", "M8Ze",nullptr, nullptr}, {"ce7a5355d90c4aabe0d96c5add93f4efb21f099b"_sha1, "chip-8", "Chipolarium",nullptr, nullptr}, + {"cf3a8c546038c63cd4cc1de8d171b9bf0d57c0ee"_sha1, "chip-48", "15 Puzzle (Roger Ivie, 1990)",nullptr, nullptr}, {"d02aa4e2e9a24c6f774c50a14c21ff30ba148daa"_sha1, "vip", "Snoopy Snipe Shoot",nullptr, nullptr}, {"d03f27f85a1cf68465e0853cc0c4abee4a94a4e5"_sha1, "schipc", "Turnover '77 (your name here, 2014-08-31)",R"({"instructionsPerFrame":200,"palette":{"border":"#000000","colors":["#ed7f37","#ffe900"],"signal":"#ffaa00"}})", nullptr}, {"d11e76793c231cdce513c09f0511202ed076834d"_sha1, "chip-8", "Space Racer (William Donnely, 2017)",nullptr, nullptr}, @@ -535,7 +536,8 @@ static KnownRomInfo g_knownRoms[] = { {"ea4ec4c07c97e1ad77eb9bfe237d2a1578795fbf"_sha1, "chip-8", "Seconds Counter (Michael Wales, 2014)",nullptr, nullptr}, {"ea6fc1ff6e57800e2322641f6f02ebd462dda2b8"_sha1, "chip-8", "2048 (Dr Gergo Erdi, 2014)",nullptr, nullptr}, {"ea7c12f458932527802fdd4a18e4c6700dd91138"_sha1, "chip-8", "Game 16 (TCNJ S.572.3)",nullptr, nullptr}, - {"ea9af3c09b0d9e265fcd92bcc5d51a2939fdf27a"_sha1, "chip-8", "15 Puzzle (Roger Ivie, 19xx)",nullptr, nullptr}, + {"ea9af3c09b0d9e265fcd92bcc5d51a2939fdf27a"_sha1, "chip-48", "15 Puzzle (Roger Ivie, 1990)",nullptr, nullptr}, + {"ea9af3c09b0d9e265fcd92bcc5d51a2939fdf27a"_sha1, "chip-8", "15 Puzzle (Roger Ivie, 1990)",nullptr, nullptr}, {"eb0076f3dd33b16fd040640b4b67bab19e491bef"_sha1, "chip-8", "Fl8Ppy Mouse (buffis, 2016)",nullptr, nullptr}, {"eb1a09cc11c73938f39ce8d52c8e06576dec3a32"_sha1, "xo-chip", "Octofonts (John Deeny, 2016)",nullptr, nullptr}, {"eb412becb086d3cbccce4e3e370b9149b969cff9"_sha1, "chip-8", "Flashing 1 Demo (Yahia Farghaly, 2019)",nullptr, nullptr}, diff --git a/src/librarian.cpp b/src/librarian.cpp index 402ad80..302ecd8 100644 --- a/src/librarian.cpp +++ b/src/librarian.cpp @@ -88,6 +88,20 @@ const KnownRomInfo* Librarian::findKnownRom(const Sha1::Digest& sha1) return nullptr; } +size_t Librarian::findKnownRoms(const Sha1::Digest& sha1, std::vector& outKnownRoms) +{ + outKnownRoms.clear(); + for(auto & g_knownRom : g_knownRoms) { + if(sha1 == g_knownRom.sha1) { + outKnownRoms.push_back(&g_knownRom); + } + else if(!outKnownRoms.empty()) { + break; + } + } + return outKnownRoms.size(); +} + std::string Librarian::Info::minimumOpcodeProfile() const { auto mask = static_cast(possibleVariants); @@ -328,7 +342,7 @@ emu::Properties Librarian::getPropertiesForSha1(const Sha1::Digest& sha1) if (const auto* romInfo = findKnownRom(sha1)) { auto properties = emu::CoreRegistry::propertiesForPreset(romInfo->preset); if(romInfo->options) { - //emu::from_json(nlohmann::json::parse(std::string(romInfo->options)), options); + properties.applyDiff(nlohmann::json::parse(std::string(romInfo->options))); } return properties; } diff --git a/src/librarian.hpp b/src/librarian.hpp index a4c87eb..002acc9 100644 --- a/src/librarian.hpp +++ b/src/librarian.hpp @@ -118,6 +118,7 @@ class Librarian static const KnownRomInfo& getRomInfo(size_t index); static const KnownRomInfo* getKnownRoms(); static const KnownRomInfo* findKnownRom(const Sha1::Digest& sha1); + static size_t findKnownRoms(const Sha1::Digest& sha1, std::vector& outKnownRoms); private: int _activeEntry{-1}; std::string _currentPath; diff --git a/src/stylemanager.cpp b/src/stylemanager.cpp index 8e7fe03..e714923 100644 --- a/src/stylemanager.cpp +++ b/src/stylemanager.cpp @@ -31,6 +31,7 @@ #include +namespace gui { #if 0 static const uint32_t cadmiumPalette[7] = { 0x00222bff, //E = 0! @@ -54,6 +55,7 @@ static const uint32_t cadmiumPalette[7] = { #endif static float cadmiumAverageHue = 200.0f; + #define CHIP8_STYLE_PROPS_COUNT 16 static const StyleManager::Entry chip8StyleProps[CHIP8_STYLE_PROPS_COUNT] = { {0, 0, 2}, // F! DEFAULT_BORDER_COLOR_NORMAL @@ -421,11 +423,11 @@ void StyleManager::renderAppearanceEditor() hsv = gui::HsvFromColor(col); } //if(!GuiIsLocked() && IsMouseButtonReleased(0) && hover) { - /*_selectedColor = &_colorPalette[i]; - _previousColor = _colorPalette[i]; - _colorText = fmt::format("{:06x}", _colorPalette[i]>>8); - _colorSelectOpen = true; - */ + /*_selectedColor = &_colorPalette[i]; + _previousColor = _colorPalette[i]; + _colorText = fmt::format("{:06x}", _colorPalette[i]>>8); + _colorSelectOpen = true; + */ //} DrawRectangleLines(pos.x + xoffset + i * 18, pos.y, 16, 16, GetColor(guard.getStyle(/*hover ? Style::BORDER_COLOR_FOCUSED :*/ Style::BORDER_COLOR_NORMAL))); } @@ -436,3 +438,5 @@ void StyleManager::renderAppearanceEditor() } End(); } + +} diff --git a/src/stylemanager.hpp b/src/stylemanager.hpp index 3a822a8..3cf3c6d 100644 --- a/src/stylemanager.hpp +++ b/src/stylemanager.hpp @@ -29,6 +29,9 @@ #include #include +struct Color; + +namespace gui { enum class Style { BORDER_COLOR_NORMAL, BASE_COLOR_NORMAL, @@ -51,8 +54,6 @@ enum class Style { TOOL_BUTTON_COLOR_ACTIVE }; -struct Color; - class StyleManager { public: @@ -99,3 +100,4 @@ class StyleManager static StyleManager* _instance; }; +}