There have been quite a bit of number of questions how to change scale of the gui, because this option is hidden from the user.

Use dropdown with values. Implement confirmation box with countdown to
revert scale change because buttons can get unable to click.

Differential revision: D3037
Comments by: @vladislavbelov, @Stan, @wraitii, @pieq, @sera
Tested by: @Langbart
This was SVN commit r25966.
This commit is contained in:
Angen 2021-10-17 10:58:51 +00:00
parent 9c2a09067f
commit b4fbbed379
19 changed files with 287 additions and 57 deletions

View File

@ -15,6 +15,24 @@ function messageBox(mbWidth, mbHeight, mbMessage, mbTitle, mbButtonCaptions, mbB
});
}
function timedConfirmation(width, height, message, timeout, title, buttonCaptions, btnCode, callbackArgs)
{
Engine.PushGuiPage(
"page_timedconfirmation.xml",
{
"width": width,
"height": height,
"message": message,
"timeout": timeout,
"title": title,
"buttonCaptions": buttonCaptions
},
button => {
if (btnCode !== undefined && btnCode[button])
btnCode[button](callbackArgs ? callbackArgs[button] : undefined);
});
}
function openURL(url)
{
Engine.OpenURL(url);

View File

@ -0,0 +1,35 @@
function distributeButtonsHorizontally(button, captions)
{
const y1 = "100%-46";
const y2 = "100%-18";
switch (captions.length)
{
case 1:
button[0].size = "18 " + y1 + " 100%-18 " + y2;
break;
case 2:
button[0].size = "18 " + y1 + " 50%-5 " + y2;
button[1].size = "50%+5 " + y1 + " 100%-18 " + y2;
break;
case 3:
button[0].size = "18 " + y1 + " 33%-5 " + y2;
button[1].size = "33%+5 " + y1 + " 66%-5 " + y2;
button[2].size = "66%+5 " + y1 + " 100%-18 " + y2;
break;
}
}
function setButtonCaptionsAndVisibitily(button, captions, cancelHotkey, name)
{
captions.forEach((caption, i) => {
button[i] = Engine.GetGUIObjectByName(name + (i + 1));
button[i].caption = caption;
button[i].hidden = false;
button[i].onPress = () => {
Engine.PopGuiPage(i);
};
if (i == 0)
cancelHotkey.onPress = button[i].onPress;
});
}

View File

@ -24,37 +24,7 @@ function init(data)
let captions = data.buttonCaptions || [translate("OK")];
// Set button captions and visibility
let mbButton = [];
captions.forEach((caption, i) => {
mbButton[i] = Engine.GetGUIObjectByName("mbButton" + (i + 1));
mbButton[i].caption = caption;
mbButton[i].hidden = false;
mbButton[i].onPress = () => {
Engine.PopGuiPage(i);
};
// Convention: Cancel is the first button
if (i == 0)
mbCancelHotkey.onPress = mbButton[i].onPress;
});
// Distribute buttons horizontally
let y1 = "100%-46";
let y2 = "100%-18";
switch (captions.length)
{
case 1:
mbButton[0].size = "18 " + y1 + " 100%-18 " + y2;
break;
case 2:
mbButton[0].size = "18 " + y1 + " 50%-5 " + y2;
mbButton[1].size = "50%+5 " + y1 + " 100%-18 " + y2;
break;
case 3:
mbButton[0].size = "18 " + y1 + " 33%-5 " + y2;
mbButton[1].size = "33%+5 " + y1 + " 66%-5 " + y2;
mbButton[2].size = "66%+5 " + y1 + " 100%-18 " + y2;
break;
}
setButtonCaptionsAndVisibitily(mbButton, captions, mbCancelHotkey, "mbButton");
distributeButtonsHorizontally(mbButton, captions);
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/modern/setup.xml</include>
<include>common/modern/styles.xml</include>
<include>common/modern/sprites.xml</include>
<include>timedconfirmation/timedconfirmation.xml</include>
</page>

View File

@ -0,0 +1,48 @@
/**
* Currently limited to at most 3 buttons per message box.
* The convention is to have "cancel" appear first.
*/
function init(data)
{
Engine.GetGUIObjectByName("tmcTitleBar").caption = data.title;
const textObj = Engine.GetGUIObjectByName("tmcText");
textObj.caption = data.message;
updateDisplayedTimer(data.timeout);
Engine.GetGUIObjectByName("tmcTimer").caption = data.timeout;
if (data.font)
textObj.font = data.font;
const cancelHotkey = Engine.GetGUIObjectByName("tmcCancelHotkey");
cancelHotkey.onPress = Engine.PopGuiPage;
const lRDiff = data.width / 2;
const uDDiff = data.height / 2;
Engine.GetGUIObjectByName("tmcMain").size = "50%-" + lRDiff + " 50%-" + uDDiff + " 50%+" + lRDiff + " 50%+" + uDDiff;
const captions = data.buttonCaptions || [translate("OK")];
// Set button captions and visibility
const button = [];
setButtonCaptionsAndVisibitily(button, captions, cancelHotkey, "tmcButton");
distributeButtonsHorizontally(button, captions);
}
function onTick()
{
const timerObj = Engine.GetGUIObjectByName("tmcTimer");
let time = +timerObj.caption;
--time;
if (time < 1)
Engine.GetGUIObjectByName("tmcButton1").onPress();
timerObj.caption = time;
updateDisplayedTimer(time);
}
function updateDisplayedTimer(time)
{
Engine.GetGUIObjectByName("tmcTimerDisplay").caption = Math.ceil(time / 100);
}

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script directory="gui/common/"/>
<script directory="gui/timedconfirmation/"/>
<!-- Fade out the background because it's non-interactable -->
<object sprite="ModernFade" type="image"/>
<object name="tmcCancelHotkey" hotkey="cancel" />
<object name="tmcMain"
style="ModernDialog"
type="image"
>
<action on="Tick">
onTick();
</action>
<object name="tmcTitleBar"
style="ModernLabelText"
type="text"
size="50%-128 -18 50%+128 14"
/>
<object name="tmcTimerDisplay"
type="text"
style="ModernLabelText"
size="18 28 100%-18 38"
/>
<object name="tmcText"
type="text"
style="ModernLabelText"
size="18 40 100%-18 100%-64"
/>
<object name="tmcTimer"
type="text"
style="ModernLabelText"
size="18 18 100%-18 100%-64"
hidden="true"
/>
<!-- The size of the following buttons is set dynamically in timedconfirmation.js -->
<object name="tmcButton1"
style="ModernButtonRed"
type="button"
hidden="true"
/>
<object name="tmcButton2"
style="ModernButtonRed"
type="button"
hidden="true"
/>
<object name="tmcButton3"
style="ModernButtonRed"
type="button"
hidden="true"
/>
</object>
</objects>

View File

@ -146,6 +146,35 @@ var g_OptionType = {
};
}
},
"dropdownNumber":
{
"configToValue": value => +value,
"valueToGui": (value, control) => {
control.selected = control.list_data.indexOf("" + value);
},
"guiToValue": control => +control.list_data[control.selected],
"guiSetter": "onSelectionChange",
"initGUI": (option, control) => {
control.list = option.list.map(e => e.label);
control.list_data = option.list.map(e => e.value);
control.onHoverChange = () => {
const item = option.list[control.hovered];
control.tooltip = item && item.tooltip || option.tooltip;
};
},
"timeout": (option, oldValue, hasChanges, newValue) => {
if (!option.timeout)
return;
timedConfirmation(
500, 200,
translate("Do you want to keep changes?"),
500,
translate("Warning"),
[translate("No"), translate("Yes")],
[() => {this.revertChange(option, +oldValue, hasChanges);}, null]
);
}
},
"slider":
{
"configToValue": value => +value,
@ -242,14 +271,20 @@ function displayOptions()
if (optionType.sanitizeValue)
optionType.sanitizeValue(value, control, option);
const oldValue = optionType.configToValue(Engine.ConfigDB_GetValue("user", option.config));
control.tooltip = option.tooltip + (optionType.tooltip ? "\n" + optionType.tooltip(value, option) : "");
const hasChanges = Engine.ConfigDB_HasChanges("user");
Engine.ConfigDB_CreateValue("user", option.config, String(value));
Engine.ConfigDB_SetChanges("user", true);
g_ChangedKeys.add(option.config);
fireConfigChangeHandlers(new Set([option.config]));
if (option.timeout)
optionType.timeout(option, oldValue, hasChanges, value);
if (option.function)
Engine[option.function](value);
@ -315,6 +350,18 @@ function reallySetDefaults()
revertChanges();
}
function revertChange(option, oldValue, hadChanges)
{
if (!hadChanges)
Engine.ConfigDB_SetChanges("user", false);
Engine.ConfigDB_CreateValue("user", option.config, String(oldValue));
if (option.function)
Engine[option.function](oldValue);
displayOptions();
}
function revertChanges()
{
Engine.ConfigDB_Reload("user");

View File

@ -150,6 +150,24 @@
"config": "adaptivefps.session",
"min": 20,
"max": 100
},
{
"type": "dropdownNumber",
"label": "GUI scale",
"timeout": 500,
"tooltip": "GUI scale",
"config": "gui.scale",
"function": "SetGUIScale",
"list": [
{ "value": 0.75, "label": "75%" },
{ "value": 1.00, "label": "100%" },
{ "value": 1.25, "label": "125%" },
{ "value": 1.50, "label": "150%" },
{ "value": 1.75, "label": "175%" },
{ "value": 2.00, "label": "200%" },
{ "value": 2.25, "label": "225%" },
{ "value": 2.50, "label": "250%" }
]
}
]
},

