Control GUI modes from Lua

macos_ci_fix
Petr Mikheev 1 year ago
parent f86a45d60a
commit 87eacf774a

@ -7,6 +7,7 @@
#include <SDL_events.h>
#include "../mwgui/mode.hpp"
#include <components/sdlutil/events.hpp>
namespace MWWorld
@ -52,6 +53,10 @@ namespace MWBase
virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
// `arg` is either forwarded from MWGui::pushGuiMode or empty
virtual void uiModeChanged(const MWWorld::Ptr& arg) = 0;
// TODO: notify LuaManager about other events
// virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object,
// const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0;

@ -378,6 +378,12 @@ namespace MWBase
/// Same as viewer->getCamera()->getCullMask(), provided for consistency.
virtual uint32_t getCullMask() = 0;
// Used in Lua bindings
virtual const std::vector<MWGui::GuiMode>& getGuiModeStack() const = 0;
virtual void setDisabledByLua(std::string_view windowId, bool disabled) = 0;
virtual std::vector<std::string_view> getAllWindowIds() const = 0;
virtual std::vector<std::string_view> getAllowedWindowIds(MWGui::GuiMode mode) const = 0;
};
}

@ -30,6 +30,8 @@ namespace MWGui
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "Alchemy"; }
private:
static const float sCountChangeInitialPause; // in seconds
static const float sCountChangeInterval; // in seconds

@ -19,6 +19,8 @@ namespace MWGui
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "Book"; }
protected:
void onNextPageButtonClicked(MyGUI::Widget* sender);
void onPrevPageButtonClicked(MyGUI::Widget* sender);

@ -30,6 +30,8 @@ namespace MWGui
void onFrame(float dt) override;
void clear() override { resetReference(); }
std::string_view getWindowIdForLua() const override { return "Companion"; }
private:
ItemView* mItemView;
SortFilterItemModel* mSortModel;

@ -38,6 +38,8 @@ namespace MWGui
void treatNextOpenAsLoot() { mTreatNextOpenAsLoot = true; }
std::string_view getWindowIdForLua() const override { return "Container"; }
private:
DragAndDrop* mDragAndDrop;

@ -165,6 +165,8 @@ namespace MWGui
void onClose() override;
std::string_view getWindowIdForLua() const override { return "Dialogue"; }
protected:
void updateTopicsPane();
bool isCompanion(const MWWorld::Ptr& actor);

@ -33,6 +33,8 @@ namespace MWGui
void resetReference() override;
std::string_view getWindowIdForLua() const override { return "EnchantingDialog"; }
protected:
void onReferenceUnavailable() override;
void notifyEffectsChanged() override;

@ -65,6 +65,8 @@ namespace MWGui
/// Cycle to previous/next weapon
void cycle(bool next);
std::string_view getWindowIdForLua() const override { return "Inventory"; }
protected:
void onTitleDoubleClicked() override;

@ -16,6 +16,8 @@ namespace MWGui
bool exit() override { return false; }
std::string_view getWindowIdForLua() const override { return "JailScreen"; }
private:
int mDays;

@ -29,6 +29,8 @@ namespace MWGui
/// show/hide the journal window
void setVisible(bool newValue) override = 0;
std::string_view getWindowIdForLua() const override { return "Journal"; }
};
}

@ -15,6 +15,8 @@ namespace MWGui
void onOpen() override;
std::string_view getWindowIdForLua() const override { return "LevelUpDialog"; }
private:
struct Widgets
{

@ -268,6 +268,8 @@ namespace MWGui
void asyncPrepareSaveMap();
std::string_view getWindowIdForLua() const override { return "Map"; }
private:
void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);

@ -16,6 +16,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor) override;
std::string_view getWindowIdForLua() const override { return "MerchantRepair"; }
private:
MyGUI::ScrollView* mList;
MyGUI::Button* mOkButton;

@ -44,6 +44,8 @@ namespace MWGui
void readRecord(ESM::ESMReader& reader, uint32_t type);
void clear() override;
std::string_view getWindowIdForLua() const override { return "QuickKeys"; }
private:
struct keyData
{

@ -26,6 +26,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& gem) override;
std::string_view getWindowIdForLua() const override { return "Recharge"; }
protected:
ItemChargeView* mBox;

