From f1b0f32786d395fcf7a7b2235292c57c3be30c43 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Oct 2020 10:15:52 +0200 Subject: [PATCH] - add an option to the ListMenu class to draw to a fixed virtual screen size. This allows proper dimensions in the stock menus and should make menu layout for mods a lot easier because coordinates are more predictable than with the clean factors. The feature is opt-in for custom content. As long as only stock elements are used, the menu will be able to switch between both on its own, but as soon as custom classes or custom elements are used, the virtual size must be declared explicitly, defaulting to clean scaling. --- src/common/2d/v_draw.cpp | 31 +++++ src/common/2d/v_draw.h | 1 + src/menu/menudef.cpp | 18 ++- wadsrc/static/menudef.txt | 3 +- wadsrc/static/zscript/base.zs | 27 +++++ wadsrc/static/zscript/ui/menu/listmenu.zs | 6 +- .../static/zscript/ui/menu/listmenuitems.zs | 107 +++++++++--------- .../static/zscript/ui/menu/playerdisplay.zs | 39 +++++-- 8 files changed, 165 insertions(+), 67 deletions(-) diff --git a/src/common/2d/v_draw.cpp b/src/common/2d/v_draw.cpp index a37f930f37..86a85c55cd 100644 --- a/src/common/2d/v_draw.cpp +++ b/src/common/2d/v_draw.cpp @@ -410,6 +410,29 @@ void CalcFullscreenScale(DrawParms *parms, double srcwidth, double srcheight, in } } +DEFINE_ACTION_FUNCTION(_Screen, GetFullscreenRect) +{ + PARAM_PROLOGUE; + PARAM_FLOAT(virtw); + PARAM_FLOAT(virth); + PARAM_INT(fsmode); + + if (!twod->HasBegun2D()) ThrowAbortException(X_OTHER, "Attempt to draw to screen outside a draw function"); + + DrawParms parms; + DoubleRect rect; + parms.viewport.width = twod->GetWidth(); + parms.viewport.height = twod->GetHeight(); + CalcFullscreenScale(&parms, virtw, virth, fsmode, rect); + if (numret >= 1) ret[0].SetFloat(rect.left); + if (numret >= 2) ret[1].SetFloat(rect.top); + if (numret >= 3) ret[2].SetFloat(rect.width); + if (numret >= 4) ret[3].SetFloat(rect.height); + return MIN(numret, 4); +} + + + //========================================================================== // // Draw parameter parsing @@ -460,6 +483,13 @@ bool SetTextureParms(F2DDrawer * drawer, DrawParms *parms, FGameTexture *img, do parms->destheight = parms->texheight * CleanYfac; break; + case DTA_CleanTop: + parms->x = (parms->x - 160.0) * CleanXfac + (GetWidth() * 0.5); + parms->y = (parms->y) * CleanYfac; + parms->destwidth = parms->texwidth * CleanXfac; + parms->destheight = parms->texheight * CleanYfac; + break; + case DTA_CleanNoMove: parms->destwidth = parms->texwidth * CleanXfac; parms->destheight = parms->texheight * CleanYfac; @@ -716,6 +746,7 @@ bool ParseDrawTextureTags(F2DDrawer *drawer, FGameTexture *img, double x, double break; case DTA_Clean: + case DTA_CleanTop: boolval = ListGetInt(tags); if (boolval) { diff --git a/src/common/2d/v_draw.h b/src/common/2d/v_draw.h index c185669364..dc9b8d28c6 100644 --- a/src/common/2d/v_draw.h +++ b/src/common/2d/v_draw.h @@ -130,6 +130,7 @@ enum DTA_Rotate, DTA_FlipOffsets, // Flips offsets when using DTA_FlipX and DTA_FlipY, this cannot be automatic due to unexpected behavior with unoffsetted graphics. DTA_Indexed, // Use an indexed texture combined with the given translation. + DTA_CleanTop, // Like DTA_Clean but aligns to the top of the screen instead of the center. }; diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp index 947e62abb5..35ad695e8d 100644 --- a/src/menu/menudef.cpp +++ b/src/menu/menudef.cpp @@ -270,6 +270,8 @@ static bool CheckSkipOptionBlock(FScanner &sc) static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc) { + bool sizeset = false; + bool sizecompatible = true; sc.MustGetStringName("{"); while (!sc.CheckString("}")) { @@ -303,6 +305,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc) sc.ScriptError("Unknown menu class '%s'", sc.String); } desc->mClass = cls; + sizecompatible = false; } else if (sc.Compare("Selector")) { @@ -382,14 +385,23 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc) { desc->mVirtWidth = -1; } - if (sc.Compare("optclean")) + else if (sc.Compare("optclean")) { desc->mVirtWidth = -2; } + else + { + sc.ScriptError("Invalid value '%s' for 'size'", sc.String); + } } } else { + // all item classes from which we know that they support sized scaling. + // If anything else comes through here the option to swich scaling mode is disabled for this menu. + static const char* const compatibles[] = { "StaticPatch", "StaticText", "StaticTextCentered", "TextItem", "PatchItem", "PlayerDisplay", nullptr }; + if (sc.MatchString(compatibles) < 0) sizecompatible = false; + bool success = false; FStringf buildname("ListMenuItem%s", sc.String); PClass *cls = PClass::FindClass(buildname); @@ -521,6 +533,10 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc) } } } + if (!sizeset && sizecompatible) // allow unclean scaling on this menu + { + desc->mVirtWidth = -2; + } for (auto &p : desc->mItems) { GC::WriteBarrier(p); diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index e56ad72305..5330269940 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -231,7 +231,6 @@ ListMenu "EpisodeMenu" ListMenu "SkillMenu" { - IfGame(Doom, Chex) { StaticPatch 96, 14, "M_NEWG", 0, "$MNU_NEWGAME" @@ -289,6 +288,7 @@ ListMenu "LoadGameMenu" StaticTextCentered 160, -10, "$MNU_LOADGAME" Position 80,54 Class "LoadMenu" // uses its own implementation + Size Clean } //------------------------------------------------------------------------------------------- @@ -302,6 +302,7 @@ ListMenu "SaveGameMenu" StaticTextCentered 160, -10, "$MNU_SAVEGAME" Position 80,54 Class "SaveMenu" // uses its own implementation + Size Clean } //------------------------------------------------------------------------------------------- diff --git a/wadsrc/static/zscript/base.zs b/wadsrc/static/zscript/base.zs index b1a86b017c..b44b703f70 100644 --- a/wadsrc/static/zscript/base.zs +++ b/wadsrc/static/zscript/base.zs @@ -124,6 +124,30 @@ struct TexMan native static void SetCameraToTexture(Actor viewpoint, String texture, double fov); } +enum EScaleMode +{ + FSMode_None = 0, + FSMode_ScaleToFit = 1, + FSMode_ScaleToFill = 2, + FSMode_ScaleToFit43 = 3, + FSMode_ScaleToScreen = 4, + FSMode_ScaleToFit43Top = 5, + FSMode_ScaleToFit43Bottom = 6, + FSMode_ScaleToHeight = 7, + + + FSMode_Max, + + // These all use ScaleToFit43, their purpose is to cut down on verbosity because they imply the virtual screen size. + FSMode_Predefined = 1000, + FSMode_Fit320x200 = 1000, + FSMode_Fit320x240, + FSMode_Fit640x400, + FSMode_Fit640x480, + FSMode_Fit320x200Top, + FSMode_Predefined_Max, +}; + enum DrawTextureTags { TAG_USER = (1<<30), @@ -208,6 +232,7 @@ enum DrawTextureTags DTA_Rotate, DTA_FlipOffsets, // Flips offsets when using DTA_FlipX and DTA_FlipY, this cannot be automatic due to unexpected behavior with unoffsetted graphics. DTA_Indexed, // Use an indexed texture combined with the given translation. + DTA_CleanTop, // Like DTA_Clean but aligns to the top of the screen instead of the center. }; @@ -257,6 +282,8 @@ struct Screen native native static void ClearClipRect(); native static int, int, int, int GetClipRect(); native static int, int, int, int GetViewWindow(); + native static double, double, double, double GetFullscreenRect(double vwidth, double vheight, int fsmode); + // This is a leftover of the abandoned Inventory.DrawPowerup method. diff --git a/wadsrc/static/zscript/ui/menu/listmenu.zs b/wadsrc/static/zscript/ui/menu/listmenu.zs index 37a896496d..76b384ba6c 100644 --- a/wadsrc/static/zscript/ui/menu/listmenu.zs +++ b/wadsrc/static/zscript/ui/menu/listmenu.zs @@ -2,7 +2,7 @@ class ListMenuDescriptor : MenuDescriptor native { - enum + enum EScale { CleanScale = -1, OptCleanScale = -2 @@ -249,10 +249,10 @@ class ListMenu : Menu { for(int i=0;i= 0 && mDesc.mSelectedItem < mDesc.mItems.Size()) - mDesc.mItems[mDesc.mSelectedItem].DrawSelector(mDesc.mSelectOfsX, mDesc.mSelectOfsY, mDesc.mSelector); + mDesc.mItems[mDesc.mSelectedItem].DrawSelector(mDesc.mSelectOfsX, mDesc.mSelectOfsY, mDesc.mSelector, mDesc); Super.Drawer(); } diff --git a/wadsrc/static/zscript/ui/menu/listmenuitems.zs b/wadsrc/static/zscript/ui/menu/listmenuitems.zs index 16b75ad0d3..dafbfd5d2c 100644 --- a/wadsrc/static/zscript/ui/menu/listmenuitems.zs +++ b/wadsrc/static/zscript/ui/menu/listmenuitems.zs @@ -35,26 +35,54 @@ class ListMenuItem : MenuItemBase { - virtual void DrawSelector(double xofs, double yofs, TextureID tex) + protected void DrawText(ListMenuDescriptor desc, Font fnt, int color, double x, double y, String text, bool ontop = false) + { + int w = desc ? desc.DisplayWidth() : ListMenuDescriptor.CleanScale; + int h = desc ? desc.DisplayHeight() : -1; + if (w == ListMenuDescriptor.CleanScale) + { + screen.DrawText(fnt, color, x, y, text, ontop? DTA_CleanTop : DTA_Clean, true); + } + else + { + screen.DrawText(fnt, color, x, y, text, DTA_VirtualWidth, w, DTA_VirtualHeight, h, DTA_FullscreenScale, FSMode_ScaleToFit43); + } + } + + protected void DrawTexture(ListMenuDescriptor desc, TextureID tex, double x, double y, bool ontop = false) + { + int w = desc ? desc.DisplayWidth() : ListMenuDescriptor.CleanScale; + int h = desc ? desc.DisplayHeight() : -1; + if (w == ListMenuDescriptor.CleanScale) + { + screen.DrawTexture(tex, true, x, y, ontop ? DTA_CleanTop : DTA_Clean, true); + } + else + { + screen.DrawTexture(tex, true, x, y, DTA_VirtualWidth, w, DTA_VirtualHeight, h, DTA_FullscreenScale, FSMode_ScaleToFit43); + } + } + + virtual void DrawSelector(double xofs, double yofs, TextureID tex, ListMenuDescriptor desc = null) { if (tex.isNull()) { if ((Menu.MenuTime() % 8) < 6) { - screen.DrawText(ConFont, OptionMenuSettings.mFontColorSelection, - (mXpos + xofs - 160) * CleanXfac + screen.GetWidth() / 2, - (mYpos + yofs - 100) * CleanYfac + screen.GetHeight() / 2, - "\xd", - DTA_CellX, 8 * CleanXfac, - DTA_CellY, 8 * CleanYfac - ); + DrawText(desc, ConFont, OptionMenuSettings.mFontColorSelection, mXpos + xofs, mYpos + yofs + 8, "\xd"); } } else { - screen.DrawTexture (tex, true, mXpos + xofs, mYpos + yofs, DTA_Clean, true); + DrawTexture(desc, tex, mXpos + xofs, mYpos + yofs); } } + + // We cannot extend Drawer here because it is inherited from the parent class. + virtual void Draw(bool selected, ListMenuDescriptor desc) + { + Drawer(selected); // fall back to the legacy version, if not overridden + } } //============================================================================= @@ -82,43 +110,26 @@ class ListMenuItemStaticPatch : ListMenuItem } - override void Drawer(bool selected) + override void Draw(bool selected, ListMenuDescriptor desc) { if (!mTexture.Exists()) { return; } - let font = generic_ui? NewSmallFont : mFont; - double x = mXpos; Vector2 vec = TexMan.GetScaledSize(mTexture); - if (mYpos >= 0) + + if (mSubstitute == "" || TexMan.OkForLocalization(mTexture, mSubstitute)) { - if (mSubstitute == "" || TexMan.OkForLocalization(mTexture, mSubstitute)) - { - if (mCentered) x -= vec.X / 2; - screen.DrawTexture (mTexture, true, x, mYpos, DTA_Clean, true); - } - else - { - if (mCentered) x -= font.StringWidth(mSubstitute)/2; - screen.DrawText(font, mColor, x, mYpos, mSubstitute, DTA_Clean, true); - } + if (mCentered) x -= vec.X / 2; + DrawTexture(desc, mTexture, x, abs(mYpos), mYpos < 0); } else { - x = (mXpos - 160) * CleanXfac + (Screen.GetWidth()>>1); - if (mSubstitute == "" || TexMan.OkForLocalization(mTexture, mSubstitute)) - { - if (mCentered) x -= (vec.X * CleanXfac)/2; - screen.DrawTexture (mTexture, true, x, -mYpos*CleanYfac, DTA_CleanNoMove, true); - } - else - { - if (mCentered) x -= (font.StringWidth(mSubstitute) * CleanXfac)/2; - screen.DrawText(font, mColor, x, mYpos, mSubstitute, DTA_CleanNoMove, true); - } + let font = generic_ui ? NewSmallFont : mFont; + if (mCentered) x -= font.StringWidth(mSubstitute) / 2; + DrawText(desc, font, mColor, x, abs(mYpos), mSubstitute, mYpos < 0); } } } @@ -162,25 +173,17 @@ class ListMenuItemStaticText : ListMenuItem mCentered = centered; } - override void Drawer(bool selected) + override void Draw(bool selected, ListMenuDescriptor desc) { if (mText.Length() != 0) { let font = generic_ui? NewSmallFont : mFont; String text = Stringtable.Localize(mText); - if (mYpos >= 0) - { - double x = mXpos; - if (mCentered) x -= font.StringWidth(text)/2; - screen.DrawText(font, mColor, x, mYpos, text, DTA_Clean, true); - } - else - { - double x = (mXpos - 160) * CleanXfac + (Screen.GetWidth() >> 1); - if (mCentered) x -= (font.StringWidth(text) * CleanXfac)/2; - screen.DrawText (font, mColor, x, -mYpos*CleanYfac, text, DTA_CleanNoMove, true); - } + + double x = mXpos; + if (mCentered) x -= font.StringWidth(text) / 2; + DrawText(desc, font, mColor, x, abs(mYpos), text, mYpos < 0); } } } @@ -288,10 +291,10 @@ class ListMenuItemTextItem : ListMenuItemSelectable mHotkey = hotkey.GetNextCodePoint(0); } - override void Drawer(bool selected) + override void Draw(bool selected, ListMenuDescriptor desc) { - let font = generic_ui? NewSmallFont : mFont; - screen.DrawText(font, selected ? mColorSelected : mColor, mXpos, mYpos, mText, DTA_Clean, true); + let font = generic_ui ? NewSmallFont : mFont; + DrawText(desc, font, selected ? mColorSelected : mColor, mXpos, mYpos, mText); } override int GetWidth() @@ -325,9 +328,9 @@ class ListMenuItemPatchItem : ListMenuItemSelectable mTexture = patch; } - override void Drawer(bool selected) + override void Draw(bool selected, ListMenuDescriptor desc) { - screen.DrawTexture (mTexture, true, mXpos, mYpos, DTA_Clean, true); + DrawTexture(desc, mTexture, mXpos, mYpos); } override int GetWidth() diff --git a/wadsrc/static/zscript/ui/menu/playerdisplay.zs b/wadsrc/static/zscript/ui/menu/playerdisplay.zs index 557185d1d7..e3d24438eb 100644 --- a/wadsrc/static/zscript/ui/menu/playerdisplay.zs +++ b/wadsrc/static/zscript/ui/menu/playerdisplay.zs @@ -228,7 +228,7 @@ class ListMenuItemPlayerDisplay : ListMenuItem // //============================================================================= - override void Drawer(bool selected) + override void Draw(bool selected, ListMenuDescriptor desc) { if (mMode == 0 && !UpdatePlayerClass()) { @@ -241,12 +241,31 @@ class ListMenuItemPlayerDisplay : ListMenuItem if (portrait != 'None' && !mNoportrait) { TextureID texid = TexMan.CheckForTexture(portrait, TexMan.Type_MiscPatch); - screen.DrawTexture (texid, true, mXpos, mYpos, DTA_Clean, true); + DrawTexture (desc, texid, mXpos, mYpos); } else { - int x = int(mXpos - 160) * CleanXfac + (screen.GetWidth() >> 1); - int y = int(mYpos - 100) * CleanYfac + (screen.GetHeight() >> 1); + // Here we need to calculate the coordinates manually because Screen.DrawFrame only works in window coordinates and have to match the rest to it. + int x, y; + int w = desc.DisplayWidth(); + int h = desc.DisplayHeight(); + double sx, sy; + if (w == ListMenuDescriptor.CleanScale) + { + x = int(mXpos - 160) * CleanXfac + (screen.GetWidth() >> 1); + y = int(mYpos - 100) * CleanYfac + (screen.GetHeight() >> 1); + sx = CleanXfac; + sy = CleanYfac; + } + else + { + double fx, fy, fw, fh; + [fx, fy, fw, fh] = Screen.GetFullscreenRect(w, h, FSMode_ScaleToFit43); + sx = fw / w; + sy = fh / h; + x = int(fx + mXpos * sx); + y = int(fy + mYpos * sy); + } int r = mBaseColor.r + mAddColor.r; int g = mBaseColor.g + mAddColor.g; @@ -258,12 +277,12 @@ class ListMenuItemPlayerDisplay : ListMenuItem Color c = Color(255, r, g, b); screen.DrawTexture(mBackdrop, false, x, y - 1, - DTA_DestWidth, 72 * CleanXfac, - DTA_DestHeight, 80 * CleanYfac, + DTA_DestWidthF, 72. * sx, + DTA_DestHeightF, 80. * sy, DTA_Color, c, DTA_Masked, true); - Screen.DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1); + Screen.DrawFrame (x, y, 72*sx, 80*sy-1); if (mPlayerState != NULL) { @@ -277,11 +296,11 @@ class ListMenuItemPlayerDisplay : ListMenuItem { int trans = mTranslate? Translation.MakeID(TRANSLATION_Players, MAXPLAYERS) : 0; let tscale = TexMan.GetScaledSize(sprite); - Scale.X *= CleanXfac * tscale.X; - Scale.Y *= CleanYfac * tscale.Y; + Scale.X *= sx * tscale.X; + Scale.Y *= sy * tscale.Y; screen.DrawTexture (sprite, false, - x + 36*CleanXfac, y + 71*CleanYfac, + x + 36*sx, y + 71*sy, DTA_DestWidthF, Scale.X, DTA_DestHeightF, Scale.Y, DTA_TranslationIndex, trans, DTA_FlipX, flip);