Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ebeb1f5
Basic integration for data-for
noodlecollie Mar 15, 2026
728b10b
Prepared controls menu for parsing entries
noodlecollie Mar 15, 2026
2a91892
Parsed default key bindings in options menu
noodlecollie Mar 16, 2026
f5363c9
Updated content hash
noodlecollie Mar 26, 2026
adee46f
Linux fixes
noodlecollie Mar 30, 2026
ea195cb
Enabled option menu tabs, removed zoo
noodlecollie Mar 31, 2026
e629957
Added cell highlight in prep for rebinding
noodlecollie Apr 1, 2026
097b3d1
Minor improvements
noodlecollie Apr 2, 2026
d4a4246
Created modal component
noodlecollie Apr 2, 2026
f79de2f
Improved menus listening to events
noodlecollie Apr 3, 2026
e9e7c55
Added proper key handling to options menu
noodlecollie Apr 3, 2026
9d79563
Added param support for components
noodlecollie Apr 3, 2026
635d7d1
Improved modal
noodlecollie Apr 5, 2026
62d0c41
Syncing
noodlecollie Apr 5, 2026
5ae4c1d
Updated content hash
noodlecollie Apr 5, 2026
f05be9e
Captured clicks and keyboard events
noodlecollie Apr 5, 2026
e0a9d1e
Implemented key interception for rebinding
noodlecollie Apr 6, 2026
cd48c90
Added function for applying bindings
noodlecollie Apr 7, 2026
9bcab24
Loaded key bindings from file
noodlecollie Apr 8, 2026
cf945a9
Properly handled loading/saving key bindings
noodlecollie Apr 9, 2026
f22d844
Allowed bindings to be single-click selected
noodlecollie Apr 10, 2026
90e75d2
Made Clear and Reset To Default functional
noodlecollie Apr 10, 2026
46cbc23
Added button to reset all bindings
noodlecollie Apr 11, 2026
3520fd6
Added modal for resetting all bindings
noodlecollie Apr 11, 2026
16f63dc
Moved options tab bar to a template
noodlecollie Apr 12, 2026
9516302
Renamed pushMenu and popMenu
noodlecollie Apr 12, 2026
975293c
Beginnings of factoring out options categories
noodlecollie Apr 12, 2026
fa8adc6
Finished refactoring options menu
noodlecollie Apr 12, 2026
0250b7a
Got a tick box to work
noodlecollie Apr 12, 2026
499c980
Refactored component impl
noodlecollie Apr 13, 2026
315ca36
Tweaks
noodlecollie Apr 14, 2026
75af245
Created model to hold and sync data to cvars
noodlecollie Apr 14, 2026
09e2883
Added mouse options
noodlecollie Apr 14, 2026
4a3941c
Fixed Windows compilation
noodlecollie Apr 14, 2026
7db3c94
Basic skeleton for resolution dropdown
noodlecollie Apr 15, 2026
5c10d76
Added some more AV options
noodlecollie Apr 15, 2026
317f123
Renamed cvar model
noodlecollie Apr 16, 2026
670bdea
Added all menu items
noodlecollie Apr 16, 2026
b31e939
Temp changes for AV menu
noodlecollie Apr 17, 2026
df5deab
Made progress on setting video modes
noodlecollie Apr 17, 2026
904b43d
Got video mode revert to work
noodlecollie Apr 17, 2026
c0b2fc6
Properly implemented video mode modal
noodlecollie Apr 17, 2026
7fde039
Updated tooltip code to automatically bind events
noodlecollie Apr 18, 2026
879d2c7
Fixed tooltips
noodlecollie Apr 19, 2026
9ed3bbb
Fixed content hash matching for hyphenated branch names
noodlecollie Apr 19, 2026
bb89bd3
Finalised content hash
noodlecollie Apr 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion game/check-content-hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def main():
with open(os.path.join(SCRIPT_DIR, "content-hash.txt"), "r") as in_file:
content_hash = in_file.read().strip()

content_hash_match = re.match(r"^([^\s-]+)-([0-9a-f]{40})(-dirty)?$", content_hash)
content_hash_match = re.match(r"^([^\s]+)-([0-9a-f]{40})(-dirty)?$", content_hash)

