Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,38 @@ endif()

include(${CMAKE_CURRENT_SOURCE_DIR}/resources/cmake/bootstrap_deps.cmake)

# ── Embed locale JSON files as C++ raw string literals ────────────────────────
# Each src/locales/<lang>.json is read at configure time and appended into
# build/generated/locale_data.h as inline const char* kLocaleData_<lang>.
# Re-run cmake automatically when any JSON changes (CMAKE_CONFIGURE_DEPENDS).

set(_LOCALE_LANGS
english russian ukrainian german french spanish italian polish turkish
swedish dutch brazilian hungarian indonesian vietnamese
schinese tchinese japanese koreana latam
bulgarian danish
)
set(_LOCALE_HEADER "${CMAKE_BINARY_DIR}/generated/locale_data.h")

file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/generated")

# Use file(WRITE/APPEND) directly — avoids CMake list/semicolon expansion bugs
# that occur when building large strings with string(APPEND) + file(WRITE).
file(WRITE "${_LOCALE_HEADER}" "// Auto-generated by CMake — do not edit. Re-run cmake to refresh.\n#pragma once\n\n")

foreach(_LANG ${_LOCALE_LANGS})
set(_JSON_PATH "${CMAKE_SOURCE_DIR}/src/locales/${_LANG}.json")
# Track for automatic re-configure when any JSON file changes
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_JSON_PATH}")
file(READ "${_JSON_PATH}" _JSON_CONTENT)
# Write open, content, close as separate appends so no CMake string
# processing can swallow the semicolon in )__LD__";
file(APPEND "${_LOCALE_HEADER}" "inline const char* kLocaleData_${_LANG} = R\"__LD__(\n")
file(APPEND "${_LOCALE_HEADER}" "${_JSON_CONTENT}")
file(APPEND "${_LOCALE_HEADER}" "\n)__LD__\";\n\n")
endforeach()
# ──────────────────────────────────────────────────────────────────────────────

add_library(imgui_lib STATIC
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
Expand All @@ -62,6 +94,7 @@ set(SOURCES
src/window/wndproc.cc
src/window/renderer.cc
src/util/updater.cc
src/util/locale.cc
src/routes/router.cc
src/routes/home.cc
src/routes/install_prompt.cc
Expand Down Expand Up @@ -98,6 +131,7 @@ include_directories(SYSTEM ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR})

target_link_libraries(${PROJECT_NAME} PRIVATE imgui_lib)
target_link_options(${PROJECT_NAME} PRIVATE /FORCE:MULTIPLE)
target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_BINARY_DIR}/generated")

if(UNIX AND NOT APPLE)
find_package(PkgConfig REQUIRED)
Expand Down Expand Up @@ -137,3 +171,4 @@ elseif(WIN32)
bcrypt
)
endif()

9 changes: 5 additions & 4 deletions src/components/bottombar.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <dpi.h>
#include <texture.hh>
#include <animate.h>
#include <i18n.h>
#ifdef _WIN32
#endif
#include <util.h>
Expand Down Expand Up @@ -66,10 +67,10 @@ const void RenderBottomNavBar(const char* identifier, float xPos, std::function<
const float cursorPosSave = GetCursorPosX();

SetCursorPosY(GetCursorPosY() - ScaleX(12));
TextColored(ImVec4(0.322f, 0.325f, 0.341f, 1.0f), "Steam Homebrew & Millennium are not affiliated with");
TextColored(ImVec4(0.322f, 0.325f, 0.341f, 1.0f), "%s", Locale::Get("installerDisclaimer1"));

SetCursorPos({ cursorPosSave, GetCursorPosY() - ScaleY(20) });
TextColored(ImVec4(0.322f, 0.325f, 0.341f, 1.0f), "Steam®, Valve, or any of their partners.");
TextColored(ImVec4(0.322f, 0.325f, 0.341f, 1.0f), "%s", Locale::Get("installerDisclaimer2"));

SameLine(0);
SetCursorPosY(GetCursorPosY() - ScaleY(25));
Expand All @@ -95,7 +96,7 @@ const void RenderBottomNavBar(const char* identifier, float xPos, std::function<
PushStyleVar(ImGuiStyleVar_WindowRounding, 6);
PushStyleVar(ImGuiStyleVar_Alpha, discordIconHoverTransparency);
PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.098f, 0.102f, 0.11f, 1.0f));
SetTooltip("Join Discord Server");
SetTooltip("%s", Locale::Get("tooltipDiscord"));

if (IsItemClicked()) {
OpenUrl(discordInviteLink);
Expand Down Expand Up @@ -126,7 +127,7 @@ const void RenderBottomNavBar(const char* identifier, float xPos, std::function<
PushStyleVar(ImGuiStyleVar_WindowRounding, 6);
PushStyleVar(ImGuiStyleVar_Alpha, githubIconHoverTransparency);
PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.098f, 0.102f, 0.11f, 1.0f));
SetTooltip("View Source Code");
SetTooltip("%s", Locale::Get("tooltipGithub"));