@ -23,6 +23,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& item) override;
std::string_view getWindowIdForLua() const override { return "Repair"; }
protected:
ItemChargeView* mRepairBox;

@ -22,6 +22,8 @@ namespace MWGui
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "Scroll"; }
protected:
void onCloseButtonClicked(MyGUI::Widget* _sender);
void onTakeButtonClicked(MyGUI::Widget* _sender);

@ -30,6 +30,8 @@ namespace MWGui
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "SpellBuying"; }
protected:
MyGUI::Button* mCancelButton;
MyGUI::TextBox* mPlayerGold;

@ -158,6 +158,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor) override;
std::string_view getWindowIdForLua() const override { return "SpellCreationDialog"; }
protected:
void onReferenceUnavailable() override;

@ -23,6 +23,8 @@ namespace MWGui
/// Cycle to next/previous spell
void cycle(bool next);
std::string_view getWindowIdForLua() const override { return "Magic"; }
protected:
MyGUI::Widget* mEffectBox;

@ -45,6 +45,8 @@ namespace MWGui
void onOpen() override { onWindowResize(mMainWidget->castType<MyGUI::Window>()); }
std::string_view getWindowIdForLua() const override { return "Stats"; }
private:
void addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);

@ -47,6 +47,8 @@ namespace MWGui
typedef MyGUI::delegates::MultiDelegate<> EventHandle_TradeDone;
EventHandle_TradeDone eventTradeDone;
std::string_view getWindowIdForLua() const override { return "Trade"; }
private:
ItemView* mItemView;
SortFilterItemModel* mSortModel;

@ -31,6 +31,8 @@ namespace MWGui
void clear() override { resetReference(); }
std::string_view getWindowIdForLua() const override { return "Training"; }
protected:
void onReferenceUnavailable() override;

@ -19,6 +19,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor) override;
std::string_view getWindowIdForLua() const override { return "Travel"; }
protected:
MyGUI::Button* mCancelButton;
MyGUI::TextBox* mPlayerGold;

@ -43,6 +43,8 @@ namespace MWGui
WindowBase* getProgressBar() { return &mProgressBar; }
std::string_view getWindowIdForLua() const override { return "WaitDialog"; }
protected:
MyGUI::TextBox* mDateTimeText;
MyGUI::TextBox* mRestText;