View File

@ -33,6 +33,7 @@
</object>
<object name="option_control_number[n]" size="65% 0 100%-8 100%" type="input" style="ModernInput"/>
<object name="option_control_dropdown[n]" size="65% 0 100%-8 100%" type="dropdown" style="ModernDropDown" tooltip_style="tooltipInstant"/>
<object name="option_control_dropdownNumber[n]" size="65% 0 100%-8 100%" type="dropdown" style="ModernDropDown" tooltip_style="tooltipInstant"/>
<object name="option_control_slider[n]" size="65% 0 100%-8 100%" type="slider" style="ModernSlider"/>
</object>
</repeat>

View File

@ -42,6 +42,7 @@
#include "ps/Hotkey.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/VideoMode.h"
#include "ps/XML/Xeromyces.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptInterface.h"
@ -154,7 +155,7 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev)
// Yes the mouse position is stored as float to avoid
// constant conversions when operating in a
// float-based environment.
m_MousePos = CVector2D((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale);
m_MousePos = CVector2D((float)ev->ev.motion.x / g_VideoMode.GetScale(), (float)ev->ev.motion.y / g_VideoMode.GetScale());
SGUIMessage msg(GUIM_MOUSE_MOTION);
m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::HandleMessage, msg);
@ -179,7 +180,7 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev)
CVector2D oldMousePos = m_MousePos;
if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
{
m_MousePos = CVector2D((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale);
m_MousePos = CVector2D((float)ev->ev.button.x / g_VideoMode.GetScale(), (float)ev->ev.button.y / g_VideoMode.GetScale());
}
// Allow the focused object to pre-empt regular GUI events.
@ -420,7 +421,7 @@ IGUIObject* CGUI::FindObjectUnderMouse()
CSize2D CGUI::GetWindowSize() const
{
return CSize2D{static_cast<float>(g_xres) / g_GuiScale, static_cast<float>(g_yres) / g_GuiScale};
return CSize2D{static_cast<float>(g_xres) / g_VideoMode.GetScale(), static_cast<float>(g_yres) / g_VideoMode.GetScale() };
}
void CGUI::SetFocusedObject(IGUIObject* pObject)

View File

@ -26,12 +26,12 @@
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/SettingTypes/CGUIString.h"
#include "ps/CStrInternStatic.h"
#include "ps/VideoMode.h"
#include "renderer/Renderer.h"
#include <math.h>
extern int g_xres, g_yres;
extern float g_GuiScale;
// TODO Gee: CRect => CPoint ?
void SGenerateTextImage::SetupSpriteCall(
@ -439,12 +439,13 @@ void CGUIText::Draw(CGUI& pGUI, CCanvas2D& canvas, const CGUIColor& DefaultColor
clipping.left = std::ceil(clipping.left);
clipping.right = std::floor(clipping.right);
float scale = g_VideoMode.GetScale();
glEnable(GL_SCISSOR_TEST);
glScissor(
std::ceil(clipping.left * g_GuiScale),
std::ceil(g_yres - clipping.bottom * g_GuiScale),
std::floor(clipping.GetWidth() * g_GuiScale),
std::floor(clipping.GetHeight() * g_GuiScale));
std::ceil(clipping.left * scale),
std::ceil(g_yres - clipping.bottom * scale),
std::floor(clipping.GetWidth() * scale),
std::floor(clipping.GetHeight() * scale));
}
CTextRenderer textRenderer;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,16 +18,15 @@
#include "precompiled.h"
#include "GUIMatrix.h"
#include "ps/VideoMode.h"
#include "maths/Matrix3D.h"
extern int g_xres, g_yres;
extern float g_GuiScale;
CMatrix3D GetDefaultGuiMatrix()
{
float xres = g_xres / g_GuiScale;
float yres = g_yres / g_GuiScale;
float xres = g_xres / g_VideoMode.GetScale();
float yres = g_yres / g_VideoMode.GetScale();
CMatrix3D m;
m.SetIdentity();

View File

@ -31,6 +31,7 @@
#include "ps/GameSetup/Config.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/VideoMode.h"
#include "renderer/Renderer.h"
#include <sstream>
@ -1238,12 +1239,13 @@ void CInput::Draw(CCanvas2D& canvas)
if (cliparea != CRect())
{
float scale = g_VideoMode.GetScale();
glEnable(GL_SCISSOR_TEST);
glScissor(
cliparea.left * g_GuiScale,
g_yres - cliparea.bottom * g_GuiScale,
cliparea.GetWidth() * g_GuiScale,
cliparea.GetHeight() * g_GuiScale);
cliparea.left * scale,
g_yres - cliparea.bottom * scale,
cliparea.GetWidth() * scale,
cliparea.GetHeight() * scale);
}
// These are useful later.

View File

@ -45,6 +45,7 @@
#include "ps/Hotkey.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/VideoMode.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/JSON.h"
@ -116,8 +117,8 @@ void CConsole::UpdateScreenSize(int w, int h)
m_fX = 0;
m_fY = 0;
float height = h * 0.6f;
m_fWidth = w / g_GuiScale;
m_fHeight = height / g_GuiScale;
m_fWidth = w / g_VideoMode.GetScale();
m_fHeight = height / g_VideoMode.GetScale();
}
void CConsole::ToggleVisible()