if (IsItemClicked()) {
OpenUrl(githubRepositoryUrl);
Expand Down
83 changes: 80 additions & 3 deletions src/components/titlebar.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@
#include <dpi.h>
#include <components.h>
#include <animate.h>
#include <i18n.h>
#include <math.h>
#include <worker.h>
#include <renderer.h>

using namespace ImGui;

Expand All @@ -44,8 +46,7 @@ using namespace ImGui;
*/
bool RenderTitleBarComponent(std::shared_ptr<RouterNav> router)
{
ImGuiIO& io = GetIO();
const std::string strTitleText = std::format("Steam Homebrew", io.Framerate);
const std::string strTitleText = Locale::Get("titlebarTitle");

ImGuiViewport* viewport = GetMainViewport();
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ScaleX(15), ScaleY(15)));
Expand Down Expand Up @@ -88,13 +89,14 @@ bool RenderTitleBarComponent(std::shared_ptr<RouterNav> router)
Text("%s", strTitleText.c_str());
SameLine();

ImVec2 closeButtonDimensions = { ceil(ScaleX(70)), ceil(ScaleY(43)) };

static bool isCloseButtonHovered = false;

if (isCloseButtonHovered) {
PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.769f, 0.169f, 0.11f, 1.0f));
}

ImVec2 closeButtonDimensions = { ceil(ScaleX(70)), ceil(ScaleY(43)) };
SetCursorPos({ viewport->Size.x - closeButtonDimensions.x, 0 });

PushStyleVar(ImGuiStyleVar_ChildRounding, 0);
Expand Down Expand Up @@ -122,3 +124,78 @@ bool RenderTitleBarComponent(std::shared_ptr<RouterNav> router)

return IsItemHovered() || (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) && IsMouseDown(ImGuiMouseButton_Left));
}

/**
* Render the language selector dropdown in the main window scope (below the
* WndProc drag zone, top-right of the content area). Must be called from
* the root Begin/End block, NOT from inside a BeginChild.
*/
void RenderLanguageSelector(float xPos)
{
ImGuiViewport* viewport = GetMainViewport();

const auto& langs = Locale::GetAvailableLanguages();
const std::string& currentLangId = Locale::GetCurrentLanguageId();

std::string previewValue;
for (const auto& lang : langs) {
if (lang.id == currentLangId) { previewValue = lang.displayName; break; }
}
if (previewValue.empty()) previewValue = currentLangId;

const float langSelectorWidth = ScaleX(170);
// ScaleY(105): safely below the WndProc drag zone (ScaleY(100)), top-right of content
// Right edge aligned with bottom nav bar button edge (WindowPadding = ScaleX(30))
SetCursorPos({ xPos + viewport->Size.x - langSelectorWidth - ScaleX(30), ScaleY(105) });

PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.13f, 0.14f, 0.15f, 1.0f));
PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.19f, 0.20f, 0.21f, 1.0f));
PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.16f, 0.17f, 0.18f, 1.0f));
PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.10f, 0.10f, 0.11f, 1.0f));
PushStyleColor(ImGuiCol_Header, ImVec4(0.20f, 0.21f, 0.22f, 1.0f));
PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.26f, 0.27f, 0.28f, 1.0f));
PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.23f, 0.25f, 1.0f));
PushStyleVar(ImGuiStyleVar_FrameRounding, ScaleX(4));
PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(ScaleX(8), ScaleY(7)));
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ScaleX(8), 0));

// Fonts[2] = VietName_Standalone: pushed for the Vietnamese item only.
// Fonts[3] = Geist+CJK+VietPreview: present only when Vietnamese is active;
// pushed around the entire combo so it looks the same as English mode.
ImGuiIO& io = GetIO();
ImFont* vietItemFont = (io.Fonts->Fonts.Size > 2) ? io.Fonts->Fonts[2] : nullptr;
ImFont* dropdownFont = (io.Fonts->Fonts.Size > 3) ? io.Fonts->Fonts[3] : nullptr;

if (dropdownFont) PushFont(dropdownFont);

SetNextItemWidth(langSelectorWidth);
if (BeginCombo("##LangSelector", previewValue.c_str())) {
for (const auto& lang : langs) {
bool isSelected = (lang.id == currentLangId);
bool useVietFont = (lang.id == "vietnamese") && (vietItemFont != nullptr);
if (useVietFont) {
if (dropdownFont) PopFont();
PushFont(vietItemFont);
}
if (Selectable(lang.displayName.c_str(), isSelected)) {
Locale::SetLanguage(lang.id);
RequestFontRebuild();
}
if (isSelected)
SetItemDefaultFocus();
if (useVietFont) {
PopFont();
if (dropdownFont) PushFont(dropdownFont);
}
}
EndCombo();
}

if (dropdownFont) PopFont();

if (IsItemHovered())
SetMouseCursor(ImGuiMouseCursor_Hand);

PopStyleVar(3);
PopStyleColor(7);
}
Loading