6721: Improve selection popups and dropdown menus, add dropdown menus to select screen and active controller r=def- a=Robyt3

Screenshots:
- Controls settings:
   - Before: 
![controls old](https://github.com/ddnet/ddnet/assets/23437060/8907c38a-d483-4766-84ad-49464b9760a0)
   - After: 
![controls new](https://github.com/ddnet/ddnet/assets/23437060/f021e961-ce90-4557-aef0-a1e1a4bc7ca4)
- Graphics settings (collapsed):
   - Before: 
![graphics_collapsed old](https://github.com/ddnet/ddnet/assets/23437060/d895c8d8-a72f-4da2-aa57-93d5f08be648)
   - After: 
![graphics_collapsed new](https://github.com/ddnet/ddnet/assets/23437060/e594a599-1210-4ba0-87f2-b2a824043dc4)
- Graphics settings (expanded):
   - Before: 
![graphics_expanded old](https://github.com/ddnet/ddnet/assets/23437060/cce1d8cb-4dff-4d43-a9f0-af2dbfeff27d)
   - After: 
![graphics_expanded_1 new](https://github.com/ddnet/ddnet/assets/23437060/c99c24cb-3398-4a80-9b99-3281ed62f6a7)
![graphics_expanded_2 new](https://github.com/ddnet/ddnet/assets/23437060/83fc2522-0da6-40d8-ad2b-3e3159b74308)
![graphics_expanded_3 new](https://github.com/ddnet/ddnet/assets/23437060/b00b30c4-411b-43a0-97f0-ecd47ebea516)
- The maximum dropdown menu height is limited to 40% of the screen height. The content will begin to scroll if this is exceeded:
![scrolling](https://github.com/ddnet/ddnet/assets/23437060/ac576819-f24f-4fed-82cd-a05d87f9ad71)
- If there is not enough space below the dropdown button to open the dropdown menu popup, then the popup opens to the top instead:
![alignment](https://github.com/ddnet/ddnet/assets/23437060/2f61ea30-996d-4def-8f5d-300b5008c666)

## Checklist

- [X] Tested the change ingame
- [X] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [X] Considered possible null pointers and out of bounds array indexing
- [X] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2023-06-10 14:01:38 +00:00 committed by GitHub
commit 6a53d72b35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 330 additions and 202 deletions

View file

@ -882,6 +882,12 @@ bool CGraphicsBackend_SDL_GL::GetDriverVersion(EGraphicsDriverAgeType DriverAgeT
return false;
}
const char *CGraphicsBackend_SDL_GL::GetScreenName(int Screen) const
{
const char *pName = SDL_GetDisplayName(Screen);
return pName == nullptr ? "unknown/error" : pName;
}
static void DisplayToVideoMode(CVideoMode *pVMode, SDL_DisplayMode *pMode, int HiDPIScale, int RefreshRate)
{
pVMode->m_CanvasWidth = pMode->w * HiDPIScale;

View file

@ -253,6 +253,7 @@ public:
const TTWGraphicsGPUList &GetGPUs() const override;
int GetNumScreens() const override { return m_NumScreens; }
const char *GetScreenName(int Screen) const override;
void GetVideoModes(CVideoMode *pModes, int MaxModes, int *pNumModes, int HiDPIScale, int MaxWindowWidth, int MaxWindowHeight, int ScreenID) override;
void GetCurrentVideoMode(CVideoMode &CurMode, int HiDPIScale, int MaxWindowWidth, int MaxWindowHeight, int ScreenID) override;

View file

@ -2939,6 +2939,11 @@ int CGraphics_Threaded::GetNumScreens() const
return m_pBackend->GetNumScreens();
}
const char *CGraphics_Threaded::GetScreenName(int Screen) const
{
return m_pBackend->GetScreenName(Screen);
}
void CGraphics_Threaded::Minimize()
{
m_pBackend->Minimize();

View file

@ -731,6 +731,7 @@ public:
virtual void GetCurrentVideoMode(CVideoMode &CurMode, int HiDPIScale, int MaxWindowWidth, int MaxWindowHeight, int Screen) = 0;
virtual int GetNumScreens() const = 0;
virtual const char *GetScreenName(int Screen) const = 0;
virtual void Minimize() = 0;
virtual void Maximize() = 0;
@ -1257,6 +1258,8 @@ public:
void IndicesNumRequiredNotify(unsigned int RequiredIndicesCount) override;
int GetNumScreens() const override;
const char *GetScreenName(int Screen) const override;
void Minimize() override;
void Maximize() override;
void WarnPngliteIncompatibleImages(bool Warn) override;

View file

@ -187,14 +187,10 @@ void CInput::CloseJoysticks()
m_pActiveJoystick = nullptr;
}
void CInput::SelectNextJoystick()
void CInput::SetActiveJoystick(size_t Index)
{
const int Num = m_vJoysticks.size();
if(Num > 1)
{
m_pActiveJoystick = &m_vJoysticks[(m_pActiveJoystick->GetIndex() + 1) % Num];
str_copy(g_Config.m_InpControllerGUID, m_pActiveJoystick->GetGUID());
}
m_pActiveJoystick = &m_vJoysticks[Index];
str_copy(g_Config.m_InpControllerGUID, m_pActiveJoystick->GetGUID());
}
float CInput::CJoystick::GetAxisValue(int Axis)

View file

@ -122,8 +122,9 @@ public:
bool KeyPress(int Key, bool CheckCounter) const override { return CheckCounter ? (m_aInputCount[Key] == m_InputCounter) : m_aInputCount[Key]; }
size_t NumJoysticks() const override { return m_vJoysticks.size(); }
CJoystick *GetJoystick(size_t Index) override { return &m_vJoysticks[Index]; }
CJoystick *GetActiveJoystick() override { return m_pActiveJoystick; }
void SelectNextJoystick() override;
void SetActiveJoystick(size_t Index) override;
bool MouseRelative(float *pX, float *pY) override;
void MouseModeAbsolute() override;

View file

@ -506,6 +506,7 @@ public:
virtual void Swap() = 0;
virtual int GetNumScreens() const = 0;
virtual const char *GetScreenName(int Screen) const = 0;
// synchronization
virtual void InsertSignal(class CSemaphore *pSemaphore) = 0;

View file

@ -98,8 +98,9 @@ public:
virtual bool Absolute(float *pX, float *pY) = 0;
};
virtual size_t NumJoysticks() const = 0;
virtual IJoystick *GetJoystick(size_t Index) = 0;
virtual IJoystick *GetActiveJoystick() = 0;
virtual void SelectNextJoystick() = 0;
virtual void SetActiveJoystick(size_t Index) = 0;
// mouse
virtual void NativeMousePos(int *pX, int *pY) const = 0;

View file

@ -662,8 +662,6 @@ private:
void RenderSettingsAppearance(CUIRect MainView);
ColorHSLA RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha = false, bool ClampedLight = false);
int RenderDropDown(int &CurDropDownState, CUIRect *pRect, int CurSelection, const void **pIDs, const char **pStr, int PickNum, CButtonContainer *pButtonContainer, float &ScrollVal);
CServerProcess m_ServerProcess;
};
#endif

View file

@ -1078,20 +1078,39 @@ float CMenus::RenderSettingsControlsJoystick(CUIRect View)
{
// show joystick device selection if more than one available or just the joystick name if there is only one
{
CUIRect JoystickDropDown;
View.HSplitTop(Spacing, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
char aBuf[96];
str_format(aBuf, sizeof(aBuf), Localize("Controller %d: %s"), Input()->GetActiveJoystick()->GetIndex(), Input()->GetActiveJoystick()->GetName());
View.HSplitTop(ButtonHeight, &JoystickDropDown, &View);
if(NumJoysticks > 1)
{
static CButtonContainer s_ButtonJoystickId;
if(DoButton_Menu(&s_ButtonJoystickId, aBuf, 0, &Button))
Input()->SelectNextJoystick();
GameClient()->m_Tooltips.DoToolTip(&s_ButtonJoystickId, &Button, Localize("Click to cycle through all available controllers."));
static std::vector<std::string> s_vJoystickNames;
static std::vector<const char *> s_vpJoystickNames;
s_vJoystickNames.resize(NumJoysticks);
s_vpJoystickNames.resize(NumJoysticks);
for(int i = 0; i < NumJoysticks; ++i)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "%s %d: %s", Localize("Controller"), i, Input()->GetJoystick(i)->GetName());
s_vJoystickNames[i] = aBuf;
s_vpJoystickNames[i] = s_vJoystickNames[i].c_str();
}
static CUI::SDropDownState s_JoystickDropDownState;
static CScrollRegion s_JoystickDropDownScrollRegion;
s_JoystickDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_JoystickDropDownScrollRegion;
const int CurrentJoystick = Input()->GetActiveJoystick()->GetIndex();
const int NewJoystick = UI()->DoDropDown(&JoystickDropDown, CurrentJoystick, s_vpJoystickNames.data(), s_vpJoystickNames.size(), s_JoystickDropDownState);
if(NewJoystick != CurrentJoystick)
{
Input()->SetActiveJoystick(NewJoystick);
}
}
else
{
UI()->DoLabel(&Button, aBuf, 13.0f, TEXTALIGN_ML);
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "%s 0: %s", Localize("Controller"), Input()->GetJoystick(0)->GetName());
UI()->DoLabel(&JoystickDropDown, aBuf, 13.0f, TEXTALIGN_ML);
}
}
@ -1447,53 +1466,6 @@ void CMenus::ResetSettingsControls()
g_Config.m_UiControllerSens = 100;
}
int CMenus::RenderDropDown(int &CurDropDownState, CUIRect *pRect, int CurSelection, const void **pIDs, const char **pStr, int PickNum, CButtonContainer *pButtonContainer, float &ScrollVal)
{
if(CurDropDownState != 0)
{
const float RowHeight = 24.0f;
const float RowSpacing = 3.0f;
CUIRect ListRect;
pRect->HSplitTop((RowHeight + RowSpacing) * PickNum, &ListRect, pRect);
static CListBox s_ListBox;
s_ListBox.DoAutoSpacing(RowSpacing);
s_ListBox.DoStart(RowHeight, PickNum, 1, 3, CurSelection, &ListRect);
for(int i = 0; i < PickNum; ++i)
{
const CListboxItem Item = s_ListBox.DoNextItem(pIDs[i], CurSelection == i);
if(!Item.m_Visible)
continue;
UI()->DoLabel(&Item.m_Rect, pStr[i], 16.0f, TEXTALIGN_MC);
}
int NewIndex = s_ListBox.DoEnd();
if(s_ListBox.WasItemSelected() || s_ListBox.WasItemActivated())
{
CurDropDownState = 0;
return NewIndex;
}
else
return CurSelection;
}
else
{
CUIRect Button;
pRect->HSplitTop(24.0f, &Button, pRect);
if(DoButton_MenuTab(pButtonContainer, CurSelection > -1 ? pStr[CurSelection] : "", 0, &Button, IGraphics::CORNER_ALL, NULL, NULL, NULL, NULL, 4.0f))
CurDropDownState = 1;
CUIRect DropDownIcon = Button;
DropDownIcon.HMargin(2.0f, &DropDownIcon);
DropDownIcon.VSplitRight(5.0f, &DropDownIcon, nullptr);
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
UI()->DoLabel(&DropDownIcon, FONT_ICON_CIRCLE_CHEVRON_DOWN, DropDownIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MR);
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(NULL);
return CurSelection;
}
}
void CMenus::RenderSettingsGraphics(CUIRect MainView)
{
CUIRect Button;
@ -1581,23 +1553,19 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
}
// switches
static float s_ScrollValueDrop = 0;
CUIRect WindowModeDropDown;
MainView.HSplitTop(20.0f, &WindowModeDropDown, &MainView);
const char *apWindowModes[] = {Localize("Windowed"), Localize("Windowed borderless"), Localize("Windowed fullscreen"), Localize("Desktop fullscreen"), Localize("Fullscreen")};
static const int s_NumWindowMode = std::size(apWindowModes);
static int s_aWindowModeIDs[s_NumWindowMode];
const void *apWindowModeIDs[s_NumWindowMode];
for(int i = 0; i < s_NumWindowMode; ++i)
apWindowModeIDs[i] = &s_aWindowModeIDs[i];
static int s_WindowModeDropDownState = 0;
static int s_OldSelectedBackend = -1;
static int s_OldSelectedGPU = -1;
const int OldWindowMode = (g_Config.m_GfxFullscreen ? (g_Config.m_GfxFullscreen == 1 ? 4 : (g_Config.m_GfxFullscreen == 2 ? 3 : 2)) : (g_Config.m_GfxBorderless ? 1 : 0));
OldSelected = (g_Config.m_GfxFullscreen ? (g_Config.m_GfxFullscreen == 1 ? 4 : (g_Config.m_GfxFullscreen == 2 ? 3 : 2)) : (g_Config.m_GfxBorderless ? 1 : 0));
static CButtonContainer s_WindowButton;
const int NewWindowMode = RenderDropDown(s_WindowModeDropDownState, &MainView, OldSelected, apWindowModeIDs, apWindowModes, s_NumWindowMode, &s_WindowButton, s_ScrollValueDrop);
if(OldSelected != NewWindowMode)
static CUI::SDropDownState s_WindowModeDropDownState;
static CScrollRegion s_WindowModeDropDownScrollRegion;
s_WindowModeDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_WindowModeDropDownScrollRegion;
const int NewWindowMode = UI()->DoDropDown(&WindowModeDropDown, OldWindowMode, apWindowModes, s_NumWindowMode, s_WindowModeDropDownState);
if(OldWindowMode != NewWindowMode)
{
if(NewWindowMode == 0)
Client()->SetWindowParams(0, false, true);
@ -1611,6 +1579,37 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
Client()->SetWindowParams(1, false, true);
}
if(Graphics()->GetNumScreens() > 1)
{
CUIRect ScreenDropDown;
MainView.HSplitTop(2.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &ScreenDropDown, &MainView);
const int NumScreens = Graphics()->GetNumScreens();
static std::vector<std::string> s_vScreenNames;
static std::vector<const char *> s_vpScreenNames;
s_vScreenNames.resize(NumScreens);
s_vpScreenNames.resize(NumScreens);
for(int i = 0; i < NumScreens; ++i)
{
str_format(aBuf, sizeof(aBuf), "%s %d: %s", Localize("Screen"), i, Graphics()->GetScreenName(i));
s_vScreenNames[i] = aBuf;
s_vpScreenNames[i] = s_vScreenNames[i].c_str();
}
static CUI::SDropDownState s_ScreenDropDownState;
static CScrollRegion s_ScreenDropDownScrollRegion;
s_ScreenDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_ScreenDropDownScrollRegion;
const int NewScreen = UI()->DoDropDown(&ScreenDropDown, g_Config.m_GfxScreen, s_vpScreenNames.data(), s_vpScreenNames.size(), s_ScreenDropDownState);
if(NewScreen != g_Config.m_GfxScreen)
{
Client()->SwitchWindowScreen(NewScreen);
s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen);
}
}
MainView.HSplitTop(2.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &Button, &MainView);
str_format(aBuf, sizeof(aBuf), "%s (%s)", Localize("V-Sync"), Localize("may cause delay"));
if(DoButton_CheckBox(&g_Config.m_GfxVsync, aBuf, g_Config.m_GfxVsync, &Button))
@ -1618,23 +1617,6 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
Client()->ToggleWindowVSync();
}
if(Graphics()->GetNumScreens() > 1)
{
int NumScreens = Graphics()->GetNumScreens();
MainView.HSplitTop(20.0f, &Button, &MainView);
int Screen_MouseButton = DoButton_CheckBox_Number(&g_Config.m_GfxScreen, Localize("Screen"), g_Config.m_GfxScreen, &Button);
if(Screen_MouseButton == 1) // inc
{
Client()->SwitchWindowScreen((g_Config.m_GfxScreen + 1) % NumScreens);
s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen);
}
else if(Screen_MouseButton == 2) // dec
{
Client()->SwitchWindowScreen((g_Config.m_GfxScreen - 1 + NumScreens) % NumScreens);
s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen);
}
}
bool MultiSamplingChanged = false;
MainView.HSplitTop(20.0f, &Button, &MainView);
str_format(aBuf, sizeof(aBuf), "%s (%s)", Localize("FSAA samples"), Localize("may cause delay"));
@ -1726,25 +1708,20 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
if(FoundBackendCount > 1)
{
CUIRect Text;
CUIRect Text, BackendDropDown;
MainView.HSplitTop(10.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &Text, &MainView);
MainView.HSplitTop(2.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &BackendDropDown, &MainView);
UI()->DoLabel(&Text, Localize("Renderer"), 16.0f, TEXTALIGN_MC);
static float s_ScrollValueDropBackend = 0;
static int s_BackendDropDownState = 0;
static std::vector<std::unique_ptr<int>> s_vBackendIDs;
static std::vector<const void *> s_vBackendIDPtrs;
static std::vector<std::string> s_vBackendIDNames;
static std::vector<const char *> s_vBackendIDNamesCStr;
static std::vector<const char *> s_vpBackendIDNamesCStr;
static std::vector<SMenuBackendInfo> s_vBackendInfos;
size_t BackendCount = FoundBackendCount + 1;
s_vBackendIDs.resize(BackendCount);
s_vBackendIDPtrs.resize(BackendCount);
s_vBackendIDNames.resize(BackendCount);
s_vBackendIDNamesCStr.resize(BackendCount);
s_vpBackendIDNamesCStr.resize(BackendCount);
s_vBackendInfos.resize(BackendCount);
char aTmpBackendName[256];
@ -1762,22 +1739,16 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
auto &Info = aaSupportedBackends[i][n];
if(Info.m_Found)
{
if(s_vBackendIDs[CurCounter].get() == nullptr)
s_vBackendIDs[CurCounter] = std::make_unique<int>();
s_vBackendIDPtrs[CurCounter] = s_vBackendIDs[CurCounter].get();
bool IsDefault = IsInfoDefault(Info);
str_format(aTmpBackendName, sizeof(aTmpBackendName), "%s (%d.%d.%d)%s%s", Info.m_pBackendName, Info.m_Major, Info.m_Minor, Info.m_Patch, IsDefault ? " - " : "", IsDefault ? Localize("default") : "");
s_vBackendIDNames[CurCounter] = aTmpBackendName;
s_vpBackendIDNamesCStr[CurCounter] = s_vBackendIDNames[CurCounter].c_str();
if(str_comp_nocase(Info.m_pBackendName, g_Config.m_GfxBackend) == 0 && g_Config.m_GfxGLMajor == Info.m_Major && g_Config.m_GfxGLMinor == Info.m_Minor && g_Config.m_GfxGLPatch == Info.m_Patch)
{
bool IsDefault = IsInfoDefault(Info);
str_format(aTmpBackendName, sizeof(aTmpBackendName), "%s (%d.%d.%d)%s%s", Info.m_pBackendName, Info.m_Major, Info.m_Minor, Info.m_Patch, IsDefault ? " - " : "", IsDefault ? Localize("default") : "");
s_vBackendIDNames[CurCounter] = aTmpBackendName;
s_vBackendIDNamesCStr[CurCounter] = s_vBackendIDNames[CurCounter].c_str();
if(str_comp_nocase(Info.m_pBackendName, g_Config.m_GfxBackend) == 0 && g_Config.m_GfxGLMajor == Info.m_Major && g_Config.m_GfxGLMinor == Info.m_Minor && g_Config.m_GfxGLPatch == Info.m_Patch)
{
OldSelectedBackend = CurCounter;
}
s_vBackendInfos[CurCounter] = Info;
OldSelectedBackend = CurCounter;
}
s_vBackendInfos[CurCounter] = Info;
++CurCounter;
}
}
@ -1791,13 +1762,9 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
else
{
// custom selected one
if(s_vBackendIDs[CurCounter].get() == nullptr)
s_vBackendIDs[CurCounter] = std::make_unique<int>();
s_vBackendIDPtrs[CurCounter] = s_vBackendIDs[CurCounter].get();
str_format(aTmpBackendName, sizeof(aTmpBackendName), "%s (%s %d.%d.%d)", Localize("custom"), g_Config.m_GfxBackend, g_Config.m_GfxGLMajor, g_Config.m_GfxGLMinor, g_Config.m_GfxGLPatch);
s_vBackendIDNames[CurCounter] = aTmpBackendName;
s_vBackendIDNamesCStr[CurCounter] = s_vBackendIDNames[CurCounter].c_str();
s_vpBackendIDNamesCStr[CurCounter] = s_vBackendIDNames[CurCounter].c_str();
OldSelectedBackend = CurCounter;
s_vBackendInfos[CurCounter].m_pBackendName = "custom";
@ -1806,14 +1773,14 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
s_vBackendInfos[CurCounter].m_Patch = g_Config.m_GfxGLPatch;
}
static int s_OldSelectedBackend = -1;
if(s_OldSelectedBackend == -1)
s_OldSelectedBackend = OldSelectedBackend;
static int s_BackendCount = 0;
s_BackendCount = BackendCount;
static CButtonContainer s_BackendButton;
const int NewBackend = RenderDropDown(s_BackendDropDownState, &MainView, OldSelectedBackend, s_vBackendIDPtrs.data(), s_vBackendIDNamesCStr.data(), s_BackendCount, &s_BackendButton, s_ScrollValueDropBackend);
static CUI::SDropDownState s_BackendDropDownState;
static CScrollRegion s_BackendDropDownScrollRegion;
s_BackendDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_BackendDropDownScrollRegion;
const int NewBackend = UI()->DoDropDown(&BackendDropDown, OldSelectedBackend, s_vpBackendIDNamesCStr.data(), BackendCount, s_BackendDropDownState);
if(OldSelectedBackend != NewBackend)
{
str_copy(g_Config.m_GfxBackend, s_vBackendInfos[NewBackend].m_pBackendName);
@ -1830,35 +1797,27 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
const auto &GPUList = Graphics()->GetGPUs();
if(GPUList.m_vGPUs.size() > 1)
{
CUIRect Text;
CUIRect Text, GpuDropDown;
MainView.HSplitTop(10.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &Text, &MainView);
UI()->DoLabel(&Text, Localize("Graphics cards"), 16.0f, TEXTALIGN_MC);
MainView.HSplitTop(2.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &GpuDropDown, &MainView);
UI()->DoLabel(&Text, Localize("Graphics card"), 16.0f, TEXTALIGN_MC);
static float s_ScrollValueDropGPU = 0;
static int s_GPUDropDownState = 0;
static std::vector<std::unique_ptr<int>> s_vGPUIDs;
static std::vector<const void *> s_vGPUIDPtrs;
static std::vector<const char *> s_vGPUIDNames;
static std::vector<const char *> s_vpGPUIDNames;
size_t GPUCount = GPUList.m_vGPUs.size() + 1;
s_vGPUIDs.resize(GPUCount);
s_vGPUIDPtrs.resize(GPUCount);
s_vGPUIDNames.resize(GPUCount);
s_vpGPUIDNames.resize(GPUCount);
char aCurDeviceName[256 + 4];
int OldSelectedGPU = -1;
for(size_t i = 0; i < GPUCount; ++i)
{
if(s_vGPUIDs[i].get() == nullptr)
s_vGPUIDs[i] = std::make_unique<int>();
s_vGPUIDPtrs[i] = s_vGPUIDs[i].get();
if(i == 0)
{
str_format(aCurDeviceName, sizeof(aCurDeviceName), "%s(%s)", Localize("auto"), GPUList.m_AutoGPU.m_aName);
s_vGPUIDNames[i] = aCurDeviceName;
str_format(aCurDeviceName, sizeof(aCurDeviceName), "%s (%s)", Localize("auto"), GPUList.m_AutoGPU.m_aName);
s_vpGPUIDNames[i] = aCurDeviceName;
if(str_comp("auto", g_Config.m_GfxGPUName) == 0)
{
OldSelectedGPU = 0;
@ -1866,7 +1825,7 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
}
else
{
s_vGPUIDNames[i] = GPUList.m_vGPUs[i - 1].m_aName;
s_vpGPUIDNames[i] = GPUList.m_vGPUs[i - 1].m_aName;
if(str_comp(GPUList.m_vGPUs[i - 1].m_aName, g_Config.m_GfxGPUName) == 0)
{
OldSelectedGPU = i;
@ -1874,14 +1833,14 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
}
}
static int s_GPUCount = 0;
s_GPUCount = GPUCount;
static int s_OldSelectedGPU = -1;
if(s_OldSelectedGPU == -1)
s_OldSelectedGPU = OldSelectedGPU;
static CButtonContainer s_GpuButton;
const int NewGPU = RenderDropDown(s_GPUDropDownState, &MainView, OldSelectedGPU, s_vGPUIDPtrs.data(), s_vGPUIDNames.data(), s_GPUCount, &s_GpuButton, s_ScrollValueDropGPU);
static CUI::SDropDownState s_GpuDropDownState;
static CScrollRegion s_GpuDropDownScrollRegion;
s_GpuDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_GpuDropDownScrollRegion;
const int NewGPU = UI()->DoDropDown(&GpuDropDown, OldSelectedGPU, s_vpGPUIDNames.data(), GPUCount, s_GpuDropDownState);
if(OldSelectedGPU != NewGPU)
{
if(NewGPU == 0)

View file

@ -1,6 +1,7 @@
/* (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 "ui.h"
#include "ui_scrollregion.h"
#include <base/math.h>
#include <base/system.h>
@ -15,6 +16,8 @@
#include <limits>
using namespace FontIcons;
void CUIElement::Init(CUI *pUI, int RequestedRectCount)
{
m_pUI = pUI;
@ -948,13 +951,14 @@ int CUI::DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pID, const
return DoButtonLogic(pID, Props.m_Checked, pRect);
}
int CUI::DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, int Align)
int CUI::DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding, bool TransparentInactive)
{
pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * ButtonColorMul(pButtonContainer)), IGraphics::CORNER_ALL, 3.0f);
if(!TransparentInactive || CheckActiveItem(pButtonContainer) || HotItem() == pButtonContainer)
pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * ButtonColorMul(pButtonContainer)), IGraphics::CORNER_ALL, 3.0f);
CUIRect Label;
pRect->VMargin(2.0f, &Label);
DoLabel(&Label, pText, 10.0f, Align);
pRect->Margin(Padding, &Label);
DoLabel(&Label, pText, Size, Align);
return DoButtonLogic(pButtonContainer, 0, pRect);
}
@ -1273,7 +1277,7 @@ void CUI::DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect,
*pOption = Value;
}
void CUI::DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, int Corners)
void CUI::DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, const SPopupMenuProperties &Props)
{
constexpr float Margin = SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN;
if(X + Width > Screen()->w - Margin)
@ -1284,11 +1288,11 @@ void CUI::DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Heig
m_vPopupMenus.emplace_back();
SPopupMenu *pNewMenu = &m_vPopupMenus.back();
pNewMenu->m_pID = pID;
pNewMenu->m_Props = Props;
pNewMenu->m_Rect.x = X;
pNewMenu->m_Rect.y = Y;
pNewMenu->m_Rect.w = Width;
pNewMenu->m_Rect.h = Height;
pNewMenu->m_Corners = Corners;
pNewMenu->m_pContext = pContext;
pNewMenu->m_pfnFunc = pfnFunc;
}
@ -1322,9 +1326,9 @@ void CUI::RenderPopupMenus()
}
CUIRect PopupRect = PopupMenu.m_Rect;
PopupRect.Draw(ColorRGBA(0.5f, 0.5f, 0.5f, 0.75f), PopupMenu.m_Corners, 3.0f);
PopupRect.Draw(PopupMenu.m_Props.m_BorderColor, PopupMenu.m_Props.m_Corners, 3.0f);
PopupRect.Margin(SPopupMenu::POPUP_BORDER, &PopupRect);
PopupRect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.75f), PopupMenu.m_Corners, 3.0f);
PopupRect.Draw(PopupMenu.m_Props.m_BackgroundColor, PopupMenu.m_Props.m_Corners, 3.0f);
PopupRect.Margin(SPopupMenu::POPUP_MARGIN, &PopupRect);
EPopupMenuFunctionResult Result = PopupMenu.m_pfnFunc(PopupMenu.m_pContext, PopupRect, Active);
@ -1453,14 +1457,14 @@ CUI::EPopupMenuFunctionResult CUI::PopupConfirm(void *pContext, CUIRect View, bo
pUI->TextRender()->Text(Label.x, Label.y, SConfirmPopupContext::POPUP_FONT_SIZE, pConfirmPopup->m_aMessage, Label.w);
static CButtonContainer s_CancelButton;
if(pUI->DoButton_PopupMenu(&s_CancelButton, pConfirmPopup->m_aNegativeButtonLabel, &CancelButton, TEXTALIGN_MC))
if(pUI->DoButton_PopupMenu(&s_CancelButton, pConfirmPopup->m_aNegativeButtonLabel, &CancelButton, SConfirmPopupContext::POPUP_FONT_SIZE, TEXTALIGN_MC))
{
pConfirmPopup->m_Result = SConfirmPopupContext::CANCELED;
return CUI::POPUP_CLOSE_CURRENT;
}
static CButtonContainer s_ConfirmButton;
if(pUI->DoButton_PopupMenu(&s_ConfirmButton, pConfirmPopup->m_aPositiveButtonLabel, &ConfirmButton, TEXTALIGN_MC) || (Active && pUI->ConsumeHotkey(HOTKEY_ENTER)))
if(pUI->DoButton_PopupMenu(&s_ConfirmButton, pConfirmPopup->m_aPositiveButtonLabel, &ConfirmButton, SConfirmPopupContext::POPUP_FONT_SIZE, TEXTALIGN_MC) || (Active && pUI->ConsumeHotkey(HOTKEY_ENTER)))
{
pConfirmPopup->m_Result = SConfirmPopupContext::CONFIRMED;
return CUI::POPUP_CLOSE_CURRENT;
@ -1476,43 +1480,150 @@ CUI::SSelectionPopupContext::SSelectionPopupContext()
void CUI::SSelectionPopupContext::Reset()
{
m_Props = SPopupMenuProperties();
m_aMessage[0] = '\0';
m_pSelection = nullptr;
m_Entries.clear();
m_SelectionIndex = -1;
m_vEntries.clear();
m_vButtonContainers.clear();
m_EntryHeight = 12.0f;
m_EntryPadding = 0.0f;
m_EntrySpacing = 5.0f;
m_FontSize = 10.0f;
m_Width = 300.0f + (SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN) * 2;
m_AlignmentHeight = -1.0f;
m_TransparentButtons = false;
}
CUI::EPopupMenuFunctionResult CUI::PopupSelection(void *pContext, CUIRect View, bool Active)
{
SSelectionPopupContext *pSelectionPopup = static_cast<SSelectionPopupContext *>(pContext);
CUI *pUI = pSelectionPopup->m_pUI;
CScrollRegion *pScrollRegion = pSelectionPopup->m_pScrollRegion;
vec2 ScrollOffset(0.0f, 0.0f);
CScrollRegionParams ScrollParams;
ScrollParams.m_ScrollbarWidth = 10.0f;
ScrollParams.m_ScrollbarMargin = SPopupMenu::POPUP_MARGIN;
ScrollParams.m_ScrollbarNoMarginRight = true;
ScrollParams.m_ScrollUnit = 3 * (pSelectionPopup->m_EntryHeight + pSelectionPopup->m_EntrySpacing);
pScrollRegion->Begin(&View, &ScrollOffset, &ScrollParams);
View.y += ScrollOffset.y;
CUIRect Slot;
const STextBoundingBox TextBoundingBox = pUI->TextRender()->TextBoundingBox(SSelectionPopupContext::POPUP_FONT_SIZE, pSelectionPopup->m_aMessage, -1, SSelectionPopupContext::POPUP_MAX_WIDTH);
View.HSplitTop(TextBoundingBox.m_H, &Slot, &View);
if(pSelectionPopup->m_aMessage[0] != '\0')
{
const STextBoundingBox TextBoundingBox = pUI->TextRender()->TextBoundingBox(pSelectionPopup->m_FontSize, pSelectionPopup->m_aMessage, -1, pSelectionPopup->m_Width);
View.HSplitTop(TextBoundingBox.m_H, &Slot, &View);
if(pScrollRegion->AddRect(Slot))
{
pUI->TextRender()->Text(Slot.x, Slot.y, pSelectionPopup->m_FontSize, pSelectionPopup->m_aMessage, Slot.w);
}
}
pUI->TextRender()->Text(Slot.x, Slot.y, SSelectionPopupContext::POPUP_FONT_SIZE, pSelectionPopup->m_aMessage, Slot.w);
pSelectionPopup->m_vButtonContainers.resize(pSelectionPopup->m_Entries.size());
pSelectionPopup->m_vButtonContainers.resize(pSelectionPopup->m_vEntries.size());
size_t Index = 0;
for(const auto &Entry : pSelectionPopup->m_Entries)
for(const auto &Entry : pSelectionPopup->m_vEntries)
{
View.HSplitTop(SSelectionPopupContext::POPUP_ENTRY_SPACING, nullptr, &View);
View.HSplitTop(SSelectionPopupContext::POPUP_ENTRY_HEIGHT, &Slot, &View);
if(pUI->DoButton_PopupMenu(&pSelectionPopup->m_vButtonContainers[Index], Entry.c_str(), &Slot, TEXTALIGN_ML))
pSelectionPopup->m_pSelection = &Entry;
if(pSelectionPopup->m_aMessage[0] != '\0' || Index != 0)
View.HSplitTop(pSelectionPopup->m_EntrySpacing, nullptr, &View);
View.HSplitTop(pSelectionPopup->m_EntryHeight, &Slot, &View);
if(pScrollRegion->AddRect(Slot))
{
if(pUI->DoButton_PopupMenu(&pSelectionPopup->m_vButtonContainers[Index], Entry.c_str(), &Slot, pSelectionPopup->m_FontSize, TEXTALIGN_ML, pSelectionPopup->m_EntryPadding, pSelectionPopup->m_TransparentButtons))
{
pSelectionPopup->m_pSelection = &Entry;
pSelectionPopup->m_SelectionIndex = Index;
}
}
++Index;
}
pScrollRegion->End();
return pSelectionPopup->m_pSelection == nullptr ? CUI::POPUP_KEEP_OPEN : CUI::POPUP_CLOSE_CURRENT;
}
void CUI::ShowPopupSelection(float X, float Y, SSelectionPopupContext *pContext)
{
const STextBoundingBox TextBoundingBox = TextRender()->TextBoundingBox(SSelectionPopupContext::POPUP_FONT_SIZE, pContext->m_aMessage, -1, SSelectionPopupContext::POPUP_MAX_WIDTH);
const float PopupHeight = TextBoundingBox.m_H + pContext->m_Entries.size() * (SSelectionPopupContext::POPUP_ENTRY_HEIGHT + SSelectionPopupContext::POPUP_ENTRY_SPACING) + 10.0f;
const STextBoundingBox TextBoundingBox = TextRender()->TextBoundingBox(pContext->m_FontSize, pContext->m_aMessage, -1, pContext->m_Width);
const float PopupHeight = minimum((pContext->m_aMessage[0] == '\0' ? -pContext->m_EntrySpacing : TextBoundingBox.m_H) + pContext->m_vEntries.size() * (pContext->m_EntryHeight + pContext->m_EntrySpacing) + (SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN) * 2 + CScrollRegion::HEIGHT_MAGIC_FIX, Screen()->h * 0.4f);
pContext->m_pUI = this;
pContext->m_pSelection = nullptr;
DoPopupMenu(pContext, X, Y, SSelectionPopupContext::POPUP_MAX_WIDTH + 10.0f, PopupHeight, pContext, PopupSelection);
pContext->m_SelectionIndex = -1;
pContext->m_Props.m_Corners = IGraphics::CORNER_ALL;
if(pContext->m_AlignmentHeight >= 0.0f)
{
constexpr float Margin = SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN;
if(X + pContext->m_Width > Screen()->w - Margin)
{
X = maximum<float>(X - pContext->m_Width, Margin);
}
if(Y + pContext->m_AlignmentHeight + PopupHeight > Screen()->h - Margin)
{
Y -= PopupHeight;
pContext->m_Props.m_Corners = IGraphics::CORNER_T;
}
else
{
Y += pContext->m_AlignmentHeight;
pContext->m_Props.m_Corners = IGraphics::CORNER_B;
}
}
DoPopupMenu(pContext, X, Y, pContext->m_Width, PopupHeight, pContext, PopupSelection, pContext->m_Props);
}
int CUI::DoDropDown(CUIRect *pRect, int CurSelection, const char **pStrs, int Num, SDropDownState &State)
{
if(!State.m_Init)
{
State.m_UiElement.Init(this, -1);
State.m_Init = true;
}
const auto LabelFunc = [CurSelection, pStrs]() {
return CurSelection > -1 ? pStrs[CurSelection] : "";
};
SMenuButtonProperties Props;
Props.m_HintRequiresStringCheck = true;
Props.m_HintCanChangePositionOrSize = true;
if(IsPopupOpen(&State.m_SelectionPopupContext))
Props.m_Corners = IGraphics::CORNER_ALL & (~State.m_SelectionPopupContext.m_Props.m_Corners);
if(DoButton_Menu(State.m_UiElement, &State.m_ButtonContainer, LabelFunc, pRect, Props))
{
State.m_SelectionPopupContext.Reset();
State.m_SelectionPopupContext.m_Props.m_BorderColor = ColorRGBA(0.7f, 0.7f, 0.7f, 0.9f);
State.m_SelectionPopupContext.m_Props.m_BackgroundColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f);
for(int i = 0; i < Num; ++i)
State.m_SelectionPopupContext.m_vEntries.emplace_back(pStrs[i]);
State.m_SelectionPopupContext.m_EntryHeight = pRect->h;
State.m_SelectionPopupContext.m_EntryPadding = pRect->h >= 20.0f ? 2.0f : 1.0f;
State.m_SelectionPopupContext.m_FontSize = (State.m_SelectionPopupContext.m_EntryHeight - 2 * State.m_SelectionPopupContext.m_EntryPadding) * CUI::ms_FontmodHeight;
State.m_SelectionPopupContext.m_Width = pRect->w;
State.m_SelectionPopupContext.m_AlignmentHeight = pRect->h;
State.m_SelectionPopupContext.m_TransparentButtons = true;
ShowPopupSelection(pRect->x, pRect->y, &State.m_SelectionPopupContext);
}
CUIRect DropDownIcon;
pRect->HMargin(2.0f, &DropDownIcon);
DropDownIcon.VSplitRight(5.0f, &DropDownIcon, nullptr);
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
DoLabel(&DropDownIcon, FONT_ICON_CIRCLE_CHEVRON_DOWN, DropDownIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MR);
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(nullptr);
if(State.m_SelectionPopupContext.m_SelectionIndex >= 0)
{
const int NewSelection = State.m_SelectionPopupContext.m_SelectionIndex;
State.m_SelectionPopupContext.Reset();
return NewSelection;
}
return CurSelection;
}
CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View, bool Active)

View file

@ -244,6 +244,13 @@ struct SPopupMenuId
{
};
struct SPopupMenuProperties
{
int m_Corners = IGraphics::CORNER_ALL;
ColorRGBA m_BorderColor = ColorRGBA(0.5f, 0.5f, 0.5f, 0.75f);
ColorRGBA m_BackgroundColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.75f);
};
class CUI
{
public:
@ -321,8 +328,8 @@ private:
static constexpr float POPUP_MARGIN = 4.0f;
const SPopupMenuId *m_pID;
SPopupMenuProperties m_Props;
CUIRect m_Rect;
int m_Corners;
void *m_pContext;
FPopupMenuFunction m_pfnFunc;
};
@ -491,7 +498,7 @@ public:
int DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pID, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props = {});
// only used for popup menus
int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, int Align);
int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding = 0.0f, bool TransparentInactive = false);
// value selector
int64_t DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props = {});
@ -510,7 +517,7 @@ public:
void DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect, const char *pStr, int Min, int Max, const IScrollbarScale *pScale = &ms_LinearScrollbarScale, unsigned Flags = 0u, const char *pSuffix = "");
// popup menu
void DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, int Corners = IGraphics::CORNER_ALL);
void DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, const SPopupMenuProperties &Props = {});
void RenderPopupMenus();
void ClosePopupMenu(const SPopupMenuId *pID, bool IncludeDescendants = false);
void ClosePopupMenus();
@ -560,16 +567,21 @@ public:
struct SSelectionPopupContext : public SPopupMenuId
{
static constexpr float POPUP_MAX_WIDTH = 300.0f;
static constexpr float POPUP_FONT_SIZE = 10.0f;
static constexpr float POPUP_ENTRY_HEIGHT = 12.0f;
static constexpr float POPUP_ENTRY_SPACING = 5.0f;
CUI *m_pUI; // set by CUI when popup is shown
class CScrollRegion *m_pScrollRegion;
SPopupMenuProperties m_Props;
char m_aMessage[256];
std::set<std::string> m_Entries;
std::vector<std::string> m_vEntries;
std::vector<CButtonContainer> m_vButtonContainers;
const std::string *m_pSelection;
int m_SelectionIndex;
float m_EntryHeight;
float m_EntryPadding;
float m_EntrySpacing;
float m_FontSize;
float m_Width;
float m_AlignmentHeight;
bool m_TransparentButtons;
SSelectionPopupContext();
void Reset();
@ -587,6 +599,16 @@ public:
const char m_aValueSelectorIds[5] = {0};
};
void ShowPopupColorPicker(float X, float Y, SColorPickerPopupContext *pContext);
// dropdown menu
struct SDropDownState
{
SSelectionPopupContext m_SelectionPopupContext;
CUIElement m_UiElement;
CButtonContainer m_ButtonContainer;
bool m_Init = false;
};
int DoDropDown(CUIRect *pRect, int CurSelection, const char **pStrs, int Num, SDropDownState &State);
};
#endif

View file

@ -34,11 +34,16 @@ void CScrollRegion::Begin(CUIRect *pClipRect, vec2 *pOutOffset, const CScrollReg
const bool ContentOverflows = m_ContentH > pClipRect->h;
const bool ForceShowScrollbar = m_Params.m_Flags & CScrollRegionParams::FLAG_CONTENT_STATIC_WIDTH;
const bool HasScrollBar = ContentOverflows || ForceShowScrollbar;
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);
pClipRect->VSplitRight(m_Params.m_ScrollbarWidth, HasScrollBar ? pClipRect : nullptr, &ScrollBarBg);
if(m_Params.m_ScrollbarNoMarginRight)
{
ScrollBarBg.HMargin(m_Params.m_ScrollbarMargin, &m_RailRect);
m_RailRect.VSplitLeft(m_Params.m_ScrollbarMargin, nullptr, &m_RailRect);
}
else
ScrollBarBg.Margin(m_Params.m_ScrollbarMargin, &m_RailRect);
// only show scrollbar if required
if(HasScrollBar)
@ -184,8 +189,8 @@ void CScrollRegion::End()
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(std::ceil(Rect.y + Rect.h - (m_ClipRect.y + m_ContentScrollOff.y)) + 1.0f, m_ContentH);
// Round up and add magic to fix pixel clipping at the end of the scrolling area
m_ContentH = maximum(std::ceil(Rect.y + Rect.h - (m_ClipRect.y + m_ContentScrollOff.y)) + HEIGHT_MAGIC_FIX, m_ContentH);
if(ShouldScrollHere)
ScrollHere();
return !IsRectClipped(Rect);

View file

@ -10,6 +10,7 @@ struct CScrollRegionParams
bool m_Active;
float m_ScrollbarWidth;
float m_ScrollbarMargin;
bool m_ScrollbarNoMarginRight;
float m_SliderMinHeight;
float m_ScrollUnit;
ColorRGBA m_ClipBgColor;
@ -30,6 +31,7 @@ struct CScrollRegionParams
m_Active = true;
m_ScrollbarWidth = 20.0f;
m_ScrollbarMargin = 5.0f;
m_ScrollbarNoMarginRight = false;
m_SliderMinHeight = 25.0f;
m_ScrollUnit = 10.0f;
m_ClipBgColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
@ -89,6 +91,10 @@ Usage:
class CScrollRegion : private CUIElementBase
{
public:
// TODO: Properly fix whatever is causing the 1-pixel discrepancy in scrolling rect height and remove this magic value.
// Currently this must be added when calculating the required height of a UI rect for a scroll region to get a perfect fit.
static constexpr float HEIGHT_MAGIC_FIX = 1.0f;
enum EScrollRelative
{
SCROLLRELATIVE_UP = -1,

View file

@ -5962,13 +5962,16 @@ void CEditor::RenderExtraEditorDragBar(CUIRect View, float *pSplit)
void CEditor::RenderMenubar(CUIRect MenuBar)
{
SPopupMenuProperties PopupProperties;
PopupProperties.m_Corners = IGraphics::CORNER_R | IGraphics::CORNER_B;
CUIRect FileButton;
static int s_FileButton = 0;
MenuBar.VSplitLeft(60.0f, &FileButton, &MenuBar);
if(DoButton_Menu(&s_FileButton, "File", 0, &FileButton, 0, nullptr))
{
static SPopupMenuId s_PopupMenuFileId;
UI()->DoPopupMenu(&s_PopupMenuFileId, FileButton.x, FileButton.y + FileButton.h - 1.0f, 120.0f, 174.0f, this, PopupMenuFile, IGraphics::CORNER_R | IGraphics::CORNER_B);
UI()->DoPopupMenu(&s_PopupMenuFileId, FileButton.x, FileButton.y + FileButton.h - 1.0f, 120.0f, 174.0f, this, PopupMenuFile, PopupProperties);
}
MenuBar.VSplitLeft(5.0f, nullptr, &MenuBar);
@ -5979,7 +5982,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar)
if(DoButton_Menu(&s_ToolsButton, "Tools", 0, &ToolsButton, 0, nullptr))
{
static SPopupMenuId s_PopupMenuToolsId;
UI()->DoPopupMenu(&s_PopupMenuToolsId, ToolsButton.x, ToolsButton.y + ToolsButton.h - 1.0f, 200.0f, 50.0f, this, PopupMenuTools, IGraphics::CORNER_R | IGraphics::CORNER_B);
UI()->DoPopupMenu(&s_PopupMenuToolsId, ToolsButton.x, ToolsButton.y + ToolsButton.h - 1.0f, 200.0f, 50.0f, this, PopupMenuTools, PopupProperties);
}
MenuBar.VSplitLeft(5.0f, nullptr, &MenuBar);
@ -5990,7 +5993,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar)
if(DoButton_Menu(&s_SettingsButton, "Settings", 0, &SettingsButton, 0, nullptr))
{
static SPopupMenuId s_PopupMenuEntitiesId;
UI()->DoPopupMenu(&s_PopupMenuEntitiesId, SettingsButton.x, SettingsButton.y + SettingsButton.h - 1.0f, 200.0f, 64.0f, this, PopupMenuSettings, IGraphics::CORNER_R | IGraphics::CORNER_B);
UI()->DoPopupMenu(&s_PopupMenuEntitiesId, SettingsButton.x, SettingsButton.y + SettingsButton.h - 1.0f, 200.0f, 64.0f, this, PopupMenuSettings, PopupProperties);
}
CUIRect Info, Close;

View file

@ -1327,19 +1327,24 @@ CUI::EPopupMenuFunctionResult CEditor::PopupImage(void *pContext, CUIRect View,
}
static CUI::SSelectionPopupContext s_SelectionPopupContext;
static CScrollRegion s_SelectionPopupScrollRegion;
s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion;
if(pEditor->DoButton_MenuItem(&s_ReaddButton, "Readd", 0, &Slot, 0, "Reloads the image from the mapres folder"))
{
char aFilename[IO_MAX_PATH_LENGTH];
str_format(aFilename, sizeof(aFilename), "%s.png", pImg->m_aName);
s_SelectionPopupContext.Reset();
pEditor->Storage()->FindFiles(aFilename, "mapres", IStorage::TYPE_ALL, &s_SelectionPopupContext.m_Entries);
if(s_SelectionPopupContext.m_Entries.empty())
std::set<std::string> EntriesSet;
pEditor->Storage()->FindFiles(aFilename, "mapres", IStorage::TYPE_ALL, &EntriesSet);
for(const auto &Entry : EntriesSet)
s_SelectionPopupContext.m_vEntries.push_back(Entry);
if(s_SelectionPopupContext.m_vEntries.empty())
{
pEditor->ShowFileDialogError("Error: could not find image '%s' in the mapres folder.", aFilename);
}
else if(s_SelectionPopupContext.m_Entries.size() == 1)
else if(s_SelectionPopupContext.m_vEntries.size() == 1)
{
s_SelectionPopupContext.m_pSelection = &*s_SelectionPopupContext.m_Entries.begin();
s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front();
}
else
{
@ -1391,19 +1396,24 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSound(void *pContext, CUIRect View,
CEditorSound *pSound = pEditor->m_Map.m_vpSounds[pEditor->m_SelectedSound];
static CUI::SSelectionPopupContext s_SelectionPopupContext;
static CScrollRegion s_SelectionPopupScrollRegion;
s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion;
if(pEditor->DoButton_MenuItem(&s_ReaddButton, "Readd", 0, &Slot, 0, "Reloads the sound from the mapres folder"))
{
char aFilename[IO_MAX_PATH_LENGTH];
str_format(aFilename, sizeof(aFilename), "%s.opus", pSound->m_aName);
s_SelectionPopupContext.Reset();
pEditor->Storage()->FindFiles(aFilename, "mapres", IStorage::TYPE_ALL, &s_SelectionPopupContext.m_Entries);
if(s_SelectionPopupContext.m_Entries.empty())
std::set<std::string> EntriesSet;
pEditor->Storage()->FindFiles(aFilename, "mapres", IStorage::TYPE_ALL, &EntriesSet);
for(const auto &Entry : EntriesSet)
s_SelectionPopupContext.m_vEntries.push_back(Entry);
if(s_SelectionPopupContext.m_vEntries.empty())
{
pEditor->ShowFileDialogError("Error: could not find sound '%s' in the mapres folder.", aFilename);
}
else if(s_SelectionPopupContext.m_Entries.size() == 1)
else if(s_SelectionPopupContext.m_vEntries.size() == 1)
{
s_SelectionPopupContext.m_pSelection = &*s_SelectionPopupContext.m_Entries.begin();
s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front();
}
else
{