移动端界面大小设置:默认 600 逻辑高,系统选项可选 600/700/800
Android 上 40250 界面原先固定按 800 逻辑像素高排版,弹窗文字在手机上只有约 1 mm 高。 - UIScaleMode.h + host ui_scale.cpp:逻辑高度来自保存的设置(默认 600),ui_height.cfg 保存,下次启动生效; 桌面可用 MT_UI_HEIGHT 测试 - app.GetUIHeight/GetAppliedUIHeight/SetUIHeight - mt_uiscale PORT 模块:系统选项对话框加高一行,"界面大小 大/中/小",修改后聊天提示重启生效 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e33e41c12b
commit
71d8f8c6e7
+4
-1
@@ -116,8 +116,11 @@ SDL3 宿主主循环(src/host/live_client.cpp run_live_client)
|
|||||||
PORT 扩展(40250 没有对应物,只为移动端/平台而加;不能改 40250 脚本;玩法语义只在用户明确要求时扩展,如 5 页背包):
|
PORT 扩展(40250 没有对应物,只为移动端/平台而加;不能改 40250 脚本;玩法语义只在用户明确要求时扩展,如 5 页背包):
|
||||||
|
|
||||||
- 由 `PythonBoot::RunMainScript` 注入的 Python 模块:`mt_autohunt`(自动狩猎,`AutoHuntScript.inc`)、`mt_register`
|
- 由 `PythonBoot::RunMainScript` 注入的 Python 模块:`mt_autohunt`(自动狩猎,`AutoHuntScript.inc`)、`mt_register`
|
||||||
(客户端内账号注册)、`mt_framerate`(系统选项里的"画面帧率"行)、`mt_inventory`(5 页背包:改写 `InventoryWindow.py` 的装备格起点和页签,
|
(客户端内账号注册)、`mt_framerate`(系统选项里的"画面帧率"行)、`mt_uiscale`(系统选项里的"界面大小"行)、`mt_inventory`(5 页背包:改写 `InventoryWindow.py` 的装备格起点和页签,
|
||||||
泛化翻页、自动药水高亮和选宝石窗口的格子扫描)。它们包装 40250 窗口类的方法,不修改脚本文件。
|
泛化翻页、自动药水高亮和选宝石窗口的格子扫描)。它们包装 40250 窗口类的方法,不修改脚本文件。
|
||||||
|
- 界面大小(`platform/EterBase/UIScaleMode.h` + `src/host/ui_scale.cpp`):Android 上 40250 界面按 600 / 700 / 800
|
||||||
|
逻辑像素高排版(默认 600,最低就是 600:背包窗口 565 高),宽度随屏幕比例;选择存在 `ui_height.cfg`,下次启动生效。
|
||||||
|
桌面按窗口大小排版,`MT_UI_HEIGHT=600` 可在桌面上测试这一设置。
|
||||||
- 触控控制(`src/host/touch_controller.h`):摇杆、攻击/技能按钮,转成 40250 的输入接口。
|
- 触控控制(`src/host/touch_controller.h`):摇杆、攻击/技能按钮,转成 40250 的输入接口。
|
||||||
- 帧率与功耗(`platform/EterBase/FrameRateMode.h` + `src/host/frame_policy.cpp` 的 `FrameRatePolicy`):30 / 60(默认)/ 显示器最高三档,
|
- 帧率与功耗(`platform/EterBase/FrameRateMode.h` + `src/host/frame_policy.cpp` 的 `FrameRatePolicy`):30 / 60(默认)/ 显示器最高三档,
|
||||||
无输入一段时间降到 30 fps(自动狩猎时除外),Android 上配合 `setFrameRate` 和 ADPF。
|
无输入一段时间降到 30 fps(自动狩猎时除外),Android 上配合 `setFrameRate` 和 ADPF。
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ set(MT_NATIVE_RENDER_SOURCES
|
|||||||
vulkan_window.cpp
|
vulkan_window.cpp
|
||||||
sdl_window.cpp
|
sdl_window.cpp
|
||||||
ui_batches.cpp
|
ui_batches.cpp
|
||||||
|
ui_scale.cpp
|
||||||
vk_device.cpp
|
vk_device.cpp
|
||||||
vk_frame.cpp
|
vk_frame.cpp
|
||||||
vk_pipelines.cpp
|
vk_pipelines.cpp
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "native_audio.h"
|
#include "native_audio.h"
|
||||||
#include "perf_log.h"
|
#include "perf_log.h"
|
||||||
#include "texture_decode.h"
|
#include "texture_decode.h"
|
||||||
|
#include "ui_scale.h"
|
||||||
#include "vulkan_window.h"
|
#include "vulkan_window.h"
|
||||||
|
|
||||||
#include "platform/MilesLib/AudioCommands.h"
|
#include "platform/MilesLib/AudioCommands.h"
|
||||||
@@ -452,6 +453,7 @@ int run_live_client(
|
|||||||
};
|
};
|
||||||
while ((frames == 0 || completed < frames) && renderer.poll(true) && PythonBoot::IsAppLooping()) {
|
while ((frames == 0 || completed < frames) && renderer.poll(true) && PythonBoot::IsAppLooping()) {
|
||||||
apply_policy();
|
apply_policy();
|
||||||
|
save_ui_height_if_changed();
|
||||||
double pace_ms = 0.0;
|
double pace_ms = 0.0;
|
||||||
if (cap_period.count()) {
|
if (cap_period.count()) {
|
||||||
// Fixed cadence: a late frame starts the next one at once, a frame more than one period
|
// Fixed cadence: a late frame starts the next one at once, a frame more than one period
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "input_keymap.h"
|
#include "input_keymap.h"
|
||||||
#include "texture_decode.h"
|
#include "texture_decode.h"
|
||||||
|
#include "ui_scale.h"
|
||||||
|
|
||||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||||
#include "platform/ScriptLib/PythonBoot.h"
|
#include "platform/ScriptLib/PythonBoot.h"
|
||||||
@@ -141,20 +142,17 @@ void VulkanWindow::request_screenshot(std::string path, std::uint64_t frame) {
|
|||||||
std::uint32_t VulkanWindow::logical_width() const {
|
std::uint32_t VulkanWindow::logical_width() const {
|
||||||
int w = 0, h = 0;
|
int w = 0, h = 0;
|
||||||
if (window_) SDL_GetWindowSize(window_, &w, &h);
|
if (window_) SDL_GetWindowSize(window_, &w, &h);
|
||||||
#ifdef __ANDROID__
|
// The UI size setting (ui_scale.h; always on Android, MT_UI_HEIGHT on the desktop) fixes the logical
|
||||||
// Keep the 40250 UI at the same height as the macOS 1280x800 window,
|
// height and keeps the device aspect ratio on wider phone displays.
|
||||||
// while retaining the device aspect ratio on wider phone displays.
|
if (const int ui_h = mt_host::ui_height(); ui_h > 0 && w > 0 && h > 0)
|
||||||
if (w > 0 && h > 0) return std::max(1, int(std::lround(double(w) * 800.0 / h)));
|
return std::max(1, int(std::lround(double(w) * ui_h / h)));
|
||||||
#endif
|
|
||||||
return w > 0 ? static_cast<std::uint32_t>(w) : extent_.width;
|
return w > 0 ? static_cast<std::uint32_t>(w) : extent_.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint32_t VulkanWindow::logical_height() const {
|
std::uint32_t VulkanWindow::logical_height() const {
|
||||||
int w = 0, h = 0;
|
int w = 0, h = 0;
|
||||||
if (window_) SDL_GetWindowSize(window_, &w, &h);
|
if (window_) SDL_GetWindowSize(window_, &w, &h);
|
||||||
#ifdef __ANDROID__
|
if (const int ui_h = mt_host::ui_height(); ui_h > 0 && w > 0 && h > 0) return std::uint32_t(ui_h);
|
||||||
if (w > 0 && h > 0) return 800;
|
|
||||||
#endif
|
|
||||||
return h > 0 ? static_cast<std::uint32_t>(h) : extent_.height;
|
return h > 0 ? static_cast<std::uint32_t>(h) : extent_.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#include "ui_scale.h"
|
||||||
|
|
||||||
|
#include "platform/EterBase/UIScaleMode.h"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace mt_host {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string settings_path() {
|
||||||
|
std::string dir;
|
||||||
|
if (char* pref = SDL_GetPrefPath("metin2port", "client")) {
|
||||||
|
dir = pref;
|
||||||
|
SDL_free(pref);
|
||||||
|
}
|
||||||
|
return dir + "ui_height.cfg";
|
||||||
|
}
|
||||||
|
|
||||||
|
int g_saved = 0;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int ui_height() {
|
||||||
|
static const int height = [] {
|
||||||
|
int saved = 0;
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
saved = MtUIScale::c_iDefaultHeight;
|
||||||
|
if (std::ifstream in{settings_path()}; in) {
|
||||||
|
int value = 0;
|
||||||
|
if (in >> value && MtUIScale::IsValid(value)) saved = value;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
int applied = saved;
|
||||||
|
if (const char* env = std::getenv("MT_UI_HEIGHT"); env && *env) {
|
||||||
|
const int value = std::atoi(env);
|
||||||
|
if (MtUIScale::IsValid(value)) applied = saved = value;
|
||||||
|
}
|
||||||
|
g_saved = saved;
|
||||||
|
MtUIScale::Applied().store(applied);
|
||||||
|
MtUIScale::Chosen().store(saved);
|
||||||
|
return applied;
|
||||||
|
}();
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save_ui_height_if_changed() {
|
||||||
|
const int chosen = MtUIScale::Chosen().load(std::memory_order_relaxed);
|
||||||
|
if (!chosen || chosen == g_saved) return;
|
||||||
|
g_saved = chosen;
|
||||||
|
std::ofstream out{settings_path(), std::ios::trunc};
|
||||||
|
out << chosen << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mt_host
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace mt_host {
|
||||||
|
|
||||||
|
// The logical UI height (platform/EterBase/UIScaleMode.h): the saved setting on Android, MT_UI_HEIGHT on
|
||||||
|
// the desktop (a test override that also shows the option), 0 when the window's own size is the UI size.
|
||||||
|
// Loaded on first use, which needs SDL initialised.
|
||||||
|
int ui_height();
|
||||||
|
|
||||||
|
// Writes the player's choice from the system option dialog when it differs from the saved one.
|
||||||
|
void save_ui_height_if_changed();
|
||||||
|
|
||||||
|
} // namespace mt_host
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
// PORT: the player's UI size setting (system option dialog, app.Get/SetUIHeight), shared between the
|
||||||
|
// script thread and the host (src/host/ui_scale.cpp), which lays the 40250 UI out that many logical
|
||||||
|
// pixels high and scales it onto the screen, and saves the choice. 40250 has no counterpart: the PC
|
||||||
|
// client's UI pixels were screen pixels. A lower height draws every window and text larger.
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace MtUIScale {
|
||||||
|
|
||||||
|
// The choices, lowest (largest UI) first. 600 is the 40250 minimum resolution's height: the inventory
|
||||||
|
// and character windows need about that much.
|
||||||
|
inline constexpr int c_aiHeights[] = {600, 700, 800};
|
||||||
|
inline constexpr int c_iDefaultHeight = 600;
|
||||||
|
|
||||||
|
inline bool IsValid(int height)
|
||||||
|
{
|
||||||
|
for (int choice : c_aiHeights)
|
||||||
|
if (choice == height)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The height the running client lays out at (0 = the setting is unavailable: a desktop window lays
|
||||||
|
// out at its own size). Set once by the host at startup; a new choice applies at the next start.
|
||||||
|
inline std::atomic<int>& Applied()
|
||||||
|
{
|
||||||
|
static std::atomic<int> height{0};
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The player's choice, saved by the host when it changes.
|
||||||
|
inline std::atomic<int>& Chosen()
|
||||||
|
{
|
||||||
|
static std::atomic<int> height{0};
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Set(int height)
|
||||||
|
{
|
||||||
|
if (IsValid(height) && Applied().load(std::memory_order_relaxed))
|
||||||
|
Chosen().store(height, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MtUIScale
|
||||||
@@ -877,6 +877,7 @@ namespace PythonBoot
|
|||||||
#include "AutoHuntScript.inc"
|
#include "AutoHuntScript.inc"
|
||||||
#include "RegisterScript.inc"
|
#include "RegisterScript.inc"
|
||||||
#include "FrameRateOptionScript.inc"
|
#include "FrameRateOptionScript.inc"
|
||||||
|
#include "UIScaleOptionScript.inc"
|
||||||
#include "InventoryPagesScript.inc"
|
#include "InventoryPagesScript.inc"
|
||||||
|
|
||||||
static void install_script_module(const char* c_szModule, const char* c_szScript)
|
static void install_script_module(const char* c_szModule, const char* c_szScript)
|
||||||
@@ -934,6 +935,7 @@ bool RunMainScript(const char* lpCmdLine, std::string* error)
|
|||||||
install_script_module("mt_autohunt", c_szAutoHuntScript);
|
install_script_module("mt_autohunt", c_szAutoHuntScript);
|
||||||
install_script_module("mt_register", c_szRegisterScript);
|
install_script_module("mt_register", c_szRegisterScript);
|
||||||
install_script_module("mt_framerate", c_szFrameRateOptionScript);
|
install_script_module("mt_framerate", c_szFrameRateOptionScript);
|
||||||
|
install_script_module("mt_uiscale", c_szUIScaleOptionScript);
|
||||||
install_script_module("mt_inventory", c_szInventoryPagesScript);
|
install_script_module("mt_inventory", c_szInventoryPagesScript);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// The UI-size row of the system option dialog (PORT; 40250 has no counterpart, its UI pixels were screen
|
||||||
|
// pixels), run by PythonBoot::RunMainScript as module mt_uiscale after mt_framerate. It wraps
|
||||||
|
// uiSystemOption.OptionDialog's __Load, grows the dialog by one row below the frame-rate row and fills it
|
||||||
|
// in the same style. The value is app.Get/SetUIHeight (platform/EterBase/UIScaleMode.h): the logical
|
||||||
|
// height the host lays the UI out at, saved by the host and applied at the next start. The row is only
|
||||||
|
// added where the setting exists (Android, or MT_UI_HEIGHT on the desktop).
|
||||||
|
// Strings are UTF-8 here and go through player.AutoHuntLocalize into the locale's code page.
|
||||||
|
static const char c_szUIScaleOptionScript[] = R"PY(# -*- coding: utf-8 -*-
|
||||||
|
import app
|
||||||
|
import chat
|
||||||
|
import player
|
||||||
|
import ui
|
||||||
|
import uiSystemOption
|
||||||
|
|
||||||
|
L = player.AutoHuntLocalize
|
||||||
|
|
||||||
|
SMALL_BUTTON = "d:/ymir work/ui/public/small_button_%02d.sub"
|
||||||
|
ROW_Y = 235
|
||||||
|
ROW_H = 25
|
||||||
|
HEIGHTS = (600, 700, 800)
|
||||||
|
HEIGHT_TEXTS = ("大", "中", "小")
|
||||||
|
|
||||||
|
|
||||||
|
def _Refresh(buttons):
|
||||||
|
height = app.GetUIHeight()
|
||||||
|
for i in xrange(len(buttons)):
|
||||||
|
if HEIGHTS[i] == height:
|
||||||
|
buttons[i].Down()
|
||||||
|
else:
|
||||||
|
buttons[i].SetUp()
|
||||||
|
|
||||||
|
|
||||||
|
def _Select(buttons, height):
|
||||||
|
changed = height != app.GetUIHeight()
|
||||||
|
app.SetUIHeight(height)
|
||||||
|
_Refresh(buttons)
|
||||||
|
if changed:
|
||||||
|
if height == app.GetAppliedUIHeight():
|
||||||
|
chat.AppendChat(chat.CHAT_TYPE_INFO, L("界面大小已恢复为当前设置"))
|
||||||
|
else:
|
||||||
|
chat.AppendChat(chat.CHAT_TYPE_INFO, L("界面大小将在重新启动游戏后生效"))
|
||||||
|
|
||||||
|
|
||||||
|
def _Attach(dialog):
|
||||||
|
if not app.GetAppliedUIHeight():
|
||||||
|
return
|
||||||
|
board = dialog.GetChild("board")
|
||||||
|
width = dialog.GetWidth()
|
||||||
|
height = dialog.GetHeight() + ROW_H
|
||||||
|
dialog.SetSize(width, height)
|
||||||
|
board.SetSize(width, height)
|
||||||
|
dialog.SetCenterPosition()
|
||||||
|
|
||||||
|
label = ui.TextLine()
|
||||||
|
label.SetParent(board)
|
||||||
|
label.SetPosition(30, ROW_Y + 2)
|
||||||
|
label.SetText(L("界面大小"))
|
||||||
|
label.Show()
|
||||||
|
|
||||||
|
buttons = []
|
||||||
|
for i in xrange(len(HEIGHTS)):
|
||||||
|
button = ui.RadioButton()
|
||||||
|
button.SetParent(board)
|
||||||
|
button.SetUpVisual(SMALL_BUTTON % 1)
|
||||||
|
button.SetOverVisual(SMALL_BUTTON % 2)
|
||||||
|
button.SetDownVisual(SMALL_BUTTON % 3)
|
||||||
|
button.SetPosition(110 + 50 * i, ROW_Y)
|
||||||
|
button.SetText(L(HEIGHT_TEXTS[i]))
|
||||||
|
button.SetEvent(lambda height=HEIGHTS[i]: _Select(buttons, height))
|
||||||
|
button.Show()
|
||||||
|
buttons.append(button)
|
||||||
|
_Refresh(buttons)
|
||||||
|
|
||||||
|
dialog.mtUIScaleLabel = label
|
||||||
|
dialog.mtUIScaleButtons = buttons
|
||||||
|
|
||||||
|
|
||||||
|
def __Install():
|
||||||
|
load = uiSystemOption.OptionDialog._OptionDialog__Load
|
||||||
|
|
||||||
|
def Load(self):
|
||||||
|
load(self)
|
||||||
|
try:
|
||||||
|
_Attach(self)
|
||||||
|
except:
|
||||||
|
import dbg
|
||||||
|
dbg.TraceError("mt_uiscale: cannot add the UI size row")
|
||||||
|
|
||||||
|
uiSystemOption.OptionDialog._OptionDialog__Load = Load
|
||||||
|
|
||||||
|
__Install()
|
||||||
|
)PY";
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "PythonApplication.h"
|
#include "PythonApplication.h"
|
||||||
#include "../EterLib/Camera.h"
|
#include "../EterLib/Camera.h"
|
||||||
#include "../../platform/EterBase/FrameRateMode.h" // PORT: frame-rate setting
|
#include "../../platform/EterBase/FrameRateMode.h" // PORT: frame-rate setting
|
||||||
|
#include "../../platform/EterBase/UIScaleMode.h" // PORT: UI size setting
|
||||||
|
|
||||||
extern bool PERF_CHECKER_RENDER_GAME;
|
extern bool PERF_CHECKER_RENDER_GAME;
|
||||||
extern D3DXCOLOR g_fSpecularColor;
|
extern D3DXCOLOR g_fSpecularColor;
|
||||||
@@ -1207,6 +1208,27 @@ PyObject* appSetFrameRateMode(PyObject* poSelf, PyObject* poArgs)
|
|||||||
return Py_BuildNone();
|
return Py_BuildNone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PORT: the UI size setting of the system option dialog (platform/EterBase/UIScaleMode.h). GetUIHeight is
|
||||||
|
// the player's choice (0 = unavailable), GetAppliedUIHeight the height this run lays out at.
|
||||||
|
PyObject* appGetUIHeight(PyObject* poSelf, PyObject* poArgs)
|
||||||
|
{
|
||||||
|
return Py_BuildValue("i", MtUIScale::Chosen().load());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* appGetAppliedUIHeight(PyObject* poSelf, PyObject* poArgs)
|
||||||
|
{
|
||||||
|
return Py_BuildValue("i", MtUIScale::Applied().load());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* appSetUIHeight(PyObject* poSelf, PyObject* poArgs)
|
||||||
|
{
|
||||||
|
int iHeight;
|
||||||
|
if (!PyTuple_GetInteger(poArgs, 0, &iHeight))
|
||||||
|
return Py_BuildException();
|
||||||
|
MtUIScale::Set(iHeight);
|
||||||
|
return Py_BuildNone();
|
||||||
|
}
|
||||||
|
|
||||||
void initapp()
|
void initapp()
|
||||||
{
|
{
|
||||||
static PyMethodDef s_methods[] =
|
static PyMethodDef s_methods[] =
|
||||||
@@ -1275,6 +1297,9 @@ void initapp()
|
|||||||
{ "GetRandom", appGetRandom, METH_VARARGS },
|
{ "GetRandom", appGetRandom, METH_VARARGS },
|
||||||
{ "GetFrameRateMode", appGetFrameRateMode, METH_VARARGS }, // PORT
|
{ "GetFrameRateMode", appGetFrameRateMode, METH_VARARGS }, // PORT
|
||||||
{ "SetFrameRateMode", appSetFrameRateMode, METH_VARARGS }, // PORT
|
{ "SetFrameRateMode", appSetFrameRateMode, METH_VARARGS }, // PORT
|
||||||
|
{ "GetUIHeight", appGetUIHeight, METH_VARARGS }, // PORT
|
||||||
|
{ "GetAppliedUIHeight", appGetAppliedUIHeight, METH_VARARGS }, // PORT
|
||||||
|
{ "SetUIHeight", appSetUIHeight, METH_VARARGS }, // PORT
|
||||||
{ "RunPythonFile", appRunPythonFile, METH_VARARGS },
|
{ "RunPythonFile", appRunPythonFile, METH_VARARGS },
|
||||||
{ "IsWebPageMode", appIsWebPageMode, METH_VARARGS },
|
{ "IsWebPageMode", appIsWebPageMode, METH_VARARGS },
|
||||||
{ "ShowWebPage", appShowWebPage, METH_VARARGS },
|
{ "ShowWebPage", appShowWebPage, METH_VARARGS },
|
||||||
|
|||||||
Reference in New Issue
Block a user