mirror of
				https://github.com/Zelda64Recomp/Zelda64Recomp.git
				synced 2025-10-30 08:03:03 +00:00 
			
		
		
		
	1.2 Release Candidate (#572)
* Remove dummy description for mod config options
* Tag release candidate version
* Apply min width to element triggering rmlui assert (#573)
* Restore 0th day (#574)
* Handle controller up events even while binding inputs to avoid spamming the bind button
* Add MouseButton UI event and use it to fix focus issue on radio, also fix sliders not moving until mouse is released
* Bump version string to 1.2.0-rc2
* mod configure menu description padding set to 16
* Added the ability for focus to set the current mod config option description (#576)
* Added the ability for focus to set the current mod config option description
* add focus to text input
* only clear description if element matches
* Fix race condition crash when setting element text, bump version to 1.2.0-rc3
* Revert "Fix race condition crash when setting element text, bump version to 1.2.0-rc3"
This reverts commit 4934a04d8a.
* Defer setting an element's text if it has children to fix race condition crash, bump version to 1.2.0-rc3
* Defer remaining set_text calls to prevent another race conditionresource
* Update runtime to fix some issues that could happen after mod conflicts
and bump version to 1.2.0-rc4
* Update runtime to fix regenerated functions using the wrong event index and bump version to 1.2.0-rc5
* Add support for suffixed .so. files. Also prevent dropping extracted dynamic libraries.
* Update RT64 commit to fix cstdint include for re-spirv.
* Bump version to rc6.
* Dummy commit to fix CI bot
* Use compile-time macro for Flatpak instead.
* Rename macro.
* Bump version to 1.2.0-rc7
* Fix define on flatpak, add cwd behavior.
* Temporarily disable current working dir code.
* Add the cmake option for flatpak.
* Bump version to 1.2.0-rc8
* Update MacPorts. (#578)
* Update MacPorts.
* Try GitHub runner.
* Deselect universal, return to blaze.
* pull universal libiconv first
* Fix controller nav issues in config menu, bump version to 1.2.0-rc9
---------
Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com>
Co-authored-by: LittleCube <littlecubehax@gmail.com>
Co-authored-by: Dario <dariosamo@gmail.com>
			
			
This commit is contained in:
		
							parent
							
								
									14f92c41ab
								
							
						
					
					
						commit
						983d7f43f8
					
				
					 30 changed files with 325 additions and 76 deletions
				
			
		
							
								
								
									
										8
									
								
								.github/macos/macports.yaml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/macos/macports.yaml
									
										
									
									
										vendored
									
									
								
							|  | @ -1,4 +1,4 @@ | |||
| version: '2.9.3' | ||||
| version: '2.10.6' | ||||
| prefix: '/opt/local' | ||||
| variants: | ||||
|   select: | ||||
|  | @ -6,9 +6,11 @@ variants: | |||
|     - metal | ||||
|   deselect: x11 | ||||
| ports: | ||||
|   - name: clang-18 | ||||
|   - name: llvm-18 | ||||
|   - name: libiconv | ||||
|     select: universal | ||||
|   - name: libsdl2 | ||||
|     select: universal | ||||
|   - name: freetype | ||||
|     select: universal | ||||
|   - name: clang-18 | ||||
|   - name: llvm-18 | ||||
|  |  | |||
|  | @ -25,6 +25,10 @@ if (APPLE) | |||
|     enable_language(OBJC OBJCXX) | ||||
| endif() | ||||
| 
 | ||||
| if (CMAKE_SYSTEM_NAME MATCHES "Linux") | ||||
|     option(RECOMP_FLATPAK "Configure the build for Flatpak compatibility." OFF) | ||||
| endif() | ||||
| 
 | ||||
| # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: | ||||
| if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") | ||||
|     cmake_policy(SET CMP0135 NEW) | ||||
|  | @ -41,6 +45,10 @@ set(RT64_STATIC TRUE) | |||
| set(RT64_SDL_WINDOW_VULKAN TRUE) | ||||
| add_compile_definitions(HLSL_CPU) | ||||
| 
 | ||||
| if (RECOMP_FLATPAK) | ||||
|     add_compile_definitions(RECOMP_FLATPAK) | ||||
| endif() | ||||
| 
 | ||||
| add_subdirectory(${CMAKE_SOURCE_DIR}/lib/rt64 ${CMAKE_BINARY_DIR}/rt64) | ||||
| 
 | ||||
| # set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}") | ||||
|  |  | |||
|  | @ -1656,6 +1656,7 @@ scrollbarhorizontal sliderbar { | |||
|   flex-direction: row; | ||||
|   justify-content: space-between; | ||||
|   width: 268dp; | ||||
|   min-width: 1dp; | ||||
|   height: 128dp; | ||||
|   margin-right: 10dp; | ||||
| } | ||||
|  |  | |||
|  | @ -342,6 +342,8 @@ $stick-size: 200; | |||
|     flex-direction: row; | ||||
|     justify-content: space-between; | ||||
|     width: space(268); | ||||
|     // WORKAROUND FIX: prevents RMLui assert error | ||||
|     min-width: 1dp; | ||||
|     height: space(128); | ||||
|     margin-right: space(10); | ||||
| } | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ | |||
|         "./N64Recomp us.rev1.toml", | ||||
|         "./RSPRecomp aspMain.us.rev1.toml", | ||||
|         "./RSPRecomp njpgdspMain.us.rev1.toml", | ||||
|         "cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_MAKE_PROGRAM=ninja -DPATCHES_C_COMPILER=clang -DPATCHES_LD=ld.lld -G Ninja -S . -B cmake-build", | ||||
|         "cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_MAKE_PROGRAM=ninja -DPATCHES_C_COMPILER=clang -DPATCHES_LD=ld.lld -DRECOMP_FLATPAK=ON -G Ninja -S . -B cmake-build", | ||||
|         "cmake --build cmake-build --config Release --target Zelda64Recompiled --parallel", | ||||
|         "rm -rf assets/scss", | ||||
|         "mkdir -p /app/bin", | ||||
|  |  | |||
|  | @ -69,6 +69,7 @@ namespace recompui { | |||
|     }; | ||||
| 
 | ||||
|     void set_config_tab(ConfigTab tab); | ||||
|     int config_tab_to_index(ConfigTab tab); | ||||
|     Rml::ElementTabSet* get_config_tabset(); | ||||
|     Rml::Element* get_mod_tab(); | ||||
|     void set_config_tabset_mod_nav(); | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit 0aa75b98baaef9d23a0d2cf51c8b44fd857c8fe1 | ||||
| Subproject commit c5e268aa0f71cf06a10a001da981dc3e02e7dff0 | ||||
							
								
								
									
										2
									
								
								lib/rt64
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								lib/rt64
									
										
									
									
									
								
							|  | @ -1 +1 @@ | |||
| Subproject commit 793abe0bc39a842bab8176264eb0003324c58f1b | ||||
| Subproject commit ada6cc62c421b142d9d90154765e44348115bd9e | ||||
|  | @ -11,7 +11,7 @@ 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 wait a much shorter amount of time for the save to complete.
 | ||||
| RECOMP_PATCH void Sram_UpdateWriteToFlashDefault(SramContext* sramCtx) { | ||||
|     if (sramCtx->status == 2) { | ||||
|         if (SysFlashrom_IsBusy() != 0) {          // if task running
 | ||||
|  | @ -23,13 +23,13 @@ RECOMP_PATCH void Sram_UpdateWriteToFlashDefault(SramContext* sramCtx) { | |||
|                 sramCtx->status = 4; | ||||
|             } | ||||
|         } | ||||
|     } else if (sramCtx->status == 4) { | ||||
|         // @recomp Patched to check status instead of using a hardcoded wait.
 | ||||
|     } else if (OSTIME_TO_TIMER(osGetTime() - sramCtx->startWriteOsTime) >= SECONDS_TO_TIMER_PRECISE(0, 25)) { | ||||
|         // @recomp Patched to wait a much shorter amount of time.
 | ||||
|         sramCtx->status = 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // @recomp Patched to not wait a hardcoded amount of time for the save to complete.
 | ||||
| // @recomp Patched to wait a much shorter amount of time for the save to complete.
 | ||||
| RECOMP_PATCH void Sram_UpdateWriteToFlashOwlSave(SramContext* sramCtx) { | ||||
|     if (sramCtx->status == 7) { | ||||
|         if (SysFlashrom_IsBusy() != 0) {          // Is task running
 | ||||
|  | @ -49,8 +49,8 @@ RECOMP_PATCH void Sram_UpdateWriteToFlashOwlSave(SramContext* sramCtx) { | |||
|                 sramCtx->status = 4; | ||||
|             } | ||||
|         } | ||||
|     } else if (sramCtx->status == 4) { | ||||
|         // @recomp Patched to check status instead of using a hardcoded wait.
 | ||||
|     } else if (OSTIME_TO_TIMER(osGetTime() - sramCtx->startWriteOsTime) >= SECONDS_TO_TIMER_PRECISE(0, 25)) { | ||||
|         // @recomp Patched to wait a much shorter amount of time.
 | ||||
|         sramCtx->status = 0; | ||||
|         bzero(sramCtx->saveBuf, SAVE_BUFFER_SIZE); | ||||
|         gSaveContext.save.isOwlSave = false; | ||||
|  |  | |||
|  | @ -287,6 +287,10 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) { | |||
|     case SDL_EventType::SDL_DROPCOMPLETE: | ||||
|         recompui::drop_files(DropState.files_dropped); | ||||
|         break; | ||||
|     case SDL_EventType::SDL_CONTROLLERBUTTONUP: | ||||
|         // Always queue button up events to avoid missing them during binding.
 | ||||
|         recompui::queue_event(*event); | ||||
|         break; | ||||
|     default: | ||||
|         queue_if_enabled(event); | ||||
|         break; | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ | |||
| 
 | ||||
| #include "../../lib/rt64/src/contrib/stb/stb_image.h" | ||||
| 
 | ||||
| const std::string version_string = "1.2.0-dev"; | ||||
| const std::string version_string = "1.2.0-rc9"; | ||||
| 
 | ||||
| template<typename... Ts> | ||||
| void exit_error(const char* str, Ts ...args) { | ||||
|  | @ -601,7 +601,14 @@ int main(int argc, char** argv) { | |||
|     // Force wasapi on Windows, as there seems to be some issue with sample queueing with directsound currently.
 | ||||
|     SDL_setenv("SDL_AUDIODRIVER", "wasapi", true); | ||||
| #endif | ||||
|     //printf("Current dir: %ls\n", std::filesystem::current_path().c_str());
 | ||||
| 
 | ||||
| #if defined(__linux__) && defined(RECOMP_FLATPAK) | ||||
|     // When using Flatpak, applications tend to launch from the home directory by default.
 | ||||
|     // Mods might use the current working directory to store the data, so we switch it to a directory
 | ||||
|     // with persistent data storage and write permissions under Flatpak to ensure it works.
 | ||||
|     std::error_code ec; | ||||
|     std::filesystem::current_path("/var/data", ec); | ||||
| #endif | ||||
| 
 | ||||
|     // Initialize SDL audio and set the output frequency.
 | ||||
|     SDL_InitSubSystem(SDL_INIT_AUDIO); | ||||
|  |  | |||
|  | @ -48,13 +48,11 @@ namespace zelda64 { | |||
|     std::filesystem::path get_program_path() { | ||||
| #if defined(__APPLE__) | ||||
|         return get_bundle_resource_directory(); | ||||
| #elif defined(__linux__) | ||||
|         std::error_code ec; | ||||
|         if (std::filesystem::exists("/.flatpak-info", ec)) { | ||||
|             return "/app/bin"; | ||||
|         } | ||||
| #endif | ||||
| #elif defined(__linux__) && defined(RECOMP_FLATPAK) | ||||
|         return "/app/bin"; | ||||
| #else | ||||
|         return ""; | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     std::filesystem::path get_asset_path(const char* asset) { | ||||
|  |  | |||
|  | @ -36,7 +36,8 @@ namespace recompui { | |||
|         Element root_element; | ||||
|         Element* autofocus_element = nullptr; | ||||
|         std::vector<Element*> loose_elements; | ||||
|         std::unordered_set<ResourceId> to_update;         | ||||
|         std::unordered_set<ResourceId> to_update; | ||||
|         std::vector<std::tuple<Element*, ResourceId, std::string>> to_set_text;      | ||||
|         bool captures_input = true; | ||||
|         bool captures_mouse = true; | ||||
|         Context(Rml::ElementDocument* document) : document(document), root_element(document) {} | ||||
|  | @ -67,6 +68,8 @@ enum class ContextErrorType { | |||
|     AddResourceToWrongContext, | ||||
|     UpdateElementWithoutContext, | ||||
|     UpdateElementInWrongContext, | ||||
|     SetTextElementWithoutContext, | ||||
|     SetTextElementInWrongContext, | ||||
|     GetResourceWithoutOpen, | ||||
|     GetResourceFailed, | ||||
|     DestroyResourceWithoutOpen, | ||||
|  | @ -119,6 +122,12 @@ void context_error(recompui::ContextId id, ContextErrorType type) { | |||
|         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::SetTextElementWithoutContext: | ||||
|             error_message = "Attempted to set the text of a UI element with no open UI context"; | ||||
|             break; | ||||
|         case ContextErrorType::SetTextElementInWrongContext: | ||||
|             error_message = "Attempted to set the text of 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; | ||||
|  | @ -407,6 +416,40 @@ void recompui::ContextId::process_updates() { | |||
| 
 | ||||
|         static_cast<Element*>(cur_resource->get())->handle_event(update_event); | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::tuple<Element*, ResourceId, std::string>> to_set_text = std::move(opened_context->to_set_text); | ||||
| 
 | ||||
|     // Delete the Rml elements that are pending deletion.
 | ||||
|     for (auto cur_text_update : to_set_text) { | ||||
|         Element* element_ptr = std::get<0>(cur_text_update); | ||||
|         ResourceId resource = std::get<1>(cur_text_update); | ||||
|         std::string& text = std::get<2>(cur_text_update); | ||||
| 
 | ||||
|         // If the resource ID is valid, prefer that as we can quickly validate if the resource still exists.
 | ||||
|         if (resource != ResourceId::null()) { | ||||
|             resource_slotmap::key cur_key{ resource.slot_id }; | ||||
|             std::unique_ptr<Style>* cur_resource = opened_context->resources.get(cur_key); | ||||
| 
 | ||||
|             // Make sure the resource exists before setting its text, as it may have been deleted.
 | ||||
|             if (cur_resource == nullptr) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             // Perform the text update.
 | ||||
|             static_cast<Element*>(cur_resource->get())->base->SetInnerRML(text); | ||||
|         } | ||||
|         // Otherwise we use the element pointer, but we need to validate that it still exists before doing so.
 | ||||
|         else { | ||||
|             // Scan the current resources to find the target element.
 | ||||
|             for (const std::unique_ptr<Style>& cur_e : opened_context->resources) { | ||||
|                 if (cur_e.get() == element_ptr) { | ||||
|                     element_ptr->base->SetInnerRML(text); | ||||
|                     // We can stop after finding the element.
 | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool recompui::ContextId::captures_input() { | ||||
|  | @ -514,6 +557,20 @@ void recompui::ContextId::queue_element_update(ResourceId element) { | |||
|     opened_context->to_update.emplace(element); | ||||
| } | ||||
| 
 | ||||
| void recompui::ContextId::queue_set_text(Element* element, std::string&& text) { | ||||
|     // Ensure a context is currently opened by this thread.
 | ||||
|     if (opened_context_id == ContextId::null()) { | ||||
|         context_error(*this, ContextErrorType::SetTextElementWithoutContext); | ||||
|     } | ||||
| 
 | ||||
|     // Check that the context that was specified is the same one that's currently open.
 | ||||
|     if (*this != opened_context_id) { | ||||
|         context_error(*this, ContextErrorType::SetTextElementInWrongContext); | ||||
|     } | ||||
| 
 | ||||
|     opened_context->to_set_text.emplace_back(std::make_tuple(element, element->resource_id, std::move(text))); | ||||
| } | ||||
| 
 | ||||
| recompui::Style* recompui::ContextId::create_style() { | ||||
|     return add_resource_impl(std::make_unique<Style>()); | ||||
| } | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ namespace recompui { | |||
| 
 | ||||
|         void add_loose_element(Element* element); | ||||
|         void queue_element_update(ResourceId element); | ||||
|         void queue_set_text(Element* element, std::string&& text); | ||||
| 
 | ||||
|         Style* create_style(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| namespace recompui { | ||||
| 
 | ||||
|     Clickable::Clickable(Element *parent, bool draggable) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, draggable ? EventType::Drag : EventType::None)) { | ||||
|     Clickable::Clickable(Element *parent, bool draggable) : Element(parent, Events(EventType::Click, EventType::MouseButton, EventType::Hover, EventType::Enable, draggable ? EventType::Drag : EventType::None)) { | ||||
|         set_cursor(Cursor::Pointer); | ||||
|         if (draggable) { | ||||
|             set_drag(Drag::Drag); | ||||
|  | @ -14,12 +14,23 @@ namespace recompui { | |||
|         case EventType::Click: { | ||||
|             if (is_enabled()) { | ||||
|                 const EventClick &click = std::get<EventClick>(e.variant); | ||||
|                 for (const auto &function : pressed_callbacks) { | ||||
|                 for (const auto &function : clicked_callbacks) { | ||||
|                     function(click.x, click.y); | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         case EventType::MouseButton: { | ||||
|             if (is_enabled()) { | ||||
|                 const EventMouseButton &mousebutton = std::get<EventMouseButton>(e.variant); | ||||
|                 if (mousebutton.button == MouseButton::Left && mousebutton.pressed) { | ||||
|                     for (const auto &function : pressed_callbacks) { | ||||
|                         function(mousebutton.x, mousebutton.y); | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         case EventType::Hover: | ||||
|             set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled()); | ||||
|             break; | ||||
|  | @ -51,6 +62,10 @@ namespace recompui { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void Clickable::add_clicked_callback(std::function<void(float, float)> callback) { | ||||
|         clicked_callbacks.emplace_back(callback); | ||||
|     } | ||||
| 
 | ||||
|     void Clickable::add_pressed_callback(std::function<void(float, float)> callback) { | ||||
|         pressed_callbacks.emplace_back(callback); | ||||
|     } | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ namespace recompui { | |||
| 
 | ||||
|     class Clickable : public Element { | ||||
|     protected: | ||||
|         std::vector<std::function<void(float, float)>> clicked_callbacks; | ||||
|         std::vector<std::function<void(float, float)>> pressed_callbacks; | ||||
|         std::vector<std::function<void(float, float, DragPhase)>> dragged_callbacks; | ||||
| 
 | ||||
|  | @ -14,6 +15,7 @@ namespace recompui { | |||
|         std::string_view get_type_name() override { return "Clickable"; } | ||||
|     public: | ||||
|         Clickable(Element *parent, bool draggable = false); | ||||
|         void add_clicked_callback(std::function<void(float, float)> callback); | ||||
|         void add_pressed_callback(std::function<void(float, float)> callback); | ||||
|         void add_dragged_callback(std::function<void(float, float, DragPhase)> callback); | ||||
|     }; | ||||
|  |  | |||
|  | @ -75,6 +75,11 @@ void Element::register_event_listeners(uint32_t events_enabled) { | |||
|         base->AddEventListener(Rml::EventId::Click, this); | ||||
|     } | ||||
| 
 | ||||
|     if (events_enabled & Events(EventType::MouseButton)) { | ||||
|         base->AddEventListener(Rml::EventId::Mousedown, this); | ||||
|         base->AddEventListener(Rml::EventId::Mouseup, this); | ||||
|     } | ||||
| 
 | ||||
|     if (events_enabled & Events(EventType::Focus)) { | ||||
|         base->AddEventListener(Rml::EventId::Focus, this); | ||||
|         base->AddEventListener(Rml::EventId::Blur, this); | ||||
|  | @ -152,6 +157,19 @@ void Element::set_id(const std::string& new_id) { | |||
|     base->SetId(new_id); | ||||
| } | ||||
| 
 | ||||
| recompui::MouseButton convert_rml_mouse_button(int button) { | ||||
|     switch (button) { | ||||
|         case 0: | ||||
|             return recompui::MouseButton::Left; | ||||
|         case 1: | ||||
|             return recompui::MouseButton::Right; | ||||
|         case 2: | ||||
|             return recompui::MouseButton::Middle; | ||||
|         default: | ||||
|             return recompui::MouseButton::Count; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Element::ProcessEvent(Rml::Event &event) { | ||||
|     ContextId prev_context = recompui::try_close_current_context(); | ||||
|     ContextId context = ContextId::null(); | ||||
|  | @ -172,6 +190,22 @@ void Element::ProcessEvent(Rml::Event &event) { | |||
|     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::Mousedown: | ||||
|         { | ||||
|             MouseButton mouse_button = convert_rml_mouse_button(event.GetParameter("button", 3)); | ||||
|             if (mouse_button != MouseButton::Count) { | ||||
|                 handle_event(Event::mousebutton_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), mouse_button, true)); | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
|     case Rml::EventId::Mouseup: | ||||
|         { | ||||
|             MouseButton mouse_button = convert_rml_mouse_button(event.GetParameter("button", 3)); | ||||
|             if (mouse_button != MouseButton::Count) { | ||||
|                 handle_event(Event::mousebutton_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), mouse_button, false)); | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
|     case Rml::EventId::Keydown: | ||||
|         switch ((Rml::Input::KeyIdentifier)event.GetParameter<int>("key_identifier", 0)) { | ||||
|             case Rml::Input::KeyIdentifier::KI_LEFT: | ||||
|  | @ -343,8 +377,12 @@ std::string escape_rml(std::string_view string) | |||
| 
 | ||||
| void Element::set_text(std::string_view text) { | ||||
|     if (can_set_text) { | ||||
|         // Queue the text update. If it's applied immediately, it might happen
 | ||||
|         // while the document is being updated or rendered. This can cause a crash
 | ||||
|         // due to the child elements being deleted while the document is being updated.
 | ||||
|         // Queueing them defers it to the update thread, which prevents that issue.
 | ||||
|         // Escape the string into Rml to prevent element injection.
 | ||||
|         base->SetInnerRML(escape_rml(text)); | ||||
|         get_current_context().queue_set_text(this, escape_rml(text)); | ||||
|     } | ||||
|     else { | ||||
|         assert(false && "Attempted to set text of an element that cannot have its text set."); | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ 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) { | ||||
|     RadioOption::RadioOption(Element *parent, std::string_view name, uint32_t index) : Element(parent, Events(EventType::MouseButton, EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable, EventType::Update), "label", true) { | ||||
|         this->index = index; | ||||
| 
 | ||||
|         enable_focus(); | ||||
|  | @ -37,12 +37,24 @@ namespace recompui { | |||
|         pressed_callback = callback; | ||||
|     } | ||||
| 
 | ||||
|     void RadioOption::set_focus_callback(std::function<void(bool)> callback) { | ||||
|         focus_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::MouseButton: | ||||
|             { | ||||
|                 const EventMouseButton &mousebutton = std::get<EventMouseButton>(e.variant); | ||||
|                 if (mousebutton.button == MouseButton::Left && mousebutton.pressed) { | ||||
|                     pressed_callback(index); | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
|         case EventType::Click: | ||||
|             pressed_callback(index); | ||||
|             break; | ||||
|  | @ -59,6 +71,9 @@ namespace recompui { | |||
|                 if (active) { | ||||
|                     queue_update(); | ||||
|                 } | ||||
|                 if (focus_callback != nullptr) { | ||||
|                     focus_callback(active); | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
|         case EventType::Update: | ||||
|  | @ -67,10 +82,6 @@ namespace recompui { | |||
|                 apply_styles(); | ||||
|                 queue_update(); | ||||
|             } | ||||
|             if (focus_queued) { | ||||
|                 focus_queued = false; | ||||
|                 focus(); | ||||
|             } | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|  | @ -106,7 +117,7 @@ namespace recompui { | |||
|         }, val); | ||||
|     } | ||||
| 
 | ||||
|     Radio::Radio(Element *parent) : Container(parent, FlexDirection::Row, JustifyContent::FlexStart, Events(EventType::Focus)) { | ||||
|     Radio::Radio(Element *parent) : Container(parent, FlexDirection::Row, JustifyContent::FlexStart, Events(EventType::Focus, EventType::Update)) { | ||||
|         set_gap(24.0f); | ||||
|         set_align_items(AlignItems::FlexStart); | ||||
|         enable_focus(); | ||||
|  | @ -118,10 +129,18 @@ namespace recompui { | |||
|             if (!options.empty()) { | ||||
|                 if (std::get<EventFocus>(e.variant).active) { | ||||
|                     blur(); | ||||
|                     options[index]->queue_focus(); | ||||
|                     queue_child_focus(); | ||||
|                 } | ||||
|                 if (focus_callback != nullptr) { | ||||
|                     focus_callback(std::get<EventFocus>(e.variant).active); | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
|         case EventType::Update: | ||||
|             if (child_focus_queued) { | ||||
|                 child_focus_queued = false; | ||||
|                 options[index]->focus(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -131,7 +150,12 @@ namespace recompui { | |||
| 
 | ||||
|     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); }); | ||||
|         option->set_pressed_callback([this](uint32_t index){ options[index]->focus(); option_selected(index); }); | ||||
|         option->set_focus_callback([this](bool active) { | ||||
|             if (focus_callback != nullptr) { | ||||
|                 focus_callback(active); | ||||
|             } | ||||
|         }); | ||||
|         options.emplace_back(option); | ||||
| 
 | ||||
|         // The first option was added, select it.
 | ||||
|  | @ -156,6 +180,10 @@ namespace recompui { | |||
|     void Radio::add_index_changed_callback(std::function<void(uint32_t)> callback) { | ||||
|         index_changed_callbacks.emplace_back(callback); | ||||
|     } | ||||
| 
 | ||||
|     void Radio::set_focus_callback(std::function<void(bool)> callback) { | ||||
|         focus_callback = callback; | ||||
|     } | ||||
|      | ||||
|     void Radio::set_nav_auto(NavDirection dir) { | ||||
|         Element::set_nav_auto(dir); | ||||
|  | @ -237,4 +265,4 @@ namespace recompui { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
| }; | ||||
|  |  | |||
|  | @ -10,16 +10,16 @@ namespace recompui { | |||
|         Style checked_style; | ||||
|         Style pulsing_style; | ||||
|         std::function<void(uint32_t)> pressed_callback = nullptr; | ||||
|         std::function<void(bool)> focus_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_focus_callback(std::function<void(bool)> callback); | ||||
|         void set_selected_state(bool enable); | ||||
|         void queue_focus() { focus_queued = true; queue_update(); } | ||||
|     }; | ||||
| 
 | ||||
|     class Radio : public Container { | ||||
|  | @ -27,6 +27,8 @@ namespace recompui { | |||
|         std::vector<RadioOption *> options; | ||||
|         uint32_t index = 0; | ||||
|         std::vector<std::function<void(uint32_t)>> index_changed_callbacks; | ||||
|         std::function<void(bool)> focus_callback = nullptr; | ||||
|         bool child_focus_queued = false; | ||||
| 
 | ||||
|         void set_index_internal(uint32_t index, bool setup, bool trigger_callbacks); | ||||
|         void option_selected(uint32_t index); | ||||
|  | @ -35,6 +37,7 @@ namespace recompui { | |||
|     protected: | ||||
|         virtual void process_event(const Event &e) override; | ||||
|         std::string_view get_type_name() override { return "LabelRadio"; } | ||||
|         void queue_child_focus() { child_focus_queued = true; queue_update(); } | ||||
|     public: | ||||
|         Radio(Element *parent); | ||||
|         virtual ~Radio(); | ||||
|  | @ -42,6 +45,7 @@ namespace recompui { | |||
|         void set_index(uint32_t index); | ||||
|         uint32_t get_index() const; | ||||
|         void add_index_changed_callback(std::function<void(uint32_t)> callback); | ||||
|         void set_focus_callback(std::function<void(bool)> 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]; } | ||||
|  | @ -51,4 +55,4 @@ namespace recompui { | |||
|         void set_nav_manual(NavDirection dir, const std::string& target) override; | ||||
|     }; | ||||
| 
 | ||||
| } // namespace recompui
 | ||||
| } // namespace recompui
 | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ namespace recompui { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void Slider::bar_clicked(float x, float) { | ||||
|     void Slider::bar_pressed(float x, float) { | ||||
|         update_value_from_mouse(x); | ||||
|     } | ||||
| 
 | ||||
|  | @ -81,6 +81,9 @@ namespace recompui { | |||
|                 if (active) { | ||||
|                     queue_update(); | ||||
|                 } | ||||
|                 if (focus_callback != nullptr) { | ||||
|                     focus_callback(active); | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
|         case EventType::Update: | ||||
|  | @ -151,7 +154,7 @@ namespace recompui { | |||
| 
 | ||||
|         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_pressed_callback([this](float x, float y){ bar_pressed(x, y); focus(); }); | ||||
|         slider_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); focus(); }); | ||||
| 
 | ||||
|         { | ||||
|  | @ -160,7 +163,7 @@ namespace recompui { | |||
|             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_pressed_callback([this](float x, float y){ bar_pressed(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); | ||||
|  | @ -219,6 +222,10 @@ namespace recompui { | |||
|         value_changed_callbacks.emplace_back(callback); | ||||
|     } | ||||
| 
 | ||||
|     void Slider::set_focus_callback(std::function<void(bool)> callback) { | ||||
|         focus_callback = callback; | ||||
|     } | ||||
| 
 | ||||
|     void Slider::do_step(bool increment) { | ||||
|         double new_value = value; | ||||
|         if (increment) { | ||||
|  | @ -233,4 +240,4 @@ namespace recompui { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
| } // namespace recompui
 | ||||
| } // namespace recompui
 | ||||
|  |  | |||
|  | @ -23,9 +23,10 @@ namespace recompui { | |||
|         double max_value = 100.0; | ||||
|         double step_value = 0.0; | ||||
|         std::vector<std::function<void(double)>> value_changed_callbacks; | ||||
|         std::function<void(bool)> focus_callback = nullptr; | ||||
| 
 | ||||
|         void set_value_internal(double v, bool setup, bool trigger_callbacks); | ||||
|         void bar_clicked(float x, float y); | ||||
|         void bar_pressed(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); | ||||
|  | @ -51,6 +52,7 @@ namespace recompui { | |||
|         double get_step_value() const; | ||||
|         void add_value_changed_callback(std::function<void(double)> callback); | ||||
|         void do_step(bool increment); | ||||
|         void set_focus_callback(std::function<void(bool)> callback); | ||||
|     }; | ||||
| 
 | ||||
| } // namespace recompui
 | ||||
|  |  | |||
|  | @ -16,12 +16,19 @@ namespace recompui { | |||
| 
 | ||||
|             break; | ||||
|         } | ||||
|         case EventType::Focus: { | ||||
|             const EventFocus &event = std::get<EventFocus>(e.variant); | ||||
|             if (focus_callback != nullptr) { | ||||
|                 focus_callback(event.active); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     TextInput::TextInput(Element *parent, bool text_visible) : Element(parent, Events(EventType::Text), "input") { | ||||
|     TextInput::TextInput(Element *parent, bool text_visible) : Element(parent, Events(EventType::Text, EventType::Focus), "input") { | ||||
|         if (!text_visible) { | ||||
|             set_attribute("type", "password"); | ||||
|         } | ||||
|  | @ -48,4 +55,7 @@ namespace recompui { | |||
|         text_changed_callbacks.emplace_back(callback); | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
|     void TextInput::set_focus_callback(std::function<void(bool)> callback) { | ||||
|         focus_callback = callback; | ||||
|     } | ||||
| }; | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ namespace recompui { | |||
|     private: | ||||
|         std::string text; | ||||
|         std::vector<std::function<void(const std::string &)>> text_changed_callbacks; | ||||
|         std::function<void(bool)> focus_callback = nullptr; | ||||
|     protected: | ||||
|         virtual void process_event(const Event &e) override; | ||||
|         std::string_view get_type_name() override { return "TextInput"; } | ||||
|  | @ -16,6 +17,7 @@ namespace recompui { | |||
|         void set_text(std::string_view text); | ||||
|         const std::string &get_text(); | ||||
|         void add_text_changed_callback(std::function<void(const std::string &)> callback); | ||||
|         void set_focus_callback(std::function<void(bool)> callback); | ||||
|     }; | ||||
| 
 | ||||
| } // namespace recompui
 | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ namespace recompui { | |||
|         Text, | ||||
|         Update, | ||||
|         Navigate, | ||||
|         MouseButton, | ||||
|         Count | ||||
|     }; | ||||
| 
 | ||||
|  | @ -50,6 +51,13 @@ namespace recompui { | |||
|         Left | ||||
|     }; | ||||
| 
 | ||||
|     enum class MouseButton { | ||||
|         Left, | ||||
|         Right, | ||||
|         Middle, | ||||
|         Count | ||||
|     }; | ||||
| 
 | ||||
|     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); | ||||
|  | @ -91,7 +99,14 @@ namespace recompui { | |||
|         NavDirection direction; | ||||
|     }; | ||||
| 
 | ||||
|     using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, EventNavigate, std::monostate>; | ||||
|     struct EventMouseButton { | ||||
|         float x; | ||||
|         float y; | ||||
|         MouseButton button; | ||||
|         bool pressed; | ||||
|     }; | ||||
| 
 | ||||
|     using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, EventNavigate, EventMouseButton, std::monostate>; | ||||
| 
 | ||||
|     struct Event { | ||||
|         EventType type; | ||||
|  | @ -153,6 +168,13 @@ namespace recompui { | |||
|             e.variant = EventNavigate{ direction }; | ||||
|             return e; | ||||
|         } | ||||
| 
 | ||||
|         static Event mousebutton_event(float x, float y, MouseButton button, bool pressed) { | ||||
|             Event e; | ||||
|             e.type = EventType::MouseButton; | ||||
|             e.variant = EventMouseButton{ x, y, button, pressed }; | ||||
|             return e; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     enum class Display { | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ Rml::DataModelHandle sound_options_model_handle; | |||
| // True if controller config menu is open, false if keyboard config menu is open, undefined otherwise
 | ||||
| bool configuring_controller = false; | ||||
| 
 | ||||
| static int config_tab_to_index(recompui::ConfigTab tab) { | ||||
| int recompui::config_tab_to_index(recompui::ConfigTab tab) { | ||||
|     switch (tab) { | ||||
|     case recompui::ConfigTab::General: | ||||
|         return 0; | ||||
|  | @ -472,7 +472,7 @@ class ConfigTabsetListener : public Rml::EventListener { | |||
|     void ProcessEvent(Rml::Event& event) override { | ||||
|         if (event.GetId() == Rml::EventId::Tabchange) { | ||||
|             int tab_index = event.GetParameter<int>("tab_index", 0); | ||||
|             bool in_mod_tab = (tab_index == config_tab_to_index(recompui::ConfigTab::Mods)); | ||||
|             bool in_mod_tab = (tab_index == recompui::config_tab_to_index(recompui::ConfigTab::Mods)); | ||||
|             if (in_mod_tab) { | ||||
|                 recompui::set_config_tabset_mod_nav(); | ||||
|             } | ||||
|  |  | |||
|  | @ -13,6 +13,9 @@ namespace recompui { | |||
| void ConfigOptionElement::process_event(const Event &e) { | ||||
|     switch (e.type) { | ||||
|     case EventType::Hover: | ||||
|         if (hover_callback == nullptr) { | ||||
|             break; | ||||
|         } | ||||
|         hover_callback(this, std::get<EventHover>(e.variant).active); | ||||
|         break; | ||||
|     case EventType::Update: | ||||
|  | @ -53,6 +56,10 @@ void ConfigOptionElement::set_hover_callback(std::function<void(ConfigOptionElem | |||
|     hover_callback = callback; | ||||
| } | ||||
| 
 | ||||
| void ConfigOptionElement::set_focus_callback(std::function<void(const std::string &, bool)> callback) { | ||||
|     focus_callback = callback; | ||||
| } | ||||
| 
 | ||||
| const std::string &ConfigOptionElement::get_description() const { | ||||
|     return description; | ||||
| } | ||||
|  | @ -73,6 +80,9 @@ ConfigOptionSlider::ConfigOptionSlider(Element *parent, double value, double min | |||
|     slider->set_step_value(step_value); | ||||
|     slider->set_value(value); | ||||
|     slider->add_value_changed_callback([this](double v){ slider_value_changed(v); }); | ||||
|     slider->set_focus_callback([this](bool active) { | ||||
|         focus_callback(option_id, active); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| // ConfigOptionTextInput
 | ||||
|  | @ -88,6 +98,9 @@ ConfigOptionTextInput::ConfigOptionTextInput(Element *parent, std::string_view v | |||
|     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); }); | ||||
|     text_input->set_focus_callback([this](bool active) { | ||||
|         focus_callback(option_id, active); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| // ConfigOptionRadio
 | ||||
|  | @ -100,6 +113,9 @@ ConfigOptionRadio::ConfigOptionRadio(Element *parent, uint32_t value, const std: | |||
|     this->callback = callback; | ||||
| 
 | ||||
|     radio = get_current_context().create_element<Radio>(this); | ||||
|     radio->set_focus_callback([this](bool active) { | ||||
|         focus_callback(option_id, active); | ||||
|     }); | ||||
|     radio->add_index_changed_callback([this](uint32_t index){ index_changed(index); }); | ||||
|     for (std::string_view option : options) { | ||||
|         radio->add_option(option); | ||||
|  | @ -122,19 +138,19 @@ void ConfigSubMenu::back_button_pressed() { | |||
|     recompui::focus_mod_configure_button(); | ||||
| } | ||||
| 
 | ||||
| void ConfigSubMenu::option_hovered(ConfigOptionElement *option, bool active) { | ||||
| void ConfigSubMenu::set_description_option_element(ConfigOptionElement *option, bool active) { | ||||
|     if (active) { | ||||
|         hover_option_elements.emplace(option); | ||||
|         description_option_element = option; | ||||
|     } | ||||
|     else { | ||||
|         hover_option_elements.erase(option); | ||||
|     else if (description_option_element == option) { | ||||
|         description_option_element = nullptr; | ||||
|     } | ||||
| 
 | ||||
|     if (hover_option_elements.empty()) { | ||||
|     if (description_option_element == nullptr) { | ||||
|         description_label->set_text(""); | ||||
|     } | ||||
|     else { | ||||
|         description_label->set_text((*hover_option_elements.begin())->get_description()); | ||||
|         description_label->set_text(description_option_element->get_description()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -170,8 +186,10 @@ ConfigSubMenu::ConfigSubMenu(Element *parent) : Element(parent) { | |||
|             config_scroll_container = context.create_element<ScrollContainer>(config_container, ScrollDirection::Vertical); | ||||
|         } | ||||
| 
 | ||||
|         description_label = context.create_element<Label>(body_container, "Description", LabelStyle::Small); | ||||
|         description_label = context.create_element<Label>(body_container, "", LabelStyle::Small); | ||||
|         description_label->set_min_width(800.0f); | ||||
|         description_label->set_padding_left(16.0f); | ||||
|         description_label->set_padding_right(16.0f); | ||||
|     } | ||||
| 
 | ||||
|     recompui::get_current_context().set_autofocus_element(back_button); | ||||
|  | @ -188,14 +206,15 @@ void ConfigSubMenu::enter(std::string_view title) { | |||
| void ConfigSubMenu::clear_options() { | ||||
|     config_scroll_container->clear_children(); | ||||
|     config_option_elements.clear(); | ||||
|     hover_option_elements.clear(); | ||||
|     description_option_element = nullptr; | ||||
| } | ||||
| 
 | ||||
| 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); }); | ||||
|     option->set_hover_callback([this](ConfigOptionElement *option, bool active){ set_description_option_element(option, active); }); | ||||
|     option->set_focus_callback([this, option](const std::string &id, bool active) { set_description_option_element(option, active); }); | ||||
|     if (config_option_elements.empty()) { | ||||
|         back_button->set_nav(NavDirection::Down, option->get_focus_element()); | ||||
|         option->set_nav(NavDirection::Up, back_button); | ||||
|  | @ -247,4 +266,4 @@ ConfigSubMenu *ElementConfigSubMenu::get_config_sub_menu_element() const { | |||
|     return config_sub_menu; | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ protected: | |||
|     std::string name; | ||||
|     std::string description; | ||||
|     std::function<void(ConfigOptionElement *, bool)> hover_callback = nullptr; | ||||
|     std::function<void(const std::string &, bool)> focus_callback = nullptr; | ||||
| 
 | ||||
|     virtual void process_event(const Event &e) override; | ||||
|     std::string_view get_type_name() override { return "ConfigOptionElement"; } | ||||
|  | @ -30,6 +31,7 @@ public: | |||
|     void set_name(std::string_view name); | ||||
|     void set_description(std::string_view description); | ||||
|     void set_hover_callback(std::function<void(ConfigOptionElement *, bool)> callback); | ||||
|     void set_focus_callback(std::function<void(const std::string &, 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); } | ||||
|  | @ -84,10 +86,10 @@ private: | |||
|     Container *config_container = nullptr; | ||||
|     ScrollContainer *config_scroll_container = nullptr; | ||||
|     std::vector<ConfigOptionElement *> config_option_elements; | ||||
|     std::unordered_set<ConfigOptionElement *> hover_option_elements; | ||||
|     ConfigOptionElement * description_option_element = nullptr; | ||||
| 
 | ||||
|     void back_button_pressed(); | ||||
|     void option_hovered(ConfigOptionElement *option, bool active); | ||||
|     void set_description_option_element(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"; } | ||||
|  | @ -112,4 +114,4 @@ private: | |||
| }; | ||||
| 
 | ||||
| } | ||||
| #endif | ||||
| #endif | ||||
|  |  | |||
|  | @ -8,6 +8,18 @@ namespace recompui { | |||
|     static const std::u8string OldExtension = u8".old"; | ||||
|     static const std::u8string NewExtension = u8".new"; | ||||
| 
 | ||||
|     static bool is_dynamic_lib(const std::filesystem::path &file_path) { | ||||
| #if defined(_WIN32) | ||||
|         return file_path.extension() == ".dll"; | ||||
| #elif defined(__linux__) | ||||
|         return file_path.extension() == ".so" || file_path.filename().string().find(".so.") != std::string::npos; | ||||
| #elif defined(__APPLE__) | ||||
|         return file_path.extension() == ".dylib"; | ||||
| #else | ||||
|         static_assert(false, "Unimplemented for this platform."); | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     size_t zip_write_func(void *opaque, mz_uint64 offset, const void *bytes, size_t count) { | ||||
|         std::ofstream &stream = *(std::ofstream *)(opaque); | ||||
|         stream.seekp(offset, std::ios::beg); | ||||
|  | @ -187,15 +199,8 @@ namespace recompui { | |||
|                     first_nrm_iterator = std::prev(result.pending_installations.end()); | ||||
|                 } | ||||
|             } | ||||
| #if defined(_WIN32) | ||||
|             else if (target_path.extension() == ".dll") { | ||||
| #elif defined(__linux__) | ||||
|             else if (target_path.extension() == ".so") { | ||||
| #elif defined(__APPLE__) | ||||
|             else if (target_path.extension() == ".dylib") { | ||||
| #else | ||||
|             static_assert(false, "Unimplemented for this platform."); { | ||||
| #endif | ||||
|              | ||||
|             if (is_dynamic_lib(target_path)) { | ||||
|                 std::filesystem::path target_write_path = target_path.u8string() + NewExtension; | ||||
|                 std::ofstream output_stream(target_write_path, std::ios::binary); | ||||
|                 if (!output_stream.is_open()) { | ||||
|  | @ -281,6 +286,13 @@ namespace recompui { | |||
|     void ModInstaller::start_mod_installation(const std::list<std::filesystem::path> &file_paths, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, Result &result) { | ||||
|         result = Result(); | ||||
| 
 | ||||
|         for (const std::filesystem::path &path : file_paths) { | ||||
|             if (is_dynamic_lib(path)) { | ||||
|                 result.error_messages.emplace_back("The provided mod(s) must be installed without extracting the ZIP file(s). Please install the mod ZIP file(s) directly."); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (const std::filesystem::path &path : file_paths) { | ||||
|             recomp::mods::ModOpenError open_error; | ||||
|             recomp::mods::ZipModFileHandle file_handle(path, open_error); | ||||
|  |  | |||
|  | @ -589,7 +589,10 @@ void ModMenu::create_mod_list() { | |||
|         install_mods_button->set_nav_manual(NavDirection::Up, mod_tab_id); | ||||
|     } | ||||
| 
 | ||||
|     recompui::set_config_tabset_mod_nav(); | ||||
|     Rml::ElementTabSet* tabset = recompui::get_config_tabset(); | ||||
|     if (tabset && tabset->GetActiveTab() == recompui::config_tab_to_index(ConfigTab::Mods)) { | ||||
|         recompui::set_config_tabset_mod_nav(); | ||||
|     }        | ||||
| 
 | ||||
|     // Add one extra spacer at the bottom.
 | ||||
|     ModEntrySpacer *spacer = context.create_element<ModEntrySpacer>(list_scroll_container); | ||||
|  |  | |||
|  | @ -585,6 +585,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s | |||
|     while (recompui::try_deque_event(cur_event)) { | ||||
|         bool context_capturing_input = recompui::is_context_capturing_input(); | ||||
|         bool context_capturing_mouse = recompui::is_context_capturing_mouse(); | ||||
| 
 | ||||
|         // Handle up button events even when input is disabled to avoid missing them during binding.
 | ||||
|         if (cur_event.type == SDL_EventType::SDL_CONTROLLERBUTTONUP) { | ||||
|             int sdl_key = cont_button_to_key(cur_event.cbutton); | ||||
|             if (sdl_key == latest_controller_key_pressed) { | ||||
|                 latest_controller_key_pressed = SDLK_UNKNOWN; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (!recomp::all_input_disabled()) { | ||||
|             bool is_mouse_input = false; | ||||
|             // Implement some additional behavior for specific events on top of what RmlUi normally does with them.
 | ||||
|  | @ -630,13 +639,6 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s | |||
|                 cont_interacted = true; | ||||
|                 break; | ||||
|             } | ||||
|             case SDL_EventType::SDL_CONTROLLERBUTTONUP: { | ||||
|                 int sdl_key = cont_button_to_key(cur_event.cbutton); | ||||
|                 if (sdl_key == latest_controller_key_pressed) { | ||||
|                     latest_controller_key_pressed = SDLK_UNKNOWN; | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|             case SDL_EventType::SDL_KEYDOWN: | ||||
|                 non_mouse_interacted = true; | ||||
|                 kb_interacted = true; | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Wiseguy
						Wiseguy