if not content_hash_match:
raise RuntimeError(f"Could not parse hash from content-hash.txt. Output:\n{content_hash}")
Expand Down
2 changes: 1 addition & 1 deletion game/content-hash.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
master-58bb220e7886c291bc5f8f7aa7322e2314b33329
master-c3312fa76076c1b2e32b7e4bf49ac647e6c5f87c
11 changes: 7 additions & 4 deletions game/game_libs/ui/menus/Controls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ void CMenuControls::ResetKeysList(void)

if ( !afile )
{
Con_Printf("UI_Parse_KeysList: kb_act.lst not found\n");
Con_Printf("UI_Parse_KeysList: kb_def.lst not found\n");
return;
}

Expand Down Expand Up @@ -420,17 +420,20 @@ void CMenuControls::_Init(void)
L("Adv. Controls"),
L("Change mouse sensitivity, enable autoaim, mouselook and crosshair"),
PC_ADV_CONTROLS,
UI_AdvControls_Menu);
UI_AdvControls_Menu
);
AddButton(
L("GameUI_OK"),
L("Save changed and return to configuration menu"),
PC_DONE,
VoidCb(&CMenuControls::SaveAndPopMenu));
VoidCb(&CMenuControls::SaveAndPopMenu)
);
AddButton(
L("GameUI_Cancel"),
L("Discard changes and return to configuration menu"),
PC_CANCEL,
VoidCb(&CMenuControls::Cancel));
VoidCb(&CMenuControls::Cancel)
);
AddItem(keysList);
}

Expand Down
44 changes: 38 additions & 6 deletions game/game_libs/ui_new/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,49 @@ include(nf_utils)
set(CMAKE_INSTALL_RPATH "\$ORIGIN")

