diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ab8c1563..61abf984f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2103,6 +2103,8 @@ if(CLIENT) ui.h ui_rect.cpp ui_rect.h + ui_scrollregion.cpp + ui_scrollregion.h ) set_src(GAME_EDITOR GLOB src/game/editor auto_map.cpp diff --git a/src/base/system.cpp b/src/base/system.cpp index 6e848895e..0a4fe1cbf 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -2997,6 +2997,18 @@ const char *str_rchr(const char *haystack, char needle) return strrchr(haystack, needle); } +int str_countchr(const char *haystack, char needle) +{ + int count = 0; + while(*haystack) + { + if(*haystack == needle) + count++; + haystack++; + } + return count; +} + void str_hex(char *dst, int dst_size, const void *data, int data_size) { static const char hex[] = "0123456789ABCDEF"; diff --git a/src/base/system.h b/src/base/system.h index 9e40dcf6a..3aebdca83 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1588,6 +1588,23 @@ const char *str_find(const char *haystack, const char *needle); */ const char *str_rchr(const char *haystack, char needle); +/* + Function: str_countchr + Counts the number of occurrences of a character in a string. + + Parameters: + haystack - String to count in + needle - Character to count + + Returns: + The number of characters in the haystack string matching + the needle character. + + Remarks: + - The strings are treated as zero-terminated strings. +*/ +int str_countchr(const char *haystack, char needle); + /* Function: str_hex Takes a datablock and generates a hex string of it, with spaces diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index d26376d82..5faa8d11a 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -324,7 +324,6 @@ MACRO_CONFIG_INT(ClShowOthers, cl_show_others, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG MACRO_CONFIG_INT(ClShowOthersAlpha, cl_show_others_alpha, 40, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show players in other teams (alpha value, 0 invisible, 100 fully visible)") MACRO_CONFIG_INT(ClOverlayEntities, cl_overlay_entities, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Overlay game tiles with a percentage of opacity") MACRO_CONFIG_INT(ClShowQuads, cl_showquads, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show quads (only interesting for mappers, or if your system has extremely bad performance)") -MACRO_CONFIG_INT(ClZoomBackgroundLayers, cl_zoom_background_layers, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom background layers") MACRO_CONFIG_COL(ClBackgroundColor, cl_background_color, 128, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background color") // 0 0 128 MACRO_CONFIG_COL(ClBackgroundEntitiesColor, cl_background_entities_color, 128, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background (entities) color") // 0 0 128 MACRO_CONFIG_STR(ClBackgroundEntities, cl_background_entities, 100, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background (entities)") diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index fb359b9c4..750886772 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -621,7 +621,7 @@ void CHud::RenderCursor() if(!m_pClient->m_Snap.m_pLocalCharacter || Client()->State() == IClient::STATE_DEMOPLAYBACK) return; - RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup()); + RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y); // render cursor int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS; diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp index b4add3538..23ded5edf 100644 --- a/src/game/client/components/maplayers.cpp +++ b/src/game/client/components/maplayers.cpp @@ -1562,6 +1562,7 @@ void CMapLayers::OnRender() for(int g = 0; g < m_pLayers->NumGroups(); g++) { CMapItemGroup *pGroup = m_pLayers->GetGroup(g); + CMapItemGroupEx *pGroupEx = m_pLayers->GetGroupEx(g); if(!pGroup) { @@ -1575,7 +1576,7 @@ void CMapLayers::OnRender() { // set clipping float aPoints[4]; - RenderTools()->MapScreenToGroup(Center.x, Center.y, m_pLayers->GameGroup(), GetCurCamera()->m_Zoom); + RenderTools()->MapScreenToGroup(Center.x, Center.y, m_pLayers->GameGroup(), m_pLayers->GameGroupEx(), GetCurCamera()->m_Zoom); Graphics()->GetScreen(&aPoints[0], &aPoints[1], &aPoints[2], &aPoints[3]); float x0 = (pGroup->m_ClipX - aPoints[0]) / (aPoints[2] - aPoints[0]); float y0 = (pGroup->m_ClipY - aPoints[1]) / (aPoints[3] - aPoints[1]); @@ -1593,12 +1594,7 @@ void CMapLayers::OnRender() (int)((x1 - x0) * Graphics()->ScreenWidth()), (int)((y1 - y0) * Graphics()->ScreenHeight())); } - if((!g_Config.m_ClZoomBackgroundLayers || m_Type == TYPE_FULL_DESIGN) && !pGroup->m_ParallaxX && !pGroup->m_ParallaxY) - { - RenderTools()->MapScreenToGroup(Center.x, Center.y, pGroup, 1.0f); - } - else - RenderTools()->MapScreenToGroup(Center.x, Center.y, pGroup, GetCurCamera()->m_Zoom); + RenderTools()->MapScreenToGroup(Center.x, Center.y, pGroup, pGroupEx, GetCurCamera()->m_Zoom); for(int l = 0; l < pGroup->m_NumLayers; l++) { diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 7536785f3..6a9065791 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -113,14 +113,14 @@ class CMenus : public CComponent Text.HMargin(pRect->h >= 20.0f ? 2.0f : 1.0f, &Text); Text.HMargin((Text.h * FontFactor) / 2.0f, &Text); - if(!UIElement.AreRectsInit() || HintRequiresStringCheck || HintCanChangePositionOrSize || UIElement.Get(0)->m_UITextContainer == -1) + if(!UIElement.AreRectsInit() || HintRequiresStringCheck || HintCanChangePositionOrSize || UIElement.Rect(0)->m_UITextContainer == -1) { - bool NeedsRecalc = !UIElement.AreRectsInit() || UIElement.Get(0)->m_UITextContainer == -1; + bool NeedsRecalc = !UIElement.AreRectsInit() || UIElement.Rect(0)->m_UITextContainer == -1; if(HintCanChangePositionOrSize) { if(UIElement.AreRectsInit()) { - if(UIElement.Get(0)->m_X != pRect->x || UIElement.Get(0)->m_Y != pRect->y || UIElement.Get(0)->m_Width != pRect->w || UIElement.Get(0)->m_Y != pRect->h) + if(UIElement.Rect(0)->m_X != pRect->x || UIElement.Rect(0)->m_Y != pRect->y || UIElement.Rect(0)->m_Width != pRect->w || UIElement.Rect(0)->m_Y != pRect->h) { NeedsRecalc = true; } @@ -132,7 +132,7 @@ class CMenus : public CComponent if(UIElement.AreRectsInit()) { pText = GetTextLambda(); - if(str_comp(UIElement.Get(0)->m_Text.c_str(), pText) != 0) + if(str_comp(UIElement.Rect(0)->m_Text.c_str(), pText) != 0) { NeedsRecalc = true; } @@ -158,7 +158,7 @@ class CMenus : public CComponent Color.a *= UI()->ButtonColorMulDefault(); Graphics()->SetColor(Color); - CUIElement::SUIElementRect &NewRect = *UIElement.Get(i); + CUIElement::SUIElementRect &NewRect = *UIElement.Rect(i); NewRect.m_UIRectQuadContainer = Graphics()->CreateRectQuadContainer(pRect->x, pRect->y, pRect->w, pRect->h, r, Corners); NewRect.m_X = pRect->x; @@ -185,11 +185,11 @@ class CMenus : public CComponent else if(UI()->HotItem() == pID) Index = 1; Graphics()->TextureClear(); - Graphics()->RenderQuadContainer(UIElement.Get(Index)->m_UIRectQuadContainer, -1); + Graphics()->RenderQuadContainer(UIElement.Rect(Index)->m_UIRectQuadContainer, -1); ColorRGBA ColorText(TextRender()->DefaultTextColor()); ColorRGBA ColorTextOutline(TextRender()->DefaultTextOutlineColor()); - if(UIElement.Get(0)->m_UITextContainer != -1) - TextRender()->RenderTextContainer(UIElement.Get(0)->m_UITextContainer, ColorText, ColorTextOutline); + if(UIElement.Rect(0)->m_UITextContainer != -1) + TextRender()->RenderTextContainer(UIElement.Rect(0)->m_UITextContainer, ColorText, ColorTextOutline); return UI()->DoButtonLogic(pID, Checked, pRect); } diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 8c85802ae..3c67629f8 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -286,13 +286,13 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) { CUIRect r = Row; r.Margin(0.5f, &r); - pItem->m_pUIElement->Get(0)->Draw(&r, ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_ALL, 4.0f); + pItem->m_pUIElement->Rect(0)->Draw(&r, ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_ALL, 4.0f); } else if(UI()->MouseHovered(&Row)) { CUIRect r = Row; r.Margin(0.5f, &r); - pItem->m_pUIElement->Get(0)->Draw(&r, ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f); + pItem->m_pUIElement->Rect(0)->Draw(&r, ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f); } if(UI()->DoButtonLogic(pItem, Selected, &Row)) @@ -328,22 +328,22 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) { if(pItem->m_Flags & SERVER_FLAG_PASSWORD) { - RenderBrowserIcons(*pItem->m_pUIElement->Get(gs_OffsetColFlagLock + 0), &Button, {0.75f, 0.75f, 0.75f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\xA3", TEXTALIGN_CENTER); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFlagLock + 0), &Button, {0.75f, 0.75f, 0.75f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\xA3", TEXTALIGN_CENTER); } } else if(ID == COL_FLAG_FAV) { if(pItem->m_Favorite != TRISTATE::NONE) { - RenderBrowserIcons(*pItem->m_pUIElement->Get(gs_OffsetColFav + 0), &Button, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\x84", TEXTALIGN_CENTER); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFav + 0), &Button, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\x84", TEXTALIGN_CENTER); } } else if(ID == COL_FLAG_OFFICIAL) { if(pItem->m_Official && g_Config.m_UiPage != PAGE_DDNET && g_Config.m_UiPage != PAGE_KOG) { - RenderBrowserIcons(*pItem->m_pUIElement->Get(gs_OffsetColOff + 0), &Button, {0.4f, 0.7f, 0.94f, 1}, {0.0f, 0.0f, 0.0f, 1.0f}, "\xEF\x82\xA3", TEXTALIGN_CENTER); - RenderBrowserIcons(*pItem->m_pUIElement->Get(gs_OffsetColOff + 1), &Button, {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, "\xEF\x80\x8C", TEXTALIGN_CENTER, true); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColOff + 0), &Button, {0.4f, 0.7f, 0.94f, 1}, {0.0f, 0.0f, 0.0f, 1.0f}, "\xEF\x82\xA3", TEXTALIGN_CENTER); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColOff + 1), &Button, {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, "\xEF\x80\x8C", TEXTALIGN_CENTER, true); } } else if(ID == COL_NAME) @@ -356,17 +356,17 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) const char *pStr = str_utf8_find_nocase(pItem->m_aName, g_Config.m_BrFilterString); if(pStr) { - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aName)); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aName)); TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColName + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(g_Config.m_BrFilterString), &pItem->m_pUIElement->Get(gs_OffsetColName + 0)->m_Cursor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(g_Config.m_BrFilterString), &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor); TextRender()->TextColor(1, 1, 1, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColName + 2), &Button, pStr + str_length(g_Config.m_BrFilterString), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Get(gs_OffsetColName + 1)->m_Cursor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pStr + str_length(g_Config.m_BrFilterString), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor); } else - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); } else - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); } else if(ID == COL_MAP) { @@ -379,7 +379,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) if(g_Config.m_BrIndicateFinished && pItem->m_HasRank == 1) { - RenderBrowserIcons(*pItem->m_pUIElement->Get(gs_OffsetColFlagLock + 1), &Icon, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), "\xEF\x84\x9E", TEXTALIGN_CENTER); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFlagLock + 1), &Icon, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), "\xEF\x84\x9E", TEXTALIGN_CENTER); } } @@ -391,17 +391,17 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) const char *pStr = str_utf8_find_nocase(pItem->m_aMap, g_Config.m_BrFilterString); if(pStr) { - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aMap)); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aMap)); TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColMap + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(g_Config.m_BrFilterString), &pItem->m_pUIElement->Get(gs_OffsetColMap + 0)->m_Cursor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(g_Config.m_BrFilterString), &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor); TextRender()->TextColor(1, 1, 1, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColMap + 2), &Button, pStr + str_length(g_Config.m_BrFilterString), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Get(gs_OffsetColMap + 1)->m_Cursor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pStr + str_length(g_Config.m_BrFilterString), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor); } else - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); } else - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); } else if(ID == COL_PLAYERS) { @@ -411,14 +411,14 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) { Button.VSplitLeft(Button.h, &Icon, &Button); Icon.Margin(2.0f, &Icon); - RenderBrowserIcons(*pItem->m_pUIElement->Get(gs_OffsetColFav + 1), &Icon, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\x84", TEXTALIGN_LEFT); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFav + 1), &Icon, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\x84", TEXTALIGN_LEFT); } str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumFilteredPlayers, ServerBrowser()->Max(*pItem)); if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_PLAYER)) TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); float FontSize = 12.0f; - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColPlayers), &Button, aTemp, FontSize, TEXTALIGN_RIGHT, -1, 1, false); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPlayers), &Button, aTemp, FontSize, TEXTALIGN_RIGHT, -1, 1, false); TextRender()->TextColor(1, 1, 1, 1); } else if(ID == COL_PING) @@ -432,14 +432,14 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) } float FontSize = 12.0f; - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColPing), &Button, aTemp, FontSize, TEXTALIGN_RIGHT, -1, 1, false); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPing), &Button, aTemp, FontSize, TEXTALIGN_RIGHT, -1, 1, false); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); } else if(ID == COL_VERSION) { const char *pVersion = pItem->m_aVersion; float FontSize = 12.0f; - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColVersion), &Button, pVersion, FontSize, TEXTALIGN_RIGHT, -1, 1, false); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColVersion), &Button, pVersion, FontSize, TEXTALIGN_RIGHT, -1, 1, false); } else if(ID == COL_GAMETYPE) { @@ -466,11 +466,11 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) ColorRGBA rgb = color_cast(hsl); TextRender()->TextColor(rgb); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); } else - UI()->DoLabelStreamed(*pItem->m_pUIElement->Get(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_LEFT, Button.w, 1, true); } } } diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 8c20c73bc..a82447be1 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "menus.h" @@ -450,14 +451,30 @@ void CMenus::RenderServerInfo(CUIRect MainView) } // motd - Motd.HSplitTop(10.0f, 0, &Motd); + const float MotdFontSize = 16.0f; + Motd.HSplitTop(10.0f, nullptr, &Motd); Motd.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - Motd.Margin(5.0f, &Motd); - y = 0.0f; - x = 5.0f; - TextRender()->Text(0, Motd.x + x, Motd.y + y, 32, Localize("MOTD"), -1.0f); - y += 32.0f + 5.0f; - TextRender()->Text(0, Motd.x + x, Motd.y + y, 16, m_pClient->m_Motd.m_aServerMotd, Motd.w); + Motd.HMargin(5.0f, &Motd); + Motd.VMargin(10.0f, &Motd); + + CUIRect MotdHeader; + Motd.HSplitTop(2.0f * MotdFontSize, &MotdHeader, &Motd); + Motd.HSplitTop(5.0f, nullptr, &Motd); + TextRender()->Text(nullptr, MotdHeader.x, MotdHeader.y, 2.0f * MotdFontSize, Localize("MOTD"), -1.0f); + + static CScrollRegion s_ScrollRegion; + vec2 ScrollOffset(0.0f, 0.0f); + CScrollRegionParams ScrollParams; + ScrollParams.m_ScrollUnit = 5 * MotdFontSize; + s_ScrollRegion.Begin(&Motd, &ScrollOffset, &ScrollParams); + Motd.y += ScrollOffset.y; + + CUIRect MotdTextArea; + Motd.HSplitTop((str_countchr(m_pClient->m_Motd.m_aServerMotd, '\n') + 1) * MotdFontSize, &MotdTextArea, &Motd); + s_ScrollRegion.AddRect(MotdTextArea); + TextRender()->Text(nullptr, MotdTextArea.x, MotdTextArea.y, MotdFontSize, m_pClient->m_Motd.m_aServerMotd, MotdTextArea.w); + + s_ScrollRegion.End(); } bool CMenus::RenderServerControlServer(CUIRect MainView) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index aec4f9333..5b88217aa 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "binds.h" @@ -927,7 +928,7 @@ float CMenus::RenderSettingsControlsJoystick(CUIRect View) const float Spacing = 2.0f; const float BackgroundHeight = NumOptions * (ButtonHeight + Spacing) + (NumOptions == 1 ? 0 : Spacing); if(View.h < BackgroundHeight) - return BackgroundHeight; // TODO: make this less hacky by porting CScrollRegion from vanilla + return BackgroundHeight; View.HSplitTop(BackgroundHeight, &View, 0); @@ -1107,110 +1108,120 @@ void CMenus::RenderSettingsControls(CUIRect MainView) } } - // controls in a scrollable listbox - static int s_ControlsList = 0; - static int s_SelectedControl = -1; - static float s_ScrollValue = 0; - static int s_OldSelected = 0; + // scrollable controls static float s_JoystickSettingsHeight = 0.0f; // we calculate this later and don't render until enough space is available - // Hacky values: Size of 10.0f per item for smoother scrolling, 72 elements - // fits the current size of controls settings - const float PseudoItemSize = 10.0f; - UiDoListboxStart(&s_ControlsList, &MainView, PseudoItemSize, Localize("Controls"), "", 72 + (int)ceilf(s_JoystickSettingsHeight / PseudoItemSize + 0.5f), 1, s_SelectedControl, s_ScrollValue); - - CUIRect MouseSettings, MovementSettings, WeaponSettings, VotingSettings, ChatSettings, DummySettings, MiscSettings, JoystickSettings, ResetButton; - CListboxItem Item = UiDoListboxNextItem(&s_OldSelected, false, false, true); - Item.m_Rect.HSplitTop(10.0f, 0, &Item.m_Rect); - Item.m_Rect.VSplitMid(&MouseSettings, &VotingSettings); + static CScrollRegion s_ScrollRegion; + vec2 ScrollOffset(0.0f, 0.0f); + CScrollRegionParams ScrollParams; + ScrollParams.m_ScrollUnit = 120.0f; + s_ScrollRegion.Begin(&MainView, &ScrollOffset, &ScrollParams); + MainView.y += ScrollOffset.y; const float FontSize = 14.0f; const float Margin = 10.0f; const float HeaderHeight = FontSize + 5.0f + Margin; + CUIRect MouseSettings, MovementSettings, WeaponSettings, VotingSettings, ChatSettings, DummySettings, MiscSettings, JoystickSettings, ResetButton; + MainView.VSplitMid(&MouseSettings, &VotingSettings); + // mouse settings { MouseSettings.VMargin(5.0f, &MouseSettings); MouseSettings.HSplitTop(80.0f, &MouseSettings, &JoystickSettings); - MouseSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - MouseSettings.VMargin(10.0f, &MouseSettings); + if(s_ScrollRegion.AddRect(MouseSettings)) + { + MouseSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + MouseSettings.VMargin(10.0f, &MouseSettings); - TextRender()->Text(0, MouseSettings.x, MouseSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Mouse"), -1.0f); + TextRender()->Text(0, MouseSettings.x, MouseSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Mouse"), -1.0f); - MouseSettings.HSplitTop(HeaderHeight, 0, &MouseSettings); + MouseSettings.HSplitTop(HeaderHeight, 0, &MouseSettings); - CUIRect Button; - MouseSettings.HSplitTop(20.0f, &Button, &MouseSettings); - UI()->DoScrollbarOption(&g_Config.m_InpMousesens, &g_Config.m_InpMousesens, &Button, Localize("Ingame mouse sens."), 1, 500, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE); + CUIRect Button; + MouseSettings.HSplitTop(20.0f, &Button, &MouseSettings); + UI()->DoScrollbarOption(&g_Config.m_InpMousesens, &g_Config.m_InpMousesens, &Button, Localize("Ingame mouse sens."), 1, 500, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE); - MouseSettings.HSplitTop(2.0f, 0, &MouseSettings); + MouseSettings.HSplitTop(2.0f, 0, &MouseSettings); - MouseSettings.HSplitTop(20.0f, &Button, &MouseSettings); - UI()->DoScrollbarOption(&g_Config.m_UiMousesens, &g_Config.m_UiMousesens, &Button, Localize("UI mouse sens."), 1, 500, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE); + MouseSettings.HSplitTop(20.0f, &Button, &MouseSettings); + UI()->DoScrollbarOption(&g_Config.m_UiMousesens, &g_Config.m_UiMousesens, &Button, Localize("UI mouse sens."), 1, 500, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE); + } } // joystick settings { JoystickSettings.HSplitTop(Margin, 0, &JoystickSettings); - JoystickSettings.HSplitTop(s_JoystickSettingsHeight, &JoystickSettings, &MovementSettings); - JoystickSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - JoystickSettings.VMargin(Margin, &JoystickSettings); + JoystickSettings.HSplitTop(s_JoystickSettingsHeight + HeaderHeight + Margin, &JoystickSettings, &MovementSettings); + if(s_ScrollRegion.AddRect(JoystickSettings)) + { + JoystickSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + JoystickSettings.VMargin(Margin, &JoystickSettings); - TextRender()->Text(0, JoystickSettings.x, JoystickSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Controller"), -1.0f); + TextRender()->Text(0, JoystickSettings.x, JoystickSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Controller"), -1.0f); - JoystickSettings.HSplitTop(HeaderHeight, 0, &JoystickSettings); - s_JoystickSettingsHeight = RenderSettingsControlsJoystick(JoystickSettings) + HeaderHeight + Margin; // + Margin for another bottom margin + JoystickSettings.HSplitTop(HeaderHeight, 0, &JoystickSettings); + s_JoystickSettingsHeight = RenderSettingsControlsJoystick(JoystickSettings); + } } // movement settings { MovementSettings.HSplitTop(Margin, 0, &MovementSettings); MovementSettings.HSplitTop(365.0f, &MovementSettings, &WeaponSettings); - MovementSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - MovementSettings.VMargin(Margin, &MovementSettings); + if(s_ScrollRegion.AddRect(MovementSettings)) + { + MovementSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + MovementSettings.VMargin(Margin, &MovementSettings); - TextRender()->Text(0, MovementSettings.x, MovementSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Movement"), -1.0f); + TextRender()->Text(0, MovementSettings.x, MovementSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Movement"), -1.0f); - MovementSettings.HSplitTop(HeaderHeight, 0, &MovementSettings); - DoSettingsControlsButtons(0, 15, MovementSettings); + MovementSettings.HSplitTop(HeaderHeight, 0, &MovementSettings); + DoSettingsControlsButtons(0, 15, MovementSettings); + } } // weapon settings { WeaponSettings.HSplitTop(Margin, 0, &WeaponSettings); WeaponSettings.HSplitTop(190.0f, &WeaponSettings, &ResetButton); - WeaponSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - WeaponSettings.VMargin(Margin, &WeaponSettings); + if(s_ScrollRegion.AddRect(WeaponSettings)) + { + WeaponSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + WeaponSettings.VMargin(Margin, &WeaponSettings); - TextRender()->Text(0, WeaponSettings.x, WeaponSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Weapon"), -1.0f); + TextRender()->Text(0, WeaponSettings.x, WeaponSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Weapon"), -1.0f); - WeaponSettings.HSplitTop(HeaderHeight, 0, &WeaponSettings); - DoSettingsControlsButtons(15, 22, WeaponSettings); + WeaponSettings.HSplitTop(HeaderHeight, 0, &WeaponSettings); + DoSettingsControlsButtons(15, 22, WeaponSettings); + } } // defaults { ResetButton.HSplitTop(Margin, 0, &ResetButton); ResetButton.HSplitTop(40.0f, &ResetButton, 0); - ResetButton.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - ResetButton.HMargin(10.0f, &ResetButton); - ResetButton.VMargin(30.0f, &ResetButton); - ResetButton.HSplitTop(20.0f, &ResetButton, 0); - static CButtonContainer s_DefaultButton; - if(DoButton_Menu(&s_DefaultButton, Localize("Reset to defaults"), 0, &ResetButton)) + if(s_ScrollRegion.AddRect(ResetButton)) { - m_pClient->m_Binds.SetDefaults(); + ResetButton.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + ResetButton.HMargin(10.0f, &ResetButton); + ResetButton.VMargin(30.0f, &ResetButton); + static CButtonContainer s_DefaultButton; + if(DoButton_Menu(&s_DefaultButton, Localize("Reset to defaults"), 0, &ResetButton)) + { + m_pClient->m_Binds.SetDefaults(); - g_Config.m_InpMousesens = 200; - g_Config.m_UiMousesens = 200; + g_Config.m_InpMousesens = 200; + g_Config.m_UiMousesens = 200; - g_Config.m_InpControllerEnable = 0; - g_Config.m_InpControllerGUID[0] = '\0'; - g_Config.m_InpControllerAbsolute = 0; - g_Config.m_InpControllerSens = 100; - g_Config.m_InpControllerX = 0; - g_Config.m_InpControllerY = 1; - g_Config.m_InpControllerTolerance = 5; - g_Config.m_UiControllerSens = 100; + g_Config.m_InpControllerEnable = 0; + g_Config.m_InpControllerGUID[0] = '\0'; + g_Config.m_InpControllerAbsolute = 0; + g_Config.m_InpControllerSens = 100; + g_Config.m_InpControllerX = 0; + g_Config.m_InpControllerY = 1; + g_Config.m_InpControllerTolerance = 5; + g_Config.m_UiControllerSens = 100; + } } } @@ -1218,55 +1229,67 @@ void CMenus::RenderSettingsControls(CUIRect MainView) { VotingSettings.VMargin(5.0f, &VotingSettings); VotingSettings.HSplitTop(80.0f, &VotingSettings, &ChatSettings); - VotingSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - VotingSettings.VMargin(Margin, &VotingSettings); + if(s_ScrollRegion.AddRect(VotingSettings)) + { + VotingSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + VotingSettings.VMargin(Margin, &VotingSettings); - TextRender()->Text(0, VotingSettings.x, VotingSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Voting"), -1.0f); + TextRender()->Text(0, VotingSettings.x, VotingSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Voting"), -1.0f); - VotingSettings.HSplitTop(HeaderHeight, 0, &VotingSettings); - DoSettingsControlsButtons(22, 24, VotingSettings); + VotingSettings.HSplitTop(HeaderHeight, 0, &VotingSettings); + DoSettingsControlsButtons(22, 24, VotingSettings); + } } // chat settings { ChatSettings.HSplitTop(Margin, 0, &ChatSettings); ChatSettings.HSplitTop(145.0f, &ChatSettings, &DummySettings); - ChatSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - ChatSettings.VMargin(Margin, &ChatSettings); + if(s_ScrollRegion.AddRect(ChatSettings)) + { + ChatSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + ChatSettings.VMargin(Margin, &ChatSettings); - TextRender()->Text(0, ChatSettings.x, ChatSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Chat"), -1.0f); + TextRender()->Text(0, ChatSettings.x, ChatSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Chat"), -1.0f); - ChatSettings.HSplitTop(HeaderHeight, 0, &ChatSettings); - DoSettingsControlsButtons(24, 29, ChatSettings); + ChatSettings.HSplitTop(HeaderHeight, 0, &ChatSettings); + DoSettingsControlsButtons(24, 29, ChatSettings); + } } // dummy settings { DummySettings.HSplitTop(Margin, 0, &DummySettings); DummySettings.HSplitTop(100.0f, &DummySettings, &MiscSettings); - DummySettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - DummySettings.VMargin(Margin, &DummySettings); + if(s_ScrollRegion.AddRect(DummySettings)) + { + DummySettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + DummySettings.VMargin(Margin, &DummySettings); - TextRender()->Text(0, DummySettings.x, DummySettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Dummy"), -1.0f); + TextRender()->Text(0, DummySettings.x, DummySettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Dummy"), -1.0f); - DummySettings.HSplitTop(HeaderHeight, 0, &DummySettings); - DoSettingsControlsButtons(29, 32, DummySettings); + DummySettings.HSplitTop(HeaderHeight, 0, &DummySettings); + DoSettingsControlsButtons(29, 32, DummySettings); + } } // misc settings { MiscSettings.HSplitTop(Margin, 0, &MiscSettings); MiscSettings.HSplitTop(300.0f, &MiscSettings, 0); - MiscSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); - MiscSettings.VMargin(Margin, &MiscSettings); + if(s_ScrollRegion.AddRect(MiscSettings)) + { + MiscSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); + MiscSettings.VMargin(Margin, &MiscSettings); - TextRender()->Text(0, MiscSettings.x, MiscSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Miscellaneous"), -1.0f); + TextRender()->Text(0, MiscSettings.x, MiscSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Miscellaneous"), -1.0f); - MiscSettings.HSplitTop(HeaderHeight, 0, &MiscSettings); - DoSettingsControlsButtons(32, 44, MiscSettings); + MiscSettings.HSplitTop(HeaderHeight, 0, &MiscSettings); + DoSettingsControlsButtons(32, 44, MiscSettings); + } } - UiDoListboxEnd(&s_ScrollValue, 0); + s_ScrollRegion.End(); } int CMenus::RenderDropDown(int &CurDropDownState, CUIRect *pRect, int CurSelection, const void **pIDs, const char **pStr, int PickNum, CButtonContainer *pButtonContainer, float &ScrollVal) diff --git a/src/game/client/components/nameplates.cpp b/src/game/client/components/nameplates.cpp index 60c42a642..0519ab641 100644 --- a/src/game/client/components/nameplates.cpp +++ b/src/game/client/components/nameplates.cpp @@ -101,7 +101,7 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP // create nameplates at standard zoom float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup()); + RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y); m_aNamePlates[ClientID].m_NameTextWidth = TextRender()->TextWidth(0, FontSize, pName, -1, -1.0f); @@ -126,7 +126,7 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP // create nameplates at standard zoom float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup()); + RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y); m_aNamePlates[ClientID].m_ClanNameTextWidth = TextRender()->TextWidth(0, FontSizeClan, pClan, -1, -1.0f); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 0a93d6b0d..2a8c307fd 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -374,6 +374,8 @@ void CGameClient::OnInit() void CGameClient::OnUpdate() { + CUIElementBase::Init(UI()); // update static pointer because game and editor use separate UI + // handle mouse movement float x = 0.0f, y = 0.0f; IInput::ECursorType CursorType = Input()->CursorRelative(&x, &y); diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp index 371834f73..a352d3c51 100644 --- a/src/game/client/render.cpp +++ b/src/game/client/render.cpp @@ -15,6 +15,7 @@ #include #include +#include static float gs_SpriteWScale; static float gs_SpriteHScale; @@ -389,10 +390,15 @@ void CRenderTools::CalcScreenParams(float Aspect, float Zoom, float *pWidth, flo } void CRenderTools::MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY, - float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints) + float ParallaxZoom, float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints) { float Width, Height; CalcScreenParams(Aspect, Zoom, &Width, &Height); + + float Scale = (ParallaxZoom * (Zoom - 1.0f) + 100.0f) / 100.0f / Zoom; + Width *= Scale; + Height *= Scale; + CenterX *= ParallaxX / 100.0f; CenterY *= ParallaxY / 100.0f; pPoints[0] = OffsetX + CenterX - Width / 2; @@ -401,10 +407,19 @@ void CRenderTools::MapScreenToWorld(float CenterX, float CenterY, float Parallax pPoints[3] = pPoints[1] + Height; } -void CRenderTools::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom) +void CRenderTools::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, CMapItemGroupEx *pGroupEx, float Zoom) { + float ParallaxZoom = GetParallaxZoom(pGroup, pGroupEx); float aPoints[4]; - MapScreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY, + MapScreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY, ParallaxZoom, pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), Zoom, aPoints); Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]); } + +void CRenderTools::MapScreenToInterface(float CenterX, float CenterY) +{ + float aPoints[4]; + MapScreenToWorld(CenterX, CenterY, 100.0f, 100.0f, 100.0f, + 0, 0, Graphics()->ScreenAspect(), 1.0f, aPoints); + Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]); +} diff --git a/src/game/client/render.h b/src/game/client/render.h index fe9dda43b..792bf96a1 100644 --- a/src/game/client/render.h +++ b/src/game/client/render.h @@ -20,6 +20,7 @@ struct CDataSprite; struct CDataSprite; struct CEnvPoint; struct CMapItemGroup; +struct CMapItemGroupEx; struct CQuad; class CTeeRenderInfo @@ -126,8 +127,9 @@ public: // helpers void CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight); void MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY, - float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints); - void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom = 1.0f); + float ParallaxZoom, float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints); + void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, CMapItemGroupEx *pGroupEx, float Zoom); + void MapScreenToInterface(float CenterX, float CenterY); // DDRace diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index f16741002..8775ef35d 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -82,6 +82,13 @@ const CLinearScrollbarScale CUI::ms_LinearScrollbarScale; const CLogarithmicScrollbarScale CUI::ms_LogarithmicScrollbarScale(25); float CUI::ms_FontmodHeight = 0.8f; +CUI *CUIElementBase::s_pUI = nullptr; + +IClient *CUIElementBase::Client() const { return s_pUI->Client(); } +IGraphics *CUIElementBase::Graphics() const { return s_pUI->Graphics(); } +IInput *CUIElementBase::Input() const { return s_pUI->Input(); } +ITextRender *CUIElementBase::TextRender() const { return s_pUI->TextRender(); } + void CUI::Init(IKernel *pKernel) { m_pClient = pKernel->RequestInterface(); @@ -90,6 +97,7 @@ void CUI::Init(IKernel *pKernel) m_pTextRender = pKernel->RequestInterface(); InitInputs(m_pInput->GetEventsRaw(), m_pInput->GetEventCountRaw()); CUIRect::Init(m_pGraphics); + CUIElementBase::Init(this); } void CUI::InitInputs(IInput::CEvent *pInputEventsArray, int *pInputEventCount) diff --git a/src/game/client/ui.h b/src/game/client/ui.h index 9c314fe4a..5dbdc6949 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -131,15 +131,12 @@ protected: CUI *UI() const { return m_pUI; } std::vector m_vUIRects; - // used for marquees or other user implemented things - int64_t m_ElementTime; - public: CUIElement() = default; void Init(CUI *pUI, int RequestedRectCount); - SUIElementRect *Get(size_t Index) + SUIElementRect *Rect(size_t Index) { return &m_vUIRects[Index]; } @@ -161,6 +158,21 @@ struct SLabelProperties bool m_EnableWidthCheck = true; }; +class CUIElementBase +{ +private: + static CUI *s_pUI; + +public: + static void Init(CUI *pUI) { s_pUI = pUI; } + + IClient *Client() const; + IGraphics *Graphics() const; + IInput *Input() const; + ITextRender *TextRender() const; + CUI *UI() const { return s_pUI; } +}; + class CButtonContainer { }; diff --git a/src/game/client/ui_scrollregion.cpp b/src/game/client/ui_scrollregion.cpp new file mode 100644 index 000000000..91ceb588f --- /dev/null +++ b/src/game/client/ui_scrollregion.cpp @@ -0,0 +1,220 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include +#include + +#include +#include + +#include "ui_scrollregion.h" + +CScrollRegion::CScrollRegion() +{ + m_ScrollY = 0.0f; + m_ContentH = 0.0f; + m_AnimTime = 0.0f; + m_AnimInitScrollY = 0.0f; + m_AnimTargetScrollY = 0.0f; + m_RequestScrollY = -1.0f; + m_ContentScrollOff = vec2(0.0f, 0.0f); + m_Params = CScrollRegionParams(); +} + +void CScrollRegion::Begin(CUIRect *pClipRect, vec2 *pOutOffset, CScrollRegionParams *pParams) +{ + if(pParams) + m_Params = *pParams; + + const bool ContentOverflows = m_ContentH > pClipRect->h; + const bool ForceShowScrollbar = m_Params.m_Flags & CScrollRegionParams::FLAG_CONTENT_STATIC_WIDTH; + + CUIRect ScrollBarBg; + bool HasScrollBar = ContentOverflows || ForceShowScrollbar; + CUIRect *pModifyRect = HasScrollBar ? pClipRect : nullptr; + pClipRect->VSplitRight(m_Params.m_ScrollbarWidth, pModifyRect, &ScrollBarBg); + ScrollBarBg.Margin(m_Params.m_ScrollbarMargin, &m_RailRect); + + // only show scrollbar if required + if(HasScrollBar) + { + if(m_Params.m_ScrollbarBgColor.a > 0.0f) + ScrollBarBg.Draw(m_Params.m_ScrollbarBgColor, IGraphics::CORNER_R, 4.0f); + if(m_Params.m_RailBgColor.a > 0.0f) + m_RailRect.Draw(m_Params.m_RailBgColor, IGraphics::CORNER_ALL, m_RailRect.w / 2.0f); + } + if(!ContentOverflows) + m_ContentScrollOff.y = 0.0f; + + if(m_Params.m_ClipBgColor.a > 0.0f) + pClipRect->Draw(m_Params.m_ClipBgColor, HasScrollBar ? IGraphics::CORNER_L : IGraphics::CORNER_ALL, 4.0f); + + UI()->ClipEnable(pClipRect); + + m_ClipRect = *pClipRect; + m_ContentH = 0.0f; + *pOutOffset = m_ContentScrollOff; +} + +void CScrollRegion::End() +{ + UI()->ClipDisable(); + + // only show scrollbar if content overflows + if(m_ContentH <= m_ClipRect.h) + return; + + // scroll wheel + CUIRect RegionRect = m_ClipRect; + RegionRect.w += m_Params.m_ScrollbarWidth; + + const float AnimationDuration = 0.5f; + + if(UI()->Enabled() && UI()->MouseHovered(&RegionRect)) + { + const bool IsPageScroll = Input()->KeyIsPressed(KEY_LALT) || Input()->KeyIsPressed(KEY_RALT); + const float ScrollUnit = IsPageScroll ? m_ClipRect.h : m_Params.m_ScrollUnit; + if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) + { + m_AnimTime = AnimationDuration; + m_AnimInitScrollY = m_ScrollY; + m_AnimTargetScrollY -= ScrollUnit; + } + else if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) + { + m_AnimTime = AnimationDuration; + m_AnimInitScrollY = m_ScrollY; + m_AnimTargetScrollY += ScrollUnit; + } + } + + const float SliderHeight = maximum(m_Params.m_SliderMinHeight, m_ClipRect.h / m_ContentH * m_RailRect.h); + + CUIRect Slider = m_RailRect; + Slider.h = SliderHeight; + + const float MaxSlider = m_RailRect.h - SliderHeight; + const float MaxScroll = m_ContentH - m_ClipRect.h; + + if(m_RequestScrollY >= 0.0f) + { + m_AnimTargetScrollY = m_RequestScrollY; + m_AnimTime = 0.0f; + m_RequestScrollY = -1.0f; + } + + m_AnimTargetScrollY = clamp(m_AnimTargetScrollY, 0.0f, MaxScroll); + + if(absolute(m_AnimInitScrollY - m_AnimTargetScrollY) < 0.5f) + m_AnimTime = 0.0f; + + if(m_AnimTime > 0.0f) + { + m_AnimTime -= Client()->RenderFrameTime(); + float AnimProgress = (1.0f - powf(m_AnimTime / AnimationDuration, 3.0f)); // cubic ease out + m_ScrollY = m_AnimInitScrollY + (m_AnimTargetScrollY - m_AnimInitScrollY) * AnimProgress; + } + else + { + m_ScrollY = m_AnimTargetScrollY; + } + + Slider.y += m_ScrollY / MaxScroll * MaxSlider; + + bool Hovered = false; + bool Grabbed = false; + const void *pID = &m_ScrollY; + const bool InsideSlider = UI()->MouseHovered(&Slider); + const bool InsideRail = UI()->MouseHovered(&m_RailRect); + + if(UI()->CheckActiveItem(pID) && UI()->MouseButton(0)) + { + float MouseY = UI()->MouseY(); + m_ScrollY += (MouseY - (Slider.y + m_SliderGrabPos.y)) / MaxSlider * MaxScroll; + m_SliderGrabPos.y = clamp(m_SliderGrabPos.y, 0.0f, SliderHeight); + m_AnimTargetScrollY = m_ScrollY; + m_AnimTime = 0.0f; + Grabbed = true; + } + else if(InsideSlider) + { + UI()->SetHotItem(pID); + + if(!UI()->CheckActiveItem(pID) && UI()->MouseButtonClicked(0)) + { + UI()->SetActiveItem(pID); + m_SliderGrabPos.y = UI()->MouseY() - Slider.y; + m_AnimTargetScrollY = m_ScrollY; + m_AnimTime = 0.0f; + } + Hovered = true; + } + else if(InsideRail && UI()->MouseButtonClicked(0)) + { + m_ScrollY += (UI()->MouseY() - (Slider.y + Slider.h / 2.0f)) / MaxSlider * MaxScroll; + UI()->SetActiveItem(pID); + m_SliderGrabPos.y = Slider.h / 2.0f; + m_AnimTargetScrollY = m_ScrollY; + m_AnimTime = 0.0f; + Hovered = true; + } + else if(UI()->CheckActiveItem(pID) && !UI()->MouseButton(0)) + { + UI()->SetActiveItem(nullptr); + } + + m_ScrollY = clamp(m_ScrollY, 0.0f, MaxScroll); + m_ContentScrollOff.y = -m_ScrollY; + + Slider.Draw(m_Params.SliderColor(Grabbed, Hovered), IGraphics::CORNER_ALL, Slider.w / 2.0f); +} + +bool CScrollRegion::AddRect(const CUIRect &Rect, bool ShouldScrollHere) +{ + m_LastAddedRect = Rect; + // Round up and add 1 to fix pixel clipping at the end of the scrolling area + m_ContentH = maximum(ceilf(Rect.y + Rect.h - (m_ClipRect.y + m_ContentScrollOff.y)) + 1.0f, m_ContentH); + if(ShouldScrollHere) + ScrollHere(); + return !IsRectClipped(Rect); +} + +void CScrollRegion::ScrollHere(EScrollOption Option) +{ + const float MinHeight = minimum(m_ClipRect.h, m_LastAddedRect.h); + const float TopScroll = m_LastAddedRect.y - (m_ClipRect.y + m_ContentScrollOff.y); + + switch(Option) + { + case SCROLLHERE_TOP: + m_RequestScrollY = TopScroll; + break; + + case SCROLLHERE_BOTTOM: + m_RequestScrollY = TopScroll - (m_ClipRect.h - MinHeight); + break; + + case SCROLLHERE_KEEP_IN_VIEW: + default: + const float DeltaY = m_LastAddedRect.y - m_ClipRect.y; + if(DeltaY < 0) + m_RequestScrollY = TopScroll; + else if(DeltaY > (m_ClipRect.h - MinHeight)) + m_RequestScrollY = TopScroll - (m_ClipRect.h - MinHeight); + break; + } +} + +bool CScrollRegion::IsRectClipped(const CUIRect &Rect) const +{ + return (m_ClipRect.x > (Rect.x + Rect.w) || (m_ClipRect.x + m_ClipRect.w) < Rect.x || m_ClipRect.y > (Rect.y + Rect.h) || (m_ClipRect.y + m_ClipRect.h) < Rect.y); +} + +bool CScrollRegion::IsScrollbarShown() const +{ + return m_ContentH > m_ClipRect.h; +} + +bool CScrollRegion::IsAnimating() const +{ + return m_AnimTime > 0.0f; +} diff --git a/src/game/client/ui_scrollregion.h b/src/game/client/ui_scrollregion.h new file mode 100644 index 000000000..b63b1f7bd --- /dev/null +++ b/src/game/client/ui_scrollregion.h @@ -0,0 +1,123 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef GAME_CLIENT_UI_SCROLLREGION_H +#define GAME_CLIENT_UI_SCROLLREGION_H + +#include "ui.h" + +struct CScrollRegionParams +{ + float m_ScrollbarWidth; + float m_ScrollbarMargin; + float m_SliderMinHeight; + float m_ScrollUnit; + ColorRGBA m_ClipBgColor; + ColorRGBA m_ScrollbarBgColor; + ColorRGBA m_RailBgColor; + ColorRGBA m_SliderColor; + ColorRGBA m_SliderColorHover; + ColorRGBA m_SliderColorGrabbed; + unsigned m_Flags; + + enum + { + FLAG_CONTENT_STATIC_WIDTH = 1 << 0, + }; + + CScrollRegionParams() + { + m_ScrollbarWidth = 20.0f; + m_ScrollbarMargin = 5.0f; + m_SliderMinHeight = 25.0f; + m_ScrollUnit = 10.0f; + m_ClipBgColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f); + m_ScrollbarBgColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f); + m_RailBgColor = ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f); + m_SliderColor = ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f); + m_SliderColorHover = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + m_SliderColorGrabbed = ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f); + m_Flags = 0; + } + + ColorRGBA SliderColor(bool Active, bool Hovered) const + { + if(Active) + return m_SliderColorGrabbed; + else if(Hovered) + return m_SliderColorHover; + return m_SliderColor; + } +}; + +/* +Usage: + -- Initialization -- + static CScrollRegion s_ScrollRegion; + vec2 ScrollOffset(0, 0); + s_ScrollRegion.Begin(&ScrollRegionRect, &ScrollOffset); + Content = ScrollRegionRect; + Content.y += ScrollOffset.y; + + -- "Register" your content rects -- + CUIRect Rect; + Content.HSplitTop(SomeValue, &Rect, &Content); + s_ScrollRegion.AddRect(Rect); + + -- [Optional] Knowing if a rect is clipped -- + s_ScrollRegion.IsRectClipped(Rect); + + -- [Optional] Scroll to a rect (to the last added rect)-- + ... + s_ScrollRegion.AddRect(Rect); + s_ScrollRegion.ScrollHere(Option); + + -- [Convenience] Add rect and check for visibility at the same time + if(s_ScrollRegion.AddRect(Rect)) + // The rect is visible (not clipped) + + -- [Convenience] Add rect and scroll to it if it's selected + if(s_ScrollRegion.AddRect(Rect, ScrollToSelection && IsSelected)) + // The rect is visible (not clipped) + + -- End -- + s_ScrollRegion.End(); +*/ + +// Instances of CScrollRegion must be static, as member addresses are used as UI item IDs +class CScrollRegion : private CUIElementBase +{ +private: + float m_ScrollY; + float m_ContentH; + float m_RequestScrollY; // [0, ContentHeight] + + float m_AnimTime; + float m_AnimInitScrollY; + float m_AnimTargetScrollY; + + CUIRect m_ClipRect; + CUIRect m_RailRect; + CUIRect m_LastAddedRect; // saved for ScrollHere() + vec2 m_SliderGrabPos; // where did user grab the slider + vec2 m_ContentScrollOff; + CScrollRegionParams m_Params; + +public: + enum EScrollOption + { + SCROLLHERE_KEEP_IN_VIEW = 0, + SCROLLHERE_TOP, + SCROLLHERE_BOTTOM, + }; + + CScrollRegion(); + void Begin(CUIRect *pClipRect, vec2 *pOutOffset, CScrollRegionParams *pParams = nullptr); + void End(); + bool AddRect(const CUIRect &Rect, bool ShouldScrollHere = false); // returns true if the added rect is visible (not clipped) + void ScrollHere(EScrollOption Option = SCROLLHERE_KEEP_IN_VIEW); + bool IsRectClipped(const CUIRect &Rect) const; + bool IsScrollbarShown() const; + bool IsAnimating() const; +}; + +#endif diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index cd73cbd56..1aec7b95a 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -102,6 +103,7 @@ CLayerGroup::CLayerGroup() m_OffsetY = 0; m_ParallaxX = 100; m_ParallaxY = 100; + m_ParallaxZoom = 100; m_UseClipping = 0; m_ClipX = 0; @@ -131,9 +133,11 @@ void CLayerGroup::Convert(CUIRect *pRect) void CLayerGroup::Mapping(float *pPoints) { + float ParallaxZoom = m_pMap->m_pEditor->m_PreviewZoom ? m_ParallaxZoom : 100.0f; + m_pMap->m_pEditor->RenderTools()->MapScreenToWorld( m_pMap->m_pEditor->m_WorldOffsetX, m_pMap->m_pEditor->m_WorldOffsetY, - m_ParallaxX, m_ParallaxY, m_OffsetX, m_OffsetY, + m_ParallaxX, m_ParallaxY, ParallaxZoom, m_OffsetX, m_OffsetY, m_pMap->m_pEditor->Graphics()->ScreenAspect(), m_pMap->m_pEditor->m_WorldZoom, pPoints); pPoints[0] += m_pMap->m_pEditor->m_EditorOffsetX; @@ -880,6 +884,16 @@ void CEditor::DoToolbar(CUIRect ToolBar) TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); + // zoom button + TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); + static int s_ZoomButton = 0; + if(DoButton_Editor(&s_ZoomButton, "Zoom", m_PreviewZoom, &Button, 0, "Toggles preview of how layers will be zoomed in-game")) + { + m_PreviewZoom = !m_PreviewZoom; + } + + TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); + // grid button TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); static int s_GridButton = 0; @@ -1250,8 +1264,8 @@ void CEditor::DoSoundSource(CSoundSource *pSource, int Index) float CenterX = fx2f(pSource->m_Position.x); float CenterY = fx2f(pSource->m_Position.y); - float dx = (CenterX - wx) / m_WorldZoom; - float dy = (CenterY - wy) / m_WorldZoom; + float dx = (CenterX - wx) / m_MouseWScale; + float dy = (CenterY - wy) / m_MouseWScale; if(dx * dx + dy * dy < 50) UI()->SetHotItem(pID); @@ -1343,7 +1357,7 @@ void CEditor::DoSoundSource(CSoundSource *pSource, int Index) Graphics()->SetColor(0, 1, 0, 1); } - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f * m_WorldZoom, 5.0f * m_WorldZoom); + IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); Graphics()->QuadsDraw(&QuadItem, 1); } @@ -1371,8 +1385,8 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) float CenterX = fx2f(pQuad->m_aPoints[4].x); float CenterY = fx2f(pQuad->m_aPoints[4].y); - float dx = (CenterX - wx) / m_WorldZoom; - float dy = (CenterY - wy) / m_WorldZoom; + float dx = (CenterX - wx) / m_MouseWScale; + float dy = (CenterY - wy) / m_MouseWScale; if(dx * dx + dy * dy < 50) UI()->SetHotItem(pID); @@ -1382,7 +1396,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) if(IsQuadSelected(Index)) { Graphics()->SetColor(0, 0, 0, 1); - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 7.0f * m_WorldZoom, 7.0f * m_WorldZoom); + IGraphics::CQuadItem QuadItem(CenterX, CenterY, 7.0f * m_MouseWScale, 7.0f * m_MouseWScale); Graphics()->QuadsDraw(&QuadItem, 1); } @@ -1598,7 +1612,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) else Graphics()->SetColor(0, 1, 0, 1); - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f * m_WorldZoom, 5.0f * m_WorldZoom); + IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); Graphics()->QuadsDraw(&QuadItem, 1); } @@ -1612,8 +1626,8 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) float px = fx2f(pQuad->m_aPoints[V].x); float py = fx2f(pQuad->m_aPoints[V].y); - float dx = (px - wx) / m_WorldZoom; - float dy = (py - wy) / m_WorldZoom; + float dx = (px - wx) / m_MouseWScale; + float dy = (py - wy) / m_MouseWScale; if(dx * dx + dy * dy < 50) UI()->SetHotItem(pID); @@ -1621,7 +1635,7 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) if(IsQuadSelected(QuadIndex) && m_SelectedPoints & (1 << V)) { Graphics()->SetColor(0, 0, 0, 1); - IGraphics::CQuadItem QuadItem(px, py, 7.0f * m_WorldZoom, 7.0f * m_WorldZoom); + IGraphics::CQuadItem QuadItem(px, py, 7.0f * m_MouseWScale, 7.0f * m_MouseWScale); Graphics()->QuadsDraw(&QuadItem, 1); } @@ -1805,7 +1819,7 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) else Graphics()->SetColor(1, 0, 0, 1); - IGraphics::CQuadItem QuadItem(px, py, 5.0f * m_WorldZoom, 5.0f * m_WorldZoom); + IGraphics::CQuadItem QuadItem(px, py, 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); Graphics()->QuadsDraw(&QuadItem, 1); } @@ -1841,7 +1855,7 @@ void CEditor::DoQuadKnife(int QuadIndex) CQuad *pQuad = &pLayer->m_vQuads[QuadIndex]; bool IgnoreGrid = Input()->KeyIsPressed(KEY_LALT) || Input()->KeyIsPressed(KEY_RALT); - float SnapRadius = 4.f * m_WorldZoom; + float SnapRadius = 4.f * m_MouseWScale; vec2 Mouse = vec2(UI()->MouseWorldX(), UI()->MouseWorldY()); vec2 Point = Mouse; @@ -2042,14 +2056,14 @@ void CEditor::DoQuadKnife(int QuadIndex) IGraphics::CQuadItem aMarkers[4]; for(int i = 0; i < m_QuadKnifeCount; i++) - aMarkers[i] = IGraphics::CQuadItem(m_aQuadKnifePoints[i].x, m_aQuadKnifePoints[i].y, 5.f * m_WorldZoom, 5.f * m_WorldZoom); + aMarkers[i] = IGraphics::CQuadItem(m_aQuadKnifePoints[i].x, m_aQuadKnifePoints[i].y, 5.f * m_MouseWScale, 5.f * m_MouseWScale); Graphics()->SetColor(0.f, 0.f, 1.f, 1.f); Graphics()->QuadsDraw(aMarkers, m_QuadKnifeCount); if(ValidPosition) { - IGraphics::CQuadItem MarkerCurrent(Point.x, Point.y, 5.f * m_WorldZoom, 5.f * m_WorldZoom); + IGraphics::CQuadItem MarkerCurrent(Point.x, Point.y, 5.f * m_MouseWScale, 5.f * m_MouseWScale); Graphics()->QuadsDraw(&MarkerCurrent, 1); } @@ -2196,8 +2210,8 @@ void CEditor::DoQuadEnvPoint(const CQuad *pQuad, int QIndex, int PIndex) float CenterX = fx2f(pQuad->m_aPoints[4].x) + fx2f(pEnvelope->m_vPoints[PIndex].m_aValues[0]); float CenterY = fx2f(pQuad->m_aPoints[4].y) + fx2f(pEnvelope->m_vPoints[PIndex].m_aValues[1]); - float dx = (CenterX - wx) / m_WorldZoom; - float dy = (CenterY - wy) / m_WorldZoom; + float dx = (CenterX - wx) / m_MouseWScale; + float dy = (CenterY - wy) / m_MouseWScale; if(dx * dx + dy * dy < 50.0f && UI()->CheckActiveItem(nullptr)) { UI()->SetHotItem(pID); @@ -2289,7 +2303,7 @@ void CEditor::DoQuadEnvPoint(const CQuad *pQuad, int QIndex, int PIndex) else Graphics()->SetColor(0.0f, 1.0f, 0.0f, 1.0f); - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f * m_WorldZoom, 5.0f * m_WorldZoom); + IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); Graphics()->QuadsDraw(&QuadItem, 1); } @@ -2723,6 +2737,7 @@ void CEditor::DoMapEditor(CUIRect View) m_Brush.m_OffsetY += pGroup->m_OffsetY; m_Brush.m_ParallaxX = pGroup->m_ParallaxX; m_Brush.m_ParallaxY = pGroup->m_ParallaxY; + m_Brush.m_ParallaxZoom = pGroup->m_ParallaxZoom; m_Brush.Render(); float w, h; m_Brush.GetSize(&w, &h); @@ -2799,13 +2814,13 @@ void CEditor::DoMapEditor(CUIRect View) { if(s_Operation == OP_PAN_WORLD) { - m_WorldOffsetX -= m_MouseDeltaX * m_WorldZoom; - m_WorldOffsetY -= m_MouseDeltaY * m_WorldZoom; + m_WorldOffsetX -= m_MouseDeltaX * m_MouseWScale; + m_WorldOffsetY -= m_MouseDeltaY * m_MouseWScale; } else if(s_Operation == OP_PAN_EDITOR) { - m_EditorOffsetX -= m_MouseDeltaX * m_WorldZoom; - m_EditorOffsetY -= m_MouseDeltaY * m_WorldZoom; + m_EditorOffsetX -= m_MouseDeltaX * m_MouseWScale; + m_EditorOffsetY -= m_MouseDeltaY * m_MouseWScale; } // release mouse @@ -2821,13 +2836,13 @@ void CEditor::DoMapEditor(CUIRect View) { float PanSpeed = 64.0f; if(Input()->KeyPress(KEY_A)) - m_WorldOffsetX -= PanSpeed * m_WorldZoom; + m_WorldOffsetX -= PanSpeed * m_MouseWScale; else if(Input()->KeyPress(KEY_D)) - m_WorldOffsetX += PanSpeed * m_WorldZoom; + m_WorldOffsetX += PanSpeed * m_MouseWScale; if(Input()->KeyPress(KEY_W)) - m_WorldOffsetY -= PanSpeed * m_WorldZoom; + m_WorldOffsetY -= PanSpeed * m_MouseWScale; else if(Input()->KeyPress(KEY_S)) - m_WorldOffsetY += PanSpeed * m_WorldZoom; + m_WorldOffsetY += PanSpeed * m_MouseWScale; } } else if(UI()->CheckActiveItem(s_pEditorID)) @@ -2886,7 +2901,7 @@ void CEditor::DoMapEditor(CUIRect View) RenderTools()->MapScreenToWorld( m_WorldOffsetX, m_WorldOffsetY, - 100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints); + 100.0f, 100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints); if(i == 0) { @@ -2928,7 +2943,7 @@ void CEditor::DoMapEditor(CUIRect View) RenderTools()->MapScreenToWorld( m_WorldOffsetX, m_WorldOffsetY, - 100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints); + 100.0f, 100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints); CUIRect r; r.x = aPoints[0]; @@ -3270,243 +3285,182 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * return Change; } -void CEditor::RenderLayers(CUIRect ToolBox, CUIRect View) +void CEditor::RenderLayers(CUIRect LayersBox) { - if(!m_GuiActive) - return; - - CUIRect LayersBox = ToolBox; - CUIRect Slot, Button; + const float RowHeight = 12.0f; char aBuf[64]; - float LayersHeight = 12.0f; // Height of AddGroup button - static float s_ScrollValue = 0; + static CScrollRegion s_ScrollRegion; + vec2 ScrollOffset(0.0f, 0.0f); + CScrollRegionParams ScrollParams; + ScrollParams.m_ScrollbarWidth = 10.0f; + ScrollParams.m_ScrollbarMargin = 3.0f; + ScrollParams.m_ScrollUnit = RowHeight * 5.0f; + s_ScrollRegion.Begin(&LayersBox, &ScrollOffset, &ScrollParams); + LayersBox.y += ScrollOffset.y; - for(auto &pGroup : m_Map.m_vpGroups) - { - // Each group is 19.0f - // Each layer is 14.0f - LayersHeight += 19.0f; - if(!pGroup->m_Collapse) - LayersHeight += pGroup->m_vpLayers.size() * 14.0f; - } - - float ScrollDifference = LayersHeight - LayersBox.h; - - if(LayersHeight > LayersBox.h) // Do we even need a scrollbar? - { - CUIRect Scroll; - LayersBox.VSplitRight(20.0f, &LayersBox, &Scroll); - s_ScrollValue = UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue); - - if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&LayersBox)) - { - int ScrollNum = (int)((LayersHeight - LayersBox.h) / 15.0f) + 1; - if(ScrollNum > 0) - { - if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f / ScrollNum, 0.0f, 1.0f); - if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f / ScrollNum, 0.0f, 1.0f); - } - } - } - - float LayerStartAt = ScrollDifference * s_ScrollValue; - if(LayerStartAt < 0.0f) - LayerStartAt = 0.0f; - - float LayerStopAt = LayersHeight - ScrollDifference * (1 - s_ScrollValue); - float LayerCur = 0; + const bool ScrollToSelection = SelectLayerByTile(); // render layers + for(int g = 0; g < (int)m_Map.m_vpGroups.size(); g++) { - for(int g = 0; g < (int)m_Map.m_vpGroups.size(); g++) + CUIRect Slot, VisibleToggle; + LayersBox.HSplitTop(RowHeight, &Slot, &LayersBox); + if(s_ScrollRegion.AddRect(Slot)) { - if(LayerCur > LayerStopAt) - break; - else if(LayerCur + m_Map.m_vpGroups[g]->m_vpLayers.size() * 14.0f + 19.0f < LayerStartAt) + Slot.VSplitLeft(12.0f, &VisibleToggle, &Slot); + if(DoButton_Ex(&m_Map.m_vpGroups[g]->m_Visible, m_Map.m_vpGroups[g]->m_Visible ? "V" : "H", m_Map.m_vpGroups[g]->m_Collapse ? 1 : 0, &VisibleToggle, 0, "Toggle group visibility", IGraphics::CORNER_L, 10.0f, 0)) + m_Map.m_vpGroups[g]->m_Visible = !m_Map.m_vpGroups[g]->m_Visible; + + str_format(aBuf, sizeof(aBuf), "#%d %s", g, m_Map.m_vpGroups[g]->m_aName); + float FontSize = 10.0f; + while(TextRender()->TextWidth(nullptr, FontSize, aBuf, -1, -1.0f) > Slot.w && FontSize >= 7.0f) + FontSize--; + if(int Result = DoButton_Ex(&m_Map.m_vpGroups[g], aBuf, g == m_SelectedGroup, &Slot, + BUTTON_CONTEXT, m_Map.m_vpGroups[g]->m_Collapse ? "Select group. Shift click to select all layers. Double click to expand." : "Select group. Shift click to select all layers. Double click to collapse.", IGraphics::CORNER_R, FontSize)) { - LayerCur += m_Map.m_vpGroups[g]->m_vpLayers.size() * 14.0f + 19.0f; + if(g != m_SelectedGroup) + SelectLayer(0, g); + + if((Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT)) && m_SelectedGroup == g) + { + m_vSelectedLayers.clear(); + for(size_t i = 0; i < m_Map.m_vpGroups[g]->m_vpLayers.size(); i++) + { + AddSelectedLayer(i); + } + } + + static int s_GroupPopupId = 0; + if(Result == 2) + UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 145, 256, PopupGroup); + + if(!m_Map.m_vpGroups[g]->m_vpLayers.empty() && Input()->MouseDoubleClick()) + m_Map.m_vpGroups[g]->m_Collapse ^= 1; + } + } + + LayersBox.HSplitTop(2.0f, &Slot, &LayersBox); + s_ScrollRegion.AddRect(Slot); + + for(int i = 0; i < (int)m_Map.m_vpGroups[g]->m_vpLayers.size(); i++) + { + if(m_Map.m_vpGroups[g]->m_Collapse) continue; + + bool IsLayerSelected = false; + if(m_SelectedGroup == g) + { + for(const auto &Selected : m_vSelectedLayers) + { + if(Selected == i) + { + IsLayerSelected = true; + break; + } + } } - CUIRect VisibleToggle; - if(LayerCur >= LayerStartAt) + LayersBox.HSplitTop(RowHeight + 2.0f, &Slot, &LayersBox); + if(!s_ScrollRegion.AddRect(Slot, ScrollToSelection && IsLayerSelected)) + continue; + Slot.HSplitTop(RowHeight, &Slot, nullptr); + + CUIRect Button; + Slot.VSplitLeft(12.0f, nullptr, &Slot); + Slot.VSplitLeft(15.0f, &VisibleToggle, &Button); + + if(DoButton_Ex(&m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Visible, m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Visible ? "V" : "H", 0, &VisibleToggle, 0, "Toggle layer visibility", IGraphics::CORNER_L, 10.0f, 0)) + m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Visible = !m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Visible; + + if(m_Map.m_vpGroups[g]->m_vpLayers[i]->m_aName[0]) + str_copy(aBuf, m_Map.m_vpGroups[g]->m_vpLayers[i]->m_aName, sizeof(aBuf)); + else { - LayersBox.HSplitTop(12.0f, &Slot, &LayersBox); - Slot.VSplitLeft(12, &VisibleToggle, &Slot); - if(DoButton_Ex(&m_Map.m_vpGroups[g]->m_Visible, m_Map.m_vpGroups[g]->m_Visible ? "V" : "H", m_Map.m_vpGroups[g]->m_Collapse ? 1 : 0, &VisibleToggle, 0, "Toggle group visibility", IGraphics::CORNER_L, 10.0f, 0)) - m_Map.m_vpGroups[g]->m_Visible = !m_Map.m_vpGroups[g]->m_Visible; - - str_format(aBuf, sizeof(aBuf), "#%d %s", g, m_Map.m_vpGroups[g]->m_aName); - float FontSize = 10.0f; - while(TextRender()->TextWidth(nullptr, FontSize, aBuf, -1, -1.0f) > Slot.w) - FontSize--; - if(int Result = DoButton_Ex(&m_Map.m_vpGroups[g], aBuf, g == m_SelectedGroup, &Slot, - BUTTON_CONTEXT, m_Map.m_vpGroups[g]->m_Collapse ? "Select group. Shift click to select all layers. Double click to expand." : "Select group. Shift click to select all layers. Double click to collapse.", IGraphics::CORNER_R, FontSize)) + if(m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Type == LAYERTYPE_TILES) { - if(g != m_SelectedGroup) - SelectLayer(0, g); + CLayerTiles *pTiles = (CLayerTiles *)m_Map.m_vpGroups[g]->m_vpLayers[i]; + str_copy(aBuf, pTiles->m_Image >= 0 ? m_Map.m_vpImages[pTiles->m_Image]->m_aName : "Tiles", sizeof(aBuf)); + } + else if(m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Type == LAYERTYPE_QUADS) + { + CLayerQuads *pQuads = (CLayerQuads *)m_Map.m_vpGroups[g]->m_vpLayers[i]; + str_copy(aBuf, pQuads->m_Image >= 0 ? m_Map.m_vpImages[pQuads->m_Image]->m_aName : "Quads", sizeof(aBuf)); + } + else if(m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Type == LAYERTYPE_SOUNDS) + { + CLayerSounds *pSounds = (CLayerSounds *)m_Map.m_vpGroups[g]->m_vpLayers[i]; + str_copy(aBuf, pSounds->m_Sound >= 0 ? m_Map.m_vpSounds[pSounds->m_Sound]->m_aName : "Sounds", sizeof(aBuf)); + } + if(str_length(aBuf) > 11) + str_format(aBuf, sizeof(aBuf), "%.8s...", aBuf); + } + float FontSize = 10.0f; + while(TextRender()->TextWidth(nullptr, FontSize, aBuf, -1, -1.0f) > Button.w && FontSize >= 7.0f) + FontSize--; + + int Checked = IsLayerSelected ? 1 : 0; + if(m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pGameLayer || + m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pFrontLayer || + m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pSwitchLayer || + m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pTuneLayer || + m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pSpeedupLayer || + m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pTeleLayer) + { + Checked += 6; + } + if(int Result = DoButton_Ex(m_Map.m_vpGroups[g]->m_vpLayers[i], aBuf, Checked, &Button, + BUTTON_CONTEXT, "Select layer. Shift click to select multiple.", IGraphics::CORNER_R, FontSize)) + { + static CLayerPopupContext s_LayerPopupContext = {}; + if(Result == 1) + { if((Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT)) && m_SelectedGroup == g) { - m_vSelectedLayers.clear(); - for(size_t i = 0; i < m_Map.m_vpGroups[g]->m_vpLayers.size(); i++) - { - AddSelectedLayer(i); - } - } - - static int s_GroupPopupId = 0; - if(Result == 2) - UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 145, 230, PopupGroup); - - if(!m_Map.m_vpGroups[g]->m_vpLayers.empty() && Input()->MouseDoubleClick()) - m_Map.m_vpGroups[g]->m_Collapse ^= 1; - } - LayersBox.HSplitTop(2.0f, &Slot, &LayersBox); - } - LayerCur += 14.0f; - - for(int i = 0; i < (int)m_Map.m_vpGroups[g]->m_vpLayers.size(); i++) - { - if(LayerCur > LayerStopAt) - break; - else if(LayerCur < LayerStartAt) - { - LayerCur += 14.0f; - continue; - } - - if(m_Map.m_vpGroups[g]->m_Collapse) - continue; - - //visible - LayersBox.HSplitTop(12.0f, &Slot, &LayersBox); - Slot.VSplitLeft(12.0f, nullptr, &Button); - Button.VSplitLeft(15, &VisibleToggle, &Button); - - if(DoButton_Ex(&m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Visible, m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Visible ? "V" : "H", 0, &VisibleToggle, 0, "Toggle layer visibility", IGraphics::CORNER_L, 10.0f, 0)) - m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Visible = !m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Visible; - - if(m_Map.m_vpGroups[g]->m_vpLayers[i]->m_aName[0]) - str_copy(aBuf, m_Map.m_vpGroups[g]->m_vpLayers[i]->m_aName, sizeof(aBuf)); - else - { - if(m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Type == LAYERTYPE_TILES) - { - CLayerTiles *pTiles = (CLayerTiles *)m_Map.m_vpGroups[g]->m_vpLayers[i]; - str_copy(aBuf, pTiles->m_Image >= 0 ? m_Map.m_vpImages[pTiles->m_Image]->m_aName : "Tiles", sizeof(aBuf)); - } - else if(m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Type == LAYERTYPE_QUADS) - { - CLayerQuads *pQuads = (CLayerQuads *)m_Map.m_vpGroups[g]->m_vpLayers[i]; - str_copy(aBuf, pQuads->m_Image >= 0 ? m_Map.m_vpImages[pQuads->m_Image]->m_aName : "Quads", sizeof(aBuf)); - } - else if(m_Map.m_vpGroups[g]->m_vpLayers[i]->m_Type == LAYERTYPE_SOUNDS) - { - CLayerSounds *pSounds = (CLayerSounds *)m_Map.m_vpGroups[g]->m_vpLayers[i]; - str_copy(aBuf, pSounds->m_Sound >= 0 ? m_Map.m_vpSounds[pSounds->m_Sound]->m_aName : "Sounds", sizeof(aBuf)); - } - if(str_length(aBuf) > 11) - str_format(aBuf, sizeof(aBuf), "%.8s...", aBuf); - } - - float FontSize = 10.0f; - while(TextRender()->TextWidth(nullptr, FontSize, aBuf, -1, -1.0f) > Button.w) - FontSize--; - int Checked = 0; - if(g == m_SelectedGroup) - { - for(const auto &Selected : m_vSelectedLayers) - { - if(Selected == i) - { - Checked = 1; - break; - } - } - } - - if(m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pGameLayer || - m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pFrontLayer || - m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pSwitchLayer || - m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pTuneLayer || - m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pSpeedupLayer || - m_Map.m_vpGroups[g]->m_vpLayers[i] == m_Map.m_pTeleLayer) - { - Checked += 6; - } - if(int Result = DoButton_Ex(m_Map.m_vpGroups[g]->m_vpLayers[i], aBuf, Checked, &Button, - BUTTON_CONTEXT, "Select layer. Shift click to select multiple.", IGraphics::CORNER_R, FontSize)) - { - static CLayerPopupContext s_LayerPopupContext = {}; - if(Result == 1) - { - if((Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT)) && m_SelectedGroup == g) - { - auto Position = std::find(m_vSelectedLayers.begin(), m_vSelectedLayers.end(), i); - if(Position != m_vSelectedLayers.end()) - m_vSelectedLayers.erase(Position); - else - AddSelectedLayer(i); - } - else if(!(Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT))) - { - SelectLayer(i, g); - } - } - else if(Result == 2) - { - bool IsLayerSelected = false; - - if(m_SelectedGroup == g) - { - for(const auto &Selected : m_vSelectedLayers) - { - if(Selected == i) - { - IsLayerSelected = true; - break; - } - } - } - - if(!IsLayerSelected) - { - SelectLayer(i, g); - } - - if(m_vSelectedLayers.size() > 1) - { - bool AllTile = true; - for(size_t j = 0; AllTile && j < m_vSelectedLayers.size(); j++) - { - if(m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[m_vSelectedLayers[j]]->m_Type == LAYERTYPE_TILES) - s_LayerPopupContext.m_vpLayers.push_back((CLayerTiles *)m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[m_vSelectedLayers[j]]); - else - AllTile = false; - } - - // Don't allow editing if all selected layers are tile layers - if(!AllTile) - s_LayerPopupContext.m_vpLayers.clear(); - } + auto Position = std::find(m_vSelectedLayers.begin(), m_vSelectedLayers.end(), i); + if(Position != m_vSelectedLayers.end()) + m_vSelectedLayers.erase(Position); else - s_LayerPopupContext.m_vpLayers.clear(); - - UiInvokePopupMenu(&s_LayerPopupContext, 0, UI()->MouseX(), UI()->MouseY(), 120, 320, PopupLayer, &s_LayerPopupContext); + AddSelectedLayer(i); + } + else if(!(Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT))) + { + SelectLayer(i, g); } } + else if(Result == 2) + { + if(!IsLayerSelected) + { + SelectLayer(i, g); + } - LayerCur += 14.0f; - LayersBox.HSplitTop(2.0f, &Slot, &LayersBox); + if(m_vSelectedLayers.size() > 1) + { + bool AllTile = true; + for(size_t j = 0; AllTile && j < m_vSelectedLayers.size(); j++) + { + if(m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[m_vSelectedLayers[j]]->m_Type == LAYERTYPE_TILES) + s_LayerPopupContext.m_vpLayers.push_back((CLayerTiles *)m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[m_vSelectedLayers[j]]); + else + AllTile = false; + } + + // Don't allow editing if all selected layers are tile layers + if(!AllTile) + s_LayerPopupContext.m_vpLayers.clear(); + } + else + s_LayerPopupContext.m_vpLayers.clear(); + + UiInvokePopupMenu(&s_LayerPopupContext, 0, UI()->MouseX(), UI()->MouseY(), 120, 320, PopupLayer, &s_LayerPopupContext); + } } - if(LayerCur > LayerStartAt && LayerCur < LayerStopAt) - LayersBox.HSplitTop(5.0f, &Slot, &LayersBox); - LayerCur += 5.0f; } + + LayersBox.HSplitTop(5.0f, &Slot, &LayersBox); + s_ScrollRegion.AddRect(Slot); } if(Input()->KeyPress(KEY_DOWN) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0) @@ -3572,22 +3526,23 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect View) } } - if(LayerCur <= LayerStopAt) + CUIRect AddGroupButton; + LayersBox.HSplitTop(RowHeight + 1.0f, &AddGroupButton, &LayersBox); + if(s_ScrollRegion.AddRect(AddGroupButton)) { - LayersBox.HSplitTop(12.0f, &Slot, &LayersBox); - - static int s_NewGroupButton = 0; - if(DoButton_Editor(&s_NewGroupButton, "Add group", 0, &Slot, IGraphics::CORNER_R, "Adds a new group")) + AddGroupButton.HSplitTop(RowHeight, &AddGroupButton, 0); + static int s_AddGroupButton = 0; + if(DoButton_Editor(&s_AddGroupButton, "Add group", 0, &AddGroupButton, IGraphics::CORNER_R, "Adds a new group")) { m_Map.NewGroup(); m_SelectedGroup = m_Map.m_vpGroups.size() - 1; } } - SelectLayerByTile(s_ScrollValue); + s_ScrollRegion.End(); } -void CEditor::SelectLayerByTile(float &Scroll) +bool CEditor::SelectLayerByTile() { // ctrl+rightclick a map index to select the layer that has a tile there static bool s_CtrlClick = false; @@ -3596,18 +3551,15 @@ void CEditor::SelectLayerByTile(float &Scroll) int MatchedLayer = -1; int Matches = 0; bool IsFound = false; - int TotalLayers = 0; - int SelectedLayer = 0; if(UI()->MouseButton(1) && Input()->ModifierIsPressed()) { if(s_CtrlClick) - return; + return false; s_CtrlClick = true; for(size_t g = 0; g < m_Map.m_vpGroups.size(); g++) { for(size_t l = 0; l < m_Map.m_vpGroups[g]->m_vpLayers.size(); l++) { - TotalLayers++; if(IsFound) continue; if(m_Map.m_vpGroups[g]->m_vpLayers[l]->m_Type != LAYERTYPE_TILES) @@ -3627,7 +3579,6 @@ void CEditor::SelectLayerByTile(float &Scroll) { MatchedGroup = g; MatchedLayer = l; - SelectedLayer = TotalLayers; } if(++Matches > s_Selected) { @@ -3635,7 +3586,6 @@ void CEditor::SelectLayerByTile(float &Scroll) MatchedGroup = g; MatchedLayer = l; IsFound = true; - SelectedLayer = TotalLayers; } } } @@ -3644,12 +3594,13 @@ void CEditor::SelectLayerByTile(float &Scroll) { if(!IsFound) s_Selected = 1; - Scroll = (float)SelectedLayer / (float)TotalLayers; SelectLayer(MatchedLayer, MatchedGroup); + return true; } } else s_CtrlClick = false; + return false; } void CEditor::ReplaceImage(const char *pFileName, int StorageType, void *pUser) @@ -3971,136 +3922,20 @@ void CEditor::SortImages() } } -void CEditor::RenderImages(CUIRect ToolBox, CUIRect View) +void CEditor::RenderImagesList(CUIRect ToolBox) { - if(!m_GuiActive) - return; + const float RowHeight = 12.0f; - static float s_ScrollValue = 0; - float ImagesHeight = 30.0f + 14.0f * m_Map.m_vpImages.size() + 27.0f; - float ScrollDifference = ImagesHeight - ToolBox.h; - - if(ImagesHeight > ToolBox.h) // Do we even need a scrollbar? - { - CUIRect Scroll; - ToolBox.VSplitRight(20.0f, &ToolBox, &Scroll); - s_ScrollValue = UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue); - - if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&ToolBox)) - { - int ScrollNum = (int)((ImagesHeight - ToolBox.h) / 14.0f) + 1; - if(ScrollNum > 0) - { - if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f / ScrollNum, 0.0f, 1.0f); - if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f / ScrollNum, 0.0f, 1.0f); - } - } - } - - float ImageStartAt = ScrollDifference * s_ScrollValue; - if(ImageStartAt < 0.0f) - ImageStartAt = 0.0f; - - float ImageStopAt = ImagesHeight - ScrollDifference * (1 - s_ScrollValue); - float ImageCur = 0.0f; - - for(int e = 0; e < 2; e++) // two passes, first embedded, then external - { - CUIRect Slot; - - if(ImageCur > ImageStopAt) - break; - else if(ImageCur >= ImageStartAt) - { - ToolBox.HSplitTop(15.0f, &Slot, &ToolBox); - if(e == 0) - UI()->DoLabel(&Slot, "Embedded", 12.0f, TEXTALIGN_CENTER); - else - UI()->DoLabel(&Slot, "External", 12.0f, TEXTALIGN_CENTER); - } - ImageCur += 15.0f; - - for(int i = 0; i < (int)m_Map.m_vpImages.size(); i++) - { - if((e && !m_Map.m_vpImages[i]->m_External) || - (!e && m_Map.m_vpImages[i]->m_External)) - { - continue; - } - - if(ImageCur > ImageStopAt) - break; - else if(ImageCur < ImageStartAt) - { - ImageCur += 14.0f; - continue; - } - ImageCur += 14.0f; - - char aBuf[128]; - str_copy(aBuf, m_Map.m_vpImages[i]->m_aName, sizeof(aBuf)); - ToolBox.HSplitTop(12.0f, &Slot, &ToolBox); - - int Selected = m_SelectedImage == i; - - const bool ImageUsed = std::any_of(m_Map.m_vpGroups.cbegin(), m_Map.m_vpGroups.cend(), [i](const auto &pGroup) { - return std::any_of(pGroup->m_vpLayers.cbegin(), pGroup->m_vpLayers.cend(), [i](const auto &pLayer) { - if(pLayer->m_Type == LAYERTYPE_QUADS) - return static_cast(pLayer)->m_Image == i; - else if(pLayer->m_Type == LAYERTYPE_TILES) - return static_cast(pLayer)->m_Image == i; - return false; - }); - }); - - if(!ImageUsed) - Selected += 2; // Image is unused - - if(Selected < 2 && e == 1) - { - if(!IsVanillaImage(m_Map.m_vpImages[i]->m_aName)) - { - Selected += 4; // Image should be embedded - } - } - - float FontSize = 10.0f; - while(TextRender()->TextWidth(nullptr, FontSize, aBuf, -1, -1.0f) > Slot.w) - FontSize--; - - if(int Result = DoButton_Ex(&m_Map.m_vpImages[i], aBuf, Selected, &Slot, - BUTTON_CONTEXT, "Select image.", 0, FontSize)) - { - m_SelectedImage = i; - - static int s_PopupImageID = 0; - if(Result == 2) - { - CEditorImage *pImg = m_Map.m_vpImages[m_SelectedImage]; - int Height; - if(pImg->m_External || IsVanillaImage(pImg->m_aName)) - Height = 60; - else - Height = 43; - UiInvokePopupMenu(&s_PopupImageID, 0, UI()->MouseX(), UI()->MouseY(), 120, Height, PopupImage); - } - } - - ToolBox.HSplitTop(2.0f, nullptr, &ToolBox); - } - - // separator - ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); - ImageCur += 5.0f; - IGraphics::CLineItem LineItem(Slot.x, Slot.y + Slot.h / 2, Slot.x + Slot.w, Slot.y + Slot.h / 2); - Graphics()->TextureClear(); - Graphics()->LinesBegin(); - Graphics()->LinesDraw(&LineItem, 1); - Graphics()->LinesEnd(); - } + static CScrollRegion s_ScrollRegion; + vec2 ScrollOffset(0.0f, 0.0f); + CScrollRegionParams ScrollParams; + ScrollParams.m_ScrollbarWidth = 10.0f; + ScrollParams.m_ScrollbarMargin = 3.0f; + ScrollParams.m_ScrollUnit = RowHeight * 5; + s_ScrollRegion.Begin(&ToolBox, &ScrollOffset, &ScrollParams); + ToolBox.y += ScrollOffset.y; + bool ScrollToSelection = false; if(Input()->KeyPress(KEY_DOWN) && m_Dialog == DIALOG_NONE) { int OldImage = m_SelectedImage; @@ -4124,6 +3959,7 @@ void CEditor::RenderImages(CUIRect ToolBox, CUIRect View) } } } + ScrollToSelection = OldImage != m_SelectedImage; } if(Input()->KeyPress(KEY_UP) && m_Dialog == DIALOG_NONE) { @@ -4148,100 +3984,161 @@ void CEditor::RenderImages(CUIRect ToolBox, CUIRect View) } } } + ScrollToSelection = OldImage != m_SelectedImage; } - // render image - int i = m_SelectedImage; - if(i >= 0 && (size_t)i < m_Map.m_vpImages.size()) + for(int e = 0; e < 2; e++) // two passes, first embedded, then external { - CUIRect r; - View.Margin(10.0f, &r); - if(r.h < r.w) - r.w = r.h; - else - r.h = r.w; - float Max = (float)(maximum(m_Map.m_vpImages[i]->m_Width, m_Map.m_vpImages[i]->m_Height)); - r.w *= m_Map.m_vpImages[i]->m_Width / Max; - r.h *= m_Map.m_vpImages[i]->m_Height / Max; - Graphics()->TextureSet(m_Map.m_vpImages[i]->m_Texture); - Graphics()->BlendNormal(); - Graphics()->WrapClamp(); - Graphics()->QuadsBegin(); - IGraphics::CQuadItem QuadItem(r.x, r.y, r.w, r.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - Graphics()->WrapNormal(); - } - //if(ImageCur + 27.0f > ImageStopAt) - // return; + CUIRect Slot; + ToolBox.HSplitTop(RowHeight + 3.0f, &Slot, &ToolBox); + if(s_ScrollRegion.AddRect(Slot)) + UI()->DoLabel(&Slot, e == 0 ? "Embedded" : "External", 12.0f, TEXTALIGN_CENTER); - CUIRect Slot; - ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); - - // new image - static int s_NewImageButton = 0; - ToolBox.HSplitTop(12.0f, &Slot, &ToolBox); - if(DoButton_Editor(&s_NewImageButton, "Add", 0, &Slot, 0, "Load a new image to use in the map")) - InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add Image", "Add", "mapres", "", AddImage, this); -} - -void CEditor::RenderSounds(CUIRect ToolBox, CUIRect View) -{ - if(!m_GuiActive) - return; - - static float s_ScrollValue = 0; - float SoundsHeight = 30.0f + 14.0f * m_Map.m_vpSounds.size() + 27.0f; - float ScrollDifference = SoundsHeight - ToolBox.h; - - if(SoundsHeight > ToolBox.h) // Do we even need a scrollbar? - { - CUIRect Scroll; - ToolBox.VSplitRight(20.0f, &ToolBox, &Scroll); - s_ScrollValue = UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue); - - if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&ToolBox)) + for(int i = 0; i < (int)m_Map.m_vpImages.size(); i++) { - int ScrollNum = (int)((SoundsHeight - ToolBox.h) / 14.0f) + 1; - if(ScrollNum > 0) + if((e && !m_Map.m_vpImages[i]->m_External) || + (!e && m_Map.m_vpImages[i]->m_External)) { - if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f / ScrollNum, 0.0f, 1.0f); - if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f / ScrollNum, 0.0f, 1.0f); + continue; } + + ToolBox.HSplitTop(RowHeight + 2.0f, &Slot, &ToolBox); + int Selected = m_SelectedImage == i; + if(!s_ScrollRegion.AddRect(Slot, Selected && ScrollToSelection)) + continue; + Slot.HSplitTop(RowHeight, &Slot, nullptr); + + const bool ImageUsed = std::any_of(m_Map.m_vpGroups.cbegin(), m_Map.m_vpGroups.cend(), [i](const auto &pGroup) { + return std::any_of(pGroup->m_vpLayers.cbegin(), pGroup->m_vpLayers.cend(), [i](const auto &pLayer) { + if(pLayer->m_Type == LAYERTYPE_QUADS) + return static_cast(pLayer)->m_Image == i; + else if(pLayer->m_Type == LAYERTYPE_TILES) + return static_cast(pLayer)->m_Image == i; + return false; + }); + }); + + if(!ImageUsed) + Selected += 2; // Image is unused + + if(Selected < 2 && e == 1) + { + if(!IsVanillaImage(m_Map.m_vpImages[i]->m_aName)) + { + Selected += 4; // Image should be embedded + } + } + + float FontSize = 10.0f; + while(TextRender()->TextWidth(nullptr, FontSize, m_Map.m_vpImages[i]->m_aName, -1, -1.0f) > Slot.w) + FontSize--; + + if(int Result = DoButton_Ex(&m_Map.m_vpImages[i], m_Map.m_vpImages[i]->m_aName, Selected, &Slot, + BUTTON_CONTEXT, "Select image.", IGraphics::CORNER_ALL, FontSize)) + { + m_SelectedImage = i; + + static int s_PopupImageID = 0; + if(Result == 2) + { + CEditorImage *pImg = m_Map.m_vpImages[m_SelectedImage]; + int Height; + if(pImg->m_External || IsVanillaImage(pImg->m_aName)) + Height = 60; + else + Height = 43; + UiInvokePopupMenu(&s_PopupImageID, 0, UI()->MouseX(), UI()->MouseY(), 120, Height, PopupImage); + } + } + } + + // separator + ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); + if(s_ScrollRegion.AddRect(Slot)) + { + IGraphics::CLineItem LineItem(Slot.x, Slot.y + Slot.h / 2, Slot.x + Slot.w, Slot.y + Slot.h / 2); + Graphics()->TextureClear(); + Graphics()->LinesBegin(); + Graphics()->LinesDraw(&LineItem, 1); + Graphics()->LinesEnd(); } } - float SoundStartAt = ScrollDifference * s_ScrollValue; - if(SoundStartAt < 0.0f) - SoundStartAt = 0.0f; + // new image + static int s_AddImageButton = 0; + CUIRect AddImageButton; + ToolBox.HSplitTop(5.0f + RowHeight + 1.0f, &AddImageButton, &ToolBox); + if(s_ScrollRegion.AddRect(AddImageButton)) + { + AddImageButton.HSplitTop(5.0f, nullptr, &AddImageButton); + AddImageButton.HSplitTop(RowHeight, &AddImageButton, nullptr); + if(DoButton_Editor(&s_AddImageButton, "Add", 0, &AddImageButton, 0, "Load a new image to use in the map")) + InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add Image", "Add", "mapres", "", AddImage, this); + } + s_ScrollRegion.End(); +} - float SoundStopAt = SoundsHeight - ScrollDifference * (1 - s_ScrollValue); - float SoundCur = 0.0f; +void CEditor::RenderSelectedImage(CUIRect View) +{ + if(m_SelectedImage < 0 || (size_t)m_SelectedImage >= m_Map.m_vpImages.size()) + return; + + View.Margin(10.0f, &View); + if(View.h < View.w) + View.w = View.h; + else + View.h = View.w; + float Max = maximum(m_Map.m_vpImages[m_SelectedImage]->m_Width, m_Map.m_vpImages[m_SelectedImage]->m_Height); + View.w *= m_Map.m_vpImages[m_SelectedImage]->m_Width / Max; + View.h *= m_Map.m_vpImages[m_SelectedImage]->m_Height / Max; + Graphics()->TextureSet(m_Map.m_vpImages[m_SelectedImage]->m_Texture); + Graphics()->BlendNormal(); + Graphics()->WrapClamp(); + Graphics()->QuadsBegin(); + IGraphics::CQuadItem QuadItem(View.x, View.y, View.w, View.h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + Graphics()->WrapNormal(); +} + +void CEditor::RenderSounds(CUIRect ToolBox) +{ + const float RowHeight = 12.0f; + + static CScrollRegion s_ScrollRegion; + vec2 ScrollOffset(0.0f, 0.0f); + CScrollRegionParams ScrollParams; + ScrollParams.m_ScrollbarWidth = 10.0f; + ScrollParams.m_ScrollbarMargin = 3.0f; + ScrollParams.m_ScrollUnit = RowHeight * 5; + s_ScrollRegion.Begin(&ToolBox, &ScrollOffset, &ScrollParams); + ToolBox.y += ScrollOffset.y; + + bool ScrollToSelection = false; + if(Input()->KeyPress(KEY_DOWN) && m_Dialog == DIALOG_NONE) + { + m_SelectedSound = (m_SelectedSound + 1) % m_Map.m_vpSounds.size(); + ScrollToSelection = true; + } + if(Input()->KeyPress(KEY_UP) && m_Dialog == DIALOG_NONE) + { + m_SelectedSound = (m_SelectedSound + m_Map.m_vpSounds.size() - 1) % m_Map.m_vpSounds.size(); + ScrollToSelection = true; + } CUIRect Slot; - - ToolBox.HSplitTop(15.0f, &Slot, &ToolBox); - UI()->DoLabel(&Slot, "Embedded", 12.0f, TEXTALIGN_CENTER); - SoundCur += 15.0f; + ToolBox.HSplitTop(RowHeight + 3.0f, &Slot, &ToolBox); + if(s_ScrollRegion.AddRect(Slot)) + UI()->DoLabel(&Slot, "Embedded", 12.0f, TEXTALIGN_CENTER); for(int i = 0; i < (int)m_Map.m_vpSounds.size(); i++) { - if(SoundCur > SoundStopAt) - break; - else if(SoundCur < SoundStartAt) - { - SoundCur += 14.0f; - continue; - } - SoundCur += 14.0f; - - char aBuf[128]; - str_copy(aBuf, m_Map.m_vpSounds[i]->m_aName, sizeof(aBuf)); - ToolBox.HSplitTop(12.0f, &Slot, &ToolBox); - + ToolBox.HSplitTop(RowHeight + 2.0f, &Slot, &ToolBox); int Selected = m_SelectedSound == i; + if(!s_ScrollRegion.AddRect(Slot, Selected && ScrollToSelection)) + continue; + Slot.HSplitTop(RowHeight, &Slot, nullptr); + const bool SoundUsed = std::any_of(m_Map.m_vpGroups.cbegin(), m_Map.m_vpGroups.cend(), [i](const auto &pGroup) { return std::any_of(pGroup->m_vpLayers.cbegin(), pGroup->m_vpLayers.cend(), [i](const auto &pLayer) { if(pLayer->m_Type == LAYERTYPE_SOUNDS) @@ -4254,11 +4151,11 @@ void CEditor::RenderSounds(CUIRect ToolBox, CUIRect View) Selected += 2; // Sound is unused float FontSize = 10.0f; - while(TextRender()->TextWidth(nullptr, FontSize, aBuf, -1, -1.0f) > Slot.w) + while(TextRender()->TextWidth(nullptr, FontSize, m_Map.m_vpSounds[i]->m_aName, -1, -1.0f) > Slot.w) FontSize--; - if(int Result = DoButton_Ex(&m_Map.m_vpSounds[i], aBuf, Selected, &Slot, - BUTTON_CONTEXT, "Select sound.", 0, FontSize)) + if(int Result = DoButton_Ex(&m_Map.m_vpSounds[i], m_Map.m_vpSounds[i]->m_aName, Selected, &Slot, + BUTTON_CONTEXT, "Select sound.", IGraphics::CORNER_ALL, FontSize)) { m_SelectedSound = i; @@ -4266,43 +4163,31 @@ void CEditor::RenderSounds(CUIRect ToolBox, CUIRect View) if(Result == 2) UiInvokePopupMenu(&s_PopupSoundID, 0, UI()->MouseX(), UI()->MouseY(), 120, 43, PopupSound); } - - ToolBox.HSplitTop(2.0f, nullptr, &ToolBox); } // separator ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); - IGraphics::CLineItem LineItem(Slot.x, Slot.y + Slot.h / 2, Slot.x + Slot.w, Slot.y + Slot.h / 2); - Graphics()->TextureClear(); - Graphics()->LinesBegin(); - Graphics()->LinesDraw(&LineItem, 1); - Graphics()->LinesEnd(); - - if(Input()->KeyPress(KEY_DOWN) && m_Dialog == DIALOG_NONE) + if(s_ScrollRegion.AddRect(Slot)) { - m_SelectedSound = clamp(m_SelectedSound, 0, (int)m_Map.m_vpSounds.size() - 1); - if(m_SelectedSound == (int)m_Map.m_vpSounds.size() - 1) - m_SelectedSound = 0; - else - m_SelectedSound += 1; - } - if(Input()->KeyPress(KEY_UP) && m_Dialog == DIALOG_NONE) - { - m_SelectedSound = clamp(m_SelectedSound, 0, (int)m_Map.m_vpSounds.size() - 1); - if(m_SelectedSound == 0 && !m_Map.m_vpSounds.empty()) - m_SelectedSound = m_Map.m_vpSounds.size() - 1; - else - m_SelectedSound -= 1; + IGraphics::CLineItem LineItem(Slot.x, Slot.y + Slot.h / 2, Slot.x + Slot.w, Slot.y + Slot.h / 2); + Graphics()->TextureClear(); + Graphics()->LinesBegin(); + Graphics()->LinesDraw(&LineItem, 1); + Graphics()->LinesEnd(); } - //CUIRect Slot; - ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); - - // new Sound - static int s_NewSoundButton = 0; - ToolBox.HSplitTop(12.0f, &Slot, &ToolBox); - if(DoButton_Editor(&s_NewSoundButton, "Add", 0, &Slot, 0, "Load a new sound to use in the map")) - InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_SOUND, "Add Sound", "Add", "mapres", "", AddSound, this); + // new sound + static int s_AddSoundButton = 0; + CUIRect AddSoundButton; + ToolBox.HSplitTop(5.0f + RowHeight + 1.0f, &AddSoundButton, &ToolBox); + if(s_ScrollRegion.AddRect(AddSoundButton)) + { + AddSoundButton.HSplitTop(5.0f, nullptr, &AddSoundButton); + AddSoundButton.HSplitTop(RowHeight, &AddSoundButton, nullptr); + if(DoButton_Editor(&s_AddSoundButton, "Add", 0, &AddSoundButton, 0, "Load a new sound to use in the map")) + InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_SOUND, "Add Sound", "Add", "mapres", "", AddSound, this); + } + s_ScrollRegion.End(); } static int EditorListdirCallback(const char *pName, int IsDir, int StorageType, void *pUser) @@ -5955,11 +5840,14 @@ void CEditor::Render() } if(m_Mode == MODE_LAYERS) - RenderLayers(ToolBox, View); + RenderLayers(ToolBox); else if(m_Mode == MODE_IMAGES) - RenderImages(ToolBox, View); + { + RenderImagesList(ToolBox); + RenderSelectedImage(View); + } else if(m_Mode == MODE_SOUNDS) - RenderSounds(ToolBox, View); + RenderSounds(ToolBox); } UI()->MapScreen(); @@ -6134,7 +6022,7 @@ void CEditor::ZoomMouseTarget(float ZoomFactor) float aPoints[4]; RenderTools()->MapScreenToWorld( m_WorldOffsetX, m_WorldOffsetY, - 100.0f, 100.0f, 0.0f, 0.0f, Graphics()->ScreenAspect(), m_WorldZoom, aPoints); + 100.0f, 100.0f, 100.0f, 0.0f, 0.0f, Graphics()->ScreenAspect(), m_WorldZoom, aPoints); float WorldWidth = aPoints[2] - aPoints[0]; float WorldHeight = aPoints[3] - aPoints[1]; @@ -6246,6 +6134,8 @@ void CEditorMap::CreateDefault(IGraphics::CTextureHandle EntitiesTexture) CLayerGroup *pGroup = NewGroup(); pGroup->m_ParallaxX = 0; pGroup->m_ParallaxY = 0; + pGroup->m_CustomParallaxZoom = 0; + pGroup->m_ParallaxZoom = 0; CLayerQuads *pLayer = new CLayerQuads; pLayer->m_pEditor = m_pEditor; CQuad *pQuad = pLayer->NewQuad(0, 0, 1600, 1200); @@ -6376,6 +6266,8 @@ void CEditor::PlaceBorderTiles() void CEditor::OnUpdate() { + CUIElementBase::Init(UI()); // update static pointer because game and editor use separate UI + if(!m_EditorWasUsedBefore) { m_EditorWasUsedBefore = true; @@ -6416,6 +6308,8 @@ void CEditor::OnUpdate() float WorldWidth = aPoints[2] - aPoints[0]; float WorldHeight = aPoints[3] - aPoints[1]; + m_MouseWScale = WorldWidth / Graphics()->WindowWidth(); + m_MouseWorldX = aPoints[0] + WorldWidth * (s_MouseX / Graphics()->WindowWidth()); m_MouseWorldY = aPoints[1] + WorldHeight * (s_MouseY / Graphics()->WindowHeight()); m_MouseDeltaWx = m_MouseDeltaX * (WorldWidth / Graphics()->WindowWidth()); diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 604aa7ec2..dead757eb 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -187,6 +188,8 @@ public: int m_ParallaxX; int m_ParallaxY; + int m_CustomParallaxZoom; + int m_ParallaxZoom; int m_UseClipping; int m_ClipX; @@ -253,6 +256,12 @@ public: } }*/ + void OnEdited() + { + if(!m_CustomParallaxZoom) + m_ParallaxZoom = GetParallaxZoomDefault(m_ParallaxX, m_ParallaxY); + } + void Clear() { m_vpLayers.clear(); @@ -788,6 +797,7 @@ public: m_GuiActive = true; m_ProofBorders = false; + m_PreviewZoom = false; m_ShowTileInfo = false; m_ShowDetail = true; @@ -962,6 +972,8 @@ public: bool m_ShowMousePointer; bool m_GuiActive; bool m_ProofBorders; + bool m_PreviewZoom; + float m_MouseWScale = 1.0f; // Mouse (i.e. UI) scale relative to the World (selected Group) float m_MouseX = 0.0f; float m_MouseY = 0.0f; float m_MouseWorldX = 0.0f; @@ -1114,9 +1126,10 @@ public: bool IsEnvelopeUsed(int EnvelopeIndex) const; - void RenderImages(CUIRect Toolbox, CUIRect View); - void RenderLayers(CUIRect Toolbox, CUIRect View); - void RenderSounds(CUIRect Toolbox, CUIRect View); + void RenderLayers(CUIRect LayersBox); + void RenderImagesList(CUIRect Toolbox); + void RenderSelectedImage(CUIRect View); + void RenderSounds(CUIRect Toolbox); void RenderModebar(CUIRect View); void RenderStatusbar(CUIRect View); void RenderEnvelopeEditor(CUIRect View); @@ -1128,7 +1141,7 @@ public: void AddFileDialogEntry(int Index, CUIRect *pView); void SelectGameLayer(); void SortImages(); - void SelectLayerByTile(float &Scroll); + bool SelectLayerByTile(); //Tile Numbers For Explanations - TODO: Add/Improve tiles and explanations enum diff --git a/src/game/editor/io.cpp b/src/game/editor/io.cpp index b0d722c05..a833148ad 100644 --- a/src/game/editor/io.cpp +++ b/src/game/editor/io.cpp @@ -184,6 +184,10 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName) // save group name StrToInts(GItem.m_aName, sizeof(GItem.m_aName) / sizeof(int), pGroup->m_aName); + CMapItemGroupEx GItemEx; + GItemEx.m_Version = CMapItemGroupEx::CURRENT_VERSION; + GItemEx.m_ParallaxZoom = pGroup->m_ParallaxZoom; + for(const auto &pLayer : pGroup->m_vpLayers) { if(pLayer->m_Type == LAYERTYPE_TILES) @@ -331,7 +335,9 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName) } } - df.AddItem(MAPITEMTYPE_GROUP, GroupCount++, sizeof(GItem), &GItem); + df.AddItem(MAPITEMTYPE_GROUP, GroupCount, sizeof(GItem), &GItem); + df.AddItem(MAPITEMTYPE_GROUP_EX, GroupCount, sizeof(GItemEx), &GItemEx); + GroupCount++; } // save envelopes @@ -581,9 +587,15 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag int Start, Num; DataFile.GetType(MAPITEMTYPE_GROUP, &Start, &Num); + + int StartEx, NumEx; + DataFile.GetType(MAPITEMTYPE_GROUP_EX, &StartEx, &NumEx); for(int g = 0; g < Num; g++) { CMapItemGroup *pGItem = (CMapItemGroup *)DataFile.GetItem(Start + g, nullptr, nullptr); + CMapItemGroupEx *pGItemEx = nullptr; + if(NumEx) + pGItemEx = (CMapItemGroupEx *)DataFile.GetItem(StartEx + g, nullptr, nullptr); if(pGItem->m_Version < 1 || pGItem->m_Version > CMapItemGroup::CURRENT_VERSION) continue; @@ -607,6 +619,9 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag if(pGItem->m_Version >= 3) IntsToStr(pGItem->m_aName, sizeof(pGroup->m_aName) / sizeof(int), pGroup->m_aName); + pGroup->m_ParallaxZoom = GetParallaxZoom(pGItem, pGItemEx); + pGroup->m_CustomParallaxZoom = pGroup->m_ParallaxZoom != GetParallaxZoomDefault(pGroup->m_ParallaxX, pGroup->m_ParallaxY); + for(int l = 0; l < pGItem->m_NumLayers; l++) { CLayer *pLayer = nullptr; diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 3b6b29716..5a3863cbf 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "editor.h" // popup menu handling @@ -320,6 +322,8 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View, void *pContext) PROP_POS_Y, PROP_PARA_X, PROP_PARA_Y, + PROP_CUSTOM_ZOOM, + PROP_PARA_ZOOM, PROP_USE_CLIPPING, PROP_CLIP_X, PROP_CLIP_Y, @@ -334,6 +338,8 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View, void *pContext) {"Pos Y", -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY, PROPTYPE_INT_SCROLL, -1000000, 1000000}, {"Para X", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX, PROPTYPE_INT_SCROLL, -1000000, 1000000}, {"Para Y", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY, PROPTYPE_INT_SCROLL, -1000000, 1000000}, + {"Custom Zoom", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_CustomParallaxZoom, PROPTYPE_BOOL, 0, 1}, + {"Para Zoom", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxZoom, PROPTYPE_INT_SCROLL, -1000000, 1000000}, {"Use Clipping", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping, PROPTYPE_BOOL, 0, 1}, {"Clip X", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX, PROPTYPE_INT_SCROLL, -1000000, 1000000}, @@ -364,6 +370,13 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View, void *pContext) pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal; else if(Prop == PROP_PARA_Y) pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal; + else if(Prop == PROP_CUSTOM_ZOOM) + pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_CustomParallaxZoom = NewVal; + else if(Prop == PROP_PARA_ZOOM) + { + pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_CustomParallaxZoom = 1; + pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxZoom = NewVal; + } else if(Prop == PROP_POS_X) pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal; else if(Prop == PROP_POS_Y) @@ -378,6 +391,8 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View, void *pContext) pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW = NewVal; else if(Prop == PROP_CLIP_H) pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH = NewVal; + + pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->OnEdited(); } return 0; @@ -1234,54 +1249,29 @@ static int g_SelectImageCurrent = -100; int CEditor::PopupSelectImage(CEditor *pEditor, CUIRect View, void *pContext) { CUIRect ButtonBar, ImageView; - View.VSplitLeft(80.0f, &ButtonBar, &View); + View.VSplitLeft(150.0f, &ButtonBar, &View); View.Margin(10.0f, &ImageView); int ShowImage = g_SelectImageCurrent; - static float s_ScrollValue = 0; - float ImagesHeight = pEditor->m_Map.m_vpImages.size() * 14; - float ScrollDifference = ImagesHeight - ButtonBar.h; + const float RowHeight = 14.0f; + static CScrollRegion s_ScrollRegion; + vec2 ScrollOffset(0.0f, 0.0f); + CScrollRegionParams ScrollParams; + ScrollParams.m_ScrollbarWidth = 10.0f; + ScrollParams.m_ScrollbarMargin = 3.0f; + ScrollParams.m_ScrollUnit = RowHeight * 5; + s_ScrollRegion.Begin(&ButtonBar, &ScrollOffset, &ScrollParams); + ButtonBar.y += ScrollOffset.y; - if(pEditor->m_Map.m_vpImages.size() > 20) // Do we need a scrollbar? - { - CUIRect Scroll; - ButtonBar.VSplitRight(20.0f, &ButtonBar, &Scroll); - s_ScrollValue = pEditor->UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue); - - if(pEditor->UI()->MouseInside(&Scroll) || pEditor->UI()->MouseInside(&ButtonBar)) - { - int ScrollNum = (int)((ImagesHeight - ButtonBar.h) / 14.0f) + 1; - if(ScrollNum > 0) - { - if(pEditor->Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f / ScrollNum, 0.0f, 1.0f); - if(pEditor->Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f / ScrollNum, 0.0f, 1.0f); - } - } - } - - float ImageStartAt = ScrollDifference * s_ScrollValue; - if(ImageStartAt < 0.0f) - ImageStartAt = 0.0f; - - float ImageStopAt = ImagesHeight - ScrollDifference * (1 - s_ScrollValue); - float ImageCur = 0.0f; for(int i = -1; i < (int)pEditor->m_Map.m_vpImages.size(); i++) { - if(ImageCur > ImageStopAt) - break; - if(ImageCur < ImageStartAt) - { - ImageCur += 14.0f; - continue; - } - ImageCur += 14.0f; - CUIRect Button; - ButtonBar.HSplitTop(14.0f, &Button, &ButtonBar); + ButtonBar.HSplitTop(RowHeight, &Button, &ButtonBar); + if(!s_ScrollRegion.AddRect(Button)) + continue; + Button.HSplitTop(12.0f, &Button, 0); if(pEditor->UI()->MouseInside(&Button)) ShowImage = i; @@ -1298,6 +1288,8 @@ int CEditor::PopupSelectImage(CEditor *pEditor, CUIRect View, void *pContext) } } + s_ScrollRegion.End(); + if(ShowImage >= 0 && (size_t)ShowImage < pEditor->m_Map.m_vpImages.size()) { if(ImageView.h < ImageView.w) @@ -1325,7 +1317,7 @@ void CEditor::PopupSelectImageInvoke(int Current, float x, float y) static int s_SelectImagePopupId = 0; g_SelectImageSelected = -100; g_SelectImageCurrent = Current; - UiInvokePopupMenu(&s_SelectImagePopupId, 0, x, y, 400, 300, PopupSelectImage); + UiInvokePopupMenu(&s_SelectImagePopupId, 0, x, y, 450, 300, PopupSelectImage); } int CEditor::PopupSelectImageResult() diff --git a/src/game/layers.cpp b/src/game/layers.cpp index ef28343a9..ab384c657 100644 --- a/src/game/layers.cpp +++ b/src/game/layers.cpp @@ -3,6 +3,7 @@ #include "layers.h" #include "mapitems.h" +#include "mapitems_ex.h" #include @@ -10,9 +11,12 @@ CLayers::CLayers() { m_GroupsNum = 0; m_GroupsStart = 0; + m_GroupsExNum = 0; + m_GroupsExStart = 0; m_LayersNum = 0; m_LayersStart = 0; m_pGameGroup = 0; + m_pGameGroupEx = 0; m_pGameLayer = 0; m_pMap = 0; @@ -27,6 +31,7 @@ void CLayers::Init(class IKernel *pKernel) { m_pMap = pKernel->RequestInterface(); m_pMap->GetType(MAPITEMTYPE_GROUP, &m_GroupsStart, &m_GroupsNum); + m_pMap->GetType(MAPITEMTYPE_GROUP_EX, &m_GroupsExStart, &m_GroupsExNum); m_pMap->GetType(MAPITEMTYPE_LAYER, &m_LayersStart, &m_LayersNum); m_pTeleLayer = 0; @@ -38,6 +43,7 @@ void CLayers::Init(class IKernel *pKernel) for(int g = 0; g < NumGroups(); g++) { CMapItemGroup *pGroup = GetGroup(g); + CMapItemGroupEx *pGroupEx = GetGroupEx(g); for(int l = 0; l < pGroup->m_NumLayers; l++) { CMapItemLayer *pLayer = GetLayer(pGroup->m_StartLayer + l); @@ -50,6 +56,7 @@ void CLayers::Init(class IKernel *pKernel) { m_pGameLayer = pTilemap; m_pGameGroup = pGroup; + m_pGameGroupEx = pGroupEx; // make sure the game group has standard settings m_pGameGroup->m_OffsetX = 0; @@ -66,6 +73,9 @@ void CLayers::Init(class IKernel *pKernel) m_pGameGroup->m_ClipH = 0; } + if(pGroupEx) + pGroupEx->m_ParallaxZoom = 100; + //break; } if(pTilemap->m_Flags & TILESLAYERFLAG_TELE) @@ -119,6 +129,7 @@ void CLayers::InitBackground(class IMap *pMap) { m_pMap = pMap; m_pMap->GetType(MAPITEMTYPE_GROUP, &m_GroupsStart, &m_GroupsNum); + m_pMap->GetType(MAPITEMTYPE_GROUP_EX, &m_GroupsExStart, &m_GroupsExNum); m_pMap->GetType(MAPITEMTYPE_LAYER, &m_LayersStart, &m_LayersNum); //following is here to prevent crash using standard map as background @@ -131,6 +142,7 @@ void CLayers::InitBackground(class IMap *pMap) for(int g = 0; g < NumGroups(); g++) { CMapItemGroup *pGroup = GetGroup(g); + CMapItemGroupEx *pGroupEx = GetGroupEx(g); for(int l = 0; l < pGroup->m_NumLayers; l++) { CMapItemLayer *pLayer = GetLayer(pGroup->m_StartLayer + l); @@ -143,6 +155,7 @@ void CLayers::InitBackground(class IMap *pMap) { m_pGameLayer = pTilemap; m_pGameGroup = pGroup; + m_pGameGroupEx = pGroupEx; // make sure the game group has standard settings m_pGameGroup->m_OffsetX = 0; @@ -158,6 +171,10 @@ void CLayers::InitBackground(class IMap *pMap) m_pGameGroup->m_ClipW = 0; m_pGameGroup->m_ClipH = 0; } + + if(pGroupEx) + pGroupEx->m_ParallaxZoom = 100; + //We don't care about tile layers. } } @@ -206,6 +223,15 @@ CMapItemGroup *CLayers::GetGroup(int Index) const return static_cast(m_pMap->GetItem(m_GroupsStart + Index, 0, 0)); } +CMapItemGroupEx *CLayers::GetGroupEx(int Index) const +{ + // Map doesn't have GroupEx items or GroupEx indexes don't match groups for some other reason. Lets turn them off completely to be safe. + if(m_GroupsExNum != m_GroupsNum) + return nullptr; + + return static_cast(m_pMap->GetItem(m_GroupsExStart + Index, 0, 0)); +} + CMapItemLayer *CLayers::GetLayer(int Index) const { return static_cast(m_pMap->GetItem(m_LayersStart + Index, 0, 0)); diff --git a/src/game/layers.h b/src/game/layers.h index 1823dea9b..59e112267 100644 --- a/src/game/layers.h +++ b/src/game/layers.h @@ -7,6 +7,7 @@ class IKernel; class IMap; struct CMapItemGroup; +struct CMapItemGroupEx; struct CMapItemLayer; struct CMapItemLayerTilemap; @@ -14,9 +15,12 @@ class CLayers { int m_GroupsNum; int m_GroupsStart; + int m_GroupsExNum; + int m_GroupsExStart; int m_LayersNum; int m_LayersStart; CMapItemGroup *m_pGameGroup; + CMapItemGroupEx *m_pGameGroupEx; CMapItemLayerTilemap *m_pGameLayer; IMap *m_pMap; @@ -30,8 +34,10 @@ public: int NumLayers() const { return m_LayersNum; } IMap *Map() const { return m_pMap; } CMapItemGroup *GameGroup() const { return m_pGameGroup; } + CMapItemGroupEx *GameGroupEx() const { return m_pGameGroupEx; } CMapItemLayerTilemap *GameLayer() const { return m_pGameLayer; } CMapItemGroup *GetGroup(int Index) const; + CMapItemGroupEx *GetGroupEx(int Index) const; CMapItemLayer *GetLayer(int Index) const; // DDRace diff --git a/src/game/mapitems_ex.cpp b/src/game/mapitems_ex.cpp index 68c29a3e2..fe35d4127 100644 --- a/src/game/mapitems_ex.cpp +++ b/src/game/mapitems_ex.cpp @@ -1,7 +1,21 @@ #include "mapitems_ex.h" +#include #include +int GetParallaxZoom(const CMapItemGroup *pGroup, const CMapItemGroupEx *pGroupEx) +{ + if(pGroupEx) + return pGroupEx->m_ParallaxZoom; + + return GetParallaxZoomDefault(pGroup->m_ParallaxX, pGroup->m_ParallaxY); +} + +int GetParallaxZoomDefault(int ParallaxX, int ParallaxY) +{ + return clamp(maximum(ParallaxX, ParallaxY), 0, 100); +} + void RegisterMapItemTypeUuids(CUuidManager *pManager) { #define UUID(id, name) pManager->RegisterName(id, name); diff --git a/src/game/mapitems_ex.h b/src/game/mapitems_ex.h index c67b36218..5486e240f 100644 --- a/src/game/mapitems_ex.h +++ b/src/game/mapitems_ex.h @@ -2,6 +2,8 @@ #define GAME_MAPITEMS_EX_H #include +#include "mapitems.h" + enum { __MAPITEMTYPE_UUID_HELPER = OFFSET_MAPITEMTYPE_UUID - 1, @@ -43,5 +45,23 @@ struct CMapItemAutoMapperConfig int m_Flags; }; +struct CMapItemGroupEx +{ + enum + { + CURRENT_VERSION = 1 + }; + + int m_Version; + + // ItemGroup's perceived distance from camera when zooming. Similar to how + // Parallax{X,Y} works when camera is moving along the X and Y axes, + // this setting applies to camera moving closer or away (zooming in or out). + int m_ParallaxZoom; +}; + +int GetParallaxZoom(const CMapItemGroup *pGroup, const CMapItemGroupEx *pGroupEx); +int GetParallaxZoomDefault(int ParallaxX, int ParallaxY); + void RegisterMapItemTypeUuids(class CUuidManager *pManager); #endif // GAME_MAPITEMS_EX_H diff --git a/src/game/mapitems_ex_types.h b/src/game/mapitems_ex_types.h index 116f9da97..304d4e5d1 100644 --- a/src/game/mapitems_ex_types.h +++ b/src/game/mapitems_ex_types.h @@ -2,3 +2,4 @@ UUID(MAPITEMTYPE_TEST, "mapitemtype-test@ddnet.tw") UUID(MAPITEMTYPE_AUTOMAPPER_CONFIG, "mapitemtype-automapper-config@ddnet.tw") +UUID(MAPITEMTYPE_GROUP_EX, "mapitemtype-group@ddnet.tw")