View File

@ -29,7 +29,6 @@
bool g_PauseOnFocusLoss = false;
int g_xres, g_yres;
float g_GuiScale = 1.0f;
bool g_Quickstart = false;
bool g_DisableAudio = false;
@ -42,11 +41,8 @@ bool g_DisableAudio = false;
static void LoadGlobals()
{
CFG_GET_VAL("pauseonfocusloss", g_PauseOnFocusLoss);
CFG_GET_VAL("gui.scale", g_GuiScale);
}
static void ProcessCommandLineArgs(const CmdLineArgs& args)
{
// TODO: all these options (and the ones processed elsewhere) should

View File

@ -24,7 +24,6 @@
extern bool g_PauseOnFocusLoss;
extern int g_xres, g_yres;
extern float g_GuiScale;
extern bool g_Quickstart;
extern bool g_DisableAudio;

View File

@ -178,7 +178,7 @@ void CVideoMode::CCursor::SetCursor(const CStrW& name)
LOGERROR("Can't create surface for cursor: %s", SDL_GetError());
return;
}
const float scale = g_GuiScale;
const float scale = g_VideoMode.GetScale();
if (scale != 1.0)
{
SDL_Surface* scaledSurface = SDL_CreateRGBSurface(0,
@ -223,6 +223,8 @@ void CVideoMode::ReadConfig()
CFG_GET_VAL("windowed", windowed);
m_ConfigFullscreen = !windowed;
CFG_GET_VAL("gui.scale", m_Scale);
CFG_GET_VAL("xres", m_ConfigW);
CFG_GET_VAL("yres", m_ConfigH);
CFG_GET_VAL("bpp", m_ConfigBPP);
@ -516,6 +518,18 @@ bool CVideoMode::ResizeWindow(int w, int h)
return true;
}
void CVideoMode::Rescale(float scale)
{
ENSURE(m_IsInitialised);
m_Scale = scale;
UpdateRenderer(m_CurrentW, m_CurrentH);
}
float CVideoMode::GetScale() const
{
return m_Scale;
}
bool CVideoMode::SetFullscreen(bool fullscreen)
{
// This might get called before initialisation by psDisplayError;

View File

@ -51,6 +51,11 @@ public:
*/
bool ResizeWindow(int w, int h);
/**
* Set scale and tell dependent compoenent to recompute sizes.
*/
void Rescale(float scale);
/**
* Switch to fullscreen or windowed mode.
*/
@ -89,6 +94,8 @@ public:
int GetDesktopBPP() const;
int GetDesktopFreq() const;
float GetScale() const;
SDL_Window* GetWindow();
void SetWindowIcon();
@ -117,6 +124,8 @@ private:
int m_PreferredBPP = 0;
int m_PreferredFreq = 0;
float m_Scale = 1.0f;
// Config file settings (0 if unspecified)
int m_ConfigW = 0;
int m_ConfigH = 0;

View File

@ -21,6 +21,7 @@
#include "ps/ConfigDB.h"
#include "ps/CLogger.h"
#include "ps/VideoMode.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptRequest.h"
@ -193,6 +194,11 @@ void PauseOnFocusLoss(bool pause)
g_PauseOnFocusLoss = pause;
}
void SetGUIScale(float scale)
{
g_VideoMode.Rescale(scale);
}
void RegisterScriptFunctions(const ScriptRequest& rq)
{
ScriptFunction::Register<&HasChanges>(rq, "ConfigDB_HasChanges");
@ -207,5 +213,6 @@ void RegisterScriptFunctions(const ScriptRequest& rq)
ScriptFunction::Register<&SetFile>(rq, "ConfigDB_SetFile");
ScriptFunction::Register<&Reload>(rq, "ConfigDB_Reload");
ScriptFunction::Register<&PauseOnFocusLoss>(rq, "PauseOnFocusLoss");
ScriptFunction::Register<&SetGUIScale>(rq, "SetGUIScale");
}
}