From 090fb62d0648c4e7ff195eb81db33b92c0bafbca Mon Sep 17 00:00:00 2001 From: karwler Date: Sat, 26 Nov 2022 10:59:53 +0100 Subject: [PATCH] Multi monitor support * move to OpenGL * move crop to shader * add DirectX 11 support * add GPU selection * fix TileBox positions --- CMakeLists.txt | 21 +- rsc/themes.ini | 96 ++-- src/engine/drawSys.cpp | 303 ++++++----- src/engine/drawSys.h | 110 ++-- src/engine/fileSys.cpp | 144 +++-- src/engine/fileSys.h | 15 +- src/engine/inputSys.cpp | 18 +- src/engine/renderer.cpp | 1103 ++++++++++++++++++++++++++++++++++++++ src/engine/renderer.h | 272 ++++++++++ src/engine/scene.cpp | 23 +- src/engine/scene.h | 3 +- src/engine/windowSys.cpp | 206 +++++-- src/engine/windowSys.h | 30 +- src/prog/browser.cpp | 6 +- src/prog/downloader.cpp | 4 +- src/prog/program.cpp | 61 ++- src/prog/program.h | 9 +- src/prog/progs.cpp | 142 +++-- src/prog/progs.h | 11 +- src/utils/layouts.cpp | 153 +++--- src/utils/layouts.h | 40 +- src/utils/settings.cpp | 47 +- src/utils/settings.h | 121 +++-- src/utils/utils.cpp | 40 +- src/utils/utils.h | 112 ++-- src/utils/widgets.cpp | 349 ++++++++++-- src/utils/widgets.h | 181 +++++-- 27 files changed, 2894 insertions(+), 726 deletions(-) create mode 100644 src/engine/renderer.cpp create mode 100644 src/engine/renderer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fbb072bb..11e9968e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,10 @@ option(DOWNLOADER "Build with downloader. (currently mostly broken)" OFF) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") option(NATIVE "Build for the current CPU." OFF) endif() -if(UNIX) +if(CMAKE_SYSTEM_NAME MATCHES "Windows") + option(DIRECTX "Build with DirectX 11 support." ON) +elseif(UNIX) + option(OPENGLES "Build for OpenGL ES 3.0 instead of OpenGL 3.0." OFF) option(APPIMAGE "Package as an AppImage." OFF) endif() @@ -25,6 +28,8 @@ set(SRC_FILES "src/engine/fileSys.h" "src/engine/inputSys.cpp" "src/engine/inputSys.h" + "src/engine/renderer.cpp" + "src/engine/renderer.h" "src/engine/scene.cpp" "src/engine/scene.h" "src/engine/windowSys.cpp" @@ -55,9 +60,9 @@ endif() # dependencies set(VER_ARC "3.6.1") -set(VER_SDL "2.0.22") -set(VER_IMG "2.6.0") -set(VER_TTF "2.20.0") +set(VER_SDL "2.26.1") +set(VER_IMG "2.6.2") +set(VER_TTF "2.20.1") set(VER_GLM "0.9.9.8") set(VER_CURL "7.83.1") set(VER_LXML "2.9.14") @@ -225,11 +230,15 @@ else() endif() set(ICONS_DIR "${DATA_DIR}/icons") set(LICN_DIR "${DATA_DIR}/licenses") + include_directories($<$:/usr/include/libxml2>) endif() # compiler flags -add_compile_definitions($<$:DOWNLOADER> "$<$:UNICODE;_UNICODE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;$<$>:_WIN32_WINNT=0x600>>") +add_compile_definitions($<$:DOWNLOADER> + $<$:WITH_DIRECTX> + $<$:OPENGLES> + "$<$:UNICODE;_UNICODE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;$<$>:_WIN32_WINNT=0x600>>") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -pedantic-errors -Wformat=2 -Wmissing-declarations -Wold-style-cast -Wshadow -Wunreachable-code -Wno-implicit-fallthrough -Wno-old-style-cast -Wno-switch $<$>:-Wdouble-promotion>) @@ -253,6 +262,8 @@ endif() add_executable(${PROJECT_NAME} WIN32 ${SRC_FILES}) target_link_libraries(${PROJECT_NAME} SDL2 SDL2_image SDL2_ttf archive "$<$:pthread;dl>" + "$,opengl32,$,GLESv2,GL>>" + "$<$:d3d11.lib;d3dcompiler.lib;dxgi.lib>" "$<$:$,libcurl;libxml2,curl;xml2>>") set_target_properties(${PROJECT_NAME} PROPERTIES diff --git a/rsc/themes.ini b/rsc/themes.ini index ba17b84f..f66623ed 100644 --- a/rsc/themes.ini +++ b/rsc/themes.ini @@ -1,59 +1,59 @@ [Gray] -background=10 10 10 255 -normal=90 90 90 255 -dark=60 60 60 255 -light=120 120 120 255 -select=105 105 105 255 -tooltip=75 75 75 255 -text=210 210 210 255 -texture=210 210 210 255 +background=0.04 0.04 0.04 1 +normal=0.35 0.35 0.35 1 +dark=0.24 0.24 0.24 1 +light=0.47 0.47 0.47 1 +select=0.41 0.41 0.41 1 +tooltip=0.29 0.29 0.29 1 +text=0.82 0.82 0.82 1 +texture=0.82 0.82 0.82 1 [Brown] -background=17 8 2 255 -normal=102 91 79 255 -dark=69 62 53 255 -light=127 114 98 255 -select=119 106 91 255 -tooltip=80 70 60 255 -text=220 220 220 255 -texture=220 220 220 255 +background=0.07 0.03 0.01 1 +normal=0.4 0.36 0.31 1 +dark=0.27 0.24 0.21 1 +light=0.5 0.45 0.38 1 +select=0.47 0.42 0.36 1 +tooltip=0.31 0.27 0.24 1 +text=0.86 0.86 0.86 1 +texture=0.86 0.86 0.86 1 [White] -background=250 250 250 255 -normal=210 210 210 255 -dark=180 180 180 255 -light=230 230 230 255 -select=220 220 220 255 -tooltip=190 190 190 255 -text=60 60 60 255 -texture=60 60 60 255 +background=0.98 0.98 0.98 1 +normal=0.82 0.82 0.82 1 +dark=0.71 0.71 0.71 1 +light=0.9 0.9 0.9 1 +select=0.86 0.86 0.86 1 +tooltip=0.75 0.75 0.75 1 +text=0.24 0.24 0.24 1 +texture=0.24 0.24 0.24 1 [Starlight] -background=80 30 111 255 -normal=127 47 172 255 -dark=98 38 136 255 -light=159 242 217 255 -select=238 190 243 255 -tooltip=106 94 180 255 -text=255 255 255 255 -texture=255 255 255 255 +background=0.31 0.12 0.44 1 +normal=0.5 0.18 0.67 1 +dark=0.38 0.15 0.53 1 +light=0.62 0.95 0.85 1 +select=0.93 0.75 0.95 1 +tooltip=0.42 0.37 0.71 1 +text=1 1 1 1 +texture=1 1 1 1 [Gay] -background=176 48 96 255 -normal=255 105 180 255 -dark=255 20 147 255 -light=255 182 193 255 -select=238 130 238 255 -tooltip=255 60 160 255 -text=255 255 255 255 -texture=255 255 255 255 +background=0.69 0.19 0.38 1 +normal=1 0.41 0.71 1 +dark=1 0.08 0.58 1 +light=1 0.71 0.76 1 +select=0.93 0.51 0.93 1 +tooltip=1 0.24 0.63 1 +text=1 1 1 1 +texture=1 1 1 1 [Oh Shit] -background=255 255 255 255 -normal=255 255 255 255 -dark=255 255 255 255 -light=255 255 255 255 -select=255 255 255 255 -tooltip=255 255 255 255 -text=255 255 255 255 -texture=255 255 255 255 +background=1 1 1 1 +normal=1 1 1 1 +dark=1 1 1 1 +light=1 1 1 1 +select=1 1 1 1 +tooltip=1 1 1 1 +text=1 1 1 1 +texture=1 1 1 1 diff --git a/src/engine/drawSys.cpp b/src/engine/drawSys.cpp index 041cf53b..1e30ef7f 100644 --- a/src/engine/drawSys.cpp +++ b/src/engine/drawSys.cpp @@ -40,7 +40,7 @@ TTF_Font* FontSet::getFont(int height) { height = int(float(height) * heightScale); #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) if (TTF_SetFontSize(font, height)) { - std::cerr << TTF_GetError() << std::endl; + logError(TTF_GetError()); return nullptr; } #else @@ -52,7 +52,7 @@ TTF_Font* FontSet::getFont(int height) { if (font) fonts.emplace(size, font); else - std::cerr << "failed to load font:" << linend << TTF_GetError() << std::endl; + logError("failed to load font:", linend, TTF_GetError()); #endif return font; } @@ -63,12 +63,12 @@ int FontSet::length(const char* text, int height) { return len; } -int FontSet::length(char* text, sizet length, int height) { +int FontSet::length(const char* text, sizet length, int height) { int len = 0; char tmp = text[length]; - text[length] = '\0'; + const_cast(text)[length] = '\0'; TTF_SizeUTF8(getFont(height), text, &len, nullptr); - text[length] = tmp; + const_cast(text)[length] = tmp; return len; } @@ -101,37 +101,63 @@ string PictureLoader::limitToStr(uptrt i, uptrt c, uptrt m, sizet mag) const { // DRAW SYS -DrawSys::DrawSys(SDL_Window* window, pair info, Settings* sets, const FileSys* fileSys) { - // create and set up renderer - if (renderer = SDL_CreateRenderer(window, info.first, info.second); !renderer) - throw std::runtime_error("Failed to create renderer:\n"s + SDL_GetError()); - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); +DrawSys::DrawSys(const umap& windows, Settings* sets, const FileSys* fileSys, int iconSize) : + colors(fileSys->loadColors(sets->setTheme(sets->getTheme(), fileSys->getAvailableThemes()))) +{ + switch (sets->renderer) { +#ifdef WITH_DIRECTX + case Settings::Renderer::directx: + try { + renderer = new RendererDx(windows, sets, viewRes, colors[uint8(Color::background)]); // TODO: exceptions here rely on the destructor being called + break; + } catch (const std::runtime_error& err) { + logError(err.what()); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", err.what(), !windows.empty() ? windows.begin()->second : nullptr); + } +#endif + default: + renderer = new RendererGl(windows, sets, viewRes, colors[uint8(Color::background)]); + } - // load default textures with colors and initialize fonts + blank = texes.emplace(string(), renderer->texFromColor(u8vec4(255))).first->second; for (const fs::directory_entry& it : fs::directory_iterator(fileSys->dirIcons(), fs::directory_options::skip_permission_denied)) { - if (SDL_Texture* tex = IMG_LoadTexture(renderer, it.path().u8string().c_str())) +#if SDL_IMAGE_VERSION_ATLEAST(2, 6, 0) + if (SDL_RWops* ifh = SDL_RWFromFile(it.path().u8string().c_str(), "rb")) { + if (Texture* atex = renderer->texFromIcon(IMG_LoadSizedSVG_RW(ifh, iconSize, iconSize)); atex) + texes.emplace(it.path().stem().u8string(), atex); + else { + SDL_RWseek(ifh, 0, RW_SEEK_SET); + if (Texture* btex = renderer->texFromIcon(IMG_Load_RW(ifh, SDL_FALSE)); btex) + texes.emplace(it.path().stem().u8string(), btex); + else + logError("failed to load texture ", it.path().filename(), linend, IMG_GetError()); + } + SDL_RWclose(ifh); + } else + logError("failed to open texture ", it.path().filename(), linend, SDL_GetError()); +#else + if (Texture* tex = renderer->texFromIcon(IMG_Load(it.path().u8string().c_str())); tex) texes.emplace(it.path().stem().u8string(), tex); else - std::cerr << "failed to load texture " << it.path().filename() << '\n' << IMG_GetError() << std::endl; + logError("failed to load texture ", it.path().filename(), linend, IMG_GetError()); +#endif } - setTheme(sets->getTheme(), sets, fileSys); setFont(sets->font, sets, fileSys); } DrawSys::~DrawSys() { for (auto& [name, tex] : texes) - SDL_DestroyTexture(tex); - SDL_DestroyRenderer(renderer); + tex->free(); +} + +int DrawSys::findPointInView(ivec2 pos) const { + umap::const_iterator vit = findViewForPoint(pos); + return vit != renderer->getViews().end() ? vit->first : Renderer::singleDspId; } void DrawSys::setTheme(string_view name, Settings* sets, const FileSys* fileSys) { colors = fileSys->loadColors(sets->setTheme(name, fileSys->getAvailableThemes())); - SDL_Color clr = colors[uint8(Color::texture)]; - - for (auto& [ts, tex] : texes) { - SDL_SetTextureColorMod(tex, clr.r, clr.g, clr.b); - SDL_SetTextureAlphaMod(tex, clr.a); - } + renderer->setClearColor(colors[uint8(Color::background)]); } void DrawSys::setFont(string_view font, Settings* sets, const FileSys* fileSys) { @@ -145,168 +171,179 @@ void DrawSys::setFont(string_view font, Settings* sets, const FileSys* fileSys) fonts.init(path); } -SDL_Texture* DrawSys::texture(const string& name) const { +const Texture* DrawSys::texture(const string& name) const { try { return texes.at(name); } catch (const std::out_of_range&) { - std::cerr << "texture " << name << " doesn't exist" << std::endl; + logError("texture ", name, " doesn't exist"); } - return nullptr; + return texes.at(string()); } -vector DrawSys::transferPictures(vector>& pics) { - vector texs(pics.size()); +vector> DrawSys::transferPictures(vector>& pics) { + vector> texs(pics.size()); size_t j = 0; for (size_t i = 0; i < pics.size(); ++i) { - if (SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, pics[i].second)) - texs[j++] = Texture(std::move(pics[i].first), tex); + if (Texture* tex = renderer->texFromIcon(pics[i].second)) + texs[j++] = pair(std::move(pics[i].first), tex); else - std::cerr << SDL_GetError() << std::endl; + logError(SDL_GetError()); } texs.resize(j); return texs; } void DrawSys::drawWidgets(Scene* scene, bool mouseLast) { - // clear screen - SDL_Color bgcolor = colors[uint8(Color::background)]; - SDL_SetRenderDrawColor(renderer, bgcolor.r, bgcolor.g, bgcolor.b, bgcolor.a); - SDL_RenderClear(renderer); - - // draw main widgets and visible overlays - scene->getLayout()->drawSelf(); - if (scene->getOverlay() && scene->getOverlay()->on) - scene->getOverlay()->drawSelf(); - - // draw popup if exists and dim main widgets - if (scene->getPopup()) { - Rect view = viewport(); - SDL_SetRenderDrawColor(renderer, colorPopupDim.r, colorPopupDim.g, colorPopupDim.b, colorPopupDim.a); - SDL_RenderFillRect(renderer, &view); - - scene->getPopup()->drawSelf(); + for (auto [id, view] : renderer->getViews()) { + renderer->startDraw(view); + + // draw main widgets and visible overlays + scene->getLayout()->drawSelf(view->rect); + if (scene->getOverlay() && scene->getOverlay()->on) + scene->getOverlay()->drawSelf(view->rect); + + // draw popup if exists and dim main widgets + if (scene->getPopup()) { + renderer->drawRect(blank, view->rect, view->rect, colorPopupDim); + scene->getPopup()->drawSelf(view->rect); + } + + // draw context menu + if (scene->getContext()) + scene->getContext()->drawSelf(view->rect); + + // draw extra stuff on top + if (scene->getCapture()) + scene->getCapture()->drawTop(view->rect); + else if (Button* but = dynamic_cast(scene->select); mouseLast && but) + drawTooltip(but, view->rect); + + renderer->finishDraw(view); } +} - // draw context menu - if (scene->getContext()) - scene->getContext()->drawSelf(); +bool DrawSys::drawPicture(const Picture* wgt, const Rect& view) { + if (Rect rect = wgt->rect(); rect.overlap(view)) { + Rect frame = wgt->frame(); + if (wgt->showBG) + renderer->drawRect(blank, rect, frame, colors[uint8(wgt->color())]); + if (wgt->tex) + renderer->drawRect(wgt->tex, wgt->texRect(), frame, colors[uint8(Color::texture)]); + return true; + } + return false; +} - // draw caret if capturing LineEdit - if (LabelEdit* let = dynamic_cast(scene->getCapture())) - drawRect(let->caretRect(), Color::light); - if (Button* but = dynamic_cast(scene->select); mouseLast && but && but->getTooltip()) - drawTooltip(but); +void DrawSys::drawCheckBox(const CheckBox* wgt, const Rect& view) { + if (drawPicture(wgt, view)) // draw background + renderer->drawRect(blank, wgt->boxRect(), wgt->frame(), colors[uint8(wgt->boxColor())]); // draw checkbox +} - SDL_RenderPresent(renderer); +void DrawSys::drawSlider(const Slider* wgt, const Rect& view) { + if (drawPicture(wgt, view)) { // draw background + Rect frame = wgt->frame(); + renderer->drawRect(blank, wgt->barRect(), frame, colors[uint8(Color::dark)]); // draw bar + renderer->drawRect(blank, wgt->sliderRect(), frame, colors[uint8(Color::light)]); // draw slider + } } -void DrawSys::drawPicture(const Picture* wgt) { - if (wgt->showBG) - drawRect(wgt->rect().intersect(wgt->frame()), wgt->color()); - if (wgt->tex) - drawImage(wgt->tex, wgt->texRect(), wgt->frame()); +void DrawSys::drawProgressBar(const ProgressBar* wgt, const Rect& view) { + if (Rect rect = wgt->rect(); rect.overlap(view)) { + Rect frame = wgt->frame(); + renderer->drawRect(blank, rect, frame, colors[uint8(Color::normal)]); // draw background + renderer->drawRect(blank, wgt->barRect(), frame, colors[uint8(Color::light)]); // draw bar + } } -void DrawSys::drawCheckBox(const CheckBox* wgt) { - drawPicture(wgt); // draw background - drawRect(wgt->boxRect().intersect(wgt->frame()), wgt->boxColor()); // draw checkbox +void DrawSys::drawLabel(const Label* wgt, const Rect& view) { + if (drawPicture(wgt, view) && wgt->getTextTex()) + renderer->drawRect(wgt->getTextTex(), wgt->textRect(), wgt->textFrame(), colors[uint8(Color::text)]); } -void DrawSys::drawSlider(const Slider* wgt) { - Rect frame = wgt->frame(); - drawPicture(wgt); // draw background - drawRect(wgt->barRect().intersect(frame), Color::dark); // draw bar - drawRect(wgt->sliderRect().intersect(frame), Color::light); // draw slider +void DrawSys::drawCaret(const Rect& rect, const Rect& frame, const Rect& view) { + if (rect.overlap(view)) + renderer->drawRect(blank, rect, frame, colors[uint8(Color::light)]); } -void DrawSys::drawProgressBar(const ProgressBar* wgt) { - drawRect(wgt->rect(), Color::normal); // draw background - drawRect(wgt->barRect().intersect(wgt->frame()), Color::light); // draw bar +void DrawSys::drawWindowArranger(const WindowArranger* wgt, const Rect& view) { + if (Rect rect = wgt->rect(); rect.overlap(view)) { + Rect frame = wgt->frame(); + renderer->drawRect(blank, rect, frame, colors[uint8(wgt->color())]); + for (const auto& [id, dsp] : wgt->getDisps()) + if (!wgt->draggingDisp(id)) { + auto [rct, color, text, tex] = wgt->dispRect(id, dsp); + drawWaDisp(rct, color, text, tex, frame, view); + } + } } -void DrawSys::drawLabel(const Label* wgt) { - drawPicture(wgt); - if (wgt->textTex) - drawText(wgt->textTex, wgt->textRect(), wgt->textFrame()); +void DrawSys::drawWaDisp(const Rect& rect, Color color, const Rect& text, const Texture* tex, const Rect& frame, const Rect& view) { + if (rect.overlap(view)) { + renderer->drawRect(blank, rect, frame, colors[uint8(color)]); + if (tex) + renderer->drawRect(tex, text, frame, colors[uint8(Color::text)]); + } } -void DrawSys::drawScrollArea(const ScrollArea* box) { +void DrawSys::drawScrollArea(const ScrollArea* box, const Rect& view) { mvec2 vis = box->visibleWidgets(); // get index interval of items on screen and draw children for (sizet i = vis.x; i < vis.y; ++i) - box->getWidget(i)->drawSelf(); + box->getWidget(i)->drawSelf(view); - drawRect(box->barRect(), Color::dark); // draw scroll bar - drawRect(box->sliderRect(), Color::light); // draw scroll slider + if (Rect bar = box->barRect(); bar.overlap(view)) { + Rect frame = box->frame(); + renderer->drawRect(blank, bar, frame, colors[uint8(Color::dark)]); // draw scroll bar + renderer->drawRect(blank, box->sliderRect(), frame, colors[uint8(Color::light)]); // draw scroll slider + } } -void DrawSys::drawReaderBox(const ReaderBox* box) { +void DrawSys::drawReaderBox(const ReaderBox* box, const Rect& view) { mvec2 vis = box->visibleWidgets(); for (sizet i = vis.x; i < vis.y; ++i) - box->getWidget(i)->drawSelf(); + box->getWidget(i)->drawSelf(view); - if (box->showBar()) { - drawRect(box->barRect(), Color::dark); - drawRect(box->sliderRect(), Color::light); + if (Rect bar = box->barRect(); box->showBar() && bar.overlap(view)) { + Rect frame = box->frame(); + renderer->drawRect(blank, bar, frame, colors[uint8(Color::dark)]); + renderer->drawRect(blank, box->sliderRect(), frame, colors[uint8(Color::light)]); } } -void DrawSys::drawPopup(const Popup* box) { - drawRect(box->rect(), box->bgColor); // draw background - for (Widget* it : box->getWidgets()) // draw children - it->drawSelf(); -} - -void DrawSys::drawTooltip(Button* but) { - ivec2 res; - Rect rct = but->tooltipRect(res); - drawRect(rct, Color::tooltip); - - rct = Rect(rct.pos() + Button::tooltipMargin, res); - SDL_RenderCopy(renderer, but->getTooltip(), nullptr, &rct); -} - -void DrawSys::drawRect(const Rect& rect, Color color) { - SDL_Color clr = colors[uint8(color)]; - SDL_SetRenderDrawColor(renderer, clr.r, clr.g, clr.b, clr.a); - SDL_RenderFillRect(renderer, &rect); +void DrawSys::drawPopup(const Popup* box, const Rect& view) { + if (Rect rect = box->rect(); rect.overlap(view)) { + renderer->drawRect(blank, rect, box->frame(), colors[uint8(box->bgColor)]); // draw background + for (const Widget* it : box->getWidgets()) // draw children + it->drawSelf(view); + } } -void DrawSys::drawText(SDL_Texture* tex, const Rect& rect, const Rect& frame) { - // crop destination rect and original texture rect - Rect dst = rect; - Rect crop = dst.crop(frame); - Rect src(crop.pos(), rect.size() - crop.size()); - SDL_RenderCopy(renderer, tex, &src, &dst); +void DrawSys::drawTooltip(Button* but, const Rect& view) { + const Texture* tip = but->getTooltip(); + if (Rect rct = but->tooltipRect(); tip && rct.overlap(view)) { + renderer->drawRect(blank, rct, view, colors[uint8(Color::tooltip)]); + renderer->drawRect(tip, Rect(rct.pos() + Button::tooltipMargin, tip->getRes()), rct, colors[uint8(Color::text)]); + } } -void DrawSys::drawImage(SDL_Texture* tex, const Rect& rect, const Rect& frame) { - // get destination rect and crop - Rect dst = rect; - Rect crop = dst.crop(frame); +Widget* DrawSys::getSelectedWidget(const Layout* box, ivec2 mPos) { + umap::const_iterator vit = findViewForPoint(mPos); + if (vit == renderer->getViews().end()) + return nullptr; - // get cropped source rect - ivec2 res = texSize(tex); - vec2 factor(vec2(res) / vec2(rect.size())); - Rect src(vec2(crop.pos()) * factor, res - ivec2(vec2(crop.size()) * factor)); - SDL_RenderCopy(renderer, tex, &src, &dst); + renderer->startSelDraw(vit->second, mPos); + box->drawAddr(vit->second->rect); + return renderer->finishSelDraw(vit->second); } -SDL_Texture* DrawSys::renderText(const char* text, int height) { - if (SDL_Surface* surf = TTF_RenderUTF8_Blended(fonts.getFont(height), text, colors[uint8(Color::text)])) { - SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); - SDL_FreeSurface(surf); - return tex; - } - return nullptr; +void DrawSys::drawPictureAddr(const Picture* wgt, const Rect& view) { + if (Rect rect = wgt->rect(); rect.overlap(view)) + renderer->drawSelRect(wgt, rect, wgt->frame()); } -SDL_Texture* DrawSys::renderText(const char* text, int height, uint length) { - if (SDL_Surface* surf = TTF_RenderUTF8_Blended_Wrapped(fonts.getFont(height), text, colors[uint8(Color::text)], length)) { - SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); - SDL_FreeSurface(surf); - return tex; - } - return nullptr; +void DrawSys::drawLayoutAddr(const Layout* wgt, const Rect& view) { + renderer->drawSelRect(wgt, wgt->rect(), wgt->frame()); // invisible background to set selection area + for (const Widget* it : wgt->getWidgets()) + it->drawAddr(view); } void DrawSys::loadTexturesDirectoryThreaded(bool* running, uptr pl) { diff --git a/src/engine/drawSys.h b/src/engine/drawSys.h index f957fddd..b5a238d7 100644 --- a/src/engine/drawSys.h +++ b/src/engine/drawSys.h @@ -1,12 +1,11 @@ #pragma once -#include "utils/settings.h" +#include "renderer.h" #ifdef _WIN32 #include #else #include #endif -#include // loads different font sizes from one file class FontSet { @@ -32,7 +31,7 @@ class FontSet { #endif TTF_Font* getFont(int height); int length(const char* text, int height); - int length(char* text, sizet length, int height); + int length(const char* text, sizet length, int height); }; inline FontSet::~FontSet() { @@ -63,59 +62,80 @@ class DrawSys { public: static constexpr int cursorHeight = 20; private: - static constexpr SDL_Color colorPopupDim = { 0, 0, 0, 127 }; + static constexpr vec4 colorPopupDim = vec4(0.f, 0.f, 0.f, 0.5f); - SDL_Renderer* renderer; - array colors; // use Color as index + Renderer* renderer = nullptr; + ivec2 viewRes = ivec2(0); + array colors; FontSet fonts; - umap texes; // name, texture data + umap texes; + const Texture* blank; public: - DrawSys(SDL_Window* window, pair info, Settings* sets, const FileSys* fileSys); + DrawSys(const umap& windows, Settings* sets, const FileSys* fileSys, int iconSize); ~DrawSys(); - Rect viewport() const; + ivec2 getViewRes() const; + const Rect& getView(int id) const; + void updateView(); + int findPointInView(ivec2 pos) const; + void setVsync(Settings::VSync& vsync); void setTheme(string_view name, Settings* sets, const FileSys* fileSys); int textLength(const char* text, int height); int textLength(const string& text, int height); - int textLength(char* text, sizet length, int height); + int textLength(const char* text, sizet length, int height); + int textLength(const string& text, sizet length, int height); void setFont(string_view font, Settings* sets, const FileSys* fileSys); #if !SDL_TTF_VERSION_ATLEAST(2, 0, 18) void clearFonts(); #endif - SDL_Texture* texture(const string& name) const; - vector transferPictures(vector>& pics); + const Texture* texture(const string& name) const; + vector> transferPictures(vector>& pics); void drawWidgets(Scene* scene, bool mouseLast); - void drawPicture(const Picture* wgt); - void drawCheckBox(const CheckBox* wgt); - void drawSlider(const Slider* wgt); - void drawProgressBar(const ProgressBar* wgt); - void drawLabel(const Label* wgt); - void drawScrollArea(const ScrollArea* box); - void drawReaderBox(const ReaderBox* box); - void drawPopup(const Popup* box); - void drawTooltip(Button* but); - - SDL_Texture* renderText(const char* text, int height); - SDL_Texture* renderText(const string& text, int height); - SDL_Texture* renderText(const char* text, int height, uint length); - SDL_Texture* renderText(const string& text, int height, uint length); + bool drawPicture(const Picture* wgt, const Rect& view); + void drawCheckBox(const CheckBox* wgt, const Rect& view); + void drawSlider(const Slider* wgt, const Rect& view); + void drawProgressBar(const ProgressBar* wgt, const Rect& view); + void drawLabel(const Label* wgt, const Rect& view); + void drawCaret(const Rect& rect, const Rect& frame, const Rect& view); + void drawWindowArranger(const WindowArranger* wgt, const Rect& view); + void drawWaDisp(const Rect& rect, Color color, const Rect& text, const Texture* tex, const Rect& frame, const Rect& view); + void drawScrollArea(const ScrollArea* box, const Rect& view); + void drawReaderBox(const ReaderBox* box, const Rect& view); + void drawPopup(const Popup* box, const Rect& view); + void drawTooltip(Button* but, const Rect& view); + + Widget* getSelectedWidget(const Layout* box, ivec2 mPos); + void drawPictureAddr(const Picture* wgt, const Rect& view); + void drawLayoutAddr(const Layout* wgt, const Rect& view); + + Texture* renderText(const char* text, int height); + Texture* renderText(const string& text, int height); + Texture* renderText(const char* text, int height, uint length); + Texture* renderText(const string& text, int height, uint length); static void loadTexturesDirectoryThreaded(bool* running, uptr pl); static void loadTexturesArchiveThreaded(bool* running, uptr pl); private: static sizet initLoadLimits(const PictureLoader* pl, vector& files, uptrt& lim, uptrt& mem); static mapFiles initLoadLimits(const PictureLoader* pl, uptrt& start, uptrt& end, uptrt& lim, uptrt& mem); - - void drawRect(const Rect& rect, Color color); - void drawText(SDL_Texture* tex, const Rect& rect, const Rect& frame); - void drawImage(SDL_Texture* tex, const Rect& rect, const Rect& frame); + umap::const_iterator findViewForPoint(ivec2 pos) const; }; -inline Rect DrawSys::viewport() const { - Rect view; - SDL_RenderGetViewport(renderer, &view); - return view; +inline ivec2 DrawSys::getViewRes() const { + return viewRes; +} + +inline const Rect& DrawSys::getView(int id) const { + return renderer->getViews().at(id)->rect; +} + +inline void DrawSys::updateView() { + renderer->updateView(viewRes); +} + +inline void DrawSys::setVsync(Settings::VSync& vsync) { + renderer->setSwapInterval(vsync); } inline int DrawSys::textLength(const char* text, int height) { @@ -126,18 +146,34 @@ inline int DrawSys::textLength(const string& text, int height) { return fonts.length(text.c_str(), height); } -inline int DrawSys::textLength(char* text, sizet length, int height) { +inline int DrawSys::textLength(const char* text, sizet length, int height) { return fonts.length(text, length, height); } -inline SDL_Texture* DrawSys::renderText(const string& text, int height) { +inline int DrawSys::textLength(const string& text, sizet length, int height) { + return fonts.length(text.c_str(), length, height); +} + +inline Texture* DrawSys::renderText(const char* text, int height) { + return renderer->texFromText(TTF_RenderUTF8_Blended(fonts.getFont(height), text, { 255, 255, 255, 255 })); +} + +inline Texture* DrawSys::renderText(const string& text, int height) { return renderText(text.c_str(), height); } -inline SDL_Texture* DrawSys::renderText(const string& text, int height, uint length) { +inline Texture* DrawSys::renderText(const char* text, int height, uint length) { + return renderer->texFromText(TTF_RenderUTF8_Blended_Wrapped(fonts.getFont(height), text, { 255, 255, 255, 255 }, length)); +} + +inline Texture* DrawSys::renderText(const string& text, int height, uint length) { return renderText(text.c_str(), height, length); } +inline umap::const_iterator DrawSys::findViewForPoint(ivec2 pos) const { + return std::find_if(renderer->getViews().begin(), renderer->getViews().end(), [&pos](const pair& it) -> bool { return it.second->rect.contain(pos); }); +} + #if !SDL_TTF_VERSION_ATLEAST(2, 0, 18) inline void DrawSys::clearFonts() { fonts.clear(); diff --git a/src/engine/fileSys.cpp b/src/engine/fileSys.cpp index 3adbfa2a..7f57edfa 100644 --- a/src/engine/fileSys.cpp +++ b/src/engine/fileSys.cpp @@ -4,9 +4,8 @@ #include #include #include -#ifdef _WIN32 -#include -#else +#include +#ifndef _WIN32 #include #include #include @@ -85,7 +84,7 @@ FileSys::FileSys() { SDL_free(path); } if (dirBase.empty()) - std::cerr << "failed to get base directory" << std::endl; + logError("failed to get base directory"); #ifdef _WIN32 dirSets = fs::path(_wgetenv(L"AppData")) / L"VertiRead"; @@ -95,12 +94,27 @@ FileSys::FileSys() { dirConfs = dirBase / "share/vertiread"; #endif + try { + std::regex rgx(R"r(log_[\d\-_]+\.txt)r", std::regex::icase | std::regex::optimize); + for (const fs::directory_entry& it : fs::directory_iterator(dirSets, fs::directory_options::skip_permission_denied)) + if (std::error_code ec; std::regex_match(it.path().filename().u8string(), rgx) && it.is_regular_file(ec)) + fs::remove(it.path(), ec); + + fs::path logPath = dirSets / ("log_" + currentDateTimeStr('-', '_') + ".txt"); + if (logFile.open(logPath); logFile.good()) + SDL_LogSetOutputFunction(logWrite, &logFile); + else + logError("failed to create log file: ", logPath); + } catch (const std::runtime_error& err) { + logError(err.what()); + } + // check if all (more or less) necessary files and directories exist try { if (!fs::is_directory(dirSets) && !fs::create_directories(dirSets)) throw std::runtime_error("failed to create settings directory"); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logError(err.what()); } try { if (!fs::is_directory(dirIcons())) @@ -108,10 +122,15 @@ FileSys::FileSys() { if (!fs::is_regular_file(dirConfs / fileThemes)) throw std::runtime_error("failed to find themes file"); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logError(err.what()); } } +FileSys::~FileSys() { + SDL_LogSetOutputFunction(nullptr, nullptr); + logFile.close(); +} + vector FileSys::getAvailableThemes() const { vector themes; vector lines = IniLine::readLines(readTextFile(dirSets / fileThemes, false)); @@ -124,8 +143,8 @@ vector FileSys::getAvailableThemes() const { return !themes.empty() ? themes : vector{ "default" }; } -array FileSys::loadColors(string_view theme) const { - array colors = Settings::defaultColors; +array FileSys::loadColors(string_view theme) const { + array colors = Settings::defaultColors; vector lines = readFileLines(dirSets / fileThemes, false); if (lines.empty()) lines = readFileLines(dirConfs / fileThemes); @@ -140,7 +159,7 @@ array FileSys::loadColors(string_view break; if (il.getType() == IniLine::Type::prpVal) if (sizet cid = strToEnum(Settings::colorNames, il.getPrp()); cid < colors.size()) - colors[cid] = readColor(il.getVal()); + colors[cid] = toVec(il.getVal()); } return colors; } @@ -173,35 +192,42 @@ Settings* FileSys::loadSettings() const { if (il.getType() != IniLine::Type::prpVal) continue; - if (il.getPrp() == iniKeywordMaximized) + if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordMaximized)) sets->maximized = toBool(il.getVal()); - else if (il.getPrp() == iniKeywordFullscreen) - sets->fullscreen = toBool(il.getVal()); - else if (il.getPrp() == iniKeywordResolution) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordScreen)) + sets->screen = strToEnum(Settings::screenModeNames, il.getVal(), Settings::defaultScreenMode); + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordDisplay)) + sets->displays[toNum(il.getKey())] = toVec(il.getVal()); + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordResolution)) sets->resolution = toVec(il.getVal()); - else if (il.getPrp() == iniKeywordDirection) - sets->direction = strToEnum(Direction::names, il.getVal(), Direction::down); - else if (il.getPrp() == iniKeywordZoom) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordVSync)) + sets->vsync = Settings::VSync(strToEnum(Settings::vsyncNames, il.getVal(), int8(Settings::defaultVSync) + 1) - 1); + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordRenderer)) + sets->renderer = strToEnum(Settings::rendererNames, il.getVal(), Settings::defaultRenderer); + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordGpuSelecting)) + sets->gpuSelecting = toBool(il.getVal()); + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordDirection)) + sets->direction = strToEnum(Direction::names, il.getVal(), Settings::defaultDirection); + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordZoom)) sets->zoom = toNum(il.getVal()); - else if (il.getPrp() == iniKeywordSpacing) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordSpacing)) sets->spacing = toNum(il.getVal()); - else if (il.getPrp() == iniKeywordPictureLimit) + else if (!SDL_strcasecmp(il.getPrp().c_str(),iniKeywordPictureLimit)) sets->picLim.set(il.getVal()); - else if (il.getPrp() == iniKeywordFont) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordFont)) sets->font = FileSys::isFont(findFont(il.getVal())) ? il.getVal() : Settings::defaultFont; - else if (il.getPrp() == iniKeywordTheme) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordTheme)) sets->setTheme(il.getVal(), getAvailableThemes()); - else if (il.getPrp() == iniKeywordShowHidden) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordShowHidden)) sets->showHidden = toBool(il.getVal()); - else if (il.getPrp() == iniKeywordLibrary) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordLibrary)) sets->setDirLib(fs::u8path(il.getVal()), dirSets); - else if (il.getPrp() == iniKeywordRenderer) - sets->renderer = std::move(il.getVal()); - else if (il.getPrp() == iniKeywordScrollSpeed) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordScrollSpeed)) sets->scrollSpeed = toVec(il.getVal()); - else if (il.getPrp() == iniKeywordDeadzone) + else if (!SDL_strcasecmp(il.getPrp().c_str(), iniKeywordDeadzone)) sets->setDeadzone(toNum(il.getVal())); } + sets->unionDisplays(); return sets; } @@ -209,12 +235,17 @@ void FileSys::saveSettings(const Settings* sets) const { fs::path path = dirSets / fileSettings; std::ofstream ofh(path, std::ios::binary); if (!ofh.good()) { - std::cerr << "failed to write settings file " << path << std::endl; + logError("failed to write settings file ", path); return; } IniLine::writeVal(ofh, iniKeywordMaximized, toStr(sets->maximized)); - IniLine::writeVal(ofh, iniKeywordFullscreen, toStr(sets->fullscreen)); + IniLine::writeVal(ofh, iniKeywordScreen, Settings::screenModeNames[sizet(sets->screen)]); + for (const auto& [id, rect] : sets->displays) + IniLine::writeKeyVal(ofh, iniKeywordDisplay, id, toStr(rect.toVec())); IniLine::writeVal(ofh, iniKeywordResolution, sets->resolution.x, ' ', sets->resolution.y); + IniLine::writeVal(ofh, iniKeywordVSync, Settings::vsyncNames[sizet(sets->vsync) + 1]); + IniLine::writeVal(ofh, iniKeywordRenderer, Settings::rendererNames[sizet(sets->renderer)]); + IniLine::writeVal(ofh, iniKeywordGpuSelecting, toStr(sets->gpuSelecting)); IniLine::writeVal(ofh, iniKeywordZoom, sets->zoom); IniLine::writeVal(ofh, iniKeywordPictureLimit, PicLim::names[uint8(sets->picLim.type)], ' ', sets->picLim.getCount(), ' ', PicLim::memoryString(sets->picLim.getSize())); IniLine::writeVal(ofh, iniKeywordSpacing, sets->spacing); @@ -223,7 +254,6 @@ void FileSys::saveSettings(const Settings* sets) const { IniLine::writeVal(ofh, iniKeywordTheme, sets->getTheme()); IniLine::writeVal(ofh, iniKeywordShowHidden, toStr(sets->showHidden)); IniLine::writeVal(ofh, iniKeywordLibrary, sets->getDirLib().u8string()); - IniLine::writeVal(ofh, iniKeywordRenderer, sets->renderer); IniLine::writeVal(ofh, iniKeywordScrollSpeed, sets->scrollSpeed.x, ' ', sets->scrollSpeed.y); IniLine::writeVal(ofh, iniKeywordDeadzone, sets->getDeadzone()); } @@ -273,7 +303,7 @@ void FileSys::saveBindings(const array& bindings fs::path path = dirSets / fileBindings; std::ofstream ofh(path, std::ios::binary); if (!ofh.good()) { - std::cerr << "failed to write bindings file " << path << std::endl; + logError("failed to write bindings file ", path); return; } for (sizet i = 0; i < bindings.size(); ++i) { @@ -316,14 +346,14 @@ string FileSys::readTextFile(const fs::path& file, bool printMessage) { return text; } if (printMessage) - std::cerr << "failed to open file " << file << std::endl; + logError("failed to open file ", file); return string(); } bool FileSys::writeTextFile(const fs::path& file, const vector& lines) { std::ofstream ofh(file, std::ios::binary); if (!ofh.good()) { - std::cerr << "failed to write file " << file << std::endl; + logError("failed to write file ", file); return false; } @@ -334,28 +364,6 @@ bool FileSys::writeTextFile(const fs::path& file, const vector& lines) { return true; } -SDL_Color FileSys::readColor(string_view str) { - SDL_Color color = { 0, 0, 0, 255 }; - sizet p = 0; - for (; p < str.length() && isSpace(str[p]); ++p); - if (p < str.length() && str[p] == '#') { - while (++p < str.length() && str[p] == '#'); - uint32 num; - if (std::from_chars_result res = std::from_chars(str.data() + p, str.data() + str.length(), num, 0x10); res.ec == std::errc()) { - if (uint8 mov = (8 - (res.ptr - str.data() - p)) * 4) - num = (num << mov) + UINT8_MAX; - memcpy(&color, &num, sizeof(uint32)); - } - } else for (uint i = 0; i < 4 && p < str.length();) { - if (std::from_chars_result res = std::from_chars(str.data() + p, str.data() + str.length(), reinterpret_cast(&color)[i]); res.ec == std::errc()) { - ++i; - for (p = res.ptr - str.data(); p < str.length() && isSpace(str[p]); ++p); - } else - ++p; - } - return color; -} - vector FileSys::listDir(const fs::path &drc, bool files, bool dirs, bool showHidden) { #ifdef _WIN32 if (drc.empty()) // if in "root" directory, get drive letters and present them as directories @@ -553,7 +561,7 @@ void FileSys::moveContentThreaded(bool* running, fs::path src, fs::path dst) { #else if (fs::path path = src / files[i]; rename(path.c_str(), (dst / files[i]).c_str())) #endif - std::cerr << "failed no move " << path << std::endl; + logError("failed no move ", path); } pushEvent(UserCode::moveFinished); *running = false; @@ -610,7 +618,7 @@ fs::path FileSys::searchFontDirs(string_view font, initlist dirs) { if (!SDL_strcasecmp(it.path().stem().u8string().c_str(), font.data()) && isFont(it.path())) return it.path(); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logError(err.what()); } } return fs::path(); @@ -626,3 +634,27 @@ vector FileSys::listDrives() { return letters; } #endif + +void SDLCALL FileSys::logWrite(void* userdata, int, SDL_LogPriority priority, const char* message) { + string prefix = currentDateTimeStr() + ' '; + switch (priority) { + case SDL_LOG_PRIORITY_VERBOSE: + prefix += "VERBOSE: "; + break; + case SDL_LOG_PRIORITY_DEBUG: + prefix += "DEBUG: "; + break; + case SDL_LOG_PRIORITY_INFO: + prefix += "INFO: "; + break; + case SDL_LOG_PRIORITY_WARN: + prefix += "WARN: "; + break; + case SDL_LOG_PRIORITY_ERROR: + prefix += "ERROR: "; + break; + case SDL_LOG_PRIORITY_CRITICAL: + prefix += "CRITICAL: "; + } + *static_cast(userdata) << prefix << message << linend; +} diff --git a/src/engine/fileSys.h b/src/engine/fileSys.h index 0830f06f..3c31cc36 100644 --- a/src/engine/fileSys.h +++ b/src/engine/fileSys.h @@ -79,8 +79,6 @@ void IniLine::writeKeyVal(std::ofstream& ss, P&& prp, K&& key, T&&... val) { // handles all filesystem interactions class FileSys { -public: - static constexpr char extIni[] = ".ini"; private: static constexpr char defaultFrMode[] = "rb"; static constexpr char defaultFwMode[] = "wb"; @@ -99,8 +97,12 @@ class FileSys { static constexpr char fileBooks[] = "books.dat"; static constexpr char iniKeywordMaximized[] = "maximized"; - static constexpr char iniKeywordFullscreen[] = "fullscreen"; + static constexpr char iniKeywordScreen[] = "screen"; + static constexpr char iniKeywordDisplay[] = "display"; static constexpr char iniKeywordResolution[] = "resolution"; + static constexpr char iniKeywordVSync[] = "vsync"; + static constexpr char iniKeywordRenderer[] = "renderer"; + static constexpr char iniKeywordGpuSelecting[] = "gpu_selecting"; static constexpr char iniKeywordDirection[] = "direction"; static constexpr char iniKeywordZoom[] = "zoom"; static constexpr char iniKeywordSpacing[] = "spacing"; @@ -109,7 +111,6 @@ class FileSys { static constexpr char iniKeywordTheme[] = "theme"; static constexpr char iniKeywordShowHidden[] = "show_hidden"; static constexpr char iniKeywordLibrary[] = "library"; - static constexpr char iniKeywordRenderer[] = "renderer"; static constexpr char iniKeywordScrollSpeed[] = "scroll_speed"; static constexpr char iniKeywordDeadzone[] = "deadzone"; @@ -126,11 +127,13 @@ class FileSys { fs::path dirBase; // application base directory fs::path dirSets; // settings directory fs::path dirConfs; // internal config directory + std::ofstream logFile; public: FileSys(); + ~FileSys(); vector getAvailableThemes() const; - array loadColors(string_view theme) const; // updates settings' colors according to settings' theme + array loadColors(string_view theme) const; // updates settings' colors according to settings' theme bool getLastPage(string_view book, string& drc, string& fname) const; bool saveLastPage(string_view book, string_view drc, string_view fname) const; Settings* loadSettings() const; @@ -162,12 +165,12 @@ class FileSys { static vector readFileLines(const fs::path& file, bool printMessage = true); static string readTextFile(const fs::path& file, bool printMessage = true); static bool writeTextFile(const fs::path& file, const vector& lines); - static SDL_Color readColor(string_view str); static fs::path searchFontDirs(string_view font, initlist dirs); #ifdef _WIN32 static vector listDrives(); #endif + static void SDLCALL logWrite(void* userdata, int category, SDL_LogPriority priority, const char* message); }; inline const fs::path& FileSys::getDirSets() const { diff --git a/src/engine/inputSys.cpp b/src/engine/inputSys.cpp index be6cafa8..c8678137 100644 --- a/src/engine/inputSys.cpp +++ b/src/engine/inputSys.cpp @@ -37,14 +37,14 @@ void InputSys::eventMouseMotion(const SDL_MouseMotionEvent& motion) { mouseLast = motion.type == SDL_MOUSEMOTION; mouseMove = ivec2(motion.xrel, motion.yrel); moveTime = motion.timestamp; - World::scene()->onMouseMove(ivec2(motion.x, motion.y), mouseMove); + World::scene()->onMouseMove(ivec2(motion.x, motion.y) + World::winSys()->winViewOffset(motion.windowID), mouseMove); } void InputSys::eventMouseButtonDown(const SDL_MouseButtonEvent& button) { mouseLast = button.type == SDL_MOUSEBUTTONDOWN; switch (button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_MIDDLE: case SDL_BUTTON_RIGHT: - World::scene()->onMouseDown(ivec2(button.x, button.y), button.button, button.clicks); + World::scene()->onMouseDown(ivec2(button.x, button.y) + World::winSys()->winViewOffset(button.windowID), button.button, button.clicks); break; case SDL_BUTTON_X1: World::srun(bindings[uint8(Binding::Type::escape)].bcall); @@ -56,12 +56,16 @@ void InputSys::eventMouseButtonDown(const SDL_MouseButtonEvent& button) { void InputSys::eventMouseButtonUp(const SDL_MouseButtonEvent& button) { if (mouseLast = button.type == SDL_MOUSEBUTTONUP; button.button < SDL_BUTTON_X1) - World::scene()->onMouseUp(ivec2(button.x, button.y), button.button, button.clicks); + World::scene()->onMouseUp(ivec2(button.x, button.y) + World::winSys()->winViewOffset(button.windowID), button.button, button.clicks); } void InputSys::eventMouseWheel(const SDL_MouseWheelEvent& wheel) { mouseLast = true; - World::scene()->onMouseWheel(ivec2(wheel.x, -wheel.y)); +#if SDL_VERSION_ATLEAST(2, 26, 0) + World::scene()->onMouseWheel(ivec2(wheel.mouseX, wheel.mouseY), ivec2(wheel.x, -wheel.y)); +#else + World::scene()->onMouseWheel(World::winSys()->mousePos(), ivec2(wheel.x, -wheel.y)); +#endif } void InputSys::eventKeypress(const SDL_KeyboardEvent& key) const { @@ -121,7 +125,7 @@ void InputSys::eventGamepadAxis(const SDL_ControllerAxisEvent& gaxis) const { } void InputSys::eventFingerMove(const SDL_TouchFingerEvent& fin) { - vec2 size = World::drawSys()->viewport().size(); + vec2 size = World::drawSys()->getViewRes(); SDL_MouseMotionEvent event{}; event.type = fin.type; event.timestamp = fin.timestamp; @@ -136,11 +140,11 @@ void InputSys::eventFingerMove(const SDL_TouchFingerEvent& fin) { } void InputSys::eventFingerDown(const SDL_TouchFingerEvent& fin) { - eventMouseButtonDown(toMouseEvent(fin, SDL_PRESSED, World::drawSys()->viewport().size())); + eventMouseButtonDown(toMouseEvent(fin, SDL_PRESSED, World::drawSys()->getViewRes())); } void InputSys::eventFingerUp(const SDL_TouchFingerEvent& fin) { - eventMouseButtonUp(toMouseEvent(fin, SDL_RELEASED, World::drawSys()->viewport().size())); + eventMouseButtonUp(toMouseEvent(fin, SDL_RELEASED, World::drawSys()->getViewRes())); World::scene()->updateSelect(ivec2(-1)); } diff --git a/src/engine/renderer.cpp b/src/engine/renderer.cpp new file mode 100644 index 00000000..5d2ede22 --- /dev/null +++ b/src/engine/renderer.cpp @@ -0,0 +1,1103 @@ +#include "renderer.h" +#include +#include +#ifdef WITH_DIRECTX +#include +#include +#include +#endif + +Renderer::View::View(SDL_Window* window, const Rect& area) : + win(window), + rect(area) +{} + +// RENDERER DX + +#ifdef WITH_DIRECTX +RendererDx::TextureDx::TextureDx(ivec2 size, ID3D11Texture2D* texture, ID3D11ShaderResourceView* textureView) : + Texture(size), + tex(texture), + view(textureView) +{} + +void RendererDx::TextureDx::free() { + view->Release(); + tex->Release(); +} + +RendererDx::ViewDx::ViewDx(SDL_Window* window, const Rect& area, IDXGISwapChain* swapchain, ID3D11RenderTargetView* backbuffer) : + View(window, area), + sc(swapchain), + tgt(backbuffer) +{} + +RendererDx::RendererDx(const umap& windows, Settings* sets, ivec2& viewRes, const vec4& bgcolor) : + syncInterval(toSwapEffect(sets->vsync)), + bgColor(bgcolor) +{ +#ifdef NDEBUG + uint flags = 0; +#else + uint flags = D3D11_CREATE_DEVICE_DEBUG; +#endif + if (HRESULT rs = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags, nullptr, 0, D3D11_SDK_VERSION, &dev, nullptr, &ctx); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + + IDXGIFactory* factory = createFactory(); + if (windows.size() == 1 && windows.begin()->first == singleDspId) { + SDL_GetWindowSize(windows.begin()->second, &viewRes.x, &viewRes.y); + auto [swapchain, backbuffer] = createSwapchain(factory, windows.begin()->second, viewRes); + views.emplace(singleDspId, new ViewDx(windows.begin()->second, Rect(ivec2(0), viewRes), swapchain, backbuffer)); + } else { + views.reserve(windows.size()); + for (auto [id, win] : windows) { + const Rect& area = sets->displays[id]; + auto [swapchain, backbuffer] = createSwapchain(factory, win, area.size()); + views.emplace(id, new ViewDx(win, area, swapchain, backbuffer)); + viewRes = glm::max(viewRes, area.end()); + } + } + factory->Release(); + + D3D11_BLEND_DESC blendDesc{}; + blendDesc.RenderTarget[0].BlendEnable = TRUE; + blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + ID3D11BlendState* blendState; + if (HRESULT rs = dev->CreateBlendState(&blendDesc, &blendState); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + ctx->OMSetBlendState(blendState, nullptr, 0xFFFFFFFF); + blendState->Release(); + initShader(); +} + +RendererDx::~RendererDx() { + for (auto [id, view] : views) + static_cast(view)->sc->SetFullscreenState(FALSE, nullptr); // TODO: only when gone fullscreen + + if (outAddr) + outAddr->Release(); + if (tgtAddr) + tgtAddr->Release(); + if (texAddr) + texAddr->Release(); + if (rasterizerSel) + rasterizerSel->Release(); + if (rasterizerGui) + rasterizerGui->Release(); + if (sampleState) + sampleState->Release(); + if (addrDatBuf) + addrDatBuf->Release(); + if (psDatBuf) + psDatBuf->Release(); + if (vsDatBuf) + vsDatBuf->Release(); + if (pixlSel) + pixlSel->Release(); + if (vertSel) + vertSel->Release(); + if (pixlGui) + pixlGui->Release(); + if (vertGui) + vertGui->Release(); + for (auto [id, view] : views) { + static_cast(view)->tgt->Release(); + static_cast(view)->sc->Release(); + delete view; + } + if (ctx) + ctx->Release(); + if (dev) + dev->Release(); +} + +IDXGIFactory* RendererDx::createFactory() { + IDXGIFactory* factory; + if (HRESULT rs = CreateDXGIFactory(__uuidof(IDXGIFactory), reinterpret_cast(&factory)); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + return factory; +} + +pair RendererDx::createSwapchain(IDXGIFactory* factory, SDL_Window* win, ivec2 res) { + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + if (!SDL_GetWindowWMInfo(win, &wmInfo)) + throw std::runtime_error(SDL_GetError()); + + DXGI_SWAP_CHAIN_DESC schainDesc{}; + schainDesc.BufferCount = 1; + schainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + schainDesc.BufferDesc.Width = res.x; + schainDesc.BufferDesc.Height = res.y; + schainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + schainDesc.OutputWindow = wmInfo.info.win.window; + schainDesc.SampleDesc.Count = 1; + schainDesc.Windowed = !(SDL_GetWindowFlags(win) & SDL_WINDOW_FULLSCREEN); + schainDesc.SwapEffect = syncInterval; + + IDXGISwapChain* swapchain; + if (HRESULT rs = factory->CreateSwapChain(dev, &schainDesc, &swapchain); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + + ID3D11Texture2D* scBackBuffer; + swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(&scBackBuffer)); + ID3D11RenderTargetView* backbuffer; + if (HRESULT rs = dev->CreateRenderTargetView(scBackBuffer, nullptr, &backbuffer); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + scBackBuffer->Release(); + return pair(swapchain, backbuffer); +} + +void RendererDx::recreateSwapchain(IDXGIFactory* factory, ViewDx* view) { + view->tgt->Release(); + view->sc->Release(); + std::tie(view->sc, view->tgt) = createSwapchain(factory, view->win, view->rect.size()); +} + +void RendererDx::initShader() { + const char* vertSrc = R"r( +struct VertOut { + float4 pos : SV_POSITION; + float2 tuv : TEXCOORD0; +}; + +cbuffer VsData : register(b0) { + int4 rect; + int4 frame; + float2 pview; +}; + +static const float2 vposs[] = { + float2(0.f, 0.f), + float2(1.f, 0.f), + float2(0.f, 1.f), + float2(1.f, 1.f) +}; + +VertOut main(uint vid : SV_VertexId) { + float4 dst = float4(0.f, 0.f, 0.f, 0.f); + if (rect[2] > 0 && rect[3] > 0 && frame[2] > 0 && frame[3] > 0) { + dst.xy = max(rect.xy, frame.xy); + dst.zw = min(rect.xy + rect.zw, frame.xy + frame.zw) - dst.xy; + } + + VertOut vout; + if (dst[2] > 0.f && dst[3] > 0.f) { + float4 uvrc = float4(dst.xy - rect.xy, dst.zw) / float4(rect.zwzw); + vout.tuv = vposs[vid] * uvrc.zw + uvrc.xy; + float2 loc = vposs[vid] * dst.zw + dst.xy; + vout.pos = float4((loc.x - pview.x) / pview.x, -(loc.y - pview.y) / pview.y, 0.f, 1.f); + } else { + vout.tuv = float2(0.f, 0.f); + vout.pos = float4(-2.f, -2.f, 0.f, 1.f); + } + return vout; +})r"; + const char* pixlSrc = R"r( +Texture2D textureView : register(t0); +SamplerState sampleState : register(s0); + +cbuffer PsData : register(b0) { + float4 color; +}; + +float4 main(float4 pos : SV_POSITION, float2 tuv : TEXCOORD0) : SV_TARGET { + return textureView.Sample(sampleState, tuv) * color; +})r"; + std::tie(vertGui, pixlGui) = createShader(vertSrc, pixlSrc, "gui"); + + vertSrc = R"r( +cbuffer VsData : register(b0) { + int4 rect; + int4 frame; + float2 pview; +}; + +static const float2 vposs[] = { + float2(0.f, 0.f), + float2(1.f, 0.f), + float2(0.f, 1.f), + float2(1.f, 1.f) +}; + +float4 main(uint vid : SV_VertexId) : SV_POSITION { + float4 dst = float4(0.f, 0.f, 0.f, 0.f); + if (rect[2] > 0 && rect[3] > 0 && frame[2] > 0 && frame[3] > 0) { + dst.xy = max(rect.xy, frame.xy); + dst.zw = min(rect.xy + rect.zw, frame.xy + frame.zw) - dst.xy; + } + + if (dst[2] > 0.f && dst[3] > 0.f) { + float2 loc = vposs[vid] * dst.zw + dst.xy; + return float4((loc.x - pview.x) / pview.x, -(loc.y - pview.y) / pview.y, 0.f, 1.f); + } + return float4(-2.f, -2.f, 0.f, 1.f); +})r"; + pixlSrc = R"r( +cbuffer AddrData : register(b1) { + uint2 addr; +}; + +uint2 main(float4 pos : SV_POSITION) : SV_TARGET { + if (addr.x != 0 || addr.y != 0) + return addr; + discard; + return uint2(0, 0); +})r"; + std::tie(vertSel, pixlSel) = createShader(vertSrc, pixlSrc, "sel"); + + D3D11_BUFFER_DESC bufferDesc{}; + bufferDesc.Usage = D3D11_USAGE_DYNAMIC; + bufferDesc.ByteWidth = sizeof(VsData); + bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + if (HRESULT rs = dev->CreateBuffer(&bufferDesc, nullptr, &vsDatBuf); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + ctx->VSSetConstantBuffers(0, 1, &vsDatBuf); + + bufferDesc.ByteWidth = sizeof(PsData); + if (HRESULT rs = dev->CreateBuffer(&bufferDesc, nullptr, &psDatBuf); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + ctx->PSSetConstantBuffers(0, 1, &psDatBuf); + + bufferDesc.ByteWidth = sizeof(AddrData); + if (HRESULT rs = dev->CreateBuffer(&bufferDesc, nullptr, &addrDatBuf); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + ctx->PSSetConstantBuffers(1, 1, &addrDatBuf); + + D3D11_SAMPLER_DESC samplerDesc{}; + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.MaxAnisotropy = 1; + samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; + samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; + if (HRESULT rs = dev->CreateSamplerState(&samplerDesc, &sampleState); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + ctx->PSSetSamplers(0, 1, &sampleState); + + D3D11_RASTERIZER_DESC rasterizerDesc{}; + rasterizerDesc.CullMode = D3D11_CULL_BACK; + rasterizerDesc.DepthClipEnable = TRUE; + rasterizerDesc.FillMode = D3D11_FILL_SOLID; + rasterizerDesc.FrontCounterClockwise = FALSE; + rasterizerDesc.ScissorEnable = FALSE; + if (HRESULT rs = dev->CreateRasterizerState(&rasterizerDesc, &rasterizerGui); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + + rasterizerDesc.ScissorEnable = TRUE; + if (HRESULT rs = dev->CreateRasterizerState(&rasterizerDesc, &rasterizerSel); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + D3D11_RECT scissor = { 0, 0, 1, 1 }; + ctx->RSSetScissorRects(1, &scissor); + ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + D3D11_TEXTURE2D_DESC texDesc{}; + texDesc.Width = 1; + texDesc.Height = 1; + texDesc.MipLevels = 1; + texDesc.ArraySize = 1; + texDesc.Format = DXGI_FORMAT_R32G32_UINT; + texDesc.SampleDesc.Count = 1; + texDesc.Usage = D3D11_USAGE_DEFAULT; + texDesc.BindFlags = D3D11_BIND_RENDER_TARGET; + if (HRESULT rs = dev->CreateTexture2D(&texDesc, nullptr, &texAddr); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + + D3D11_RENDER_TARGET_VIEW_DESC tgtDesc{}; + tgtDesc.Format = texDesc.Format; + tgtDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + if (HRESULT rs = dev->CreateRenderTargetView(texAddr, &tgtDesc, &tgtAddr); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + + texDesc.Usage = D3D11_USAGE_STAGING; + texDesc.BindFlags = 0; + texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + if (HRESULT rs = dev->CreateTexture2D(&texDesc, nullptr, &outAddr); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); +} + +pair RendererDx::createShader(const char* vertSrc, const char* pixlSrc, const char* name) const { +#ifdef NDEBUG + uint flags = D3DCOMPILE_SKIP_VALIDATION | D3DCOMPILE_OPTIMIZATION_LEVEL3; +#else + uint flags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#endif + ID3DBlob* error; + ID3DBlob* blob; + if (HRESULT rs = D3DCompile(vertSrc, strlen(vertSrc), (name + ".vs"s).c_str(), nullptr, nullptr, "main", "vs_5_0", flags, 0, &blob, &error); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs) + ": " + static_cast(error->GetBufferPointer())); + ID3D11VertexShader* vertShader; + if (HRESULT rs = dev->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, &vertShader); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + blob->Release(); + + if (HRESULT rs = D3DCompile(pixlSrc, strlen(pixlSrc), (name + ".ps"s).c_str(), nullptr, nullptr, "main", "ps_5_0", flags, 0, &blob, &error); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs) + ": " + static_cast(error->GetBufferPointer())); + ID3D11PixelShader* pixlShader; + if (HRESULT rs = dev->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, &pixlShader); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + blob->Release(); + return pair(vertShader, pixlShader); +} + +void RendererDx::setClearColor(const vec4& color) { + bgColor = color; +} + +void RendererDx::setSwapInterval(Settings::VSync& vsync) { + syncInterval = toSwapEffect(vsync); + IDXGIFactory* factory = createFactory(); + for (auto [id, view] : views) + recreateSwapchain(factory, static_cast(view)); + factory->Release(); +} + +void RendererDx::updateView(ivec2& viewRes) { + if (views.size() == 1) { + IDXGIFactory* factory = createFactory(); + SDL_GetWindowSize(views.begin()->second->win, &viewRes.x, &viewRes.y); + views.begin()->second->rect.size() = viewRes; + recreateSwapchain(factory, static_cast(views.begin()->second)); + factory->Release(); + } +} + +void RendererDx::startDraw(const View* view) { + D3D11_VIEWPORT viewport{}; + viewport.Width = float(view->rect.w); + viewport.Height = float(view->rect.h); + + ctx->VSSetShader(vertGui, nullptr, 0); + ctx->PSSetShader(pixlGui, nullptr, 0); + ctx->RSSetState(rasterizerGui); + ctx->RSSetViewports(1, &viewport); + ctx->OMSetRenderTargets(1, &static_cast(view)->tgt, nullptr); + ctx->ClearRenderTargetView(static_cast(view)->tgt, glm::value_ptr(bgColor)); + vsData.pview = vec2(view->rect.size()) / 2.f; +} + +void RendererDx::drawRect(const Texture* tex, const Rect& rect, const Rect& frame, const vec4& color) { + ctx->PSSetShaderResources(0, 1, &static_cast(tex)->view); + vsData.rect = rect.toVec(); + vsData.frame = frame.toVec(); + uploadBuffer(vsDatBuf, vsData); + uploadBuffer(psDatBuf, PsData{ color }); + ctx->Draw(4, 0); +} + +void RendererDx::finishDraw(const View* view) { + static_cast(view)->sc->Present(syncInterval, 0); +} + +void RendererDx::startSelDraw(const View* view, ivec2 pos) { + float zero[4] = { 0.f, 0.f, 0.f, 0.f }; + D3D11_VIEWPORT viewport{}; + viewport.TopLeftX = float(-pos.x); + viewport.TopLeftY = float(-pos.y); + viewport.Width = float(view->rect.w); + viewport.Height = float(view->rect.h); + + ctx->VSSetShader(vertGui, nullptr, 0); + ctx->PSSetShader(pixlGui, nullptr, 0); + ctx->RSSetState(rasterizerSel); + ctx->RSSetViewports(1, &viewport); + ctx->OMSetRenderTargets(1, &tgtAddr, nullptr); + ctx->ClearRenderTargetView(tgtAddr, zero); + vsData.pview = vec2(view->rect.size()) / 2.f; +} + +void RendererDx::drawSelRect(const Widget* wgt, const Rect& rect, const Rect& frame) { + vsData.rect = rect.toVec(); + vsData.frame = frame.toVec(); + uploadBuffer(vsDatBuf, vsData); + uploadBuffer(addrDatBuf, AddrData{ uvec2(uptrt(wgt), uptrt(wgt) >> 32) }); + ctx->Draw(4, 0); +} + +Widget* RendererDx::finishSelDraw(const View* view) { + ctx->CopyResource(outAddr, texAddr); + D3D11_MAPPED_SUBRESOURCE mapRsc; + if (HRESULT rs = ctx->Map(outAddr, 0, D3D11_MAP_READ, 0, &mapRsc); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + uvec2 val; + memcpy(&val, mapRsc.pData, sizeof(uvec2)); + ctx->Unmap(outAddr, 0); + return reinterpret_cast(uptrt(val.x) | (uptrt(val.y) << 32)); +} + +template +void RendererDx::uploadBuffer(ID3D11Buffer* buffer, const T& data) { // TODO: mayve use UpdateSubresource instead + D3D11_MAPPED_SUBRESOURCE mapRsc; + if (HRESULT rs = ctx->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapRsc); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + memcpy(mapRsc.pData, &data, sizeof(T)); + ctx->Unmap(buffer, 0); +} + +Texture* RendererDx::texFromColor(u8vec4 color) { + return createTexture(array{ color, color, color, color }.data(), ivec2(2), sizeof(uint8) * 4, DXGI_FORMAT_B8G8R8A8_UNORM); +} + +Texture* RendererDx::texFromText(SDL_Surface* img) { + if (img) { + if (SDL_Surface* dst = SDL_CreateRGBSurfaceWithFormat(0, img->w, img->h, img->format->BitsPerPixel, img->format->format)) { // TODO: this is necessary cause something's fucked + SDL_BlitSurface(img, nullptr, dst, nullptr); + SDL_FreeSurface(img); + img = dst; + } + TextureDx* tex = createTexture(img->pixels, ivec2(img->w, img->h), img->pitch, DXGI_FORMAT_B8G8R8A8_UNORM); + SDL_FreeSurface(img); + return tex; + } + return nullptr; +} + +Texture* RendererDx::texFromIcon(SDL_Surface* pic) { + if (auto [img, fmt] = pickPixFormat(pic); pic) { + TextureDx* tex = createTexture(img->pixels, ivec2(img->w, img->h), img->pitch, fmt); + SDL_FreeSurface(img); + return tex; + } + return nullptr; +} + +RendererDx::TextureDx* RendererDx::createTexture(const void* pixels, ivec2 res, uint rowLen, DXGI_FORMAT format) { + try { + D3D11_TEXTURE2D_DESC texDesc{}; + texDesc.Width = res.x; + texDesc.Height = res.y; + texDesc.MipLevels = 1; + texDesc.ArraySize = 1; + texDesc.Format = format; + texDesc.SampleDesc.Count = 1; + texDesc.Usage = D3D11_USAGE_DEFAULT; + texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + D3D11_SUBRESOURCE_DATA subrscData{}; + subrscData.pSysMem = pixels; + subrscData.SysMemPitch = rowLen; + + ID3D11Texture2D* texture; + if (HRESULT rs = dev->CreateTexture2D(&texDesc, &subrscData, &texture); FAILED(rs)) + throw std::runtime_error(hresultToStr(rs)); + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = texDesc.Format; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = texDesc.MipLevels; + + ID3D11ShaderResourceView* textureView; + if (HRESULT rs = dev->CreateShaderResourceView(texture, &srvDesc, &textureView); FAILED(rs)) { + texture->Release(); + throw std::runtime_error(hresultToStr(rs)); + } + return new TextureDx(res, texture, textureView); + } catch (const std::runtime_error& err) { + logError(err.what()); + } + return nullptr; +} + +pair RendererDx::pickPixFormat(SDL_Surface* img) { + if (img) { + switch (img->format->format) { + case SDL_PIXELFORMAT_BGRA32: case SDL_PIXELFORMAT_XRGB8888: + return pair(img, DXGI_FORMAT_B8G8R8A8_UNORM); + case SDL_PIXELFORMAT_RGBA32: case SDL_PIXELFORMAT_XBGR8888: + return pair(img, DXGI_FORMAT_R8G8B8A8_UNORM); + } + SDL_Surface* dst = SDL_ConvertSurfaceFormat(img, SDL_PIXELFORMAT_BGRA32, 0); + SDL_FreeSurface(img); + img = dst; + } + return pair(img, DXGI_FORMAT_B8G8R8A8_UNORM); +} + +DXGI_SWAP_EFFECT RendererDx::toSwapEffect(Settings::VSync vsync) { + switch (vsync) { + case Settings::VSync::adaptive: + return DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + case Settings::VSync::immediate: + return DXGI_SWAP_EFFECT_DISCARD; + } + return DXGI_SWAP_EFFECT_SEQUENTIAL; +} + +string RendererDx::hresultToStr(HRESULT rs) { + _com_error err(rs); +#if TCHAR == WCHAR + return swtos(err.ErrorMessage()); +#else + return err.ErrorMessage(); +#endif +} +#endif + +// RENDERER GL + +RendererGl::TextureGl::TextureGl(ivec2 size, GLuint tex) : + Texture(size), + id(tex) +{} + +void RendererGl::TextureGl::free() { + glDeleteTextures(1, &id); +} + +RendererGl::ViewGl::ViewGl(SDL_Window* window, const Rect& area, SDL_GLContext context) : + View(window, area), + ctx(context) +{} + +RendererGl::RendererGl(const umap& windows, Settings* sets, ivec2& viewRes, const vec4& bgcolor) { + if (windows.size() == 1 && windows.begin()->first == singleDspId) { + SDL_GetWindowSize(windows.begin()->second, &viewRes.x, &viewRes.y); + if (!static_cast(views.emplace(singleDspId, new ViewGl(windows.begin()->second, Rect(ivec2(0), viewRes), SDL_GL_CreateContext(windows.begin()->second))).first->second)->ctx) + throw std::runtime_error("Failed to create context:"s + linend + SDL_GetError()); + initGl(viewRes, sets->vsync, bgcolor); + } else { + views.reserve(windows.size()); + for (auto [id, win] : windows) { + ViewGl* view = static_cast(views.emplace(id, new ViewGl(win, sets->displays[id], SDL_GL_CreateContext(win))).first->second); + if (!view->ctx) + throw std::runtime_error("Failed to create context:"s + linend + SDL_GetError()); + viewRes = glm::max(viewRes, view->rect.end()); + initGl(view->rect.size(), sets->vsync, bgcolor); + } + } +#ifndef OPENGLES + initFunctions(); +#endif + initShader(); +} + +RendererGl::~RendererGl() { + glDeleteVertexArrays(1, &vao); + glDeleteTextures(1, &texSel); + glDeleteFramebuffers(1, &fboSel); + glDeleteProgram(progSel); + glDeleteProgram(progGui); + + for (auto [id, view] : views) { + SDL_GL_DeleteContext(static_cast(view)->ctx); + delete view; + } +} + +void RendererGl::setClearColor(const vec4& color) { + for (auto [id, view] : views) { + SDL_GL_MakeCurrent(view->win, static_cast(view)->ctx); + glClearColor(color.r, color.g, color.b, color.a); + } +} + +void RendererGl::initGl(ivec2 res, Settings::VSync& vsync, const vec4& bgcolor) { + switch (vsync) { + case Settings::VSync::adaptive: + if (trySetSwapIntervalSingle(vsync)) + break; + vsync = Settings::VSync::synchronized; + case Settings::VSync::synchronized: + if (trySetSwapIntervalSingle(vsync)) + break; + vsync = Settings::VSync::immediate; + case Settings::VSync::immediate: + trySetSwapIntervalSingle(vsync); + } + + glViewport(0, 0, res.x, res.y); + glClearColor(bgcolor.r, bgcolor.g, bgcolor.b, bgcolor.a); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + glFrontFace(GL_CCW); + glScissor(0, 0, 1, 1); // for address pass +} + +void RendererGl::setSwapInterval(Settings::VSync& vsync) { + switch (vsync) { + case Settings::VSync::adaptive: + if (trySetSwapInterval(vsync)) + break; + vsync = Settings::VSync::synchronized; + case Settings::VSync::synchronized: + if (trySetSwapInterval(vsync)) + break; + vsync = Settings::VSync::immediate; + case Settings::VSync::immediate: + trySetSwapInterval(vsync); + } +} + +bool RendererGl::trySetSwapInterval(Settings::VSync vsync) { + for (auto [id, view] : views) + if (SDL_GL_MakeCurrent(view->win, static_cast(view)->ctx); !trySetSwapIntervalSingle(vsync)) + return false; + return true; +} + +bool RendererGl::trySetSwapIntervalSingle(Settings::VSync vsync) { + if (SDL_GL_SetSwapInterval(int8(vsync))) { + logError("swap interval ", int(vsync), " not supported"); + return false; + } + return true; +} + +void RendererGl::updateView(ivec2& viewRes) { + if (views.size() == 1) { + SDL_GL_GetDrawableSize(views.begin()->second->win, &viewRes.x, &viewRes.y); + views.begin()->second->rect.size() = viewRes; + glViewport(0, 0, viewRes.x, viewRes.y); + } +} + +#ifndef OPENGLES +void RendererGl::initFunctions() { + glActiveTexture = reinterpret_cast(SDL_GL_GetProcAddress("glActiveTexture")); + glAttachShader = reinterpret_cast(SDL_GL_GetProcAddress("glAttachShader")); + glBindFramebuffer = reinterpret_cast(SDL_GL_GetProcAddress("glBindFramebuffer")); + glBindVertexArray = reinterpret_cast(SDL_GL_GetProcAddress("glBindVertexArray")); + glCheckFramebufferStatus = reinterpret_cast(SDL_GL_GetProcAddress("glCheckFramebufferStatus")); + glClearBufferuiv = reinterpret_cast(SDL_GL_GetProcAddress("glClearBufferuiv")); + glCompileShader = reinterpret_cast(SDL_GL_GetProcAddress("glCompileShader")); + glCreateProgram = reinterpret_cast(SDL_GL_GetProcAddress("glCreateProgram")); + glCreateShader = reinterpret_cast(SDL_GL_GetProcAddress("glCreateShader")); + glDeleteFramebuffers = reinterpret_cast(SDL_GL_GetProcAddress("glDeleteFramebuffers")); + glDeleteShader = reinterpret_cast(SDL_GL_GetProcAddress("glDeleteShader")); + glDeleteProgram = reinterpret_cast(SDL_GL_GetProcAddress("glDeleteProgram")); + glDeleteVertexArrays = reinterpret_cast(SDL_GL_GetProcAddress("glDeleteVertexArrays")); + glDetachShader = reinterpret_cast(SDL_GL_GetProcAddress("glDetachShader")); + glFramebufferTexture2D = reinterpret_cast(SDL_GL_GetProcAddress("glFramebufferTexture2D")); + glGenerateMipmap = reinterpret_cast(SDL_GL_GetProcAddress("glGenerateMipmap")); + glGenFramebuffers = reinterpret_cast(SDL_GL_GetProcAddress("glGenFramebuffers")); + glGenVertexArrays = reinterpret_cast(SDL_GL_GetProcAddress("glGenVertexArrays")); + glGetProgramInfoLog = reinterpret_cast(SDL_GL_GetProcAddress("glGetProgramInfoLog")); + glGetProgramiv = reinterpret_cast(SDL_GL_GetProcAddress("glGetProgramiv")); + glGetShaderInfoLog = reinterpret_cast(SDL_GL_GetProcAddress("glGetShaderInfoLog")); + glGetShaderiv = reinterpret_cast(SDL_GL_GetProcAddress("glGetShaderiv")); + glGetUniformLocation = reinterpret_cast(SDL_GL_GetProcAddress("glGetUniformLocation")); + glLinkProgram = reinterpret_cast(SDL_GL_GetProcAddress("glLinkProgram")); + glShaderSource = reinterpret_cast(SDL_GL_GetProcAddress("glShaderSource")); + glUniform1i = reinterpret_cast(SDL_GL_GetProcAddress("glUniform1i")); + glUniform2f = reinterpret_cast(SDL_GL_GetProcAddress("glUniform2f")); + glUniform2fv = reinterpret_cast(SDL_GL_GetProcAddress("glUniform2fv")); + glUniform2ui = reinterpret_cast(SDL_GL_GetProcAddress("glUniform2ui")); + glUniform2uiv = reinterpret_cast(SDL_GL_GetProcAddress("glUniform2uiv")); + glUniform4fv = reinterpret_cast(SDL_GL_GetProcAddress("glUniform4fv")); + glUniform4iv = reinterpret_cast(SDL_GL_GetProcAddress("glUniform4iv")); + glUseProgram = reinterpret_cast(SDL_GL_GetProcAddress("glUseProgram")); +#ifndef NDEBUG + void (APIENTRY* glDebugMessageCallback)(void (APIENTRY* callback)(GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar*, const void*), const void* userParam) = reinterpret_cast(SDL_GL_GetProcAddress("glDebugMessageCallback")); + void (APIENTRY* glDebugMessageControl)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint * ids, GLboolean enabled) = reinterpret_cast(SDL_GL_GetProcAddress("glDebugMessageControl")); + int gval; + if (glGetIntegerv(GL_CONTEXT_FLAGS, &gval); (gval & GL_CONTEXT_FLAG_DEBUG_BIT) && glDebugMessageCallback && glDebugMessageControl && SDL_GL_ExtensionSupported("GL_KHR_debug")) { + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(debugMessage, nullptr); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + } +#endif +} +#endif + +void RendererGl::initShader() { + const char* vertSrc = R"r(#version 130 + +const vec2 vposs[4] = vec2[]( + vec2(0.0, 0.0), + vec2(1.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 1.0) +); + +uniform vec2 pview; +uniform ivec4 rect; +uniform ivec4 frame; + +noperspective out vec2 fragUV; + +void main() { + vec4 dst = vec4(0.0); + if (rect[2] > 0 && rect[3] > 0 && frame[2] > 0 && frame[3] > 0) { + dst.xy = vec2(max(rect.xy, frame.xy)); + dst.zw = vec2(min(rect.xy + rect.zw, frame.xy + frame.zw)) - dst.xy; + } + + if (dst[2] > 0.0 && dst[3] > 0.0) { + vec4 uvrc = vec4(dst.xy - vec2(rect.xy), dst.zw) / vec4(rect.zwzw); + fragUV = vposs[gl_VertexID] * uvrc.zw + uvrc.xy; + vec2 loc = vposs[gl_VertexID] * dst.zw + dst.xy; + gl_Position = vec4((loc.x - pview.x) / pview.x, -(loc.y - pview.y) / pview.y, 0.0, 1.0); + } else { + fragUV = vec2(0.0); + gl_Position = vec4(-2.0, -2.0, 0.0, 1.0); + } +})r"; + const char* fragSrc = R"r(#version 130 + +uniform sampler2D colorMap; +uniform vec4 color; + +noperspective in vec2 fragUV; + +out vec4 outColor; + +void main() { + outColor = texture(colorMap, fragUV) * color; +})r"; + progGui = createShader(vertSrc, fragSrc, "gui"); + uniPviewGui = glGetUniformLocation(progGui, "pview"); + uniRectGui = glGetUniformLocation(progGui, "rect"); + uniFrameGui = glGetUniformLocation(progGui, "frame"); + uniColorGui = glGetUniformLocation(progGui, "color"); + glUniform1i(glGetUniformLocation(progGui, "colorMap"), 0); + + vertSrc = R"r(#version 130 + +const vec2 vposs[4] = vec2[]( + vec2(0.0, 0.0), + vec2(1.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 1.0) +); + +uniform vec2 pview; +uniform ivec4 rect; +uniform ivec4 frame; + +void main() { + vec4 dst = vec4(0.0); + if (rect[2] > 0 && rect[3] > 0 && frame[2] > 0 && frame[3] > 0) { + dst.xy = vec2(max(rect.xy, frame.xy)); + dst.zw = vec2(min(rect.xy + rect.zw, frame.xy + frame.zw)) - dst.xy; + } + + if (dst[2] > 0.0 && dst[3] > 0.0) { + vec2 loc = vposs[gl_VertexID] * dst.zw + dst.xy; + gl_Position = vec4((loc.x - pview.x) / pview.x, -(loc.y - pview.y) / pview.y, 0.0, 1.0); + } else + gl_Position = vec4(-2.0, -2.0, 0.0, 1.0); +})r"; + fragSrc = R"r(#version 130 + +uniform uvec2 addr; + +out uvec2 outAddr; + +void main() { + if (addr.x != 0u || addr.y != 0u) + outAddr = addr; + else + discard; +})r"; + progSel = createShader(vertSrc, fragSrc, "sel"); + uniPviewSel = glGetUniformLocation(progSel, "pview"); + uniRectSel = glGetUniformLocation(progSel, "rect"); + uniFrameSel = glGetUniformLocation(progSel, "frame"); + uniAddrSel = glGetUniformLocation(progSel, "addr"); + + glActiveTexture(GL_TEXTURE1); + glGenTextures(1, &texSel); + glBindTexture(GL_TEXTURE_2D, texSel); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32UI, 1, 1, 0, GL_RG_INTEGER, GL_UNSIGNED_INT, nullptr); + + glGenFramebuffers(1, &fboSel); + glBindFramebuffer(GL_FRAMEBUFFER, fboSel); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texSel, 0); + checkFramebufferStatus("sel"); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glActiveTexture(GL_TEXTURE0); + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); +} + +GLuint RendererGl::createShader(const char* vertSrc, const char* fragSrc, const char* name) const { +#ifdef OPENGLES + array, 2> replacers = { + pair(std::regex(R"r(#version\s+\d+)r"), "#version 300 es\nprecision highp float;precision highp int;precision highp sampler2D;"), + pair(std::regex(R"r(noperspective\s+)r"), "") + }; + string vertTmp = vertSrc, fragTmp = fragSrc; + for (const auto& [rgx, rpl] : replacers) { + vertTmp = std::regex_replace(vertTmp, rgx, rpl); + fragTmp = std::regex_replace(fragTmp, rgx, rpl); + } + vertSrc = vertTmp.c_str(); + fragSrc = fragTmp.c_str(); +#endif + GLuint vert = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vert, 1, &vertSrc, nullptr); + glCompileShader(vert); + checkStatus(vert, GL_COMPILE_STATUS, glGetShaderiv, glGetShaderInfoLog, name + ".vert"s); + + GLuint frag = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(frag, 1, &fragSrc, nullptr); + glCompileShader(frag); + checkStatus(frag, GL_COMPILE_STATUS, glGetShaderiv, glGetShaderInfoLog, name + ".frag"s); + + GLuint sprog = glCreateProgram(); + glAttachShader(sprog, vert); + glAttachShader(sprog, frag); + glLinkProgram(sprog); + glDetachShader(sprog, vert); + glDetachShader(sprog, frag); + glDeleteShader(vert); + glDeleteShader(frag); + checkStatus(sprog, GL_LINK_STATUS, glGetProgramiv, glGetProgramInfoLog, name + " program"s); + glUseProgram(sprog); + return sprog; +} + +template +void RendererGl::checkStatus(GLuint id, GLenum stat, C check, I info, const string& name) { + int res; + if (check(id, stat, &res); res == GL_FALSE) { + string err; + if (check(id, GL_INFO_LOG_LENGTH, &res); res) { + err.resize(res); + info(id, res, nullptr, err.data()); + err = trim(err); + } + err = !err.empty() ? name + ":" + linend + err : name + ": unknown error"; + logError(err); + throw std::runtime_error(err); + } +} + +void RendererGl::checkFramebufferStatus(const char* name) { + switch (GLenum rc = glCheckFramebufferStatus(GL_FRAMEBUFFER)) { + case GL_FRAMEBUFFER_UNDEFINED: + throw std::runtime_error(name + ": GL_FRAMEBUFFER_UNDEFINED"s); + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + throw std::runtime_error(name + ": GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"s); + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + throw std::runtime_error(name + ": GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"s); +#ifndef OPENGLES + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + throw std::runtime_error(name + ": GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"s); + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + throw std::runtime_error(name + ": GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"s); +#endif + case GL_FRAMEBUFFER_UNSUPPORTED: + throw std::runtime_error(name + ": GL_FRAMEBUFFER_UNSUPPORTED"s); + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + throw std::runtime_error(name + ": GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"s); +#ifndef OPENGLES + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: + throw std::runtime_error(name + ": GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS"s); +#endif + default: + if (rc != GL_FRAMEBUFFER_COMPLETE) + throw std::runtime_error(name + ": unknown framebuffer error"s); + } +} + +void RendererGl::startDraw(const View* view) { + SDL_GL_MakeCurrent(view->win, static_cast(view)->ctx); + glUseProgram(progGui); + glUniform2f(uniPviewGui, float(view->rect.w) / 2.f, float(view->rect.h) / 2.f); + glClear(GL_COLOR_BUFFER_BIT); +} + +void RendererGl::drawRect(const Texture* tex, const Rect& rect, const Rect& frame, const vec4& color) { + glBindTexture(GL_TEXTURE_2D, static_cast(tex)->id); + glUniform4iv(uniRectGui, 1, reinterpret_cast(&rect)); + glUniform4iv(uniFrameGui, 1, reinterpret_cast(&frame)); + glUniform4fv(uniColorGui, 1, glm::value_ptr(color)); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +void RendererGl::finishDraw(const View* view) { + SDL_GL_SwapWindow(static_cast(view)->win); +} + +void RendererGl::startSelDraw(const View* view, ivec2 pos) { + uint zero[4] = { 0, 0, 0, 0 }; + SDL_GL_MakeCurrent(view->win, static_cast(view)->ctx); + glEnable(GL_SCISSOR_TEST); + glViewport(-pos.x, -view->rect.h + pos.y + 1, view->rect.w, view->rect.h); + glUseProgram(progSel); + glUniform2f(uniPviewGui, float(view->rect.w) / 2.f, float(view->rect.h) / 2.f); + glBindFramebuffer(GL_FRAMEBUFFER, fboSel); + glClearBufferuiv(GL_COLOR, 0, zero); +} + +void RendererGl::drawSelRect(const Widget* wgt, const Rect& rect, const Rect& frame) { + glUniform4iv(uniRectSel, 1, reinterpret_cast(&rect)); + glUniform4iv(uniFrameSel, 1, reinterpret_cast(&frame)); + glUniform2ui(uniAddrSel, uptrt(wgt), uptrt(wgt) >> 32); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +Widget* RendererGl::finishSelDraw(const View* view) { + uvec2 val; + glReadPixels(0, 0, 1, 1, GL_RG_INTEGER, GL_UNSIGNED_INT, glm::value_ptr(val)); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, view->rect.w, view->rect.h); + glDisable(GL_SCISSOR_TEST); + return reinterpret_cast(uptrt(val.x) | (uptrt(val.y) << 32)); +} + +Texture* RendererGl::texFromColor(u8vec4 color) { + return new TextureGl(ivec2(2), createTexture(array{ color, color, color, color }.data(), 2, 2, 0, GL_RGBA)); +} + +Texture* RendererGl::texFromText(SDL_Surface* img) { + if (img) { + GLuint id = createTexture(img->pixels, img->w, img->h, img->pitch / img->format->BytesPerPixel, textPixFormat); + SDL_FreeSurface(img); + return new TextureGl(ivec2(img->w, img->h), id); + } + return nullptr; +} + +Texture* RendererGl::texFromIcon(SDL_Surface* pic) { + if (auto [img, fmt] = pickPixFormat(pic); pic) { + GLuint id = createTexture(img->pixels, img->w, img->h, img->pitch / img->format->BytesPerPixel, fmt); + glGenerateMipmap(GL_TEXTURE_2D); + SDL_FreeSurface(img); + return new TextureGl(ivec2(img->w, img->h), id); + } + return nullptr; +} + +template +GLuint RendererGl::createTexture(const void* pixels, int width, int height, int rowLen, GLenum format) { + GLuint id; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if constexpr (mip < 1000) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mip); + glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLen); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, format, GL_UNSIGNED_BYTE, pixels); + return id; +} + +pair RendererGl::pickPixFormat(SDL_Surface* img) { + if (img) { + switch (img->format->format) { +#ifndef OPENGLES + case SDL_PIXELFORMAT_BGRA32: case SDL_PIXELFORMAT_XRGB8888: + return pair(img, GL_BGRA); +#endif + case SDL_PIXELFORMAT_RGBA32: case SDL_PIXELFORMAT_XBGR8888: + return pair(img, GL_RGBA); +#ifndef OPENGLES + case SDL_PIXELFORMAT_BGR24: + return pair(img, GL_BGR); + case SDL_PIXELFORMAT_RGB24: + return pair(img, GL_RGB); +#endif + } + SDL_Surface* dst = SDL_ConvertSurfaceFormat(img, SDL_PIXELFORMAT_RGBA32, 0); + SDL_FreeSurface(img); + img = dst; + } + return pair(img, GL_RGBA); +} + +#ifndef OPENGLES +#ifndef NDEBUG +void APIENTRY RendererGl::debugMessage(GLenum source, GLenum type, uint id, GLenum severity, GLsizei, const char* message, const void*) { + if (id == 131169 || id == 131185 || id == 131218 || id == 131204) + return; + string msg = "Debug message " + toStr(id) + ": " + message; + if (msg.back() != '\n' && msg.back() != '\r') + msg += linend; + + switch (source) { + case GL_DEBUG_SOURCE_API: + msg += "Source: API, "; + break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + msg += "Source: window system, "; + break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + msg += "Source: shader compiler, "; + break; + case GL_DEBUG_SOURCE_THIRD_PARTY: + msg += "Source: third party, "; + break; + case GL_DEBUG_SOURCE_APPLICATION: + msg += "Source: application, "; + break; + case GL_DEBUG_SOURCE_OTHER: + msg += "Source: other, "; + break; + default: + msg += "Source: unknown, "; + } + + switch (type) { + case GL_DEBUG_TYPE_ERROR: + msg += "Type: error, "; + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + msg += "Type: deprecated behavior, "; + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + msg += "Type: undefined behavior, "; + break; + case GL_DEBUG_TYPE_PORTABILITY: + msg += "Type: portability, "; + break; + case GL_DEBUG_TYPE_PERFORMANCE: + msg += "Type: performance, "; + break; + case GL_DEBUG_TYPE_MARKER: + msg += "Type: marker, "; + break; + case GL_DEBUG_TYPE_PUSH_GROUP: + msg += "Type: push group, "; + break; + case GL_DEBUG_TYPE_POP_GROUP: + msg += "Type: pop group, "; + break; + case GL_DEBUG_TYPE_OTHER: + msg += "Type: other, "; + break; + default: + msg += "Type: unknown, "; + } + + switch (severity) { + case GL_DEBUG_SEVERITY_HIGH: + logError(std::move(msg), "Severity: high"); + break; + case GL_DEBUG_SEVERITY_MEDIUM: + logError(std::move(msg), "Severity: medium"); + break; + case GL_DEBUG_SEVERITY_LOW: + logError(std::move(msg), "Severity: low"); + break; + case GL_DEBUG_SEVERITY_NOTIFICATION: + logInfo(std::move(msg), "Severity: notification"); + break; + default: + logError(std::move(msg), "Severity: unknown"); + } +} +#endif +#endif diff --git a/src/engine/renderer.h b/src/engine/renderer.h new file mode 100644 index 00000000..7fb80958 --- /dev/null +++ b/src/engine/renderer.h @@ -0,0 +1,272 @@ +#pragma once + +#include "utils/settings.h" +#if WITH_DIRECTX +#include +#endif +#ifdef _WIN32 +#include // needs to be here because SDL_opengl.h breaks some declarations +#include +#else +#ifdef OPENGLES +#include +#endif +#include +#endif + +class Texture { +private: + ivec2 res = ivec2(0); + +protected: + Texture(ivec2 size); + +public: + virtual void free() = 0; + ivec2 getRes() const; +}; + +inline Texture::Texture(ivec2 size) : + res(size) +{} + +inline ivec2 Texture::getRes() const { + return res; +} + +class Renderer { +public: + static constexpr int singleDspId = -1; + + struct View { + SDL_Window* win; + Rect rect; + + View(SDL_Window* window, const Rect& area = Rect()); + }; + +protected: + umap views; + +public: + virtual ~Renderer() = default; + + virtual void setClearColor(const vec4& color) = 0; + virtual void setSwapInterval(Settings::VSync& vsync) = 0; + virtual void updateView(ivec2& viewRes) = 0; + virtual void startDraw(const View* view) = 0; + virtual void drawRect(const Texture* tex, const Rect& rect, const Rect& frame, const vec4& color) = 0; + virtual void finishDraw(const View* view) = 0; + virtual void startSelDraw(const View* view, ivec2 pos) = 0; + virtual void drawSelRect(const Widget* wgt, const Rect& rect, const Rect& frame) = 0; + virtual Widget* finishSelDraw(const View* view) = 0; + virtual Texture* texFromColor(u8vec4 color) = 0; + virtual Texture* texFromText(SDL_Surface* img) = 0; + virtual Texture* texFromIcon(SDL_Surface* pic) = 0; + const umap& getViews() const; +}; + +inline const umap& Renderer::getViews() const { + return views; +} + +#ifdef WITH_DIRECTX +class RendererDx : public Renderer { +private: + class TextureDx : public Texture { + private: + ID3D11Texture2D* tex = nullptr; + ID3D11ShaderResourceView* view = nullptr; + + TextureDx(ivec2 size, ID3D11Texture2D* texture, ID3D11ShaderResourceView* textureView); + public: + void free() final; + + friend class RendererDx; + }; + + struct ViewDx : View { + IDXGISwapChain* sc; + ID3D11RenderTargetView* tgt; + + ViewDx(SDL_Window* window, const Rect& area, IDXGISwapChain* swapchain, ID3D11RenderTargetView* backbuffer); + }; + + struct VsData { + alignas(16) ivec4 rect = ivec4(0); + alignas(16) ivec4 frame = ivec4(0); + alignas(16) vec2 pview = vec2(0.f); + }; + + struct PsData { + alignas(16) vec4 color = vec4(0.f); + }; + + struct AddrData { + alignas(16) uvec2 addr = uvec2(0); + }; + + ID3D11Device* dev = nullptr; + ID3D11DeviceContext* ctx = nullptr; + + ID3D11VertexShader* vertGui = nullptr; + ID3D11PixelShader* pixlGui = nullptr; + ID3D11VertexShader* vertSel = nullptr; + ID3D11PixelShader* pixlSel = nullptr; + + ID3D11Buffer* vsDatBuf = nullptr; + ID3D11Buffer* psDatBuf = nullptr; + ID3D11Buffer* addrDatBuf = nullptr; + ID3D11SamplerState* sampleState = nullptr; + + ID3D11RasterizerState* rasterizerGui = nullptr; + ID3D11RasterizerState* rasterizerSel = nullptr; + ID3D11Texture2D* texAddr = nullptr; + ID3D11RenderTargetView* tgtAddr = nullptr; + ID3D11Texture2D* outAddr = nullptr; + + DXGI_SWAP_EFFECT syncInterval; + vec4 bgColor; + VsData vsData; + +public: + RendererDx(const umap& windows, Settings* sets, ivec2& viewRes, const vec4& bgcolor); + ~RendererDx() final; + + void setClearColor(const vec4& color) final; + void setSwapInterval(Settings::VSync& vsync) final; + void updateView(ivec2& viewRes) final; + + void startDraw(const View* view) final; + void drawRect(const Texture* tex, const Rect& rect, const Rect& frame, const vec4& color) final; + void finishDraw(const View* view) final; + + void startSelDraw(const View* view, ivec2 pos) final; + void drawSelRect(const Widget* wgt, const Rect& rect, const Rect& frame) final; + Widget* finishSelDraw(const View* view) final; + + Texture* texFromColor(u8vec4 color) final; + Texture* texFromText(SDL_Surface* img) final; + Texture* texFromIcon(SDL_Surface* pic) final; + +private: + static IDXGIFactory* createFactory(); + pair createSwapchain(IDXGIFactory* factory, SDL_Window* win, ivec2 res); + void recreateSwapchain(IDXGIFactory* factory, ViewDx* view); + void initShader(); + pair createShader(const char* vertSrc, const char* pixlSrc, const char* name) const; + + template void uploadBuffer(ID3D11Buffer* buffer, const T& data); + TextureDx* createTexture(const void* pixels, ivec2 res, uint rowLen, DXGI_FORMAT format); + static pair pickPixFormat(SDL_Surface* img); + static DXGI_SWAP_EFFECT toSwapEffect(Settings::VSync vsync); + static string hresultToStr(HRESULT rs); +}; +#endif + +class RendererGl : public Renderer { +private: +#ifdef OPENGLES + static constexpr GLenum textPixFormat = GL_RGBA; +#else + static constexpr GLenum textPixFormat = GL_BGRA; +#endif + + class TextureGl : public Texture { + private: + GLuint id = 0; + + TextureGl(ivec2 size, GLuint tex); + public: + void free() final; + + friend class RendererGl; + }; + + struct ViewGl : View { + SDL_GLContext ctx; + + ViewGl(SDL_Window* window, const Rect& area, SDL_GLContext context); + }; + + GLint uniPviewGui, uniRectGui, uniFrameGui, uniColorGui; + GLint uniPviewSel, uniRectSel, uniFrameSel, uniAddrSel; + GLuint progGui = 0, progSel = 0; + GLuint fboSel = 0, texSel = 0; + GLuint vao = 0; + +#ifndef OPENGLES + void (APIENTRY* glActiveTexture)(GLenum texture); + void (APIENTRY* glAttachShader)(GLuint program, GLuint shader); + void (APIENTRY* glBindFramebuffer)(GLenum target, GLuint framebuffer); + void (APIENTRY* glBindVertexArray)(GLuint array); + GLenum (APIENTRY* glCheckFramebufferStatus)(GLenum target); + void (APIENTRY* glClearBufferuiv)(GLenum buffer, GLint drawbuffer, const GLuint* value); + void (APIENTRY* glCompileShader)(GLuint shader); + GLuint (APIENTRY* glCreateProgram)(); + GLuint (APIENTRY* glCreateShader)(GLenum shaderType); + void (APIENTRY* glDeleteFramebuffers)(GLsizei n, GLuint* framebuffers); + void (APIENTRY* glDeleteShader)(GLuint shader); + void (APIENTRY* glDeleteProgram)(GLuint program); + void (APIENTRY* glDeleteVertexArrays)(GLsizei n, const GLuint* arrays); + void (APIENTRY* glDetachShader)(GLuint program, GLuint shader); + void (APIENTRY* glFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); + void (APIENTRY* glGenerateMipmap)(GLenum target); + void (APIENTRY* glGenFramebuffers)(GLsizei n, GLuint* ids); + void (APIENTRY* glGenVertexArrays)(GLsizei n, GLuint* arrays); + void (APIENTRY* glGetProgramInfoLog)(GLuint program, GLsizei maxLength, GLsizei* length, GLchar* infoLog); + void (APIENTRY* glGetProgramiv)(GLuint program, GLenum pname, GLint* params); + void (APIENTRY* glGetShaderInfoLog)(GLuint shader, GLsizei maxLength, GLsizei* length, GLchar* infoLog); + void (APIENTRY* glGetShaderiv)(GLuint shader, GLenum pname, GLint* params); + GLint (APIENTRY* glGetUniformLocation)(GLuint program, const GLchar* name); + void (APIENTRY* glLinkProgram)(GLuint program); + void (APIENTRY* glShaderSource)(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); + void (APIENTRY* glUniform1i)(GLint location, GLint v0); + void (APIENTRY* glUniform2f)(GLint location, GLfloat v0, GLfloat v1); + void (APIENTRY* glUniform2fv)(GLint location, GLsizei count, const GLfloat* value); + void (APIENTRY* glUniform2ui)(GLint location, GLuint v0, GLuint v1); + void (APIENTRY* glUniform2uiv)(GLint location, GLsizei count, const GLuint* value); + void (APIENTRY* glUniform4fv)(GLint location, GLsizei count, const GLfloat* value); + void (APIENTRY* glUniform4iv)(GLint location, GLsizei count, const GLint* value); + void (APIENTRY* glUseProgram)(GLuint program); +#endif +public: + RendererGl(const umap& windows, Settings* sets, ivec2& viewRes, const vec4& bgcolor); + ~RendererGl() final; + + void setClearColor(const vec4& color) final; + void setSwapInterval(Settings::VSync& vsync) final; + void updateView(ivec2& viewRes) final; + + void startDraw(const View* view) final; + void drawRect(const Texture* tex, const Rect& rect, const Rect& frame, const vec4& color) final; + void finishDraw(const View* view) final; + + void startSelDraw(const View* view, ivec2 pos) final; + void drawSelRect(const Widget* wgt, const Rect& rect, const Rect& frame) final; + Widget* finishSelDraw(const View* view) final; + + Texture* texFromColor(u8vec4 color) final; + Texture* texFromText(SDL_Surface* img) final; + Texture* texFromIcon(SDL_Surface* pic) final; + +private: + void initGl(ivec2 res, Settings::VSync& vsync, const vec4& bgcolor); + bool trySetSwapInterval(Settings::VSync vsync); + bool trySetSwapIntervalSingle(Settings::VSync vsync); +#ifndef OPENGLES + void initFunctions(); +#endif + void initShader(); + GLuint createShader(const char* vertSrc, const char* fragSrc, const char* name) const; + void checkFramebufferStatus(const char* name); + + template static void checkStatus(GLuint id, GLenum stat, C check, I info, const string& name); + template static GLuint createTexture(const void* pixels, int width, int height, int rowLen, GLenum format); + static pair pickPixFormat(SDL_Surface* img); +#ifndef OPENGLES +#ifndef NDEBUG + static void APIENTRY debugMessage(GLenum source, GLenum type, uint id, GLenum severity, GLsizei length, const char* message, const void* userParam); +#endif +#endif +}; diff --git a/src/engine/scene.cpp b/src/engine/scene.cpp index 59e18546..304fdadc 100644 --- a/src/engine/scene.cpp +++ b/src/engine/scene.cpp @@ -75,10 +75,10 @@ void Scene::onMouseUp(ivec2 mPos, uint8 mBut, uint8 mCnt) { select->onClick(mPos, mBut); } -void Scene::onMouseWheel(ivec2 wMov) { +void Scene::onMouseWheel(ivec2 mPos, ivec2 wMov) { if (ScrollArea* box = getSelectedScrollArea()) { box->onScroll(wMov * scrollFactorWheel); - updateSelect(mousePos()); + updateSelect(mPos); } else if (select) select->onScroll(wMov * scrollFactorWheel); } @@ -113,6 +113,14 @@ void Scene::onResize() { context->onResize(); } +void Scene::onDisplayChange() { + layout->onDisplayChange(); + if (popup) + popup->onDisplayChange(); + if (overlay) + overlay->onDisplayChange(); +} + void Scene::resetLayouts() { // clear scene #if !SDL_TTF_VERSION_ATLEAST(2, 0, 18) @@ -149,7 +157,7 @@ void Scene::setPopup(Popup* newPopup, Widget* newCapture) { if (popup = newPopup; popup) popup->postInit(); if (capture = newCapture; capture) - capture->onClick(mousePos(), SDL_BUTTON_LEFT); + capture->onClick(World::winSys()->mousePos(), SDL_BUTTON_LEFT); if (!World::inputSys()->mouseLast) select = capture ? capture : popup ? popup->firstNavSelect : nullptr; updateSelect(); @@ -168,11 +176,11 @@ void Scene::setContext(Context* newContext) { void Scene::updateSelect() { if (World::inputSys()->mouseLast) - updateSelect(mousePos()); + updateSelect(World::winSys()->mousePos()); } Widget* Scene::getSelected(ivec2 mPos) { - if (!World::drawSys()->viewport().contain(mPos)) + if (!Rect(ivec2(0), World::drawSys()->getViewRes()).contain(mPos)) return nullptr; Layout* box = layout; @@ -183,6 +191,11 @@ Widget* Scene::getSelected(ivec2 mPos) { else if (overlayFocused(mPos)) box = overlay; + if (World::sets()->gpuSelecting) { + Widget* wgt = World::drawSys()->getSelectedWidget(box, mPos); + return wgt ? wgt->navSelectable() ? wgt : wgt->getParent() : nullptr; + } + for (;;) { Rect frame = box->frame(); if (vector::const_iterator it = std::find_if(box->getWidgets().begin(), box->getWidgets().end(), [&frame, &mPos](const Widget* wi) -> bool { return wi->rect().intersect(frame).contain(mPos); }); it != box->getWidgets().end()) { diff --git a/src/engine/scene.h b/src/engine/scene.h index 77435be2..0c60d3a3 100644 --- a/src/engine/scene.h +++ b/src/engine/scene.h @@ -34,11 +34,12 @@ class Scene { void onMouseMove(ivec2 mPos, ivec2 mMov); void onMouseDown(ivec2 mPos, uint8 mBut, uint8 mCnt); void onMouseUp(ivec2 mPos, uint8 mBut, uint8 mCnt); - void onMouseWheel(ivec2 wMov); + void onMouseWheel(ivec2 mPos, ivec2 wMov); void onMouseLeave(); void onCompose(string_view str); void onText(string_view str); void onResize(); + void onDisplayChange(); Widget* getCapture() const; void setCapture(Widget* inter); diff --git a/src/engine/windowSys.cpp b/src/engine/windowSys.cpp index 5326bc58..51f29009 100644 --- a/src/engine/windowSys.cpp +++ b/src/engine/windowSys.cpp @@ -12,26 +12,26 @@ int WindowSys::start() { program = nullptr; scene = nullptr; sets = nullptr; - window = nullptr; int rc = EXIT_SUCCESS; try { init(); exec(); } catch (const std::runtime_error& e) { + logError(e.what()); std::cerr << e.what() << std::endl; - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", e.what(), window); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", e.what(), !windows.empty() ? windows.begin()->second : nullptr); rc = EXIT_FAILURE; #ifdef NDEBUG } catch (...) { - std::cerr << "unknown error" << std::endl; - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "unknown error", window); + logError("unknown error"); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "unknown error", !windows.empty() ? windows.begin()->second : nullptr); rc = EXIT_FAILURE; #endif } delete program; delete scene; delete inputSys; - destroyWindow(); + destroyWindows(); delete fileSys; delete sets; @@ -59,30 +59,40 @@ void WindowSys::init() { SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1"); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER)) - throw std::runtime_error("Failed to initialize SDL:\n"s + SDL_GetError()); + throw std::runtime_error(SDL_GetError()); if (TTF_Init()) - throw std::runtime_error("Failed to initialize fonts:\n"s + TTF_GetError()); + throw std::runtime_error(TTF_GetError()); #if SDL_IMAGE_VERSION_ATLEAST(2, 6, 0) int flags = IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG | IMG_INIT_TIF | IMG_INIT_WEBP | IMG_INIT_JXL | IMG_INIT_AVIF); #else int flags = IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG | IMG_INIT_TIF | IMG_INIT_WEBP); #endif if (!(flags & IMG_INIT_JPG)) - std::cerr << "failed to initialize JPG:\n" << IMG_GetError() << std::endl; + logError(IMG_GetError()); if (!(flags & IMG_INIT_PNG)) - std::cerr << "failed to initialize PNG:\n" << IMG_GetError() << std::endl; + logError(IMG_GetError()); if (!(flags & IMG_INIT_TIF)) - std::cerr << "failed to initialize TIF:\n" << IMG_GetError() << std::endl; + logError(IMG_GetError()); if (!(flags & IMG_INIT_WEBP)) - std::cerr << "failed to initialize WEBP:\n" << IMG_GetError() << std::endl; + logError(IMG_GetError()); #if SDL_IMAGE_VERSION_ATLEAST(2, 6, 0) if (!(flags & IMG_INIT_JXL)) - std::cerr << "failed to initialize JXL:\n" << IMG_GetError() << std::endl; + logError(IMG_GetError()); if (!(flags & IMG_INIT_AVIF)) - std::cerr << "failed to initialize AVIF:\n" << IMG_GetError() << std::endl; + logError(IMG_GetError()); +#endif + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#ifdef OPENGLES + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); +#else +#ifdef NDEBUG + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); +#else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG | SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); +#endif #endif SDL_EventState(SDL_LOCALECHANGED, SDL_DISABLE); - SDL_EventState(SDL_DISPLAYEVENT, SDL_DISABLE); SDL_EventState(SDL_SYSWMEVENT, SDL_DISABLE); SDL_EventState(SDL_KEYUP, SDL_DISABLE); SDL_EventState(SDL_KEYMAPCHANGED, SDL_DISABLE); @@ -108,12 +118,15 @@ void WindowSys::init() { SDL_EventState(SDL_RENDER_TARGETS_RESET, SDL_DISABLE); SDL_EventState(SDL_RENDER_DEVICE_RESET, SDL_DISABLE); if (SDL_RegisterEvents(1) == UINT32_MAX) - throw std::runtime_error("Failed to register application events:\n"s + SDL_GetError()); + throw std::runtime_error(SDL_GetError()); SDL_StopTextInput(); fileSys = new FileSys; sets = fileSys->loadSettings(); - createWindow(); + if (sets->screen != Settings::Screen::multiFullscreen) + createWindow(); + else + createMultiWindow(); inputSys = new InputSys; scene = new Scene; program = new Program; @@ -146,29 +159,57 @@ void WindowSys::exec() { } void WindowSys::createWindow() { - destroyWindow(); // make sure old window (if exists) is destroyed - - // create new window sets->resolution = clamp(sets->resolution, windowMinSize, displayResolution()); - if (window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, sets->resolution.x, sets->resolution.y, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | (sets->maximized ? SDL_WINDOW_MAXIMIZED : 0) | (sets->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0)); !window) - throw std::runtime_error("Failed to create window:\n"s + SDL_GetError()); + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, SDL_DISABLE); + SDL_Window* win = windows.emplace(Renderer::singleDspId, SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, sets->resolution.x, sets->resolution.y, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | (sets->maximized ? SDL_WINDOW_MAXIMIZED : 0) | (sets->screen != Settings::Screen::windowed ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0))).first->second; + if (!win) + throw std::runtime_error("Failed to create window:"s + linend + SDL_GetError()); + + SDL_Surface* icon = IMG_Load((fileSys->dirIcons() / "vertiread.svg").u8string().c_str()); + SDL_SetWindowIcon(win, icon); + SDL_FreeSurface(icon); + + drawSys = new DrawSys(windows, sets, fileSys, int(128.f / fallbackDpi * winDpi)); + SDL_SetWindowMinimumSize(win, windowMinSize.x, windowMinSize.y); + if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(windows.begin()->second), nullptr, nullptr, &winDpi)) + winDpi = fallbackDpi; +} - // visual stuff - if (SDL_Surface* icon = IMG_Load((fileSys->dirIcons() / "vertiread.svg").u8string().c_str())) { - SDL_SetWindowIcon(window, icon); - SDL_FreeSurface(icon); +void WindowSys::createMultiWindow() { + windows.reserve(sets->displays.size()); + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, SDL_ENABLE); + SDL_Surface* icon = IMG_Load((fileSys->dirIcons() / "vertiread.svg").u8string().c_str()); + for (const auto& [id, rect] : sets->displays) { + SDL_Window* win = windows.emplace(id, SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED_DISPLAY(id), SDL_WINDOWPOS_UNDEFINED_DISPLAY(id), rect.w, rect.h, SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_FULLSCREEN_DESKTOP)).first->second; + if (!win) { + std::for_each(windows.begin(), windows.end(), [](pair& it) { SDL_DestroyWindow(it.second); }); + SDL_FreeSurface(icon); + throw std::runtime_error("Failed to create window:"s + linend + SDL_GetError()); + } + SDL_SetWindowIcon(win, icon); } - drawSys = new DrawSys(window, sets->getRendererInfo(), sets, fileSys); - SDL_SetWindowMinimumSize(window, windowMinSize.x, windowMinSize.y); // for some reason this function has to be called after the renderer is created - if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), nullptr, nullptr, &winDpi)) + SDL_FreeSurface(icon); + + drawSys = new DrawSys(windows, sets, fileSys, int(128.f / fallbackDpi * winDpi)); + if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(windows.begin()->second), nullptr, nullptr, &winDpi)) winDpi = fallbackDpi; } -void WindowSys::destroyWindow() { +void WindowSys::destroyWindows() { delete drawSys; drawSys = nullptr; - SDL_DestroyWindow(window); - window = nullptr; + for (auto [id, win] : windows) + SDL_DestroyWindow(win); + windows.clear(); +} + +void WindowSys::recreateWindows() { + destroyWindows(); + if (sets->screen != Settings::Screen::multiFullscreen) + createWindow(); + else + createMultiWindow(); + scene->resetLayouts(); } void WindowSys::handleEvent(const SDL_Event& event) { @@ -176,6 +217,9 @@ void WindowSys::handleEvent(const SDL_Event& event) { case SDL_QUIT: program->eventTryExit(); break; + case SDL_DISPLAYEVENT: + eventDisplay(event.display); + break; case SDL_WINDOWEVENT: eventWindow(event.window); break; @@ -249,17 +293,26 @@ void WindowSys::handleEvent(const SDL_Event& event) { void WindowSys::eventWindow(const SDL_WindowEvent& winEvent) { switch (winEvent.event) { case SDL_WINDOWEVENT_RESIZED: - if (uint32 flags = SDL_GetWindowFlags(window); !(flags & SDL_WINDOW_FULLSCREEN_DESKTOP)) - if (sets->maximized = flags & SDL_WINDOW_MAXIMIZED; !sets->maximized) - sets->resolution = ivec2(winEvent.data1, winEvent.data2); + if (windows.size() == 1) + if (uint32 flags = SDL_GetWindowFlags(windows.begin()->second); !(flags & SDL_WINDOW_FULLSCREEN_DESKTOP)) + if (sets->maximized = flags & SDL_WINDOW_MAXIMIZED; !sets->maximized) + sets->resolution = ivec2(winEvent.data1, winEvent.data2); case SDL_WINDOWEVENT_SIZE_CHANGED: + drawSys->updateView(); scene->onResize(); break; case SDL_WINDOWEVENT_LEAVE: scene->onMouseLeave(); break; - case SDL_WINDOWEVENT_CLOSE: - program->eventTryExit(); + case SDL_WINDOWEVENT_FOCUS_GAINED: + if (sets->screen == Settings::Screen::multiFullscreen) + for (auto [id, win] : windows) + SDL_RaiseWindow(win); + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + if (sets->screen == Settings::Screen::multiFullscreen) + for (auto [id, win] : windows) + SDL_HideWindow(win); break; #if SDL_VERSION_ATLEAST(2, 0, 18) case SDL_WINDOWEVENT_DISPLAY_CHANGED: { @@ -276,38 +329,87 @@ void WindowSys::eventWindow(const SDL_WindowEvent& winEvent) { } } } +void WindowSys::eventDisplay(const SDL_DisplayEvent& dspEvent) { + if (dspEvent.type == SDL_DISPLAYEVENT_ORIENTATION || dspEvent.type == SDL_DISPLAYEVENT_CONNECTED || dspEvent.type == SDL_DISPLAYEVENT_DISCONNECTED) { + sets->unionDisplays(); + if (windows.size() > 1) + recreateWindows(); + scene->onDisplayChange(); + } +} + +ivec2 WindowSys::winViewOffset(uint32 wid) const { + if (SDL_Window* win = SDL_GetWindowFromID(wid)) + if (umap::const_iterator it = std::find_if(windows.begin(), windows.end(), [win](const pair& p) -> bool { return p.second == win; }); it != windows.end()) + return drawSys->getView(it->first).pos(); + return ivec2(INT_MIN); +} + +ivec2 WindowSys::mousePos() const { + ivec2 mp; + SDL_GetMouseState(&mp.x, &mp.y); + SDL_Window* win = SDL_GetMouseFocus(); + umap::const_iterator it = std::find_if(windows.begin(), windows.end(), [win](const pair& p) -> bool { return p.second == win; }); + return it != windows.end() ? mp + drawSys->getView(it->first).pos() : mp; +} + void WindowSys::moveCursor(ivec2 mov) { - int px, py; - SDL_GetMouseState(&px, &py); - SDL_WarpMouseInWindow(window, px + mov.x, py + mov.y); + SDL_Window* win = SDL_GetMouseFocus(); + if (umap::iterator it = std::find_if(windows.begin(), windows.end(), [win](const pair& p) -> bool { return p.second == win; }); it != windows.end()) { + ivec2 wpos; + SDL_GetMouseState(&wpos.x, &wpos.y); + wpos += drawSys->getView(it->first).pos() + mov; + int id = drawSys->findPointInView(wpos); + if (it = windows.find(id); it != windows.end()) { + ivec2 vpos = drawSys->getView(id).pos(); + SDL_WarpMouseInWindow(it->second, wpos.x - vpos.x, wpos.y - vpos.y); + } + } } void WindowSys::toggleOpacity() { - if (float val; !SDL_GetWindowOpacity(window, &val)) - SDL_SetWindowOpacity(window, val < 1.f ? 1.f : 0.f); + for (auto [id, win] : windows) { + if (float val; !SDL_GetWindowOpacity(win, &val)) + SDL_SetWindowOpacity(win, val < 1.f ? 1.f : 0.f); + else + SDL_MinimizeWindow(win); + } +} + +void WindowSys::setScreenMode(Settings::Screen sm) { + bool changeFlag = sets->screen != Settings::Screen::multiFullscreen && sm != Settings::Screen::multiFullscreen; + sets->screen = sm; + if (changeFlag) + SDL_SetWindowFullscreen(windows.begin()->second, sm == Settings::Screen::fullscreen ? SDL_GetWindowFlags(windows.begin()->second) | SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_GetWindowFlags(windows.begin()->second) & ~SDL_WINDOW_FULLSCREEN_DESKTOP); else - SDL_MinimizeWindow(window); + recreateWindows(); } -void WindowSys::setFullscreen(bool on) { - sets->fullscreen = on; - SDL_SetWindowFullscreen(window, on ? SDL_GetWindowFlags(window) | SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_GetWindowFlags(window) & ~SDL_WINDOW_FULLSCREEN_DESKTOP); +void WindowSys::setWindowPos(ivec2 pos) { + if (sets->screen != Settings::Screen::multiFullscreen) + SDL_SetWindowPosition(windows.begin()->second, pos.x, pos.y); } void WindowSys::setResolution(ivec2 res) { sets->resolution = clamp(res, windowMinSize, displayResolution()); - SDL_SetWindowSize(window, res.x, res.y); + if (sets->screen != Settings::Screen::multiFullscreen) + SDL_SetWindowSize(windows.begin()->second, res.x, res.y); } -void WindowSys::setRenderer(string_view name) { - sets->renderer = name; - createWindow(); - scene->resetLayouts(); +ivec2 WindowSys::displayResolution() const { + SDL_DisplayMode mode; + if (!windows.empty() && !SDL_GetDesktopDisplayMode(SDL_GetWindowDisplayIndex(windows.begin()->second), &mode)) + return ivec2(mode.w, mode.h); + + ivec2 res(0); + for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i) + if (!SDL_GetDesktopDisplayMode(i, &mode)) + res = glm::max(res, ivec2(mode.w, mode.h)); + return res; } void WindowSys::resetSettings() { delete sets; sets = new Settings(fileSys->getDirSets(), fileSys->getAvailableThemes()); - createWindow(); - scene->resetLayouts(); + recreateWindows(); } diff --git a/src/engine/windowSys.h b/src/engine/windowSys.h index 256a208d..fa7aee83 100644 --- a/src/engine/windowSys.h +++ b/src/engine/windowSys.h @@ -1,6 +1,6 @@ #pragma once -#include "utils/utils.h" +#include "utils/settings.h" // handles window events and contains video settings class WindowSys { @@ -18,7 +18,7 @@ class WindowSys { Program* program; Scene* scene; Settings* sets; - SDL_Window* window = nullptr; + umap windows; float winDpi; float dSec = 0.f; // delta seconds, aka the time between each iteration of the above mentioned loop bool run = true; // whether the loop in which the program runs should continue @@ -29,15 +29,16 @@ class WindowSys { float getDSec() const; float getWinDpi() const; - uint32 windowID() const; + ivec2 mousePos() const; + ivec2 winViewOffset(uint32 wid) const; ivec2 displayResolution() const; - void setWindowPos(ivec2 pos); void moveCursor(ivec2 mov); void toggleOpacity(); - void setFullscreen(bool on); + void setScreenMode(Settings::Screen sm); + void setWindowPos(ivec2 pos); void setResolution(ivec2 res); - void setRenderer(string_view name); void resetSettings(); + void recreateWindows(); FileSys* getFileSys(); DrawSys* getDrawSys(); @@ -51,9 +52,11 @@ class WindowSys { void exec(); void createWindow(); - void destroyWindow(); + void createMultiWindow(); + void destroyWindows(); void handleEvent(const SDL_Event& event); // pass events to their specific handlers void eventWindow(const SDL_WindowEvent& winEvent); + void eventDisplay(const SDL_DisplayEvent& dspEvent); }; inline void WindowSys::close() { @@ -91,16 +94,3 @@ inline Settings* WindowSys::getSets() { inline float WindowSys::getWinDpi() const { return winDpi; } - -inline uint32 WindowSys::windowID() const { - return SDL_GetWindowID(window); -} - -inline ivec2 WindowSys::displayResolution() const { - SDL_DisplayMode mode; - return !SDL_GetDesktopDisplayMode(window ? SDL_GetWindowDisplayIndex(window) : 0, &mode) ? ivec2(mode.w, mode.h) : ivec2(INT_MAX); -} - -inline void WindowSys::setWindowPos(ivec2 pos) { - SDL_SetWindowPosition(window, pos.x, pos.y); -} diff --git a/src/prog/browser.cpp b/src/prog/browser.cpp index 35220cea..2f0b0a6b 100644 --- a/src/prog/browser.cpp +++ b/src/prog/browser.cpp @@ -40,7 +40,7 @@ fs::file_type Browser::goTo(const fs::path& path) { return fs::file_type::directory; } } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logError(err.what()); } if (FileSys::isArchive(path)) { @@ -63,7 +63,7 @@ bool Browser::goIn(const fs::path& dname) { return true; } } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logError(err.what()); } return false; } @@ -106,7 +106,7 @@ void Browser::goNext(bool fwd, bool showHidden) { else if (curDir != rootDir) shiftDir(fwd, showHidden); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logError(err.what()); } } diff --git a/src/prog/downloader.cpp b/src/prog/downloader.cpp index 8eab9b68..31c0e7df 100644 --- a/src/prog/downloader.cpp +++ b/src/prog/downloader.cpp @@ -32,7 +32,7 @@ xmlDoc* WebSource::downloadHtml(const string& url) { mainLock.unlock(); if (code) { - std::cerr << "CURL error: " << curl_easy_strerror(code) << std::endl; + logError("CURL error: ", curl_easy_strerror(code)); return nullptr; } return htmlReadDoc(reinterpret_cast(data.c_str()), url.c_str(), nullptr, HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING); @@ -53,7 +53,7 @@ DownloadState WebSource::downloadFile(const string& url, const fs::path& drc) { CURLcode code = curl_easy_perform(curlFile); fclose(ofh); if (code) { - std::cerr << "CURL error: " << curl_easy_strerror(code) << std::endl; + logError("CURL error: ", curl_easy_strerror(code)); fs::remove(fpath); } return World::downloader()->getDlState(); diff --git a/src/prog/program.cpp b/src/prog/program.cpp index 593c7811..43364992 100644 --- a/src/prog/program.cpp +++ b/src/prog/program.cpp @@ -74,7 +74,7 @@ void Program::eventOpenLastPage(Button*) { browser = std::make_unique(lbl ? World::sets()->getDirLib() / fs::u8path(lbl->getText()) : Browser::topDir, lbl ? World::sets()->getDirLib() / fs::u8path(lbl->getText()) / fs::u8path(drc) : fs::u8path(drc), fs::u8path(fname), &Program::eventOpenBookList, true); eventStartLoadingReader(fname); } catch (const std::runtime_error& e) { - std::cerr << e.what() << std::endl; + logError(e.what()); eventOpenPageBrowser(lbl); } } else @@ -100,7 +100,7 @@ bool Program::openFile(const fs::path& file) { browser = std::make_unique(Browser::topDir, isPic ? parentPath(file) : file, isPic ? file.filename() : fs::path(), &Program::eventOpenBookList, false); eventStartLoadingReader(file.u8string()); } catch (const std::runtime_error& e) { - std::cerr << e.what() << std::endl; + logError(e.what()); return false; } return true; @@ -331,9 +331,7 @@ void Program::eventOpenSettings(Button*) { } void Program::eventSwitchDirection(Button* but) { - World::sets()->direction = strToEnum(Direction::names, static_cast(but)->getText(), Direction::down); - World::scene()->getContext()->owner()->setCurOpt(World::sets()->direction); - World::scene()->setContext(nullptr); + World::sets()->direction = finishComboBox(but, Direction::names, Settings::defaultDirection); } void Program::eventSetZoom(Button* but) { @@ -408,8 +406,43 @@ void Program::eventMoveFinished() { eventClosePopup(); } -void Program::eventSetFullscreen(Button* but) { - World::winSys()->setFullscreen(static_cast(but)->on); +void Program::eventSetScreenMode(Button* but) { + if (Settings::Screen screen = finishComboBox(but, Settings::screenModeNames, Settings::defaultScreenMode); World::sets()->screen != screen) + World::winSys()->setScreenMode(screen); +} + +void Program::eventSetVsync(Button* but) { + if (Settings::VSync vsync = Settings::VSync(finishComboBox(but, Settings::vsyncNames, int8(Settings::defaultVSync) + 1) - 1); World::sets()->vsync != vsync) { + World::sets()->vsync = vsync; + World::drawSys()->setVsync(World::sets()->vsync); + } +} + +void Program::eventSetRenderer(Button* but) { + if (Settings::Renderer renderer = finishComboBox(but, Settings::rendererNames, Settings::defaultRenderer); World::sets()->renderer != renderer) { + World::sets()->renderer = renderer; + World::winSys()->recreateWindows(); + } +} + +void Program::eventSetGpuSelecting(Button* but) { + World::sets()->gpuSelecting = static_cast(but)->on; +} + +template +T Program::finishComboBox(Button* but, const array& names, T defaultValue) { + T val = strToEnum(names, decomboboxify ? ProgSettings::decomboboxify(static_cast(but)->getText()) : static_cast(but)->getText(), defaultValue); + World::scene()->getContext()->owner()->setCurOpt(sizet(val)); + World::scene()->setContext(nullptr); + return val; +} + +void Program::eventSetMultiFullscreen(Button* but) { + if (umap dsps = static_cast(but)->getActiveDisps(); dsps != World::sets()->displays) { + World::sets()->displays = std::move(dsps); + if (World::sets()->screen == Settings::Screen::multiFullscreen) + World::winSys()->recreateWindows(); + } } void Program::eventSetHide(Button* but) { @@ -429,10 +462,6 @@ void Program::eventSetFont(Button* but) { World::scene()->setPopup(state->createPopupMessage("Invalid font", &Program::eventClosePopup)); } -void Program::eventSetRenderer(Button* but) { - World::winSys()->setRenderer(static_cast(but)->getText()); -} - void Program::eventSetScrollSpeed(Button* but) { World::sets()->scrollSpeed = toVec(static_cast(but)->getText()); } @@ -486,11 +515,11 @@ void Program::eventSetFill(Button*) { } void Program::eventSetPicLimitType(Button* but) { - ProgSettings* ps = static_cast(state); - World::sets()->picLim.type = strToEnum(PicLim::names, static_cast(but)->getText(), PicLim::Type::none); - World::scene()->getContext()->owner()->setCurOpt(uint8(World::sets()->picLim.type)); - World::scene()->setContext(nullptr); - ps->limitLine->replaceWidget(ps->limitLine->getWidgets().size() - 1, static_cast(state)->createLimitEdit()); + if (PicLim::Type plim = finishComboBox(but, PicLim::names, PicLim::Type::none); plim != World::sets()->picLim.type) { + ProgSettings* ps = static_cast(state); + World::sets()->picLim.type = plim; + ps->limitLine->replaceWidget(ps->limitLine->getWidgets().size() - 1, static_cast(state)->createLimitEdit()); + } } void Program::eventSetPicLimCount(Button* but) { diff --git a/src/prog/program.h b/src/prog/program.h index c387b464..4b933357 100644 --- a/src/prog/program.h +++ b/src/prog/program.h @@ -75,6 +75,7 @@ class Program { void eventDownloadListNext() {} void eventDownloadListFinish() {} #endif + // settings void eventOpenSettings(Button* but = nullptr); void eventSwitchDirection(Button* but); @@ -87,11 +88,14 @@ class Program { void eventDontMoveComics(Button* but = nullptr); void eventMoveProgress(uptrt prg, uptrt lim); void eventMoveFinished(); - void eventSetFullscreen(Button* but); + void eventSetScreenMode(Button* but); + void eventSetVsync(Button* but); + void eventSetRenderer(Button* but); + void eventSetGpuSelecting(Button* but); + void eventSetMultiFullscreen(Button* but); void eventSetHide(Button* but); void eventSetTheme(Button* but); void eventSetFont(Button* but); - void eventSetRenderer(Button* but); void eventSetScrollSpeed(Button* but); void eventSetDeadzoneSL(Button* but); void eventSetDeadzoneLE(Button* but); @@ -119,6 +123,7 @@ class Program { private: void switchPictures(bool fwd, string_view picname); void offerMoveBooks(fs::path&& oldLib); + template T finishComboBox(Button* but, const array& names, T defaultValue = T(N)); template void setState(A&&... args); void reposizeWindow(ivec2 dres, ivec2 wsiz); }; diff --git a/src/prog/progs.cpp b/src/prog/progs.cpp index 2e2886a9..0308a4e2 100644 --- a/src/prog/progs.cpp +++ b/src/prog/progs.cpp @@ -69,7 +69,11 @@ void ProgState::eventCursorRight(float amt) { } void ProgState::eventFullscreen() { - World::winSys()->setFullscreen(!World::sets()->fullscreen); + World::winSys()->setScreenMode(World::sets()->screen != Settings::Screen::fullscreen ? Settings::Screen::fullscreen : Settings::Screen::windowed); +} + +void ProgState::eventMultiFullscreen() { + World::winSys()->setScreenMode(World::sets()->screen != Settings::Screen::multiFullscreen ? Settings::Screen::multiFullscreen : Settings::Screen::windowed); } void ProgState::eventHide() { @@ -93,7 +97,7 @@ void ProgState::onResize() { picSize = int(40.f / WindowSys::fallbackDpi * World::winSys()->getWinDpi()); picMargin = int(4.f / WindowSys::fallbackDpi * World::winSys()->getWinDpi()); contextMargin = int(3.f / WindowSys::fallbackDpi * World::winSys()->getWinDpi()); - maxTooltipLength = World::drawSys()->viewport().w * 2 / 3; + maxTooltipLength = World::drawSys()->getViewRes().x * 2 / 3; cursorMoveFactor = 10.f / WindowSys::fallbackDpi * World::winSys()->getWinDpi(); } @@ -137,7 +141,7 @@ Context* ProgState::createContext(vector>&& items, Widget* p wgts[i] = new Label(lineHeight, std::move(items[i].first), items[i].second, &Program::eventCloseContext); Widget* first = wgts[0]; - Rect rect = calcTextContextRect(wgts, mousePos(), ivec2(0, lineHeight), contextMargin); + Rect rect = calcTextContextRect(wgts, World::winSys()->mousePos(), ivec2(0, lineHeight), contextMargin); return new Context(rect.pos(), rect.size(), vector{ new ScrollArea(1.f, std::move(wgts), Layout::defaultDirection, Layout::Select::none, 0) }, first, parent, Color::dark, nullptr, Layout::defaultDirection, contextMargin); } @@ -159,7 +163,7 @@ Rect ProgState::calcTextContextRect(const vector& items, ivec2 pos, ive size.x = w; size.y = size.y * items.size() + margin * 2; - ivec2 res = World::drawSys()->viewport().size(); + ivec2 res = World::drawSys()->getViewRes(); calcContextPos(pos.x, size.x, res.x); calcContextPos(pos.y, size.y, res.y); return Rect(pos, size); @@ -183,11 +187,11 @@ int ProgState::findMaxLength(T pos, T end, int height) { return width; } -SDL_Texture* ProgState::makeTooltip(const char* str) { +Texture* ProgState::makeTooltip(const char* str) { return World::drawSys()->renderText(str, tooltipHeight, maxTooltipLength); } -SDL_Texture* ProgState::makeTooltipL(const char* str) { +Texture* ProgState::makeTooltipL(const char* str) { uint width = 0; while (*str) { sizet len = strcspn(str, "\n"); @@ -589,7 +593,12 @@ void ProgSettings::eventEscape() { void ProgSettings::eventFullscreen() { ProgState::eventFullscreen(); - fullscreen->on = World::sets()->fullscreen; + screen->setCurOpt(sizet(World::sets()->screen)); +} + +void ProgSettings::eventMultiFullscreen() { + ProgState::eventMultiFullscreen(); + screen->setCurOpt(sizet(World::sets()->screen)); } void ProgSettings::eventHide() { @@ -606,7 +615,7 @@ void ProgSettings::eventFileDrop(const fs::path& file) { if (fs::is_directory(file)) World::sets()->setDirLib(file, World::fileSys()->getDirSets()); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logError(err.what()); } } } @@ -631,94 +640,135 @@ RootLayout* ProgSettings::createLayout() { Text tsizl("Landscape", lineHeight); Text tsizs("Square", lineHeight); Text tsizf("Fill", lineHeight); - initlist txs = { + static constexpr initlist txs = { + "Monitors", "Direction", "Zoom", "Spacing", "Picture limit", - "Fullscreen", + "Screen", + "VSync", + "Renderer", + "GPU selecting", "Size", "Theme", "Show hidden", "Font", "Library", - "Renderer", "Scroll speed", "Deadzone" }; initlist::iterator itxs = txs.begin(); + vector dnames(Direction::names.size()); + std::transform(Direction::names.begin(), Direction::names.end(), dnames.begin(), comboboxify); + vector pnames(PicLim::names.size()); + std::transform(PicLim::names.begin(), PicLim::names.end(), pnames.begin(), comboboxify); + vector snames(Settings::screenModeNames.size()); + std::transform(Settings::screenModeNames.begin(), Settings::screenModeNames.end(), snames.begin(), comboboxify); + vector vnames(Settings::vsyncNames.size()); + std::transform(Settings::vsyncNames.begin(), Settings::vsyncNames.end(), vnames.begin(), comboboxify); array bnames; - for (sizet i = 0; i < Binding::names.size(); ++i) - bnames[i] = firstUpper(Binding::names[i]); + std::transform(Binding::names.begin(), Binding::names.end(), bnames.begin(), comboboxify); + int plimLength = findMaxLength(pnames.begin(), pnames.end(), lineHeight); int descLength = std::max(findMaxLength(txs.begin(), txs.end(), lineHeight), findMaxLength(bnames.begin(), bnames.end(), lineHeight)); constexpr char tipPicLim[] = "Picture limit per batch:\n" "- none: all pictures in directory/archive\n" "- count: number of pictures\n" "- size: total size of pictures"; + constexpr char vsyncTip[] = "Immediate: off\n" + "Synchronized: on\n" + "Adaptive: on and smooth (works on fewer computers)"; constexpr char tipDeadzon[] = "Controller axis deadzone"; + vector monitorLeftSide = { + new Label(lineHeight, *itxs++), + new Widget(1.f) + }; + Size monitorSize = Size([](const Widget* wgt) -> int { + const Layout* box = static_cast(wgt); + return static_cast(box->getWidget(1))->precalcSizeExpand(box->size()); + }); + // action fields for labels - SDL_RendererInfo rendererInfo; - vector renderers(SDL_GetNumRenderDrivers()); - for (sizet i = 0; i < renderers.size(); ++i) - if (!SDL_GetRenderDriverInfo(i, &rendererInfo)) - renderers[i] = rendererInfo.name; vector themes = World::fileSys()->getAvailableThemes(); - vector directs(Direction::names.begin(), Direction::names.end()); - vector plims(PicLim::names.begin(), PicLim::names.end()); Text dots(KeyGetter::ellipsisStr, lineHeight); Text dznum(toStr(Settings::axisLimit), lineHeight); - vector lx[] = { { + pair> lx[txs.size()] = { + { lineHeight, { new Label(descLength, *itxs++), - new ComboBox(1.f, directs[uint8(World::sets()->direction)], std::move(directs), &Program::eventSwitchDirection, makeTooltip("Reading direction")) - }, { + new ComboBox(1.f, comboboxify(Direction::names[uint8(World::sets()->direction)]), std::move(dnames), &Program::eventSwitchDirection, makeTooltip("Reading direction")) + } }, + { lineHeight, { new Label(descLength, *itxs++), new LabelEdit(1.f, toStr(World::sets()->zoom), &Program::eventSetZoom, nullptr, nullptr, makeTooltip("Default reader zoom"), LabelEdit::TextType::uFloat) - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), new LabelEdit(1.f, toStr(World::sets()->spacing), &Program::eventSetSpacing, nullptr, nullptr, makeTooltip("Picture spacing in reader"), LabelEdit::TextType::uInt) - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), - new ComboBox(findMaxLength(plims.begin(), plims.end(), lineHeight), plims[uint8(World::sets()->picLim.type)], std::move(plims), &Program::eventSetPicLimitType, makeTooltipL(tipPicLim)), + new ComboBox(plimLength, comboboxify(PicLim::names[uint8(World::sets()->picLim.type)]), std::move(pnames), &Program::eventSetPicLimitType, makeTooltipL(tipPicLim)), createLimitEdit() - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), - fullscreen = new CheckBox(lineHeight, World::sets()->fullscreen, &Program::eventSetFullscreen, nullptr, nullptr, makeTooltip("Fullscreen in native resolution")) - }, { + screen = new ComboBox(1.f, comboboxify(Settings::screenModeNames[sizet(World::sets()->screen)]), std::move(snames), &Program::eventSetScreenMode, makeTooltip("Window screen mode")) + } }, + { lineHeight, { + new Label(descLength, *itxs++), + new ComboBox(1.f, comboboxify(Settings::vsyncNames[uint8(World::sets()->vsync) + 1]), std::move(vnames), &Program::eventSetVsync, makeTooltipL(vsyncTip)) + } }, + { lineHeight, { + new Label(descLength, *itxs++), + new ComboBox(1.f, Settings::rendererNames[uint8(World::sets()->renderer)], vector(Settings::rendererNames.begin(), Settings::rendererNames.end()), &Program::eventSetRenderer, makeTooltip("Rendering backend")) + } }, + { monitorSize, { + new Layout(descLength, std::move(monitorLeftSide)), + new WindowArranger(1.f, float(lineHeight * 2) / 1080.f, true, &Program::eventSetMultiFullscreen, &Program::eventSetMultiFullscreen, makeTooltip("Monitor arrangement for multi fullscreen")) + } }, + { lineHeight, { + new Label(descLength, *itxs++), + new CheckBox(lineHeight, World::sets()->gpuSelecting, &Program::eventSetGpuSelecting, nullptr, nullptr, makeTooltip("Use the graphics process to determine which widget is being selected")) + } }, + { lineHeight, { new Label(descLength, *itxs++), new Label(tsizp.length, std::move(tsizp.text), &Program::eventSetPortrait, nullptr, nullptr, makeTooltip("Portrait window size")), new Label(tsizl.length, std::move(tsizl.text), &Program::eventSetLandscape, nullptr, nullptr, makeTooltip("Landscape window size")), new Label(tsizs.length, std::move(tsizs.text), &Program::eventSetSquare, nullptr, nullptr, makeTooltip("Square window size")), new Label(tsizf.length, std::move(tsizf.text), &Program::eventSetFill, nullptr, nullptr, makeTooltip("Fill screen with window")) - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), new ComboBox(1.f, World::sets()->getTheme(), std::move(themes), &Program::eventSetTheme, makeTooltip("Color scheme")) - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), showHidden = new CheckBox(lineHeight, World::sets()->showHidden, &Program::eventSetHide, nullptr, nullptr, makeTooltip("Show hidden files")) - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), new LabelEdit(1.f, World::sets()->font, &Program::eventSetFont, nullptr, nullptr, makeTooltip("Font name or path")) - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), new LabelEdit(1.f, World::sets()->getDirLib().u8string(), &Program::eventSetLibraryDirLE, nullptr, nullptr, makeTooltip("Library path")), new Label(dots.length, std::move(dots.text), &Program::eventOpenLibDirBrowser, nullptr, nullptr, makeTooltip("Browse for library"), Alignment::center) - }, { - new Label(descLength, *itxs++), - new ComboBox(1.f, World::sets()->renderer, std::move(renderers), &Program::eventSetRenderer, makeTooltip("Graphics renderer")) - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), new LabelEdit(1.f, World::sets()->scrollSpeedString(), &Program::eventSetScrollSpeed, nullptr, nullptr, makeTooltip("Scroll speed for button presses or axes"), LabelEdit::TextType::sFloatSpaced) - }, { + } }, + { lineHeight, { new Label(descLength, *itxs++), deadzoneSL = new Slider(1.f, World::sets()->getDeadzone(), 0, Settings::axisLimit, &Program::eventSetDeadzoneSL, nullptr, nullptr, makeTooltip(tipDeadzon)), deadzoneLE = new LabelEdit(dznum.length + LabelEdit::caretWidth, toStr(World::sets()->getDeadzone()), &Program::eventSetDeadzoneLE, nullptr, nullptr, makeTooltip(tipDeadzon), LabelEdit::TextType::uInt) - } }; + } } }; sizet lcnt = txs.size(); vector lns(lcnt + 1 + bnames.size() + 2); for (sizet i = 0; i < lcnt; ++i) - lns[i] = new Layout(lineHeight, std::move(lx[i]), Direction::right); + lns[i] = new Layout(lx[i].first, std::move(lx[i].second), Direction::right); lns[lcnt] = new Widget(0); limitLine = static_cast(lns[3]); @@ -758,6 +808,18 @@ Widget* ProgSettings::createLimitEdit() { return new Widget(); } +string ProgSettings::comboboxify(const char* name) { + string str = name; + str[0] = toupper(str[0]); + std::replace(str.begin() + 1, str.end(), '_', ' '); + return str; +} + +string ProgSettings::decomboboxify(string name) { + std::replace(name.begin() + 1, name.end(), ' ', '_'); + return name; +} + // PROG SEARCH DIR void ProgSearchDir::eventEscape() { diff --git a/src/prog/progs.h b/src/prog/progs.h index c9a280e2..c68d434a 100644 --- a/src/prog/progs.h +++ b/src/prog/progs.h @@ -57,6 +57,7 @@ class ProgState { virtual void eventNextDir() {} virtual void eventPrevDir() {} virtual void eventFullscreen(); + virtual void eventMultiFullscreen(); virtual void eventHide(); void eventBoss(); void eventRefresh(); @@ -74,8 +75,8 @@ class ProgState { static Rect calcTextContextRect(const vector& items, ivec2 pos, ivec2 size, int margin); protected: template static int findMaxLength(T pos, T end, int height); - SDL_Texture* makeTooltip(const char* str); - SDL_Texture* makeTooltipL(const char* str); + Texture* makeTooltip(const char* str); + Texture* makeTooltipL(const char* str); bool eventCommonEscape(); // returns true if something happened private: @@ -188,7 +189,7 @@ class ProgSettings : public ProgState { Slider* deadzoneSL; LabelEdit* deadzoneLE; private: - CheckBox* fullscreen; + ComboBox* screen; CheckBox* showHidden; public: @@ -196,12 +197,16 @@ class ProgSettings : public ProgState { void eventEscape() final; void eventFullscreen() final; + void eventMultiFullscreen() final; void eventHide() final; void eventFileDrop(const fs::path& file) final; RootLayout* createLayout() final; Widget* createLimitEdit(); + static string decomboboxify(string name); +private: + static string comboboxify(const char* name); }; class ProgSearchDir : public ProgState { diff --git a/src/utils/layouts.cpp b/src/utils/layouts.cpp index 4948ca98..7dcca695 100644 --- a/src/utils/layouts.cpp +++ b/src/utils/layouts.cpp @@ -20,52 +20,74 @@ Layout::~Layout() { clearWidgets(); } -void Layout::drawSelf() const { - for (Widget* it : widgets) - it->drawSelf(); +void Layout::drawSelf(const Rect& view) const { + for (const Widget* it : widgets) + it->drawSelf(view); +} + +void Layout::drawAddr(const Rect& view) const { + World::drawSys()->drawLayoutAddr(this, view); } void Layout::onResize() { + calculateWidgetPositions(); + for (Widget* it : widgets) + it->onResize(); +} + +void Layout::tick(float dSec) { + for (Widget* it : widgets) + it->tick(dSec); +} + +void Layout::postInit() { + calculateWidgetPositions(); + for (Widget* it : widgets) + it->postInit(); +} + +void Layout::calculateWidgetPositions() { // get amount of space for widgets with percent size and get total sum int vi = direction.vertical(); int pad = margin ? spacing : 0; ivec2 wsiz = size() - pad * 2; + vector pixSizes(widgets.size()); int space = wsiz[vi] - (widgets.size() - 1) * spacing; float total = 0; - for (Widget* it : widgets) { - if (it->getRelSize().usePix) - space -= it->getRelSize().pix; - else - total += it->getRelSize().prc; - } + for (sizet i = 0; i < widgets.size(); ++i) + switch (const Size& siz = widgets[i]->getRelSize(); siz.mod) { + case Size::rela: + total += siz.prc; + break; + case Size::pixv: + pixSizes[i] = siz.pix; + space -= std::min(pixSizes[i], space); + break; + case Size::calc: + pixSizes[i] = siz(widgets[i]); + space -= std::min(pixSizes[i], space); + } // calculate positions for each widget and set last poss element to end position of the last widget ivec2 pos(pad); for (sizet i = 0; i < widgets.size(); ++i) { positions[i] = pos; - pos[vi] += (widgets[i]->getRelSize().usePix ? widgets[i]->getRelSize().pix : int(widgets[i]->getRelSize().prc * float(space) / total)) + spacing; + if (const Size& siz = widgets[i]->getRelSize(); siz.mod != Size::rela) + pos[vi] += pixSizes[i] + spacing; + else if (float val = siz.prc * float(space); val != 0.f) + pos[vi] += int(val / total) + spacing; } positions.back() = vswap(wsiz[!vi], pos[vi], !vi); - - // do the same for children - for (Widget* it : widgets) - it->onResize(); -} - -void Layout::tick(float dSec) { - for (Widget* it : widgets) - it->tick(dSec); } -void Layout::postInit() { - onResize(); +void Layout::onMouseMove(ivec2 mPos, ivec2 mMov) { for (Widget* it : widgets) - it->postInit(); + it->onMouseMove(mPos, mMov); } -void Layout::onMouseMove(ivec2 mPos, ivec2 mMov) { +void Layout::onDisplayChange() { for (Widget* it : widgets) - it->onMouseMove(mPos, mMov); + it->onDisplayChange(); } bool Layout::navSelectable() const { @@ -228,11 +250,16 @@ ivec2 RootLayout::position() const { } ivec2 RootLayout::size() const { - return World::drawSys()->viewport().size(); + return World::drawSys()->getViewRes(); } Rect RootLayout::frame() const { - return World::drawSys()->viewport(); + return Rect(ivec2(0), World::drawSys()->getViewRes()); +} + +void RootLayout::setSize(const Size& size) { + relSize = size; + onResize(); } // POPUP @@ -244,17 +271,17 @@ Popup::Popup(const svec2& size, vector&& children, Widget* first, Color sizeY(size.y) {} -void Popup::drawSelf() const { - World::drawSys()->drawPopup(this); +void Popup::drawSelf(const Rect& view) const { + World::drawSys()->drawPopup(this, view); } ivec2 Popup::position() const { - return (World::drawSys()->viewport().size() - size()) / 2; + return (World::drawSys()->getViewRes() - size()) / 2; } ivec2 Popup::size() const { - vec2 res = World::drawSys()->viewport().size(); - return ivec2(relSize.usePix ? relSize.pix : int(relSize.prc * res.x), sizeY.usePix ? sizeY.pix : int(sizeY.prc * res.y)); + ivec2 res = World::drawSys()->getViewRes(); + return ivec2(sizeToPixAbs(relSize, res.x), sizeToPixAbs(sizeY, res.y)); } // OVERLAY @@ -267,13 +294,13 @@ Overlay::Overlay(const svec2& position, const svec2& size, const svec2& activati {} ivec2 Overlay::position() const { - vec2 res = World::drawSys()->viewport().size(); - return ivec2(pos.x.usePix ? pos.x.pix : int(pos.x.prc * res.x), pos.y.usePix ? pos.y.pix : int(pos.y.prc * res.y)); + ivec2 res = World::drawSys()->getViewRes(); + return ivec2(sizeToPixAbs(pos.x, res.x), sizeToPixAbs(pos.y, res.y)); } Rect Overlay::actRect() const { - vec2 res = World::drawSys()->viewport().size(); - return Rect(actPos.x.usePix ? actPos.x.pix : int(actPos.x.prc * res.x), actPos.y.usePix ? actPos.y.pix : int(actPos.y.prc * res.y), actSize.x.usePix ? actSize.x.pix : int(actSize.x.prc * res.x), actSize.y.usePix ? actSize.y.pix : int(actSize.y.prc * res.y)); + ivec2 res = World::drawSys()->getViewRes(); + return Rect(sizeToPixAbs(actPos.x, res.x), sizeToPixAbs(actPos.y, res.y), sizeToPixAbs(actSize.x, res.x), sizeToPixAbs(actSize.y, res.y)); } // CONTEXT @@ -292,8 +319,8 @@ void Context::onResize() { } ivec2 Context::position() const { - vec2 res = World::drawSys()->viewport().size(); - return ivec2(pos.x.usePix ? pos.x.pix : int(pos.x.prc * res.x), pos.y.usePix ? pos.y.pix : int(pos.y.prc * res.y)); + ivec2 res = World::drawSys()->getViewRes(); + return ivec2(sizeToPixAbs(pos.x, res.x), sizeToPixAbs(pos.y, res.y)); } void Context::setRect(const Rect& rct) { @@ -304,8 +331,8 @@ void Context::setRect(const Rect& rct) { // SCROLL AREA -void ScrollArea::drawSelf() const { - World::drawSys()->drawScrollArea(this); +void ScrollArea::drawSelf(const Rect& view) const { + World::drawSys()->drawScrollArea(this, view); } void ScrollArea::onResize() { @@ -354,7 +381,7 @@ void ScrollArea::onDrag(ivec2 mPos, ivec2 mMov) { void ScrollArea::onUndrag(uint8 mBut) { if (mBut == SDL_BUTTON_LEFT) { - if (!World::scene()->cursorInClickRange(mousePos(), mBut) && !draggingSlider) + if (!World::scene()->cursorInClickRange(World::winSys()->mousePos(), mBut) && !draggingSlider) motion = World::inputSys()->getMouseMove() * vswap(0, -1, direction.horizontal()); draggingSlider = false; @@ -536,11 +563,12 @@ TileBox::TileBox(const Size& size, vector&& children, int childHeight, wheight(childHeight) {} -void TileBox::onResize() { +void TileBox::calculateWidgetPositions() { + positions[0] = ivec2(0); int wsiz = size()[direction.horizontal()] - Slider::barSize; - ivec2 pos(0); - for (sizet i = 0; i < widgets.size(); ++i) { - if (int end = pos.x + widgets[i]->getRelSize().pix; end > wsiz && i && positions[i - 1].y == pos.y) { + ivec2 pos(!widgets.empty() ? widgets[0]->sizeToPixAbs(widgets[0]->getRelSize(), wsiz) + spacing : 0, 0); + for (sizet i = 1; i < widgets.size(); ++i) { + if (int end = pos.x + widgets[i]->sizeToPixAbs(widgets[i]->getRelSize(), wsiz); end > wsiz && positions[i - 1].y == pos.y) { pos = ivec2(0, pos.y + wheight + spacing); positions[i] = pos; pos.x += widgets[i]->getRelSize().pix + spacing; @@ -551,9 +579,6 @@ void TileBox::onResize() { } positions.back() = ivec2(wsiz, pos.y + wheight) + spacing; listPos = min(listPos, listLim()); - - for (Widget* it : widgets) - it->onResize(); } void TileBox::navSelectNext(sizet id, int mid, Direction dir) { @@ -617,7 +642,7 @@ int TileBox::wgtREnd(sizet id) const { // READER BOX -ReaderBox::ReaderBox(const Size& size, vector&& imgs, Direction dir, float fzoom, int space, bool pad) : +ReaderBox::ReaderBox(const Size& size, vector>&& imgs, Direction dir, float fzoom, int space, bool pad) : ScrollArea(size, {}, dir, Select::none, space, pad), zoom(fzoom) { @@ -625,26 +650,26 @@ ReaderBox::ReaderBox(const Size& size, vector&& imgs, Direction dir, fl } ReaderBox::~ReaderBox() { - for (Texture& it : pics) - SDL_DestroyTexture(it.tex); + for (pair& it : pics) + it.second->free(); } -void ReaderBox::drawSelf() const { - World::drawSys()->drawReaderBox(this); +void ReaderBox::drawSelf(const Rect& view) const { + World::drawSys()->drawReaderBox(this, view); } void ReaderBox::onResize() { // figure out the width of the list int hi = direction.horizontal(); int maxRSiz = size()[hi]; - for (const Texture& it : pics) - if (int rsiz = int(float(it.res()[hi]) * zoom); rsiz > maxRSiz) + for (const pair& it : pics) + if (int rsiz = int(float(it.second->getRes()[hi]) * zoom); rsiz > maxRSiz) maxRSiz = rsiz; // set position of each picture int rpos = 0; for (sizet i = 0; i < widgets.size(); ++i) { - ivec2 psz = vec2(pics[i].res()) * zoom; + ivec2 psz = vec2(pics[i].second->getRes()) * zoom; positions[i] = vswap((maxRSiz - psz[hi]) / 2, rpos, hi); rpos += psz[!hi]; } @@ -673,7 +698,7 @@ void ReaderBox::postInit() { // scroll down to opened picture if it exists, otherwise start at beginning string file = World::browser()->getCurFile().u8string(); - if (size_t id = std::find_if(pics.begin(), pics.end(), [&file](const Texture& it) -> bool { return it.name == file; }) - pics.begin(); id < pics.size()) { + if (size_t id = std::find_if(pics.begin(), pics.end(), [&file](const pair& it) -> bool { return it.first == file; }) - pics.begin(); id < pics.size()) { if (direction.positive()) scrollToWidgetPos(id); else @@ -692,9 +717,9 @@ void ReaderBox::onMouseMove(ivec2 mPos, ivec2 mMov) { } } -void ReaderBox::initWidgets(vector&& imgs) { - for (Texture& it : pics) - SDL_DestroyTexture(it.tex); +void ReaderBox::initWidgets(vector>&& imgs) { + for (pair& it : pics) + it.second->free(); clearWidgets(); pics = std::move(imgs); @@ -704,16 +729,20 @@ void ReaderBox::initWidgets(vector&& imgs) { if (direction.negative()) std::reverse(pics.begin(), pics.end()); for (sizet i = 0; i < pics.size(); ++i) { - widgets[i] = new Picture(0, false, pics[i].tex, 0); + widgets[i] = new Picture(0, false, pics[i].second, 0); widgets[i]->setParent(this, i); } } -void ReaderBox::setWidgets(vector&& imgs) { +void ReaderBox::setWidgets(vector>&& imgs) { initWidgets(std::move(imgs)); postInit(); } +bool ReaderBox::showBar() const { + return barRect().contain(World::winSys()->mousePos()) || draggingSlider; +} + void ReaderBox::setZoom(float factor) { ivec2 sh = size() / 2; zoom *= factor; @@ -731,7 +760,7 @@ ivec2 ReaderBox::wgtPosition(sizet id) const { } ivec2 ReaderBox::wgtSize(sizet id) const { - return vec2(pics[id].res()) * zoom; + return vec2(pics[id].second->getRes()) * zoom; } ivec2 ReaderBox::listSize() const { diff --git a/src/utils/layouts.h b/src/utils/layouts.h index c167ef8c..bb299250 100644 --- a/src/utils/layouts.h +++ b/src/utils/layouts.h @@ -27,12 +27,13 @@ class Layout : public Widget { Layout(const Size& size = Size(), vector&& children = vector(), Direction dir = defaultDirection, Select select = Select::none, int space = defaultItemSpacing, bool pad = false); ~Layout() override; - void drawSelf() const override; + void drawSelf(const Rect& view) const override; + void drawAddr(const Rect& view) const override; void onResize() override; void tick(float dSec) override; void postInit() override; - void onMouseMove(ivec2 mPos, ivec2 mMov) override; + void onDisplayChange() override; void onNavSelect(Direction) override {} bool navSelectable() const override; @@ -50,10 +51,12 @@ class Layout : public Widget { void selectWidget(sizet id); void deselectWidget(sizet id); int getSpacing() const; + bool isVertical() const; protected: void initWidgets(vector&& wgts); void clearWidgets(); + virtual void calculateWidgetPositions(); virtual ivec2 listSize() const; void navSelectWidget(sizet id, int mid, Direction dir); @@ -85,6 +88,10 @@ inline int Layout::getSpacing() const { return spacing; } +inline bool Layout::isVertical() const { + return direction.vertical(); +} + // top level layout class RootLayout : public Layout { public: @@ -94,6 +101,7 @@ class RootLayout : public Layout { ivec2 position() const override; ivec2 size() const override; Rect frame() const override; + void setSize(const Size& size) override; }; // layout with background with free position/size (shouldn't have a parent) @@ -108,7 +116,7 @@ class Popup : public RootLayout { Popup(const svec2& size = svec2(0), vector&& children = vector(), Widget* first = nullptr, Color background = Color::normal, Direction dir = defaultDirection, int space = defaultItemSpacing, bool pad = true); ~Popup() override = default; - void drawSelf() const override; + void drawSelf(const Rect& view) const override; ivec2 position() const override; ivec2 size() const override; @@ -167,7 +175,7 @@ class ScrollArea : public Layout { using Layout::Layout; ~ScrollArea() override = default; - void drawSelf() const override; + void drawSelf(const Rect& view) const override; void onResize() override; void tick(float dSec) override; void postInit() override; @@ -227,12 +235,12 @@ class TileBox : public ScrollArea { TileBox(const Size& size = Size(), vector&& children = vector(), int childHeight = defaultItemHeight, Direction dir = defaultDirection, Select select = Select::none, int space = defaultItemSpacing, bool pad = false); ~TileBox() final = default; - void onResize() final; - void navSelectNext(sizet id, int mid, Direction dir) final; void navSelectFrom(int mid, Direction dir) final; ivec2 wgtSize(sizet id) const final; +protected: + void calculateWidgetPositions() final; private: void scanVertically(sizet id, int mid, Direction dir); @@ -250,22 +258,22 @@ class ReaderBox : public ScrollArea { static constexpr float menuHideTimeout = 3.f; static inline const string emptyFile; - vector pics; + vector> pics; float cursorTimer = menuHideTimeout; // time left until cursor/overlay disappears float zoom; bool countDown = true; // whether to decrease cursorTimer until cursor hide public: - ReaderBox(const Size& size = Size(), vector&& imgs = vector(), Direction dir = defaultDirection, float fzoom = Settings::defaultZoom, int space = Settings::defaultSpacing, bool pad = false); + ReaderBox(const Size& size = Size(), vector>&& imgs = vector>(), Direction dir = defaultDirection, float fzoom = Settings::defaultZoom, int space = Settings::defaultSpacing, bool pad = false); ~ReaderBox() final; - void drawSelf() const final; + void drawSelf(const Rect& view) const final; void onResize() final; void tick(float dSec) final; void postInit() final; void onMouseMove(ivec2 mPos, ivec2 mMov) final; - void setWidgets(vector&& imgs); + void setWidgets(vector>&& imgs); bool showBar() const; float getZoom() const; void setZoom(float factor); @@ -277,28 +285,24 @@ class ReaderBox : public ScrollArea { ivec2 wgtSize(sizet id) const final; private: - void initWidgets(vector&& imgs); + void initWidgets(vector>&& imgs); ivec2 listSize() const final; int wgtRPos(sizet id) const final; int wgtREnd(sizet id) const final; }; -inline bool ReaderBox::showBar() const { - return barRect().contain(mousePos()) || draggingSlider; -} - inline float ReaderBox::getZoom() const { return zoom; } inline const string& ReaderBox::firstPage() const { - return !pics.empty() ? pics.front().name : emptyFile; + return !pics.empty() ? pics.front().first : emptyFile; } inline const string& ReaderBox::lastPage() const { - return !pics.empty() ? pics.back().name : emptyFile; + return !pics.empty() ? pics.back().first : emptyFile; } inline const string& ReaderBox::curPage() const { - return !pics.empty() ? pics[direction.positive() ? visibleWidgets().x : visibleWidgets().y - 1].name : emptyFile; + return !pics.empty() ? pics[direction.positive() ? visibleWidgets().x : visibleWidgets().y - 1].first : emptyFile; } diff --git a/src/utils/settings.cpp b/src/utils/settings.cpp index 2392c830..9a277e46 100644 --- a/src/utils/settings.cpp +++ b/src/utils/settings.cpp @@ -98,6 +98,12 @@ void Binding::reset(Type newType) { setJbutton(8); setGbutton(SDL_CONTROLLER_BUTTON_BACK); break; + case Type::multiFullscreen: + bcall = &ProgState::eventMultiFullscreen; + setKey(SDL_SCANCODE_G); + setJbutton(9); + setGbutton(SDL_CONTROLLER_BUTTON_START); + break; case Type::hide: bcall = &ProgState::eventHide; setKey(SDL_SCANCODE_H); @@ -215,14 +221,14 @@ void Binding::setGaxis(SDL_GameControllerAxis axis, bool positive) { // PICTURE LIMIT PicLim::PicLim(Type ltype, uptrt cnt) : - type(ltype), count(cnt), - size(defaultSize()) + size(defaultSize()), + type(ltype) {} void PicLim::set(string_view str) { vector elems = getWords(str); - type = !elems.empty() ? strToEnum(names, elems[0], Type::none) : Type::none; + type = !elems.empty() ? strToEnum(names, elems[0], Type::none) : Type::none; count = elems.size() > 1 ? toCount(elems[1]) : defaultCount; size = elems.size() > 2 ? toSize(elems[2]) : defaultSize(); } @@ -287,22 +293,33 @@ const fs::path& Settings::setDirLib(const fs::path& drc, const fs::path& dirSets if (dirLib = dirSets / defaultDirLib; !fs::is_directory(dirLib)) fs::create_directories(dirLib); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logError(err.what()); dirLib = dirSets / defaultDirLib; } return dirLib; } -pair Settings::getRendererInfo() { - SDL_RendererInfo info; - for (int i = 0; i < SDL_GetNumRenderDrivers(); ++i) - if (!SDL_GetRenderDriverInfo(i, &info) && info.name == renderer) - return pair(i, info.flags); +umap Settings::displayArrangement() { + ivec2 origin(INT_MAX); + umap dsps; + for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i) + if (Rect rect; !SDL_GetDisplayBounds(i, &rect)) { + dsps.emplace(i, rect); + origin = glm::min(origin, rect.pos()); + } + for (auto& [id, rect] : dsps) + rect.pos() -= origin; + return dsps; +} - if (!SDL_GetRenderDriverInfo(0, &info)) { - renderer = info.name; - return pair(0, info.flags); - } - renderer.clear(); - return pair(-1, 0); +void Settings::unionDisplays() { + umap dsps = displayArrangement(); + for (umap::iterator it = displays.begin(); it != displays.end(); ++it) + if (!dsps.count(it->first)) + displays.erase(it); + for (umap::const_iterator ds = dsps.begin(); ds != dsps.end(); ++ds) + if (umap::iterator it = displays.find(ds->first); it != displays.end() && it->second.size() != ds->second.size()) + displays.erase(it); + if (displays.empty()) + displays = std::move(dsps); } diff --git a/src/utils/settings.h b/src/utils/settings.h index b4adf924..1f8ff18a 100644 --- a/src/utils/settings.h +++ b/src/utils/settings.h @@ -28,10 +28,10 @@ class Direction { right }; static constexpr array names = { - "Up", - "Down", - "Left", - "Right" + "up", + "down", + "left", + "right" }; private: @@ -92,6 +92,7 @@ class Binding { nextDir, prevDir, fullscreen, + multiFullscreen, hide, boss, refresh, @@ -124,6 +125,7 @@ class Binding { "next directory", "prev directory", "fullscreen", + "multi fullscreen", "show hidden", "boss", "refresh", @@ -356,13 +358,13 @@ class PicLim { 'G' }; - Type type; -private: - uptrt count, size; // size in bytes - static constexpr uptrt defaultCount = 128; +private: + uptrt count, size; // size in bytes public: + Type type; + PicLim(Type ltype = Type::none, uptrt cnt = defaultCount); uptrt getCount() const; @@ -407,21 +409,15 @@ inline string PicLim::memoryString(uptrt num) { class Settings { public: - static constexpr float defaultZoom = 1.f; - static constexpr int defaultSpacing = 10; - static constexpr int axisLimit = SHRT_MAX + 1; - static constexpr char defaultFont[] = "BrisaSans"; - static constexpr char defaultDirLib[] = "library"; - - static constexpr array defaultColors = { - SDL_Color{ 10, 10, 10, 255 }, // background - SDL_Color{ 90, 90, 90, 255 }, // normal - SDL_Color{ 60, 60, 60, 255 }, // dark - SDL_Color{ 120, 120, 120, 255 }, // light - SDL_Color{ 105, 105, 105, 255 }, // select - SDL_Color{ 75, 75, 75, 255 }, // tooltip - SDL_Color{ 210, 210, 210, 255 }, // text - SDL_Color{ 210, 210, 210, 255 } // texture + static constexpr array defaultColors = { + vec4(0.04f, 0.04f, 0.04f, 1.f), // background + vec4(0.35f, 0.35f, 0.35f, 1.f), // normal + vec4(0.24f, 0.24f, 0.24f, 1.f), // dark + vec4(0.47f, 0.47f, 0.47f, 1.f), // light + vec4(0.41f, 0.41f, 0.41f, 1.f), // select + vec4(0.29f, 0.29f, 0.29f, 1.f), // tooltip + vec4(0.82f, 0.82f, 0.82f, 1.f), // text + vec4(0.82f, 0.82f, 0.82f, 1.f) // texture }; static constexpr array colorNames = { "background", @@ -434,23 +430,77 @@ class Settings { "texture" }; - bool maximized = false; - bool fullscreen = false; - bool showHidden = false; - Direction direction = Direction::down; + enum class Screen : uint8 { + windowed, + fullscreen, + multiFullscreen + }; + static constexpr array screenModeNames = { + "windowed", + "fullscreen", + "multi_fullscreen" + }; + + enum class VSync : int8 { + adaptive = -1, + immediate, + synchronized + }; + static constexpr array vsyncNames = { // add 1 to vsync value to get name + "adaptive", + "immediate", + "synchronized" + }; + + enum class Renderer : uint8 { +#ifdef WITH_DIRECTX + directx, +#endif + opengl + }; + static constexpr array rendererNames = { +#ifdef WITH_DIRECTX + "DirectX 11", +#endif +#ifdef OPENGLES + "OpenGL ES 3.0" +#else + "OpenGL 3.0" +#endif + }; + + static constexpr float defaultZoom = 1.f; + static constexpr int defaultSpacing = 10; + static constexpr int axisLimit = SHRT_MAX + 1; + static constexpr Screen defaultScreenMode = Screen::windowed; + static constexpr VSync defaultVSync = VSync::synchronized; + static constexpr Direction::Dir defaultDirection = Direction::down; + static constexpr Renderer defaultRenderer = Renderer::opengl; + static constexpr char defaultFont[] = "BrisaSans"; + static constexpr char defaultDirLib[] = "library"; + + string font = defaultFont; +private: + string theme; + fs::path dirLib; +public: + umap displays; PicLim picLim; + ivec2 resolution = ivec2(800, 600); + vec2 scrollSpeed = vec2(1600.f, 1600.f); float zoom = defaultZoom; int spacing = defaultSpacing; - ivec2 resolution = { 800, 600 }; - string renderer; - string font = defaultFont; - vec2 scrollSpeed = { 1600.f, 1600.f }; private: int deadzone = 256; - string theme; - fs::path dirLib; - public: + bool maximized = false; + Screen screen = defaultScreenMode; + bool showHidden = false; + Direction direction = defaultDirection; + VSync vsync = defaultVSync; + Renderer renderer = defaultRenderer; + bool gpuSelecting = false; + Settings(const fs::path& dirSets, vector&& themes); const string& getTheme() const; @@ -458,7 +508,8 @@ class Settings { const fs::path& getDirLib() const; const fs::path& setDirLib(const fs::path& drc, const fs::path& dirSets); - pair getRendererInfo(); + static umap displayArrangement(); + void unionDisplays(); string scrollSpeedString() const; int getDeadzone() const; void setDeadzone(int val); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 242febbb..419fa011 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -3,25 +3,20 @@ #include #endif -// GENERAL - -Rect Rect::crop(const Rect& rect) { - Rect isct; - if (!SDL_IntersectRect(this, &rect, &isct)) - return *this = Rect(0); - - ivec2 te = end(), ie = isct.end(); - Rect crop; - crop.x = isct.x > x ? isct.x - x : 0; - crop.y = isct.y > y ? isct.y - y : 0; - crop.w = ie.x < te.x ? te.x - ie.x + crop.x : 0; - crop.h = ie.y < te.y ? te.y - ie.y + crop.y : 0; - *this = isct; - return crop; +constexpr int Rect::operator[](sizet i) const { + switch (i) { + case 0: + return x; + case 1: + return y; + case 2: + return w; + case 3: + return h; + } + throw std::out_of_range("Rect index " + toStr(i) + " out of range" + linend); } -// FILES AND STRINGS - bool isDriveLetter(const fs::path& path) { if (const fs::path::value_type* p = path.c_str(); isalpha(p[0]) && p[1] == ':') { for (p += 2; isDsep(*p); ++p); @@ -90,6 +85,17 @@ vector getWords(string_view str) { return words; } +string currentDateTimeStr(char ts, char sep, char ds) { + time_t rawt = time(nullptr); + tm tim; +#ifdef _WIN32 + localtime_s(&tim, &rawt); +#else + localtime_r(&rawt, &tim); +#endif + return toStr(tim.tm_year + 1900) + ds + toStr(tim.tm_mon, 2) + ds + toStr(tim.tm_mday, 2) + sep + toStr(tim.tm_hour, 2) + ts + toStr(tim.tm_min, 2) + ts + toStr(tim.tm_sec, 2); +} + #ifdef _WIN32 string swtos(wstring_view src) { string dst; diff --git a/src/utils/utils.h b/src/utils/utils.h index dbe2a4d6..05c99ac8 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ using std::array; using std::pair; using std::string; using std::string_view; +using std::tuple; using std::vector; using std::wstring; using std::wstring_view; @@ -57,6 +59,7 @@ template using uset = std::unordered_set; using sizet = size_t; using pdift = ptrdiff_t; +using iptrt = intptr_t; using uptrt = uintptr_t; using pairStr = pair; using pairPath = pair; @@ -64,6 +67,10 @@ using mapFiles = umap>; using glm::vec2; using glm::ivec2; +using glm::uvec2; +using glm::vec4; +using glm::u8vec4; +using glm::ivec4; using mvec2 = glm::vec<2, sizet, glm::defaultp>; // forward declarations @@ -93,7 +100,9 @@ class Scene; class ScrollArea; class Settings; class Slider; +class Texture; class Widget; +class WindowArranger; // events @@ -195,17 +204,6 @@ string operator+(std::basic_string_view a, const std::basic_string& b) { return r; } -inline ivec2 texSize(SDL_Texture* tex) { - ivec2 s; - return !SDL_QueryTexture(tex, nullptr, nullptr, &s.x, &s.y) ? s : ivec2(0); -} - -inline ivec2 mousePos() { - ivec2 p; - SDL_GetMouseState(&p.x, &p.y); - return p; -} - inline void pushEvent(UserCode code, void* data1 = nullptr, void* data2 = nullptr) { SDL_Event event; event.user = { SDL_USEREVENT, SDL_GetTicks(), 0, int32(code), data1, data2 }; @@ -218,16 +216,22 @@ struct Rect : SDL_Rect { constexpr Rect(int n); constexpr Rect(int px, int py, int sw, int sh); constexpr Rect(ivec2 pos, ivec2 size); + constexpr Rect(const ivec4& vec); + + int& operator[](sizet i); + constexpr int operator[](sizet i) const; + constexpr bool operator==(const Rect& rect) const; ivec2& pos(); constexpr ivec2 pos() const; ivec2& size(); constexpr ivec2 size() const; constexpr ivec2 end() const; + constexpr ivec4 toVec() const; bool contain(ivec2 point) const; - Rect crop(const Rect& rect); // crop rect so it fits in the frame (aka set rect to the area where they overlap) and return how much was cut off - Rect intersect(const Rect& rect) const; // same as above except it returns the overlap instead of the crop and it doesn't modify itself + bool overlap(const Rect& rect) const; + Rect intersect(const Rect& rect) const; }; constexpr Rect::Rect(int n) : @@ -242,6 +246,18 @@ constexpr Rect::Rect(ivec2 pos, ivec2 size) : SDL_Rect{ pos.x, pos.y, size.x, size.y } {} +constexpr Rect::Rect(const ivec4& vec) : + SDL_Rect{ vec.x, vec.y, vec.z, vec.w } +{} + +inline int& Rect::operator[](sizet i) { + return reinterpret_cast(this)[i]; +} + +constexpr bool Rect::operator==(const Rect& rect) const { + return x == rect.x && y == rect.y && w == rect.w && h == rect.h; +} + inline ivec2& Rect::pos() { return *reinterpret_cast(this); } @@ -262,34 +278,23 @@ constexpr ivec2 Rect::end() const { return pos() + size(); } +constexpr ivec4 Rect::toVec() const { + return ivec4(x, y, w, h); +} + inline bool Rect::contain(ivec2 point) const { return SDL_PointInRect(reinterpret_cast(&point), this); } +inline bool Rect::overlap(const Rect& rect) const { + return SDL_HasIntersection(this, &rect); +} + inline Rect Rect::intersect(const Rect& rect) const { Rect isct; return SDL_IntersectRect(this, &rect, &isct) ? isct : Rect(0); } -// reader picture -struct Texture { - string name; - SDL_Texture* tex; - - Texture(string&& tname = string(), SDL_Texture* texture = nullptr); - - ivec2 res() const; -}; - -inline Texture::Texture(string&& tname, SDL_Texture* texture) : - name(std::move(tname)), - tex(texture) -{} - -inline ivec2 Texture::res() const { - return texSize(tex); -} - // files and strings bool isDriveLetter(const fs::path& path); @@ -297,6 +302,7 @@ fs::path parentPath(const fs::path& path); string strEnclose(string_view str); vector strUnenclose(string_view str); vector getWords(string_view str); +string currentDateTimeStr(char ts = ':', char sep = ' ', char ds = '-'); inline bool isSpace(int c) { return (c > '\0' && c <= ' ') || c == 0x7F; @@ -308,13 +314,7 @@ inline bool notSpace(int c) { inline string_view trim(string_view str) { string_view::iterator pos = std::find_if(str.begin(), str.end(), notSpace); - return string_view(&*pos, std::find_if(str.rbegin(), std::make_reverse_iterator(pos), notSpace).base() - pos); -} - -inline string firstUpper(string_view str) { - string txt(str); - txt[0] = toupper(txt[0]); - return txt; + return string_view(str.data() + (pos - str.begin()), std::find_if(str.rbegin(), std::make_reverse_iterator(pos), notSpace).base() - pos); } inline bool isDsep(int c) { @@ -377,13 +377,15 @@ string toStr(T num, uint8 pad) { std::transform(buf.data(), res.ptr, buf.data(), toupper); uint8 len = res.ptr - buf.data(); - if (pad > len) { - pad = std::min(uint8(pad - len), uint8(buf.size())); - std::move_backward(buf.data(), res.ptr, buf.data() + pad); - std::fill_n(buf.begin(), pad, '0'); - len += pad; - } - return string(buf.data(), len); + if (len >= pad) + return string(buf.data(), res.ptr); + + string str; + str.resize(pad); + pad -= len; + std::fill_n(str.begin(), pad, '0'); + std::copy(buf.data(), res.ptr, str.begin() + pad); + return str; } template , int> = 0> @@ -442,7 +444,7 @@ T toVec(string_view str, typename T::value_type fill = typename T::value_type(0) for (glm::length_t i = 0; p < str.length() && i < vec.length(); ++i) { for (; p < str.length() && isSpace(str[p]); ++p); for (; p < str.length(); ++p) - if (std::from_chars_result res = std::from_chars(str.data() + p, str.data() + str.length(), vec[i], args...); res.ec == std::errc()) { + if (std::from_chars_result res = std::from_chars(str.data() + p, str.data() + str.length(), vec[i], args...); res.ec == std::errc(0)) { p = res.ptr - str.data(); break; } @@ -450,7 +452,7 @@ T toVec(string_view str, typename T::value_type fill = typename T::value_type(0) return vec; } -// container stuff +// other template T btom(bool b) { @@ -461,3 +463,17 @@ template glm::vec<2, T, Q> vswap(const T& x, const T& y, bool swap) { return swap ? glm::vec<2, T, Q>(y, x) : glm::vec<2, T, Q>(x, y); } + +template +void logInfo(A&&... args) { + std::ostringstream ss; + (ss << ... << std::forward(args)); + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s%s", ss.str().c_str(), linend.data()); +} + +template +void logError(A&&... args) { + std::ostringstream ss; + (ss << ... << std::forward(args)); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s%s", ss.str().c_str(), linend.data()); +} diff --git a/src/utils/widgets.cpp b/src/utils/widgets.cpp index 7f14c4c9..26f0640f 100644 --- a/src/utils/widgets.cpp +++ b/src/utils/widgets.cpp @@ -32,21 +32,42 @@ Rect Widget::frame() const { return parent->frame(); } +void Widget::setSize(const Size& size) { + relSize = size; + parent->onResize(); +} + void Widget::onNavSelect(Direction dir) { parent->navSelectNext(index, dir.vertical() ? center().x : center().y, dir); } +int Widget::sizeToPixAbs(const Size& siz, int res) const { + switch (siz.mod) { + case Size::rela: + return int(siz.prc * float(res)); + case Size::pixv: + return siz.pix; + case Size::calc: + return siz(this); + } + return 0; +} + // PICTURE -Picture::Picture(const Size& size, bool bg, SDL_Texture* texture, int margin) : +Picture::Picture(const Size& size, bool bg, const Texture* texture, int margin) : Widget(size), tex(texture), showBG(bg), texMargin(margin) {} -void Picture::drawSelf() const { - World::drawSys()->drawPicture(this); +void Picture::drawSelf(const Rect& view) const { + World::drawSys()->drawPicture(this, view); +} + +void Picture::drawAddr(const Rect& view) const { + World::drawSys()->drawPictureAddr(this, view); } Color Picture::color() const { @@ -60,7 +81,7 @@ Rect Picture::texRect() const { // BUTTON -Button::Button(const Size& size, PCall leftCall, PCall rightCall, PCall doubleCall, SDL_Texture* tip, bool bg, SDL_Texture* texture, int margin) : +Button::Button(const Size& size, PCall leftCall, PCall rightCall, PCall doubleCall, Texture* tip, bool bg, const Texture* texture, int margin) : Picture(size, bg, texture, margin), lcall(leftCall), rcall(rightCall), @@ -70,7 +91,7 @@ Button::Button(const Size& size, PCall leftCall, PCall rightCall, PCall doubleCa Button::~Button() { if (tooltip) - SDL_DestroyTexture(tooltip); + tooltip->free(); } void Button::onClick(ivec2, uint8 mBut) { @@ -104,10 +125,13 @@ Color Button::color() const { return Color::normal; } -Rect Button::tooltipRect(ivec2& tres) const { - tres = texSize(tooltip); - ivec2 view = World::drawSys()->viewport().size(); - Rect rct(mousePos() + ivec2(0, DrawSys::cursorHeight), tres + tooltipMargin * 2); +const Texture* Button::getTooltip() { + return tooltip; +} + +Rect Button::tooltipRect() const { + ivec2 view = World::drawSys()->getViewRes(); + Rect rct(World::winSys()->mousePos() + ivec2(0, DrawSys::cursorHeight), tooltip ? tooltip->getRes() + tooltipMargin * 2 : ivec2(0)); if (rct.x + rct.w > view.x) rct.x = view.x - rct.w; if (rct.y + rct.h > view.y) @@ -117,13 +141,13 @@ Rect Button::tooltipRect(ivec2& tres) const { // CHECK BOX -CheckBox::CheckBox(const Size& size, bool checked, PCall leftCall, PCall rightCall, PCall doubleCall, SDL_Texture* tip, bool bg, SDL_Texture* texture, int margin) : +CheckBox::CheckBox(const Size& size, bool checked, PCall leftCall, PCall rightCall, PCall doubleCall, Texture* tip, bool bg, const Texture* texture, int margin) : Button(size, leftCall, rightCall, doubleCall, tip, bg, texture, margin), on(checked) {} -void CheckBox::drawSelf() const { - World::drawSys()->drawCheckBox(this); +void CheckBox::drawSelf(const Rect& view) const { + World::drawSys()->drawCheckBox(this, view); } void CheckBox::onClick(ivec2 mPos, uint8 mBut) { @@ -140,15 +164,15 @@ Rect CheckBox::boxRect() const { // SLIDER -Slider::Slider(const Size& size, int value, int minimum, int maximum, PCall leftCall, PCall rightCall, PCall doubleCall, SDL_Texture* tip, bool bg, SDL_Texture* texture, int margin) : +Slider::Slider(const Size& size, int value, int minimum, int maximum, PCall leftCall, PCall rightCall, PCall doubleCall, Texture* tip, bool bg, const Texture* texture, int margin) : Button(size, leftCall, rightCall, doubleCall, tip, bg, texture, margin), val(value), vmin(minimum), vmax(maximum) {} -void Slider::drawSelf() const { - World::drawSys()->drawSlider(this); +void Slider::drawSelf(const Rect& view) const { + World::drawSys()->drawSlider(this, view); } void Slider::onClick(ivec2, uint8 mBut) { @@ -197,15 +221,15 @@ int Slider::sliderLim() const { // PROGRESS BAR -ProgressBar::ProgressBar(const Size& size, int value, int minimum, int maximum, bool bg, SDL_Texture* texture, int margin) : +ProgressBar::ProgressBar(const Size& size, int value, int minimum, int maximum, bool bg, const Texture* texture, int margin) : Picture(size, bg, texture, margin), val(value), vmin(minimum), vmax(maximum) {} -void ProgressBar::drawSelf() const { - World::drawSys()->drawProgressBar(this); +void ProgressBar::drawSelf(const Rect& view) const { + World::drawSys()->drawProgressBar(this, view); } Rect ProgressBar::barRect() const { @@ -216,7 +240,7 @@ Rect ProgressBar::barRect() const { // LABEL -Label::Label(const Size& size, string line, PCall leftCall, PCall rightCall, PCall doubleCall, SDL_Texture* tip, Alignment alignment, SDL_Texture* texture, bool bg, int lineMargin, int iconMargin) : +Label::Label(const Size& size, string line, PCall leftCall, PCall rightCall, PCall doubleCall, Texture* tip, Alignment alignment, const Texture* texture, bool bg, int lineMargin, int iconMargin) : Button(size, leftCall, rightCall, doubleCall, tip, bg, texture, iconMargin), text(std::move(line)), textMargin(lineMargin), @@ -224,11 +248,12 @@ Label::Label(const Size& size, string line, PCall leftCall, PCall rightCall, PCa {} Label::~Label() { - SDL_DestroyTexture(textTex); + if (textTex) + textTex->free(); } -void Label::drawSelf() const { - World::drawSys()->drawLabel(this); +void Label::drawSelf(const Rect& view) const { + World::drawSys()->drawLabel(this, view); } void Label::onResize() { @@ -249,6 +274,10 @@ void Label::setText(const string& str) { updateTextTex(); } +Rect Label::textRect() const { + return Rect(textPos(), textTex->getRes()); +} + Rect Label::textFrame() const { Rect rct = rect(); int ofs = textIconOffset(); @@ -258,8 +287,11 @@ Rect Label::textFrame() const { Rect Label::texRect() const { Rect rct = rect(); rct.h -= texMargin * 2; - ivec2 res = texSize(tex); - return Rect(rct.pos() + texMargin, ivec2(float(rct.h * res.x) / float(res.y), rct.h)); + return Rect(rct.pos() + texMargin, ivec2(float(rct.h * tex->getRes().x) / float(tex->getRes().y), rct.h)); +} + +int Label::textIconOffset() const { + return tex ? int(float(size().y * tex->getRes().x) / float(tex->getRes().y)) : 0; } ivec2 Label::textPos() const { @@ -268,29 +300,22 @@ ivec2 Label::textPos() const { return ivec2(pos.x + textIconOffset() + textMargin, pos.y); case Alignment::center: { int iofs = textIconOffset(); - return ivec2(pos.x + iofs + (size().x - iofs - texSize(textTex).x) / 2, pos.y); } + return ivec2(pos.x + iofs + (size().x - iofs - textTex->getRes().x) / 2, pos.y); } case Alignment::right: - return ivec2(pos.x + size().x - texSize(textTex).x - textMargin, pos.y); + return ivec2(pos.x + size().x - textTex->getRes().x - textMargin, pos.y); } throw std::runtime_error("Invalid alignment type: " + toStr(align)); } -int Label::textIconOffset() const { - if (tex) { - ivec2 res = texSize(tex); - return int(float(size().y * res.x) / float(res.y)); - } - return 0; -} - void Label::updateTextTex() { - SDL_DestroyTexture(textTex); + if (textTex) + textTex->free(); textTex = World::drawSys()->renderText(text, size().y); } -// SWITCH BOX +// COMBO BOX -ComboBox::ComboBox(const Size& size, string curOption, vector&& opts, PCall call, SDL_Texture* tip, Alignment alignment, SDL_Texture* texture, bool bg, int lineMargin, int iconMargin) : +ComboBox::ComboBox(const Size& size, string curOption, vector&& opts, PCall call, Texture* tip, Alignment alignment, const Texture* texture, bool bg, int lineMargin, int iconMargin) : Label(size, std::move(curOption), call, call, nullptr, tip, alignment, texture, bg, lineMargin, iconMargin), options(std::move(opts)), curOpt(std::min(sizet(std::find(options.begin(), options.end(), text) - options.begin()), options.size())) @@ -308,7 +333,7 @@ void ComboBox::setCurOpt(sizet id) { // LABEL EDIT -LabelEdit::LabelEdit(const Size& size, string line, PCall leftCall, PCall rightCall, PCall doubleCall, SDL_Texture* tip, TextType type, bool focusLossConfirm, SDL_Texture* texture, bool bg, int lineMargin, int iconMargin) : +LabelEdit::LabelEdit(const Size& size, string line, PCall leftCall, PCall rightCall, PCall doubleCall, Texture* tip, TextType type, bool focusLossConfirm, const Texture* texture, bool bg, int lineMargin, int iconMargin) : Label(size, std::move(line), leftCall, rightCall, doubleCall, tip, Alignment::left, texture, bg, lineMargin, iconMargin), unfocusConfirm(focusLossConfirm), textType(type), @@ -317,6 +342,11 @@ LabelEdit::LabelEdit(const Size& size, string line, PCall leftCall, PCall rightC cleanText(); } +void LabelEdit::drawTop(const Rect& view) const { + ivec2 ps = position(); + World::drawSys()->drawCaret(Rect(caretPos() + ps.x + textIconOffset() + textMargin, ps.y, caretWidth, size().y), frame(), view); +} + void LabelEdit::onClick(ivec2, uint8 mBut) { if (mBut == SDL_BUTTON_LEFT) { Rect rct = rect(); @@ -448,11 +478,6 @@ void LabelEdit::onTextReset() { setCPos(text.length()); } -Rect LabelEdit::caretRect() { - ivec2 ps = position(); - return { caretPos() + ps.x + textIconOffset() + textMargin, ps.y, caretWidth, size().y }; -} - void LabelEdit::setCPos(uint cp) { cpos = cp; if (int cl = caretPos(); cl < 0) @@ -461,8 +486,8 @@ void LabelEdit::setCPos(uint cp) { textOfs -= ce - sx; } -int LabelEdit::caretPos() { - return World::drawSys()->textLength(text.data(), cpos, size().y) + textOfs; +int LabelEdit::caretPos() const { + return World::drawSys()->textLength(text, cpos, size().y) + textOfs; } void LabelEdit::confirm() { @@ -648,7 +673,7 @@ void LabelEdit::cleanUFloatSpacedText() { // KEY GETTER -KeyGetter::KeyGetter(const Size& size, AcceptType type, Binding::Type binding, SDL_Texture* tip, Alignment alignment, SDL_Texture* texture, bool bg, int lineMargin, int iconMargin) : +KeyGetter::KeyGetter(const Size& size, AcceptType type, Binding::Type binding, Texture* tip, Alignment alignment, const Texture* texture, bool bg, int lineMargin, int iconMargin) : Label(size, bindingText(binding, type), nullptr, nullptr, nullptr, tip, alignment, texture, bg, lineMargin, iconMargin), acceptType(type), bindingType(binding) @@ -764,3 +789,235 @@ string KeyGetter::bindingText(Binding::Type binding, KeyGetter::AcceptType accep } return string(); } + +// WINDOW ARRANGER + +WindowArranger::Dsp::Dsp(const Rect& vdsp, bool on) : + full(vdsp), + active(on) +{} + +WindowArranger::WindowArranger(const Size& size, float baseScale, bool vertExp, PCall leftCall, PCall rightCall, Texture* tip, bool bg, const Texture* texture, int margin) : + Button(size, leftCall, rightCall, nullptr, tip, bg, texture, margin), + bscale(baseScale), + vertical(vertExp) +{ + calcDisplays(); +} + +WindowArranger::~WindowArranger() { + freeTextures(); +} + +void WindowArranger::freeTextures() { + for (auto& [id, dsp] : disps) + if (dsp.txt) + dsp.txt->free(); +} + +void WindowArranger::calcDisplays() { + umap all = Settings::displayArrangement(); + disps.reserve(all.size()); + totalDim = ivec2(0); + + const umap& sadisp = World::sets()->displays; + for (auto& [id, rect] : sadisp) { + disps.emplace(id, Dsp(rect, true)); + totalDim = glm::max(totalDim, rect.end()); + } + ivec2 border = vswap(totalDim[vertical], 0, vertical); + for (auto& [id, rect] : all) { + Rect dst = rect; + if (std::any_of(disps.begin(), disps.end(), [&dst](const pair& p) -> bool { return p.second.full.overlap(dst); })) { + dst.pos() = border; + border[vertical] += dst.size()[vertical]; + } + if (auto [it, ok] = disps.emplace(id, Dsp(dst, false)); ok) + totalDim = glm::max(totalDim, dst.end()); + } +} + +void WindowArranger::buildEntries() { + float scale = entryScale(size()); + for (auto& [id, dsp] : disps) { + dsp.rect = Rect(vec2(dsp.full.pos()) * scale, vec2(dsp.full.size()) * scale); + dsp.txt = World::drawSys()->renderText(toStr(id), dsp.rect.h); + } +} + +void WindowArranger::drawSelf(const Rect& view) const { + World::drawSys()->drawWindowArranger(this, view); +} + +void WindowArranger::drawTop(const Rect& view) const { + const Dsp& dsp = disps.at(dragging); + World::drawSys()->drawWaDisp(dragr, Color::light, dsp.txt ? Rect(dragr.pos() + (dragr.size() - dsp.txt->getRes()) / 2, dsp.txt->getRes()) : Rect(0), dsp.txt, frame(), view); +} + +void WindowArranger::onResize() { + freeTextures(); + buildEntries(); +} + +void WindowArranger::postInit() { + buildEntries(); +} + +void WindowArranger::onClick(ivec2 mPos, uint8 mBut) { + if (((mBut == SDL_BUTTON_LEFT && lcall) || (mBut == SDL_BUTTON_RIGHT && rcall)) && disps.size() > 1) { + selected = dispUnderPos(mPos); + if (umap::iterator it = disps.find(selected); it != disps.end()) { + it->second.active = !it->second.active; + World::prun(mBut == SDL_BUTTON_LEFT ? lcall : rcall, this); + } + } +} + +void WindowArranger::onMouseMove(ivec2 mPos, ivec2) { + selected = dispUnderPos(mPos); +} + +void WindowArranger::onHold(ivec2 mPos, uint8 mBut) { + if (mBut == SDL_BUTTON_LEFT && disps.size() > 1) { + dragging = dispUnderPos(mPos); + if (umap::iterator it = disps.find(dragging); it != disps.end()) { + World::scene()->setCapture(this); + dragr = Rect(it->second.rect.pos() + position() + winMargin, it->second.rect.size()); + } + } +} + +void WindowArranger::onDrag(ivec2 mPos, ivec2) { + dragr.pos() = mPos - disps.at(dragging).rect.size() / 2; + if (ivec2 spos = snapDrag(); spos.x >= 0 && spos.y >= 0) + dragr.pos() = ivec2(vec2(spos) * entryScale(size())) + position() + winMargin; +} + +void WindowArranger::onUndrag(uint8 mBut) { + if (mBut == SDL_BUTTON_LEFT) { + World::scene()->setCapture(nullptr); + + ivec2 spos = snapDrag(); + float scale = entryScale(size()); + Dsp& dsp = disps.find(dragging)->second; + dsp.full.pos() = spos.x >= 0 && spos.y >= 0 ? spos : ivec2(vec2(dragr.pos() - position() - winMargin) / scale); + glm::clamp(dsp.full.pos(), ivec2(0), totalDim); + totalDim = glm::max(totalDim, dsp.full.end()); + dsp.rect.pos() = vec2(dsp.full.pos()) * scale; + World::prun(lcall, this); + } +} + +ivec2 WindowArranger::snapDrag() const { + constexpr array, 24> snapRelationsOuter = { + pair(0, 2), pair(0, 5), pair(0, 7), // top left to top right, bottom left, bottom right + pair(1, 5), pair(1, 6), pair(1, 7), // top center to bottom left, bottom center, bottom right + pair(2, 0), pair(2, 5), pair(2, 7), // top right to top left, bottom left, bottom right + pair(3, 2), pair(3, 4), pair(3, 7), // left center to right top, right center, right bottom + pair(4, 0), pair(4, 3), pair(4, 5), // right center to left top, left center, left bottom + pair(5, 0), pair(5, 2), pair(5, 7), // bottom left to top left, top right, bottom right + pair(6, 0), pair(6, 1), pair(6, 2), // bottom center to top left, top center, top right + pair(7, 0), pair(7, 2), pair(7, 5) // bottom right to top left, top right, bottom left + }; + constexpr array, 8> snapRelationsInner = { + pair(0, 0), pair(1, 1), pair(2, 2), pair(3, 3), pair(4, 4), pair(5, 5), pair(6, 6), pair(7, 7) + }; + ivec2 (* const offsetCalc[8])(ivec2, ivec2) = { + [](ivec2, ivec2 pnt) -> ivec2 { return pnt; }, + [](ivec2 siz, ivec2 pnt) -> ivec2 { return ivec2(pnt.x - siz.x / 2, pnt.y); }, + [](ivec2 siz, ivec2 pnt) -> ivec2 { return ivec2(pnt.x - siz.x, pnt.y); }, + [](ivec2 siz, ivec2 pnt) -> ivec2 { return ivec2(pnt.x, pnt.y - siz.y / 2); }, + [](ivec2 siz, ivec2 pnt) -> ivec2 { return ivec2(pnt.x - siz.x, pnt.y - siz.y / 2); }, + [](ivec2 siz, ivec2 pnt) -> ivec2 { return ivec2(pnt.x, pnt.y - siz.y); }, + [](ivec2 siz, ivec2 pnt) -> ivec2 { return ivec2(pnt.x - siz.x / 2, pnt.y - siz.y); }, + [](ivec2 siz, ivec2 pnt) -> ivec2 { return pnt - siz; } + }; + + umap::const_iterator snapFrom = disps.find(dragging); + array snaps = getSnapPoints(Rect(vec2(dragr.pos() - position() - winMargin) / entryScale(size()), snapFrom->second.full.size())); + uint snapId; + ivec2 snapPnt; + float minDist = FLT_MAX; + for (umap::const_iterator it = disps.begin(); it != disps.end(); ++it) + if (it->first != dragging) + scanClosestSnapPoint(snapRelationsOuter, it->second.full, snaps, snapId, snapPnt, minDist); + scanClosestSnapPoint(snapRelationsInner, Rect(ivec2(0), totalDim), snaps, snapId, snapPnt, minDist); + return minDist <= float(std::min(snapFrom->second.full.w, snapFrom->second.full.h)) / 3.f + ? offsetCalc[snapId](snapFrom->second.full.size(), snapPnt) + : ivec2(INT_MIN); +} + +array WindowArranger::getSnapPoints(const Rect& rect) { + return { + rect.pos(), + ivec2(rect.x + rect.w / 2, rect.y), + ivec2(rect.end().x, rect.y), + ivec2(rect.x, rect.y + rect.h / 2), + ivec2(rect.end().x, rect.y + rect.h / 2), + ivec2(rect.x, rect.end().y), + ivec2(rect.x + rect.w / 2, rect.end().y), + rect.end() + }; +} + +template +void WindowArranger::scanClosestSnapPoint(const array, S>& relations, const Rect& rect, const array& snaps, uint& snapId, ivec2& snapPnt, float& minDist) { + array rpnts = getSnapPoints(rect); + for (auto [from, to] : relations) + if (float dist = glm::length(vec2(rpnts[to] - snaps[from])); dist < minDist) { + minDist = dist; + snapPnt = rpnts[to]; + snapId = from; + } +} + +void WindowArranger::onDisplayChange() { + freeTextures(); + disps.clear(); + calcDisplays(); + buildEntries(); +} + +bool WindowArranger::navSelectable() const { + return true; +} + +Color WindowArranger::color() const { + return Color::dark; +} + +const Texture* WindowArranger::getTooltip() { + return disps.count(selected) ? tooltip : nullptr; +} + +bool WindowArranger::draggingDisp(int id) const { + return id == dragging && World::scene()->getCapture() == this; +} + +int WindowArranger::dispUnderPos(ivec2 pnt) const { + ivec2 pos = position(); + for (const auto& [id, dsp] : disps) + if (offsetDisp(dsp.rect, pos).contain(pnt)) + return id; + return Renderer::singleDspId; +} + +float WindowArranger::entryScale(ivec2 siz) const { + int fsiz = siz[!vertical] - winMargin * 2; + return int(float(totalDim[!vertical]) * bscale) <= fsiz ? bscale : float(totalDim[!vertical]) / float(fsiz); +} + +tuple WindowArranger::dispRect(int id, const Dsp& dsp) const { + ivec2 offs = position() + winMargin; + Rect rct = Rect(dsp.rect.pos() + offs, dsp.rect.size()); + Color clr = id != selected || World::scene()->select != this || World::scene()->getCapture() != this ? dsp.active ? Color::light : Color::normal : Color::select; + return tuple(rct, clr, dsp.txt ? Rect(rct.pos() + (rct.size() - dsp.txt->getRes()) / 2, dsp.txt->getRes()) : Rect(0), dsp.txt); +} + +umap WindowArranger::getActiveDisps() const { + umap act; + for (auto& [id, dsp] : disps) + if (dsp.active) + act.emplace(id, dsp.full); + return act; +} diff --git a/src/utils/widgets.h b/src/utils/widgets.h index 93ed00b8..d0702a59 100644 --- a/src/utils/widgets.h +++ b/src/utils/widgets.h @@ -4,38 +4,44 @@ // size of a widget in pixels or relative to it's parent struct Size { + enum Mode : uint8 { + rela, // relative to parent size + pixv, // absolute pixel value + calc // use the calculation function + }; + union { - int pix; // use if type is pix - float prc; // use if type is prc + float prc; + int pix; + int (*cfn)(const Widget*); }; - bool usePix; + Mode mod; - Size(int pixels); - Size(float percent = 1.f); + constexpr Size(float percent = 1.f); + constexpr Size(int pixels); + constexpr Size(int (*calcul)(const Widget*)); - void set(int pixels); - void set(float percent); + int operator()(const Widget* wgt) const; }; using svec2 = glm::vec<2, Size, glm::defaultp>; -inline Size::Size(int pixels) : - pix(pixels), - usePix(true) +constexpr Size::Size(float percent) : + prc(percent), + mod(rela) {} -inline Size::Size(float percent) : - prc(percent), - usePix(false) +constexpr Size::Size(int pixels) : + pix(pixels), + mod(pixv) {} -inline void Size::set(int pixels) { - usePix = true; - pix = pixels; -} +constexpr Size::Size(int (*calcul)(const Widget*)) : + cfn(calcul), + mod(calc) +{} -inline void Size::set(float percent) { - usePix = false; - prc = percent; +inline int Size::operator()(const Widget* wgt) const { + return cfn(wgt); } // can be used as spacer @@ -49,7 +55,9 @@ class Widget { Widget(const Size& size = Size()); virtual ~Widget() = default; - virtual void drawSelf() const {} // calls appropriate drawing function(s) in DrawSys + virtual void drawSelf(const Rect&) const {} // calls appropriate drawing function(s) in DrawSys + virtual void drawTop(const Rect&) const {} + virtual void drawAddr(const Rect&) const {} virtual void onResize() {} // for updating values when window size changed virtual void tick(float) {} virtual void postInit() {} // gets called after parent is set and all set up @@ -68,6 +76,7 @@ class Widget { virtual void onGAxis(SDL_GameControllerAxis, bool) {} virtual void onCompose(string_view, uint) {} virtual void onText(string_view, uint) {} + virtual void onDisplayChange() {} virtual void onNavSelect(Direction dir); virtual bool navSelectable() const; virtual bool hasDoubleclick() const; @@ -82,6 +91,8 @@ class Widget { ivec2 center() const; Rect rect() const; // the rectangle that is the widget virtual Rect frame() const; // the rectangle to restrain a widget's visibility (in Widget it returns the parent's frame and if in Layout, it returns a frame for it's children) + virtual void setSize(const Size& size); + int sizeToPixAbs(const Size& siz, int res) const; }; inline Widget::Widget(const Size& size) : @@ -113,16 +124,17 @@ class Picture : public Widget { public: static constexpr int defaultIconMargin = 2; - SDL_Texture* tex; // doesn't get freed automatically + const Texture* tex; bool showBG; protected: int texMargin; public: - Picture(const Size& size = Size(), bool bg = true, SDL_Texture* texture = nullptr, int margin = defaultIconMargin); + Picture(const Size& size = Size(), bool bg = true, const Texture* texture = nullptr, int margin = defaultIconMargin); ~Picture() override = default; - void drawSelf() const override; + void drawSelf(const Rect& view) const override; + void drawAddr(const Rect& view) const override; virtual Color color() const; virtual Rect texRect() const; @@ -135,11 +147,10 @@ class Button : public Picture { protected: PCall lcall, rcall, dcall; -private: - SDL_Texture* tooltip; + Texture* tooltip; public: - Button(const Size& size = Size(), PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, SDL_Texture* tip = nullptr, bool bg = true, SDL_Texture* texture = nullptr, int margin = defaultIconMargin); + Button(const Size& size = Size(), PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, Texture* tip = nullptr, bool bg = true, const Texture* texture = nullptr, int margin = defaultIconMargin); ~Button() override; void onClick(ivec2 mPos, uint8 mBut) override; @@ -148,23 +159,19 @@ class Button : public Picture { bool hasDoubleclick() const override; Color color() const override; - SDL_Texture* getTooltip(); - Rect tooltipRect(ivec2& tres) const; + virtual const Texture* getTooltip(); + Rect tooltipRect() const; }; -inline SDL_Texture* Button::getTooltip() { - return tooltip; -} - // if you don't know what a checkbox is then I don't know what to tell ya class CheckBox : public Button { public: bool on; - CheckBox(const Size& size = Size(), bool checked = false, PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, SDL_Texture* tip = nullptr, bool bg = true, SDL_Texture* texture = nullptr, int margin = defaultIconMargin); + CheckBox(const Size& size = Size(), bool checked = false, PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, Texture* tip = nullptr, bool bg = true, const Texture* texture = nullptr, int margin = defaultIconMargin); ~CheckBox() final = default; - void drawSelf() const final; + void drawSelf(const Rect& view) const final; void onClick(ivec2 mPos, uint8 mBut) final; Rect boxRect() const; @@ -189,10 +196,10 @@ class Slider : public Button { int diffSliderMouse = 0; public: - Slider(const Size& size = Size(), int value = 0, int minimum = 0, int maximum = 255, PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, SDL_Texture* tip = nullptr, bool bg = true, SDL_Texture* texture = nullptr, int margin = defaultIconMargin); + Slider(const Size& size = Size(), int value = 0, int minimum = 0, int maximum = 255, PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, Texture* tip = nullptr, bool bg = true, const Texture* texture = nullptr, int margin = defaultIconMargin); ~Slider() final = default; - void drawSelf() const final; + void drawSelf(const Rect& view) const final; void onClick(ivec2 mPos, uint8 mBut) final; void onHold(ivec2 mPos, uint8 mBut) final; void onDrag(ivec2 mPos, ivec2 mMov) final; @@ -230,10 +237,10 @@ class ProgressBar : public Picture { int val, vmin, vmax; public: - ProgressBar(const Size& size = Size(), int value = 0, int minimum = 0, int maximum = 255, bool bg = true, SDL_Texture* texture = nullptr, int margin = defaultIconMargin); + ProgressBar(const Size& size = Size(), int value = 0, int minimum = 0, int maximum = 255, bool bg = true, const Texture* texture = nullptr, int margin = defaultIconMargin); ~ProgressBar() final = default; - void drawSelf() const final; + void drawSelf(const Rect& view) const final; int getVal() const; void setVal(int value); @@ -254,23 +261,24 @@ class Label : public Button { public: static constexpr int defaultTextMargin = 5; - SDL_Texture* textTex = nullptr; protected: string text; + Texture* textTex = nullptr; int textMargin; Alignment align; // text alignment public: - Label(const Size& size = Size(), string line = string(), PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, SDL_Texture* tip = nullptr, Alignment alignment = Alignment::left, SDL_Texture* texture = nullptr, bool bg = true, int lineMargin = defaultTextMargin, int iconMargin = defaultIconMargin); + Label(const Size& size = Size(), string line = string(), PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, Texture* tip = nullptr, Alignment alignment = Alignment::left, const Texture* texture = nullptr, bool bg = true, int lineMargin = defaultTextMargin, int iconMargin = defaultIconMargin); ~Label() override; - void drawSelf() const override; + void drawSelf(const Rect& view) const override; void onResize() override; void postInit() override; const string& getText() const; virtual void setText(string&& str); virtual void setText(const string& str); + const Texture* getTextTex() const; Rect textRect() const; Rect textFrame() const; Rect texRect() const override; @@ -285,8 +293,8 @@ inline const string& Label::getText() const { return text; } -inline Rect Label::textRect() const { - return Rect(textPos(), texSize(textTex)); +inline const Texture* Label::getTextTex() const { + return textTex; } inline int Label::getTextMargin() const { @@ -300,7 +308,7 @@ class ComboBox : public Label { sizet curOpt; public: - ComboBox(const Size& size = Size(), string curOption = string(), vector&& opts = vector(), PCall call = nullptr, SDL_Texture* tip = nullptr, Alignment alignment = Alignment::left, SDL_Texture* texture = nullptr, bool bg = true, int lineMargin = defaultTextMargin, int iconMargin = defaultIconMargin); + ComboBox(const Size& size = Size(), string curOption = string(), vector&& opts = vector(), PCall call = nullptr, Texture* tip = nullptr, Alignment alignment = Alignment::left, const Texture* texture = nullptr, bool bg = true, int lineMargin = defaultTextMargin, int iconMargin = defaultIconMargin); ~ComboBox() final = default; void onClick(ivec2 mPos, uint8 mBut) final; @@ -343,9 +351,10 @@ class LabelEdit : public Label { string oldText; public: - LabelEdit(const Size& size = Size(), string line = string(), PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, SDL_Texture* tip = nullptr, TextType type = TextType::text, bool focusLossConfirm = true, SDL_Texture* texture = nullptr, bool bg = true, int lineMargin = defaultTextMargin, int iconMargin = defaultIconMargin); + LabelEdit(const Size& size = Size(), string line = string(), PCall leftCall = nullptr, PCall rightCall = nullptr, PCall doubleCall = nullptr, Texture* tip = nullptr, TextType type = TextType::text, bool focusLossConfirm = true, const Texture* texture = nullptr, bool bg = true, int lineMargin = defaultTextMargin, int iconMargin = defaultIconMargin); ~LabelEdit() final = default; + void drawTop(const Rect& view) const final; void onClick(ivec2 mPos, uint8 mBut) final; void onKeypress(const SDL_Keysym& key) final; void onCompose(string_view str, uint olen) final; @@ -354,7 +363,6 @@ class LabelEdit : public Label { const string& getOldText() const; void setText(string&& str) final; void setText(const string& str) final; - Rect caretRect(); void confirm(); void cancel(); @@ -362,7 +370,7 @@ class LabelEdit : public Label { private: void onTextReset(); ivec2 textPos() const final; - int caretPos(); // caret's relative x position + int caretPos() const; // caret's relative x position void setCPos(uint cp); static bool kmodCtrl(uint16 mod); @@ -414,7 +422,7 @@ class KeyGetter : public Label { Binding::Type bindingType; // index of what is currently being edited public: - KeyGetter(const Size& size = Size(), AcceptType type = AcceptType::keyboard, Binding::Type binding = Binding::Type(-1), SDL_Texture* tip = nullptr, Alignment alignment = Alignment::center, SDL_Texture* texture = nullptr, bool bg = true, int lineMargin = defaultTextMargin, int iconMargin = defaultIconMargin); + KeyGetter(const Size& size = Size(), AcceptType type = AcceptType::keyboard, Binding::Type binding = Binding::Type(-1), Texture* tip = nullptr, Alignment alignment = Alignment::center, const Texture* texture = nullptr, bool bg = true, int lineMargin = defaultTextMargin, int iconMargin = defaultIconMargin); ~KeyGetter() final = default; void onClick(ivec2 mPos, uint8 mBut) final; @@ -435,3 +443,78 @@ class KeyGetter : public Label { inline void KeyGetter::restoreText() { setText(bindingText(bindingType, acceptType)); } + +// for arranging multi window setup +class WindowArranger : public Button { +private: + struct Dsp { + Rect rect, full; + Texture* txt = nullptr; + bool active; + + Dsp() = default; + Dsp(const Dsp&) = default; + Dsp(Dsp&&) = default; + Dsp(const Rect& vdsp, bool on); + + Dsp& operator=(const Dsp&) = default; + Dsp& operator=(Dsp&&) = default; + }; + + static constexpr int winMargin = 5; + + umap disps; + ivec2 totalDim; + Rect dragr; + int dragging; + int selected; + float bscale; + bool vertical; + +public: + WindowArranger(const Size& size = Size(), float baseScale = 1.f, bool vertExp = true, PCall leftCall = nullptr, PCall rightCall = nullptr, Texture* tip = nullptr, bool bg = true, const Texture* texture = nullptr, int margin = defaultIconMargin); + ~WindowArranger() final; + + void drawSelf(const Rect& view) const final; + void drawTop(const Rect& view) const final; + void onResize() final; + void postInit() final; + void onClick(ivec2 mPos, uint8 mBut) final; + void onMouseMove(ivec2 mPos, ivec2 mMov) final; + void onHold(ivec2 mPos, uint8 mBut) final; + void onDrag(ivec2 mPos, ivec2 mMov) final; + void onUndrag(uint8 mBut) final; + void onDisplayChange() final; + bool navSelectable() const final; + + Color color() const final; + const Texture* getTooltip() final; + bool draggingDisp(int id) const; + + const umap& getDisps() const; + umap getActiveDisps() const; + Rect offsetDisp(const Rect& rect, ivec2 pos) const; + int precalcSizeExpand(ivec2 siz) const; + tuple dispRect(int id, const Dsp& dsp) const; +private: + int dispUnderPos(ivec2 pnt) const; + void calcDisplays(); + void buildEntries(); + float entryScale(ivec2 siz) const; + void freeTextures(); + ivec2 snapDrag() const; + static array getSnapPoints(const Rect& rect); + template static void scanClosestSnapPoint(const array, S>& relations, const Rect& rect, const array& snaps, uint& snapId, ivec2& snapPnt, float& minDist); +}; + +inline const umap& WindowArranger::getDisps() const { + return disps; +} + +inline Rect WindowArranger::offsetDisp(const Rect& rect, ivec2 pos) const { + return Rect(rect.pos() + pos + winMargin, rect.size()); +} + +inline int WindowArranger::precalcSizeExpand(ivec2 siz) const { + return int(float(totalDim[vertical]) * entryScale(siz)) + winMargin * 2; +}