set(SOURCES_UI
src/components/ModalComponent.h
src/components/ModalComponent.cpp
src/framework/BaseComponent.h
src/framework/BaseComponent.cpp
src/framework/BaseMenu.h
src/framework/BaseMenu.cpp
src/framework/DataBinding.h
src/framework/BaseTableModel.h
src/framework/BaseTemplateBinding.h
src/framework/CvarAccessor.h
src/framework/CvarDataVar.h
src/framework/DataVar.h
src/framework/DocumentObserver.h
src/framework/DocumentObserver.cpp
src/framework/ElementFinder.h
src/framework/ElementFinder.cpp
src/framework/EventListenerObject.h
src/framework/MenuDirectory.h
src/framework/MenuDirectory.cpp
src/framework/MenuPage.h
src/framework/MenuPage.cpp
src/framework/MenuStack.h
src/framework/MenuStack.cpp
src/menus/options/AvOptionsMenu.h
src/menus/options/AvOptionsMenu.cpp
src/menus/options/BaseOptionsMenu.h
src/menus/options/BaseOptionsMenu.cpp
src/menus/options/GameplayOptionsMenu.h
src/menus/options/GameplayOptionsMenu.cpp
src/menus/options/KeysOptionsMenu.h
src/menus/options/KeysOptionsMenu.cpp
src/menus/options/MouseOptionsMenu.h
src/menus/options/MouseOptionsMenu.cpp
src/menus/MainMenu.h
src/menus/MainMenu.cpp
src/menus/MainMenu.cpp
src/menus/MultiplayerMenu.h
src/menus/MultiplayerMenu.cpp
src/menus/OptionsMenu.h
src/menus/OptionsMenu.cpp
src/menus/ZooMenu.h
src/menus/ZooMenu.cpp
src/models/CvarModel.h
src/models/CvarModel.cpp
src/models/KeyBindingModel.h
src/models/KeyBindingModel.cpp
src/models/VideoModesModel.h
src/models/VideoModesModel.cpp
src/rmlui/EventListenerImpl.h
src/rmlui/EventListenerImpl.cpp
src/rmlui/EventListenerInstancerImpl.h
Expand All @@ -42,9 +69,13 @@ set(SOURCES_UI
src/rmlui/SystemInterfaceImpl.cpp
src/rmlui/TextInputHandlerImpl.h
src/rmlui/TextInputHandlerImpl.cpp
src/templatebindings/BaseTemplateBinding.h
src/rmlui/Utils.h
src/rmlui/Utils.cpp
src/templatebindings/MenuFrameDataBinding.h
src/templatebindings/MenuFrameDataBinding.cpp
src/templatebindings/OptionsTabBarDataBinding.h
src/templatebindings/OptionsTabBarDataBinding.cpp
src/utils/InFilePtr.h
src/udll_int.h
src/udll_int.cpp
src/UIDebug.h
Expand All @@ -61,6 +92,7 @@ target_link_libraries(${TARGETNAME_LIB_UI} PRIVATE
${TARGETNAME_LIB_ENGINEPUBLICAPI}
${TARGETNAME_LIB_ENGINEINTERNALAPI}
${TARGETNAME_LIB_RMLUI}
${TARGETNAME_LIB_CRTLIB}
)

set_common_library_compiler_settings(${TARGETNAME_LIB_UI})
Expand Down
3 changes: 3 additions & 0 deletions game/game_libs/ui_new/src/UIDebug.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#pragma once

#include <stdlib.h>
#include <RmlUi/Core/Log.h>

#ifdef _DEBUG
void UIDBG_AssertFunction(bool fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage);
#define ASSERT(f) UIDBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL)
#define ASSERTSZ(f, sz) UIDBG_AssertFunction(f, #f, __FILE__, __LINE__, sz)
#define ASSERTSZ_Q(f, sz) UIDBG_AssertFunction(f, #f, __FILE__, __LINE__, sz, false)
#define RML_DBGLOG(level, ...) Rml::Log::Message((level), __VA_ARGS__)
#else // !DEBUG
#define ASSERT(f)
#define ASSERTSZ(f, sz)
#define ASSERTSZ_Q(f, sz)
#define RML_DBGLOG(level, ...)
#endif // !DEBUG
163 changes: 163 additions & 0 deletions game/game_libs/ui_new/src/components/ModalComponent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#include "components/ModalComponent.h"
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Core/StringUtilities.h>
#include "framework/ElementFinder.h"

static constexpr const char* const PARAM_TITLE = "title";
static constexpr const char* const PARAM_BUTTONS = "buttons";

ModalComponent::ModalComponent(BaseMenu* parentMenu, Rml::String id) :
BaseComponent(parentMenu, std::move(id)),
m_ButtonEventListener(this, &ModalComponent::HandleButtonEvent)
{
AddParamSpec(PARAM_TITLE, Rml::Variant(""));
AddParamSpec(PARAM_BUTTONS, Rml::Variant(""));
}

void ModalComponent::SetTitle(const Rml::String& title)
{
ASSERTSZ(m_Elems.modalHeader, "SetTitle called before modal elements were loaded");

if ( !m_Elems.modalHeader )
{
return;
}

if ( !title.empty() )
{
m_Elems.modalHeader->SetInnerRML("<h2>" + Rml::StringUtilities::EncodeRml(title) + "</h2>");
}
else
{
m_Elems.modalHeader->SetInnerRML("");
}
}

void ModalComponent::SetContentsRml(const Rml::String& rml)
{
ASSERTSZ(m_Elems.modalBody, "SetContentsRml called before modal elements were loaded");

if ( !m_Elems.modalBody )
{
return;
}

m_Elems.modalBody->SetInnerRML(rml);
}

void ModalComponent::SetButtons(const Rml::StringList& buttons)
{
ASSERTSZ(m_Elems.modalFooter, "SetButtons called before modal elements were loaded");

if ( !m_Elems.modalFooter )
{
return;
}

m_Elems.buttons.clear();

while ( m_Elems.modalFooter->HasChildNodes() )
{
m_Elems.modalFooter->RemoveChild(m_Elems.modalFooter->GetFirstChild());
}

Rml::String rmlString = "";

for ( const Rml::String& button : buttons )
{
rmlString += "<button class=\"primary\">" + Rml::StringUtilities::EncodeRml(button) + "</button>";
}

m_Elems.modalFooter->SetInnerRML(rmlString);
m_Elems.buttons.reserve(m_Elems.modalFooter->GetNumChildren());

for ( Rml::Element* child = m_Elems.modalFooter->GetFirstChild(); child; child = child->GetNextSibling() )
{
m_Elems.buttons.push_back(child);
child->AddEventListener(Rml::EventId::Click, &m_ButtonEventListener);
child->AddEventListener(Rml::EventId::Mouseup, &m_ButtonEventListener);
}
}

void ModalComponent::SetButtonClickCallback(ButtonClickCallback callback)
{
m_ButtonClickCallback = std::move(callback);
}

const Rml::Variant& ModalComponent::UserData() const
{
return m_UserData;
}

void ModalComponent::SetUserData(Rml::Variant data)
{
m_UserData = std::move(data);
}

bool ModalComponent::OnLoadFromDocument(Rml::ElementDocument*)
{
ElementFinder finder;

finder.Add(ComponentElementPtrPtr(), ".modal-shade", &m_Elems.shade);
finder.Add(&m_Elems.shade, "modal", &m_Elems.modal);
finder.Add(&m_Elems.modal, ".modal-header", &m_Elems.modalHeader);
finder.Add(&m_Elems.modal, ".modal-body", &m_Elems.modalBody);
finder.Add(&m_Elems.modal, ".modal-footer", &m_Elems.modalFooter);

if ( !finder.FindAll() )
{
return false;
}

LoadParams();
return true;
}

void ModalComponent::OnUnload()
{
for ( Rml::Element* button : m_Elems.buttons )
{
button->RemoveEventListener(Rml::EventId::Click, &m_ButtonEventListener);
button->RemoveEventListener(Rml::EventId::Mouseup, &m_ButtonEventListener);
}

m_Elems = Elements {};
}

void ModalComponent::LoadParams()
{
SetTitle(GetParam(PARAM_TITLE).Get<Rml::String>());

const Rml::String buttons = GetParam(PARAM_BUTTONS).Get<Rml::String>();

if ( !buttons.empty() )
{
m_Elems.buttons.clear();

Rml::StringList buttonsList;
Rml::StringUtilities::ExpandString(buttonsList, buttons, ';');
SetButtons(buttonsList);
}
}

void ModalComponent::HandleButtonEvent(Rml::Event& event)
{
if ( !m_ButtonClickCallback )
{
return;
}

Rml::Element* button = event.GetTargetElement();
const auto buttonIt = std::find(m_Elems.buttons.begin(), m_Elems.buttons.end(), button);

// We should only get events for buttons we know about.
ASSERT(buttonIt != m_Elems.buttons.end());

if ( buttonIt == m_Elems.buttons.end() )
{
return;
}

event.StopPropagation();
m_ButtonClickCallback(event, buttonIt - m_Elems.buttons.begin(), m_UserData);
}
45 changes: 45 additions & 0 deletions game/game_libs/ui_new/src/components/ModalComponent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

#include "framework/BaseComponent.h"
#include "framework/EventListenerObject.h"
#include <functional>

class ModalComponent : public BaseComponent
{
public:
using ButtonClickCallback =
std::function<void(Rml::Event& /*event*/, size_t /*buttonIndex*/, const Rml::Variant& /*userData*/)>;

explicit ModalComponent(BaseMenu* parentMenu, Rml::String id);

void SetTitle(const Rml::String& title);
void SetContentsRml(const Rml::String& rml);
void SetButtons(const Rml::StringList& buttons);
void SetButtonClickCallback(ButtonClickCallback callback);

const Rml::Variant& UserData() const;
void SetUserData(Rml::Variant data);

protected:
bool OnLoadFromDocument(Rml::ElementDocument* document) override;
void OnUnload() override;

private:
struct Elements
{
Rml::Element* shade = nullptr;
Rml::Element* modal = nullptr;
Rml::Element* modalHeader = nullptr;
Rml::Element* modalBody = nullptr;
Rml::Element* modalFooter = nullptr;
Rml::ElementList buttons;
};

void LoadParams();
void HandleButtonEvent(Rml::Event& event);

Elements m_Elems {};
ButtonClickCallback m_ButtonClickCallback;
EventListenerObject m_ButtonEventListener;
Rml::Variant m_UserData;
};
Loading
Loading