mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp
synced 2025-10-06 00:22:39 +02:00
Modding menu and UI and additional mod exports (#535)
* init config opt system w/ 3 types and description support * Move config registry/option to librecomp + added Color conf opt type * Updated color option type styling * Added dropdown option type * Added TextField option type * Button config type + callback wip * init mod menu + bem class + button presets * WIP mod menu, fix some warnings * Rewrite mod details under new UI system. * Refactored mods menu entirely. * Remove ModMenu.scss. * Take ownership of created pointers on Element class. * Add styles. * Multi-style state and disabled state propagation. * Switch to string views. * Convert to spaces, hook up mod enabled to toggle. * Mod menu progress. * Layout for mod details panel, add gap property setters * Update RmlUi for gap property in flexbox * Add slot_map and begin ui context * Implement context and resource storage slotmaps * Config submenu. * Refactored to account for context changes. * Turn off tab searching when submenu is open. * Revert accidental RmlUi downgrade * Upgrade RmlUi to 6.0 release * Text input. * Radio option. * Cleanup. * Refactor Rml document handling to use new ContextId system (prompts currently unimplemented) * Add support for config schema. * Split config sub menu into separate context and fix configure button, prevent infinite loop when looking for autofocus element * Reimplement mechanism to open the config menu to a specific tab * Begin implementing mod UI API * Link storage to mod menu. * Proper enum parsing. * Enable mod reordering. * Draggable improvements to mod menu and runtime update. * Adjust styling of submenu. * Mods folder button. * Linux build fixes. * Hook up new manifest fields to mod UI * Add basic thumbnail parsing functionality. * More style changes. * Implement update event for elements * Use RT64's texture laoding instead. * Restore spacer animations. * Animation API begone. * Auto-enabled mods. * Update runtime submodule and N64Recomp commit in CI for mod config API, remove unnecessary extern C * Sub menu display name, assert on text input. * Clamp delta time to fix UI disappearing on OS with timestamps that don't always increase. * Add a state for when no mods are installed. * Unify API function naming scheme and export relevant API functions * Add actor update/init events and save init event (#536) * Expose remaining property setters to mod UI API * Implemented mod UI callbacks * Implement actor extension data and use it for transform tagging * Zero the memory allocated to hold extended actor data * Implement label and textinput in mod UI API * Patch virtual address translation to support entire extended RAM address space (#533) * Download full target build of llvm in CI Windows runners to fix missing MIPS support and update N64Recomp CI commit * Enable triple buffering in RT64 (#546) * Implement controlling input capturing for mod UI contexts * Created mod UI API functions for setting visibility, setting text, and destroying elements * Fix errant RML tag in mod menu and insert breaks for newlines when setting element text * Fix compilation after rebase * Fixes for macOS * Set the blender description manually for the UI renderer * Created mod UI API functions for imageview elements * Switch to designated initializers to work around missing aggregate initialization compiler support * Update RT64 for driver bug workarounds and misc fixes * Update RT64 to fix native sampler issues with tile copies * Update RT64 for depth clear optimization and more native sampler changes * Update RT64 and allow it to choose the graphics API when set to Auto * Update runtime to allow renderers to choose the graphics API * Update RT64 to enable early Z test shader optimization * Implement data structure mod APIs * Update lunasvg to increase its minimum cmake version * Switch to runtime concatenation of function name in data API error reporting to fix Linux compilation issue * Add missing typename to fix compilation on some compilers * Update RT64 to fix failed assert with MSAA off * Reimplement prompts as a separate UI context and make it so the quit game prompt doesn't bring up the config menu * DnD prototype. * Fix to dynamic lib path and runtime commit. * Finish drag and drop mod installation, disable mod refresh button and code mod toggle when game starts * Remove std::format usage and add missing <list> includes to fix Linux/MacOS compilation * Switch to aggregate initialization for Version to work around missing implicit constructor on some compilers * Replace use of std::bind with lambdas * Add mod install button, put mod description in scroll container, minor mod menu tweaks * Update runtime to fix renderer shutdown race condition * Implement texture pack reordering * Add mod UI API exports for slider, password input, and label radio and expose RmlUi debugger on F8 * Update runtime for mod version export * Update runtime for save swapping mod API * Apply recomp.rcss to mod UI contexts (fixes scrolls) * Updated mod list styling (#561) * Updated mod list styling * mod entry max height * Update RT64 for v5 texture hash * Update runtime for mod API to get save file path * Add special config option id to control texture pack state for code mods * Update runtime for mod default enabled state * Add exports for stars' display lists (#563) * Update runtime to fix default value of enabled_by_default * Update runtime to allow NULL recomp_free * Implement navigation and focus styling for new UI framework (no manual overrides yet) * Fix the previous scissor state bleeding when drawing the RmlUi output onto the swapchain buffer * Use a multiple file select dialog for mod install button * Add mod export for loading UI image from bytes (png/dds) * Manual navigation in UI framework and WIP mod menu navigation * Repeat key events when holding down controller inputs for UI navigation * Patch AnimationContext_SetLoadFrame to allow custom animations (#564) * Close context when showing or hiding a context and reopen afterwards to prevent deadlocks * Add quotes around xdg-open and open commands to support paths with spaces * Update RT64 for high precision texture coordinates when using texture replacements * Add support for built-in mods and convert D-Pad to a built-in mod (#567) * Add embedded mod (using mm_recomp_draw_distance as an example). * Update runtime after merge * Experiment with removing the D-Pad. * Add event needed for dpad as mod, revert remaining changes in built-in patches for dpad * Add built-in dpad mod, add remaining event calls to input.c * Add built-in mods readme --------- Co-authored-by: Dario <dariosamo@gmail.com> * Fixing navigation of mods menu. * Focused state for mod entry. * Prevent hover styling and focus on input elements when disabled * Fix up/down navigation on text input elements * Set mod tab to navigate down to first mod, fix redundant mod scanning * Remove more redundant mod scanning and fix mods being scanned during gameplay * Update runtime for mod folder export * Improve radio navigation and setup mod config submenu navigation setup * Restore fd anywhere export functionality (#570) * fix fd * add comment back in * Make config tabset navigate down to first mod entry when mod menu is open, make mod configure screen focus on configure button after closing * Add navigation exports to mod UI API * Fix opening the config menu via keyboard/controller causing a double animation warning in RmlUi --------- Co-authored-by: Dario <dariosamo@gmail.com> Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com> Co-authored-by: Garrett Cox <garrettjcox@gmail.com> Co-authored-by: David Chavez <david@dcvz.io> Co-authored-by: danielryb <59661841+danielryb@users.noreply.github.com> Co-authored-by: Reonu <danileon95@gmail.com> Co-authored-by: LittleCube <littlecubehax@gmail.com>
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -19,3 +19,6 @@
|
|||||||
[submodule "Zelda64RecompSyms"]
|
[submodule "Zelda64RecompSyms"]
|
||||||
path = Zelda64RecompSyms
|
path = Zelda64RecompSyms
|
||||||
url = https://github.com/Zelda64Recomp/Zelda64RecompSyms
|
url = https://github.com/Zelda64Recomp/Zelda64RecompSyms
|
||||||
|
[submodule "lib/slot_map"]
|
||||||
|
path = lib/slot_map
|
||||||
|
url = https://github.com/SergeyMakeev/slot_map
|
||||||
|
@@ -10,6 +10,7 @@ set(CMAKE_CXX_STANDARD 20)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
# set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
# set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-everything /W4")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-everything /W4")
|
||||||
@@ -38,6 +39,8 @@ endif()
|
|||||||
|
|
||||||
set(RT64_STATIC TRUE)
|
set(RT64_STATIC TRUE)
|
||||||
set(RT64_SDL_WINDOW_VULKAN TRUE)
|
set(RT64_SDL_WINDOW_VULKAN TRUE)
|
||||||
|
add_compile_definitions(HLSL_CPU)
|
||||||
|
|
||||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/rt64 ${CMAKE_BINARY_DIR}/rt64)
|
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/rt64 ${CMAKE_BINARY_DIR}/rt64)
|
||||||
|
|
||||||
# set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}")
|
# set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}")
|
||||||
@@ -45,10 +48,10 @@ set(BUILD_SHARED_LIBS OFF)
|
|||||||
SET(LUNASVG_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
SET(LUNASVG_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/lunasvg)
|
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/lunasvg)
|
||||||
# set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}")
|
# set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}")
|
||||||
SET(ENABLE_SVG_PLUGIN ON CACHE BOOL "" FORCE)
|
SET(RMLUI_SVG_PLUGIN ON CACHE BOOL "" FORCE)
|
||||||
SET(RMLUI_TESTS_ENABLED OFF CACHE BOOL "" FORCE)
|
SET(RMLUI_TESTS_ENABLED OFF CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi)
|
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi)
|
||||||
target_compile_definitions(RmlCore PRIVATE LUNASVG_BUILD_STATIC)
|
target_compile_definitions(rmlui_core PRIVATE LUNASVG_BUILD_STATIC)
|
||||||
|
|
||||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime)
|
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime)
|
||||||
|
|
||||||
@@ -150,13 +153,41 @@ set (SOURCES
|
|||||||
${CMAKE_SOURCE_DIR}/src/game/debug.cpp
|
${CMAKE_SOURCE_DIR}/src/game/debug.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/game/quicksaving.cpp
|
${CMAKE_SOURCE_DIR}/src/game/quicksaving.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/game/recomp_api.cpp
|
${CMAKE_SOURCE_DIR}/src/game/recomp_api.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/game/recomp_actor_api.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/game/recomp_data_api.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/game/rom_decompression.cpp
|
${CMAKE_SOURCE_DIR}/src/game/rom_decompression.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_state.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_prompt.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_elements.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_details_panel.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_installer.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_menu.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_api.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_api_images.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_utils.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_clickable.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_image.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_label.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_radio.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_scroll_container.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_span.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_style.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_text_input.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp
|
${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp
|
||||||
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp
|
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp
|
||||||
@@ -183,6 +214,7 @@ target_include_directories(Zelda64Recompiled PRIVATE
|
|||||||
${CMAKE_SOURCE_DIR}/lib/rt64/src/render
|
${CMAKE_SOURCE_DIR}/lib/rt64/src/render
|
||||||
${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include
|
${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include
|
||||||
${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/include
|
${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/include
|
||||||
|
${CMAKE_SOURCE_DIR}/lib/slot_map/slot_map
|
||||||
${CMAKE_BINARY_DIR}/shaders
|
${CMAKE_BINARY_DIR}/shaders
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
)
|
)
|
||||||
@@ -315,8 +347,8 @@ target_link_libraries(Zelda64Recompiled PRIVATE
|
|||||||
librecomp
|
librecomp
|
||||||
ultramodern
|
ultramodern
|
||||||
rt64
|
rt64
|
||||||
RmlCore
|
RmlUi::Core
|
||||||
RmlDebugger
|
RmlUi::Debugger
|
||||||
nfd
|
nfd
|
||||||
lunasvg
|
lunasvg
|
||||||
)
|
)
|
||||||
@@ -356,6 +388,27 @@ endif()
|
|||||||
build_vertex_shader(Zelda64Recompiled "shaders/InterfaceVS.hlsl" "shaders/InterfaceVS.hlsl")
|
build_vertex_shader(Zelda64Recompiled "shaders/InterfaceVS.hlsl" "shaders/InterfaceVS.hlsl")
|
||||||
build_pixel_shader(Zelda64Recompiled "shaders/InterfacePS.hlsl" "shaders/InterfacePS.hlsl")
|
build_pixel_shader(Zelda64Recompiled "shaders/InterfacePS.hlsl" "shaders/InterfacePS.hlsl")
|
||||||
|
|
||||||
|
# Embed all .nrm files in the "mods" directory
|
||||||
|
file(GLOB NRM_FILES "${CMAKE_SOURCE_DIR}/mods/*.nrm")
|
||||||
|
|
||||||
|
set(GENERATED_NRM_SOURCES "")
|
||||||
|
|
||||||
|
foreach(NRM_FILE ${NRM_FILES})
|
||||||
|
get_filename_component(NRM_NAME ${NRM_FILE} NAME_WE)
|
||||||
|
set(OUT_C "${CMAKE_CURRENT_BINARY_DIR}/mods/${NRM_NAME}.c")
|
||||||
|
set(OUT_H "${CMAKE_CURRENT_BINARY_DIR}/mods/${NRM_NAME}.h")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${OUT_C} ${OUT_H}
|
||||||
|
COMMAND file_to_c ${NRM_FILE} ${NRM_NAME} ${OUT_C} ${OUT_H}
|
||||||
|
DEPENDS ${NRM_FILE}
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND GENERATED_NRM_SOURCES ${OUT_C})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
target_sources(Zelda64Recompiled PRIVATE ${GENERATED_NRM_SOURCES})
|
||||||
|
|
||||||
target_sources(Zelda64Recompiled PRIVATE ${SOURCES})
|
target_sources(Zelda64Recompiled PRIVATE ${SOURCES})
|
||||||
|
|
||||||
set_property(TARGET Zelda64Recompiled PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
set_property(TARGET Zelda64Recompiled PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||||
|
@@ -1,30 +0,0 @@
|
|||||||
<template name="prompt">
|
|
||||||
<head>
|
|
||||||
</head>
|
|
||||||
<body class="prompt">
|
|
||||||
<div class="prompt__overlay" />
|
|
||||||
<div class="prompt__content-wrapper" data-if="promptOpen">
|
|
||||||
<div class="prompt__content">
|
|
||||||
<h3>{{ promptHeader }}</h3>
|
|
||||||
<p>{{ promptContent }}</p>
|
|
||||||
<div class="prompt__controls">
|
|
||||||
<button
|
|
||||||
autofocus="true"
|
|
||||||
id="prompt__confirm-button"
|
|
||||||
class="button button--success"
|
|
||||||
style="nav-left: none; nav-right: #prompt__cancel-button"
|
|
||||||
>
|
|
||||||
<div class="button__label" id="prompt__confirm-button-label">{{ promptConfirmLabel }}</div>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
id="prompt__cancel-button"
|
|
||||||
class="button button--error"
|
|
||||||
style="nav-right: none; nav-left: #prompt__confirm-button"
|
|
||||||
>
|
|
||||||
<div class="button__label" id="prompt__cancel-button-label">{{ promptCancelLabel }}</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</template>
|
|
@@ -20,19 +20,19 @@
|
|||||||
}
|
}
|
||||||
.col {
|
.col {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link type="text/template" href="config_menu/general.rml" />
|
<link type="text/template" href="config_menu/general.rml" />
|
||||||
<link type="text/template" href="config_menu/controls.rml" />
|
<link type="text/template" href="config_menu/controls.rml" />
|
||||||
<link type="text/template" href="config_menu/graphics.rml" />
|
<link type="text/template" href="config_menu/graphics.rml" />
|
||||||
<link type="text/template" href="config_menu/sound.rml" />
|
<link type="text/template" href="config_menu/sound.rml" />
|
||||||
|
<link type="text/template" href="config_menu/mods.rml" />
|
||||||
<link type="text/template" href="config_menu/debug.rml" />
|
<link type="text/template" href="config_menu/debug.rml" />
|
||||||
<link type="text/template" href="components/prompt.rml" />
|
|
||||||
</head>
|
</head>
|
||||||
<body class="window">
|
<body class="window">
|
||||||
<!-- <handle move_target="#document"> -->
|
<!-- <handle move_target="#document"> -->
|
||||||
<div id="window" class="rmlui-window rmlui-window--hidden" style="display:flex; flex-flow: column; background-color:rgba(0,0,0,0)" onkeydown="config_keydown">
|
<div id="window" class="rmlui-window" style="display:flex; flex-flow: column; background-color:rgba(0,0,0,0)" onkeydown="config_keydown">
|
||||||
<div class="centered-page" onclick="close_config_menu_backdrop">
|
<div class="centered-page" onclick="close_config_menu_backdrop">
|
||||||
<div class="centered-page__modal">
|
<div class="centered-page__modal">
|
||||||
<tabset class="tabs" id="config_tabset">
|
<tabset class="tabs" id="config_tabset">
|
||||||
@@ -64,6 +64,13 @@
|
|||||||
<panel class="config" data-model="sound_options_model">
|
<panel class="config" data-model="sound_options_model">
|
||||||
<template src="config-menu__sound" />
|
<template src="config-menu__sound" />
|
||||||
</panel>
|
</panel>
|
||||||
|
<tab class="tab" id="tab_mods">
|
||||||
|
<div>Mods</div>
|
||||||
|
<div class="tab__indicator"></div>
|
||||||
|
</tab>
|
||||||
|
<panel class="config">
|
||||||
|
<template src="config-menu__mods" />
|
||||||
|
</panel>
|
||||||
<tab class="tab" data-model="debug_model" data-if="debug_enabled" id="tab_debug">
|
<tab class="tab" data-model="debug_model" data-if="debug_enabled" id="tab_debug">
|
||||||
<div>Debug</div>
|
<div>Debug</div>
|
||||||
<div class="tab__indicator"></div>
|
<div class="tab__indicator"></div>
|
||||||
@@ -109,19 +116,6 @@
|
|||||||
<label><span style="font-family:promptfont;">↧</span> Accept</label> -->
|
<label><span style="font-family:promptfont;">↧</span> Accept</label> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
id="prompt-root"
|
|
||||||
data-model="prompt_model"
|
|
||||||
data-if="prompt__open"
|
|
||||||
data-alias-promptOpen="prompt__open"
|
|
||||||
data-alias-promptHeader="prompt__header"
|
|
||||||
data-alias-promptContent="prompt__content"
|
|
||||||
data-alias-promptConfirmLabel="prompt__confirmLabel"
|
|
||||||
data-alias-promptCancelLabel="prompt__cancelLabel"
|
|
||||||
data-event-click="prompt__on_click"
|
|
||||||
>
|
|
||||||
<template src="prompt"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- </handle> -->
|
<!-- </handle> -->
|
||||||
<!-- <handle size_target="#document" style="position: absolute; width: 16dp; height: 16dp; bottom: 0px; right: 0px; cursor: resize;"></handle> -->
|
<!-- <handle size_target="#document" style="position: absolute; width: 16dp; height: 16dp; bottom: 0px; right: 0px; cursor: resize;"></handle> -->
|
||||||
|
@@ -71,7 +71,7 @@
|
|||||||
data-event-blur="set_input_row_focus(-1)"
|
data-event-blur="set_input_row_focus(-1)"
|
||||||
data-event-focus="set_input_row_focus(i)"
|
data-event-focus="set_input_row_focus(i)"
|
||||||
data-event-click="clear_input_bindings(i)"
|
data-event-click="clear_input_bindings(i)"
|
||||||
class="icon-button icon-button--danger"
|
class="icon-button icon-button--error"
|
||||||
data-attr-style="i == 0 ? 'nav-up:#cont_kb_toggle' : 'nav-up:auto'"
|
data-attr-style="i == 0 ? 'nav-up:#cont_kb_toggle' : 'nav-up:auto'"
|
||||||
>
|
>
|
||||||
<svg src="icons/Trash.svg" />
|
<svg src="icons/Trash.svg" />
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
data-event-blur="set_input_row_focus(-1)"
|
data-event-blur="set_input_row_focus(-1)"
|
||||||
data-event-focus="set_input_row_focus(i)"
|
data-event-focus="set_input_row_focus(i)"
|
||||||
data-event-click="reset_single_input_binding_to_default(i)"
|
data-event-click="reset_single_input_binding_to_default(i)"
|
||||||
class="icon-button icon-button--danger"
|
class="icon-button icon-button--error"
|
||||||
data-attr-style="i == 0 ? 'nav-up:#cont_kb_toggle' : 'nav-up:auto'"
|
data-attr-style="i == 0 ? 'nav-up:#cont_kb_toggle' : 'nav-up:auto'"
|
||||||
>
|
>
|
||||||
<svg src="icons/Reset.svg" />
|
<svg src="icons/Reset.svg" />
|
||||||
|
9
assets/config_menu/mods.rml
Normal file
9
assets/config_menu/mods.rml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template name="config-menu__mods">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form class="config__form">
|
||||||
|
<recomp-mod-menu id="menu_mods" />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</template>
|
70
assets/config_sub_menu.rml
Normal file
70
assets/config_sub_menu.rml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<rml>
|
||||||
|
<head>
|
||||||
|
<link type="text/rcss" href="rml.rcss"/>
|
||||||
|
<link type="text/rcss" href="recomp.rcss"/>
|
||||||
|
<title>Inventory</title>
|
||||||
|
<style>
|
||||||
|
body
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the window icon. */
|
||||||
|
div#title_bar div#icon
|
||||||
|
{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.flex-grid {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.col {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="window">
|
||||||
|
<!-- <handle move_target="#document"> -->
|
||||||
|
<div id="window" class="rmlui-window" style="display:flex; flex-flow: column; background-color:rgba(0,0,0,0)" onkeydown="config_keydown">
|
||||||
|
<div class="centered-page" onclick="close_config_menu_backdrop">
|
||||||
|
<div class="centered-page__modal">
|
||||||
|
<div class="config__icon-buttons">
|
||||||
|
<button
|
||||||
|
class="icon-button"
|
||||||
|
onclick="open_quit_game_prompt"
|
||||||
|
id="config__quit-game-button"
|
||||||
|
>
|
||||||
|
<svg src="icons/Quit.svg" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="icon-button"
|
||||||
|
onclick="close_config_menu"
|
||||||
|
id="config__close-menu-button"
|
||||||
|
>
|
||||||
|
<svg src="icons/X.svg" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<recomp-config-sub-menu id="config_sub_menu" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="centered-page__controls"
|
||||||
|
data-model="nav_help_model"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<span>Navigate</span>
|
||||||
|
<span class="prompt-font-sm">{{nav_help__navigate}}</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Accept</span>
|
||||||
|
<span class="prompt-font-sm">{{nav_help__accept}}</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Exit</span>
|
||||||
|
<span class="prompt-font-sm">{{nav_help__exit}}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</rml>
|
@@ -51,6 +51,10 @@
|
|||||||
<div class="menu-list-item__bullet">•</div>
|
<div class="menu-list-item__bullet">•</div>
|
||||||
<div class="menu-list-item__label">Settings</div>
|
<div class="menu-list-item__label">Settings</div>
|
||||||
</button>
|
</button>
|
||||||
|
<button onclick="open_mods" class="menu-list-item menu-list-item--right">
|
||||||
|
<div class="menu-list-item__bullet">•</div>
|
||||||
|
<div class="menu-list-item__label">Mods</div>
|
||||||
|
</button>
|
||||||
<button onclick="exit_game" class="menu-list-item menu-list-item--right">
|
<button onclick="exit_game" class="menu-list-item menu-list-item--right">
|
||||||
<div class="menu-list-item__bullet">•</div>
|
<div class="menu-list-item__bullet">•</div>
|
||||||
<div class="menu-list-item__label">Exit</div>
|
<div class="menu-list-item__label">Exit</div>
|
||||||
|
2512
assets/recomp.rcss
2512
assets/recomp.rcss
File diff suppressed because one or more lines are too long
22
assets/scss/package-lock.json
generated
22
assets/scss/package-lock.json
generated
@@ -307,11 +307,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.0.1"
|
"fill-range": "^7.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -638,9 +638,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
},
|
},
|
||||||
@@ -1192,12 +1192,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/micromatch": {
|
"node_modules/micromatch": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.2",
|
"braces": "^3.0.3",
|
||||||
"picomatch": "^2.3.1"
|
"picomatch": "^2.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@@ -99,159 +99,3 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-option {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
margin: space(16) space(0) space(24);
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-top: space(16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__title {
|
|
||||||
@extend %label-md;
|
|
||||||
padding: 0 space(12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
input:first-of-type {
|
|
||||||
nav-left: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:last-of-type {
|
|
||||||
nav-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__tab-label {
|
|
||||||
@extend %label-sm;
|
|
||||||
@include trans-colors-opa;
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
height: auto;
|
|
||||||
margin: space(4) space(12) 0;
|
|
||||||
padding: space(8) 0;
|
|
||||||
color: $color-text-inactive;
|
|
||||||
tab-index: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $color-text;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.radio {
|
|
||||||
@extend %nav-all;
|
|
||||||
@include trans-colors-opa;
|
|
||||||
visibility: visible;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
|
|
||||||
&:not(:disabled) {
|
|
||||||
&:checked + .config-option__tab-label {
|
|
||||||
border-bottom: 1dp;
|
|
||||||
border-color: $color-text;
|
|
||||||
color: $color-text;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rmlui-window:not([mouse-active]) &:focus + .config-option__tab-label {
|
|
||||||
transition: none;
|
|
||||||
animation: $focus-anim-border;
|
|
||||||
border-color: $color-secondary;
|
|
||||||
color: $color-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus + .config-option__tab-label,
|
|
||||||
&:hover + .config-option__tab-label {
|
|
||||||
color: $color-text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled + .config-option__tab-label {
|
|
||||||
opacity: 0.5;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.range slidertrack {
|
|
||||||
@include trans-colors;
|
|
||||||
height: 2dp;
|
|
||||||
margin-top: space(8);
|
|
||||||
background-color: $color-border;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.range sliderbar {
|
|
||||||
@include trans-colors;
|
|
||||||
width: space(16);
|
|
||||||
height: space(16);
|
|
||||||
margin-top: space(1);
|
|
||||||
margin-right: space(-8);
|
|
||||||
margin-left: space(-8);
|
|
||||||
transition: background-color $transition-quick;
|
|
||||||
border-radius: 8dp;
|
|
||||||
background-color: $color-text-dim;
|
|
||||||
|
|
||||||
.rmlui-window:not([mouse-active]) &:focus {
|
|
||||||
@include border($color-a);
|
|
||||||
animation: $focus-anim-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $color-text;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.range sliderbar:active,
|
|
||||||
input.range slidertrack:active + sliderbar {
|
|
||||||
background-color: $color-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.range sliderarrowdec,
|
|
||||||
input.range sliderarrowinc {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__details {
|
|
||||||
@extend %label-xs;
|
|
||||||
height: space(18);
|
|
||||||
margin: space(14) space(12) 0;
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__range-wrapper {
|
|
||||||
max-width: space(360);
|
|
||||||
margin-top: space(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__range-label {
|
|
||||||
@extend %label-sm;
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
// flex: 0 0 space(32);
|
|
||||||
width: space(56);
|
|
||||||
margin: 0 12dp;
|
|
||||||
margin-right: space(16);
|
|
||||||
padding: 0;
|
|
||||||
color: $color-text;
|
|
||||||
tab-index: none;
|
|
||||||
}
|
|
||||||
|
27
assets/scss/styles/components/ConfigDescription.scss
Normal file
27
assets/scss/styles/components/ConfigDescription.scss
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.config-description {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
padding: space(16);
|
||||||
|
border-radius: 0dp;
|
||||||
|
border-bottom-right-radius: $border-radius-modal;
|
||||||
|
border-bottom-left-radius: $border-radius-modal;
|
||||||
|
background-color: $color-bg-shadow;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&__contents {
|
||||||
|
@extend %body;
|
||||||
|
padding: space(16);
|
||||||
|
line-height: space(28);
|
||||||
|
white-space: pre-line;
|
||||||
|
|
||||||
|
b {
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $color-warning;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
assets/scss/styles/components/ConfigGroup.scss
Normal file
29
assets/scss/styles/components/ConfigGroup.scss
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
.config-group {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&--scrollable {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
padding: 0 0 0 space(16);
|
||||||
|
|
||||||
|
.config-group__wrapper {
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
@extend %label-md;
|
||||||
|
color: $color-primary;
|
||||||
|
|
||||||
|
&--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
padding: space(16) 0;
|
||||||
|
}
|
||||||
|
}
|
413
assets/scss/styles/components/ConfigOption.scss
Normal file
413
assets/scss/styles/components/ConfigOption.scss
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
.config-option {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin: space(16) space(0) space(24);
|
||||||
|
|
||||||
|
&--hz {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: space(4);
|
||||||
|
margin-bottom: space(4);
|
||||||
|
|
||||||
|
.config-option__title {
|
||||||
|
@extend %label-md;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__list {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__title {
|
||||||
|
@extend %label-md;
|
||||||
|
padding: 0 space(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__radio-tabs,
|
||||||
|
.config-option__list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
input:first-of-type {
|
||||||
|
nav-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:last-of-type {
|
||||||
|
nav-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__tab-label {
|
||||||
|
@extend %label-sm;
|
||||||
|
@include trans-colors-opa;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
height: auto;
|
||||||
|
margin: space(4) space(12) 0;
|
||||||
|
padding: space(8) 0;
|
||||||
|
color: $color-text-inactive;
|
||||||
|
tab-index: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $color-text;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__checkbox-wrapper {
|
||||||
|
@include trans-colors-opa;
|
||||||
|
width: space(32);
|
||||||
|
height: space(32);
|
||||||
|
margin: space(4) space(12) 0;
|
||||||
|
border-radius: $border-radius-sm;
|
||||||
|
opacity: 0.5;
|
||||||
|
background-color: $color-bg-overlay;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[checked] {
|
||||||
|
background-color: $color-a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__checkbox {
|
||||||
|
@extend %nav-all;
|
||||||
|
@include trans-colors-opa;
|
||||||
|
visibility: visible;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove & Replace old stylings
|
||||||
|
input.radio {
|
||||||
|
@extend %nav-all;
|
||||||
|
@include trans-colors-opa;
|
||||||
|
visibility: visible;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
&:not(:disabled) {
|
||||||
|
&:checked + .config-option__tab-label {
|
||||||
|
border-bottom: 1dp;
|
||||||
|
border-color: $color-text;
|
||||||
|
color: $color-text;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rmlui-window:not([mouse-active]) &:focus + .config-option__tab-label {
|
||||||
|
transition: none;
|
||||||
|
animation: $focus-anim-border;
|
||||||
|
border-color: $color-secondary;
|
||||||
|
color: $color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus + .config-option__tab-label,
|
||||||
|
&:hover + .config-option__tab-label {
|
||||||
|
color: $color-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled + .config-option__tab-label {
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.range slidertrack {
|
||||||
|
@include trans-colors;
|
||||||
|
height: 2dp;
|
||||||
|
margin-top: space(8);
|
||||||
|
background-color: $color-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.range sliderbar {
|
||||||
|
@include trans-colors;
|
||||||
|
width: space(16);
|
||||||
|
height: space(16);
|
||||||
|
margin-top: space(1);
|
||||||
|
margin-right: space(-8);
|
||||||
|
margin-left: space(-8);
|
||||||
|
transition: background-color $transition-quick;
|
||||||
|
border-radius: 8dp;
|
||||||
|
background-color: $color-text-dim;
|
||||||
|
|
||||||
|
.rmlui-window:not([mouse-active]) &:focus {
|
||||||
|
@include border($color-a);
|
||||||
|
animation: $focus-anim-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-text;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.range sliderbar:active,
|
||||||
|
input.range slidertrack:active + sliderbar {
|
||||||
|
background-color: $color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.range sliderarrowdec,
|
||||||
|
input.range sliderarrowinc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__details {
|
||||||
|
@extend %label-xs;
|
||||||
|
height: space(18);
|
||||||
|
margin: space(14) space(12) 0;
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option-color {
|
||||||
|
width: 100%;
|
||||||
|
max-width: space(360);
|
||||||
|
height: auto;
|
||||||
|
margin-top: space(4);
|
||||||
|
margin-left: space(12);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&__preview-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
height: space(8 * 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__preview-block {
|
||||||
|
display: block;
|
||||||
|
width: space(8 * 11);
|
||||||
|
height: 100%;
|
||||||
|
border-width: $border-width-thickness;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
border-color: $color-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__hsv-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
padding-left: space(8);
|
||||||
|
|
||||||
|
.config-option-range {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
label {
|
||||||
|
min-width: space(72);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option-range {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
max-width: space(360);
|
||||||
|
height: auto;
|
||||||
|
margin-top: space(4);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
@extend %label-sm;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
width: space(56);
|
||||||
|
margin: 0 12dp;
|
||||||
|
margin-right: space(16);
|
||||||
|
padding: 0;
|
||||||
|
color: $color-text;
|
||||||
|
tab-index: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__range-input {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
slidertrack {
|
||||||
|
@include trans-colors;
|
||||||
|
height: 2dp;
|
||||||
|
margin-top: space(8);
|
||||||
|
background-color: $color-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderbar {
|
||||||
|
@include trans-colors;
|
||||||
|
width: space(16);
|
||||||
|
height: space(16);
|
||||||
|
margin-top: space(1);
|
||||||
|
margin-right: space(-8);
|
||||||
|
margin-left: space(-8);
|
||||||
|
transition: background-color $transition-quick;
|
||||||
|
border-radius: 8dp;
|
||||||
|
background-color: $color-text-dim;
|
||||||
|
|
||||||
|
.rmlui-window:not([mouse-active]) &:focus {
|
||||||
|
@include border($color-a);
|
||||||
|
animation: $focus-anim-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-text;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderbar:active,
|
||||||
|
slidertrack:active + sliderbar {
|
||||||
|
background-color: $color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderarrowdec,
|
||||||
|
sliderarrowinc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__range-wrapper {
|
||||||
|
max-width: space(360);
|
||||||
|
margin-top: space(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__range-label {
|
||||||
|
@extend %label-sm;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
// flex: 0 0 space(32);
|
||||||
|
width: space(56);
|
||||||
|
margin: 0 12dp;
|
||||||
|
margin-right: space(16);
|
||||||
|
padding: 0;
|
||||||
|
color: $color-text;
|
||||||
|
tab-index: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option-dropdown, .config-option-textfield {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
padding: space(8) space(24) space(8) space(12);
|
||||||
|
|
||||||
|
&__select {
|
||||||
|
display: block;
|
||||||
|
height: space(48);
|
||||||
|
padding: space(14);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
// Cursed guess & check so that this appears to be the same height as the select
|
||||||
|
$extra-space: 2;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
padding: space(0 + $extra-space) 0 space(10 + $extra-space);
|
||||||
|
cursor: text;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__select, &__wrapper {
|
||||||
|
@extend %body;
|
||||||
|
@extend %nav-all;
|
||||||
|
@include trans-colors-border;
|
||||||
|
@include border($color-white-a50);
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
|
||||||
|
width: auto;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
background-color: $color-white-a5;
|
||||||
|
|
||||||
|
&:hover, &:focus {
|
||||||
|
@include border($color-white-a80);
|
||||||
|
background-color: $color-white-a20;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectvalue {
|
||||||
|
display: inline;
|
||||||
|
height: auto;
|
||||||
|
margin: auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectbox {
|
||||||
|
@include border($color-border);
|
||||||
|
margin-top: space(2);
|
||||||
|
padding: space(4) 0;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
background-color: $color-background-3;
|
||||||
|
|
||||||
|
option {
|
||||||
|
@extend %nav-all;
|
||||||
|
@include trans-colors;
|
||||||
|
padding: space(8) space(12);
|
||||||
|
background-color: $color-transparent;
|
||||||
|
color: $color-text-dim;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
&:hover, &:focus {
|
||||||
|
background-color: $color-white-a20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:checked) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: $color-white-a5;
|
||||||
|
color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
<button
|
<button
|
||||||
class="icon-button icon-button--danger"
|
class="icon-button icon-button--error"
|
||||||
>
|
>
|
||||||
<svg src="icons/Trash.svg" />
|
<svg src="icons/Trash.svg" />
|
||||||
</button>
|
</button>
|
||||||
@@ -82,7 +82,7 @@ $icon-button-size: 56 - ($border-width-thickness-num * 2);
|
|||||||
@include create-icon-button-variation($color-success);
|
@include create-icon-button-variation($color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
&--danger {
|
&--error {
|
||||||
@include create-icon-button-variation($color-error);
|
@include create-icon-button-variation($color-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: space(20) space(24);
|
padding: space(20) space(24);
|
||||||
transition: color $transition-quick;
|
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
background-color: rgba(0,0,0,0);
|
background-color: rgba(0,0,0,0);
|
||||||
color: $color-text-inactive;
|
color: $color-text-inactive;
|
||||||
|
@@ -2,6 +2,9 @@
|
|||||||
@import "./ControlOption";
|
@import "./ControlOption";
|
||||||
@import "./Tabs";
|
@import "./Tabs";
|
||||||
@import "./Config";
|
@import "./Config";
|
||||||
|
@import "./ConfigGroup";
|
||||||
|
@import "./ConfigOption";
|
||||||
|
@import "./ConfigDescription";
|
||||||
@import "./InputConfig";
|
@import "./InputConfig";
|
||||||
@import "./Button";
|
@import "./Button";
|
||||||
@import "./IconButton";
|
@import "./IconButton";
|
||||||
|
@@ -189,8 +189,7 @@ select {
|
|||||||
// background: rgb(150,150,150)
|
// background: rgb(150,150,150)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
input.radio,
|
input.radio {
|
||||||
input.checkbox {
|
|
||||||
flex: 0;
|
flex: 0;
|
||||||
width:0dp;
|
width:0dp;
|
||||||
nav-up:auto;
|
nav-up:auto;
|
||||||
@@ -200,3 +199,14 @@ input.checkbox {
|
|||||||
tab-index:auto;
|
tab-index:auto;
|
||||||
focus:auto;
|
focus:auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.checkbox {
|
||||||
|
width: space(20);
|
||||||
|
height: space(20);
|
||||||
|
nav-up:auto;
|
||||||
|
nav-right:auto;
|
||||||
|
nav-down:auto;
|
||||||
|
nav-left:auto;
|
||||||
|
tab-index:auto;
|
||||||
|
focus:auto;
|
||||||
|
}
|
||||||
|
38
config_example.cheats.en_us.json
Normal file
38
config_example.cheats.en_us.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"consumables": "Consumables",
|
||||||
|
"consumables/infinite_magic": "Infinite Magic",
|
||||||
|
"consumables/infinite_magic:description": "You get sooo much magic lol!!",
|
||||||
|
"consumables/infinite_rupees": "Infinite Rupees",
|
||||||
|
"consumables/infinite_arrows": "Infinite Arrows",
|
||||||
|
"consumables/infinite_bombs": "Infinite Bombs",
|
||||||
|
"consumables/infinite_health": "Infinite Health",
|
||||||
|
|
||||||
|
"consumable_actions": "Consumable Actions",
|
||||||
|
"consumable_actions/refill_all": "primary",
|
||||||
|
"consumable_actions/refill_all:description": "Refills anything that can be refilled, like magic, rupees, arrows, bombs, health, etc.",
|
||||||
|
|
||||||
|
"gameplay": "Gameplay",
|
||||||
|
|
||||||
|
"gameplay/movement": "Movement",
|
||||||
|
"gameplay/movement/L_for_fast": "Hold L to move fast",
|
||||||
|
"gameplay/movement/L_for_fast/values/off": "Off",
|
||||||
|
"gameplay/movement/L_for_fast/values/x2": "X2",
|
||||||
|
"gameplay/movement/L_for_fast/values/x4": "X4",
|
||||||
|
"gameplay/movement/L_for_fast/values/x6": "X6",
|
||||||
|
"gameplay/movement/L_for_fast2": "Hold L to move fast the sequel",
|
||||||
|
"gameplay/movement/L_for_fast2/values/off": "Off",
|
||||||
|
"gameplay/movement/L_for_fast2/values/x2": "X2",
|
||||||
|
"gameplay/movement/L_for_fast2/values/x4": "X4",
|
||||||
|
"gameplay/movement/L_for_fast2/values/x6": "X6",
|
||||||
|
"gameplay/movement/L_to_levitate": "Hold L to levitate",
|
||||||
|
"gameplay/movement/always_quickspin": "Always quickspin",
|
||||||
|
"gameplay/movement/always_quickspin:description": "Always <b>quickspin</b> whenever using your <i>sword</i> and in a <i>state</i> where <i>you</i> can <b>quickspin.</b><br /><br />yeah...",
|
||||||
|
"gameplay/movement/heart_color": "Hearts color",
|
||||||
|
"gameplay/movement/link_size": "Link's Size",
|
||||||
|
"gameplay/movement/link_name": "Link's Name",
|
||||||
|
"gameplay/movement/link_name:description": "Change Link's name to something silly!",
|
||||||
|
"gameplay/abilities": "Abilities",
|
||||||
|
"gameplay/abilities/fd_anywhere": "Fierce Deity Anywhere",
|
||||||
|
"gameplay/abilities/permanent_razor_sword": "Permanent Razor Sword",
|
||||||
|
"gameplay/abilities/permanent_razor_sword2": "MORE Permanent Razor Sword"
|
||||||
|
}
|
135
config_example.cheats.json
Normal file
135
config_example.cheats.json
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "CheckboxGroup",
|
||||||
|
"key": "consumables",
|
||||||
|
"toggle": true,
|
||||||
|
"toggleDefault": false,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_magic",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_rupees",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_arrows",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_bombs",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_health",
|
||||||
|
"default": false,
|
||||||
|
"callback": "on_update_health"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Group",
|
||||||
|
"key": "consumable_actions",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"key": "refill_all",
|
||||||
|
"variant": "primary",
|
||||||
|
"callback": "on_refill_all"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Group",
|
||||||
|
"key": "gameplay",
|
||||||
|
"toggle": true,
|
||||||
|
"toggleDefault": true,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "Group",
|
||||||
|
"key": "movement",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "Dropdown",
|
||||||
|
"key": "L_for_fast",
|
||||||
|
"default": "x2",
|
||||||
|
"values": [
|
||||||
|
"off",
|
||||||
|
"x2",
|
||||||
|
"x4",
|
||||||
|
"x6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "RadioTabs",
|
||||||
|
"key": "L_for_fast2",
|
||||||
|
"default": "x2",
|
||||||
|
"values": [
|
||||||
|
"off",
|
||||||
|
"x2",
|
||||||
|
"x4",
|
||||||
|
"x6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "L_to_levitate",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "always_quickspin",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Color",
|
||||||
|
"key": "heart_color",
|
||||||
|
"default": [255, 50, 50]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Range",
|
||||||
|
"key": "link_size",
|
||||||
|
"default": 100,
|
||||||
|
"suffix": "%",
|
||||||
|
"min": 20,
|
||||||
|
"max": 400,
|
||||||
|
"step": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TextField",
|
||||||
|
"key": "link_name",
|
||||||
|
"default": "George",
|
||||||
|
"maxlength": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Group",
|
||||||
|
"key": "abilities",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "fd_anywhere",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "permanent_razor_sword",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "permanent_razor_sword2",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
10
include/overloaded.h
Normal file
10
include/overloaded.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef __OVERLOADED_H__
|
||||||
|
#define __OVERLOADED_H__
|
||||||
|
|
||||||
|
// Helper for std::visit
|
||||||
|
template<class... Ts>
|
||||||
|
struct overloaded : Ts... { using Ts::operator()...; };
|
||||||
|
template<class... Ts>
|
||||||
|
overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
|
|
||||||
|
#endif
|
11
include/recomp_data.h
Normal file
11
include/recomp_data.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef __RECOMP_DATA_H__
|
||||||
|
#define __RECOMP_DATA_H__
|
||||||
|
|
||||||
|
namespace recomputil {
|
||||||
|
void init_extended_actor_data();
|
||||||
|
void reset_actor_data();
|
||||||
|
|
||||||
|
void register_data_api_exports();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -3,125 +3,143 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
// TODO move this file into src/ui
|
||||||
|
|
||||||
#include "SDL.h"
|
#include "SDL.h"
|
||||||
#include "RmlUi/Core.h"
|
#include "RmlUi/Core.h"
|
||||||
|
|
||||||
|
#include "../src/ui/util/hsv.h"
|
||||||
|
#include "../src/ui/util/bem.h"
|
||||||
|
|
||||||
|
#include "../src/ui/core/ui_context.h"
|
||||||
|
|
||||||
namespace Rml {
|
namespace Rml {
|
||||||
class ElementDocument;
|
class ElementDocument;
|
||||||
class EventListenerInstancer;
|
class EventListenerInstancer;
|
||||||
class Context;
|
class Context;
|
||||||
class Event;
|
class Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace recompui {
|
namespace recompui {
|
||||||
class UiEventListenerInstancer;
|
class UiEventListenerInstancer;
|
||||||
|
|
||||||
class MenuController {
|
// TODO remove this once the UI has been ported over to the new system.
|
||||||
public:
|
class MenuController {
|
||||||
virtual ~MenuController() {}
|
public:
|
||||||
virtual Rml::ElementDocument* load_document(Rml::Context* context) = 0;
|
virtual ~MenuController() {}
|
||||||
virtual void register_events(UiEventListenerInstancer& listener) = 0;
|
virtual void load_document() = 0;
|
||||||
virtual void make_bindings(Rml::Context* context) = 0;
|
virtual void register_events(UiEventListenerInstancer& listener) = 0;
|
||||||
};
|
virtual void make_bindings(Rml::Context* context) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
std::unique_ptr<MenuController> create_launcher_menu();
|
std::unique_ptr<MenuController> create_launcher_menu();
|
||||||
std::unique_ptr<MenuController> create_config_menu();
|
std::unique_ptr<MenuController> create_config_menu();
|
||||||
|
|
||||||
using event_handler_t = void(const std::string& param, Rml::Event&);
|
using event_handler_t = void(const std::string& param, Rml::Event&);
|
||||||
|
|
||||||
void queue_event(const SDL_Event& event);
|
void queue_event(const SDL_Event& event);
|
||||||
bool try_deque_event(SDL_Event& out);
|
bool try_deque_event(SDL_Event& out);
|
||||||
|
|
||||||
std::unique_ptr<UiEventListenerInstancer> make_event_listener_instancer();
|
std::unique_ptr<UiEventListenerInstancer> make_event_listener_instancer();
|
||||||
void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler);
|
void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler);
|
||||||
|
|
||||||
enum class Menu {
|
void show_context(ContextId context, std::string_view param);
|
||||||
Launcher,
|
void hide_context(ContextId context);
|
||||||
Config,
|
void hide_all_contexts();
|
||||||
None
|
bool is_context_shown(ContextId context);
|
||||||
};
|
bool is_context_capturing_input();
|
||||||
|
bool is_context_capturing_mouse();
|
||||||
|
bool is_any_context_shown();
|
||||||
|
ContextId try_close_current_context();
|
||||||
|
|
||||||
void set_current_menu(Menu menu);
|
ContextId get_launcher_context_id();
|
||||||
Menu get_current_menu();
|
ContextId get_config_context_id();
|
||||||
|
ContextId get_config_sub_menu_context_id();
|
||||||
|
|
||||||
enum class ConfigSubmenu {
|
enum class ConfigTab {
|
||||||
General,
|
General,
|
||||||
Controls,
|
Controls,
|
||||||
Graphics,
|
Graphics,
|
||||||
Audio,
|
Sound,
|
||||||
Debug,
|
Mods,
|
||||||
Count
|
Debug,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ButtonVariant {
|
void set_config_tab(ConfigTab tab);
|
||||||
Primary,
|
Rml::ElementTabSet* get_config_tabset();
|
||||||
Secondary,
|
Rml::Element* get_mod_tab();
|
||||||
Tertiary,
|
void set_config_tabset_mod_nav();
|
||||||
Success,
|
void focus_mod_configure_button();
|
||||||
Error,
|
|
||||||
Warning,
|
|
||||||
NumVariants,
|
|
||||||
};
|
|
||||||
|
|
||||||
void set_config_submenu(ConfigSubmenu submenu);
|
enum class ButtonVariant {
|
||||||
|
Primary,
|
||||||
|
Secondary,
|
||||||
|
Tertiary,
|
||||||
|
Success,
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
NumVariants,
|
||||||
|
};
|
||||||
|
|
||||||
void destroy_ui();
|
void init_styling(const std::filesystem::path& rcss_file);
|
||||||
void apply_color_hack();
|
void init_prompt_context();
|
||||||
void get_window_size(int& width, int& height);
|
void open_choice_prompt(
|
||||||
void set_cursor_visible(bool visible);
|
const std::string& header_text,
|
||||||
void update_supported_options();
|
const std::string& content_text,
|
||||||
void toggle_fullscreen();
|
const std::string& confirm_label_text,
|
||||||
void update_rml_display_refresh_rate();
|
const std::string& cancel_label_text,
|
||||||
|
std::function<void()> confirm_action,
|
||||||
|
std::function<void()> cancel_action,
|
||||||
|
ButtonVariant confirm_variant = ButtonVariant::Success,
|
||||||
|
ButtonVariant cancel_variant = ButtonVariant::Error,
|
||||||
|
bool focus_on_cancel = true,
|
||||||
|
const std::string& return_element_id = ""
|
||||||
|
);
|
||||||
|
void open_info_prompt(
|
||||||
|
const std::string& header_text,
|
||||||
|
const std::string& content_text,
|
||||||
|
const std::string& okay_label_text,
|
||||||
|
std::function<void()> okay_action,
|
||||||
|
ButtonVariant okay_variant = ButtonVariant::Error,
|
||||||
|
const std::string& return_element_id = ""
|
||||||
|
);
|
||||||
|
void open_notification(
|
||||||
|
const std::string& header_text,
|
||||||
|
const std::string& content_text,
|
||||||
|
const std::string& return_element_id = ""
|
||||||
|
);
|
||||||
|
void close_prompt();
|
||||||
|
bool is_prompt_open();
|
||||||
|
void update_mod_list(bool scan_mods = true);
|
||||||
|
void process_game_started();
|
||||||
|
|
||||||
extern const std::unordered_map<ButtonVariant, std::string> button_variants;
|
void apply_color_hack();
|
||||||
|
void get_window_size(int& width, int& height);
|
||||||
|
void set_cursor_visible(bool visible);
|
||||||
|
void update_supported_options();
|
||||||
|
void toggle_fullscreen();
|
||||||
|
|
||||||
struct PromptContext {
|
bool get_cont_active(void);
|
||||||
Rml::DataModelHandle model_handle;
|
void set_cont_active(bool active);
|
||||||
std::string header = "";
|
void activate_mouse();
|
||||||
std::string content = "";
|
|
||||||
std::string confirmLabel = "Confirm";
|
|
||||||
std::string cancelLabel = "Cancel";
|
|
||||||
ButtonVariant confirmVariant = ButtonVariant::Success;
|
|
||||||
ButtonVariant cancelVariant = ButtonVariant::Error;
|
|
||||||
std::function<void()> onConfirm;
|
|
||||||
std::function<void()> onCancel;
|
|
||||||
|
|
||||||
std::string returnElementId = "";
|
void message_box(const char* msg);
|
||||||
|
|
||||||
bool open = false;
|
void set_render_hooks();
|
||||||
bool shouldFocus = false;
|
|
||||||
bool focusOnCancel = true;
|
|
||||||
|
|
||||||
PromptContext() = default;
|
Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag);
|
||||||
|
Rml::ElementDocument* load_document(const std::filesystem::path& path);
|
||||||
|
Rml::ElementDocument* create_empty_document();
|
||||||
|
Rml::Element* get_child_by_tag(Rml::Element* parent, const std::string& tag);
|
||||||
|
|
||||||
void close_prompt();
|
void queue_image_from_bytes_rgba32(const std::string &src, const std::vector<char> &bytes, uint32_t width, uint32_t height);
|
||||||
void open_prompt(
|
void queue_image_from_bytes_file(const std::string &src, const std::vector<char> &bytes);
|
||||||
const std::string& headerText,
|
void release_image(const std::string &src);
|
||||||
const std::string& contentText,
|
|
||||||
const std::string& confirmLabelText,
|
|
||||||
const std::string& cancelLabelText,
|
|
||||||
std::function<void()> confirmCb,
|
|
||||||
std::function<void()> cancelCb,
|
|
||||||
ButtonVariant _confirmVariant = ButtonVariant::Success,
|
|
||||||
ButtonVariant _cancelVariant = ButtonVariant::Error,
|
|
||||||
bool _focusOnCancel = true,
|
|
||||||
const std::string& _returnElementId = ""
|
|
||||||
);
|
|
||||||
void on_confirm(void);
|
|
||||||
void on_cancel(void);
|
|
||||||
void on_click(Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs);
|
|
||||||
};
|
|
||||||
|
|
||||||
PromptContext *get_prompt_context(void);
|
void drop_files(const std::list<std::filesystem::path> &file_list);
|
||||||
|
|
||||||
bool get_cont_active(void);
|
|
||||||
void set_cont_active(bool active);
|
|
||||||
void activate_mouse();
|
|
||||||
|
|
||||||
void message_box(const char* msg);
|
|
||||||
|
|
||||||
void set_render_hooks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#ifndef __ZELDA_RENDER_H__
|
#ifndef __ZELDA_RENDER_H__
|
||||||
#define __ZELDA_RENDER_H__
|
#define __ZELDA_RENDER_H__
|
||||||
|
|
||||||
#include <set>
|
#include <unordered_set>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#include "common/rt64_user_configuration.h"
|
#include "common/rt64_user_configuration.h"
|
||||||
@@ -14,6 +14,8 @@ namespace RT64 {
|
|||||||
|
|
||||||
namespace zelda64 {
|
namespace zelda64 {
|
||||||
namespace renderer {
|
namespace renderer {
|
||||||
|
inline const std::string special_option_texture_pack_enabled = "_recomp_texture_pack_enabled";
|
||||||
|
|
||||||
class RT64Context final : public ultramodern::renderer::RendererContext {
|
class RT64Context final : public ultramodern::renderer::RendererContext {
|
||||||
public:
|
public:
|
||||||
~RT64Context() override;
|
~RT64Context() override;
|
||||||
@@ -30,9 +32,12 @@ namespace zelda64 {
|
|||||||
uint32_t get_display_framerate() const override;
|
uint32_t get_display_framerate() const override;
|
||||||
float get_resolution_scale() const override;
|
float get_resolution_scale() const override;
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
std::unique_ptr<RT64::Application> app;
|
std::unique_ptr<RT64::Application> app;
|
||||||
std::set<std::filesystem::path> enabled_texture_packs;
|
std::unordered_set<std::string> enabled_texture_packs;
|
||||||
|
std::unordered_set<std::string> secondary_disabled_texture_packs;
|
||||||
|
|
||||||
|
void check_texture_pack_actions();
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<ultramodern::renderer::RendererContext> create_render_context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode);
|
std::unique_ptr<ultramodern::renderer::RendererContext> create_render_context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode);
|
||||||
@@ -41,8 +46,15 @@ namespace zelda64 {
|
|||||||
bool RT64SamplePositionsSupported();
|
bool RT64SamplePositionsSupported();
|
||||||
bool RT64HighPrecisionFBEnabled();
|
bool RT64HighPrecisionFBEnabled();
|
||||||
|
|
||||||
void enable_texture_pack(const recomp::mods::ModHandle& mod);
|
void trigger_texture_pack_update();
|
||||||
|
void enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod);
|
||||||
void disable_texture_pack(const recomp::mods::ModHandle& mod);
|
void disable_texture_pack(const recomp::mods::ModHandle& mod);
|
||||||
|
void secondary_enable_texture_pack(const std::string& mod_id);
|
||||||
|
void secondary_disable_texture_pack(const std::string& mod_id);
|
||||||
|
|
||||||
|
// Texture pack enable option. Must be an enum with two options.
|
||||||
|
// The first option is treated as disabled and the second option is treated as enabled.
|
||||||
|
bool is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,11 +3,14 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
namespace zelda64 {
|
namespace zelda64 {
|
||||||
std::filesystem::path get_asset_path(const char* asset);
|
std::filesystem::path get_asset_path(const char* asset);
|
||||||
void open_file_dialog(std::function<void(bool success, const std::filesystem::path& path)> callback);
|
void open_file_dialog(std::function<void(bool success, const std::filesystem::path& path)> callback);
|
||||||
|
void open_file_dialog_multiple(std::function<void(bool success, const std::list<std::filesystem::path>& paths)> callback);
|
||||||
void show_error_message_box(const char *title, const char *message);
|
void show_error_message_box(const char *title, const char *message);
|
||||||
|
|
||||||
// Apple specific methods that usually require Objective-C. Implemented in support_apple.mm.
|
// Apple specific methods that usually require Objective-C. Implemented in support_apple.mm.
|
||||||
|
@@ -1,2 +1,9 @@
|
|||||||
set(FREETYPE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include)
|
set(FREETYPE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include)
|
||||||
set(FREETYPE_LIBRARIES "${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/release static/vs2015-2022/win64/freetype.lib")
|
set(FREETYPE_LIBRARIES "${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/release static/vs2015-2022/win64/freetype.lib")
|
||||||
|
add_library(Freetype::Freetype STATIC IMPORTED)
|
||||||
|
set_target_properties(Freetype::Freetype PROPERTIES
|
||||||
|
IMPORTED_LOCATION ${FREETYPE_LIBRARIES}
|
||||||
|
)
|
||||||
|
target_include_directories(Freetype::Freetype INTERFACE
|
||||||
|
${FREETYPE_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
Submodule lib/N64ModernRuntime updated: ec56fb39b0...0aa75b98ba
Submodule lib/RmlUi updated: a893ea6386...7a06f27db0
2
lib/rt64
2
lib/rt64
Submodule lib/rt64 updated: ca146dbd31...793abe0bc3
1
lib/slot_map
Submodule
1
lib/slot_map
Submodule
Submodule lib/slot_map added at b8ac8ebd89
6
mods/BUILTIN_MODS.md
Normal file
6
mods/BUILTIN_MODS.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Built-in Mods
|
||||||
|
|
||||||
|
This folder contains mods that are built into the Zelda 64: Recompiled project. Built-in mods behave like normal mods but are present in a clean installation of the project. They can be updated or downgraded by placing the .nrm file for the mod in the appdata mods folder in the same way a normal mod would be, which overrides the built-in version with the manually installed version.
|
||||||
|
|
||||||
|
The list of built-in mods is as follows:
|
||||||
|
* Majora's Mask: Recompiled D-Pad Mod - https://github.com/Zelda64Recomp/MMRecompDpadMod
|
BIN
mods/mm_recomp_dpad_builtin.nrm
Normal file
BIN
mods/mm_recomp_dpad_builtin.nrm
Normal file
Binary file not shown.
91
patches/actor_data.c
Normal file
91
patches/actor_data.c
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include "patches.h"
|
||||||
|
#include "extended_actors.h"
|
||||||
|
#include "transform_ids.h"
|
||||||
|
#include "actor_funcs.h"
|
||||||
|
|
||||||
|
// Use 32 bits of compiler-inserted padding to hold the actor's slot.
|
||||||
|
// 0x22 between halfDaysBits and world
|
||||||
|
#define actorIdByte0(actor) ((u8*)(actor))[0x22]
|
||||||
|
// 0x23 between halfDaysBits and world
|
||||||
|
#define actorIdByte1(actor) ((u8*)(actor))[0x23]
|
||||||
|
// 0x3A between audioFlags and focus
|
||||||
|
#define actorIdByte2(actor) ((u8*)(actor))[0x3A]
|
||||||
|
// 0x3B between audioFlags and focus
|
||||||
|
#define actorIdByte3(actor) ((u8*)(actor))[0x3B]
|
||||||
|
|
||||||
|
u32 actor_get_slot(Actor* actor) {
|
||||||
|
return (actorIdByte0(actor) << 24) | (actorIdByte1(actor) << 16) | (actorIdByte2(actor) << 8) | (actorIdByte3(actor) << 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void actor_set_slot(Actor* actor, ActorExtensionId slot) {
|
||||||
|
u32 b0 = (slot >> 24) & 0xFF;
|
||||||
|
u32 b1 = (slot >> 16) & 0xFF;
|
||||||
|
u32 b2 = (slot >> 8) & 0xFF;
|
||||||
|
u32 b3 = (slot >> 0) & 0xFF;
|
||||||
|
|
||||||
|
actorIdByte0(actor) = b0;
|
||||||
|
actorIdByte1(actor) = b1;
|
||||||
|
actorIdByte2(actor) = b2;
|
||||||
|
actorIdByte3(actor) = b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_EXPORT ActorExtensionId z64recomp_extend_actor(s16 actor_id, u32 size) {
|
||||||
|
return recomp_register_actor_extension(actor_id, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_EXPORT ActorExtensionId z64recomp_extend_actor_all(u32 size) {
|
||||||
|
return recomp_register_actor_extension_generic(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_EXPORT void* z64recomp_get_extended_actor_data(Actor* actor, ActorExtensionId extension) {
|
||||||
|
return recomp_get_actor_data(actor_get_slot(actor), extension, actor->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_EXPORT u32 z64recomp_get_actor_spawn_index(Actor* actor) {
|
||||||
|
return recomp_get_actor_spawn_index(actor_get_slot(actor));
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_EXPORT u32 actor_transform_id(Actor* actor) {
|
||||||
|
u32 spawn_index = z64recomp_get_actor_spawn_index(actor);
|
||||||
|
|
||||||
|
return (spawn_index * ACTOR_TRANSFORM_ID_COUNT) + ACTOR_TRANSFORM_ID_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ACTOR_TRANSFORM_FLAG_INTERPOLATION_SKIPPED = 1 << 0,
|
||||||
|
ACTOR_CUSTOM_FLAG_1 = 1 << 1,
|
||||||
|
} CustomActorFlags;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CustomActorFlags flags;
|
||||||
|
} BaseActorExtensionData;
|
||||||
|
|
||||||
|
ActorExtensionId base_actor_extension_handle;
|
||||||
|
|
||||||
|
void register_base_actor_extensions() {
|
||||||
|
base_actor_extension_handle = z64recomp_extend_actor_all(sizeof(BaseActorExtensionData));
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseActorExtensionData* get_base_extension_data(Actor* actor) {
|
||||||
|
return (BaseActorExtensionData*)z64recomp_get_extended_actor_data(actor, base_actor_extension_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_EXPORT u32 actor_get_interpolation_skipped(Actor* actor) {
|
||||||
|
return (get_base_extension_data(actor)->flags & ACTOR_TRANSFORM_FLAG_INTERPOLATION_SKIPPED) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_EXPORT void actor_set_interpolation_skipped(Actor* actor) {
|
||||||
|
get_base_extension_data(actor)->flags |= ACTOR_TRANSFORM_FLAG_INTERPOLATION_SKIPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_EXPORT void actor_clear_interpolation_skipped(Actor* actor) {
|
||||||
|
get_base_extension_data(actor)->flags &= ~ACTOR_TRANSFORM_FLAG_INTERPOLATION_SKIPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void actor_set_custom_flag_1(Actor* actor) {
|
||||||
|
get_base_extension_data(actor)->flags |= ACTOR_CUSTOM_FLAG_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool actor_get_custom_flag_1(Actor* actor) {
|
||||||
|
return (get_base_extension_data(actor)->flags & ACTOR_CUSTOM_FLAG_1) != 0;
|
||||||
|
}
|
14
patches/actor_funcs.h
Normal file
14
patches/actor_funcs.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef __MEM_FUNCS_H__
|
||||||
|
#define __MEM_FUNCS_H__
|
||||||
|
|
||||||
|
#include "patch_helpers.h"
|
||||||
|
|
||||||
|
DECLARE_FUNC(u32, recomp_register_actor_extension, u32 actor_type, u32 size);
|
||||||
|
DECLARE_FUNC(u32, recomp_register_actor_extension_generic, u32 size);
|
||||||
|
DECLARE_FUNC(void, recomp_clear_all_actor_data);
|
||||||
|
DECLARE_FUNC(u32, recomp_create_actor_data, u32 actor_type);
|
||||||
|
DECLARE_FUNC(void, recomp_destroy_actor_data, u32 actor_handle);
|
||||||
|
DECLARE_FUNC(void*, recomp_get_actor_data, u32 actor_handle, u32 extension_handle, u32 actor_type);
|
||||||
|
DECLARE_FUNC(u32, recomp_get_actor_spawn_index, u32 actor_handle);
|
||||||
|
|
||||||
|
#endif
|
@@ -1,12 +1,16 @@
|
|||||||
#include "patches.h"
|
#include "patches.h"
|
||||||
#include "fault.h"
|
#include "fault.h"
|
||||||
#include "transform_ids.h"
|
#include "transform_ids.h"
|
||||||
|
#include "extended_actors.h"
|
||||||
u16 next_actor_transform = 0;
|
#include "z64actor.h"
|
||||||
|
#include "actor_funcs.h"
|
||||||
|
|
||||||
extern FaultClient sActorFaultClient;
|
extern FaultClient sActorFaultClient;
|
||||||
|
void Actor_Destroy(Actor* actor, PlayState* play);
|
||||||
Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play);
|
Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play);
|
||||||
void ZeldaArena_Free(void* ptr);
|
void ZeldaArena_Free(void* ptr);
|
||||||
|
Actor* Actor_RemoveFromCategory(PlayState* play, ActorContext* actorCtx, Actor* actorToRemove);
|
||||||
|
void Actor_FreeOverlay(ActorOverlay* entry);
|
||||||
|
|
||||||
RECOMP_PATCH void Actor_CleanupContext(ActorContext* actorCtx, PlayState* play) {
|
RECOMP_PATCH void Actor_CleanupContext(ActorContext* actorCtx, PlayState* play) {
|
||||||
s32 i;
|
s32 i;
|
||||||
@@ -33,21 +37,22 @@ RECOMP_PATCH void Actor_CleanupContext(ActorContext* actorCtx, PlayState* play)
|
|||||||
actorCtx->absoluteSpace = NULL;
|
actorCtx->absoluteSpace = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @recomp Reset the actor transform IDs as all actors have been deleted.
|
// @recomp Reset the actor extension data.
|
||||||
next_actor_transform = 0;
|
recomp_clear_all_actor_data();
|
||||||
|
|
||||||
Play_SaveCycleSceneFlags(&play->state);
|
Play_SaveCycleSceneFlags(&play->state);
|
||||||
ActorOverlayTable_Cleanup();
|
ActorOverlayTable_Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 create_actor_transform_id() {
|
RECOMP_DECLARE_EVENT(recomp_should_actor_init(PlayState* play, Actor* actor, bool* should));
|
||||||
u32 ret = next_actor_transform;
|
RECOMP_DECLARE_EVENT(recomp_after_actor_init(PlayState* play, Actor* actor));
|
||||||
next_actor_transform++;
|
RECOMP_DECLARE_EVENT(recomp_should_actor_update(PlayState* play, Actor* actor, bool* should));
|
||||||
|
RECOMP_DECLARE_EVENT(recomp_after_actor_update(PlayState* play, Actor* actor));
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
RECOMP_PATCH void Actor_Init(Actor* actor, PlayState* play) {
|
RECOMP_PATCH void Actor_Init(Actor* actor, PlayState* play) {
|
||||||
|
// @recomp Allocate the actor's extension data.
|
||||||
|
actor_set_slot(actor, recomp_create_actor_data(actor->id));
|
||||||
|
|
||||||
Actor_SetWorldToHome(actor);
|
Actor_SetWorldToHome(actor);
|
||||||
Actor_SetShapeRotToWorld(actor);
|
Actor_SetShapeRotToWorld(actor);
|
||||||
Actor_SetFocus(actor, 0.0f);
|
Actor_SetFocus(actor, 0.0f);
|
||||||
@@ -69,14 +74,160 @@ RECOMP_PATCH void Actor_Init(Actor* actor, PlayState* play) {
|
|||||||
ActorShape_Init(&actor->shape, 0.0f, NULL, 0.0f);
|
ActorShape_Init(&actor->shape, 0.0f, NULL, 0.0f);
|
||||||
if (Object_IsLoaded(&play->objectCtx, actor->objectSlot)) {
|
if (Object_IsLoaded(&play->objectCtx, actor->objectSlot)) {
|
||||||
Actor_SetObjectDependency(play, actor);
|
Actor_SetObjectDependency(play, actor);
|
||||||
actor->init(actor, play);
|
|
||||||
actor->init = NULL;
|
// @recomp Augmented, allowing us to prevent actor init and hook into after init
|
||||||
|
bool shouldInit = true;
|
||||||
|
recomp_should_actor_init(play, actor, &shouldInit);
|
||||||
|
if (shouldInit) {
|
||||||
|
actor->init(actor, play);
|
||||||
|
actor->init = NULL;
|
||||||
|
recomp_after_actor_init(play, actor);
|
||||||
|
} else {
|
||||||
|
actor->init = NULL;
|
||||||
|
Actor_Kill(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RECOMP_PATCH Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) {
|
||||||
|
s32 pad;
|
||||||
|
Player* player = GET_PLAYER(play);
|
||||||
|
Actor* newHead;
|
||||||
|
ActorOverlay* overlayEntry = actor->overlayEntry;
|
||||||
|
|
||||||
|
if ((player != NULL) && (actor == player->lockOnActor)) {
|
||||||
|
Player_Untarget(player);
|
||||||
|
Camera_ChangeMode(Play_GetCamera(play, Play_GetActiveCamId(play)), CAM_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @recomp Pick a transform ID for this actor and encode it into struct padding
|
if (actor == actorCtx->targetCtx.fairyActor) {
|
||||||
u32 cur_transform_id = create_actor_transform_id();
|
actorCtx->targetCtx.fairyActor = NULL;
|
||||||
actorIdByte0(actor) = (cur_transform_id >> 0) & 0xFF;
|
}
|
||||||
actorIdByte1(actor) = (cur_transform_id >> 8) & 0xFF;;
|
|
||||||
|
if (actor == actorCtx->targetCtx.forcedTargetActor) {
|
||||||
|
actorCtx->targetCtx.forcedTargetActor = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor == actorCtx->targetCtx.bgmEnemy) {
|
||||||
|
actorCtx->targetCtx.bgmEnemy = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioSfx_StopByPos(&actor->projectedPos);
|
||||||
|
Actor_Destroy(actor, play);
|
||||||
|
|
||||||
|
newHead = Actor_RemoveFromCategory(play, actorCtx, actor);
|
||||||
|
|
||||||
|
// @recomp Destroy the actor's extension data.
|
||||||
|
recomp_destroy_actor_data(actor_get_slot(actor));
|
||||||
|
|
||||||
|
ZeldaArena_Free(actor);
|
||||||
|
|
||||||
|
if (overlayEntry->vramStart != NULL) {
|
||||||
|
overlayEntry->numLoaded--;
|
||||||
|
Actor_FreeOverlay(overlayEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @recomp Copied from z_actor.c
|
||||||
|
typedef struct {
|
||||||
|
/* 0x00 */ PlayState* play;
|
||||||
|
/* 0x04 */ Actor* actor;
|
||||||
|
/* 0x08 */ u32 freezeExceptionFlag;
|
||||||
|
/* 0x0C */ u32 canFreezeCategory;
|
||||||
|
/* 0x10 */ Actor* talkActor;
|
||||||
|
/* 0x14 */ Player* player;
|
||||||
|
/* 0x18 */ u32 updateActorFlagsMask; // Actor will update only if at least 1 actor flag is set in this bitmask
|
||||||
|
} UpdateActor_Params; // size = 0x1C
|
||||||
|
|
||||||
|
RECOMP_PATCH Actor* Actor_UpdateActor(UpdateActor_Params* params) {
|
||||||
|
PlayState* play = params->play;
|
||||||
|
Actor* actor = params->actor;
|
||||||
|
Actor* nextActor;
|
||||||
|
|
||||||
|
if (actor->world.pos.y < -25000.0f) {
|
||||||
|
actor->world.pos.y = -25000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor->sfxId = 0;
|
||||||
|
actor->audioFlags &= ~(((1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | (1 << 0)) | ((1 << 6) | (1 << 5))); // ACTOR_AUDIO_FLAG_ALL
|
||||||
|
|
||||||
|
if (actor->init != NULL) {
|
||||||
|
if (Object_IsLoaded(&play->objectCtx, actor->objectSlot)) {
|
||||||
|
Actor_SetObjectDependency(play, actor);
|
||||||
|
|
||||||
|
// @recomp Augmented, allowing us to prevent actor init and hook into after init
|
||||||
|
bool shouldInit = true;
|
||||||
|
recomp_should_actor_init(play, actor, &shouldInit);
|
||||||
|
if (shouldInit) {
|
||||||
|
actor->init(actor, play);
|
||||||
|
actor->init = NULL;
|
||||||
|
recomp_after_actor_init(play, actor);
|
||||||
|
} else {
|
||||||
|
actor->init = NULL;
|
||||||
|
Actor_Kill(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextActor = actor->next;
|
||||||
|
} else if (actor->update == NULL) {
|
||||||
|
if (!actor->isDrawn) {
|
||||||
|
nextActor = Actor_Delete(&play->actorCtx, actor, play);
|
||||||
|
} else {
|
||||||
|
Actor_Destroy(actor, play);
|
||||||
|
nextActor = actor->next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!Object_IsLoaded(&play->objectCtx, actor->objectSlot)) {
|
||||||
|
Actor_Kill(actor);
|
||||||
|
} else if (((params->freezeExceptionFlag != 0) && !(actor->flags & params->freezeExceptionFlag)) ||
|
||||||
|
(((!params->freezeExceptionFlag) != 0) &&
|
||||||
|
(!(actor->flags & (1 << 20)) ||
|
||||||
|
((actor->category == ACTORCAT_EXPLOSIVES) && (params->player->stateFlags1 & PLAYER_STATE1_200))) &&
|
||||||
|
params->canFreezeCategory && (actor != params->talkActor) && (actor != params->player->heldActor) &&
|
||||||
|
(actor->parent != ¶ms->player->actor))) {
|
||||||
|
CollisionCheck_ResetDamage(&actor->colChkInfo);
|
||||||
|
} else {
|
||||||
|
Math_Vec3f_Copy(&actor->prevPos, &actor->world.pos);
|
||||||
|
actor->xzDistToPlayer = Actor_WorldDistXZToActor(actor, ¶ms->player->actor);
|
||||||
|
actor->playerHeightRel = Actor_HeightDiff(actor, ¶ms->player->actor);
|
||||||
|
actor->xyzDistToPlayerSq = SQ(actor->xzDistToPlayer) + SQ(actor->playerHeightRel);
|
||||||
|
|
||||||
|
actor->yawTowardsPlayer = Actor_WorldYawTowardActor(actor, ¶ms->player->actor);
|
||||||
|
actor->flags &= ~(1 << 24);
|
||||||
|
|
||||||
|
if ((DECR(actor->freezeTimer) == 0) && (actor->flags & params->updateActorFlagsMask)) {
|
||||||
|
if (actor == params->player->lockOnActor) {
|
||||||
|
actor->isLockedOn = true;
|
||||||
|
} else {
|
||||||
|
actor->isLockedOn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((actor->targetPriority != 0) && (params->player->lockOnActor == NULL)) {
|
||||||
|
actor->targetPriority = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor_SetObjectDependency(play, actor);
|
||||||
|
|
||||||
|
if (actor->colorFilterTimer != 0) {
|
||||||
|
actor->colorFilterTimer--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @recomp Augmented, allowing us to prevent actor update and hook into after update
|
||||||
|
bool shouldUpdate = true;
|
||||||
|
recomp_should_actor_update(play, actor, &shouldUpdate);
|
||||||
|
if (shouldUpdate) {
|
||||||
|
actor->update(actor, play);
|
||||||
|
recomp_after_actor_update(play, actor);
|
||||||
|
}
|
||||||
|
DynaPoly_UnsetAllInteractFlags(play, &play->colCtx.dyna, actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionCheck_ResetDamage(&actor->colChkInfo);
|
||||||
|
}
|
||||||
|
nextActor = actor->next;
|
||||||
|
}
|
||||||
|
return nextActor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the transform ID for this actor, add the limb index and write that as the matrix group to POLY_OPA_DISP.
|
// Extract the transform ID for this actor, add the limb index and write that as the matrix group to POLY_OPA_DISP.
|
||||||
@@ -1285,3 +1436,10 @@ RECOMP_PATCH void Actor_Draw(PlayState* play, Actor* actor) {
|
|||||||
|
|
||||||
CLOSE_DISPS(play->state.gfxCtx);
|
CLOSE_DISPS(play->state.gfxCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ActorExtensionId z64recomp_extend_actor(s16 actor_id, u32 size);
|
||||||
|
ActorExtensionId z64recomp_extend_actor_all(u32 size);
|
||||||
|
|
||||||
|
void* z64recomp_get_extended_actor_data(Actor* actor, ActorExtensionId extension);
|
||||||
|
u32 z64recomp_get_actor_spawn_index(Actor* actor);
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 955 B |
14
patches/extended_actors.h
Normal file
14
patches/extended_actors.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef __EXTENDED_ACTORS_H__
|
||||||
|
#define __EXTENDED_ACTORS_H__
|
||||||
|
|
||||||
|
#include "global.h"
|
||||||
|
|
||||||
|
typedef u32 ActorExtensionId;
|
||||||
|
|
||||||
|
ActorExtensionId z64recomp_extend_actor(s16 actor_id, u32 size);
|
||||||
|
ActorExtensionId z64recomp_extend_actor_all(u32 size);
|
||||||
|
|
||||||
|
void* z64recomp_get_extended_actor_data(Actor* actor, ActorExtensionId extension);
|
||||||
|
u32 z64recomp_get_actor_spawn_index(Actor* actor);
|
||||||
|
|
||||||
|
#endif
|
14
patches/gamestate_patches.c
Normal file
14
patches/gamestate_patches.c
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include "patches.h"
|
||||||
|
#include "ui_funcs.h"
|
||||||
|
|
||||||
|
// @recomp Patched to run UI callbacks.
|
||||||
|
RECOMP_PATCH void Graph_UpdateGame(GameState* gameState) {
|
||||||
|
recomp_run_ui_callbacks();
|
||||||
|
|
||||||
|
GameState_GetInput(gameState);
|
||||||
|
GameState_IncrementFrameCount(gameState);
|
||||||
|
if (SREG(20) < 3) {
|
||||||
|
Audio_Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1637
patches/input.c
1637
patches/input.c
File diff suppressed because it is too large
Load Diff
42
patches/memory_patches.c
Normal file
42
patches/memory_patches.c
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include "patches.h"
|
||||||
|
|
||||||
|
// @recomp Leave the entire KSEG0 range unmodified when translating to a virtual address. This will allow
|
||||||
|
// using the entirety of the extended RAM address space for custom assets.
|
||||||
|
RECOMP_PATCH void* Lib_SegmentedToVirtual(void* ptr) {
|
||||||
|
if (IS_KSEG0(ptr)) {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return SEGMENTED_TO_K0(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationEntry* AnimationContext_AddEntry(AnimationContext* animationCtx, AnimationType type);
|
||||||
|
#define LINK_ANIMETION_OFFSET(addr, offset) \
|
||||||
|
(SEGMENT_ROM_START(link_animetion) + ((uintptr_t)addr & 0xFFFFFF) + ((u32)offset))
|
||||||
|
|
||||||
|
// @recomp Skip the DMA if the animation is already in RAM. Allows mods to play custom animations.
|
||||||
|
RECOMP_PATCH void AnimationContext_SetLoadFrame(PlayState* play, PlayerAnimationHeader* animation, s32 frame, s32 limbCount,
|
||||||
|
Vec3s* frameTable) {
|
||||||
|
AnimationEntry* task = AnimationContext_AddEntry(&play->animationCtx, ANIMATION_LINKANIMETION);
|
||||||
|
|
||||||
|
if (task != NULL) {
|
||||||
|
PlayerAnimationHeader* playerAnimHeader = Lib_SegmentedToVirtual(animation);
|
||||||
|
s32 pad;
|
||||||
|
|
||||||
|
osCreateMesgQueue(&task->data.load.msgQueue, task->data.load.msg,
|
||||||
|
ARRAY_COUNT(task->data.load.msg));
|
||||||
|
|
||||||
|
if (IS_KSEG0(playerAnimHeader->linkAnimSegment)) {
|
||||||
|
osSendMesg(&task->data.load.msgQueue, NULL, OS_MESG_NOBLOCK);
|
||||||
|
Lib_MemCpy(frameTable, ((u8*)playerAnimHeader->segmentVoid) + (sizeof(Vec3s) * limbCount + sizeof(s16)) * frame,
|
||||||
|
sizeof(Vec3s) * limbCount + sizeof(s16));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DmaMgr_SendRequestImpl(
|
||||||
|
&task->data.load.req, frameTable,
|
||||||
|
LINK_ANIMETION_OFFSET(playerAnimHeader->linkAnimSegment, (sizeof(Vec3s) * limbCount + sizeof(s16)) * frame),
|
||||||
|
sizeof(Vec3s) * limbCount + sizeof(s16), 0, &task->data.load.msgQueue, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -92,9 +92,6 @@ typedef enum {
|
|||||||
"\t.popsection\n"); \
|
"\t.popsection\n"); \
|
||||||
extern u8 identifier[]
|
extern u8 identifier[]
|
||||||
|
|
||||||
void draw_dpad(PlayState* play);
|
|
||||||
void draw_dpad_icons(PlayState* play);
|
|
||||||
|
|
||||||
void View_ApplyInterpolate(View* view, s32 mask, bool reset_interpolation_state);
|
void View_ApplyInterpolate(View* view, s32 mask, bool reset_interpolation_state);
|
||||||
|
|
||||||
void set_camera_skipped(bool skipped);
|
void set_camera_skipped(bool skipped);
|
||||||
|
@@ -179,7 +179,7 @@ RECOMP_PATCH void Play_Init(GameState* thisx) {
|
|||||||
if (CHECK_EVENTINF(EVENTINF_TRIGGER_DAYTELOP)) {
|
if (CHECK_EVENTINF(EVENTINF_TRIGGER_DAYTELOP)) {
|
||||||
CLEAR_EVENTINF(EVENTINF_TRIGGER_DAYTELOP);
|
CLEAR_EVENTINF(EVENTINF_TRIGGER_DAYTELOP);
|
||||||
STOP_GAMESTATE(&this->state);
|
STOP_GAMESTATE(&this->state);
|
||||||
// Use non-relocatable reference to DayTelop_Init instead.
|
// @recomp Use non-relocatable reference to DayTelop_Init instead.
|
||||||
SET_NEXT_GAMESTATE(&this->state, DayTelop_Init_NORELOCATE, sizeof(DayTelopState));
|
SET_NEXT_GAMESTATE(&this->state, DayTelop_Init_NORELOCATE, sizeof(DayTelopState));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ RECOMP_PATCH void Play_Init(GameState* thisx) {
|
|||||||
if (gSaveContext.save.entrance == -1) {
|
if (gSaveContext.save.entrance == -1) {
|
||||||
gSaveContext.save.entrance = 0;
|
gSaveContext.save.entrance = 0;
|
||||||
STOP_GAMESTATE(&this->state);
|
STOP_GAMESTATE(&this->state);
|
||||||
// Use non-relocatable reference to TitleSetup_Init instead.
|
// @recomp Use non-relocatable reference to TitleSetup_Init instead.
|
||||||
SET_NEXT_GAMESTATE(&this->state, TitleSetup_Init_NORELOCATE, sizeof(TitleSetupState));
|
SET_NEXT_GAMESTATE(&this->state, TitleSetup_Init_NORELOCATE, sizeof(TitleSetupState));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
52
patches/recompui_event_structs.h
Normal file
52
patches/recompui_event_structs.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#ifndef __UI_FUNCS_H__
|
||||||
|
#define __UI_FUNCS_H__
|
||||||
|
|
||||||
|
// These two enums must be kept in sync with src/ui/elements/ui_types.h!
|
||||||
|
typedef enum {
|
||||||
|
UI_EVENT_NONE,
|
||||||
|
UI_EVENT_CLICK,
|
||||||
|
UI_EVENT_FOCUS,
|
||||||
|
UI_EVENT_HOVER,
|
||||||
|
UI_EVENT_ENABLE,
|
||||||
|
UI_EVENT_DRAG,
|
||||||
|
UI_EVENT_RESERVED1, // Would be UI_EVENT_TEXT but text events aren't usable in mods currently
|
||||||
|
UI_EVENT_UPDATE,
|
||||||
|
UI_EVENT_COUNT
|
||||||
|
} RecompuiEventType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UI_DRAG_NONE,
|
||||||
|
UI_DRAG_START,
|
||||||
|
UI_DRAG_MOVE,
|
||||||
|
UI_DRAG_END
|
||||||
|
} RecompuiDragPhase;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
RecompuiEventType type;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
} click;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool active;
|
||||||
|
} focus;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool active;
|
||||||
|
} hover;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool active;
|
||||||
|
} enable;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
RecompuiDragPhase phase;
|
||||||
|
} drag;
|
||||||
|
} data;
|
||||||
|
} RecompuiEventData;
|
||||||
|
|
||||||
|
#endif
|
@@ -1,5 +1,6 @@
|
|||||||
#include "patches.h"
|
#include "patches.h"
|
||||||
#include "misc_funcs.h"
|
#include "misc_funcs.h"
|
||||||
|
#include "transform_ids.h"
|
||||||
#include "loadfragment.h"
|
#include "loadfragment.h"
|
||||||
|
|
||||||
void Main_ClearMemory(void* begin, void* end);
|
void Main_ClearMemory(void* begin, void* end);
|
||||||
@@ -16,6 +17,9 @@ RECOMP_PATCH void Main_Init(void) {
|
|||||||
OSMesg msg[1];
|
OSMesg msg[1];
|
||||||
size_t prevSize;
|
size_t prevSize;
|
||||||
|
|
||||||
|
// @recomp Register base actor extensions.
|
||||||
|
register_base_actor_extensions();
|
||||||
|
|
||||||
// @recomp_event recomp_on_init(): Allow mods to initialize themselves once.
|
// @recomp_event recomp_on_init(): Allow mods to initialize themselves once.
|
||||||
recomp_on_init();
|
recomp_on_init();
|
||||||
|
|
||||||
|
@@ -2,10 +2,14 @@
|
|||||||
#include "sys_flashrom.h"
|
#include "sys_flashrom.h"
|
||||||
#include "PR/os_internal_flash.h"
|
#include "PR/os_internal_flash.h"
|
||||||
#include "fault.h"
|
#include "fault.h"
|
||||||
|
#include "overlays/gamestates/ovl_file_choose/z_file_select.h"
|
||||||
|
#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
|
||||||
|
|
||||||
extern OSMesgQueue sFlashromMesgQueue;
|
extern OSMesgQueue sFlashromMesgQueue;
|
||||||
s32 SysFlashrom_IsInit(void);
|
s32 SysFlashrom_IsInit(void);
|
||||||
void Sleep_Msec(u32 ms);
|
void Sleep_Msec(u32 ms);
|
||||||
|
extern u16 D_801F6AF0;
|
||||||
|
extern u8 D_801F6AF2;
|
||||||
|
|
||||||
// @recomp Patched to not wait a hardcoded amount of time for the save to complete.
|
// @recomp Patched to not wait a hardcoded amount of time for the save to complete.
|
||||||
RECOMP_PATCH void Sram_UpdateWriteToFlashDefault(SramContext* sramCtx) {
|
RECOMP_PATCH void Sram_UpdateWriteToFlashDefault(SramContext* sramCtx) {
|
||||||
@@ -56,3 +60,72 @@ RECOMP_PATCH void Sram_UpdateWriteToFlashOwlSave(SramContext* sramCtx) {
|
|||||||
Lib_MemCpy(&gSaveContext, sramCtx->saveBuf, offsetof(SaveContext, fileNum));
|
Lib_MemCpy(&gSaveContext, sramCtx->saveBuf, offsetof(SaveContext, fileNum));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RECOMP_DECLARE_EVENT(recomp_after_init_save(FileSelectState* fileSelect, SramContext* sramCtx));
|
||||||
|
|
||||||
|
// @recomp Patched to expose recomp_on_save_init event
|
||||||
|
RECOMP_PATCH void Sram_InitSave(FileSelectState* fileSelect2, SramContext* sramCtx) {
|
||||||
|
s32 phi_v0;
|
||||||
|
u16 i;
|
||||||
|
FileSelectState* fileSelect = fileSelect2;
|
||||||
|
s16 maskCount;
|
||||||
|
|
||||||
|
if (gSaveContext.flashSaveAvailable) {
|
||||||
|
Sram_InitNewSave();
|
||||||
|
if (fileSelect->buttonIndex == 0) {
|
||||||
|
gSaveContext.save.cutsceneIndex = 0xFFF0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (phi_v0 = 0; phi_v0 < ARRAY_COUNT(gSaveContext.save.saveInfo.playerData.playerName); phi_v0++) {
|
||||||
|
gSaveContext.save.saveInfo.playerData.playerName[phi_v0] =
|
||||||
|
fileSelect->fileNames[fileSelect->buttonIndex][phi_v0];
|
||||||
|
}
|
||||||
|
|
||||||
|
gSaveContext.save.saveInfo.playerData.newf[0] = 'Z';
|
||||||
|
gSaveContext.save.saveInfo.playerData.newf[1] = 'E';
|
||||||
|
gSaveContext.save.saveInfo.playerData.newf[2] = 'L';
|
||||||
|
gSaveContext.save.saveInfo.playerData.newf[3] = 'D';
|
||||||
|
gSaveContext.save.saveInfo.playerData.newf[4] = 'A';
|
||||||
|
gSaveContext.save.saveInfo.playerData.newf[5] = '3';
|
||||||
|
|
||||||
|
recomp_after_init_save(fileSelect, sramCtx);
|
||||||
|
|
||||||
|
gSaveContext.save.saveInfo.checksum = Sram_CalcChecksum(&gSaveContext.save, sizeof(Save));
|
||||||
|
|
||||||
|
Lib_MemCpy(sramCtx->saveBuf, &gSaveContext.save, sizeof(Save));
|
||||||
|
Lib_MemCpy(&sramCtx->saveBuf[0x2000], &gSaveContext.save, sizeof(Save));
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_COUNT(gSaveContext.save.saveInfo.playerData.newf); i++) {
|
||||||
|
fileSelect->newf[fileSelect->buttonIndex][i] = gSaveContext.save.saveInfo.playerData.newf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSelect->threeDayResetCount[fileSelect->buttonIndex] =
|
||||||
|
gSaveContext.save.saveInfo.playerData.threeDayResetCount;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_COUNT(gSaveContext.save.saveInfo.playerData.playerName); i++) {
|
||||||
|
fileSelect->fileNames[fileSelect->buttonIndex][i] = gSaveContext.save.saveInfo.playerData.playerName[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSelect->healthCapacity[fileSelect->buttonIndex] = gSaveContext.save.saveInfo.playerData.healthCapacity;
|
||||||
|
fileSelect->health[fileSelect->buttonIndex] = gSaveContext.save.saveInfo.playerData.health;
|
||||||
|
fileSelect->defenseHearts[fileSelect->buttonIndex] = gSaveContext.save.saveInfo.inventory.defenseHearts;
|
||||||
|
fileSelect->questItems[fileSelect->buttonIndex] = gSaveContext.save.saveInfo.inventory.questItems;
|
||||||
|
fileSelect->time[fileSelect->buttonIndex] = CURRENT_TIME;
|
||||||
|
fileSelect->day[fileSelect->buttonIndex] = gSaveContext.save.day;
|
||||||
|
fileSelect->isOwlSave[fileSelect->buttonIndex] = gSaveContext.save.isOwlSave;
|
||||||
|
fileSelect->rupees[fileSelect->buttonIndex] = gSaveContext.save.saveInfo.playerData.rupees;
|
||||||
|
fileSelect->walletUpgrades[fileSelect->buttonIndex] = CUR_UPG_VALUE(UPG_WALLET);
|
||||||
|
|
||||||
|
for (i = 0, maskCount = 0; i < MASK_NUM_SLOTS; i++) {
|
||||||
|
if (gSaveContext.save.saveInfo.inventory.items[i + ITEM_NUM_SLOTS] != ITEM_NONE) {
|
||||||
|
maskCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSelect->maskCount[fileSelect->buttonIndex] = maskCount;
|
||||||
|
fileSelect->heartPieceCount[fileSelect->buttonIndex] = GET_QUEST_HEART_PIECE_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
gSaveContext.save.time = D_801F6AF0;
|
||||||
|
gSaveContext.flashSaveAvailable = D_801F6AF2;
|
||||||
|
}
|
||||||
|
@@ -45,3 +45,11 @@ recomp_high_precision_fb_enabled = 0x8F0000A8;
|
|||||||
recomp_get_resolution_scale = 0x8F0000AC;
|
recomp_get_resolution_scale = 0x8F0000AC;
|
||||||
recomp_get_analog_inverted_axes = 0x8F0000B0;
|
recomp_get_analog_inverted_axes = 0x8F0000B0;
|
||||||
recomp_get_window_resolution = 0x8F0000B4;
|
recomp_get_window_resolution = 0x8F0000B4;
|
||||||
|
recomp_run_ui_callbacks = 0x8F0000B8;
|
||||||
|
recomp_register_actor_extension = 0x8F0000BC;
|
||||||
|
recomp_register_actor_extension_generic = 0x8F0000C0;
|
||||||
|
recomp_clear_all_actor_data = 0x8F0000C4;
|
||||||
|
recomp_create_actor_data = 0x8F0000C8;
|
||||||
|
recomp_destroy_actor_data = 0x8F0000CC;
|
||||||
|
recomp_get_actor_data = 0x8F0000D0;
|
||||||
|
recomp_get_actor_spawn_index = 0x8F0000D4;
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
#ifndef __TRANSFORM_IDS_H__
|
#ifndef __TRANSFORM_IDS_H__
|
||||||
#define __TRANSFORM_IDS_H__
|
#define __TRANSFORM_IDS_H__
|
||||||
|
|
||||||
|
#include "extended_actors.h"
|
||||||
|
|
||||||
#define CAMERA_TRANSFORM_ID 0x10U
|
#define CAMERA_TRANSFORM_ID 0x10U
|
||||||
#define CIRCLE_OVERLAY_TRANSFORM_ID 0x11U
|
#define CIRCLE_OVERLAY_TRANSFORM_ID 0x11U
|
||||||
#define CIRCLE_OVERLAY_TRANSFORM_PROJECTION_ID 0x12U
|
#define CIRCLE_OVERLAY_TRANSFORM_PROJECTION_ID 0x12U
|
||||||
@@ -40,55 +42,18 @@
|
|||||||
#define ACTOR_TRANSFORM_ID_COUNT (ACTOR_TRANSFORM_LIMB_COUNT * 2) // One ID for each limb and another for each post-draw
|
#define ACTOR_TRANSFORM_ID_COUNT (ACTOR_TRANSFORM_LIMB_COUNT * 2) // One ID for each limb and another for each post-draw
|
||||||
#define ACTOR_TRANSFORM_ID_START 0x1000000U
|
#define ACTOR_TRANSFORM_ID_START 0x1000000U
|
||||||
|
|
||||||
// Use 16 bits of compiler-inserted padding to hold the actor's transform ID.
|
u32 actor_transform_id(Actor* actor);
|
||||||
// 0x22 between halfDaysBits and world
|
u32 actor_get_interpolation_skipped(Actor* actor);
|
||||||
#define actorIdByte0(actor) ((u8*)(actor))[0x22]
|
void actor_set_interpolation_skipped(Actor* actor);
|
||||||
// 0x23 between halfDaysBits and world
|
void actor_clear_interpolation_skipped(Actor* actor);
|
||||||
#define actorIdByte1(actor) ((u8*)(actor))[0x23]
|
void actor_set_custom_flag_1(Actor* actor);
|
||||||
// 0x3A between audioFlags and focus
|
bool actor_get_custom_flag_1(Actor* actor);
|
||||||
#define actorIdByte2(actor) ((u8*)(actor))[0x3A]
|
|
||||||
|
|
||||||
// Other unused padding:
|
|
||||||
// 0x3B between audioFlags and focus
|
|
||||||
|
|
||||||
static inline u32 actor_transform_id(Actor* actor) {
|
|
||||||
u32 actor_id =
|
|
||||||
(actorIdByte0(actor) << 0) |
|
|
||||||
(actorIdByte1(actor) << 8);
|
|
||||||
|
|
||||||
return (actor_id * ACTOR_TRANSFORM_ID_COUNT) + ACTOR_TRANSFORM_ID_START;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
ACTOR_TRANSFORM_FLAG_INTERPOLATION_SKIPPED = 1 << 0,
|
|
||||||
ACTOR_CUSTOM_FLAG_1 = 1 << 1,
|
|
||||||
} CustomActorFlags;
|
|
||||||
|
|
||||||
static inline u32 actor_get_interpolation_skipped(Actor* actor) {
|
|
||||||
return (actorIdByte2(actor) & ACTOR_TRANSFORM_FLAG_INTERPOLATION_SKIPPED) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void actor_set_interpolation_skipped(Actor* actor) {
|
|
||||||
actorIdByte2(actor) |= ACTOR_TRANSFORM_FLAG_INTERPOLATION_SKIPPED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void actor_clear_interpolation_skipped(Actor* actor) {
|
|
||||||
actorIdByte2(actor) &= ~ACTOR_TRANSFORM_FLAG_INTERPOLATION_SKIPPED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void actor_set_custom_flag_1(Actor* actor) {
|
|
||||||
actorIdByte2(actor) |= ACTOR_CUSTOM_FLAG_1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void actor_clear_custom_flag_1(Actor* actor) {
|
|
||||||
actorIdByte2(actor) &= ~ACTOR_CUSTOM_FLAG_1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool actor_get_custom_flag_1(Actor* actor) {
|
|
||||||
return (actorIdByte2(actor) & ACTOR_CUSTOM_FLAG_1) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void force_camera_interpolation();
|
void force_camera_interpolation();
|
||||||
void force_camera_skip_interpolation();
|
void force_camera_skip_interpolation();
|
||||||
|
|
||||||
|
ActorExtensionId actor_get_slot(Actor* actor);
|
||||||
|
void actor_set_slot(Actor* actor, ActorExtensionId);
|
||||||
|
|
||||||
|
void register_base_actor_extensions();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
9
patches/ui_funcs.h
Normal file
9
patches/ui_funcs.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef __UI_FUNCS_INTERNAL_H__
|
||||||
|
#define __UI_FUNCS_INTERNAL_H__
|
||||||
|
|
||||||
|
#include "patch_helpers.h"
|
||||||
|
#include "recompui_event_structs.h"
|
||||||
|
|
||||||
|
DECLARE_FUNC(void, recomp_run_ui_callbacks);
|
||||||
|
|
||||||
|
#endif
|
@@ -487,10 +487,8 @@ RECOMP_PATCH void Interface_Draw(PlayState* play) {
|
|||||||
|
|
||||||
Magic_DrawMeter(play);
|
Magic_DrawMeter(play);
|
||||||
|
|
||||||
// @recomp Draw the D-Pad and its item icons as well as the autosave icon if the game is unpaused.
|
// @recomp Draw the autosave icon if the game is unpaused.
|
||||||
if (pauseCtx->state != PAUSE_STATE_MAIN) {
|
if (pauseCtx->state != PAUSE_STATE_MAIN) {
|
||||||
draw_dpad(play);
|
|
||||||
draw_dpad_icons(play);
|
|
||||||
draw_autosave_icon(play);
|
draw_autosave_icon(play);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,6 +42,10 @@ static struct {
|
|||||||
bool rumble_active;
|
bool rumble_active;
|
||||||
} InputState;
|
} InputState;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
std::list<std::filesystem::path> files_dropped;
|
||||||
|
} DropState;
|
||||||
|
|
||||||
std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT;
|
std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT;
|
||||||
std::atomic<recomp::InputField> scanned_input;
|
std::atomic<recomp::InputField> scanned_input;
|
||||||
|
|
||||||
@@ -103,7 +107,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
|||||||
SDL_KeyboardEvent* keyevent = &event->key;
|
SDL_KeyboardEvent* keyevent = &event->key;
|
||||||
|
|
||||||
// Skip repeated events when not in the menu
|
// Skip repeated events when not in the menu
|
||||||
if (recompui::get_current_menu() == recompui::Menu::None &&
|
if (!recompui::is_context_capturing_input() &&
|
||||||
event->key.repeat) {
|
event->key.repeat) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -156,10 +160,6 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recompui::get_current_menu() != recompui::Menu::Config) {
|
|
||||||
recompui::set_current_menu(recompui::Menu::Config);
|
|
||||||
}
|
|
||||||
|
|
||||||
zelda64::open_quit_game_prompt();
|
zelda64::open_quit_game_prompt();
|
||||||
recompui::activate_mouse();
|
recompui::activate_mouse();
|
||||||
break;
|
break;
|
||||||
@@ -275,6 +275,18 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
|||||||
InputState.pending_mouse_delta[0] += motion_event->xrel;
|
InputState.pending_mouse_delta[0] += motion_event->xrel;
|
||||||
InputState.pending_mouse_delta[1] += motion_event->yrel;
|
InputState.pending_mouse_delta[1] += motion_event->yrel;
|
||||||
}
|
}
|
||||||
|
queue_if_enabled(event);
|
||||||
|
break;
|
||||||
|
case SDL_EventType::SDL_DROPBEGIN:
|
||||||
|
DropState.files_dropped.clear();
|
||||||
|
break;
|
||||||
|
case SDL_EventType::SDL_DROPFILE:
|
||||||
|
DropState.files_dropped.emplace_back(std::filesystem::path(std::u8string_view((const char8_t *)(event->drop.file))));
|
||||||
|
SDL_free(event->drop.file);
|
||||||
|
break;
|
||||||
|
case SDL_EventType::SDL_DROPCOMPLETE:
|
||||||
|
recompui::drop_files(DropState.files_dropped);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
queue_if_enabled(event);
|
queue_if_enabled(event);
|
||||||
break;
|
break;
|
||||||
@@ -284,6 +296,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
|||||||
|
|
||||||
void recomp::handle_events() {
|
void recomp::handle_events() {
|
||||||
SDL_Event cur_event;
|
SDL_Event cur_event;
|
||||||
|
static bool started = false;
|
||||||
static bool exited = false;
|
static bool exited = false;
|
||||||
while (SDL_PollEvent(&cur_event) && !exited) {
|
while (SDL_PollEvent(&cur_event) && !exited) {
|
||||||
exited = sdl_event_filter(nullptr, &cur_event);
|
exited = sdl_event_filter(nullptr, &cur_event);
|
||||||
@@ -300,6 +313,11 @@ void recomp::handle_events() {
|
|||||||
SDL_ShowCursor(cursor_visible ? SDL_ENABLE : SDL_DISABLE);
|
SDL_ShowCursor(cursor_visible ? SDL_ENABLE : SDL_DISABLE);
|
||||||
SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE);
|
SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!started && ultramodern::is_game_started()) {
|
||||||
|
started = true;
|
||||||
|
recompui::process_game_started();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A;
|
constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A;
|
||||||
@@ -711,8 +729,8 @@ void recomp::set_right_analog_suppressed(bool suppressed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool recomp::game_input_disabled() {
|
bool recomp::game_input_disabled() {
|
||||||
// Disable input if any menu is open.
|
// Disable input if any menu that blocks input is open.
|
||||||
return recompui::get_current_menu() != recompui::Menu::None;
|
return recompui::is_context_capturing_input();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool recomp::all_input_disabled() {
|
bool recomp::all_input_disabled() {
|
||||||
|
237
src/game/recomp_actor_api.cpp
Normal file
237
src/game/recomp_actor_api.cpp
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "slot_map.h"
|
||||||
|
|
||||||
|
#include "librecomp/helpers.hpp"
|
||||||
|
#include "librecomp/addresses.hpp"
|
||||||
|
#include "ultramodern/error_handling.hpp"
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include "recomp_data.h"
|
||||||
|
#include "../patches/actor_funcs.h"
|
||||||
|
|
||||||
|
struct ExtensionInfo {
|
||||||
|
// Either the actor's type ID, or 0xFFFFFFFF if this is for generic data.
|
||||||
|
uint32_t actor_type;
|
||||||
|
// The offset from either the start of the actor's data or the start of the actor's specific extension data depending on the value of actor_type.
|
||||||
|
uint32_t data_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExtensionData {
|
||||||
|
uint32_t actor_spawn_index;
|
||||||
|
PTR(void) data_addr;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex actor_data_mutex{};
|
||||||
|
// The total size of actor-specific extension data for each actor type.
|
||||||
|
std::vector<uint32_t> actor_data_sizes{};
|
||||||
|
// The total size of all generic actor extension data.
|
||||||
|
uint32_t generic_data_size;
|
||||||
|
// The registered actor extensions.
|
||||||
|
std::vector<ExtensionInfo> actor_extensions{};
|
||||||
|
// The extension data for every actor.
|
||||||
|
using actor_data_map_t = dod::slot_map32<ExtensionData>;
|
||||||
|
actor_data_map_t actor_data{};
|
||||||
|
// The number of actors spawned since the last reset.
|
||||||
|
uint32_t actor_spawn_count = 0;
|
||||||
|
// Whether or not extensions can be registered at this time.
|
||||||
|
bool can_register = false;
|
||||||
|
|
||||||
|
// Debug counters.
|
||||||
|
size_t alloc_count = 0;
|
||||||
|
size_t free_count = 0;
|
||||||
|
|
||||||
|
void recomputil::init_extended_actor_data() {
|
||||||
|
std::lock_guard lock{ actor_data_mutex };
|
||||||
|
|
||||||
|
actor_data_sizes.clear();
|
||||||
|
generic_data_size = 0;
|
||||||
|
actor_extensions.clear();
|
||||||
|
actor_data.reset();
|
||||||
|
actor_spawn_count = 0;
|
||||||
|
can_register = true;
|
||||||
|
// Create a dummy extension so the first extension handle is nonzero, should help catch bugs.
|
||||||
|
actor_extensions.push_back({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil::reset_actor_data() {
|
||||||
|
std::lock_guard lock{ actor_data_mutex };
|
||||||
|
actor_data.reset();
|
||||||
|
actor_spawn_count = 0;
|
||||||
|
|
||||||
|
assert(alloc_count == free_count);
|
||||||
|
alloc_count = 0;
|
||||||
|
free_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint32_t round_up_16(uint32_t value) {
|
||||||
|
return (value + 15) & (~15);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_register_actor_extension(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
u32 actor_type = _arg<0, u32>(rdram, ctx);
|
||||||
|
u32 size = _arg<1, u32>(rdram, ctx);
|
||||||
|
|
||||||
|
if (!can_register) {
|
||||||
|
recompui::message_box("Fatal error in mod - attempted to register actor extension data after actors have been spawned.");
|
||||||
|
assert(false);
|
||||||
|
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor_data_sizes.size() <= actor_type) {
|
||||||
|
actor_data_sizes.resize(2 * actor_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase the actor type's extension data size by the provided size (rounded up to a multiple of 16).
|
||||||
|
uint32_t data_offset = actor_data_sizes[actor_type];
|
||||||
|
actor_data_sizes[actor_type] += round_up_16(size);
|
||||||
|
|
||||||
|
// Register the extension.
|
||||||
|
uint32_t ret = static_cast<uint32_t>(actor_extensions.size());
|
||||||
|
actor_extensions.emplace_back(ExtensionInfo{.actor_type = actor_type, .data_offset = data_offset});
|
||||||
|
|
||||||
|
// printf("Registered actor extension data for type %u (size 0x%08X, offset 0x%08X)\n", actor_type, size, data_offset);
|
||||||
|
|
||||||
|
_return<u32>(ctx, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_register_actor_extension_generic(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
u32 size = _arg<0, u32>(rdram, ctx);
|
||||||
|
|
||||||
|
// Increase the generic extension data size by the provided size (rounded up to a multiple of 16).
|
||||||
|
uint32_t data_offset = generic_data_size;
|
||||||
|
generic_data_size += round_up_16(size);
|
||||||
|
|
||||||
|
// Register the extension.
|
||||||
|
uint32_t ret = static_cast<uint32_t>(actor_extensions.size());
|
||||||
|
actor_extensions.emplace_back(ExtensionInfo{.actor_type = 0xFFFFFFFFU, .data_offset = data_offset});
|
||||||
|
|
||||||
|
// printf("Registered generic actor extension data (size 0x%08X, offset 0x%08X)\n", size, data_offset);
|
||||||
|
_return<u32>(ctx, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_clear_all_actor_data(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
(void)rdram;
|
||||||
|
(void)ctx;
|
||||||
|
recomputil::reset_actor_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_create_actor_data(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
std::lock_guard lock{ actor_data_mutex };
|
||||||
|
|
||||||
|
can_register = false;
|
||||||
|
|
||||||
|
// Determine the number of bytes to allocate based on the actor type's extensions and the generic extensions.
|
||||||
|
u32 actor_type = _arg<0, u32>(rdram, ctx);
|
||||||
|
u32 alloc_size = generic_data_size;
|
||||||
|
[[maybe_unused]] u32 type_data_size = 0;
|
||||||
|
|
||||||
|
if (actor_type < actor_data_sizes.size()) {
|
||||||
|
type_data_size = actor_data_sizes[actor_type];
|
||||||
|
alloc_size += type_data_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the extension data if it's of nonzero size.
|
||||||
|
PTR(void) data_ptr = NULLPTR;
|
||||||
|
if (alloc_size != 0) {
|
||||||
|
void* data = recomp::alloc(rdram, alloc_size);
|
||||||
|
alloc_count++;
|
||||||
|
data_ptr = reinterpret_cast<uint8_t*>(data) - rdram + 0xFFFFFFFF80000000U;
|
||||||
|
// Zero the allocated memory.
|
||||||
|
// A memset should be fine here since this data is aligned, but use a byteswapped loop just to be safe.
|
||||||
|
for (size_t i = 0; i < alloc_size; i++) {
|
||||||
|
MEM_B(i, data_ptr) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the actor's fields to the actor data slotmap.
|
||||||
|
u32 spawn_index = actor_spawn_count++;
|
||||||
|
dod::slot_map_key32<ExtensionData> key = actor_data.emplace(ExtensionData{.actor_spawn_index = spawn_index, .data_addr = data_ptr});
|
||||||
|
|
||||||
|
// printf("Allocated actor data: address 0x%08X with 0x%08X bytes total (0x%08X bytes generic and 0x%08X bytes specific), handle 0x%08X, spawn index %d\n",
|
||||||
|
// data_ptr, alloc_size, generic_data_size, type_data_size, key.raw, spawn_index);
|
||||||
|
|
||||||
|
_return<u32>(ctx, key.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_destroy_actor_data(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
std::lock_guard lock{ actor_data_mutex };
|
||||||
|
|
||||||
|
u32 actor_handle = _arg<0, u32>(rdram, ctx);
|
||||||
|
actor_data_map_t::key actor_key{actor_handle};
|
||||||
|
|
||||||
|
ExtensionData* data = actor_data.get(actor_key);
|
||||||
|
if (data != nullptr) {
|
||||||
|
// printf("Freeing actor data: address 0x%08X handle 0x%08X\n", data->data_addr, actor_handle);
|
||||||
|
if (data->data_addr != NULLPTR) {
|
||||||
|
recomp::free(rdram, TO_PTR(void, data->data_addr));
|
||||||
|
free_count++;
|
||||||
|
}
|
||||||
|
actor_data.erase(actor_data_map_t::key{actor_handle});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Not an irrecoverable error, but catch it in debug mode with an assert to help find bugs.
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_get_actor_data(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
std::lock_guard lock{ actor_data_mutex };
|
||||||
|
|
||||||
|
u32 actor_handle = _arg<0, u32>(rdram, ctx);
|
||||||
|
u32 extension_handle = _arg<1, u32>(rdram, ctx);
|
||||||
|
u32 actor_type = _arg<2, u32>(rdram, ctx);
|
||||||
|
|
||||||
|
// Check if the extension handle is valid.
|
||||||
|
if (extension_handle == 0 || extension_handle >= actor_extensions.size()) {
|
||||||
|
_return<PTR(void)>(ctx, NULLPTR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionInfo& extension = actor_extensions[extension_handle];
|
||||||
|
bool generic_extension = extension.actor_type == 0xFFFFFFFFU;
|
||||||
|
|
||||||
|
// Check if the extension is generic or for the provided actor type.
|
||||||
|
if (!generic_extension && extension.actor_type != actor_type) {
|
||||||
|
_return<PTR(void)>(ctx, NULLPTR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor_data_map_t::key actor_key{actor_handle};
|
||||||
|
ExtensionData* data = actor_data.get(actor_key);
|
||||||
|
|
||||||
|
// Check if actor handle is valid.
|
||||||
|
if (data == nullptr) {
|
||||||
|
_return<PTR(void)>(ctx, NULLPTR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the address for this specific extension's data.
|
||||||
|
PTR(void) base_address = data->data_addr;
|
||||||
|
u32 offset = extension.data_offset;
|
||||||
|
// Specific actor data is after generic actor data, so increase the offset by the total generic actor data if this isn't generic data.
|
||||||
|
if (!generic_extension) {
|
||||||
|
offset += generic_data_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
PTR(void) ret = base_address + offset;
|
||||||
|
_return<PTR(void)>(ctx, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_get_actor_spawn_index(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
std::lock_guard lock{ actor_data_mutex };
|
||||||
|
|
||||||
|
u32 actor_handle = _arg<0, u32>(rdram, ctx);
|
||||||
|
|
||||||
|
actor_data_map_t::key actor_key{actor_handle};
|
||||||
|
ExtensionData* data = actor_data.get(actor_key);
|
||||||
|
|
||||||
|
// Check if actor handle is valid.
|
||||||
|
if (data == nullptr) {
|
||||||
|
_return<u32>(ctx, 0xFFFFFFFFU);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_return<u32>(ctx, data->actor_spawn_index);
|
||||||
|
}
|
||||||
|
|
727
src/game/recomp_data_api.cpp
Normal file
727
src/game/recomp_data_api.cpp
Normal file
@@ -0,0 +1,727 @@
|
|||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include "slot_map.h"
|
||||||
|
#include "recomp_data.h"
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include "librecomp/helpers.hpp"
|
||||||
|
#include "librecomp/overlays.hpp"
|
||||||
|
#include "librecomp/addresses.hpp"
|
||||||
|
#include "ultramodern/error_handling.hpp"
|
||||||
|
|
||||||
|
template <typename KeyType, typename ValueType>
|
||||||
|
class LockedMap {
|
||||||
|
private:
|
||||||
|
std::mutex mutex{};
|
||||||
|
std::unordered_map<KeyType, ValueType> map{};
|
||||||
|
public:
|
||||||
|
bool get(const KeyType& key, ValueType& out) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
auto find_it = map.find(key);
|
||||||
|
if (find_it == map.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = find_it->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert(const KeyType& key, ValueType val) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
auto ret = map.insert_or_assign(key, val);
|
||||||
|
return ret.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase(const KeyType& key) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
size_t num_erased = map.erase(key);
|
||||||
|
return num_erased != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase_first(ValueType& out) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
auto it = map.begin();
|
||||||
|
|
||||||
|
if (it == map.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = it->second;
|
||||||
|
map.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const KeyType& key) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
|
||||||
|
return map.contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename KeyType>
|
||||||
|
class LockedSet {
|
||||||
|
private:
|
||||||
|
std::mutex mutex{};
|
||||||
|
std::unordered_set<KeyType> set{};
|
||||||
|
public:
|
||||||
|
bool contains(const KeyType& key) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
return set.contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert(const KeyType& key) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
auto it = set.insert(key);
|
||||||
|
return it.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase(const KeyType& key) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
size_t num_erased = set.erase(key);
|
||||||
|
return num_erased != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
set.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
return set.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ValueType>
|
||||||
|
class LockedSlotmap {
|
||||||
|
private:
|
||||||
|
std::mutex mutex{};
|
||||||
|
dod::slot_map32<ValueType> map{};
|
||||||
|
using key_t = typename dod::slot_map32<ValueType>::key;
|
||||||
|
public:
|
||||||
|
bool get(uint32_t key, ValueType** out) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
ValueType* ret = map.get(key_t{key});
|
||||||
|
*out = ret;
|
||||||
|
return ret != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t create() {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
return map.emplace().raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase(uint32_t key) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
if (!map.has_key(key_t{ key })) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.erase(key_t{ key });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase_first(ValueType& out) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
auto it = map.items().begin();
|
||||||
|
|
||||||
|
if (it == map.items().end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = it->second;
|
||||||
|
map.erase(it->first);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using U32ValueMap = LockedMap<uint32_t, uint32_t>;
|
||||||
|
using U32MemoryMap = std::pair<LockedMap<uint32_t, PTR(void)>, u32>;
|
||||||
|
using U32HashSet = LockedSet<uint32_t>;
|
||||||
|
using U32Slotmap = LockedSlotmap<uint32_t>;
|
||||||
|
using MemorySlotmap = std::pair<LockedSlotmap<PTR(void)>, u32>;
|
||||||
|
|
||||||
|
LockedSlotmap<U32ValueMap> u32_value_hashmaps{};
|
||||||
|
LockedSlotmap<U32MemoryMap> u32_memory_hashmaps{};
|
||||||
|
LockedSlotmap<U32HashSet> u32_hashsets{};
|
||||||
|
LockedSlotmap<U32Slotmap> u32_slotmaps{};
|
||||||
|
LockedSlotmap<MemorySlotmap> memory_slotmaps{};
|
||||||
|
|
||||||
|
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||||
|
|
||||||
|
static void show_fatal_error_message_box(const char* funcname, const char* errstr) {
|
||||||
|
std::string message = std::string{"Fatal error in mod - "} + funcname + " : " + errstr;
|
||||||
|
recompui::message_box(message.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#define HANDLE_INVALID_ERROR() \
|
||||||
|
show_fatal_error_message_box(__FUNCTION__, "handle is invalid"); \
|
||||||
|
assert(false); \
|
||||||
|
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||||
|
|
||||||
|
#define SLOTMAP_KEY_INVALID_ERROR() \
|
||||||
|
show_fatal_error_message_box(__FUNCTION__, "slotmap key is invalid"); \
|
||||||
|
assert(false); \
|
||||||
|
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||||
|
|
||||||
|
// u32 -> 32-bit value hashmap.
|
||||||
|
|
||||||
|
void recomputil_create_u32_value_hashmap(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
(void)rdram;
|
||||||
|
_return(ctx, u32_value_hashmaps.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_destroy_u32_value_hashmap(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
if (!u32_value_hashmaps.erase(mapkey)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_value_hashmap_contains(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32ValueMap* map;
|
||||||
|
if (!u32_value_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, map->contains(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_value_hashmap_insert(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t value = _arg<2, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32ValueMap* map;
|
||||||
|
if (!u32_value_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, map->insert(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_value_hashmap_get(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx);
|
||||||
|
|
||||||
|
U32ValueMap* map;
|
||||||
|
if (!u32_value_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ret;
|
||||||
|
if (map->get(key, ret)) {
|
||||||
|
MEM_W(0, val_out) = ret;
|
||||||
|
_return(ctx, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_return(ctx, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_value_hashmap_erase(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32ValueMap* map;
|
||||||
|
if (!u32_value_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, map->erase(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_value_hashmap_size(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32ValueMap* map;
|
||||||
|
if (!u32_value_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, static_cast<uint32_t>(map->size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// u32 -> memory hashmap.
|
||||||
|
|
||||||
|
void recomputil_create_u32_memory_hashmap(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t element_size = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
// Create the map.
|
||||||
|
uint32_t map_key = u32_memory_hashmaps.create();
|
||||||
|
|
||||||
|
// Retrieve the map and set its element size to the provided value.
|
||||||
|
U32MemoryMap* map;
|
||||||
|
u32_memory_hashmaps.get(map_key, &map);
|
||||||
|
map->second = element_size;
|
||||||
|
|
||||||
|
// Return the created map's key.
|
||||||
|
_return(ctx, map_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_destroy_u32_memory_hashmap(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
// Retrieve the map.
|
||||||
|
U32MemoryMap* map;
|
||||||
|
if (!u32_memory_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free all of the entries in the map.
|
||||||
|
PTR(void) cur_mem;
|
||||||
|
while (map->first.erase_first(cur_mem)) {
|
||||||
|
recomp::free(rdram, TO_PTR(void, cur_mem));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the map itself.
|
||||||
|
u32_memory_hashmaps.erase(mapkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_memory_hashmap_contains(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32MemoryMap* map;
|
||||||
|
if (!u32_memory_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, map->first.contains(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_memory_hashmap_create(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32MemoryMap* map;
|
||||||
|
if (!u32_memory_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the map contains the key already to prevent inserting it twice.
|
||||||
|
PTR(void) dummy;
|
||||||
|
if (map->first.get(key, dummy)) {
|
||||||
|
_return(ctx, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the map's size and return the pointer.
|
||||||
|
void* mem = recomp::alloc(rdram, map->second);
|
||||||
|
gpr addr = reinterpret_cast<uint8_t*>(mem) - rdram + 0xFFFFFFFF80000000ULL;
|
||||||
|
|
||||||
|
// Zero the memory.
|
||||||
|
for (size_t i = 0; i < map->second; i++) {
|
||||||
|
MEM_B(i, addr) = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PTR(void) ret = static_cast<PTR(void)>(addr);
|
||||||
|
map->first.insert(key, ret);
|
||||||
|
_return(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_memory_hashmap_get(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32MemoryMap* map;
|
||||||
|
if (!u32_memory_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
PTR(void) ret;
|
||||||
|
if (map->first.get(key, ret)) {
|
||||||
|
_return(ctx, ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_return(ctx, NULLPTR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_memory_hashmap_erase(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32MemoryMap* map;
|
||||||
|
if (!u32_memory_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the memory for this key if the key exists.
|
||||||
|
PTR(void) addr;
|
||||||
|
bool has_value = map->first.get(key, addr);
|
||||||
|
if (has_value) {
|
||||||
|
void* mem = TO_PTR(void, addr);
|
||||||
|
recomp::free(rdram, mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, map->first.erase(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_memory_hashmap_size(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32MemoryMap* map;
|
||||||
|
if (!u32_memory_hashmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, static_cast<uint32_t>(map->first.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// u32 hashset.
|
||||||
|
|
||||||
|
void recomputil_create_u32_hashset(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
(void)rdram;
|
||||||
|
_return(ctx, u32_hashsets.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_destroy_u32_hashset(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
if (!u32_hashsets.erase(setkey)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_hashset_contains(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32HashSet* set;
|
||||||
|
if (!u32_hashsets.get(setkey, &set)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, set->contains(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_hashset_insert(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32HashSet* set;
|
||||||
|
if (!u32_hashsets.get(setkey, &set)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, set->insert(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_hashset_erase(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32HashSet* set;
|
||||||
|
if (!u32_hashsets.get(setkey, &set)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, set->erase(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_hashset_size(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32HashSet* set;
|
||||||
|
if (!u32_hashsets.get(setkey, &set)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, static_cast<uint32_t>(set->size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// u32 value slotmap.
|
||||||
|
|
||||||
|
void recomputil_create_u32_slotmap(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
(void)rdram;
|
||||||
|
_return(ctx, u32_slotmaps.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_destroy_u32_slotmap(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
if (!u32_slotmaps.erase(mapkey)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_slotmap_contains(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32Slotmap* map;
|
||||||
|
if (!u32_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t* dummy_ptr;
|
||||||
|
_return(ctx, map->get(key, &dummy_ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_slotmap_create(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32Slotmap* map;
|
||||||
|
if (!u32_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, map->create());
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_slotmap_get(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx);
|
||||||
|
|
||||||
|
U32Slotmap* map;
|
||||||
|
if (!u32_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t* ret;
|
||||||
|
if (!map->get(key, &ret)) {
|
||||||
|
_return(ctx, 0);
|
||||||
|
}
|
||||||
|
MEM_W(0, val_out) = *ret;
|
||||||
|
_return(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_slotmap_set(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t value = _arg<2, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32Slotmap* map;
|
||||||
|
if (!u32_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t* value_ptr;
|
||||||
|
if (!map->get(key, &value_ptr)) {
|
||||||
|
_return(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
*value_ptr = value;
|
||||||
|
_return(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_slotmap_erase(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32Slotmap* map;
|
||||||
|
if (!u32_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!map->erase(key)) {
|
||||||
|
_return(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_u32_slotmap_size(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
U32Slotmap* map;
|
||||||
|
if (!u32_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, static_cast<uint32_t>(map->size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// memory slotmap.
|
||||||
|
|
||||||
|
void recomputil_create_memory_slotmap(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
(void)rdram;
|
||||||
|
_return(ctx, memory_slotmaps.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_destroy_memory_slotmap(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
// Retrieve the map.
|
||||||
|
MemorySlotmap* map;
|
||||||
|
if (!memory_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free all of the entries in the map.
|
||||||
|
PTR(void) cur_mem;
|
||||||
|
while (map->first.erase_first(cur_mem)) {
|
||||||
|
recomp::free(rdram, TO_PTR(void, cur_mem));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the map itself.
|
||||||
|
memory_slotmaps.erase(mapkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_memory_slotmap_contains(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
MemorySlotmap* map;
|
||||||
|
if (!memory_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
PTR(void)* dummy_ptr;
|
||||||
|
_return(ctx, map->first.get(key, &dummy_ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_memory_slotmap_create(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
MemorySlotmap* map;
|
||||||
|
if (!memory_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the slotmap element.
|
||||||
|
u32 key = map->first.create();
|
||||||
|
|
||||||
|
// Allocate the map's element size.
|
||||||
|
void* mem = recomp::alloc(rdram, map->second);
|
||||||
|
gpr addr = reinterpret_cast<uint8_t*>(mem) - rdram + 0xFFFFFFFF80000000ULL;
|
||||||
|
|
||||||
|
// Zero the memory.
|
||||||
|
for (size_t i = 0; i < map->second; i++) {
|
||||||
|
MEM_B(i, addr) = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the allocated pointer.
|
||||||
|
PTR(void)* value_ptr;
|
||||||
|
map->first.get(key, &value_ptr);
|
||||||
|
MEM_W(0, *value_ptr) = addr;
|
||||||
|
|
||||||
|
// Return the key.
|
||||||
|
_return(ctx, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_memory_slotmap_get(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx);
|
||||||
|
|
||||||
|
MemorySlotmap* map;
|
||||||
|
if (!memory_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
PTR(void)* ret;
|
||||||
|
if (!map->first.get(key, &ret)) {
|
||||||
|
SLOTMAP_KEY_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
MEM_W(0, val_out) = *ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_memory_slotmap_erase(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
MemorySlotmap* map;
|
||||||
|
if (!memory_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the memory for this key if the key exists.
|
||||||
|
PTR(void)* addr;
|
||||||
|
bool has_value = map->first.get(key, &addr);
|
||||||
|
if (has_value) {
|
||||||
|
void* mem = TO_PTR(void, addr);
|
||||||
|
recomp::free(rdram, mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, map->first.erase(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomputil_memory_slotmap_size(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
MemorySlotmap* map;
|
||||||
|
if (!memory_slotmaps.get(mapkey, &map)) {
|
||||||
|
HANDLE_INVALID_ERROR();
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, static_cast<uint32_t>(map->first.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exports.
|
||||||
|
|
||||||
|
void recomputil::register_data_api_exports() {
|
||||||
|
REGISTER_FUNC(recomputil_create_u32_value_hashmap);
|
||||||
|
REGISTER_FUNC(recomputil_destroy_u32_value_hashmap);
|
||||||
|
REGISTER_FUNC(recomputil_u32_value_hashmap_contains);
|
||||||
|
REGISTER_FUNC(recomputil_u32_value_hashmap_insert);
|
||||||
|
REGISTER_FUNC(recomputil_u32_value_hashmap_get);
|
||||||
|
REGISTER_FUNC(recomputil_u32_value_hashmap_erase);
|
||||||
|
REGISTER_FUNC(recomputil_u32_value_hashmap_size);
|
||||||
|
|
||||||
|
REGISTER_FUNC(recomputil_create_u32_memory_hashmap);
|
||||||
|
REGISTER_FUNC(recomputil_destroy_u32_memory_hashmap);
|
||||||
|
REGISTER_FUNC(recomputil_u32_memory_hashmap_contains);
|
||||||
|
REGISTER_FUNC(recomputil_u32_memory_hashmap_create);
|
||||||
|
REGISTER_FUNC(recomputil_u32_memory_hashmap_get);
|
||||||
|
REGISTER_FUNC(recomputil_u32_memory_hashmap_erase);
|
||||||
|
REGISTER_FUNC(recomputil_u32_memory_hashmap_size);
|
||||||
|
|
||||||
|
REGISTER_FUNC(recomputil_create_u32_hashset);
|
||||||
|
REGISTER_FUNC(recomputil_destroy_u32_hashset);
|
||||||
|
REGISTER_FUNC(recomputil_u32_hashset_contains);
|
||||||
|
REGISTER_FUNC(recomputil_u32_hashset_insert);
|
||||||
|
REGISTER_FUNC(recomputil_u32_hashset_erase);
|
||||||
|
REGISTER_FUNC(recomputil_u32_hashset_size);
|
||||||
|
|
||||||
|
REGISTER_FUNC(recomputil_create_u32_slotmap);
|
||||||
|
REGISTER_FUNC(recomputil_destroy_u32_slotmap);
|
||||||
|
REGISTER_FUNC(recomputil_u32_slotmap_contains);
|
||||||
|
REGISTER_FUNC(recomputil_u32_slotmap_create);
|
||||||
|
REGISTER_FUNC(recomputil_u32_slotmap_get);
|
||||||
|
REGISTER_FUNC(recomputil_u32_slotmap_set);
|
||||||
|
REGISTER_FUNC(recomputil_u32_slotmap_erase);
|
||||||
|
REGISTER_FUNC(recomputil_u32_slotmap_size);
|
||||||
|
|
||||||
|
REGISTER_FUNC(recomputil_create_memory_slotmap);
|
||||||
|
REGISTER_FUNC(recomputil_destroy_memory_slotmap);
|
||||||
|
REGISTER_FUNC(recomputil_memory_slotmap_contains);
|
||||||
|
REGISTER_FUNC(recomputil_memory_slotmap_create);
|
||||||
|
REGISTER_FUNC(recomputil_memory_slotmap_get);
|
||||||
|
REGISTER_FUNC(recomputil_memory_slotmap_erase);
|
||||||
|
REGISTER_FUNC(recomputil_memory_slotmap_size);
|
||||||
|
}
|
@@ -27,6 +27,7 @@
|
|||||||
#include "zelda_render.h"
|
#include "zelda_render.h"
|
||||||
#include "zelda_support.h"
|
#include "zelda_support.h"
|
||||||
#include "zelda_game.h"
|
#include "zelda_game.h"
|
||||||
|
#include "recomp_data.h"
|
||||||
#include "ovl_patches.hpp"
|
#include "ovl_patches.hpp"
|
||||||
#include "librecomp/game.hpp"
|
#include "librecomp/game.hpp"
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
@@ -37,6 +38,8 @@
|
|||||||
#include "../../patches/sound.h"
|
#include "../../patches/sound.h"
|
||||||
#include "../../patches/misc_funcs.h"
|
#include "../../patches/misc_funcs.h"
|
||||||
|
|
||||||
|
#include "mods/mm_recomp_dpad_builtin.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@@ -544,15 +547,17 @@ void release_preload(PreloadContext& context) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||||
(void)context;
|
zelda64::renderer::enable_texture_pack(context, mod);
|
||||||
zelda64::renderer::enable_texture_pack(mod);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void disable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
void disable_texture_pack(recomp::mods::ModContext&, const recomp::mods::ModHandle& mod) {
|
||||||
(void)context;
|
|
||||||
zelda64::renderer::disable_texture_pack(mod);
|
zelda64::renderer::disable_texture_pack(mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reorder_texture_pack(recomp::mods::ModContext&) {
|
||||||
|
zelda64::renderer::trigger_texture_pack_update();
|
||||||
|
}
|
||||||
|
|
||||||
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
@@ -614,6 +619,8 @@ int main(int argc, char** argv) {
|
|||||||
recomp::register_game(game);
|
recomp::register_game(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recomp::mods::register_embedded_mod("mm_recomp_dpad_builtin", { (const uint8_t*)(mm_recomp_dpad_builtin), std::size(mm_recomp_dpad_builtin)});
|
||||||
|
|
||||||
REGISTER_FUNC(recomp_get_window_resolution);
|
REGISTER_FUNC(recomp_get_window_resolution);
|
||||||
REGISTER_FUNC(recomp_get_target_aspect_ratio);
|
REGISTER_FUNC(recomp_get_target_aspect_ratio);
|
||||||
REGISTER_FUNC(recomp_get_target_framerate);
|
REGISTER_FUNC(recomp_get_target_framerate);
|
||||||
@@ -627,9 +634,12 @@ int main(int argc, char** argv) {
|
|||||||
REGISTER_FUNC(recomp_get_mouse_deltas);
|
REGISTER_FUNC(recomp_get_mouse_deltas);
|
||||||
REGISTER_FUNC(recomp_get_inverted_axes);
|
REGISTER_FUNC(recomp_get_inverted_axes);
|
||||||
REGISTER_FUNC(recomp_get_analog_inverted_axes);
|
REGISTER_FUNC(recomp_get_analog_inverted_axes);
|
||||||
|
recompui::register_ui_exports();
|
||||||
|
recomputil::register_data_api_exports();
|
||||||
|
|
||||||
zelda64::register_overlays();
|
zelda64::register_overlays();
|
||||||
zelda64::register_patches();
|
zelda64::register_patches();
|
||||||
|
recomputil::init_extended_actor_data();
|
||||||
zelda64::load_config();
|
zelda64::load_config();
|
||||||
|
|
||||||
recomp::rsp::callbacks_t rsp_callbacks{
|
recomp::rsp::callbacks_t rsp_callbacks{
|
||||||
@@ -678,39 +688,13 @@ int main(int argc, char** argv) {
|
|||||||
.allow_runtime_toggle = true,
|
.allow_runtime_toggle = true,
|
||||||
.on_enabled = enable_texture_pack,
|
.on_enabled = enable_texture_pack,
|
||||||
.on_disabled = disable_texture_pack,
|
.on_disabled = disable_texture_pack,
|
||||||
|
.on_reordered = reorder_texture_pack,
|
||||||
};
|
};
|
||||||
auto texture_pack_content_type_id = recomp::mods::register_mod_content_type(texture_pack_content_type);
|
auto texture_pack_content_type_id = recomp::mods::register_mod_content_type(texture_pack_content_type);
|
||||||
|
|
||||||
// Register the .rtz texture pack file format with the previous content type as its only allowed content type.
|
// Register the .rtz texture pack file format with the previous content type as its only allowed content type.
|
||||||
recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false);
|
recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false);
|
||||||
|
|
||||||
recomp::mods::scan_mods();
|
|
||||||
|
|
||||||
printf("Found mods:\n");
|
|
||||||
for (const auto& mod : recomp::mods::get_mod_details("mm")) {
|
|
||||||
printf(" %s(%s)\n", mod.mod_id.c_str(), mod.version.to_string().c_str());
|
|
||||||
if (!mod.authors.empty()) {
|
|
||||||
printf(" Authors: %s", mod.authors[0].c_str());
|
|
||||||
for (size_t author_index = 1; author_index < mod.authors.size(); author_index++) {
|
|
||||||
const std::string& author = mod.authors[author_index];
|
|
||||||
printf(", %s", author.c_str());
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
printf(" Runtime toggleable: %d\n", mod.runtime_toggleable);
|
|
||||||
}
|
|
||||||
if (!mod.dependencies.empty()) {
|
|
||||||
printf(" Dependencies: %s:%s", mod.dependencies[0].mod_id.c_str(), mod.dependencies[0].version.to_string().c_str());
|
|
||||||
for (size_t dep_index = 1; dep_index < mod.dependencies.size(); dep_index++) {
|
|
||||||
const recomp::mods::Dependency& dep = mod.dependencies[dep_index];
|
|
||||||
printf(", %s:%s", dep.mod_id.c_str(), dep.version.to_string().c_str());
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
// TODO load all mods as a temporary solution to not having a UI yet.
|
|
||||||
recomp::mods::enable_mod(mod.mod_id, true);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
recomp::start(
|
recomp::start(
|
||||||
project_version,
|
project_version,
|
||||||
{},
|
{},
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#define HLSL_CPU
|
#define HLSL_CPU
|
||||||
#include "hle/rt64_application.h"
|
#include "hle/rt64_application.h"
|
||||||
#include "rt64_render_hooks.h"
|
#include "rt64_render_hooks.h"
|
||||||
|
#include "overloaded.h"
|
||||||
|
|
||||||
#include "ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
#include "ultramodern/config.hpp"
|
#include "ultramodern/config.hpp"
|
||||||
@@ -13,12 +15,6 @@
|
|||||||
#include "recomp_ui.h"
|
#include "recomp_ui.h"
|
||||||
#include "concurrentqueue.h"
|
#include "concurrentqueue.h"
|
||||||
|
|
||||||
// Helper class for variant visiting.
|
|
||||||
template<class... Ts>
|
|
||||||
struct overloaded : Ts... { using Ts::operator()...; };
|
|
||||||
template<class... Ts>
|
|
||||||
overloaded(Ts...) -> overloaded<Ts...>;
|
|
||||||
|
|
||||||
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
||||||
static bool sample_positions_supported = false;
|
static bool sample_positions_supported = false;
|
||||||
static bool high_precision_fb_enabled = false;
|
static bool high_precision_fb_enabled = false;
|
||||||
@@ -27,14 +23,25 @@ static uint8_t DMEM[0x1000];
|
|||||||
static uint8_t IMEM[0x1000];
|
static uint8_t IMEM[0x1000];
|
||||||
|
|
||||||
struct TexturePackEnableAction {
|
struct TexturePackEnableAction {
|
||||||
std::filesystem::path path;
|
std::string mod_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TexturePackDisableAction {
|
struct TexturePackDisableAction {
|
||||||
std::filesystem::path path;
|
std::string mod_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
using TexturePackAction = std::variant<TexturePackEnableAction, TexturePackDisableAction>;
|
struct TexturePackSecondaryEnableAction {
|
||||||
|
std::string mod_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TexturePackSecondaryDisableAction {
|
||||||
|
std::string mod_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TexturePackUpdateAction {
|
||||||
|
};
|
||||||
|
|
||||||
|
using TexturePackAction = std::variant<TexturePackEnableAction, TexturePackDisableAction, TexturePackSecondaryEnableAction, TexturePackSecondaryDisableAction, TexturePackUpdateAction>;
|
||||||
|
|
||||||
static moodycamel::ConcurrentQueue<TexturePackAction> texture_pack_action_queue;
|
static moodycamel::ConcurrentQueue<TexturePackAction> texture_pack_action_queue;
|
||||||
|
|
||||||
@@ -171,6 +178,7 @@ void set_application_user_config(RT64::Application* application, const ultramode
|
|||||||
application->userConfig.refreshRate = to_rt64(config.rr_option);
|
application->userConfig.refreshRate = to_rt64(config.rr_option);
|
||||||
application->userConfig.refreshRateTarget = config.rr_manual_value;
|
application->userConfig.refreshRateTarget = config.rr_manual_value;
|
||||||
application->userConfig.internalColorFormat = to_rt64(config.hpfb_option);
|
application->userConfig.internalColorFormat = to_rt64(config.hpfb_option);
|
||||||
|
application->userConfig.displayBuffering = RT64::UserConfiguration::DisplayBuffering::Triple;
|
||||||
}
|
}
|
||||||
|
|
||||||
ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) {
|
ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) {
|
||||||
@@ -192,6 +200,23 @@ ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResu
|
|||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ultramodern::renderer::GraphicsApi map_graphics_api(RT64::UserConfiguration::GraphicsAPI api) {
|
||||||
|
switch (api) {
|
||||||
|
case RT64::UserConfiguration::GraphicsAPI::D3D12:
|
||||||
|
return ultramodern::renderer::GraphicsApi::D3D12;
|
||||||
|
case RT64::UserConfiguration::GraphicsAPI::Vulkan:
|
||||||
|
return ultramodern::renderer::GraphicsApi::Vulkan;
|
||||||
|
case RT64::UserConfiguration::GraphicsAPI::Metal:
|
||||||
|
return ultramodern::renderer::GraphicsApi::Metal;
|
||||||
|
case RT64::UserConfiguration::GraphicsAPI::Automatic:
|
||||||
|
return ultramodern::renderer::GraphicsApi::Auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Unhandled `RT64::UserConfiguration::GraphicsAPI` ?\n");
|
||||||
|
assert(false);
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) {
|
zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) {
|
||||||
static unsigned char dummy_rom_header[0x40];
|
static unsigned char dummy_rom_header[0x40];
|
||||||
recompui::set_render_hooks();
|
recompui::set_render_hooks();
|
||||||
@@ -266,9 +291,8 @@ zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::rendere
|
|||||||
case ultramodern::renderer::GraphicsApi::Metal:
|
case ultramodern::renderer::GraphicsApi::Metal:
|
||||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Metal;
|
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Metal;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
case ultramodern::renderer::GraphicsApi::Auto:
|
case ultramodern::renderer::GraphicsApi::Auto:
|
||||||
// Don't override if auto is selected.
|
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Automatic;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +302,8 @@ zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::rendere
|
|||||||
thread_id = window_handle.thread_id;
|
thread_id = window_handle.thread_id;
|
||||||
#endif
|
#endif
|
||||||
setup_result = map_setup_result(app->setup(thread_id));
|
setup_result = map_setup_result(app->setup(thread_id));
|
||||||
|
// Get the API that RT64 chose.
|
||||||
|
chosen_api = map_graphics_api(app->chosenGraphicsAPI);
|
||||||
if (setup_result != ultramodern::renderer::SetupResult::Success) {
|
if (setup_result != ultramodern::renderer::SetupResult::Success) {
|
||||||
app = nullptr;
|
app = nullptr;
|
||||||
return;
|
return;
|
||||||
@@ -306,32 +332,7 @@ zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::rendere
|
|||||||
zelda64::renderer::RT64Context::~RT64Context() = default;
|
zelda64::renderer::RT64Context::~RT64Context() = default;
|
||||||
|
|
||||||
void zelda64::renderer::RT64Context::send_dl(const OSTask* task) {
|
void zelda64::renderer::RT64Context::send_dl(const OSTask* task) {
|
||||||
bool packs_disabled = false;
|
check_texture_pack_actions();
|
||||||
TexturePackAction cur_action;
|
|
||||||
while (texture_pack_action_queue.try_dequeue(cur_action)) {
|
|
||||||
std::visit(overloaded{
|
|
||||||
[&](TexturePackDisableAction& to_disable) {
|
|
||||||
enabled_texture_packs.erase(to_disable.path);
|
|
||||||
packs_disabled = true;
|
|
||||||
},
|
|
||||||
[&](TexturePackEnableAction& to_enable) {
|
|
||||||
enabled_texture_packs.insert(to_enable.path);
|
|
||||||
// Load the pack now if no packs have been disabled.
|
|
||||||
if (!packs_disabled) {
|
|
||||||
app->textureCache->loadReplacementDirectory(to_enable.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, cur_action);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any packs were disabled, unload all packs and load all the active ones.
|
|
||||||
if (packs_disabled) {
|
|
||||||
app->textureCache->clearReplacementDirectories();
|
|
||||||
for (const std::filesystem::path& cur_pack_path : enabled_texture_packs) {
|
|
||||||
app->textureCache->loadReplacementDirectory(cur_pack_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app->state->rsp->reset();
|
app->state->rsp->reset();
|
||||||
app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true);
|
app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true);
|
||||||
app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true);
|
app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true);
|
||||||
@@ -397,6 +398,66 @@ float zelda64::renderer::RT64Context::get_resolution_scale() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void zelda64::renderer::RT64Context::check_texture_pack_actions() {
|
||||||
|
bool packs_changed = false;
|
||||||
|
TexturePackAction cur_action;
|
||||||
|
while (texture_pack_action_queue.try_dequeue(cur_action)) {
|
||||||
|
std::visit(overloaded{
|
||||||
|
[&](TexturePackDisableAction &to_disable) {
|
||||||
|
enabled_texture_packs.erase(to_disable.mod_id);
|
||||||
|
packs_changed = true;
|
||||||
|
},
|
||||||
|
[&](TexturePackEnableAction &to_enable) {
|
||||||
|
enabled_texture_packs.insert(to_enable.mod_id);
|
||||||
|
packs_changed = true;
|
||||||
|
},
|
||||||
|
[&](TexturePackSecondaryDisableAction &to_override_disable) {
|
||||||
|
secondary_disabled_texture_packs.insert(to_override_disable.mod_id);
|
||||||
|
packs_changed = true;
|
||||||
|
},
|
||||||
|
[&](TexturePackSecondaryEnableAction &to_override_enable) {
|
||||||
|
secondary_disabled_texture_packs.erase(to_override_enable.mod_id);
|
||||||
|
packs_changed = true;
|
||||||
|
},
|
||||||
|
[&](TexturePackUpdateAction &) {
|
||||||
|
packs_changed = true;
|
||||||
|
}
|
||||||
|
}, cur_action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any packs were disabled, unload all packs and load all the active ones.
|
||||||
|
if (packs_changed) {
|
||||||
|
// Sort the enabled texture packs in reverse order so that earlier ones override later ones.
|
||||||
|
std::vector<std::string> sorted_texture_packs{};
|
||||||
|
sorted_texture_packs.reserve(enabled_texture_packs.size());
|
||||||
|
for (const std::string& mod : enabled_texture_packs) {
|
||||||
|
if (!secondary_disabled_texture_packs.contains(mod)) {
|
||||||
|
sorted_texture_packs.emplace_back(mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(sorted_texture_packs.begin(), sorted_texture_packs.end(),
|
||||||
|
[](const std::string& lhs, const std::string& rhs) {
|
||||||
|
return recomp::mods::get_mod_order_index(lhs) > recomp::mods::get_mod_order_index(rhs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build the path list from the sorted mod list.
|
||||||
|
std::vector<RT64::ReplacementDirectory> replacement_directories;
|
||||||
|
replacement_directories.reserve(enabled_texture_packs.size());
|
||||||
|
for (const std::string &mod_id : sorted_texture_packs) {
|
||||||
|
replacement_directories.emplace_back(RT64::ReplacementDirectory(recomp::mods::get_mod_filename(mod_id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!replacement_directories.empty()) {
|
||||||
|
app->textureCache->loadReplacementDirectories(replacement_directories);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app->textureCache->clearReplacementDirectories();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RT64::UserConfiguration::Antialiasing zelda64::renderer::RT64MaxMSAA() {
|
RT64::UserConfiguration::Antialiasing zelda64::renderer::RT64MaxMSAA() {
|
||||||
return device_max_msaa;
|
return device_max_msaa;
|
||||||
}
|
}
|
||||||
@@ -413,10 +474,72 @@ bool zelda64::renderer::RT64HighPrecisionFBEnabled() {
|
|||||||
return high_precision_fb_enabled;
|
return high_precision_fb_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void zelda64::renderer::enable_texture_pack(const recomp::mods::ModHandle& mod) {
|
void zelda64::renderer::trigger_texture_pack_update() {
|
||||||
texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_root_path});
|
texture_pack_action_queue.enqueue(TexturePackUpdateAction{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void zelda64::renderer::enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||||
|
texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_id});
|
||||||
|
|
||||||
|
// Check for the texture pack enabled config option.
|
||||||
|
const recomp::mods::ConfigSchema& config_schema = context.get_mod_config_schema(mod.manifest.mod_id);
|
||||||
|
auto find_it = config_schema.options_by_id.find(zelda64::renderer::special_option_texture_pack_enabled);
|
||||||
|
if (find_it != config_schema.options_by_id.end()) {
|
||||||
|
const recomp::mods::ConfigOption& config_option = config_schema.options[find_it->second];
|
||||||
|
|
||||||
|
if (is_texture_pack_enable_config_option(config_option, false)) {
|
||||||
|
recomp::mods::ConfigValueVariant value_variant = context.get_mod_config_value(mod.manifest.mod_id, config_option.id);
|
||||||
|
uint32_t value;
|
||||||
|
if (uint32_t* value_ptr = std::get_if<uint32_t>(&value_variant)) {
|
||||||
|
value = *value_ptr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
zelda64::renderer::secondary_enable_texture_pack(mod.manifest.mod_id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
zelda64::renderer::secondary_disable_texture_pack(mod.manifest.mod_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void zelda64::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) {
|
void zelda64::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) {
|
||||||
texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_root_path});
|
texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_id});
|
||||||
|
}
|
||||||
|
|
||||||
|
void zelda64::renderer::secondary_enable_texture_pack(const std::string& mod_id) {
|
||||||
|
texture_pack_action_queue.enqueue(TexturePackSecondaryEnableAction{mod_id});
|
||||||
|
}
|
||||||
|
|
||||||
|
void zelda64::renderer::secondary_disable_texture_pack(const std::string& mod_id) {
|
||||||
|
texture_pack_action_queue.enqueue(TexturePackSecondaryDisableAction{mod_id});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HD texture enable option. Must be an enum with two options.
|
||||||
|
// The first option is treated as disabled and the second option is treated as enabled.
|
||||||
|
bool zelda64::renderer::is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors) {
|
||||||
|
if (option.id == zelda64::renderer::special_option_texture_pack_enabled) {
|
||||||
|
if (option.type != recomp::mods::ConfigOptionType::Enum) {
|
||||||
|
if (show_errors) {
|
||||||
|
recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + zelda64::renderer::special_option_texture_pack_enabled + "\"), but the config option is not an enum.").c_str());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant);
|
||||||
|
if (option_enum.options.size() != 2) {
|
||||||
|
if (show_errors) {
|
||||||
|
recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + zelda64::renderer::special_option_texture_pack_enabled + "\"), but the config option doesn't have exactly 2 values.").c_str());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,29 @@ namespace zelda64 {
|
|||||||
callback(success, path);
|
callback(success, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void perform_file_dialog_operation_multiple(const std::function<void(bool, const std::list<std::filesystem::path>&)>& callback) {
|
||||||
|
const nfdpathset_t* native_paths = nullptr;
|
||||||
|
nfdresult_t result = NFD_OpenDialogMultipleN(&native_paths, nullptr, 0, nullptr);
|
||||||
|
|
||||||
|
bool success = (result == NFD_OKAY);
|
||||||
|
std::list<std::filesystem::path> paths;
|
||||||
|
nfdpathsetsize_t count = 0;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
NFD_PathSet_GetCount(native_paths, &count);
|
||||||
|
for (nfdpathsetsize_t i = 0; i < count; i++) {
|
||||||
|
nfdnchar_t* cur_path = nullptr;
|
||||||
|
nfdresult_t cur_result = NFD_PathSet_GetPathN(native_paths, i, &cur_path);
|
||||||
|
if (cur_result == NFD_OKAY) {
|
||||||
|
paths.emplace_back(std::filesystem::path{cur_path});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NFD_PathSet_Free(native_paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(success, paths);
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Public API
|
// MARK: - Public API
|
||||||
|
|
||||||
std::filesystem::path get_asset_path(const char* asset) {
|
std::filesystem::path get_asset_path(const char* asset) {
|
||||||
@@ -41,6 +64,16 @@ namespace zelda64 {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void open_file_dialog_multiple(std::function<void(bool success, const std::list<std::filesystem::path>& paths)> callback) {
|
||||||
|
#ifdef __APPLE__
|
||||||
|
dispatch_on_ui_thread([callback]() {
|
||||||
|
perform_file_dialog_operation_multiple(callback);
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
perform_file_dialog_operation_multiple(callback);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void show_error_message_box(const char *title, const char *message) {
|
void show_error_message_box(const char *title, const char *message) {
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
std::string title_copy(title);
|
std::string title_copy(title);
|
||||||
|
668
src/ui/core/ui_context.cpp
Normal file
668
src/ui/core/ui_context.cpp
Normal file
@@ -0,0 +1,668 @@
|
|||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "slot_map.h"
|
||||||
|
#include "RmlUi/Core/StreamMemory.h"
|
||||||
|
|
||||||
|
#include "ultramodern/error_handling.hpp"
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include "ui_context.h"
|
||||||
|
#include "../elements/ui_element.h"
|
||||||
|
|
||||||
|
// Hash implementations for ContextId and ResourceId.
|
||||||
|
template <>
|
||||||
|
struct std::hash<recompui::ContextId> {
|
||||||
|
std::size_t operator()(const recompui::ContextId& id) const {
|
||||||
|
return std::hash<uint32_t>()(id.slot_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct std::hash<recompui::ResourceId> {
|
||||||
|
std::size_t operator()(const recompui::ResourceId& id) const {
|
||||||
|
return std::hash<uint32_t>()(id.slot_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using resource_slotmap = dod::slot_map32<std::unique_ptr<recompui::Style>>;
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
struct Context {
|
||||||
|
std::mutex context_lock;
|
||||||
|
resource_slotmap resources;
|
||||||
|
Rml::ElementDocument* document;
|
||||||
|
Element root_element;
|
||||||
|
Element* autofocus_element = nullptr;
|
||||||
|
std::vector<Element*> loose_elements;
|
||||||
|
std::unordered_set<ResourceId> to_update;
|
||||||
|
bool captures_input = true;
|
||||||
|
bool captures_mouse = true;
|
||||||
|
Context(Rml::ElementDocument* document) : document(document), root_element(document) {}
|
||||||
|
};
|
||||||
|
} // namespace recompui
|
||||||
|
|
||||||
|
using context_slotmap = dod::slot_map32<recompui::Context>;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
std::recursive_mutex all_contexts_lock;
|
||||||
|
context_slotmap all_contexts;
|
||||||
|
std::unordered_set<recompui::ContextId> opened_contexts;
|
||||||
|
std::unordered_map<Rml::ElementDocument*, recompui::ContextId> documents_to_contexts;
|
||||||
|
Rml::SharedPtr<Rml::StyleSheetContainer> style_sheet;
|
||||||
|
} context_state;
|
||||||
|
|
||||||
|
thread_local recompui::Context* opened_context = nullptr;
|
||||||
|
thread_local recompui::ContextId opened_context_id = recompui::ContextId::null();
|
||||||
|
|
||||||
|
enum class ContextErrorType {
|
||||||
|
OpenWithoutClose,
|
||||||
|
OpenInvalidContext,
|
||||||
|
CloseWithoutOpen,
|
||||||
|
CloseWrongContext,
|
||||||
|
DestroyInvalidContext,
|
||||||
|
GetContextWithoutOpen,
|
||||||
|
AddResourceWithoutOpen,
|
||||||
|
AddResourceToWrongContext,
|
||||||
|
UpdateElementWithoutContext,
|
||||||
|
UpdateElementInWrongContext,
|
||||||
|
GetResourceWithoutOpen,
|
||||||
|
GetResourceFailed,
|
||||||
|
DestroyResourceWithoutOpen,
|
||||||
|
DestroyResourceInWrongContext,
|
||||||
|
DestroyResourceNotFound,
|
||||||
|
GetDocumentInvalidContext,
|
||||||
|
GetAutofocusInvalidContext,
|
||||||
|
SetAutofocusInvalidContext,
|
||||||
|
InternalError,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SlotTag : uint8_t {
|
||||||
|
Style = 0,
|
||||||
|
Element = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
void context_error(recompui::ContextId id, ContextErrorType type) {
|
||||||
|
(void)id;
|
||||||
|
|
||||||
|
const char* error_message = "";
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ContextErrorType::OpenWithoutClose:
|
||||||
|
error_message = "Attempted to open a UI context without closing another UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::OpenInvalidContext:
|
||||||
|
error_message = "Attempted to open an invalid UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::CloseWithoutOpen:
|
||||||
|
error_message = "Attempted to close a UI context without one being open";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::CloseWrongContext:
|
||||||
|
error_message = "Attempted to close a different UI context than the one that's open";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::DestroyInvalidContext:
|
||||||
|
error_message = "Attempted to destroy an invalid UI element";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::GetContextWithoutOpen:
|
||||||
|
error_message = "Attempted to get the current UI context with no UI context open";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::AddResourceWithoutOpen:
|
||||||
|
error_message = "Attempted to create a UI resource with no open UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::AddResourceToWrongContext:
|
||||||
|
error_message = "Attempted to create a UI resource in a different UI context than the one that's open";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::UpdateElementWithoutContext:
|
||||||
|
error_message = "Attempted to update a UI element with no open UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::UpdateElementInWrongContext:
|
||||||
|
error_message = "Attempted to update a UI element in a different UI context than the one that's open";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::GetResourceWithoutOpen:
|
||||||
|
error_message = "Attempted to get a UI resource with no open UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::GetResourceFailed:
|
||||||
|
error_message = "Failed to get a UI resource from the current open UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::DestroyResourceWithoutOpen:
|
||||||
|
error_message = "Attempted to destroy a UI resource with no open UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::DestroyResourceInWrongContext:
|
||||||
|
error_message = "Attempted to destroy a UI resource in a different UI context than the one that's open";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::DestroyResourceNotFound:
|
||||||
|
error_message = "Attempted to destroy a UI resource that doesn't exist in the current context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::GetDocumentInvalidContext:
|
||||||
|
error_message = "Attempted to get the document of an invalid UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::GetAutofocusInvalidContext:
|
||||||
|
error_message = "Attempted to get the autofocus element of an invalid UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::SetAutofocusInvalidContext:
|
||||||
|
error_message = "Attempted to set the autofocus element of an invalid UI context";
|
||||||
|
break;
|
||||||
|
case ContextErrorType::InternalError:
|
||||||
|
error_message = "Internal error in UI context";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error_message = "Unknown UI context error";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This assumes the error is coming from a mod, as it's unlikely that an end user will see a UI context error
|
||||||
|
// in the base recomp.
|
||||||
|
recompui::message_box((std::string{"Fatal error in mod - "} + error_message + ".").c_str());
|
||||||
|
assert(false);
|
||||||
|
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId create_context_impl(Rml::ElementDocument* document) {
|
||||||
|
static Rml::ElementDocument dummy_document{""};
|
||||||
|
bool add_to_dict = true;
|
||||||
|
|
||||||
|
if (document == nullptr) {
|
||||||
|
document = &dummy_document;
|
||||||
|
add_to_dict = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId ret;
|
||||||
|
{
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
ret = { context_state.all_contexts.emplace(document).raw };
|
||||||
|
|
||||||
|
if (add_to_dict) {
|
||||||
|
context_state.documents_to_contexts.emplace(document, ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::init_styling(const std::filesystem::path& rcss_file) {
|
||||||
|
std::string style{};
|
||||||
|
{
|
||||||
|
std::ifstream style_stream{rcss_file};
|
||||||
|
style_stream.seekg(0, std::ios::end);
|
||||||
|
style.resize(style_stream.tellg());
|
||||||
|
style_stream.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
style_stream.read(style.data(), style.size());
|
||||||
|
}
|
||||||
|
std::unique_ptr<Rml::StreamMemory> rml_stream = std::make_unique<Rml::StreamMemory>(reinterpret_cast<Rml::byte*>(style.data()), style.size());
|
||||||
|
rml_stream->SetSourceURL(rcss_file.filename().string());
|
||||||
|
context_state.style_sheet = Rml::Factory::InstanceStyleSheetStream(rml_stream.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId recompui::create_context(const std::filesystem::path& path) {
|
||||||
|
ContextId new_context = create_context_impl(nullptr);
|
||||||
|
|
||||||
|
auto workingdir = std::filesystem::current_path();
|
||||||
|
|
||||||
|
new_context.open();
|
||||||
|
Rml::ElementDocument* doc = recompui::load_document(path.string());
|
||||||
|
opened_context->document = doc;
|
||||||
|
opened_context->root_element.base = doc;
|
||||||
|
new_context.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
context_state.documents_to_contexts.emplace(doc, new_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId recompui::create_context(Rml::ElementDocument* document) {
|
||||||
|
assert(document != nullptr);
|
||||||
|
|
||||||
|
return create_context_impl(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId recompui::create_context() {
|
||||||
|
Rml::ElementDocument* doc = create_empty_document();
|
||||||
|
doc->SetStyleSheetContainer(context_state.style_sheet);
|
||||||
|
ContextId ret = create_context_impl(doc);
|
||||||
|
Element* root = ret.get_root_element();
|
||||||
|
// Mark the root element as not being a shim, as that's only needed for elements that were parented to Rml ones manually.
|
||||||
|
root->shim = false;
|
||||||
|
|
||||||
|
ret.open();
|
||||||
|
root->set_width(100.0f, Unit::Percent);
|
||||||
|
root->set_height(100.0f, Unit::Percent);
|
||||||
|
root->set_display(Display::Flex);
|
||||||
|
ret.close();
|
||||||
|
|
||||||
|
doc->Hide();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::destroy_context(ContextId id) {
|
||||||
|
bool existed = false;
|
||||||
|
|
||||||
|
// TODO prevent deletion of a context while its mutex is in use. Second lock on the context's mutex before popping
|
||||||
|
// from the slotmap?
|
||||||
|
|
||||||
|
// Check if the provided id exists.
|
||||||
|
{
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
// Check if the target context is currently open.
|
||||||
|
existed = context_state.all_contexts.has_key(context_slotmap::key{ id.slot_id });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Raise an error if the context didn't exist.
|
||||||
|
if (!existed) {
|
||||||
|
context_error(id, ContextErrorType::DestroyInvalidContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
id.open();
|
||||||
|
id.clear_children();
|
||||||
|
id.close();
|
||||||
|
|
||||||
|
// Delete the provided id.
|
||||||
|
{
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
context_state.all_contexts.erase(context_slotmap::key{ id.slot_id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::destroy_all_contexts() {
|
||||||
|
recompui::hide_all_contexts();
|
||||||
|
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
// TODO prevent deletion of a context while its mutex is in use. Second lock on the context's mutex before popping
|
||||||
|
// from the slotmap
|
||||||
|
|
||||||
|
std::vector<context_slotmap::key> keys{};
|
||||||
|
for (const auto& [key, item] : context_state.all_contexts.items()) {
|
||||||
|
keys.push_back(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto key : keys) {
|
||||||
|
Context* ctx = context_state.all_contexts.get(key);
|
||||||
|
|
||||||
|
std::lock_guard context_lock{ ctx->context_lock };
|
||||||
|
opened_context = ctx;
|
||||||
|
opened_context_id = ContextId{ key };
|
||||||
|
|
||||||
|
opened_context_id.clear_children();
|
||||||
|
|
||||||
|
opened_context = nullptr;
|
||||||
|
opened_context_id = ContextId::null();
|
||||||
|
}
|
||||||
|
|
||||||
|
context_state.all_contexts.reset();
|
||||||
|
context_state.documents_to_contexts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::open() {
|
||||||
|
// Ensure no other context is opened by this thread already.
|
||||||
|
if (opened_context_id != ContextId::null()) {
|
||||||
|
context_error(*this, ContextErrorType::OpenWithoutClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the context with this id.
|
||||||
|
Context* ctx;
|
||||||
|
{
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
// If the context was found, add it to the opened contexts.
|
||||||
|
if (ctx != nullptr) {
|
||||||
|
context_state.opened_contexts.emplace(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the context exists.
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
context_error(*this, ContextErrorType::OpenInvalidContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take ownership of the target context.
|
||||||
|
ctx->context_lock.lock();
|
||||||
|
opened_context = ctx;
|
||||||
|
opened_context_id = *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recompui::ContextId::open_if_not_already() {
|
||||||
|
if (opened_context_id == *this) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
open();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::close() {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == ContextId::null()) {
|
||||||
|
context_error(*this, ContextErrorType::CloseWithoutOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the context that was specified is the same one that's currently open.
|
||||||
|
if (*this != opened_context_id) {
|
||||||
|
context_error(*this, ContextErrorType::CloseWrongContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release ownership of the target context.
|
||||||
|
opened_context->context_lock.unlock();
|
||||||
|
opened_context = nullptr;
|
||||||
|
opened_context_id = ContextId::null();
|
||||||
|
|
||||||
|
// Remove this context from the opened contexts.
|
||||||
|
{
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
context_state.opened_contexts.erase(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId recompui::try_close_current_context() {
|
||||||
|
if (opened_context_id != ContextId::null()) {
|
||||||
|
ContextId prev_context = opened_context_id;
|
||||||
|
opened_context_id.close();
|
||||||
|
return prev_context;
|
||||||
|
}
|
||||||
|
return ContextId::null();
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::process_updates() {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == ContextId::null()) {
|
||||||
|
context_error(*this, ContextErrorType::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the context that was specified is the same one that's currently open.
|
||||||
|
if (*this != opened_context_id) {
|
||||||
|
context_error(*this, ContextErrorType::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the current update set into a local variable. This clears the update set
|
||||||
|
// and allows it to be used to queue updates from any element callbacks.
|
||||||
|
std::unordered_set<ResourceId> to_update = std::move(opened_context->to_update);
|
||||||
|
|
||||||
|
Event update_event = Event::update_event();
|
||||||
|
|
||||||
|
for (auto cur_resource_id : to_update) {
|
||||||
|
resource_slotmap::key cur_key{ cur_resource_id.slot_id };
|
||||||
|
|
||||||
|
// Ignore any resources that aren't elements.
|
||||||
|
if (cur_key.get_tag() != static_cast<uint8_t>(SlotTag::Element)) {
|
||||||
|
// Assert to catch errors of queueing other resource types for update.
|
||||||
|
// This isn't an actual error, so there's no issue with continuing in release builds.
|
||||||
|
assert(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the resource being updaten from the context.
|
||||||
|
std::unique_ptr<Style>* cur_resource = opened_context->resources.get(cur_key);
|
||||||
|
|
||||||
|
// Make sure the resource exists before dispatching the event. It may have been deleted
|
||||||
|
// after being queued for a update, so just continue to the next element if it doesn't exist.
|
||||||
|
if (cur_resource == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_cast<Element*>(cur_resource->get())->handle_event(update_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recompui::ContextId::captures_input() {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ctx->captures_input;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recompui::ContextId::captures_mouse() {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ctx->captures_mouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::set_captures_input(bool captures_input) {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx->captures_input = captures_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::set_captures_mouse(bool captures_mouse) {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx->captures_mouse = captures_mouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::Style* recompui::ContextId::add_resource_impl(std::unique_ptr<Style>&& resource) {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == ContextId::null()) {
|
||||||
|
context_error(*this, ContextErrorType::AddResourceWithoutOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the context that was specified is the same one that's currently open.
|
||||||
|
if (*this != opened_context_id) {
|
||||||
|
context_error(*this, ContextErrorType::AddResourceToWrongContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_element = resource->is_element();
|
||||||
|
Style* resource_ptr = resource.get();
|
||||||
|
auto key = opened_context->resources.emplace(std::move(resource));
|
||||||
|
|
||||||
|
if (is_element) {
|
||||||
|
Element* element_ptr = static_cast<Element*>(resource_ptr);
|
||||||
|
element_ptr->set_id(std::string{element_ptr->get_type_name()} + "-" + std::to_string(key.raw));
|
||||||
|
key.set_tag(static_cast<uint8_t>(SlotTag::Element));
|
||||||
|
// Send one update to the element.
|
||||||
|
opened_context->to_update.emplace(ResourceId{ key.raw });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
key.set_tag(static_cast<uint8_t>(SlotTag::Style));
|
||||||
|
}
|
||||||
|
|
||||||
|
resource_ptr->resource_id = { key.raw };
|
||||||
|
return resource_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::add_loose_element(Element* element) {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == ContextId::null()) {
|
||||||
|
context_error(*this, ContextErrorType::AddResourceWithoutOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the context that was specified is the same one that's currently open.
|
||||||
|
if (*this != opened_context_id) {
|
||||||
|
context_error(*this, ContextErrorType::AddResourceToWrongContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
opened_context->loose_elements.emplace_back(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::queue_element_update(ResourceId element) {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == ContextId::null()) {
|
||||||
|
context_error(*this, ContextErrorType::UpdateElementWithoutContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the context that was specified is the same one that's currently open.
|
||||||
|
if (*this != opened_context_id) {
|
||||||
|
context_error(*this, ContextErrorType::UpdateElementInWrongContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the element that was specified is in the open context.
|
||||||
|
auto* elementPtr = opened_context->resources.get(resource_slotmap::key{ element.slot_id });
|
||||||
|
if (elementPtr == nullptr) {
|
||||||
|
context_error(*this, ContextErrorType::UpdateElementInWrongContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
opened_context->to_update.emplace(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::Style* recompui::ContextId::create_style() {
|
||||||
|
return add_resource_impl(std::make_unique<Style>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::destroy_resource(Style* resource) {
|
||||||
|
destroy_resource(resource->resource_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::destroy_resource(ResourceId resource) {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == ContextId::null()) {
|
||||||
|
context_error(*this, ContextErrorType::DestroyResourceWithoutOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the context that was specified is the same one that's currently open.
|
||||||
|
if (*this != opened_context_id) {
|
||||||
|
context_error(*this, ContextErrorType::DestroyResourceInWrongContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to remove the resource from the current context.
|
||||||
|
auto pop_result = opened_context->resources.pop(resource_slotmap::key{ resource.slot_id });
|
||||||
|
if (!pop_result.has_value()) {
|
||||||
|
context_error(*this, ContextErrorType::DestroyResourceNotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::clear_children() {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == ContextId::null()) {
|
||||||
|
context_error(*this, ContextErrorType::DestroyResourceWithoutOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the context that was specified is the same one that's currently open.
|
||||||
|
if (*this != opened_context_id) {
|
||||||
|
context_error(*this, ContextErrorType::DestroyResourceInWrongContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the root element's children.
|
||||||
|
opened_context->root_element.clear_children();
|
||||||
|
|
||||||
|
// Remove any loose resources.
|
||||||
|
for (Element* e : opened_context->loose_elements) {
|
||||||
|
destroy_resource(e->resource_id);
|
||||||
|
}
|
||||||
|
opened_context->loose_elements.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::ElementDocument* recompui::ContextId::get_document() {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
context_error(*this, ContextErrorType::GetDocumentInvalidContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx->document;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::Element* recompui::ContextId::get_root_element() {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
context_error(*this, ContextErrorType::GetDocumentInvalidContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ctx->root_element;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::Element* recompui::ContextId::get_autofocus_element() {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
context_error(*this, ContextErrorType::GetAutofocusInvalidContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx->autofocus_element;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui::ContextId::set_autofocus_element(Element* element) {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
|
||||||
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
context_error(*this, ContextErrorType::SetAutofocusInvalidContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->autofocus_element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId recompui::get_current_context() {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == ContextId::null()) {
|
||||||
|
context_error(ContextId::null(), ContextErrorType::GetContextWithoutOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
return opened_context_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::Style* get_resource_from_current_context(resource_slotmap::key key) {
|
||||||
|
// Ensure a context is currently opened by this thread.
|
||||||
|
if (opened_context_id == recompui::ContextId::null()) {
|
||||||
|
context_error(recompui::ContextId::null(), ContextErrorType::GetResourceWithoutOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* value = opened_context->resources.get(key);
|
||||||
|
if (value == nullptr) {
|
||||||
|
context_error(opened_context_id, ContextErrorType::GetResourceFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const recompui::Style* recompui::ResourceId::operator*() const {
|
||||||
|
resource_slotmap::key key{ slot_id };
|
||||||
|
|
||||||
|
return get_resource_from_current_context(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::Style* recompui::ResourceId::operator*() {
|
||||||
|
resource_slotmap::key key{ slot_id };
|
||||||
|
|
||||||
|
return get_resource_from_current_context(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recompui::Element* recompui::ResourceId::as_element() const {
|
||||||
|
resource_slotmap::key key{ slot_id };
|
||||||
|
uint8_t tag = key.get_tag();
|
||||||
|
|
||||||
|
assert(tag == static_cast<uint8_t>(SlotTag::Element));
|
||||||
|
|
||||||
|
return static_cast<Element*>(get_resource_from_current_context(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::Element* recompui::ResourceId::as_element() {
|
||||||
|
resource_slotmap::key key{ slot_id };
|
||||||
|
uint8_t tag = key.get_tag();
|
||||||
|
|
||||||
|
assert(tag == static_cast<uint8_t>(SlotTag::Element));
|
||||||
|
|
||||||
|
return static_cast<Element*>(get_resource_from_current_context(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId recompui::get_context_from_document(Rml::ElementDocument* document) {
|
||||||
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||||
|
auto find_it = context_state.documents_to_contexts.find(document);
|
||||||
|
if (find_it == context_state.documents_to_contexts.end()) {
|
||||||
|
return ContextId::null();
|
||||||
|
}
|
||||||
|
return find_it->second;
|
||||||
|
}
|
69
src/ui/core/ui_context.h
Normal file
69
src/ui/core/ui_context.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "RmlUi/Core.h"
|
||||||
|
|
||||||
|
#include "ui_resource.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
class Style;
|
||||||
|
class Element;
|
||||||
|
class ContextId {
|
||||||
|
Style* add_resource_impl(std::unique_ptr<Style>&& resource);
|
||||||
|
public:
|
||||||
|
uint32_t slot_id;
|
||||||
|
auto operator<=>(const ContextId& rhs) const = default;
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
T* create_element(Args... args) {
|
||||||
|
return static_cast<T*>(add_resource_impl(std::make_unique<T>(std::forward<Args>(args)...)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* create_element(T&& element) {
|
||||||
|
return static_cast<T*>(add_resource_impl(std::make_unique<T>(std::move(element))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_loose_element(Element* element);
|
||||||
|
void queue_element_update(ResourceId element);
|
||||||
|
|
||||||
|
Style* create_style();
|
||||||
|
|
||||||
|
void destroy_resource(Style* resource);
|
||||||
|
void destroy_resource(ResourceId resource);
|
||||||
|
void clear_children();
|
||||||
|
|
||||||
|
Rml::ElementDocument* get_document();
|
||||||
|
Element* get_root_element();
|
||||||
|
Element* get_autofocus_element();
|
||||||
|
void set_autofocus_element(Element* element);
|
||||||
|
|
||||||
|
void open();
|
||||||
|
bool open_if_not_already();
|
||||||
|
void close();
|
||||||
|
void process_updates();
|
||||||
|
|
||||||
|
static constexpr ContextId null() { return ContextId{ .slot_id = uint32_t(-1) }; }
|
||||||
|
|
||||||
|
bool captures_input();
|
||||||
|
bool captures_mouse();
|
||||||
|
|
||||||
|
void set_captures_input(bool captures_input);
|
||||||
|
void set_captures_mouse(bool captures_input);
|
||||||
|
};
|
||||||
|
|
||||||
|
ContextId create_context(const std::filesystem::path& path);
|
||||||
|
ContextId create_context(Rml::ElementDocument* document);
|
||||||
|
ContextId create_context();
|
||||||
|
void destroy_context(ContextId id);
|
||||||
|
ContextId get_current_context();
|
||||||
|
ContextId get_context_from_document(Rml::ElementDocument* document);
|
||||||
|
void destroy_all_contexts();
|
||||||
|
|
||||||
|
void register_ui_exports();
|
||||||
|
} // namespace recompui
|
24
src/ui/core/ui_resource.h
Normal file
24
src/ui/core/ui_resource.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
class Style;
|
||||||
|
class Element;
|
||||||
|
struct ResourceId {
|
||||||
|
uint32_t slot_id;
|
||||||
|
|
||||||
|
bool operator==(const ResourceId& rhs) const = default;
|
||||||
|
|
||||||
|
const Style* operator*() const;
|
||||||
|
Style* operator*();
|
||||||
|
|
||||||
|
const Style* operator->() const { return *(*this); }
|
||||||
|
Style* operator->() { return *(*this); }
|
||||||
|
|
||||||
|
const Element* as_element() const;
|
||||||
|
Element* as_element();
|
||||||
|
|
||||||
|
static constexpr ResourceId null() { return ResourceId{ uint32_t(-1) }; }
|
||||||
|
};
|
||||||
|
} // namespace recompui
|
114
src/ui/elements/ui_button.cpp
Normal file
114
src/ui/elements/ui_button.cpp
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#include "ui_button.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
Button::Button(Element *parent, const std::string &text, ButtonStyle style) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button", true) {
|
||||||
|
this->style = style;
|
||||||
|
|
||||||
|
enable_focus();
|
||||||
|
|
||||||
|
set_text(text);
|
||||||
|
set_display(Display::Block);
|
||||||
|
set_padding(23.0f);
|
||||||
|
set_border_width(1.1f);
|
||||||
|
set_border_radius(12.0f);
|
||||||
|
set_font_size(28.0f);
|
||||||
|
set_letter_spacing(3.08f);
|
||||||
|
set_line_height(28.0f);
|
||||||
|
set_font_style(FontStyle::Normal);
|
||||||
|
set_font_weight(700);
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
set_color(Color{ 204, 204, 204, 255 });
|
||||||
|
set_tab_index(TabIndex::Auto);
|
||||||
|
hover_style.set_color(Color{ 242, 242, 242, 255 });
|
||||||
|
focus_style.set_color(Color{ 242, 242, 242, 255 });
|
||||||
|
disabled_style.set_color(Color{ 204, 204, 204, 128 });
|
||||||
|
hover_disabled_style.set_color(Color{ 242, 242, 242, 128 });
|
||||||
|
|
||||||
|
const uint8_t border_opacity = 204;
|
||||||
|
const uint8_t background_opacity = 13;
|
||||||
|
const uint8_t border_hover_opacity = 255;
|
||||||
|
const uint8_t background_hover_opacity = 76;
|
||||||
|
switch (style) {
|
||||||
|
case ButtonStyle::Primary: {
|
||||||
|
set_border_color({ 185, 125, 242, border_opacity });
|
||||||
|
set_background_color({ 185, 125, 242, background_opacity });
|
||||||
|
hover_style.set_border_color({ 185, 125, 242, border_hover_opacity });
|
||||||
|
hover_style.set_background_color({ 185, 125, 242, background_hover_opacity });
|
||||||
|
focus_style.set_border_color({ 185, 125, 242, border_hover_opacity });
|
||||||
|
focus_style.set_background_color({ 185, 125, 242, background_hover_opacity });
|
||||||
|
disabled_style.set_border_color({ 185, 125, 242, border_opacity / 4 });
|
||||||
|
disabled_style.set_background_color({ 185, 125, 242, background_opacity / 4 });
|
||||||
|
hover_disabled_style.set_border_color({ 185, 125, 242, border_hover_opacity / 4 });
|
||||||
|
hover_disabled_style.set_background_color({ 185, 125, 242, background_hover_opacity / 4 });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ButtonStyle::Secondary: {
|
||||||
|
set_border_color({ 23, 214, 232, border_opacity });
|
||||||
|
set_background_color({ 23, 214, 232, background_opacity });
|
||||||
|
hover_style.set_border_color({ 23, 214, 232, border_hover_opacity });
|
||||||
|
hover_style.set_background_color({ 23, 214, 232, background_hover_opacity });
|
||||||
|
focus_style.set_border_color({ 23, 214, 232, border_hover_opacity });
|
||||||
|
focus_style.set_background_color({ 23, 214, 232, background_hover_opacity });
|
||||||
|
disabled_style.set_border_color({ 23, 214, 232, border_opacity / 4 });
|
||||||
|
disabled_style.set_background_color({ 23, 214, 232, background_opacity / 4 });
|
||||||
|
hover_disabled_style.set_border_color({ 23, 214, 232, border_hover_opacity / 4 });
|
||||||
|
hover_disabled_style.set_background_color({ 23, 214, 232, background_hover_opacity / 4 });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown button style.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_style(&hover_style, hover_state);
|
||||||
|
add_style(&focus_style, focus_state);
|
||||||
|
add_style(&disabled_style, disabled_state);
|
||||||
|
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||||
|
|
||||||
|
// transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::process_event(const Event &e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case EventType::Click:
|
||||||
|
if (is_enabled()) {
|
||||||
|
for (const auto &function : pressed_callbacks) {
|
||||||
|
function();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::Hover:
|
||||||
|
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||||
|
break;
|
||||||
|
case EventType::Enable:
|
||||||
|
{
|
||||||
|
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||||
|
set_style_enabled(disabled_state, !enable_active);
|
||||||
|
if (enable_active) {
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
set_focusable(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
set_cursor(Cursor::None);
|
||||||
|
set_focusable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::Focus:
|
||||||
|
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||||
|
break;
|
||||||
|
case EventType::Update:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown event type.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::add_pressed_callback(std::function<void()> callback) {
|
||||||
|
pressed_callbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
};
|
33
src/ui/elements/ui_button.h
Normal file
33
src/ui/elements/ui_button.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
enum class ButtonStyle {
|
||||||
|
Primary,
|
||||||
|
Secondary
|
||||||
|
};
|
||||||
|
|
||||||
|
class Button : public Element {
|
||||||
|
protected:
|
||||||
|
ButtonStyle style = ButtonStyle::Primary;
|
||||||
|
Style hover_style;
|
||||||
|
Style focus_style;
|
||||||
|
Style disabled_style;
|
||||||
|
Style hover_disabled_style;
|
||||||
|
std::list<std::function<void()>> pressed_callbacks;
|
||||||
|
|
||||||
|
// Element overrides.
|
||||||
|
virtual void process_event(const Event &e) override;
|
||||||
|
std::string_view get_type_name() override { return "Button"; }
|
||||||
|
public:
|
||||||
|
Button(Element *parent, const std::string &text, ButtonStyle style);
|
||||||
|
void add_pressed_callback(std::function<void()> callback);
|
||||||
|
Style* get_hover_style() { return &hover_style; }
|
||||||
|
Style* get_focus_style() { return &focus_style; }
|
||||||
|
Style* get_disabled_style() { return &disabled_style; }
|
||||||
|
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
62
src/ui/elements/ui_clickable.cpp
Normal file
62
src/ui/elements/ui_clickable.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#include "ui_clickable.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
Clickable::Clickable(Element *parent, bool draggable) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, draggable ? EventType::Drag : EventType::None)) {
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
if (draggable) {
|
||||||
|
set_drag(Drag::Drag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clickable::process_event(const Event &e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case EventType::Click: {
|
||||||
|
if (is_enabled()) {
|
||||||
|
const EventClick &click = std::get<EventClick>(e.variant);
|
||||||
|
for (const auto &function : pressed_callbacks) {
|
||||||
|
function(click.x, click.y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case EventType::Hover:
|
||||||
|
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||||
|
break;
|
||||||
|
case EventType::Enable:
|
||||||
|
{
|
||||||
|
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||||
|
set_style_enabled(disabled_state, !enable_active);
|
||||||
|
if (enable_active) {
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
set_focusable(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
set_cursor(Cursor::None);
|
||||||
|
set_focusable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::Drag: {
|
||||||
|
if (is_enabled()) {
|
||||||
|
const EventDrag &drag = std::get<EventDrag>(e.variant);
|
||||||
|
for (const auto &function : dragged_callbacks) {
|
||||||
|
function(drag.x, drag.y, drag.phase);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clickable::add_pressed_callback(std::function<void(float, float)> callback) {
|
||||||
|
pressed_callbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clickable::add_dragged_callback(std::function<void(float, float, DragPhase)> callback) {
|
||||||
|
dragged_callbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
21
src/ui/elements/ui_clickable.h
Normal file
21
src/ui/elements/ui_clickable.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class Clickable : public Element {
|
||||||
|
protected:
|
||||||
|
std::vector<std::function<void(float, float)>> pressed_callbacks;
|
||||||
|
std::vector<std::function<void(float, float, DragPhase)>> dragged_callbacks;
|
||||||
|
|
||||||
|
// Element overrides.
|
||||||
|
virtual void process_event(const Event &e) override;
|
||||||
|
std::string_view get_type_name() override { return "Clickable"; }
|
||||||
|
public:
|
||||||
|
Clickable(Element *parent, bool draggable = false);
|
||||||
|
void add_pressed_callback(std::function<void(float, float)> callback);
|
||||||
|
void add_dragged_callback(std::function<void(float, float, DragPhase)> callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
13
src/ui/elements/ui_container.cpp
Normal file
13
src/ui/elements/ui_container.cpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include "ui_container.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
Container::Container(Element *parent, FlexDirection direction, JustifyContent justify_content, uint32_t events_enabled) : Element(parent, events_enabled) {
|
||||||
|
set_display(Display::Flex);
|
||||||
|
set_flex_direction(direction);
|
||||||
|
set_justify_content(justify_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
14
src/ui/elements/ui_container.h
Normal file
14
src/ui/elements/ui_container.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class Container : public Element {
|
||||||
|
protected:
|
||||||
|
std::string_view get_type_name() override { return "Container"; }
|
||||||
|
public:
|
||||||
|
Container(Element* parent, FlexDirection direction, JustifyContent justify_content, uint32_t events_enabled = 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
478
src/ui/elements/ui_element.cpp
Normal file
478
src/ui/elements/ui_element.cpp
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
#include "RmlUi/Core/StringUtilities.h"
|
||||||
|
|
||||||
|
#include "overloaded.h"
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include "ui_element.h"
|
||||||
|
#include "../core/ui_context.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
Element::Element(Rml::Element *base) {
|
||||||
|
assert(base != nullptr);
|
||||||
|
|
||||||
|
this->base = base;
|
||||||
|
this->base_owning = {};
|
||||||
|
this->shim = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Element::Element(Element* parent, uint32_t events_enabled, Rml::String base_class, bool can_set_text) : can_set_text(can_set_text) {
|
||||||
|
ContextId context = get_current_context();
|
||||||
|
base_owning = context.get_document()->CreateElement(base_class);
|
||||||
|
|
||||||
|
if (parent != nullptr) {
|
||||||
|
base = parent->base->AppendChild(std::move(base_owning));
|
||||||
|
parent->add_child(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
base = base_owning.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
set_display(Display::Block);
|
||||||
|
set_property(Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox);
|
||||||
|
|
||||||
|
register_event_listeners(events_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element::~Element() {
|
||||||
|
if (!shim) {
|
||||||
|
clear_children();
|
||||||
|
if (!base_owning) {
|
||||||
|
base->GetParentNode()->RemoveChild(base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::add_child(Element *child) {
|
||||||
|
assert(child != nullptr);
|
||||||
|
if (can_set_text) {
|
||||||
|
assert(false && "Elements with settable text cannot have children");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
children.emplace_back(child);
|
||||||
|
|
||||||
|
if (shim) {
|
||||||
|
ContextId context = get_current_context();
|
||||||
|
context.add_loose_element(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::set_property(Rml::PropertyId property_id, const Rml::Property &property) {
|
||||||
|
assert(base != nullptr);
|
||||||
|
|
||||||
|
base->SetProperty(property_id, property);
|
||||||
|
Style::set_property(property_id, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::register_event_listeners(uint32_t events_enabled) {
|
||||||
|
assert(base != nullptr);
|
||||||
|
|
||||||
|
this->events_enabled = events_enabled;
|
||||||
|
|
||||||
|
if (events_enabled & Events(EventType::Click)) {
|
||||||
|
base->AddEventListener(Rml::EventId::Click, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_enabled & Events(EventType::Focus)) {
|
||||||
|
base->AddEventListener(Rml::EventId::Focus, this);
|
||||||
|
base->AddEventListener(Rml::EventId::Blur, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_enabled & Events(EventType::Hover)) {
|
||||||
|
base->AddEventListener(Rml::EventId::Mouseover, this);
|
||||||
|
base->AddEventListener(Rml::EventId::Mouseout, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_enabled & Events(EventType::Drag)) {
|
||||||
|
base->AddEventListener(Rml::EventId::Drag, this);
|
||||||
|
base->AddEventListener(Rml::EventId::Dragstart, this);
|
||||||
|
base->AddEventListener(Rml::EventId::Dragend, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_enabled & Events(EventType::Text)) {
|
||||||
|
base->AddEventListener(Rml::EventId::Change, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_enabled & Events(EventType::Navigate)) {
|
||||||
|
base->AddEventListener(Rml::EventId::Keydown, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::apply_style(Style *style) {
|
||||||
|
for (auto it : style->property_map) {
|
||||||
|
// Skip redundant SetProperty calls to prevent dirtying unnecessary state.
|
||||||
|
// This avoids expensive layout operations when a simple color-only style is applied.
|
||||||
|
const Rml::Property* cur_value = base->GetLocalProperty(it.first);
|
||||||
|
if (*cur_value != it.second) {
|
||||||
|
base->SetProperty(it.first, it.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::apply_styles() {
|
||||||
|
apply_style(this);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < styles_counter.size(); i++) {
|
||||||
|
if (styles_counter[i] == 0) {
|
||||||
|
apply_style(styles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::propagate_disabled(bool disabled) {
|
||||||
|
disabled_from_parent = disabled;
|
||||||
|
|
||||||
|
bool attribute_state = disabled_from_parent || !enabled;
|
||||||
|
if (disabled_attribute != attribute_state) {
|
||||||
|
disabled_attribute = attribute_state;
|
||||||
|
base->SetAttribute("disabled", attribute_state);
|
||||||
|
|
||||||
|
if (events_enabled & Events(EventType::Enable)) {
|
||||||
|
handle_event(Event::enable_event(!attribute_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &child : children) {
|
||||||
|
child->propagate_disabled(attribute_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::handle_event(const Event& event) {
|
||||||
|
for (const auto& callback : callbacks) {
|
||||||
|
recompui::queue_ui_callback(resource_id, event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
process_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::set_id(const std::string& new_id) {
|
||||||
|
id = new_id;
|
||||||
|
base->SetId(new_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::ProcessEvent(Rml::Event &event) {
|
||||||
|
ContextId prev_context = recompui::try_close_current_context();
|
||||||
|
ContextId context = ContextId::null();
|
||||||
|
Rml::ElementDocument* doc = event.GetTargetElement()->GetOwnerDocument();
|
||||||
|
if (doc != nullptr) {
|
||||||
|
context = get_context_from_document(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool did_open = false;
|
||||||
|
|
||||||
|
// TODO disallow null contexts once the entire UI system has been migrated.
|
||||||
|
if (context != ContextId::null()) {
|
||||||
|
did_open = context.open_if_not_already();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events that are processed during any phase.
|
||||||
|
switch (event.GetId()) {
|
||||||
|
case Rml::EventId::Click:
|
||||||
|
handle_event(Event::click_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f)));
|
||||||
|
break;
|
||||||
|
case Rml::EventId::Keydown:
|
||||||
|
switch ((Rml::Input::KeyIdentifier)event.GetParameter<int>("key_identifier", 0)) {
|
||||||
|
case Rml::Input::KeyIdentifier::KI_LEFT:
|
||||||
|
handle_event(Event::navigate_event(NavDirection::Left));
|
||||||
|
break;
|
||||||
|
case Rml::Input::KeyIdentifier::KI_UP:
|
||||||
|
handle_event(Event::navigate_event(NavDirection::Up));
|
||||||
|
break;
|
||||||
|
case Rml::Input::KeyIdentifier::KI_RIGHT:
|
||||||
|
handle_event(Event::navigate_event(NavDirection::Right));
|
||||||
|
break;
|
||||||
|
case Rml::Input::KeyIdentifier::KI_DOWN:
|
||||||
|
handle_event(Event::navigate_event(NavDirection::Down));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Rml::EventId::Drag:
|
||||||
|
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Move));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events that are only processed during the Target phase.
|
||||||
|
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||||
|
switch (event.GetId()) {
|
||||||
|
case Rml::EventId::Mouseover:
|
||||||
|
handle_event(Event::hover_event(true));
|
||||||
|
break;
|
||||||
|
case Rml::EventId::Mouseout:
|
||||||
|
handle_event(Event::hover_event(false));
|
||||||
|
break;
|
||||||
|
case Rml::EventId::Focus:
|
||||||
|
handle_event(Event::focus_event(true));
|
||||||
|
break;
|
||||||
|
case Rml::EventId::Blur:
|
||||||
|
handle_event(Event::focus_event(false));
|
||||||
|
break;
|
||||||
|
case Rml::EventId::Dragstart:
|
||||||
|
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Start));
|
||||||
|
break;
|
||||||
|
case Rml::EventId::Dragend:
|
||||||
|
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::End));
|
||||||
|
break;
|
||||||
|
case Rml::EventId::Change: {
|
||||||
|
if (events_enabled & Events(EventType::Text)) {
|
||||||
|
Rml::Variant *value_variant = base->GetAttribute("value");
|
||||||
|
if (value_variant != nullptr) {
|
||||||
|
handle_event(Event::text_event(value_variant->Get<std::string>()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context != ContextId::null() && did_open) {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev_context != ContextId::null()) {
|
||||||
|
prev_context.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value) {
|
||||||
|
base->SetAttribute(attribute_key, attribute_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::process_event(const Event &) {
|
||||||
|
// Does nothing by default.
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::enable_focus() {
|
||||||
|
set_tab_index_auto();
|
||||||
|
set_focusable(true);
|
||||||
|
set_nav_auto(NavDirection::Up);
|
||||||
|
set_nav_auto(NavDirection::Down);
|
||||||
|
set_nav_auto(NavDirection::Left);
|
||||||
|
set_nav_auto(NavDirection::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::clear_children() {
|
||||||
|
if (children.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ContextId context = get_current_context();
|
||||||
|
|
||||||
|
// Remove the children from the context.
|
||||||
|
for (Element* child : children) {
|
||||||
|
context.destroy_resource(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the child list.
|
||||||
|
children.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Element::remove_child(ResourceId child) {
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
ContextId context = get_current_context();
|
||||||
|
|
||||||
|
for (auto it = children.begin(); it != children.end(); ++it) {
|
||||||
|
Element* cur_child = *it;
|
||||||
|
if (cur_child->get_resource_id() == child) {
|
||||||
|
children.erase(it);
|
||||||
|
context.destroy_resource(cur_child);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::add_style(Style *style, const std::string_view style_name) {
|
||||||
|
add_style(style, { style_name });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::add_style(Style *style, const std::initializer_list<std::string_view> &style_names) {
|
||||||
|
for (const std::string_view &style_name : style_names) {
|
||||||
|
style_name_index_map.emplace(style_name, styles.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
styles.emplace_back(style);
|
||||||
|
|
||||||
|
uint32_t initial_style_counter = style_names.size();
|
||||||
|
for (const std::string_view &style_name : style_names) {
|
||||||
|
if (style_active_set.find(style_name) != style_active_set.end()) {
|
||||||
|
initial_style_counter--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
styles_counter.push_back(initial_style_counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::set_enabled(bool enabled) {
|
||||||
|
this->enabled = enabled;
|
||||||
|
|
||||||
|
propagate_disabled(disabled_from_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Element::is_enabled() const {
|
||||||
|
return enabled && !disabled_from_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from RmlUi's `EncodeRml`.
|
||||||
|
std::string escape_rml(std::string_view string)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result.reserve(string.size());
|
||||||
|
for (char c : string)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '<': result += "<"; break;
|
||||||
|
case '>': result += ">"; break;
|
||||||
|
case '&': result += "&"; break;
|
||||||
|
case '"': result += """; break;
|
||||||
|
case '\n': result += "<br/>"; break;
|
||||||
|
default: result += c; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::set_text(std::string_view text) {
|
||||||
|
if (can_set_text) {
|
||||||
|
// Escape the string into Rml to prevent element injection.
|
||||||
|
base->SetInnerRML(escape_rml(text));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(false && "Attempted to set text of an element that cannot have its text set.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Element::get_input_text() {
|
||||||
|
return base->GetAttribute("value", std::string{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::set_input_text(std::string_view val) {
|
||||||
|
base->SetAttribute("value", std::string{ val });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::set_src(std::string_view src) {
|
||||||
|
base->SetAttribute("src", std::string(src));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::set_style_enabled(std::string_view style_name, bool enable) {
|
||||||
|
if (enable && style_active_set.find(style_name) == style_active_set.end()) {
|
||||||
|
// Style was disabled and will be enabled.
|
||||||
|
style_active_set.emplace(style_name);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (!enable && style_active_set.find(style_name) != style_active_set.end()) {
|
||||||
|
// Style was enabled and will be disabled.
|
||||||
|
style_active_set.erase(style_name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto range = style_name_index_map.equal_range(style_name);
|
||||||
|
for (auto it = range.first; it != range.second; it++) {
|
||||||
|
if (enable) {
|
||||||
|
styles_counter[it->second]--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
styles_counter[it->second]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_styles();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Element::is_style_enabled(std::string_view style_name) {
|
||||||
|
return style_active_set.contains(style_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Element::get_absolute_left() {
|
||||||
|
return base->GetAbsoluteLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Element::get_absolute_top() {
|
||||||
|
return base->GetAbsoluteTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Element::get_client_left() {
|
||||||
|
return base->GetClientLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Element::get_client_top() {
|
||||||
|
return base->GetClientTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Element::get_client_width() {
|
||||||
|
return base->GetClientWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Element::get_client_height() {
|
||||||
|
return base->GetClientHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Element::get_input_value_u32() {
|
||||||
|
ElementValue value = get_element_value();
|
||||||
|
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[](double d) { return (uint32_t)d; },
|
||||||
|
[](float f) { return (uint32_t)f; },
|
||||||
|
[](uint32_t u) { return u; },
|
||||||
|
[](std::monostate) { return 0U; }
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Element::get_input_value_float() {
|
||||||
|
ElementValue value = get_element_value();
|
||||||
|
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[](double d) { return (float)d; },
|
||||||
|
[](float f) { return f; },
|
||||||
|
[](uint32_t u) { return (float)u; },
|
||||||
|
[](std::monostate) { return 0.0f; }
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Element::get_input_value_double() {
|
||||||
|
ElementValue value = get_element_value();
|
||||||
|
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[](double d) { return d; },
|
||||||
|
[](float f) { return (double)f; },
|
||||||
|
[](uint32_t u) { return (double)u; },
|
||||||
|
[](std::monostate) { return 0.0; }
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::focus() {
|
||||||
|
base->Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::blur() {
|
||||||
|
base->Blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::queue_update() {
|
||||||
|
ContextId cur_context = get_current_context();
|
||||||
|
|
||||||
|
// TODO disallow null contexts once the entire UI system has been migrated.
|
||||||
|
if (cur_context == ContextId::null()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_context.queue_element_update(resource_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Element::register_callback(ContextId context, PTR(void) callback, PTR(void) userdata) {
|
||||||
|
callbacks.emplace_back(UICallback{.context = context, .callback = callback, .userdata = userdata});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
106
src/ui/elements/ui_element.h
Normal file
106
src/ui/elements/ui_element.h
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_style.h"
|
||||||
|
#include "../core/ui_context.h"
|
||||||
|
|
||||||
|
#include "recomp.h"
|
||||||
|
#include <ultramodern/ultra64.h>
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
struct UICallback {
|
||||||
|
ContextId context;
|
||||||
|
PTR(void) callback;
|
||||||
|
PTR(void) userdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ElementValue = std::variant<uint32_t, float, double, std::monostate>;
|
||||||
|
|
||||||
|
class ContextId;
|
||||||
|
class Element : public Style, public Rml::EventListener {
|
||||||
|
friend ContextId create_context(const std::filesystem::path& path);
|
||||||
|
friend ContextId create_context();
|
||||||
|
friend class ContextId; // To allow ContextId to call the handle_event method directly.
|
||||||
|
private:
|
||||||
|
Rml::Element *base = nullptr;
|
||||||
|
Rml::ElementPtr base_owning = {};
|
||||||
|
uint32_t events_enabled = 0;
|
||||||
|
std::vector<Style *> styles;
|
||||||
|
std::vector<uint32_t> styles_counter;
|
||||||
|
std::unordered_set<std::string_view> style_active_set;
|
||||||
|
std::unordered_multimap<std::string_view, uint32_t> style_name_index_map;
|
||||||
|
std::vector<UICallback> callbacks;
|
||||||
|
std::vector<Element *> children;
|
||||||
|
std::string id;
|
||||||
|
bool shim = false;
|
||||||
|
bool enabled = true;
|
||||||
|
bool disabled_attribute = false;
|
||||||
|
bool disabled_from_parent = false;
|
||||||
|
bool can_set_text = false;
|
||||||
|
|
||||||
|
void add_child(Element *child);
|
||||||
|
void register_event_listeners(uint32_t events_enabled);
|
||||||
|
void apply_style(Style *style);
|
||||||
|
void propagate_disabled(bool disabled);
|
||||||
|
void handle_event(const Event &e);
|
||||||
|
void set_id(const std::string& new_id);
|
||||||
|
|
||||||
|
// Style overrides.
|
||||||
|
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property) override;
|
||||||
|
|
||||||
|
// Rml::EventListener overrides.
|
||||||
|
void ProcessEvent(Rml::Event &event) override final;
|
||||||
|
protected:
|
||||||
|
// Use of this method in inherited classes is discouraged unless it's necessary.
|
||||||
|
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
|
||||||
|
virtual void process_event(const Event &e);
|
||||||
|
virtual ElementValue get_element_value() { return std::monostate{}; }
|
||||||
|
virtual void set_input_value(const ElementValue&) {}
|
||||||
|
virtual std::string_view get_type_name() { return "Element"; }
|
||||||
|
public:
|
||||||
|
// Used for backwards compatibility with legacy UI elements.
|
||||||
|
Element(Rml::Element *base);
|
||||||
|
|
||||||
|
// Used to actually construct elements.
|
||||||
|
Element(Element* parent, uint32_t events_enabled = 0, Rml::String base_class = "div", bool can_set_text = false);
|
||||||
|
virtual ~Element();
|
||||||
|
void clear_children();
|
||||||
|
bool remove_child(ResourceId child);
|
||||||
|
bool remove_child(Element *child) { return remove_child(child->get_resource_id()); }
|
||||||
|
void add_style(Style *style, std::string_view style_name);
|
||||||
|
void add_style(Style *style, const std::initializer_list<std::string_view> &style_names);
|
||||||
|
void set_enabled(bool enabled);
|
||||||
|
bool is_enabled() const;
|
||||||
|
void set_text(std::string_view text);
|
||||||
|
std::string get_input_text();
|
||||||
|
void set_input_text(std::string_view text);
|
||||||
|
void set_src(std::string_view src);
|
||||||
|
void set_style_enabled(std::string_view style_name, bool enabled);
|
||||||
|
bool is_style_enabled(std::string_view style_name);
|
||||||
|
void apply_styles();
|
||||||
|
bool is_element() override { return true; }
|
||||||
|
float get_absolute_left();
|
||||||
|
float get_absolute_top();
|
||||||
|
float get_client_left();
|
||||||
|
float get_client_top();
|
||||||
|
float get_client_width();
|
||||||
|
float get_client_height();
|
||||||
|
void enable_focus();
|
||||||
|
void focus();
|
||||||
|
void blur();
|
||||||
|
void queue_update();
|
||||||
|
void register_callback(ContextId context, PTR(void) callback, PTR(void) userdata);
|
||||||
|
uint32_t get_input_value_u32();
|
||||||
|
float get_input_value_float();
|
||||||
|
double get_input_value_double();
|
||||||
|
void set_input_value_u32(uint32_t val) { set_input_value(val); }
|
||||||
|
void set_input_value_float(float val) { set_input_value(val); }
|
||||||
|
void set_input_value_double(double val) { set_input_value(val); }
|
||||||
|
const std::string& get_id() { return id; }
|
||||||
|
};
|
||||||
|
|
||||||
|
void queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback);
|
||||||
|
|
||||||
|
} // namespace recompui
|
11
src/ui/elements/ui_image.cpp
Normal file
11
src/ui/elements/ui_image.cpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include "ui_image.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
Image::Image(Element *parent, std::string_view src) : Element(parent, 0, "img") {
|
||||||
|
set_src(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
14
src/ui/elements/ui_image.h
Normal file
14
src/ui/elements/ui_image.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class Image : public Element {
|
||||||
|
protected:
|
||||||
|
std::string_view get_type_name() override { return "ImageView"; }
|
||||||
|
public:
|
||||||
|
Image(Element *parent, std::string_view src);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
43
src/ui/elements/ui_label.cpp
Normal file
43
src/ui/elements/ui_label.cpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include "ui_label.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
Label::Label(Element *parent, LabelStyle label_style) : Element(parent, 0U, "div", true) {
|
||||||
|
switch (label_style) {
|
||||||
|
case LabelStyle::Annotation:
|
||||||
|
set_color(Color{ 185, 125, 242, 255 });
|
||||||
|
set_font_size(18.0f);
|
||||||
|
set_letter_spacing(2.52f);
|
||||||
|
set_line_height(18.0f);
|
||||||
|
set_font_weight(400);
|
||||||
|
break;
|
||||||
|
case LabelStyle::Small:
|
||||||
|
set_font_size(20.0f);
|
||||||
|
set_letter_spacing(0.0f);
|
||||||
|
set_line_height(20.0f);
|
||||||
|
set_font_weight(400);
|
||||||
|
break;
|
||||||
|
case LabelStyle::Normal:
|
||||||
|
set_font_size(28.0f);
|
||||||
|
set_letter_spacing(3.08f);
|
||||||
|
set_line_height(28.0f);
|
||||||
|
set_font_weight(700);
|
||||||
|
break;
|
||||||
|
case LabelStyle::Large:
|
||||||
|
set_font_size(36.0f);
|
||||||
|
set_letter_spacing(2.52f);
|
||||||
|
set_line_height(36.0f);
|
||||||
|
set_font_weight(700);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_font_style(FontStyle::Normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label::Label(Element *parent, const std::string &text, LabelStyle label_style) : Label(parent, label_style) {
|
||||||
|
set_text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
22
src/ui/elements/ui_label.h
Normal file
22
src/ui/elements/ui_label.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
enum class LabelStyle {
|
||||||
|
Annotation,
|
||||||
|
Small,
|
||||||
|
Normal,
|
||||||
|
Large
|
||||||
|
};
|
||||||
|
|
||||||
|
class Label : public Element {
|
||||||
|
protected:
|
||||||
|
std::string_view get_type_name() override { return "Label"; }
|
||||||
|
public:
|
||||||
|
Label(Element *parent, LabelStyle label_style);
|
||||||
|
Label(Element *parent, const std::string &text, LabelStyle label_style);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
240
src/ui/elements/ui_radio.cpp
Normal file
240
src/ui/elements/ui_radio.cpp
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#include "overloaded.h"
|
||||||
|
#include "ui_radio.h"
|
||||||
|
#include "../ui_utils.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
// RadioOption
|
||||||
|
|
||||||
|
RadioOption::RadioOption(Element *parent, std::string_view name, uint32_t index) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable, EventType::Update), "label", true) {
|
||||||
|
this->index = index;
|
||||||
|
|
||||||
|
enable_focus();
|
||||||
|
set_text(name);
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
set_font_size(20.0f);
|
||||||
|
set_letter_spacing(2.8f);
|
||||||
|
set_line_height(20.0f);
|
||||||
|
set_font_weight(400);
|
||||||
|
set_font_style(FontStyle::Normal);
|
||||||
|
set_border_color(Color{ 242, 242, 242, 0 });
|
||||||
|
set_border_bottom_width(1.0f);
|
||||||
|
set_color(Color{ 255, 255, 255, 153 });
|
||||||
|
set_padding_bottom(8.0f);
|
||||||
|
set_text_transform(TextTransform::Uppercase);
|
||||||
|
set_height_auto();
|
||||||
|
hover_style.set_color(Color{ 255, 255, 255, 204 });
|
||||||
|
checked_style.set_color(Color{ 255, 255, 255, 255 });
|
||||||
|
checked_style.set_border_color(Color{ 242, 242, 242, 255 });
|
||||||
|
pulsing_style.set_border_color(Color{ 23, 214, 232, 244 });
|
||||||
|
|
||||||
|
add_style(&hover_style, { hover_state });
|
||||||
|
add_style(&checked_style, { checked_state });
|
||||||
|
add_style(&pulsing_style, { focus_state });
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioOption::set_pressed_callback(std::function<void(uint32_t)> callback) {
|
||||||
|
pressed_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioOption::set_selected_state(bool enable) {
|
||||||
|
set_style_enabled(checked_state, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioOption::process_event(const Event &e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case EventType::Click:
|
||||||
|
pressed_callback(index);
|
||||||
|
break;
|
||||||
|
case EventType::Hover:
|
||||||
|
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active);
|
||||||
|
break;
|
||||||
|
case EventType::Enable:
|
||||||
|
set_style_enabled(disabled_state, !std::get<EventEnable>(e.variant).active);
|
||||||
|
break;
|
||||||
|
case EventType::Focus:
|
||||||
|
{
|
||||||
|
bool active = std::get<EventFocus>(e.variant).active;
|
||||||
|
set_style_enabled(focus_state, active);
|
||||||
|
if (active) {
|
||||||
|
queue_update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::Update:
|
||||||
|
if (is_style_enabled(focus_state)) {
|
||||||
|
pulsing_style.set_color(recompui::get_pulse_color(750));
|
||||||
|
apply_styles();
|
||||||
|
queue_update();
|
||||||
|
}
|
||||||
|
if (focus_queued) {
|
||||||
|
focus_queued = false;
|
||||||
|
focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radio
|
||||||
|
|
||||||
|
void Radio::set_index_internal(uint32_t index, bool setup, bool trigger_callbacks) {
|
||||||
|
if (this->index != index || setup) {
|
||||||
|
options[this->index]->set_selected_state(false);
|
||||||
|
this->index = index;
|
||||||
|
options[index]->set_selected_state(true);
|
||||||
|
|
||||||
|
if (trigger_callbacks) {
|
||||||
|
for (const auto &function : index_changed_callbacks) {
|
||||||
|
function(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::option_selected(uint32_t index) {
|
||||||
|
set_index_internal(index, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::set_input_value(const ElementValue& val) {
|
||||||
|
std::visit(overloaded {
|
||||||
|
[this](uint32_t u) { set_index(u); },
|
||||||
|
[this](float f) { set_index(f); },
|
||||||
|
[this](double d) { set_index(d); },
|
||||||
|
[](std::monostate) {}
|
||||||
|
}, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Radio::Radio(Element *parent) : Container(parent, FlexDirection::Row, JustifyContent::FlexStart, Events(EventType::Focus)) {
|
||||||
|
set_gap(24.0f);
|
||||||
|
set_align_items(AlignItems::FlexStart);
|
||||||
|
enable_focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::process_event(const Event &e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case EventType::Focus:
|
||||||
|
if (!options.empty()) {
|
||||||
|
if (std::get<EventFocus>(e.variant).active) {
|
||||||
|
blur();
|
||||||
|
options[index]->queue_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Radio::~Radio() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::add_option(std::string_view name) {
|
||||||
|
RadioOption *option = get_current_context().create_element<RadioOption>(this, name, uint32_t(options.size()));
|
||||||
|
option->set_pressed_callback([this](uint32_t index){ option_selected(index); });
|
||||||
|
options.emplace_back(option);
|
||||||
|
|
||||||
|
// The first option was added, select it.
|
||||||
|
if (options.size() == 1) {
|
||||||
|
set_index_internal(0, true, false);
|
||||||
|
}
|
||||||
|
// At least one other option already existed, so set up navigation.
|
||||||
|
else {
|
||||||
|
options[options.size() - 2]->set_nav(NavDirection::Right, options[options.size() - 1]);
|
||||||
|
options[options.size() - 1]->set_nav(NavDirection::Left, options[options.size() - 2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::set_index(uint32_t index) {
|
||||||
|
set_index_internal(index, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Radio::get_index() const {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::add_index_changed_callback(std::function<void(uint32_t)> callback) {
|
||||||
|
index_changed_callbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::set_nav_auto(NavDirection dir) {
|
||||||
|
Element::set_nav_auto(dir);
|
||||||
|
if (!options.empty()) {
|
||||||
|
switch (dir) {
|
||||||
|
case NavDirection::Up:
|
||||||
|
case NavDirection::Down:
|
||||||
|
for (Element* e : options) {
|
||||||
|
e->set_nav_auto(dir);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavDirection::Left:
|
||||||
|
options.front()->set_nav_auto(dir);
|
||||||
|
break;
|
||||||
|
case NavDirection::Right:
|
||||||
|
options.back()->set_nav_auto(dir);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::set_nav_none(NavDirection dir) {
|
||||||
|
Element::set_nav_none(dir);
|
||||||
|
if (!options.empty()) {
|
||||||
|
switch (dir) {
|
||||||
|
case NavDirection::Up:
|
||||||
|
case NavDirection::Down:
|
||||||
|
for (Element* e : options) {
|
||||||
|
e->set_nav_none(dir);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavDirection::Left:
|
||||||
|
options.front()->set_nav_none(dir);
|
||||||
|
break;
|
||||||
|
case NavDirection::Right:
|
||||||
|
options.back()->set_nav_none(dir);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::set_nav(NavDirection dir, Element* element) {
|
||||||
|
Element::set_nav(dir, element);
|
||||||
|
if (!options.empty()) {
|
||||||
|
switch (dir) {
|
||||||
|
case NavDirection::Up:
|
||||||
|
case NavDirection::Down:
|
||||||
|
for (Element* e : options) {
|
||||||
|
e->set_nav(dir, element);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavDirection::Left:
|
||||||
|
options.front()->set_nav(dir, element);
|
||||||
|
break;
|
||||||
|
case NavDirection::Right:
|
||||||
|
options.back()->set_nav(dir, element);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Radio::set_nav_manual(NavDirection dir, const std::string& target) {
|
||||||
|
Element::set_nav_manual(dir, target);
|
||||||
|
if (!options.empty()) {
|
||||||
|
switch (dir) {
|
||||||
|
case NavDirection::Up:
|
||||||
|
case NavDirection::Down:
|
||||||
|
for (Element* e : options) {
|
||||||
|
e->set_nav_manual(dir, target);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavDirection::Left:
|
||||||
|
options.front()->set_nav_manual(dir, target);
|
||||||
|
break;
|
||||||
|
case NavDirection::Right:
|
||||||
|
options.back()->set_nav_manual(dir, target);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
54
src/ui/elements/ui_radio.h
Normal file
54
src/ui/elements/ui_radio.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_container.h"
|
||||||
|
#include "ui_label.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
class RadioOption : public Element {
|
||||||
|
private:
|
||||||
|
Style hover_style;
|
||||||
|
Style checked_style;
|
||||||
|
Style pulsing_style;
|
||||||
|
std::function<void(uint32_t)> pressed_callback = nullptr;
|
||||||
|
uint32_t index = 0;
|
||||||
|
bool focus_queued = false;
|
||||||
|
protected:
|
||||||
|
virtual void process_event(const Event &e) override;
|
||||||
|
std::string_view get_type_name() override { return "LabelRadioOption"; }
|
||||||
|
public:
|
||||||
|
RadioOption(Element *parent, std::string_view name, uint32_t index);
|
||||||
|
void set_pressed_callback(std::function<void(uint32_t)> callback);
|
||||||
|
void set_selected_state(bool enable);
|
||||||
|
void queue_focus() { focus_queued = true; queue_update(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Radio : public Container {
|
||||||
|
private:
|
||||||
|
std::vector<RadioOption *> options;
|
||||||
|
uint32_t index = 0;
|
||||||
|
std::vector<std::function<void(uint32_t)>> index_changed_callbacks;
|
||||||
|
|
||||||
|
void set_index_internal(uint32_t index, bool setup, bool trigger_callbacks);
|
||||||
|
void option_selected(uint32_t index);
|
||||||
|
void set_input_value(const ElementValue& val) override;
|
||||||
|
ElementValue get_element_value() override { return get_index(); }
|
||||||
|
protected:
|
||||||
|
virtual void process_event(const Event &e) override;
|
||||||
|
std::string_view get_type_name() override { return "LabelRadio"; }
|
||||||
|
public:
|
||||||
|
Radio(Element *parent);
|
||||||
|
virtual ~Radio();
|
||||||
|
void add_option(std::string_view name);
|
||||||
|
void set_index(uint32_t index);
|
||||||
|
uint32_t get_index() const;
|
||||||
|
void add_index_changed_callback(std::function<void(uint32_t)> callback);
|
||||||
|
size_t num_options() const { return options.size(); }
|
||||||
|
RadioOption* get_option_element(size_t option_index) { return options[option_index]; }
|
||||||
|
RadioOption* get_current_option_element() { return options.empty() ? nullptr : options[index]; }
|
||||||
|
void set_nav_auto(NavDirection dir) override;
|
||||||
|
void set_nav_none(NavDirection dir) override;
|
||||||
|
void set_nav(NavDirection dir, Element* element) override;
|
||||||
|
void set_nav_manual(NavDirection dir, const std::string& target) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
27
src/ui/elements/ui_scroll_container.cpp
Normal file
27
src/ui/elements/ui_scroll_container.cpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#include "ui_scroll_container.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
ScrollContainer::ScrollContainer(Element *parent, ScrollDirection direction) : Element(parent) {
|
||||||
|
set_flex(1.0f, 1.0f, 100.0f);
|
||||||
|
set_width(100.0f, Unit::Percent);
|
||||||
|
set_height(100.0f, Unit::Percent);
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case ScrollDirection::Horizontal:
|
||||||
|
set_max_width(100.0f, Unit::Percent);
|
||||||
|
set_overflow_x(Overflow::Auto);
|
||||||
|
break;
|
||||||
|
case ScrollDirection::Vertical:
|
||||||
|
set_max_height(100.0f, Unit::Percent);
|
||||||
|
set_overflow_y(Overflow::Auto);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown scroll direction.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
19
src/ui/elements/ui_scroll_container.h
Normal file
19
src/ui/elements/ui_scroll_container.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
enum class ScrollDirection {
|
||||||
|
Horizontal,
|
||||||
|
Vertical
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScrollContainer : public Element {
|
||||||
|
protected:
|
||||||
|
std::string_view get_type_name() override { return "ScrollContainer"; }
|
||||||
|
public:
|
||||||
|
ScrollContainer(Element *parent, ScrollDirection direction);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
236
src/ui/elements/ui_slider.cpp
Normal file
236
src/ui/elements/ui_slider.cpp
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
#include "overloaded.h"
|
||||||
|
#include "ui_slider.h"
|
||||||
|
#include "../ui_utils.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <charconv>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
void Slider::set_value_internal(double v, bool setup, bool trigger_callbacks) {
|
||||||
|
if (step_value != 0.0) {
|
||||||
|
v = std::lround(v / step_value) * step_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != v || setup) {
|
||||||
|
value = v;
|
||||||
|
update_circle_position();
|
||||||
|
update_label_text();
|
||||||
|
|
||||||
|
if (trigger_callbacks) {
|
||||||
|
for (auto callback : value_changed_callbacks) {
|
||||||
|
callback(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::bar_clicked(float x, float) {
|
||||||
|
update_value_from_mouse(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::bar_dragged(float x, float, DragPhase) {
|
||||||
|
update_value_from_mouse(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::circle_dragged(float x, float, DragPhase) {
|
||||||
|
update_value_from_mouse(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::update_value_from_mouse(float x) {
|
||||||
|
double left = slider_element->get_absolute_left();
|
||||||
|
double width = slider_element->get_client_width();
|
||||||
|
double ratio = std::clamp((x - left) / width, 0.0, 1.0);
|
||||||
|
set_value_internal(min_value + ratio * (max_value - min_value), false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::update_circle_position() {
|
||||||
|
double ratio = std::clamp((value - min_value) / (max_value - min_value), 0.0, 1.0);
|
||||||
|
circle_element->set_left(ratio * 100.0, Unit::Percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::update_label_text() {
|
||||||
|
char text_buffer[32];
|
||||||
|
|
||||||
|
if (type == SliderType::Double) {
|
||||||
|
std::snprintf(text_buffer, sizeof(text_buffer), "%.1f", value);
|
||||||
|
} else if (type == SliderType::Percent) {
|
||||||
|
std::snprintf(text_buffer, sizeof(text_buffer), "%d%%", static_cast<int>(value));
|
||||||
|
} else {
|
||||||
|
std::snprintf(text_buffer, sizeof(text_buffer), "%d", static_cast<int>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
value_label->set_text(text_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::set_input_value(const ElementValue& val) {
|
||||||
|
std::visit(overloaded {
|
||||||
|
[this](uint32_t u) { set_value(u); },
|
||||||
|
[this](float f) { set_value(f); },
|
||||||
|
[this](double d) { set_value(d); },
|
||||||
|
[](std::monostate) {}
|
||||||
|
}, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::process_event(const Event& e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case EventType::Focus:
|
||||||
|
{
|
||||||
|
bool active = std::get<EventFocus>(e.variant).active;
|
||||||
|
circle_element->set_style_enabled(focus_state, active);
|
||||||
|
if (active) {
|
||||||
|
queue_update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::Update:
|
||||||
|
if (is_enabled()) {
|
||||||
|
if (circle_element->is_style_enabled(focus_state)) {
|
||||||
|
circle_element->set_background_color(recompui::get_pulse_color(750));
|
||||||
|
queue_update();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
circle_element->set_background_color(Color{ 204, 204, 204, 255 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
circle_element->set_background_color(Color{ 102, 102, 102, 255 });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::Navigate:
|
||||||
|
{
|
||||||
|
NavDirection dir = std::get<EventNavigate>(e.variant).direction;
|
||||||
|
if (dir == NavDirection::Left) {
|
||||||
|
do_step(false);
|
||||||
|
}
|
||||||
|
else if (dir == NavDirection::Right) {
|
||||||
|
do_step(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::Enable:
|
||||||
|
{
|
||||||
|
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||||
|
circle_element->set_enabled(enable_active);
|
||||||
|
if (enable_active) {
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
set_focusable(true);
|
||||||
|
circle_element->set_background_color(Color{ 204, 204, 204, 255 });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
set_cursor(Cursor::None);
|
||||||
|
set_focusable(false);
|
||||||
|
circle_element->set_background_color(Color{ 102, 102, 102, 255 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider::Slider(Element *parent, SliderType type) : Element(parent, Events(EventType::Focus, EventType::Update, EventType::Navigate, EventType::Enable)) {
|
||||||
|
this->type = type;
|
||||||
|
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
set_display(Display::Flex);
|
||||||
|
set_flex_direction(FlexDirection::Row);
|
||||||
|
set_text_align(TextAlign::Left);
|
||||||
|
set_min_width(120.0f);
|
||||||
|
|
||||||
|
enable_focus();
|
||||||
|
set_nav_none(NavDirection::Left);
|
||||||
|
set_nav_none(NavDirection::Right);
|
||||||
|
|
||||||
|
ContextId context = get_current_context();
|
||||||
|
|
||||||
|
value_label = context.create_element<Label>(this, "0", LabelStyle::Small);
|
||||||
|
value_label->set_margin_right(20.0f);
|
||||||
|
value_label->set_min_width(60.0f);
|
||||||
|
value_label->set_max_width(60.0f);
|
||||||
|
|
||||||
|
slider_element = context.create_element<Clickable>(this, true);
|
||||||
|
slider_element->set_flex(1.0f, 0.0f);
|
||||||
|
slider_element->add_pressed_callback([this](float x, float y){ bar_clicked(x, y); focus(); });
|
||||||
|
slider_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); focus(); });
|
||||||
|
|
||||||
|
{
|
||||||
|
bar_element = context.create_element<Clickable>(slider_element, true);
|
||||||
|
bar_element->set_width(100.0f, Unit::Percent);
|
||||||
|
bar_element->set_height(2.0f);
|
||||||
|
bar_element->set_margin_top(8.0f);
|
||||||
|
bar_element->set_background_color(Color{ 255, 255, 255, 50 });
|
||||||
|
bar_element->add_pressed_callback([this](float x, float y){ bar_clicked(x, y); focus(); });
|
||||||
|
bar_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); focus(); });
|
||||||
|
|
||||||
|
circle_element = context.create_element<Clickable>(bar_element, true);
|
||||||
|
circle_element->set_position(Position::Relative);
|
||||||
|
circle_element->set_width(16.0f);
|
||||||
|
circle_element->set_height(16.0f);
|
||||||
|
circle_element->set_margin_top(-7.0f);
|
||||||
|
circle_element->set_margin_right(-8.0f);
|
||||||
|
circle_element->set_margin_left(-8.0f);
|
||||||
|
circle_element->set_background_color(Color{ 204, 204, 204, 255 });
|
||||||
|
circle_element->set_border_radius(8.0f);
|
||||||
|
circle_element->add_pressed_callback([this](float, float){ focus(); });
|
||||||
|
circle_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ circle_dragged(x, y, phase); focus(); });
|
||||||
|
circle_element->set_cursor(Cursor::Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_value_internal(value, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider::~Slider() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::set_value(double v) {
|
||||||
|
set_value_internal(v, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Slider::get_value() const {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
void Slider::set_min_value(double v) {
|
||||||
|
min_value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Slider::get_min_value() const {
|
||||||
|
return min_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::set_max_value(double v) {
|
||||||
|
max_value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Slider::get_max_value() const {
|
||||||
|
return max_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::set_step_value(double v) {
|
||||||
|
step_value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Slider::get_step_value() const {
|
||||||
|
return step_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::add_value_changed_callback(std::function<void(double)> callback) {
|
||||||
|
value_changed_callbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider::do_step(bool increment) {
|
||||||
|
double new_value = value;
|
||||||
|
if (increment) {
|
||||||
|
new_value += step_value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new_value -= step_value;
|
||||||
|
}
|
||||||
|
new_value = std::clamp(new_value, min_value, max_value);
|
||||||
|
if (new_value != value) {
|
||||||
|
set_value_internal(new_value, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace recompui
|
56
src/ui/elements/ui_slider.h
Normal file
56
src/ui/elements/ui_slider.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_clickable.h"
|
||||||
|
#include "ui_label.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
enum SliderType {
|
||||||
|
Double,
|
||||||
|
Percent,
|
||||||
|
Integer
|
||||||
|
};
|
||||||
|
|
||||||
|
class Slider : public Element {
|
||||||
|
private:
|
||||||
|
SliderType type = SliderType::Percent;
|
||||||
|
Label *value_label = nullptr;
|
||||||
|
Clickable *slider_element = nullptr;
|
||||||
|
Clickable *bar_element = nullptr;
|
||||||
|
Clickable *circle_element = nullptr;
|
||||||
|
double value = 50.0;
|
||||||
|
double min_value = 0.0;
|
||||||
|
double max_value = 100.0;
|
||||||
|
double step_value = 0.0;
|
||||||
|
std::vector<std::function<void(double)>> value_changed_callbacks;
|
||||||
|
|
||||||
|
void set_value_internal(double v, bool setup, bool trigger_callbacks);
|
||||||
|
void bar_clicked(float x, float y);
|
||||||
|
void bar_dragged(float x, float y, DragPhase phase);
|
||||||
|
void circle_dragged(float x, float y, DragPhase phase);
|
||||||
|
void update_value_from_mouse(float x);
|
||||||
|
void update_circle_position();
|
||||||
|
void update_label_text();
|
||||||
|
void set_input_value(const ElementValue& val) override;
|
||||||
|
ElementValue get_element_value() override { return get_value(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void process_event(const Event &e) override;
|
||||||
|
std::string_view get_type_name() override { return "Slider"; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
Slider(Element *parent, SliderType type);
|
||||||
|
virtual ~Slider();
|
||||||
|
void set_value(double v);
|
||||||
|
double get_value() const;
|
||||||
|
void set_min_value(double v);
|
||||||
|
double get_min_value() const;
|
||||||
|
void set_max_value(double v);
|
||||||
|
double get_max_value() const;
|
||||||
|
void set_step_value(double v);
|
||||||
|
double get_step_value() const;
|
||||||
|
void add_value_changed_callback(std::function<void(double)> callback);
|
||||||
|
void do_step(bool increment);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
15
src/ui/elements/ui_span.cpp
Normal file
15
src/ui/elements/ui_span.cpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include "ui_span.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
Span::Span(Element *parent) : Element(parent, 0, "span", true) {
|
||||||
|
set_font_style(FontStyle::Normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span::Span(Element *parent, const std::string &text) : Span(parent) {
|
||||||
|
set_text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
16
src/ui/elements/ui_span.h
Normal file
16
src/ui/elements/ui_span.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
#include "ui_label.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class Span : public Element {
|
||||||
|
protected:
|
||||||
|
std::string_view get_type_name() override { return "Span"; }
|
||||||
|
public:
|
||||||
|
Span(Element *parent);
|
||||||
|
Span(Element *parent, const std::string &text);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
612
src/ui/elements/ui_style.cpp
Normal file
612
src/ui/elements/ui_style.cpp
Normal file
@@ -0,0 +1,612 @@
|
|||||||
|
#include "ui_style.h"
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
static Rml::Unit to_rml(Unit unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case Unit::Px:
|
||||||
|
return Rml::Unit::PX;
|
||||||
|
case Unit::Dp:
|
||||||
|
return Rml::Unit::DP;
|
||||||
|
case Unit::Percent:
|
||||||
|
return Rml::Unit::PERCENT;
|
||||||
|
default:
|
||||||
|
return Rml::Unit::UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::Style::AlignItems to_rml(AlignItems align_items) {
|
||||||
|
switch (align_items) {
|
||||||
|
case AlignItems::FlexStart:
|
||||||
|
return Rml::Style::AlignItems::FlexStart;
|
||||||
|
case AlignItems::FlexEnd:
|
||||||
|
return Rml::Style::AlignItems::FlexEnd;
|
||||||
|
case AlignItems::Center:
|
||||||
|
return Rml::Style::AlignItems::Center;
|
||||||
|
case AlignItems::Baseline:
|
||||||
|
return Rml::Style::AlignItems::Baseline;
|
||||||
|
case AlignItems::Stretch:
|
||||||
|
return Rml::Style::AlignItems::Stretch;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown align items.");
|
||||||
|
return Rml::Style::AlignItems::FlexStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::Style::Overflow to_rml(Overflow overflow) {
|
||||||
|
switch (overflow) {
|
||||||
|
case Overflow::Visible:
|
||||||
|
return Rml::Style::Overflow::Visible;
|
||||||
|
case Overflow::Hidden:
|
||||||
|
return Rml::Style::Overflow::Hidden;
|
||||||
|
case Overflow::Auto:
|
||||||
|
return Rml::Style::Overflow::Auto;
|
||||||
|
case Overflow::Scroll:
|
||||||
|
return Rml::Style::Overflow::Scroll;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown overflow.");
|
||||||
|
return Rml::Style::Overflow::Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::Style::TextAlign to_rml(TextAlign text_align) {
|
||||||
|
switch (text_align) {
|
||||||
|
case TextAlign::Left:
|
||||||
|
return Rml::Style::TextAlign::Left;
|
||||||
|
case TextAlign::Right:
|
||||||
|
return Rml::Style::TextAlign::Right;
|
||||||
|
case TextAlign::Center:
|
||||||
|
return Rml::Style::TextAlign::Center;
|
||||||
|
case TextAlign::Justify:
|
||||||
|
return Rml::Style::TextAlign::Justify;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown text align.");
|
||||||
|
return Rml::Style::TextAlign::Left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::Style::TextTransform to_rml(TextTransform text_transform) {
|
||||||
|
switch (text_transform) {
|
||||||
|
case TextTransform::None:
|
||||||
|
return Rml::Style::TextTransform::None;
|
||||||
|
case TextTransform::Capitalize:
|
||||||
|
return Rml::Style::TextTransform::Capitalize;
|
||||||
|
case TextTransform::Uppercase:
|
||||||
|
return Rml::Style::TextTransform::Uppercase;
|
||||||
|
case TextTransform::Lowercase:
|
||||||
|
return Rml::Style::TextTransform::Lowercase;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown text transform.");
|
||||||
|
return Rml::Style::TextTransform::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::Style::Drag to_rml(Drag drag) {
|
||||||
|
switch (drag) {
|
||||||
|
case Drag::None:
|
||||||
|
return Rml::Style::Drag::None;
|
||||||
|
case Drag::Drag:
|
||||||
|
return Rml::Style::Drag::Drag;
|
||||||
|
case Drag::DragDrop:
|
||||||
|
return Rml::Style::Drag::DragDrop;
|
||||||
|
case Drag::Block:
|
||||||
|
return Rml::Style::Drag::Block;
|
||||||
|
case Drag::Clone:
|
||||||
|
return Rml::Style::Drag::Clone;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown drag.");
|
||||||
|
return Rml::Style::Drag::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::Style::TabIndex to_rml(TabIndex tab_index) {
|
||||||
|
switch (tab_index) {
|
||||||
|
case TabIndex::None:
|
||||||
|
return Rml::Style::TabIndex::None;
|
||||||
|
case TabIndex::Auto:
|
||||||
|
return Rml::Style::TabIndex::Auto;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown tab index.");
|
||||||
|
return Rml::Style::TabIndex::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::Style::Display to_rml(Display display) {
|
||||||
|
switch (display) {
|
||||||
|
case Display::None:
|
||||||
|
return Rml::Style::Display::None;
|
||||||
|
case Display::Block:
|
||||||
|
return Rml::Style::Display::Block;
|
||||||
|
case Display::Inline:
|
||||||
|
return Rml::Style::Display::Inline;
|
||||||
|
case Display::InlineBlock:
|
||||||
|
return Rml::Style::Display::InlineBlock;
|
||||||
|
case Display::FlowRoot:
|
||||||
|
return Rml::Style::Display::FlowRoot;
|
||||||
|
case Display::Flex:
|
||||||
|
return Rml::Style::Display::Flex;
|
||||||
|
case Display::InlineFlex:
|
||||||
|
return Rml::Style::Display::InlineFlex;
|
||||||
|
case Display::Table:
|
||||||
|
return Rml::Style::Display::Table;
|
||||||
|
case Display::InlineTable:
|
||||||
|
return Rml::Style::Display::InlineTable;
|
||||||
|
case Display::TableRow:
|
||||||
|
return Rml::Style::Display::TableRow;
|
||||||
|
case Display::TableRowGroup:
|
||||||
|
return Rml::Style::Display::TableRowGroup;
|
||||||
|
case Display::TableColumn:
|
||||||
|
return Rml::Style::Display::TableColumn;
|
||||||
|
case Display::TableColumnGroup:
|
||||||
|
return Rml::Style::Display::TableColumnGroup;
|
||||||
|
case Display::TableCell:
|
||||||
|
return Rml::Style::Display::TableCell;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown display.");
|
||||||
|
return Rml::Style::Display::Block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::Style::JustifyContent to_rml(JustifyContent justify_content) {
|
||||||
|
switch (justify_content) {
|
||||||
|
case JustifyContent::FlexStart:
|
||||||
|
return Rml::Style::JustifyContent::FlexStart;
|
||||||
|
case JustifyContent::FlexEnd:
|
||||||
|
return Rml::Style::JustifyContent::FlexEnd;
|
||||||
|
case JustifyContent::Center:
|
||||||
|
return Rml::Style::JustifyContent::Center;
|
||||||
|
case JustifyContent::SpaceBetween:
|
||||||
|
return Rml::Style::JustifyContent::SpaceBetween;
|
||||||
|
case JustifyContent::SpaceAround:
|
||||||
|
return Rml::Style::JustifyContent::SpaceAround;
|
||||||
|
case JustifyContent::SpaceEvenly:
|
||||||
|
return Rml::Style::JustifyContent::SpaceEvenly;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown justify content.");
|
||||||
|
return Rml::Style::JustifyContent::FlexStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::PropertyId nav_to_property(NavDirection dir) {
|
||||||
|
switch (dir) {
|
||||||
|
case NavDirection::Up:
|
||||||
|
return Rml::PropertyId::NavUp;
|
||||||
|
case NavDirection::Right:
|
||||||
|
return Rml::PropertyId::NavRight;
|
||||||
|
case NavDirection::Down:
|
||||||
|
return Rml::PropertyId::NavDown;
|
||||||
|
case NavDirection::Left:
|
||||||
|
return Rml::PropertyId::NavLeft;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown nav direction.");
|
||||||
|
return Rml::PropertyId::Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_property(Rml::PropertyId property_id, const Rml::Property &property) {
|
||||||
|
property_map[property_id] = property;
|
||||||
|
}
|
||||||
|
|
||||||
|
Style::Style() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Style::~Style() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_visibility(Visibility visibility) {
|
||||||
|
switch (visibility) {
|
||||||
|
case Visibility::Visible:
|
||||||
|
set_property(Rml::PropertyId::Visibility, Rml::Style::Visibility::Visible);
|
||||||
|
break;
|
||||||
|
case Visibility::Hidden:
|
||||||
|
set_property(Rml::PropertyId::Visibility, Rml::Style::Visibility::Hidden);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_position(Position position) {
|
||||||
|
switch (position) {
|
||||||
|
case Position::Absolute:
|
||||||
|
set_property(Rml::PropertyId::Position, Rml::Style::Position::Absolute);
|
||||||
|
break;
|
||||||
|
case Position::Relative:
|
||||||
|
set_property(Rml::PropertyId::Position, Rml::Style::Position::Relative);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown position.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_left(float left, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::Left, Rml::Property(left, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_top(float top, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::Top, Rml::Property(top, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_right(float right, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::Right, Rml::Property(right, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_bottom(float bottom, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::Bottom, Rml::Property(bottom, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_width(float width, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::Width, Rml::Property(width, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_width_auto() {
|
||||||
|
set_property(Rml::PropertyId::Width, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_height(float height, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::Height, Rml::Property(height, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_height_auto() {
|
||||||
|
set_property(Rml::PropertyId::Height, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_min_width(float width, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MinWidth, Rml::Property(width, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_min_height(float height, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MinHeight, Rml::Property(height, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_max_width(float width, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MaxWidth, Rml::Property(width, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_max_height(float height, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MaxHeight, Rml::Property(height, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_padding(float padding, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::PaddingLeft, Rml::Property(padding, to_rml(unit)));
|
||||||
|
set_property(Rml::PropertyId::PaddingTop, Rml::Property(padding, to_rml(unit)));
|
||||||
|
set_property(Rml::PropertyId::PaddingRight, Rml::Property(padding, to_rml(unit)));
|
||||||
|
set_property(Rml::PropertyId::PaddingBottom, Rml::Property(padding, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_padding_left(float padding, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::PaddingLeft, Rml::Property(padding, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_padding_top(float padding, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::PaddingTop, Rml::Property(padding, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_padding_right(float padding, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::PaddingRight, Rml::Property(padding, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_padding_bottom(float padding, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::PaddingBottom, Rml::Property(padding, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin(float margin, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MarginLeft, Rml::Property(margin, to_rml(unit)));
|
||||||
|
set_property(Rml::PropertyId::MarginTop, Rml::Property(margin, to_rml(unit)));
|
||||||
|
set_property(Rml::PropertyId::MarginRight, Rml::Property(margin, to_rml(unit)));
|
||||||
|
set_property(Rml::PropertyId::MarginBottom, Rml::Property(margin, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_left(float margin, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MarginLeft, Rml::Property(margin, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_top(float margin, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MarginTop, Rml::Property(margin, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_right(float margin, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MarginRight, Rml::Property(margin, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_bottom(float margin, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::MarginBottom, Rml::Property(margin, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_auto() {
|
||||||
|
set_property(Rml::PropertyId::MarginLeft, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
set_property(Rml::PropertyId::MarginTop, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
set_property(Rml::PropertyId::MarginRight, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
set_property(Rml::PropertyId::MarginBottom, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_left_auto() {
|
||||||
|
set_property(Rml::PropertyId::MarginLeft, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_top_auto() {
|
||||||
|
set_property(Rml::PropertyId::MarginTop, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_right_auto() {
|
||||||
|
set_property(Rml::PropertyId::MarginRight, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_margin_bottom_auto() {
|
||||||
|
set_property(Rml::PropertyId::MarginBottom, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_width(float width, Unit unit) {
|
||||||
|
Rml::Property property(width, to_rml(unit));
|
||||||
|
set_property(Rml::PropertyId::BorderTopWidth, property);
|
||||||
|
set_property(Rml::PropertyId::BorderBottomWidth, property);
|
||||||
|
set_property(Rml::PropertyId::BorderLeftWidth, property);
|
||||||
|
set_property(Rml::PropertyId::BorderRightWidth, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_left_width(float width, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::BorderLeftWidth, Rml::Property(width, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_top_width(float width, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::BorderTopWidth, Rml::Property(width, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_right_width(float width, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::BorderRightWidth, Rml::Property(width, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_bottom_width(float width, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::BorderBottomWidth, Rml::Property(width, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_radius(float radius, Unit unit) {
|
||||||
|
Rml::Property property(radius, to_rml(unit));
|
||||||
|
set_property(Rml::PropertyId::BorderTopLeftRadius, property);
|
||||||
|
set_property(Rml::PropertyId::BorderTopRightRadius, property);
|
||||||
|
set_property(Rml::PropertyId::BorderBottomLeftRadius, property);
|
||||||
|
set_property(Rml::PropertyId::BorderBottomRightRadius, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_top_left_radius(float radius, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::BorderTopLeftRadius, Rml::Property(radius, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_top_right_radius(float radius, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::BorderTopRightRadius, Rml::Property(radius, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_bottom_left_radius(float radius, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::BorderBottomLeftRadius, Rml::Property(radius, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_bottom_right_radius(float radius, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::BorderBottomRightRadius, Rml::Property(radius, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_background_color(const Color &color) {
|
||||||
|
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||||
|
set_property(Rml::PropertyId::BackgroundColor, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_color(const Color &color) {
|
||||||
|
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||||
|
set_property(Rml::PropertyId::BorderTopColor, property);
|
||||||
|
set_property(Rml::PropertyId::BorderBottomColor, property);
|
||||||
|
set_property(Rml::PropertyId::BorderLeftColor, property);
|
||||||
|
set_property(Rml::PropertyId::BorderRightColor, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_left_color(const Color &color) {
|
||||||
|
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||||
|
set_property(Rml::PropertyId::BorderLeftColor, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_top_color(const Color &color) {
|
||||||
|
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||||
|
set_property(Rml::PropertyId::BorderTopColor, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_right_color(const Color &color) {
|
||||||
|
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||||
|
set_property(Rml::PropertyId::BorderRightColor, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_border_bottom_color(const Color &color) {
|
||||||
|
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||||
|
set_property(Rml::PropertyId::BorderBottomColor, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_color(const Color &color) {
|
||||||
|
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||||
|
set_property(Rml::PropertyId::Color, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_cursor(Cursor cursor) {
|
||||||
|
switch (cursor) {
|
||||||
|
case Cursor::None:
|
||||||
|
set_property(Rml::PropertyId::Cursor, Rml::Property("", Rml::Unit::STRING));
|
||||||
|
break;
|
||||||
|
case Cursor::Pointer:
|
||||||
|
set_property(Rml::PropertyId::Cursor, Rml::Property("pointer", Rml::Unit::STRING));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown cursor.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_opacity(float opacity) {
|
||||||
|
set_property(Rml::PropertyId::Opacity, Rml::Property(opacity, Rml::Unit::NUMBER));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_display(Display display) {
|
||||||
|
set_property(Rml::PropertyId::Display, to_rml(display));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_justify_content(JustifyContent justify_content) {
|
||||||
|
set_property(Rml::PropertyId::JustifyContent, to_rml(justify_content));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_flex_grow(float grow) {
|
||||||
|
set_property(Rml::PropertyId::FlexGrow, Rml::Property(grow, Rml::Unit::NUMBER));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_flex_shrink(float shrink) {
|
||||||
|
set_property(Rml::PropertyId::FlexShrink, Rml::Property(shrink, Rml::Unit::NUMBER));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_flex_basis_auto() {
|
||||||
|
set_property(Rml::PropertyId::FlexBasis, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_flex_basis(float basis, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::FlexBasis, Rml::Property(basis, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_flex(float grow, float shrink) {
|
||||||
|
set_flex_grow(grow);
|
||||||
|
set_flex_shrink(shrink);
|
||||||
|
set_flex_basis_auto();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_flex(float grow, float shrink, float basis, Unit basis_unit) {
|
||||||
|
set_flex_grow(grow);
|
||||||
|
set_flex_shrink(shrink);
|
||||||
|
set_flex_basis(basis, basis_unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_flex_direction(FlexDirection flex_direction) {
|
||||||
|
switch (flex_direction) {
|
||||||
|
case FlexDirection::Row:
|
||||||
|
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row);
|
||||||
|
break;
|
||||||
|
case FlexDirection::Column:
|
||||||
|
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column);
|
||||||
|
break;
|
||||||
|
case FlexDirection::RowReverse:
|
||||||
|
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::RowReverse);
|
||||||
|
break;
|
||||||
|
case FlexDirection::ColumnReverse:
|
||||||
|
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::ColumnReverse);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown flex direction.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_align_items(AlignItems align_items) {
|
||||||
|
set_property(Rml::PropertyId::AlignItems, to_rml(align_items));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_overflow(Overflow overflow) {
|
||||||
|
set_property(Rml::PropertyId::OverflowX, to_rml(overflow));
|
||||||
|
set_property(Rml::PropertyId::OverflowY, to_rml(overflow));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_overflow_x(Overflow overflow) {
|
||||||
|
set_property(Rml::PropertyId::OverflowX, to_rml(overflow));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_overflow_y(Overflow overflow) {
|
||||||
|
set_property(Rml::PropertyId::OverflowY, to_rml(overflow));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_font_size(float size, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::FontSize, Rml::Property(size, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_letter_spacing(float spacing, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::LetterSpacing, Rml::Property(spacing, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_line_height(float height, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::LineHeight, Rml::Property(height, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_font_style(FontStyle style) {
|
||||||
|
switch (style) {
|
||||||
|
case FontStyle::Normal:
|
||||||
|
set_property(Rml::PropertyId::FontStyle, Rml::Style::FontStyle::Normal);
|
||||||
|
break;
|
||||||
|
case FontStyle::Italic:
|
||||||
|
set_property(Rml::PropertyId::FontStyle, Rml::Style::FontStyle::Italic);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown font style.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_font_weight(uint32_t weight) {
|
||||||
|
set_property(Rml::PropertyId::FontWeight, Rml::Style::FontWeight(weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_text_align(TextAlign text_align) {
|
||||||
|
set_property(Rml::PropertyId::TextAlign, to_rml(text_align));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_text_transform(TextTransform text_transform) {
|
||||||
|
set_property(Rml::PropertyId::TextTransform, to_rml(text_transform));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_gap(float size, Unit unit) {
|
||||||
|
set_row_gap(size, unit);
|
||||||
|
set_column_gap(size, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_row_gap(float size, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::RowGap, Rml::Property(size, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_column_gap(float size, Unit unit) {
|
||||||
|
set_property(Rml::PropertyId::ColumnGap, Rml::Property(size, to_rml(unit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_drag(Drag drag) {
|
||||||
|
set_property(Rml::PropertyId::Drag, to_rml(drag));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_tab_index(TabIndex tab_index) {
|
||||||
|
set_property(Rml::PropertyId::TabIndex, to_rml(tab_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_font_family(std::string_view family) {
|
||||||
|
set_property(Rml::PropertyId::FontFamily, Rml::Property(Rml::String{ family }, Rml::Unit::UNKNOWN));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_nav_auto(NavDirection dir) {
|
||||||
|
set_property(nav_to_property(dir), Rml::Style::Nav::Auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_nav_none(NavDirection dir) {
|
||||||
|
set_property(nav_to_property(dir), Rml::Style::Nav::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_nav(NavDirection dir, Element* element) {
|
||||||
|
set_property(nav_to_property(dir), Rml::Property(Rml::String{ "#" + element->get_id() }, Rml::Unit::STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_nav_manual(NavDirection dir, const std::string& target) {
|
||||||
|
set_property(nav_to_property(dir), Rml::Property(target, Rml::Unit::STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_tab_index_auto() {
|
||||||
|
set_property(Rml::PropertyId::TabIndex, Rml::Style::Nav::Auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_tab_index_none() {
|
||||||
|
set_property(Rml::PropertyId::TabIndex, Rml::Style::Nav::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Style::set_focusable(bool focusable) {
|
||||||
|
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace recompui
|
108
src/ui/elements/ui_style.h
Normal file
108
src/ui/elements/ui_style.h
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "RmlUi/Core.h"
|
||||||
|
|
||||||
|
#include "../core/ui_resource.h"
|
||||||
|
#include "ui_types.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
class ContextId;
|
||||||
|
class Style {
|
||||||
|
friend class Element; // For access to property_map without making it visible to element subclasses.
|
||||||
|
friend class ContextId;
|
||||||
|
private:
|
||||||
|
std::map<Rml::PropertyId, Rml::Property> property_map;
|
||||||
|
protected:
|
||||||
|
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property);
|
||||||
|
ResourceId resource_id = ResourceId::null();
|
||||||
|
public:
|
||||||
|
Style();
|
||||||
|
virtual ~Style();
|
||||||
|
void set_visibility(Visibility visibility);
|
||||||
|
void set_position(Position position);
|
||||||
|
void set_left(float left, Unit unit = Unit::Dp);
|
||||||
|
void set_top(float top, Unit unit = Unit::Dp);
|
||||||
|
void set_right(float right, Unit unit = Unit::Dp);
|
||||||
|
void set_bottom(float bottom, Unit unit = Unit::Dp);
|
||||||
|
void set_width(float width, Unit unit = Unit::Dp);
|
||||||
|
void set_width_auto();
|
||||||
|
void set_height(float height, Unit unit = Unit::Dp);
|
||||||
|
void set_height_auto();
|
||||||
|
void set_min_width(float width, Unit unit = Unit::Dp);
|
||||||
|
void set_min_height(float height, Unit unit = Unit::Dp);
|
||||||
|
void set_max_width(float width, Unit unit = Unit::Dp);
|
||||||
|
void set_max_height(float height, Unit unit = Unit::Dp);
|
||||||
|
void set_padding(float padding, Unit unit = Unit::Dp);
|
||||||
|
void set_padding_left(float padding, Unit unit = Unit::Dp);
|
||||||
|
void set_padding_top(float padding, Unit unit = Unit::Dp);
|
||||||
|
void set_padding_right(float padding, Unit unit = Unit::Dp);
|
||||||
|
void set_padding_bottom(float padding, Unit unit = Unit::Dp);
|
||||||
|
void set_margin(float margin, Unit unit = Unit::Dp);
|
||||||
|
void set_margin_left(float margin, Unit unit = Unit::Dp);
|
||||||
|
void set_margin_top(float margin, Unit unit = Unit::Dp);
|
||||||
|
void set_margin_right(float margin, Unit unit = Unit::Dp);
|
||||||
|
void set_margin_bottom(float margin, Unit unit = Unit::Dp);
|
||||||
|
void set_margin_auto();
|
||||||
|
void set_margin_left_auto();
|
||||||
|
void set_margin_top_auto();
|
||||||
|
void set_margin_right_auto();
|
||||||
|
void set_margin_bottom_auto();
|
||||||
|
void set_border_width(float width, Unit unit = Unit::Dp);
|
||||||
|
void set_border_left_width(float width, Unit unit = Unit::Dp);
|
||||||
|
void set_border_top_width(float width, Unit unit = Unit::Dp);
|
||||||
|
void set_border_right_width(float width, Unit unit = Unit::Dp);
|
||||||
|
void set_border_bottom_width(float width, Unit unit = Unit::Dp);
|
||||||
|
void set_border_radius(float radius, Unit unit = Unit::Dp);
|
||||||
|
void set_border_top_left_radius(float radius, Unit unit = Unit::Dp);
|
||||||
|
void set_border_top_right_radius(float radius, Unit unit = Unit::Dp);
|
||||||
|
void set_border_bottom_left_radius(float radius, Unit unit = Unit::Dp);
|
||||||
|
void set_border_bottom_right_radius(float radius, Unit unit = Unit::Dp);
|
||||||
|
void set_background_color(const Color &color);
|
||||||
|
void set_border_color(const Color &color);
|
||||||
|
void set_border_left_color(const Color &color);
|
||||||
|
void set_border_top_color(const Color &color);
|
||||||
|
void set_border_right_color(const Color &color);
|
||||||
|
void set_border_bottom_color(const Color &color);
|
||||||
|
void set_color(const Color &color);
|
||||||
|
void set_cursor(Cursor cursor);
|
||||||
|
void set_opacity(float opacity);
|
||||||
|
void set_display(Display display);
|
||||||
|
void set_justify_content(JustifyContent justify_content);
|
||||||
|
void set_flex_grow(float grow);
|
||||||
|
void set_flex_shrink(float shrink);
|
||||||
|
void set_flex_basis_auto();
|
||||||
|
void set_flex_basis(float basis, Unit unit = Unit::Percent);
|
||||||
|
void set_flex(float grow, float shrink);
|
||||||
|
void set_flex(float grow, float shrink, float basis, Unit basis_unit = Unit::Percent);
|
||||||
|
void set_flex_direction(FlexDirection flex_direction);
|
||||||
|
void set_align_items(AlignItems align_items);
|
||||||
|
void set_overflow(Overflow overflow);
|
||||||
|
void set_overflow_x(Overflow overflow);
|
||||||
|
void set_overflow_y(Overflow overflow);
|
||||||
|
void set_font_size(float size, Unit unit = Unit::Dp);
|
||||||
|
void set_letter_spacing(float spacing, Unit unit = Unit::Dp);
|
||||||
|
void set_line_height(float height, Unit unit = Unit::Dp);
|
||||||
|
void set_font_style(FontStyle style);
|
||||||
|
void set_font_weight(uint32_t weight);
|
||||||
|
void set_text_align(TextAlign text_align);
|
||||||
|
void set_text_transform(TextTransform text_transform);
|
||||||
|
void set_gap(float size, Unit unit = Unit::Dp);
|
||||||
|
void set_row_gap(float size, Unit unit = Unit::Dp);
|
||||||
|
void set_column_gap(float size, Unit unit = Unit::Dp);
|
||||||
|
void set_drag(Drag drag);
|
||||||
|
void set_tab_index(TabIndex focus);
|
||||||
|
void set_font_family(std::string_view family);
|
||||||
|
virtual void set_nav_auto(NavDirection dir);
|
||||||
|
virtual void set_nav_none(NavDirection dir);
|
||||||
|
virtual void set_nav(NavDirection dir, Element* element);
|
||||||
|
virtual void set_nav_manual(NavDirection dir, const std::string& target);
|
||||||
|
void set_tab_index_auto();
|
||||||
|
void set_tab_index_none();
|
||||||
|
void set_focusable(bool focusable);
|
||||||
|
virtual bool is_element() { return false; }
|
||||||
|
ResourceId get_resource_id() { return resource_id; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
51
src/ui/elements/ui_text_input.cpp
Normal file
51
src/ui/elements/ui_text_input.cpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#include "ui_text_input.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
void TextInput::process_event(const Event &e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case EventType::Text: {
|
||||||
|
const EventText &event = std::get<EventText>(e.variant);
|
||||||
|
text = event.text;
|
||||||
|
|
||||||
|
for (const auto &function : text_changed_callbacks) {
|
||||||
|
function(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextInput::TextInput(Element *parent, bool text_visible) : Element(parent, Events(EventType::Text), "input") {
|
||||||
|
if (!text_visible) {
|
||||||
|
set_attribute("type", "password");
|
||||||
|
}
|
||||||
|
set_min_width(60.0f);
|
||||||
|
set_border_color(Color{ 242, 242, 242, 255 });
|
||||||
|
set_border_bottom_width(1.0f);
|
||||||
|
set_padding_bottom(6.0f);
|
||||||
|
set_focusable(true);
|
||||||
|
set_nav_auto(NavDirection::Up);
|
||||||
|
set_nav_auto(NavDirection::Down);
|
||||||
|
set_tab_index_auto();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInput::set_text(std::string_view text) {
|
||||||
|
this->text = std::string(text);
|
||||||
|
set_attribute("value", this->text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &TextInput::get_text() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInput::add_text_changed_callback(std::function<void(const std::string &)> callback) {
|
||||||
|
text_changed_callbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
21
src/ui/elements/ui_text_input.h
Normal file
21
src/ui/elements/ui_text_input.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class TextInput : public Element {
|
||||||
|
private:
|
||||||
|
std::string text;
|
||||||
|
std::vector<std::function<void(const std::string &)>> text_changed_callbacks;
|
||||||
|
protected:
|
||||||
|
virtual void process_event(const Event &e) override;
|
||||||
|
std::string_view get_type_name() override { return "TextInput"; }
|
||||||
|
public:
|
||||||
|
TextInput(Element *parent, bool text_visible = true);
|
||||||
|
void set_text(std::string_view text);
|
||||||
|
const std::string &get_text();
|
||||||
|
void add_text_changed_callback(std::function<void(const std::string &)> callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
161
src/ui/elements/ui_toggle.cpp
Normal file
161
src/ui/elements/ui_toggle.cpp
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#include "ui_toggle.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include <ultramodern/ultramodern.hpp>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
Toggle::Toggle(Element *parent) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable), "button") {
|
||||||
|
enable_focus();
|
||||||
|
|
||||||
|
set_width(162.0f);
|
||||||
|
set_height(72.0f);
|
||||||
|
set_border_radius(36.0f);
|
||||||
|
set_opacity(0.9f);
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
set_border_width(2.0f);
|
||||||
|
set_border_color(Color{ 177, 76, 34, 255 });
|
||||||
|
set_background_color(Color{ 0, 0, 0, 0 });
|
||||||
|
checked_style.set_border_color(Color{ 34, 177, 76, 255 });
|
||||||
|
hover_style.set_border_color(Color{ 177, 76, 34, 255 });
|
||||||
|
hover_style.set_background_color(Color{ 206, 120, 68, 76 });
|
||||||
|
focus_style.set_border_color(Color{ 177, 76, 34, 255 });
|
||||||
|
focus_style.set_background_color(Color{ 206, 120, 68, 76 });
|
||||||
|
checked_hover_style.set_border_color(Color{ 34, 177, 76, 255 });
|
||||||
|
checked_hover_style.set_background_color(Color{ 68, 206, 120, 76 });
|
||||||
|
checked_focus_style.set_border_color(Color{ 34, 177, 76, 255 });
|
||||||
|
checked_focus_style.set_background_color(Color{ 68, 206, 120, 76 });
|
||||||
|
disabled_style.set_border_color(Color{ 177, 76, 34, 128 });
|
||||||
|
checked_disabled_style.set_border_color(Color{ 34, 177, 76, 128 });
|
||||||
|
add_style(&checked_style, checked_state);
|
||||||
|
add_style(&hover_style, hover_state);
|
||||||
|
add_style(&focus_style, focus_state);
|
||||||
|
add_style(&checked_hover_style, { checked_state, hover_state });
|
||||||
|
add_style(&checked_focus_style, { checked_state, focus_state });
|
||||||
|
add_style(&disabled_style, disabled_state);
|
||||||
|
add_style(&checked_disabled_style, { checked_state, disabled_state });
|
||||||
|
|
||||||
|
ContextId context = get_current_context();
|
||||||
|
|
||||||
|
floater = context.create_element<Element>(this);
|
||||||
|
floater->set_position(Position::Relative);
|
||||||
|
floater->set_top(2.0f);
|
||||||
|
floater->set_width(80.0f);
|
||||||
|
floater->set_height(64.0f);
|
||||||
|
floater->set_border_radius(32.0f);
|
||||||
|
floater->set_background_color(Color{ 177, 76, 34, 255 });
|
||||||
|
floater_checked_style.set_background_color(Color{ 34, 177, 76, 255 });
|
||||||
|
floater_disabled_style.set_background_color(Color{ 177, 76, 34, 128 });
|
||||||
|
floater_disabled_checked_style.set_background_color(Color{ 34, 177, 76, 128 });
|
||||||
|
floater->add_style(&floater_checked_style, checked_state);
|
||||||
|
floater->add_style(&floater_disabled_style, disabled_state);
|
||||||
|
floater->add_style(&floater_disabled_checked_style, { checked_state, disabled_state });
|
||||||
|
|
||||||
|
set_checked_internal(false, false, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toggle::set_checked_internal(bool checked, bool animate, bool setup, bool trigger_callbacks) {
|
||||||
|
if (this->checked != checked || setup) {
|
||||||
|
this->checked = checked;
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
last_time = ultramodern::time_since_start();
|
||||||
|
queue_update();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
floater_left = floater_left_target();
|
||||||
|
}
|
||||||
|
|
||||||
|
floater->set_left(floater_left, Unit::Dp);
|
||||||
|
|
||||||
|
if (trigger_callbacks) {
|
||||||
|
for (const auto &function : checked_callbacks) {
|
||||||
|
function(checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_style_enabled(checked_state, checked);
|
||||||
|
floater->set_style_enabled(checked_state, checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float Toggle::floater_left_target() const {
|
||||||
|
return checked ? 78.0f : 4.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toggle::process_event(const Event &e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case EventType::Click:
|
||||||
|
if (is_enabled()) {
|
||||||
|
set_checked_internal(!checked, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case EventType::Hover: {
|
||||||
|
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
|
||||||
|
set_style_enabled(hover_state, hover_active);
|
||||||
|
floater->set_style_enabled(hover_state, hover_active);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType::Focus: {
|
||||||
|
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||||
|
set_style_enabled(focus_state, focus_active);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType::Enable: {
|
||||||
|
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||||
|
set_style_enabled(disabled_state, !enable_active);
|
||||||
|
floater->set_style_enabled(disabled_state, !enable_active);
|
||||||
|
if (enable_active) {
|
||||||
|
set_cursor(Cursor::Pointer);
|
||||||
|
set_focusable(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
set_cursor(Cursor::None);
|
||||||
|
set_focusable(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType::Update: {
|
||||||
|
std::chrono::high_resolution_clock::duration now = ultramodern::time_since_start();
|
||||||
|
float delta_time = std::max(std::chrono::duration<float>(now - last_time).count(), 0.0f);
|
||||||
|
last_time = now;
|
||||||
|
|
||||||
|
constexpr float dp_speed = 740.0f;
|
||||||
|
const float target = floater_left_target();
|
||||||
|
if (target < floater_left) {
|
||||||
|
floater_left += std::max(-dp_speed * delta_time, target - floater_left);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
floater_left += std::min(dp_speed * delta_time, target - floater_left);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abs(target - floater_left) < 1e-4f) {
|
||||||
|
floater_left = target;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
queue_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
floater->set_left(floater_left, Unit::Dp);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toggle::set_checked(bool checked) {
|
||||||
|
set_checked_internal(checked, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Toggle::is_checked() const {
|
||||||
|
return checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toggle::add_checked_callback(std::function<void(bool)> callback) {
|
||||||
|
checked_callbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
};
|
38
src/ui/elements/ui_toggle.h
Normal file
38
src/ui/elements/ui_toggle.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_element.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class Toggle : public Element {
|
||||||
|
protected:
|
||||||
|
Element *floater;
|
||||||
|
float floater_left = 0.0f;
|
||||||
|
std::chrono::high_resolution_clock::duration last_time;
|
||||||
|
std::list<std::function<void(bool)>> checked_callbacks;
|
||||||
|
Style checked_style;
|
||||||
|
Style hover_style;
|
||||||
|
Style focus_style;
|
||||||
|
Style checked_hover_style;
|
||||||
|
Style checked_focus_style;
|
||||||
|
Style disabled_style;
|
||||||
|
Style checked_disabled_style;
|
||||||
|
Style floater_checked_style;
|
||||||
|
Style floater_disabled_style;
|
||||||
|
Style floater_disabled_checked_style;
|
||||||
|
bool checked = false;
|
||||||
|
|
||||||
|
void set_checked_internal(bool checked, bool animate, bool setup, bool trigger_callbacks);
|
||||||
|
float floater_left_target() const;
|
||||||
|
|
||||||
|
// Element overrides.
|
||||||
|
virtual void process_event(const Event &e) override;
|
||||||
|
std::string_view get_type_name() override { return "Toggle"; }
|
||||||
|
public:
|
||||||
|
Toggle(Element *parent);
|
||||||
|
void set_checked(bool checked);
|
||||||
|
bool is_checked() const;
|
||||||
|
void add_checked_callback(std::function<void(bool)> callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
260
src/ui/elements/ui_types.h
Normal file
260
src/ui/elements/ui_types.h
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
constexpr std::string_view checked_state = "checked";
|
||||||
|
constexpr std::string_view hover_state = "hover";
|
||||||
|
constexpr std::string_view focus_state = "focus";
|
||||||
|
constexpr std::string_view disabled_state = "disabled";
|
||||||
|
|
||||||
|
struct Color {
|
||||||
|
uint8_t r = 255;
|
||||||
|
uint8_t g = 255;
|
||||||
|
uint8_t b = 255;
|
||||||
|
uint8_t a = 255;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Cursor {
|
||||||
|
None,
|
||||||
|
Pointer
|
||||||
|
};
|
||||||
|
|
||||||
|
// These two enums must be kept in sync with patches/recompui_event_structs.h!
|
||||||
|
enum class EventType {
|
||||||
|
None,
|
||||||
|
Click,
|
||||||
|
Focus,
|
||||||
|
Hover,
|
||||||
|
Enable,
|
||||||
|
Drag,
|
||||||
|
Text,
|
||||||
|
Update,
|
||||||
|
Navigate,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DragPhase {
|
||||||
|
None,
|
||||||
|
Start,
|
||||||
|
Move,
|
||||||
|
End
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class NavDirection {
|
||||||
|
Up,
|
||||||
|
Right,
|
||||||
|
Down,
|
||||||
|
Left
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Enum, typename = std::enable_if_t<std::is_enum_v<Enum>>>
|
||||||
|
constexpr uint32_t Events(Enum first) {
|
||||||
|
return 1u << static_cast<uint32_t>(first);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Enum, typename... Enums, typename = std::enable_if_t<std::is_enum_v<Enum>>>
|
||||||
|
constexpr uint32_t Events(Enum first, Enums... rest) {
|
||||||
|
return Events(first) | Events(rest...);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EventClick {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventFocus {
|
||||||
|
bool active;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventHover {
|
||||||
|
bool active;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventEnable {
|
||||||
|
bool active;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventDrag {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
DragPhase phase;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventText {
|
||||||
|
std::string text;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventNavigate {
|
||||||
|
NavDirection direction;
|
||||||
|
};
|
||||||
|
|
||||||
|
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, EventNavigate, std::monostate>;
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
EventType type;
|
||||||
|
EventVariant variant;
|
||||||
|
|
||||||
|
// Factory methods for creating specific events
|
||||||
|
static Event click_event(float x, float y) {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::Click;
|
||||||
|
e.variant = EventClick{ x, y };
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Event focus_event(bool active) {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::Focus;
|
||||||
|
e.variant = EventFocus{ active };
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Event hover_event(bool active) {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::Hover;
|
||||||
|
e.variant = EventHover{ active };
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Event enable_event(bool enable) {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::Enable;
|
||||||
|
e.variant = EventEnable{ enable };
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Event drag_event(float x, float y, DragPhase phase) {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::Drag;
|
||||||
|
e.variant = EventDrag{ x, y, phase };
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Event text_event(const std::string &text) {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::Text;
|
||||||
|
e.variant = EventText{ text };
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Event update_event() {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::Update;
|
||||||
|
e.variant = std::monostate{};
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Event navigate_event(NavDirection direction) {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::Navigate;
|
||||||
|
e.variant = EventNavigate{ direction };
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Display {
|
||||||
|
None,
|
||||||
|
Block,
|
||||||
|
Inline,
|
||||||
|
InlineBlock,
|
||||||
|
FlowRoot,
|
||||||
|
Flex,
|
||||||
|
InlineFlex,
|
||||||
|
Table,
|
||||||
|
InlineTable,
|
||||||
|
TableRow,
|
||||||
|
TableRowGroup,
|
||||||
|
TableColumn,
|
||||||
|
TableColumnGroup,
|
||||||
|
TableCell
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Visibility {
|
||||||
|
Visible,
|
||||||
|
Hidden
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Position {
|
||||||
|
Absolute,
|
||||||
|
Relative
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class JustifyContent {
|
||||||
|
FlexStart,
|
||||||
|
FlexEnd,
|
||||||
|
Center,
|
||||||
|
SpaceBetween,
|
||||||
|
SpaceAround,
|
||||||
|
SpaceEvenly
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FlexDirection {
|
||||||
|
Row,
|
||||||
|
Column,
|
||||||
|
RowReverse,
|
||||||
|
ColumnReverse
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AlignItems {
|
||||||
|
FlexStart,
|
||||||
|
FlexEnd,
|
||||||
|
Center,
|
||||||
|
Baseline,
|
||||||
|
Stretch
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Overflow {
|
||||||
|
Visible,
|
||||||
|
Hidden,
|
||||||
|
Auto,
|
||||||
|
Scroll
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Unit {
|
||||||
|
Px,
|
||||||
|
Dp,
|
||||||
|
Percent
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AnimationType : uint32_t {
|
||||||
|
None,
|
||||||
|
Set,
|
||||||
|
Tween
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FontStyle {
|
||||||
|
Normal,
|
||||||
|
Italic
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TextAlign {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Center,
|
||||||
|
Justify
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TextTransform {
|
||||||
|
None,
|
||||||
|
Capitalize,
|
||||||
|
Uppercase,
|
||||||
|
Lowercase
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Drag {
|
||||||
|
None,
|
||||||
|
Drag,
|
||||||
|
DragDrop,
|
||||||
|
Block,
|
||||||
|
Clone
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TabIndex {
|
||||||
|
None,
|
||||||
|
Auto
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
1031
src/ui/ui_api.cpp
Normal file
1031
src/ui/ui_api.cpp
Normal file
File diff suppressed because it is too large
Load Diff
118
src/ui/ui_api_events.cpp
Normal file
118
src/ui/ui_api_events.cpp
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#include "concurrentqueue.h"
|
||||||
|
|
||||||
|
#include "overloaded.h"
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
|
||||||
|
#include "core/ui_context.h"
|
||||||
|
#include "core/ui_resource.h"
|
||||||
|
|
||||||
|
#include "elements/ui_element.h"
|
||||||
|
#include "elements/ui_button.h"
|
||||||
|
#include "elements/ui_clickable.h"
|
||||||
|
#include "elements/ui_container.h"
|
||||||
|
#include "elements/ui_image.h"
|
||||||
|
#include "elements/ui_label.h"
|
||||||
|
#include "elements/ui_radio.h"
|
||||||
|
#include "elements/ui_scroll_container.h"
|
||||||
|
#include "elements/ui_slider.h"
|
||||||
|
#include "elements/ui_style.h"
|
||||||
|
#include "elements/ui_text_input.h"
|
||||||
|
#include "elements/ui_toggle.h"
|
||||||
|
#include "elements/ui_types.h"
|
||||||
|
|
||||||
|
#include "librecomp/overlays.hpp"
|
||||||
|
#include "librecomp/helpers.hpp"
|
||||||
|
|
||||||
|
#include "../patches/ui_funcs.h"
|
||||||
|
|
||||||
|
struct QueuedCallback {
|
||||||
|
recompui::ResourceId resource;
|
||||||
|
recompui::Event event;
|
||||||
|
recompui::UICallback callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
moodycamel::ConcurrentQueue<QueuedCallback> queued_callbacks{};
|
||||||
|
|
||||||
|
void recompui::queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback) {
|
||||||
|
queued_callbacks.enqueue(QueuedCallback{ .resource = resource, .event = e, .callback = callback });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool convert_event(const recompui::Event& in, RecompuiEventData& out) {
|
||||||
|
bool skip = false;
|
||||||
|
out = {};
|
||||||
|
out.type = static_cast<RecompuiEventType>(in.type);
|
||||||
|
|
||||||
|
switch (in.type) {
|
||||||
|
default:
|
||||||
|
case recompui::EventType::None:
|
||||||
|
case recompui::EventType::Count:
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
case recompui::EventType::Click:
|
||||||
|
{
|
||||||
|
const recompui::EventClick &click = std::get<recompui::EventClick>(in.variant);
|
||||||
|
out.data.click.x = click.x;
|
||||||
|
out.data.click.y = click.y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case recompui::EventType::Focus:
|
||||||
|
{
|
||||||
|
const recompui::EventFocus &focus = std::get<recompui::EventFocus>(in.variant);
|
||||||
|
out.data.focus.active = focus.active;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case recompui::EventType::Hover:
|
||||||
|
{
|
||||||
|
const recompui::EventHover &hover = std::get<recompui::EventHover>(in.variant);
|
||||||
|
out.data.hover.active = hover.active;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case recompui::EventType::Enable:
|
||||||
|
{
|
||||||
|
const recompui::EventEnable &enable = std::get<recompui::EventEnable>(in.variant);
|
||||||
|
out.data.enable.active = enable.active;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case recompui::EventType::Drag:
|
||||||
|
{
|
||||||
|
const recompui::EventDrag &drag = std::get<recompui::EventDrag>(in.variant);
|
||||||
|
out.data.drag.phase = static_cast<RecompuiDragPhase>(drag.phase);
|
||||||
|
out.data.drag.x = drag.x;
|
||||||
|
out.data.drag.y = drag.y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case recompui::EventType::Text:
|
||||||
|
skip = true; // Text events aren't supported in the UI mod API.
|
||||||
|
break;
|
||||||
|
case recompui::EventType::Update:
|
||||||
|
// No data for an update event.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_run_ui_callbacks(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
// Allocate the event on the stack.
|
||||||
|
gpr stack_frame = ctx->r29;
|
||||||
|
ctx->r29 -= sizeof(RecompuiEventData);
|
||||||
|
RecompuiEventData* event_data = TO_PTR(RecompuiEventData, stack_frame);
|
||||||
|
|
||||||
|
QueuedCallback cur_callback;
|
||||||
|
|
||||||
|
while (queued_callbacks.try_dequeue(cur_callback)) {
|
||||||
|
if (convert_event(cur_callback.event, *event_data)) {
|
||||||
|
recompui::ContextId cur_context = cur_callback.callback.context;
|
||||||
|
cur_context.open();
|
||||||
|
|
||||||
|
ctx->r4 = static_cast<int32_t>(cur_callback.resource.slot_id);
|
||||||
|
ctx->r5 = stack_frame;
|
||||||
|
ctx->r6 = cur_callback.callback.userdata;
|
||||||
|
|
||||||
|
LOOKUP_FUNC(cur_callback.callback.callback)(rdram, ctx);
|
||||||
|
cur_context.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->r29 += sizeof(RecompuiEventData);
|
||||||
|
}
|
131
src/ui/ui_api_images.cpp
Normal file
131
src/ui/ui_api_images.cpp
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#include <mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include "librecomp/overlays.hpp"
|
||||||
|
#include "librecomp/helpers.hpp"
|
||||||
|
#include "ultramodern/error_handling.hpp"
|
||||||
|
|
||||||
|
#include "ui_helpers.h"
|
||||||
|
#include "ui_api_images.h"
|
||||||
|
#include "elements/ui_image.h"
|
||||||
|
|
||||||
|
using namespace recompui;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
std::mutex mutex;
|
||||||
|
std::unordered_set<uint32_t> textures{};
|
||||||
|
uint32_t textures_created = 0;
|
||||||
|
} TextureState;
|
||||||
|
|
||||||
|
const std::string mod_texture_prefix = "?/mod_api/";
|
||||||
|
|
||||||
|
static std::string get_texture_name(uint32_t texture_id) {
|
||||||
|
return mod_texture_prefix + std::to_string(texture_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t get_new_texture_id() {
|
||||||
|
std::lock_guard lock{TextureState.mutex};
|
||||||
|
uint32_t cur_id = TextureState.textures_created++;
|
||||||
|
TextureState.textures.emplace(cur_id);
|
||||||
|
|
||||||
|
return cur_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void release_texture(uint32_t texture_id) {
|
||||||
|
std::string texture_name = get_texture_name(texture_id);
|
||||||
|
std::lock_guard lock{TextureState.mutex};
|
||||||
|
|
||||||
|
if (TextureState.textures.erase(texture_id) == 0) {
|
||||||
|
recompui::message_box("Fatal error in mod - attempted to destroy texture that doesn't exist!");
|
||||||
|
assert(false);
|
||||||
|
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::release_image(texture_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local std::vector<char> swapped_image_bytes;
|
||||||
|
|
||||||
|
void recompui_create_texture_rgba32(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
PTR(void) data_in = _arg<0, PTR(void)>(rdram, ctx);
|
||||||
|
uint32_t width = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t height = _arg<2, uint32_t>(rdram, ctx);
|
||||||
|
uint32_t cur_id = get_new_texture_id();
|
||||||
|
|
||||||
|
// The size in bytes of the image's pixel data.
|
||||||
|
size_t size_bytes = width * height * 4 * sizeof(uint8_t);
|
||||||
|
swapped_image_bytes.resize(size_bytes);
|
||||||
|
|
||||||
|
// Byteswap copy the pixel data.
|
||||||
|
for (size_t i = 0; i < size_bytes; i++) {
|
||||||
|
swapped_image_bytes[i] = MEM_B(i, data_in);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a texture name from the ID and queue its bytes.
|
||||||
|
std::string texture_name = get_texture_name(cur_id);
|
||||||
|
recompui::queue_image_from_bytes_rgba32(texture_name, swapped_image_bytes, width, height);
|
||||||
|
|
||||||
|
// Return the new texture ID.
|
||||||
|
_return(ctx, cur_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui_create_texture_image_bytes(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
PTR(void) data_in = _arg<0, PTR(void)>(rdram, ctx);
|
||||||
|
uint32_t size_bytes = _arg<1, u32>(rdram, ctx);
|
||||||
|
uint32_t cur_id = get_new_texture_id();
|
||||||
|
|
||||||
|
// The size in bytes of the image's data.
|
||||||
|
swapped_image_bytes.resize(size_bytes);
|
||||||
|
|
||||||
|
// Byteswap copy the image's data.
|
||||||
|
for (size_t i = 0; i < size_bytes; i++) {
|
||||||
|
swapped_image_bytes[i] = MEM_B(i, data_in);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a texture name from the ID and queue its bytes.
|
||||||
|
std::string texture_name = get_texture_name(cur_id);
|
||||||
|
recompui::queue_image_from_bytes_file(texture_name, swapped_image_bytes);
|
||||||
|
|
||||||
|
// Return the new texture ID.
|
||||||
|
_return(ctx, cur_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui_destroy_texture(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t texture_id = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
release_texture(texture_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui_create_imageview(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
ContextId ui_context = get_context(rdram, ctx);
|
||||||
|
Element* parent = arg_element<1>(rdram, ctx, ui_context);
|
||||||
|
uint32_t texture_id = _arg<2, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
Element* ret = ui_context.create_element<Image>(parent, get_texture_name(texture_id));
|
||||||
|
return_resource(ctx, ret->get_resource_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
void recompui_set_imageview_texture(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
Style* resource = arg_style<0>(rdram, ctx);
|
||||||
|
uint32_t texture_id = _arg<1, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
if (!resource->is_element()) {
|
||||||
|
recompui::message_box("Fatal error in mod - attempted to set texture of non-element");
|
||||||
|
assert(false);
|
||||||
|
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element* element = static_cast<Element*>(resource);
|
||||||
|
element->set_src(get_texture_name(texture_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||||
|
|
||||||
|
void recompui::register_ui_image_exports() {
|
||||||
|
REGISTER_FUNC(recompui_create_texture_rgba32);
|
||||||
|
REGISTER_FUNC(recompui_create_texture_image_bytes);
|
||||||
|
REGISTER_FUNC(recompui_destroy_texture);
|
||||||
|
REGISTER_FUNC(recompui_create_imageview);
|
||||||
|
REGISTER_FUNC(recompui_set_imageview_texture);
|
||||||
|
}
|
10
src/ui/ui_api_images.h
Normal file
10
src/ui/ui_api_images.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef __UI_API_IMAGES_H__
|
||||||
|
#define __UI_API_IMAGES_H__
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
void register_ui_image_exports();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -7,166 +7,166 @@
|
|||||||
using ColourMap = Rml::UnorderedMap<Rml::String, Rml::Colourb>;
|
using ColourMap = Rml::UnorderedMap<Rml::String, Rml::Colourb>;
|
||||||
|
|
||||||
namespace recompui {
|
namespace recompui {
|
||||||
class PropertyParserColorHack : public Rml::PropertyParser {
|
class PropertyParserColorHack : public Rml::PropertyParser {
|
||||||
public:
|
public:
|
||||||
PropertyParserColorHack();
|
PropertyParserColorHack();
|
||||||
virtual ~PropertyParserColorHack();
|
virtual ~PropertyParserColorHack();
|
||||||
bool ParseValue(Rml::Property& property, const Rml::String& value, const Rml::ParameterMap& /*parameters*/) const override;
|
bool ParseValue(Rml::Property& property, const Rml::String& value, const Rml::ParameterMap& /*parameters*/) const override;
|
||||||
private:
|
private:
|
||||||
static ColourMap html_colours;
|
static ColourMap html_colours;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(PropertyParserColorHack) == sizeof(Rml::PropertyParserColour));
|
static_assert(sizeof(PropertyParserColorHack) == sizeof(Rml::PropertyParserColour));
|
||||||
PropertyParserColorHack::PropertyParserColorHack() {
|
PropertyParserColorHack::PropertyParserColorHack() {
|
||||||
html_colours["black"] = Rml::Colourb(0, 0, 0);
|
html_colours["black"] = Rml::Colourb(0, 0, 0);
|
||||||
html_colours["silver"] = Rml::Colourb(192, 192, 192);
|
html_colours["silver"] = Rml::Colourb(192, 192, 192);
|
||||||
html_colours["gray"] = Rml::Colourb(128, 128, 128);
|
html_colours["gray"] = Rml::Colourb(128, 128, 128);
|
||||||
html_colours["grey"] = Rml::Colourb(128, 128, 128);
|
html_colours["grey"] = Rml::Colourb(128, 128, 128);
|
||||||
html_colours["white"] = Rml::Colourb(255, 255, 255);
|
html_colours["white"] = Rml::Colourb(255, 255, 255);
|
||||||
html_colours["maroon"] = Rml::Colourb(128, 0, 0);
|
html_colours["maroon"] = Rml::Colourb(128, 0, 0);
|
||||||
html_colours["red"] = Rml::Colourb(255, 0, 0);
|
html_colours["red"] = Rml::Colourb(255, 0, 0);
|
||||||
html_colours["orange"] = Rml::Colourb(255, 165, 0);
|
html_colours["orange"] = Rml::Colourb(255, 165, 0);
|
||||||
html_colours["purple"] = Rml::Colourb(128, 0, 128);
|
html_colours["purple"] = Rml::Colourb(128, 0, 128);
|
||||||
html_colours["fuchsia"] = Rml::Colourb(255, 0, 255);
|
html_colours["fuchsia"] = Rml::Colourb(255, 0, 255);
|
||||||
html_colours["green"] = Rml::Colourb(0, 128, 0);
|
html_colours["green"] = Rml::Colourb(0, 128, 0);
|
||||||
html_colours["lime"] = Rml::Colourb(0, 255, 0);
|
html_colours["lime"] = Rml::Colourb(0, 255, 0);
|
||||||
html_colours["olive"] = Rml::Colourb(128, 128, 0);
|
html_colours["olive"] = Rml::Colourb(128, 128, 0);
|
||||||
html_colours["yellow"] = Rml::Colourb(255, 255, 0);
|
html_colours["yellow"] = Rml::Colourb(255, 255, 0);
|
||||||
html_colours["navy"] = Rml::Colourb(0, 0, 128);
|
html_colours["navy"] = Rml::Colourb(0, 0, 128);
|
||||||
html_colours["blue"] = Rml::Colourb(0, 0, 255);
|
html_colours["blue"] = Rml::Colourb(0, 0, 255);
|
||||||
html_colours["teal"] = Rml::Colourb(0, 128, 128);
|
html_colours["teal"] = Rml::Colourb(0, 128, 128);
|
||||||
html_colours["aqua"] = Rml::Colourb(0, 255, 255);
|
html_colours["aqua"] = Rml::Colourb(0, 255, 255);
|
||||||
html_colours["transparent"] = Rml::Colourb(0, 0, 0, 0);
|
html_colours["transparent"] = Rml::Colourb(0, 0, 0, 0);
|
||||||
html_colours["whitesmoke"] = Rml::Colourb(245, 245, 245);
|
html_colours["whitesmoke"] = Rml::Colourb(245, 245, 245);
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyParserColorHack::~PropertyParserColorHack() {}
|
PropertyParserColorHack::~PropertyParserColorHack() {}
|
||||||
|
|
||||||
bool PropertyParserColorHack::ParseValue(Rml::Property& property, const Rml::String& value, const Rml::ParameterMap& /*parameters*/) const {
|
bool PropertyParserColorHack::ParseValue(Rml::Property& property, const Rml::String& value, const Rml::ParameterMap& /*parameters*/) const {
|
||||||
if (value.empty())
|
if (value.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Rml::Colourb colour;
|
Rml::Colourb colour;
|
||||||
|
|
||||||
// Check for a hex colour.
|
// Check for a hex colour.
|
||||||
if (value[0] == '#')
|
if (value[0] == '#')
|
||||||
{
|
{
|
||||||
char hex_values[4][2] = { {'f', 'f'}, {'f', 'f'}, {'f', 'f'}, {'f', 'f'} };
|
char hex_values[4][2] = { {'f', 'f'}, {'f', 'f'}, {'f', 'f'}, {'f', 'f'} };
|
||||||
|
|
||||||
switch (value.size())
|
switch (value.size())
|
||||||
{
|
{
|
||||||
// Single hex digit per channel, RGB and alpha.
|
// Single hex digit per channel, RGB and alpha.
|
||||||
case 5:
|
case 5:
|
||||||
hex_values[3][0] = hex_values[3][1] = value[4];
|
hex_values[3][0] = hex_values[3][1] = value[4];
|
||||||
//-fallthrough
|
//-fallthrough
|
||||||
// Single hex digit per channel, RGB only.
|
// Single hex digit per channel, RGB only.
|
||||||
case 4:
|
case 4:
|
||||||
hex_values[0][0] = hex_values[0][1] = value[1];
|
hex_values[0][0] = hex_values[0][1] = value[1];
|
||||||
hex_values[1][0] = hex_values[1][1] = value[2];
|
hex_values[1][0] = hex_values[1][1] = value[2];
|
||||||
hex_values[2][0] = hex_values[2][1] = value[3];
|
hex_values[2][0] = hex_values[2][1] = value[3];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Two hex digits per channel, RGB and alpha.
|
// Two hex digits per channel, RGB and alpha.
|
||||||
case 9:
|
case 9:
|
||||||
hex_values[3][0] = value[7];
|
hex_values[3][0] = value[7];
|
||||||
hex_values[3][1] = value[8];
|
hex_values[3][1] = value[8];
|
||||||
//-fallthrough
|
//-fallthrough
|
||||||
// Two hex digits per channel, RGB only.
|
// Two hex digits per channel, RGB only.
|
||||||
case 7: memcpy(hex_values, &value.c_str()[1], sizeof(char) * 6); break;
|
case 7: memcpy(hex_values, &value.c_str()[1], sizeof(char) * 6); break;
|
||||||
|
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse each of the colour elements.
|
// Parse each of the colour elements.
|
||||||
for (int i = 0; i < 4; i++)
|
for (size_t i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
int tens = Rml::Math::HexToDecimal(hex_values[i][0]);
|
int tens = Rml::Math::HexToDecimal(hex_values[i][0]);
|
||||||
int ones = Rml::Math::HexToDecimal(hex_values[i][1]);
|
int ones = Rml::Math::HexToDecimal(hex_values[i][1]);
|
||||||
if (tens == -1 || ones == -1)
|
if (tens == -1 || ones == -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
colour[i] = (Rml::byte)(tens * 16 + ones);
|
colour[i] = (Rml::byte)(tens * 16 + ones);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (value.substr(0, 3) == "rgb")
|
else if (value.substr(0, 3) == "rgb")
|
||||||
{
|
{
|
||||||
Rml::StringList values;
|
Rml::StringList values;
|
||||||
values.reserve(4);
|
values.reserve(4);
|
||||||
|
|
||||||
size_t find = value.find('(');
|
size_t find = value.find('(');
|
||||||
if (find == Rml::String::npos)
|
if (find == Rml::String::npos)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
size_t begin_values = find + 1;
|
size_t begin_values = find + 1;
|
||||||
|
|
||||||
Rml::StringUtilities::ExpandString(values, value.substr(begin_values, value.rfind(')') - begin_values), ',');
|
Rml::StringUtilities::ExpandString(values, value.substr(begin_values, value.rfind(')') - begin_values), ',');
|
||||||
|
|
||||||
// Check if we're parsing an 'rgba' or 'rgb' colour declaration.
|
// Check if we're parsing an 'rgba' or 'rgb' colour declaration.
|
||||||
if (value.size() > 3 && value[3] == 'a')
|
if (value.size() > 3 && value[3] == 'a')
|
||||||
{
|
{
|
||||||
if (values.size() != 4)
|
if (values.size() != 4)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (values.size() != 3)
|
if (values.size() != 3)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
values.push_back("255");
|
values.push_back("255");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the three RGB values.
|
// Parse the three RGB values.
|
||||||
for (int i = 0; i < 3; ++i)
|
for (size_t i = 0; i < 3; ++i)
|
||||||
{
|
{
|
||||||
int component;
|
int component;
|
||||||
|
|
||||||
// We're parsing a percentage value.
|
// We're parsing a percentage value.
|
||||||
if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%')
|
if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%')
|
||||||
component = int((float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (255.0f / 100.0f));
|
component = int((float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (255.0f / 100.0f));
|
||||||
// We're parsing a 0 -> 255 integer value.
|
// We're parsing a 0 -> 255 integer value.
|
||||||
else
|
else
|
||||||
component = atoi(values[i].c_str());
|
component = atoi(values[i].c_str());
|
||||||
|
|
||||||
colour[i] = (Rml::byte)(Rml::Math::Clamp(component, 0, 255));
|
colour[i] = (Rml::byte)(Rml::Math::Clamp(component, 0, 255));
|
||||||
}
|
}
|
||||||
// Parse the alpha value. Modified from the original RmlUi implementation to use 0-1 instead of 0-255.
|
// Parse the alpha value. Modified from the original RmlUi implementation to use 0-1 instead of 0-255.
|
||||||
{
|
{
|
||||||
int component;
|
int component;
|
||||||
|
|
||||||
// We're parsing a percentage value.
|
// We're parsing a percentage value.
|
||||||
if (values[3].size() > 0 && values[3][values[3].size() - 1] == '%')
|
if (values[3].size() > 0 && values[3][values[3].size() - 1] == '%')
|
||||||
component = ((float)atof(values[3].substr(0, values[3].size() - 1).c_str()) * (255.0f / 100.0f));
|
component = ((float)atof(values[3].substr(0, values[3].size() - 1).c_str()) * (255.0f / 100.0f));
|
||||||
// We're parsing a 0 -> 1 float value.
|
// We're parsing a 0 -> 1 float value.
|
||||||
else
|
else
|
||||||
component = atof(values[3].c_str()) * 255.0f;
|
component = atof(values[3].c_str()) * 255.0f;
|
||||||
|
|
||||||
colour[3] = (Rml::byte)(Rml::Math::Clamp(component, 0, 255));
|
colour[3] = (Rml::byte)(Rml::Math::Clamp(component, 0, 255));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check for the specification of an HTML colour.
|
// Check for the specification of an HTML colour.
|
||||||
ColourMap::const_iterator iterator = html_colours.find(Rml::StringUtilities::ToLower(value));
|
ColourMap::const_iterator iterator = html_colours.find(Rml::StringUtilities::ToLower(value));
|
||||||
if (iterator == html_colours.end())
|
if (iterator == html_colours.end())
|
||||||
return false;
|
return false;
|
||||||
else
|
else
|
||||||
colour = (*iterator).second;
|
colour = (*iterator).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
property.value = Rml::Variant(colour);
|
property.value = Rml::Variant(colour);
|
||||||
property.unit = Rml::Unit::COLOUR;
|
property.unit = Rml::Unit::COLOUR;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This hack overwrites the contents of a property parser pointer for "color" (which is known to point to a valid Rml::PropertyParserColour) with the contents of a PropertyParserColorHack.
|
// This hack overwrites the contents of a property parser pointer for "color" (which is known to point to a valid Rml::PropertyParserColour) with the contents of a PropertyParserColorHack.
|
||||||
// This overwrites the object's vtable, allowing us to override color parsing behavior to use 0-1 alpha instead of 0-255.
|
// This overwrites the object's vtable, allowing us to override color parsing behavior to use 0-1 alpha instead of 0-255.
|
||||||
// Ideally we'd just replace the pointer itself, but RmlUi doesn't provide a way to do that currently.
|
// Ideally we'd just replace the pointer itself, but RmlUi doesn't provide a way to do that currently.
|
||||||
void apply_color_hack() {
|
void apply_color_hack() {
|
||||||
// Allocate and leak a parser to act as a vtable source.
|
// Allocate and leak a parser to act as a vtable source.
|
||||||
PropertyParserColorHack* new_parser = new PropertyParserColorHack();
|
PropertyParserColorHack* new_parser = new PropertyParserColorHack();
|
||||||
// Copy the allocated object into the color parser pointer to overwrite its vtable.
|
// Copy the allocated object into the color parser pointer to overwrite its vtable.
|
||||||
memcpy((void*)Rml::StyleSheetSpecification::GetParser("color"), (void*)new_parser, sizeof(*new_parser));
|
memcpy((void*)Rml::StyleSheetSpecification::GetParser("color"), (void*)new_parser, sizeof(*new_parser));
|
||||||
}
|
}
|
||||||
|
|
||||||
ColourMap PropertyParserColorHack::html_colours{};
|
ColourMap PropertyParserColorHack::html_colours{};
|
||||||
}
|
}
|
||||||
|
1508
src/ui/ui_config.cpp
1508
src/ui/ui_config.cpp
File diff suppressed because it is too large
Load Diff
250
src/ui/ui_config_sub_menu.cpp
Normal file
250
src/ui/ui_config_sub_menu.cpp
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
#include "ui_config_sub_menu.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
// ConfigOptionElement
|
||||||
|
|
||||||
|
|
||||||
|
void ConfigOptionElement::process_event(const Event &e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case EventType::Hover:
|
||||||
|
hover_callback(this, std::get<EventHover>(e.variant).active);
|
||||||
|
break;
|
||||||
|
case EventType::Update:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown event type.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOptionElement::ConfigOptionElement(Element *parent) : Element(parent, Events(EventType::Hover)) {
|
||||||
|
set_display(Display::Flex);
|
||||||
|
set_flex_direction(FlexDirection::Column);
|
||||||
|
set_gap(16.0f);
|
||||||
|
set_height(100.0f);
|
||||||
|
|
||||||
|
name_label = get_current_context().create_element<Label>(this, LabelStyle::Normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOptionElement::~ConfigOptionElement() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigOptionElement::set_option_id(std::string_view id) {
|
||||||
|
this->option_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigOptionElement::set_name(std::string_view name) {
|
||||||
|
this->name = name;
|
||||||
|
name_label->set_text(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigOptionElement::set_description(std::string_view description) {
|
||||||
|
this->description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigOptionElement::set_hover_callback(std::function<void(ConfigOptionElement *, bool)> callback) {
|
||||||
|
hover_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &ConfigOptionElement::get_description() const {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigOptionSlider
|
||||||
|
|
||||||
|
void ConfigOptionSlider::slider_value_changed(double v) {
|
||||||
|
callback(option_id, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOptionSlider::ConfigOptionSlider(Element *parent, double value, double min_value, double max_value, double step_value, bool percent, std::function<void(const std::string &, double)> callback) : ConfigOptionElement(parent) {
|
||||||
|
this->callback = callback;
|
||||||
|
|
||||||
|
slider = get_current_context().create_element<Slider>(this, percent ? SliderType::Percent : SliderType::Double);
|
||||||
|
slider->set_max_width(380.0f);
|
||||||
|
slider->set_min_value(min_value);
|
||||||
|
slider->set_max_value(max_value);
|
||||||
|
slider->set_step_value(step_value);
|
||||||
|
slider->set_value(value);
|
||||||
|
slider->add_value_changed_callback([this](double v){ slider_value_changed(v); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigOptionTextInput
|
||||||
|
|
||||||
|
void ConfigOptionTextInput::text_changed(const std::string &text) {
|
||||||
|
callback(option_id, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOptionTextInput::ConfigOptionTextInput(Element *parent, std::string_view value, std::function<void(const std::string &, const std::string &)> callback) : ConfigOptionElement(parent) {
|
||||||
|
this->callback = callback;
|
||||||
|
|
||||||
|
text_input = get_current_context().create_element<TextInput>(this);
|
||||||
|
text_input->set_max_width(400.0f);
|
||||||
|
text_input->set_text(value);
|
||||||
|
text_input->add_text_changed_callback([this](const std::string &text){ text_changed(text); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigOptionRadio
|
||||||
|
|
||||||
|
void ConfigOptionRadio::index_changed(uint32_t index) {
|
||||||
|
callback(option_id, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOptionRadio::ConfigOptionRadio(Element *parent, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback) : ConfigOptionElement(parent) {
|
||||||
|
this->callback = callback;
|
||||||
|
|
||||||
|
radio = get_current_context().create_element<Radio>(this);
|
||||||
|
radio->add_index_changed_callback([this](uint32_t index){ index_changed(index); });
|
||||||
|
for (std::string_view option : options) {
|
||||||
|
radio->add_option(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < options.size()) {
|
||||||
|
radio->set_index(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigSubMenu
|
||||||
|
|
||||||
|
void ConfigSubMenu::back_button_pressed() {
|
||||||
|
// Hide the config sub menu and show the config menu.
|
||||||
|
ContextId config_context = recompui::get_config_context_id();
|
||||||
|
ContextId sub_menu_context = recompui::get_config_sub_menu_context_id();
|
||||||
|
|
||||||
|
recompui::hide_context(sub_menu_context);
|
||||||
|
recompui::show_context(config_context, "");
|
||||||
|
recompui::focus_mod_configure_button();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigSubMenu::option_hovered(ConfigOptionElement *option, bool active) {
|
||||||
|
if (active) {
|
||||||
|
hover_option_elements.emplace(option);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hover_option_elements.erase(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hover_option_elements.empty()) {
|
||||||
|
description_label->set_text("");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
description_label->set_text((*hover_option_elements.begin())->get_description());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigSubMenu::ConfigSubMenu(Element *parent) : Element(parent) {
|
||||||
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
|
set_display(Display::Flex);
|
||||||
|
set_flex(1, 1, 100.0f, Unit::Percent);
|
||||||
|
set_flex_direction(FlexDirection::Column);
|
||||||
|
set_height(100.0f, Unit::Percent);
|
||||||
|
|
||||||
|
recompui::ContextId context = get_current_context();
|
||||||
|
header_container = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexStart);
|
||||||
|
header_container->set_flex_grow(0.0f);
|
||||||
|
header_container->set_align_items(AlignItems::Center);
|
||||||
|
header_container->set_padding(12.0f);
|
||||||
|
header_container->set_gap(24.0f);
|
||||||
|
|
||||||
|
{
|
||||||
|
back_button = context.create_element<Button>(header_container, "Back", ButtonStyle::Secondary);
|
||||||
|
back_button->add_pressed_callback([this](){ back_button_pressed(); });
|
||||||
|
title_label = context.create_element<Label>(header_container, "Title", LabelStyle::Large);
|
||||||
|
}
|
||||||
|
|
||||||
|
body_container = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::SpaceEvenly);
|
||||||
|
body_container->set_padding(32.0f);
|
||||||
|
{
|
||||||
|
config_container = context.create_element<Container>(body_container, FlexDirection::Column, JustifyContent::Center);
|
||||||
|
config_container->set_display(Display::Block);
|
||||||
|
config_container->set_flex_basis(100.0f);
|
||||||
|
config_container->set_align_items(AlignItems::Center);
|
||||||
|
{
|
||||||
|
config_scroll_container = context.create_element<ScrollContainer>(config_container, ScrollDirection::Vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
description_label = context.create_element<Label>(body_container, "Description", LabelStyle::Small);
|
||||||
|
description_label->set_min_width(800.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::get_current_context().set_autofocus_element(back_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigSubMenu::~ConfigSubMenu() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigSubMenu::enter(std::string_view title) {
|
||||||
|
title_label->set_text(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigSubMenu::clear_options() {
|
||||||
|
config_scroll_container->clear_children();
|
||||||
|
config_option_elements.clear();
|
||||||
|
hover_option_elements.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigSubMenu::add_option(ConfigOptionElement *option, std::string_view id, std::string_view name, std::string_view description) {
|
||||||
|
option->set_option_id(id);
|
||||||
|
option->set_name(name);
|
||||||
|
option->set_description(description);
|
||||||
|
option->set_hover_callback([this](ConfigOptionElement *option, bool active){ option_hovered(option, active); });
|
||||||
|
if (config_option_elements.empty()) {
|
||||||
|
back_button->set_nav(NavDirection::Down, option->get_focus_element());
|
||||||
|
option->set_nav(NavDirection::Up, back_button);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
config_option_elements.back()->set_nav(NavDirection::Down, option->get_focus_element());
|
||||||
|
option->set_nav(NavDirection::Up, config_option_elements.back()->get_focus_element());
|
||||||
|
}
|
||||||
|
|
||||||
|
config_option_elements.emplace_back(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigSubMenu::add_slider_option(std::string_view id, std::string_view name, std::string_view description, double value, double min, double max, double step, bool percent, std::function<void(const std::string &, double)> callback) {
|
||||||
|
ConfigOptionSlider *option_slider = get_current_context().create_element<ConfigOptionSlider>(config_scroll_container, value, min, max, step, percent, callback);
|
||||||
|
add_option(option_slider, id, name, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigSubMenu::add_text_option(std::string_view id, std::string_view name, std::string_view description, std::string_view value, std::function<void(const std::string &, const std::string &)> callback) {
|
||||||
|
ConfigOptionTextInput *option_text_input = get_current_context().create_element<ConfigOptionTextInput>(config_scroll_container, value, callback);
|
||||||
|
add_option(option_text_input, id, name, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigSubMenu::add_radio_option(std::string_view id, std::string_view name, std::string_view description, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback) {
|
||||||
|
ConfigOptionRadio *option_radio = get_current_context().create_element<ConfigOptionRadio>(config_scroll_container, value, options, callback);
|
||||||
|
add_option(option_radio, id, name, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElementConfigSubMenu
|
||||||
|
|
||||||
|
ElementConfigSubMenu::ElementConfigSubMenu(const Rml::String &tag) : Rml::Element(tag) {
|
||||||
|
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Flex);
|
||||||
|
SetProperty("width", "100%");
|
||||||
|
SetProperty("height", "100%");
|
||||||
|
|
||||||
|
recompui::Element this_compat(this);
|
||||||
|
recompui::ContextId context = get_current_context();
|
||||||
|
config_sub_menu = context.create_element<ConfigSubMenu>(&this_compat);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementConfigSubMenu::~ElementConfigSubMenu() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigSubMenu::set_display(bool display) {
|
||||||
|
SetProperty(Rml::PropertyId::Display, display ? Rml::Style::Display::Block : Rml::Style::Display::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigSubMenu *ElementConfigSubMenu::get_config_sub_menu_element() const {
|
||||||
|
return config_sub_menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
115
src/ui/ui_config_sub_menu.h
Normal file
115
src/ui/ui_config_sub_menu.h
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#ifndef RECOMPUI_CONFIG_SUB_MENU_H
|
||||||
|
#define RECOMPUI_CONFIG_SUB_MENU_H
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "elements/ui_button.h"
|
||||||
|
#include "elements/ui_container.h"
|
||||||
|
#include "elements/ui_label.h"
|
||||||
|
#include "elements/ui_radio.h"
|
||||||
|
#include "elements/ui_scroll_container.h"
|
||||||
|
#include "elements/ui_slider.h"
|
||||||
|
#include "elements/ui_text_input.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class ConfigOptionElement : public Element {
|
||||||
|
protected:
|
||||||
|
Label *name_label = nullptr;
|
||||||
|
std::string option_id;
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
std::function<void(ConfigOptionElement *, bool)> hover_callback = nullptr;
|
||||||
|
|
||||||
|
virtual void process_event(const Event &e) override;
|
||||||
|
std::string_view get_type_name() override { return "ConfigOptionElement"; }
|
||||||
|
public:
|
||||||
|
ConfigOptionElement(Element *parent);
|
||||||
|
virtual ~ConfigOptionElement();
|
||||||
|
void set_option_id(std::string_view id);
|
||||||
|
void set_name(std::string_view name);
|
||||||
|
void set_description(std::string_view description);
|
||||||
|
void set_hover_callback(std::function<void(ConfigOptionElement *, bool)> callback);
|
||||||
|
const std::string &get_description() const;
|
||||||
|
void set_nav_auto(NavDirection dir) override { get_focus_element()->set_nav_auto(dir); }
|
||||||
|
void set_nav_none(NavDirection dir) override { get_focus_element()->set_nav_none(dir); }
|
||||||
|
void set_nav(NavDirection dir, Element* element) override { get_focus_element()->set_nav(dir, element); }
|
||||||
|
void set_nav_manual(NavDirection dir, const std::string& target) override { get_focus_element()->set_nav_manual(dir, target); }
|
||||||
|
virtual Element* get_focus_element() { return this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigOptionSlider : public ConfigOptionElement {
|
||||||
|
protected:
|
||||||
|
Slider *slider = nullptr;
|
||||||
|
std::function<void(const std::string &, double)> callback;
|
||||||
|
|
||||||
|
void slider_value_changed(double v);
|
||||||
|
std::string_view get_type_name() override { return "ConfigOptionSlider"; }
|
||||||
|
public:
|
||||||
|
ConfigOptionSlider(Element *parent, double value, double min_value, double max_value, double step_value, bool percent, std::function<void(const std::string &, double)> callback);
|
||||||
|
Element* get_focus_element() override { return slider; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigOptionTextInput : public ConfigOptionElement {
|
||||||
|
protected:
|
||||||
|
TextInput *text_input = nullptr;
|
||||||
|
std::function<void(const std::string &, const std::string &)> callback;
|
||||||
|
|
||||||
|
void text_changed(const std::string &text);
|
||||||
|
std::string_view get_type_name() override { return "ConfigOptionTextInput"; }
|
||||||
|
public:
|
||||||
|
ConfigOptionTextInput(Element *parent, std::string_view value, std::function<void(const std::string &, const std::string &)> callback);
|
||||||
|
Element* get_focus_element() override { return text_input; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigOptionRadio : public ConfigOptionElement {
|
||||||
|
protected:
|
||||||
|
Radio *radio = nullptr;
|
||||||
|
std::function<void(const std::string &, uint32_t)> callback;
|
||||||
|
|
||||||
|
void index_changed(uint32_t index);
|
||||||
|
std::string_view get_type_name() override { return "ConfigOptionRadio"; }
|
||||||
|
public:
|
||||||
|
ConfigOptionRadio(Element *parent, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback);
|
||||||
|
Element* get_focus_element() override { return radio; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigSubMenu : public Element {
|
||||||
|
private:
|
||||||
|
Container *header_container = nullptr;
|
||||||
|
Button *back_button = nullptr;
|
||||||
|
Label *title_label = nullptr;
|
||||||
|
Container *body_container = nullptr;
|
||||||
|
Label *description_label = nullptr;
|
||||||
|
Container *config_container = nullptr;
|
||||||
|
ScrollContainer *config_scroll_container = nullptr;
|
||||||
|
std::vector<ConfigOptionElement *> config_option_elements;
|
||||||
|
std::unordered_set<ConfigOptionElement *> hover_option_elements;
|
||||||
|
|
||||||
|
void back_button_pressed();
|
||||||
|
void option_hovered(ConfigOptionElement *option, bool active);
|
||||||
|
void add_option(ConfigOptionElement *option, std::string_view id, std::string_view name, std::string_view description);
|
||||||
|
protected:
|
||||||
|
std::string_view get_type_name() override { return "ConfigSubMenu"; }
|
||||||
|
public:
|
||||||
|
ConfigSubMenu(Element *parent);
|
||||||
|
virtual ~ConfigSubMenu();
|
||||||
|
void enter(std::string_view title);
|
||||||
|
void clear_options();
|
||||||
|
void add_slider_option(std::string_view id, std::string_view name, std::string_view description, double value, double min, double max, double step, bool percent, std::function<void(const std::string &, double)> callback);
|
||||||
|
void add_text_option(std::string_view id, std::string_view name, std::string_view description, std::string_view value, std::function<void(const std::string &, const std::string &)> callback);
|
||||||
|
void add_radio_option(std::string_view id, std::string_view name, std::string_view description, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ElementConfigSubMenu : public Rml::Element {
|
||||||
|
public:
|
||||||
|
ElementConfigSubMenu(const Rml::String &tag);
|
||||||
|
virtual ~ElementConfigSubMenu();
|
||||||
|
void set_display(bool display);
|
||||||
|
ConfigSubMenu *get_config_sub_menu_element() const;
|
||||||
|
private:
|
||||||
|
ConfigSubMenu *config_sub_menu;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
42
src/ui/ui_elements.cpp
Normal file
42
src/ui/ui_elements.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include "ui_elements.h"
|
||||||
|
|
||||||
|
struct RecompCustomElement {
|
||||||
|
Rml::String tag;
|
||||||
|
std::unique_ptr<Rml::ElementInstancer> instancer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CUSTOM_ELEMENT(s, e) { s, std::make_unique< Rml::ElementInstancerGeneric< e > >() }
|
||||||
|
|
||||||
|
static RecompCustomElement custom_elements[] = {
|
||||||
|
CUSTOM_ELEMENT("recomp-mod-menu", recompui::ElementModMenu),
|
||||||
|
CUSTOM_ELEMENT("recomp-config-sub-menu", recompui::ElementConfigSubMenu),
|
||||||
|
};
|
||||||
|
|
||||||
|
void recompui::register_custom_elements() {
|
||||||
|
for (auto& element_config : custom_elements) {
|
||||||
|
Rml::Factory::RegisterElementInstancer(element_config.tag, element_config.instancer.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::ElementInstancer* recompui::get_custom_element_instancer(std::string tag) {
|
||||||
|
for (auto& element_config : custom_elements) {
|
||||||
|
if (tag == element_config.tag) {
|
||||||
|
return element_config.instancer.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::ElementPtr recompui::create_custom_element(Rml::Element* parent, std::string tag) {
|
||||||
|
auto instancer = recompui::get_custom_element_instancer(tag);
|
||||||
|
const Rml::XMLAttributes attributes = {};
|
||||||
|
if (Rml::ElementPtr element = instancer->InstanceElement(parent, tag, attributes))
|
||||||
|
{
|
||||||
|
element->SetInstancer(instancer);
|
||||||
|
element->SetAttributes(attributes);
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
16
src/ui/ui_elements.h
Normal file
16
src/ui/ui_elements.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef RECOMPUI_ELEMENTS_H
|
||||||
|
#define RECOMPUI_ELEMENTS_H
|
||||||
|
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include "RmlUi/Core/Element.h"
|
||||||
|
|
||||||
|
#include "ui_mod_menu.h"
|
||||||
|
#include "ui_config_sub_menu.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
void register_custom_elements();
|
||||||
|
|
||||||
|
Rml::ElementInstancer* get_custom_element_instancer(std::string tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
154
src/ui/ui_helpers.h
Normal file
154
src/ui/ui_helpers.h
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
#ifndef __UI_HELPERS_H__
|
||||||
|
#define __UI_HELPERS_H__
|
||||||
|
|
||||||
|
#include "librecomp/helpers.hpp"
|
||||||
|
#include "librecomp/addresses.hpp"
|
||||||
|
|
||||||
|
#include "elements/ui_element.h"
|
||||||
|
#include "elements/ui_types.h"
|
||||||
|
#include "core/ui_context.h"
|
||||||
|
#include "core/ui_resource.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
constexpr ResourceId root_element_id{ 0xFFFFFFFE };
|
||||||
|
|
||||||
|
inline ContextId get_context(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t context_id = _arg<0, uint32_t>(rdram, ctx);
|
||||||
|
return ContextId{ .slot_id = context_id };
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float arg_float2(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
union {
|
||||||
|
float f32;
|
||||||
|
uint32_t u32;
|
||||||
|
} val;
|
||||||
|
|
||||||
|
val.u32 = _arg<2, uint32_t>(rdram, ctx);
|
||||||
|
return val.f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float arg_float3(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
union {
|
||||||
|
float f32;
|
||||||
|
uint32_t u32;
|
||||||
|
} val;
|
||||||
|
|
||||||
|
val.u32 = _arg<3, uint32_t>(rdram, ctx);
|
||||||
|
return val.f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float arg_float4(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
union {
|
||||||
|
float f32;
|
||||||
|
uint32_t u32;
|
||||||
|
} val;
|
||||||
|
|
||||||
|
val.u32 = MEM_W(0x10, ctx->r29);
|
||||||
|
return val.f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float arg_float5(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
union {
|
||||||
|
float f32;
|
||||||
|
uint32_t u32;
|
||||||
|
} val;
|
||||||
|
|
||||||
|
val.u32 = MEM_W(0x14, ctx->r29);
|
||||||
|
return val.f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float arg_float6(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
union {
|
||||||
|
float f32;
|
||||||
|
uint32_t u32;
|
||||||
|
} val;
|
||||||
|
|
||||||
|
val.u32 = MEM_W(0x18, ctx->r29);
|
||||||
|
return val.f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int arg_index>
|
||||||
|
ResourceId arg_resource_id(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint32_t slot_id = _arg<arg_index, uint32_t>(rdram, ctx);
|
||||||
|
|
||||||
|
return ResourceId{ .slot_id = slot_id };
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int arg_index>
|
||||||
|
Element* arg_element(uint8_t* rdram, recomp_context* ctx, ContextId ui_context) {
|
||||||
|
ResourceId resource = arg_resource_id<arg_index>(rdram, ctx);
|
||||||
|
|
||||||
|
if (resource == ResourceId::null()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
else if (resource == root_element_id) {
|
||||||
|
return ui_context.get_root_element();
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource.as_element();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int arg_index>
|
||||||
|
Style* arg_style(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
ResourceId resource = arg_resource_id<arg_index>(rdram, ctx);
|
||||||
|
|
||||||
|
if (resource == ResourceId::null()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
else if (resource == root_element_id) {
|
||||||
|
ContextId ui_context = recompui::get_current_context();
|
||||||
|
return ui_context.get_root_element();
|
||||||
|
}
|
||||||
|
|
||||||
|
return *resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int arg_index>
|
||||||
|
Color arg_color(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
PTR(u8) color_arg = _arg<arg_index, PTR(u8)>(rdram, ctx);
|
||||||
|
|
||||||
|
Color ret{};
|
||||||
|
|
||||||
|
ret.r = MEM_B(0, color_arg);
|
||||||
|
ret.g = MEM_B(1, color_arg);
|
||||||
|
ret.b = MEM_B(2, color_arg);
|
||||||
|
ret.a = MEM_B(3, color_arg);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void return_resource(recomp_context* ctx, ResourceId resource) {
|
||||||
|
_return<uint32_t>(ctx, resource.slot_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void return_string(uint8_t* rdram, recomp_context* ctx, const std::string& ret) {
|
||||||
|
gpr addr = (reinterpret_cast<uint8_t*>(recomp::alloc(rdram, ret.size() + 1)) - rdram) + 0xFFFFFFFF80000000ULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ret.size(); i++) {
|
||||||
|
MEM_B(i, addr) = ret[i];
|
||||||
|
}
|
||||||
|
MEM_B(ret.size(), addr) = '\x00';
|
||||||
|
|
||||||
|
_return<PTR(char)>(ctx, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string decode_string(uint8_t* rdram, PTR(char) str) {
|
||||||
|
// Get the length of the byteswapped string.
|
||||||
|
size_t len = 0;
|
||||||
|
while (MEM_B(str, len) != 0x00) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ret{};
|
||||||
|
ret.reserve(len + 1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
ret += (char)MEM_B(str, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -15,97 +15,111 @@ bool mm_rom_valid = false;
|
|||||||
extern std::vector<recomp::GameEntry> supported_games;
|
extern std::vector<recomp::GameEntry> supported_games;
|
||||||
|
|
||||||
void select_rom() {
|
void select_rom() {
|
||||||
nfdnchar_t* native_path = nullptr;
|
nfdnchar_t* native_path = nullptr;
|
||||||
zelda64::open_file_dialog([](bool success, const std::filesystem::path& path) {
|
zelda64::open_file_dialog([](bool success, const std::filesystem::path& path) {
|
||||||
if (success) {
|
if (success) {
|
||||||
recomp::RomValidationError rom_error = recomp::select_rom(path, supported_games[0].game_id);
|
recomp::RomValidationError rom_error = recomp::select_rom(path, supported_games[0].game_id);
|
||||||
switch (rom_error) {
|
switch (rom_error) {
|
||||||
case recomp::RomValidationError::Good:
|
case recomp::RomValidationError::Good:
|
||||||
mm_rom_valid = true;
|
mm_rom_valid = true;
|
||||||
model_handle.DirtyVariable("mm_rom_valid");
|
model_handle.DirtyVariable("mm_rom_valid");
|
||||||
break;
|
break;
|
||||||
case recomp::RomValidationError::FailedToOpen:
|
case recomp::RomValidationError::FailedToOpen:
|
||||||
recompui::message_box("Failed to open ROM file.");
|
recompui::message_box("Failed to open ROM file.");
|
||||||
break;
|
break;
|
||||||
case recomp::RomValidationError::NotARom:
|
case recomp::RomValidationError::NotARom:
|
||||||
recompui::message_box("This is not a valid ROM file.");
|
recompui::message_box("This is not a valid ROM file.");
|
||||||
break;
|
break;
|
||||||
case recomp::RomValidationError::IncorrectRom:
|
case recomp::RomValidationError::IncorrectRom:
|
||||||
recompui::message_box("This ROM is not the correct game.");
|
recompui::message_box("This ROM is not the correct game.");
|
||||||
break;
|
break;
|
||||||
case recomp::RomValidationError::NotYet:
|
case recomp::RomValidationError::NotYet:
|
||||||
recompui::message_box("This game isn't supported yet.");
|
recompui::message_box("This game isn't supported yet.");
|
||||||
break;
|
break;
|
||||||
case recomp::RomValidationError::IncorrectVersion:
|
case recomp::RomValidationError::IncorrectVersion:
|
||||||
recompui::message_box(
|
recompui::message_box(
|
||||||
"This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
"This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
||||||
break;
|
break;
|
||||||
case recomp::RomValidationError::OtherError:
|
case recomp::RomValidationError::OtherError:
|
||||||
recompui::message_box("An unknown error has occurred.");
|
recompui::message_box("An unknown error has occurred.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
recompui::ContextId launcher_context;
|
||||||
|
|
||||||
|
recompui::ContextId recompui::get_launcher_context_id() {
|
||||||
|
return launcher_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LauncherMenu : public recompui::MenuController {
|
class LauncherMenu : public recompui::MenuController {
|
||||||
public:
|
public:
|
||||||
LauncherMenu() {
|
LauncherMenu() {
|
||||||
mm_rom_valid = recomp::is_rom_valid(supported_games[0].game_id);
|
mm_rom_valid = recomp::is_rom_valid(supported_games[0].game_id);
|
||||||
}
|
}
|
||||||
~LauncherMenu() override {
|
~LauncherMenu() override {
|
||||||
|
|
||||||
}
|
}
|
||||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
void load_document() override {
|
||||||
const std::filesystem::path asset = zelda64::get_asset_path("launcher.rml");
|
launcher_context = recompui::create_context(zelda64::get_asset_path("launcher.rml"));
|
||||||
return context->LoadDocument(asset.string());
|
}
|
||||||
}
|
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
recompui::register_event(listener, "select_rom",
|
||||||
recompui::register_event(listener, "select_rom",
|
[](const std::string& param, Rml::Event& event) {
|
||||||
[](const std::string& param, Rml::Event& event) {
|
select_rom();
|
||||||
select_rom();
|
}
|
||||||
}
|
);
|
||||||
);
|
recompui::register_event(listener, "rom_selected",
|
||||||
recompui::register_event(listener, "rom_selected",
|
[](const std::string& param, Rml::Event& event) {
|
||||||
[](const std::string& param, Rml::Event& event) {
|
mm_rom_valid = true;
|
||||||
mm_rom_valid = true;
|
model_handle.DirtyVariable("mm_rom_valid");
|
||||||
model_handle.DirtyVariable("mm_rom_valid");
|
}
|
||||||
}
|
);
|
||||||
);
|
recompui::register_event(listener, "start_game",
|
||||||
recompui::register_event(listener, "start_game",
|
[](const std::string& param, Rml::Event& event) {
|
||||||
[](const std::string& param, Rml::Event& event) {
|
recomp::start_game(supported_games[0].game_id);
|
||||||
recomp::start_game(supported_games[0].game_id);
|
recompui::hide_all_contexts();
|
||||||
recompui::set_current_menu(recompui::Menu::None);
|
}
|
||||||
}
|
);
|
||||||
);
|
|
||||||
recompui::register_event(listener, "open_controls",
|
recompui::register_event(listener, "open_controls",
|
||||||
[](const std::string& param, Rml::Event& event) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
recompui::set_current_menu(recompui::Menu::Config);
|
recompui::set_config_tab(recompui::ConfigTab::Controls);
|
||||||
recompui::set_config_submenu(recompui::ConfigSubmenu::Controls);
|
recompui::hide_all_contexts();
|
||||||
}
|
recompui::show_context(recompui::get_config_context_id(), "");
|
||||||
);
|
}
|
||||||
|
);
|
||||||
recompui::register_event(listener, "open_settings",
|
recompui::register_event(listener, "open_settings",
|
||||||
[](const std::string& param, Rml::Event& event) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
recompui::set_current_menu(recompui::Menu::Config);
|
recompui::set_config_tab(recompui::ConfigTab::General);
|
||||||
recompui::set_config_submenu(recompui::ConfigSubmenu::General);
|
recompui::hide_all_contexts();
|
||||||
}
|
recompui::show_context(recompui::get_config_context_id(), "");
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
recompui::register_event(listener, "open_mods",
|
||||||
|
[](const std::string ¶m, Rml::Event &event) {
|
||||||
|
recompui::set_config_tab(recompui::ConfigTab::Mods);
|
||||||
|
recompui::hide_all_contexts();
|
||||||
|
recompui::show_context(recompui::get_config_context_id(), "");
|
||||||
|
}
|
||||||
|
);
|
||||||
recompui::register_event(listener, "exit_game",
|
recompui::register_event(listener, "exit_game",
|
||||||
[](const std::string& param, Rml::Event& event) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
ultramodern::quit();
|
ultramodern::quit();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
void make_bindings(Rml::Context* context) override {
|
void make_bindings(Rml::Context* context) override {
|
||||||
Rml::DataModelConstructor constructor = context->CreateDataModel("launcher_model");
|
Rml::DataModelConstructor constructor = context->CreateDataModel("launcher_model");
|
||||||
|
|
||||||
constructor.Bind("mm_rom_valid", &mm_rom_valid);
|
constructor.Bind("mm_rom_valid", &mm_rom_valid);
|
||||||
|
|
||||||
version_string = recomp::get_project_version().to_string();
|
version_string = recomp::get_project_version().to_string();
|
||||||
constructor.Bind("version_number", &version_string);
|
constructor.Bind("version_number", &version_string);
|
||||||
|
|
||||||
model_handle = constructor.GetModelHandle();
|
model_handle = constructor.GetModelHandle();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<recompui::MenuController> recompui::create_launcher_menu() {
|
std::unique_ptr<recompui::MenuController> recompui::create_launcher_menu() {
|
||||||
|
153
src/ui/ui_mod_details_panel.cpp
Normal file
153
src/ui/ui_mod_details_panel.cpp
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#include "ui_mod_details_panel.h"
|
||||||
|
|
||||||
|
#include "librecomp/mods.hpp"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
extern const std::string mod_tab_id;
|
||||||
|
|
||||||
|
ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
|
||||||
|
set_flex(1.0f, 1.0f, 200.0f);
|
||||||
|
set_height(100.0f, Unit::Percent);
|
||||||
|
set_display(Display::Flex);
|
||||||
|
set_flex_direction(FlexDirection::Column);
|
||||||
|
set_background_color(Color{ 190, 184, 219, 25 });
|
||||||
|
|
||||||
|
ContextId context = get_current_context();
|
||||||
|
|
||||||
|
header_container = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexStart);
|
||||||
|
header_container->set_flex(0.0f, 0.0f);
|
||||||
|
header_container->set_padding(16.0f);
|
||||||
|
header_container->set_gap(16.0f);
|
||||||
|
header_container->set_background_color(Color{ 0, 0, 0, 89 });
|
||||||
|
header_container->set_border_bottom_width(1.1f);
|
||||||
|
header_container->set_border_bottom_color(Color{ 255, 255, 255, 25 });
|
||||||
|
{
|
||||||
|
thumbnail_container = context.create_element<Container>(header_container, FlexDirection::Column, JustifyContent::SpaceEvenly);
|
||||||
|
thumbnail_container->set_flex(0.0f, 0.0f);
|
||||||
|
{
|
||||||
|
thumbnail_image = context.create_element<Image>(thumbnail_container, "");
|
||||||
|
thumbnail_image->set_width(100.0f);
|
||||||
|
thumbnail_image->set_height(100.0f);
|
||||||
|
thumbnail_image->set_background_color(Color{ 190, 184, 219, 25 });
|
||||||
|
}
|
||||||
|
|
||||||
|
header_details_container = context.create_element<Container>(header_container, FlexDirection::Column, JustifyContent::SpaceEvenly);
|
||||||
|
header_details_container->set_flex(1.0f, 1.0f);
|
||||||
|
header_details_container->set_flex_basis(100.0f, Unit::Percent);
|
||||||
|
header_details_container->set_text_align(TextAlign::Left);
|
||||||
|
{
|
||||||
|
title_label = context.create_element<Label>(header_details_container, LabelStyle::Large);
|
||||||
|
version_label = context.create_element<Label>(header_details_container, LabelStyle::Normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body_container = context.create_element<ScrollContainer>(this, ScrollDirection::Vertical);
|
||||||
|
body_container->set_text_align(TextAlign::Left);
|
||||||
|
body_container->set_padding(16.0f);
|
||||||
|
{
|
||||||
|
authors_label = context.create_element<Label>(body_container, LabelStyle::Normal);
|
||||||
|
authors_label->set_margin_bottom(16.0f);
|
||||||
|
description_label = context.create_element<Label>(body_container, LabelStyle::Normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons_container = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::SpaceAround);
|
||||||
|
buttons_container->set_flex(0.0f, 0.0f);
|
||||||
|
buttons_container->set_padding(16.0f);
|
||||||
|
buttons_container->set_justify_content(JustifyContent::SpaceBetween);
|
||||||
|
buttons_container->set_border_top_width(1.1f);
|
||||||
|
buttons_container->set_border_top_color(Color{ 255, 255, 255, 25 });
|
||||||
|
buttons_container->set_background_color(Color{ 0, 0, 0, 89 });
|
||||||
|
{
|
||||||
|
enable_container = context.create_element<Container>(buttons_container, FlexDirection::Row, JustifyContent::FlexStart);
|
||||||
|
enable_container->set_align_items(AlignItems::Center);
|
||||||
|
enable_container->set_gap(16.0f);
|
||||||
|
{
|
||||||
|
enable_toggle = context.create_element<Toggle>(enable_container);
|
||||||
|
enable_toggle->add_checked_callback([this](bool checked){ enable_toggle_checked(checked); });
|
||||||
|
enable_toggle->set_nav_manual(NavDirection::Up, mod_tab_id);
|
||||||
|
|
||||||
|
enable_label = context.create_element<Label>(enable_container, "A currently enabled mod requires this mod", LabelStyle::Annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_button = context.create_element<Button>(buttons_container, "Configure", recompui::ButtonStyle::Secondary);
|
||||||
|
configure_button->add_pressed_callback([this](){ configure_button_pressed(); });
|
||||||
|
configure_button->set_nav_manual(NavDirection::Up, mod_tab_id);
|
||||||
|
}
|
||||||
|
clear_mod_navigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
ModDetailsPanel::~ModDetailsPanel() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModDetailsPanel::disable_toggle() {
|
||||||
|
enable_toggle->set_enabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModDetailsPanel::set_mod_details(const recomp::mods::ModDetails& details, const std::string &thumbnail, bool toggle_checked, bool toggle_enabled, bool toggle_label_visible, bool configure_enabled) {
|
||||||
|
cur_details = details;
|
||||||
|
|
||||||
|
thumbnail_image->set_src(thumbnail);
|
||||||
|
|
||||||
|
title_label->set_text(cur_details.display_name);
|
||||||
|
version_label->set_text(cur_details.version.to_string());
|
||||||
|
|
||||||
|
std::string authors_str = "Authors:";
|
||||||
|
bool first = true;
|
||||||
|
for (const std::string& author : details.authors) {
|
||||||
|
authors_str += (first ? " " : ", ") + author;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
authors_label->set_text(authors_str);
|
||||||
|
description_label->set_text(cur_details.description);
|
||||||
|
enable_toggle->set_checked(toggle_checked);
|
||||||
|
enable_toggle->set_enabled(toggle_enabled);
|
||||||
|
configure_button->set_enabled(configure_enabled);
|
||||||
|
enable_label->set_display(toggle_label_visible ? Display::Block : Display::None);
|
||||||
|
|
||||||
|
if (configure_enabled) {
|
||||||
|
enable_toggle->set_nav(NavDirection::Right, configure_button);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
enable_toggle->set_nav_none(NavDirection::Right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModDetailsPanel::set_mod_toggled_callback(std::function<void(bool)> callback) {
|
||||||
|
mod_toggled_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModDetailsPanel::set_mod_configure_pressed_callback(std::function<void()> callback) {
|
||||||
|
mod_configure_pressed_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModDetailsPanel::setup_mod_navigation(Element* nav_target) {
|
||||||
|
enable_toggle->set_nav(NavDirection::Left, nav_target);
|
||||||
|
|
||||||
|
if (enable_toggle->is_enabled()) {
|
||||||
|
configure_button->set_nav(NavDirection::Left, enable_toggle);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
configure_button->set_nav(NavDirection::Left, nav_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModDetailsPanel::clear_mod_navigation() {
|
||||||
|
enable_toggle->set_nav_none(NavDirection::Left);
|
||||||
|
configure_button->set_nav_none(NavDirection::Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModDetailsPanel::enable_toggle_checked(bool checked) {
|
||||||
|
if (mod_toggled_callback != nullptr) {
|
||||||
|
mod_toggled_callback(checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModDetailsPanel::configure_button_pressed() {
|
||||||
|
if (mod_configure_pressed_callback != nullptr) {
|
||||||
|
mod_configure_pressed_callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace recompui
|
52
src/ui/ui_mod_details_panel.h
Normal file
52
src/ui/ui_mod_details_panel.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#ifndef RECOMPUI_ELEMENT_MOD_DETAILS_PANEL_H
|
||||||
|
#define RECOMPUI_ELEMENT_MOD_DETAILS_PANEL_H
|
||||||
|
|
||||||
|
#include "librecomp/mods.hpp"
|
||||||
|
#include "elements/ui_button.h"
|
||||||
|
#include "elements/ui_container.h"
|
||||||
|
#include "elements/ui_image.h"
|
||||||
|
#include "elements/ui_label.h"
|
||||||
|
#include "elements/ui_toggle.h"
|
||||||
|
#include "elements/ui_scroll_container.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class ModDetailsPanel : public Element {
|
||||||
|
public:
|
||||||
|
ModDetailsPanel(Element *parent);
|
||||||
|
virtual ~ModDetailsPanel();
|
||||||
|
void set_mod_details(const recomp::mods::ModDetails& details, const std::string &thumbnail, bool toggle_checked, bool toggle_enabled, bool toggle_label_visible, bool configure_enabled);
|
||||||
|
void set_mod_toggled_callback(std::function<void(bool)> callback);
|
||||||
|
void set_mod_configure_pressed_callback(std::function<void()> callback);
|
||||||
|
void setup_mod_navigation(Element* nav_target);
|
||||||
|
void clear_mod_navigation();
|
||||||
|
Toggle* get_enable_toggle() { return enable_toggle; }
|
||||||
|
Button* get_configure_button() { return configure_button; }
|
||||||
|
void disable_toggle();
|
||||||
|
protected:
|
||||||
|
std::string_view get_type_name() override { return "ModDetailsPanel"; }
|
||||||
|
private:
|
||||||
|
recomp::mods::ModDetails cur_details;
|
||||||
|
Container *thumbnail_container = nullptr;
|
||||||
|
Image *thumbnail_image = nullptr;
|
||||||
|
Container *header_container = nullptr;
|
||||||
|
Container *header_details_container = nullptr;
|
||||||
|
Label *title_label = nullptr;
|
||||||
|
Label *version_label = nullptr;
|
||||||
|
ScrollContainer *body_container = nullptr;
|
||||||
|
Label *description_label = nullptr;
|
||||||
|
Label *authors_label = nullptr;
|
||||||
|
Container *buttons_container = nullptr;
|
||||||
|
Container *enable_container = nullptr;
|
||||||
|
Toggle *enable_toggle = nullptr;
|
||||||
|
Label *enable_label = nullptr;
|
||||||
|
Button *configure_button = nullptr;
|
||||||
|
std::function<void(bool)> mod_toggled_callback = nullptr;
|
||||||
|
std::function<void()> mod_configure_pressed_callback = nullptr;
|
||||||
|
|
||||||
|
void enable_toggle_checked(bool checked);
|
||||||
|
void configure_button_pressed();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
||||||
|
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user