@ -48,6 +48,7 @@ void WindowBase::onDoubleClick(MyGUI::Widget* _sender)
void WindowBase::setVisible(bool visible)
{
visible = visible && !mDisabledByLua;
bool wasVisible = mMainWidget->getVisible();
mMainWidget->setVisible(visible);

@ -49,11 +49,16 @@ namespace MWGui
virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) {}
virtual std::string_view getWindowIdForLua() const { return ""; }
void setDisabledByLua(bool disabled) { mDisabledByLua = disabled; }
protected:
virtual void onTitleDoubleClicked();
private:
void onDoubleClick(MyGUI::Widget* _sender);
bool mDisabledByLua = false;
};
/*

@ -525,6 +525,13 @@ namespace MWGui
mStatsWatcher->addListener(mHud);
mStatsWatcher->addListener(mStatsWindow);
mStatsWatcher->addListener(mCharGen.get());
for (auto& window : mWindows)
{
std::string_view id = window->getWindowIdForLua();
if (!id.empty())
mLuaIdToWindow.emplace(id, window.get());
}
}
void WindowManager::setNewGame(bool newgame)
@ -1277,6 +1284,7 @@ namespace MWGui
mKeyboardNavigation->restoreFocus(mode);
updateVisible();
MWBase::Environment::get().getLuaManager()->uiModeChanged(arg);
}
void WindowManager::setCullMask(uint32_t mask)
@ -1309,6 +1317,7 @@ namespace MWGui
mGuiModeStates[mode].update(false);
if (!noSound)
playSound(mGuiModeStates[mode].mCloseSound);
MWBase::Environment::get().getLuaManager()->uiModeChanged(MWWorld::Ptr());
}
if (!mGuiModes.empty())
@ -1343,6 +1352,7 @@ namespace MWGui
}
updateVisible();
MWBase::Environment::get().getLuaManager()->uiModeChanged(MWWorld::Ptr());
}
void WindowManager::goToJail(int days)
@ -1748,7 +1758,10 @@ namespace MWGui
mPlayerBounty = -1;
for (const auto& window : mWindows)
{
window->clear();
window->setDisabledByLua(false);
}
if (mLocalMapRender)
mLocalMapRender->clear();
@ -2334,4 +2347,45 @@ namespace MWGui
{
mMap->asyncPrepareSaveMap();
}
void WindowManager::setDisabledByLua(std::string_view windowId, bool disabled)
{
mLuaIdToWindow.at(windowId)->setDisabledByLua(disabled);
updateVisible();
}
std::vector<std::string_view> WindowManager::getAllWindowIds() const
{
std::vector<std::string_view> res;
for (const auto& [id, _] : mLuaIdToWindow)
res.push_back(id);
return res;
}
std::vector<std::string_view> WindowManager::getAllowedWindowIds(GuiMode mode) const
{
std::vector<std::string_view> res;
if (mode == GM_Inventory)
{
if (mAllowed & GW_Map)
res.push_back(mMap->getWindowIdForLua());
if (mAllowed & GW_Inventory)
res.push_back(mInventoryWindow->getWindowIdForLua());
if (mAllowed & GW_Magic)
res.push_back(mSpellWindow->getWindowIdForLua());
if (mAllowed & GW_Stats)
res.push_back(mStatsWindow->getWindowIdForLua());
}
else
{
auto it = mGuiModeStates.find(mode);
if (it != mGuiModeStates.end())
{
for (const auto* w : it->second.mWindows)
if (!w->getWindowIdForLua().empty())
res.push_back(w->getWindowIdForLua());
}
}
return res;
}
}

@ -387,6 +387,12 @@ namespace MWGui
void asyncPrepareSaveMap() override;
// Used in Lua bindings
const std::vector<GuiMode>& getGuiModeStack() const override { return mGuiModes; }
void setDisabledByLua(std::string_view windowId, bool disabled) override;
std::vector<std::string_view> getAllWindowIds() const override;
std::vector<std::string_view> getAllowedWindowIds(GuiMode mode) const override;
private:
unsigned int mOldUpdateMask;
unsigned int mOldCullMask;
@ -451,6 +457,9 @@ namespace MWGui
std::vector<std::unique_ptr<WindowBase>> mWindows;
// Mapping windowId -> Window; used by Lua bindings.
std::map<std::string_view, WindowBase*> mLuaIdToWindow;
Translation::Storage& mTranslationDataStorage;
std::unique_ptr<CharacterCreation> mCharGen;

@ -121,7 +121,7 @@ namespace MWLua
{
auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 43;
api["API_REVISION"] = 44;
api["quit"] = [lua]() {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
MWBase::Environment::get().getStateManager()->requestQuit();

@ -311,6 +311,15 @@ namespace MWLua
mGlobalScriptsStarted = true;
}
void LuaManager::uiModeChanged(const MWWorld::Ptr& arg)
{
if (mPlayer.isEmpty())
return;
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
if (playerScripts)
playerScripts->uiModeChanged(arg);
}
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.

@ -83,6 +83,7 @@ namespace MWLua
}
void objectTeleported(const MWWorld::Ptr& ptr) override;
void questUpdated(const ESM::RefId& questId, int stage) override;
void uiModeChanged(const MWWorld::Ptr& arg) override;
MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override;

@ -20,7 +20,7 @@ namespace MWLua
{
registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers,
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers,
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved, &mQuestUpdate });
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved, &mQuestUpdate, &mUiModeChanged });
}
void processInputEvent(const MWBase::LuaManager::InputEvent& event)
@ -65,6 +65,15 @@ namespace MWLua
return !mConsoleCommandHandlers.mList.empty();
}
// `arg` is either forwarded from MWGui::pushGuiMode or empty
void uiModeChanged(const MWWorld::Ptr& arg)
{
if (arg.isEmpty())
callEngineHandlers(mUiModeChanged);
else
callEngineHandlers(mUiModeChanged, LObject(arg));
}
private:
EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" };
EngineHandlerList mKeyPressHandlers{ "onKeyPress" };
@ -77,6 +86,7 @@ namespace MWLua
EngineHandlerList mTouchpadReleased{ "onTouchRelease" };
EngineHandlerList mTouchpadMoved{ "onTouchMove" };
EngineHandlerList mQuestUpdate{ "onQuestUpdate" };
EngineHandlerList mUiModeChanged{ "_onUiModeChanged" };
};
}

@ -45,10 +45,55 @@ namespace MWLua
{
return i + 1;
}
const std::unordered_map<MWGui::GuiMode, std::string_view> modeToName{
{ MWGui::GM_Settings, "SettingsMenu" },
{ MWGui::GM_Inventory, "Interface" },
{ MWGui::GM_Container, "Container" },
{ MWGui::GM_Companion, "Companion" },
{ MWGui::GM_MainMenu, "MainMenu" },
{ MWGui::GM_Journal, "Journal" },
{ MWGui::GM_Scroll, "Scroll" },
{ MWGui::GM_Book, "Book" },
{ MWGui::GM_Alchemy, "Alchemy" },
{ MWGui::GM_Repair, "Repair" },
{ MWGui::GM_Dialogue, "Dialogue" },
{ MWGui::GM_Barter, "Barter" },
{ MWGui::GM_Rest, "Rest" },
{ MWGui::GM_SpellBuying, "SpellBuying" },
{ MWGui::GM_Travel, "Travel" },
{ MWGui::GM_SpellCreation, "SpellCreation" },
{ MWGui::GM_Enchanting, "Enchanting" },
{ MWGui::GM_Recharge, "Recharge" },
{ MWGui::GM_Training, "Training" },
{ MWGui::GM_MerchantRepair, "MerchantRepair" },
{ MWGui::GM_Levelup, "LevelUp" },
{ MWGui::GM_Name, "ChargenName" },
{ MWGui::GM_Race, "ChargenRace" },
{ MWGui::GM_Birth, "ChargenBirth" },
{ MWGui::GM_Class, "ChargenClass" },
{ MWGui::GM_ClassGenerate, "ChargenClassGenerate" },
{ MWGui::GM_ClassPick, "ChargenClassPick" },
{ MWGui::GM_ClassCreate, "ChargenClassCreate" },
{ MWGui::GM_Review, "ChargenClassReview" },
{ MWGui::GM_Loading, "Loading" },
{ MWGui::GM_LoadingWallpaper, "LoadingWallpaper" },
{ MWGui::GM_Jail, "Jail" },
{ MWGui::GM_QuickKeysMenu, "QuickKeysMenu" },
};
const auto nameToMode = [] {
std::unordered_map<std::string_view, MWGui::GuiMode> res;
for (const auto& [mode, name] : modeToName)
res[name] = mode;
return res;
}();
}
sol::table initUserInterfacePackage(const Context& context)
{
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; },
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; });
@ -78,19 +123,18 @@ namespace MWLua
= [luaManager = context.mLuaManager](const std::string& message, const Misc::Color& color) {
luaManager->addInGameConsoleMessage(message + "\n", color);
};
api["setConsoleMode"] = [luaManager = context.mLuaManager](std::string_view mode) {
luaManager->addAction(
[mode = std::string(mode)] { MWBase::Environment::get().getWindowManager()->setConsoleMode(mode); });
api["setConsoleMode"] = [luaManager = context.mLuaManager, windowManager](std::string_view mode) {
luaManager->addAction([mode = std::string(mode), windowManager] { windowManager->setConsoleMode(mode); });
};
api["setConsoleSelectedObject"] = [luaManager = context.mLuaManager](const sol::object& obj) {
const auto wm = MWBase::Environment::get().getWindowManager();
api["setConsoleSelectedObject"] = [luaManager = context.mLuaManager, windowManager](const sol::object& obj) {
if (obj == sol::nil)
luaManager->addAction([wm] { wm->setConsoleSelectedObject(MWWorld::Ptr()); });
luaManager->addAction([windowManager] { windowManager->setConsoleSelectedObject(MWWorld::Ptr()); });
else
{
if (!obj.is<LObject>())
throw std::runtime_error("Game object expected");
luaManager->addAction([wm, obj = obj.as<LObject>()] { wm->setConsoleSelectedObject(obj.ptr()); });
luaManager->addAction(
[windowManager, obj = obj.as<LObject>()] { windowManager->setConsoleSelectedObject(obj.ptr()); });
}
};
api["content"] = LuaUi::loadContentConstructor(context.mLua);
@ -189,6 +233,65 @@ namespace MWLua
Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video"));
};
api["_getAllUiModes"] = [](sol::this_state lua) {
sol::table res(lua, sol::create);
for (const auto& [_, name] : modeToName)
res[name] = name;
return res;
};
api["_getUiModeStack"] = [windowManager](sol::this_state lua) {
sol::table res(lua, sol::create);
int i = 1;
for (MWGui::GuiMode m : windowManager->getGuiModeStack())
res[i++] = modeToName.at(m);
return res;
};
api["_setUiModeStack"]
= [windowManager, luaManager = context.mLuaManager](sol::table modes, sol::optional<LObject> arg) {
std::vector<MWGui::GuiMode> newStack(modes.size());
for (unsigned i = 0; i < newStack.size(); ++i)
newStack[i] = nameToMode.at(LuaUtil::cast<std::string_view>(modes[i + 1]));
luaManager->addAction(
[windowManager, newStack, arg]() {
MWWorld::Ptr ptr;
if (arg.has_value())
ptr = arg->ptr();
const std::vector<MWGui::GuiMode>& stack = windowManager->getGuiModeStack();
unsigned common = 0;
while (common < std::min(stack.size(), newStack.size()) && stack[common] == newStack[common])
common++;
// TODO: Maybe disallow opening/closing special modes (main menu, settings, loading screen)
// from player scripts. Add new Lua context "menu" that can do it.
for (unsigned i = stack.size() - common; i > 0; i--)
windowManager->popGuiMode();
if (common == newStack.size() && !newStack.empty() && arg.has_value())
windowManager->pushGuiMode(newStack.back(), ptr);
for (unsigned i = common; i < newStack.size(); ++i)
windowManager->pushGuiMode(newStack[i], ptr);
},
"Set UI modes");
};
api["_getAllWindowIds"] = [windowManager](sol::this_state lua) {
sol::table res(lua, sol::create);
for (std::string_view name : windowManager->getAllWindowIds())
res[name] = name;
return res;
};
api["_getAllowedWindows"] = [windowManager](sol::this_state lua, std::string_view mode) {
sol::table res(lua, sol::create);
for (std::string_view name : windowManager->getAllowedWindowIds(nameToMode.at(mode)))
res[name] = name;
return res;
};
api["_setWindowDisabled"]
= [windowManager, luaManager = context.mLuaManager](std::string_view window, bool disabled) {
luaManager->addAction([=]() { windowManager->setDisabledByLua(window, disabled); });
};
// TODO
// api["_showHUD"] = [](bool) {};
// api["_showMouseCursor"] = [](bool) {};
return LuaUtil::makeReadOnly(api);
}
}

@ -204,8 +204,7 @@ CUSTOM, PLAYER: useInterface.lua
{
testing::internal::CaptureStdout();
scripts.receiveEvent("SomeEvent", X1);
EXPECT_EQ(internal::GetCapturedStdout(),
"Test has received event 'SomeEvent', but there are no handlers for this event\n");
EXPECT_EQ(internal::GetCapturedStdout(), "");
}
{
testing::internal::CaptureStdout();

@ -297,11 +297,7 @@ namespace LuaUtil
{
auto it = mEventHandlers.find(eventName);
if (it == mEventHandlers.end())
{
Log(Debug::Warning) << mNamePrefix << " has received event '" << eventName
<< "', but there are no handlers for this event";
return;
}
sol::object data;
try
{

@ -6,5 +6,6 @@ paths=(
scripts/omw/camera/camera.lua
scripts/omw/mwui/init.lua
scripts/omw/settings/player.lua
scripts/omw/ui.lua
)
printf '%s\n' "${paths[@]}"

@ -34,6 +34,7 @@ Lua API reference
interface_controls
interface_mwui
interface_settings
interface_ui
iterables
@ -90,3 +91,7 @@ Interfaces of built-in scripts
* - :ref:`MWUI <Interface MWUI>`
- by player scripts
- Morrowind-style UI templates.
* - :ref:`UI <Interface UI>`
- by player scripts
- | High-level UI modes interface. Allows to override parts
| of the interface.

@ -1,6 +1,9 @@
Built-in events
===============
Actor events
------------
Any script can send to any actor (except player, for player will be ignored) events ``StartAIPackage`` and ``RemoveAIPackages``.
The effect is equivalent to calling ``interfaces.AI.startPackage`` or ``interfaces.AI.removePackages`` in a local script on this actor.
@ -11,3 +14,16 @@ Examples:
actor:sendEvent('StartAIPackage', {type='Combat', target=self.object})
actor:sendEvent('RemoveAIPackages', 'Pursue')
UI events
---------
Every time UI mode is changed built-in scripts send to player the event ``UiModeChanged`` with arguments ``mode`` (same as ``I.UI.getMode()``)
and ``arg`` (for example in the mode ``Book`` the argument is the book the player is reading).
.. code-block:: Lua
eventHandlers = {
UiModeChanged = function(data)
print('UiModeChanged to', data.mode, '('..tostring(data.arg)..')')
end
}

@ -0,0 +1,6 @@
Interface UI
============
.. raw:: html
:file: generated_html/scripts_omw_ui.html

@ -481,6 +481,10 @@ The order in which the scripts are started is important. So if one mod should ov
* - :ref:`MWUI <Interface MWUI>`
- by player scripts
- Morrowind-style UI templates.
* - :ref:`UI <Interface UI>`
- by player scripts
- | High-level UI modes interface. Allows to override parts
| of the interface.
Event system
============

@ -89,6 +89,7 @@ set(BUILTIN_DATA_FILES
scripts/omw/mwui/textEdit.lua
scripts/omw/mwui/space.lua
scripts/omw/mwui/init.lua
scripts/omw/ui.lua
shaders/adjustments.omwfx
shaders/bloomlinear.omwfx

@ -13,6 +13,9 @@ PLAYER: scripts/omw/playercontrols.lua
PLAYER: scripts/omw/camera/camera.lua
NPC,CREATURE: scripts/omw/ai.lua
# User interface
PLAYER: scripts/omw/ui.lua
# Lua console
PLAYER: scripts/omw/console/player.lua
GLOBAL: scripts/omw/console/global.lua

@ -0,0 +1,187 @@
local ui = require('openmw.ui')
local util = require('openmw.util')
local self = require('openmw.self')
local MODE = ui._getAllUiModes()
local WINDOW = ui._getAllWindowIds()
local replacedWindows = {}
local hiddenWindows = {}
local modeStack = {}
local function registerWindow(window, showFn, hideFn)
if not WINDOW[window] then
error('At the moment it is only possible to override existing windows. Window "'..
tostring(window)..'" not found.')
end
ui._setWindowDisabled(window, true)
if replacedWindows[window] then
replacedWindows[window].hideFn()
end
replacedWindows[window] = {showFn = showFn, hideFn = hideFn, visible = false}
hiddenWindows[window] = nil
end
local function updateHidden(mode, options)
local toHide = {}
if options and options.windows then
for _, w in pairs(ui._getAllowedWindows(mode)) do
toHide[w] = true
end
for _, w in pairs(options.windows) do
toHide[w] = nil
end
end
for w, _ in pairs(hiddenWindows) do
if toHide[w] then
toHide[w] = nil
else
hiddenWindows[w] = nil
if not replacedWindows[w] then
ui._setWindowDisabled(w, false)
end
end
end
for w, _ in pairs(toHide) do
hiddenWindows[w] = true
if not replacedWindows[w] then
ui._setWindowDisabled(w, true)
end
end
end
local function setMode(mode, options)
if mode then
updateHidden(mode, options)
ui._setUiModeStack({mode}, options and options.target)
else
ui._setUiModeStack({})
end
end
local function addMode(mode, options)
updateHidden(mode, options)
modeStack[#modeStack + 1] = mode
ui._setUiModeStack(modeStack, options and options.target)
end
local function removeMode(mode)
local sizeBefore = #modeStack
local j = 1
for i = 1, sizeBefore do
if modeStack[i] ~= mode then
modeStack[j] = modeStack[i]
j = j + 1
end
end
for i = j, sizeBefore do modeStack[i] = nil end
if sizeBefore > #modeStack then
ui._setUiModeStack(modeStack)
end
end
local function onUiModeChanged(arg)
local newStack = ui._getUiModeStack()
for i = 1, math.max(#modeStack, #newStack) do
modeStack[i] = newStack[i]
end
for w, state in pairs(replacedWindows) do
if state.visible then
state.hideFn()
state.visible = false
end
end
local mode = newStack[#newStack]
if mode then
for _, w in pairs(ui._getAllowedWindows(mode)) do
local state = replacedWindows[w]
if state and not hiddenWindows[w] then
state.showFn(arg)
state.visible = true
end
end
end
self:sendEvent('UiModeChanged', {mode = mode, arg = arg})
end
return {
interfaceName = 'UI',
---
-- @module UI
-- @usage require('openmw.interfaces').UI
interface = {
--- Interface version
-- @field [parent=#UI] #number version
version = 0,
--- All available UI modes.
-- Use `view(I.UI.MODE)` in `luap` console mode to see the list.
-- @field [parent=#UI] #table MODE
MODE = util.makeStrictReadOnly(MODE),
--- All windows.
-- Use `view(I.UI.WINDOW)` in `luap` console mode to see the list.
-- @field [parent=#UI] #table WINDOW
WINDOW = util.makeStrictReadOnly(WINDOW),
--- Register new implementation for the window with given name; overrides previous implementation.
-- Adding new windows is not supported yet. At the moment it is only possible to override built-in windows.
-- @function [parent=#UI] registerWindow
-- @param #string windowName
-- @param #function showFn Callback that will be called when the window should become visible
-- @param #function hideFn Callback that will be called when the window should be hidden
registerWindow = registerWindow,
--- Returns windows that can be shown in given mode.
-- @function [parent=#UI] getWindowsForMode
-- @param #string mode
-- @return #table
getWindowsForMode = ui._getAllowedWindows,
--- Stack of currently active modes
-- @field [parent=#UI] modes
modes = util.makeReadOnly(modeStack),
--- Get current mode (nil if all windows are closed), equivalent to `I.UI.modes[#I.UI.modes]`
-- @function [parent=#UI] getMode
-- @return #string
getMode = function() return modeStack[#modeStack] end,
--- Drop all active modes and set mode.
-- @function [parent=#UI] setMode
-- @param #string mode (optional) New mode
-- @param #table options (optional) Table with keys 'windows' and/or 'target' (see example).
-- @usage I.UI.setMode() -- drop all modes
-- @usage I.UI.setMode('Interface') -- drop all modes and open interface
-- @usage -- Drop all modes, open interface, but show only the map window.
-- I.UI.setMode('Interface', {windows = {'Map'}})
setMode = setMode,
--- Add mode to stack without dropping other active modes.
-- @function [parent=#UI] addMode
-- @param #string mode New mode
-- @param #table options (optional) Table with keys 'windows' and/or 'target' (see example).
-- @usage I.UI.addMode('Journal') -- open journal without dropping active modes.
-- @usage -- Open barter with an NPC
-- I.UI.addMode('Barter', {target = actor})
addMode = addMode,
--- Remove the specified mode from active modes.
-- @function [parent=#UI] removeMode
-- @param #string mode Mode to drop
removeMode = removeMode,
-- TODO
-- registerHudElement = function(name, showFn, hideFn) end,
-- showHud = function(bool) end,
-- isHudVisible = function() end,
-- showHudElement = function(name, bool) end,
-- hudElements, -- map from element name to its visibility
},
engineHandlers = {
_onUiModeChanged = onUiModeChanged,
},
eventHandlers = {
UiModeChanged = function() end,
},
}

@ -17,6 +17,9 @@
---
-- @field [parent=#interfaces] scripts.omw.settings.player#scripts.omw.settings.player Settings
---
-- @field [parent=#interfaces] scripts.omw.ui#scripts.omw.ui UI
---
-- @function [parent=#interfaces] __index
-- @param #interfaces self

Loading…
Cancel
Save