Compare commits

...

17 Commits

Author SHA1 Message Date
bicarus 336500c8ae ccj: fix D3D10Warp.dll_unloaded issue (#609)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Fix random crashes with CCJ.

This should remove the need to add `d3d10warp.dll` to `
preload_libraries` in `ee-config.xml`.

## Testing
Tried a couple games and didn't run into the crash..
2026-04-05 16:18:19 -07:00
bicarus ee3fa58bfa graphics: option to change primary monitor before launching game (#608)
## Description of change

Adds `-mainmonitor` option which changes the monitor layout before
launching the game. This is much more reliable than the old `-monitor`
option which operated at DX9 level. This works for TDJ/UFC subscreens,
for example.

Rename `-monitor` option to `-dx9mainadapter` to discourage use.
`-monitor` will continue to work, of course.

Changing of primary monitor happens first before any orientation changes
/ refresh rate changes, which means those options will result in the
logically correct configuration.

Create a new option category for `Monitor` (rotate, refresh rate, etc).

Add a monitor selection widget for `-mainmonitor` option in the
configurator.

Improve how we log names of monitors in the log during boot - should be
less of "Generic PnP Monitor" after this.
2026-04-04 23:26:38 -07:00
bicarus a4c3eddc2f graphics: rewrite auto-rotate and refresh rate options to be more reliable (#607)
## Description of change

Rotate monitor & change the refresh rate **well before** the game is
launched.

This is much faster and more reliable compared to what we do now, which
is trying to do monitor changes during CreateWindow hooks. For example,
SDVX makes a bunch of OS / NVAPI calls to figure out monitor geometry
while it's booting, and by the time CreateWindow is called, the game
already made up its mind, which leads to weird graphical issues.

This affects both windowed mode and full screen.

In addition, add options for normal orientation (landscape) and upside
down (landscape, flipped) options. This can be used if the monitor is
normally rotated to portrait, but the game needs to be in landscape.
2026-04-04 03:23:28 -07:00
bicarus e0f6cb2cbd api: dump key binding strings (#606)
Print out human readable text for vKey when dumping bindings.
2026-04-03 20:22:24 -07:00
bicarus 1ad6a9412e imgui: update imgui to v1.92.7, update dx9 backend to latest (#605) 2026-04-03 01:51:08 -07:00
bicarus 5af3e0d494 imgui: migrate to new event-based IO (#604)
## Link to GitHub Issue or related Pull Request, if one exists
#287

## Description of change
Switch over to new event-based I/O system in ImGui so that we can keep
up with ImGui updates.

Unlike #287, this change does not modify how mouse position is handled.
That continues to be supported by newer versions of ImGui.

## Testing
wip
2026-04-02 21:00:42 -07:00
bicarus-dev b1ee828457 add some logging to device reload 2026-04-01 02:51:03 -07:00
bicarus 0d60649715 overlay: small tweaks to cards tab (#602)
## Link to GitHub Issue or related Pull Request, if one exists
#593 

## Description of change
* Display a confirmation dialog when clicking on `Generate` for the
first time (to prevent accidental loss of card number)
* If `cardN.txt` contents are read back successfully, show card number
in green to indicate success, and red in case of failure.
2026-03-31 00:07:34 -07:00
bicarus 220b27159b ea3: don't treat failure from dll_entry_init as fatal (#601)
## Link to GitHub Issue or related Pull Request, if one exists
fixes #600
2026-03-30 02:31:08 -07:00
bicarus b06a0a7d47 overlay: card override input should be disabled when overridden with cmd line (#599) 2026-03-29 20:36:42 -07:00
bicarus d66af8e16b overlay: options tab(s) clean up (#598)
## Description of change
Merge option name and command line parameter columns.
Expand the size of widget column.
In Search tab, add a new column to display the category.
Add a highlight effect to rows when hovered over - apply this to all
tables in the configurator UI.
2026-03-28 18:21:53 -07:00
bicarus-dev ad366ecdd0 update fatal crash message 2026-03-27 18:46:08 -07:00
llm96 22cb4d689c utils: print DLL info for early hooks (#597)
## Link to GitHub Issue or related Pull Request, if one exists
#596

## Description of change
Adds call to `libutils::print_dll_info` for early hooks too.

## Testing
```
[2026/03/27 09:51:16] I:launcher: SpiceTools Bootstrap (x64) (spice2x)
[2026/03/27 09:51:16] I:launcher: 1.0-V-2026-03-27T07:46:10
[2026/03/27 09:51:16] I:launcher: spice2x is free & open source; if you paid money for it, you got scammed
[2026/03/27 09:51:16] I:launcher: visit https://spice2x.github.io to download the latest version for free
[2026/03/27 09:51:16] I:launcher: arguments:
                                      [...]
                                      -z C:\Users\Noe\AppData\Local\Programs\SpiceTools\hooks\64\mempatcher64.dll
                                      [...]
[2026/03/27 09:51:16] I:launcher: loading early hook DLL C:\Users\Noe\AppData\Local\Programs\SpiceTools\hooks\64\mempatcher64.dll
[2026/03/27 09:51:16] I:superexit: enabled
[2026/03/27 09:51:16] I:libutils: DLL info for mempatcher64.dll: CompanyName = aixxe, ProductName = mempatcher, Version = 4975386c637a86e8daa89d479a0c67ab28d792ec
```
2026-03-27 12:19:32 -07:00
bicarus b38ea463aa utils: print out DLL metadata (#596)
## Link to GitHub Issue or related Pull Request, if one exists
#592 

## Description of change
* when custom DLLs are detected (`d3d9.dll`) print out some basic data
from the PE header
* for our own NVIDIA DLL stubs, add manifest files to identify ourselves
* as a bonus, when DLL hooks are loaded from `-k`, print DLL info there
as well

This is mainly to detect when DXVK is in use.
2026-03-27 02:34:56 -07:00
bicarus 056d1b2d90 ea3: warn about missing ea3 config (#595)
Show better warning message when `ea3-config.xml` / `eamuse-config.xml`
is missing. Right now we just say `failed to load prop/eamuseconfig.xml`
which is really confusing when people look for `eamuse-config.xml` that
never existed in the first place.
2026-03-27 01:56:32 -07:00
bicarus 37693916d9 overlay: show warning when binding analog axis as a button for TT/knobs (#594)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

## Description of change
When X or Y axis is being bound to any button labeled `TT` or `Knob` or
`VOL-`, show a warning and ask if the user really meant to do that,
encouraging them to use the Analog tab instead.
2026-03-26 21:17:51 -07:00
bicarus 889e8b43eb overlay: redesign Cards tab (#593)
* add `-card0` `-card1` options at the top of Cards tab to clearly
indicate that they take precedence over card files
* reorganize UI / clean up
* remove ability to directly edit the contents of `card0.txt` /
`card1.txt` as that wasn't very intuitive; instead, encourage users to
use overrides directly or use an external text editor (`Edit` button
opens notepad)
2026-03-26 01:32:48 -07:00
43 changed files with 15563 additions and 8111 deletions
+9 -4
View File
@@ -266,6 +266,7 @@ set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC) set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC) set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC) set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
# sources # sources
######### #########
@@ -813,7 +814,8 @@ endif()
# nvcuda.dll # nvcuda.dll
set(SOURCE_FILES stubs/nvcuda.cpp) set(SOURCE_FILES stubs/nvcuda.cpp)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} stubs/nvcuda.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuda.def)
set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda") set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
@@ -823,7 +825,8 @@ endif()
# nvcuvid.dll # nvcuvid.dll
set(SOURCE_FILES stubs/nvcuvid.cpp) set(SOURCE_FILES stubs/nvcuvid.cpp)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} stubs/nvcuvid.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuvid.def)
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid") set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
@@ -833,7 +836,8 @@ endif()
# nvEncodeAPI64.dll # nvEncodeAPI64.dll
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp) set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} stubs/nvEncodeAPI64.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvEncodeAPI64.def)
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64") set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
@@ -843,7 +847,8 @@ endif()
# cpusbxpkm.dll (32 bit) # cpusbxpkm.dll (32 bit)
set(SOURCE_FILES stubs/cpusbxpkm.cpp) set(SOURCE_FILES stubs/cpusbxpkm.cpp)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/cpusbxpkm.def)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm") set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
+27 -3
View File
@@ -217,7 +217,23 @@ namespace avs {
ea3_config_name = "prop/eamuse-config.xml"; ea3_config_name = "prop/eamuse-config.xml";
} }
log_info("avs-ea3", "booting (using {})", ea3_config_name); if (avs::core::file_exists(ea3_config_name)) {
log_info("avs-ea3", "booting (using {})", ea3_config_name);
} else {
log_warning("avs-ea3", "looked for the following files in order:");
log_warning("avs-ea3", " * prop/ea3-config.xml");
log_warning("avs-ea3", " * prop/ea3-cfg.xml");
if (avs::game::DLL_NAME == "beatstream1.dll") {
log_warning("avs-ea3", " * prop/ea3-config-1.xml");
}
if (avs::game::DLL_NAME == "beatstream2.dll") {
log_warning("avs-ea3", " * prop/ea3-config-2.xml");
}
log_warning("avs-ea3", " * prop/eamuse-config.xml");
log_warning("avs-ea3", "none of the files above were found, game will fail to boot");
log_warning("avs-ea3", "usually, this means your game data is incomplete");
log_fatal("avs-ea3", "no ea3 config file found in prop directory; check log messages");
}
// remember new config path // remember new config path
CFG_PATH = ea3_config_name; CFG_PATH = ea3_config_name;
@@ -554,9 +570,17 @@ namespace avs {
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param"); auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
// call the game init // call the game init
log_info("avs-ea3", "calling entry init (init code = {})", init_code_str); log_info("avs-ea3", "calling dll_entry_init (init code = {})", init_code_str);
if (!avs::game::entry_init(init_code_str.data(), app_param)) { if (!avs::game::entry_init(init_code_str.data(), app_param)) {
log_fatal("avs-ea3", "entry init failed :("); for (size_t i = 0; i < 255; i++) {
log_warning("avs-ea3", "dll_entry_init failed :(");
}
deferredlogs::defer_error_messages({
"dll_entry_init failed",
" init code: " + init_code_str,
" this is a fatal error that causes boot failure",
" most likely, your prop files have errors"
});
} }
// accommodate changes to soft id code // accommodate changes to soft id code
+2
View File
@@ -278,6 +278,8 @@ std::string Button::getVKeyString() {
return "Left Alt"; return "Left Alt";
case 0xA5: case 0xA5:
return "Right Alt"; return "Right Alt";
case INVALID_VKEY:
return "Invalid";
default: default:
// check win API // check win API
+9 -1
View File
@@ -83,12 +83,20 @@ public:
if (this->override_enabled) { if (this->override_enabled) {
return true; return true;
} }
// has a device
if (!this->device_identifier.empty()) {
return true;
}
// naive, and has a valid vKey
if (this->vKey != INVALID_VKEY) { if (this->vKey != INVALID_VKEY) {
return true; return true;
} }
for (auto &alternative : this->alternatives) { for (auto &alternative : this->alternatives) {
if (alternative.vKey != INVALID_VKEY) { if (!alternative.getDeviceIdentifier().empty() ||
alternative.getVKey() != INVALID_VKEY) {
return true; return true;
} }
} }
+1
View File
@@ -20,6 +20,7 @@ enum class OptionPickerType {
CpuAffinity, CpuAffinity,
FilePath, FilePath,
DirectoryPath, DirectoryPath,
Monitor,
}; };
struct OptionDefinition { struct OptionDefinition {
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2023 Omar Cornut Copyright (c) 2014-2026 Omar Cornut
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+645 -269
View File
@@ -1,16 +1,37 @@
// dear imgui: Renderer for DirectX9 // dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// https://github.com/ocornut/imgui // Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-19: Fixed issue in ImGui_ImplDX9_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-06-25: DirectX9: Explicitly disable texture state stages after >= 1.
// 2021-05-19: DirectX9: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-04-23: DirectX9: Explicitly setting up more graphics states to increase compatibility with unusual non-default states.
// 2021-03-18: DirectX9: Calling IDirect3DStateBlock9::Capture() after CreateStateBlock() as a workaround for state restoring issues (see #3857).
// 2021-03-03: DirectX9: Added support for IMGUI_USE_BGRA_PACKED_COLOR in user's imconfig file.
// 2021-02-18: DirectX9: Change blending equation to preserve alpha in output buffer.
// 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects(). // 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
@@ -22,160 +43,154 @@
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself. // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. // 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
#include "imgui_impl_dx9.h"
#include <algorithm> #include <algorithm>
// DirectX
#include <d3d9.h>
#include "imgui.h" #include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_dx9.h"
// allow std::min use // allow std::min use
#ifdef min #ifdef min
#undef min #undef min
#endif #endif
// DirectX data // DirectX
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; #include <d3d9.h>
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL;
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000;
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// DirectX data
struct ImGui_ImplDX9_Data
{
LPDIRECT3DDEVICE9 pd3dDevice;
LPDIRECT3DVERTEXBUFFER9 pVB;
LPDIRECT3DINDEXBUFFER9 pIB;
int VertexBufferSize;
int IndexBufferSize;
bool HasRgbaSupport;
ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
};
struct CUSTOMVERTEX
{
float pos[3];
D3DCOLOR col;
float uv[2];
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
// Render function. #ifdef IMGUI_USE_BGRA_PACKED_COLOR
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) #define IMGUI_COL_TO_DX9_ARGB(_COL) (_COL)
void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) { #else
#define IMGUI_COL_TO_DX9_ARGB(_COL) (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16))
#endif
// Avoid rendering when minimized // Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) // It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
return; static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Create and grow buffers if needed // Forward Declarations
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) { static void ImGui_ImplDX9_InitMultiViewportSupport();
if (g_pVB) { static void ImGui_ImplDX9_ShutdownMultiViewportSupport();
g_pVB->Release(); static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
g_pVB = NULL; static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
}
g_VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(ImDrawVert),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL) < 0)
return;
}
if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) {
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
g_IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32,
D3DPOOL_DEFAULT, &g_pIB, NULL) < 0)
return;
}
// Backup the DX9 state struct render_state_t {
IDirect3DStateBlock9 *d3d9_state_block = NULL; IDirect3DSurface9 *back_buffer;
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0) IDirect3DSurface9 *depth_stencil;
return; IDirect3DSurface9 *render_targets[8];
D3DCAPS9 caps;
};
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to) // Functions
D3DMATRIX last_world, last_view, last_projection; static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data, struct render_state_t *render_state)
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world); {
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
// Copy all vertices into a single contiguous buffer
ImDrawVert *vtx_dst;
ImDrawIdx *idx_dst;
if (g_pVB->Lock(0, (UINT) (draw_data->TotalVtxCount * sizeof(ImDrawVert)), (void **) &vtx_dst,
D3DLOCK_DISCARD) < 0)
return;
if (g_pIB->Lock(0, (UINT) (draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void **) &idx_dst,
D3DLOCK_DISCARD) < 0)
return;
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList *cmd_list = draw_data->CmdLists[n];
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
g_pVB->Unlock();
g_pIB->Unlock();
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(ImDrawVert));
g_pd3dDevice->SetIndices(g_pIB);
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
// Setup viewport // Setup viewport
D3DVIEWPORT9 vp; D3DVIEWPORT9 vp;
vp.X = vp.Y = 0; vp.X = vp.Y = 0;
vp.Width = (DWORD) draw_data->DisplaySize.x; vp.Width = (DWORD)draw_data->DisplaySize.x;
vp.Height = (DWORD) draw_data->DisplaySize.y; vp.Height = (DWORD)draw_data->DisplaySize.y;
vp.MinZ = 0.0f; vp.MinZ = 0.0f;
vp.MaxZ = 1.0f; vp.MaxZ = 1.0f;
g_pd3dDevice->SetViewport(&vp);
g_pd3dDevice->SetPixelShader(nullptr); LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
g_pd3dDevice->SetVertexShader(nullptr); device->SetViewport(&vp);
D3DCAPS9 caps {}; // spice ldj
if (FAILED(g_pd3dDevice->GetDeviceCaps(&caps))) { {
caps.NumSimultaneousRTs = 0UL; if (FAILED(device->GetDeviceCaps(&render_state->caps))) {
} render_state->caps.NumSimultaneousRTs = 0UL;
}
IDirect3DSurface9 *back_buffer = nullptr; // save all previous render target state
IDirect3DSurface9 *depth_stencil = nullptr; for (size_t target = 0; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
IDirect3DSurface9 *render_targets[8]; if (FAILED(device->GetRenderTarget(target, &render_state->render_targets[target]))) {
render_state->render_targets[target] = nullptr;
}
}
// save all previous render target state // get the previous depth stencil
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { if (FAILED(device->GetDepthStencilSurface(&render_state->depth_stencil))) {
if (FAILED(g_pd3dDevice->GetRenderTarget(target, &render_targets[target]))) { render_state->depth_stencil = nullptr;
render_targets[target] = nullptr; }
// set the back buffer as the current render target
if (SUCCEEDED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &render_state->back_buffer))) {
device->SetRenderTarget(0, render_state->back_buffer);
device->SetDepthStencilSurface(nullptr);
for (size_t target = 1; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
device->SetRenderTarget(target, nullptr);
}
} else {
render_state->back_buffer = nullptr;
} }
} }
// get the previous depth stencil // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
if (FAILED(g_pd3dDevice->GetDepthStencilSurface(&depth_stencil))) { device->SetPixelShader(nullptr);
depth_stencil = nullptr; device->SetVertexShader(nullptr);
} device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
// set the back buffer as the current render target device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
if (SUCCEEDED(g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) { device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
g_pd3dDevice->SetRenderTarget(0, back_buffer); device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
g_pd3dDevice->SetDepthStencilSurface(nullptr); device->SetRenderState(D3DRS_ZENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
for (size_t target = 1; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
g_pd3dDevice->SetRenderTarget(target, nullptr); device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
} device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
} else { device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
back_buffer = nullptr; device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
} device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient) device->SetRenderState(D3DRS_FOGENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); device->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false); device->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true); device->SetRenderState(D3DRS_CLIPPING, TRUE);
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false); device->SetRenderState(D3DRS_LIGHTING, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, true); device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, false); device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
// Setup orthographic projection matrix // Setup orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
@@ -185,176 +200,537 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
float T = draw_data->DisplayPos.y + 0.5f; float T = draw_data->DisplayPos.y + 0.5f;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f; float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
D3DMATRIX mat_identity = {{{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}}; D3DMATRIX mat_identity = { { { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } } };
D3DMATRIX mat_projection = D3DMATRIX mat_projection =
{{{ { { {
2.0f / (R - L), 0.0f, 0.0f, 0.0f, 2.0f/(R-L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / (T - B), 0.0f, 0.0f, 0.0f, 2.0f/(T-B), 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
(L + R) / (L - R), (T + B) / (B - T), 0.5f, 1.0f (L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f
}}}; } } };
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity); device->SetTransform(D3DTS_WORLD, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity); device->SetTransform(D3DTS_VIEW, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection); device->SetTransform(D3DTS_PROJECTION, &mat_projection);
} }
}
// Render function.
void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX9_UpdateTexture(tex);
// Create and grow buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
return;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
return;
}
// Backup the DX9 state
IDirect3DStateBlock9* state_block = nullptr;
if (device->CreateStateBlock(D3DSBT_ALL, &state_block) < 0)
return;
if (state_block->Capture() < 0)
{
state_block->Release();
return;
}
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
D3DMATRIX last_world, last_view, last_projection;
device->GetTransform(D3DTS_WORLD, &last_world);
device->GetTransform(D3DTS_VIEW, &last_view);
device->GetTransform(D3DTS_PROJECTION, &last_projection);
// Allocate buffers
CUSTOMVERTEX* vtx_dst;
ImDrawIdx* idx_dst;
if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0)
{
state_block->Release();
return;
}
if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0)
{
bd->pVB->Unlock();
state_block->Release();
return;
}
// Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format.
// FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and
// 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR
// 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
const ImDrawVert* vtx_src = draw_list->VtxBuffer.Data;
for (int i = 0; i < draw_list->VtxBuffer.Size; i++)
{
vtx_dst->pos[0] = vtx_src->pos.x;
vtx_dst->pos[1] = vtx_src->pos.y;
vtx_dst->pos[2] = 0.0f;
vtx_dst->col = IMGUI_COL_TO_DX9_ARGB(vtx_src->col);
vtx_dst->uv[0] = vtx_src->uv.x;
vtx_dst->uv[1] = vtx_src->uv.y;
vtx_dst++;
vtx_src++;
}
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
idx_dst += draw_list->IdxBuffer.Size;
}
bd->pVB->Unlock();
bd->pIB->Unlock();
device->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
device->SetIndices(bd->pIB);
device->SetFVF(D3DFVF_CUSTOMVERTEX);
// Setup desired DX state
struct render_state_t render_state = {};
ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
// Render command lists // Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them) // (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_vtx_offset = 0; int global_vtx_offset = 0;
int global_idx_offset = 0; int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos; ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++) { for (const ImDrawList* draw_list : draw_data->CmdLists)
const ImDrawList *cmd_list = draw_data->CmdLists[n]; {
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i]; {
if (pcmd->UserCallback != NULL) { const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
pcmd->UserCallback(cmd_list, pcmd); if (pcmd->UserCallback != nullptr)
} else { {
const RECT r = {(LONG) (pcmd->ClipRect.x - clip_off.x), (LONG) (pcmd->ClipRect.y - clip_off.y), // User callback, registered via ImDrawList::AddCallback()
(LONG) (pcmd->ClipRect.z - clip_off.x), (LONG) (pcmd->ClipRect.w - clip_off.y)}; // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
auto texture = reinterpret_cast<IDirect3DBaseTexture9 *>(pcmd->TextureId); if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
g_pd3dDevice->SetTexture(0, texture); ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
g_pd3dDevice->SetScissorRect(&r); else
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->UserCallback(draw_list, pcmd);
pcmd->VtxOffset + global_vtx_offset, 0, }
(UINT) cmd_list->VtxBuffer.Size, else
pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); {
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Apply scissor/clipping rectangle
const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
device->SetScissorRect(&r);
// Bind texture, Draw
const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID();
device->SetTexture(0, texture);
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)draw_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
} }
} }
global_idx_offset += cmd_list->IdxBuffer.Size; global_idx_offset += draw_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size; global_vtx_offset += draw_list->VtxBuffer.Size;
} }
if (back_buffer) { // When using multi-viewports, it appears that there's an odd logic in DirectX9 which prevent subsequent windows
back_buffer->Release(); // from rendering until the first window submits at least one draw call, even once. That's our workaround. (see #2560)
back_buffer = nullptr; if (global_vtx_offset == 0)
} bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 0, 0, 0);
// restore previous depth stencil // spice ldj
if (depth_stencil) { {
g_pd3dDevice->SetDepthStencilSurface(depth_stencil); if (render_state.back_buffer) {
depth_stencil->Release(); render_state.back_buffer->Release();
depth_stencil = nullptr; render_state.back_buffer = nullptr;
} }
// restore previous depth stencil
if (render_state.depth_stencil) {
device->SetDepthStencilSurface(render_state.depth_stencil);
render_state.depth_stencil->Release();
render_state.depth_stencil = nullptr;
}
// restore all render target state
for (size_t target = 0; target < std::min(8UL, render_state.caps.NumSimultaneousRTs); target++) {
auto render_target = render_state.render_targets[target];
// restore all render target state if (render_target) {
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { device->SetRenderTarget(target, render_target);
auto render_target = render_targets[target]; render_target->Release();
}
if (render_target) {
g_pd3dDevice->SetRenderTarget(target, render_target);
render_target->Release();
} }
} }
// restore the DX9 transform // Restore the DX9 transform
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world); device->SetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view); device->SetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection); device->SetTransform(D3DTS_PROJECTION, &last_projection);
// restore the DX9 state // Restore the DX9 state
d3d9_state_block->Apply(); state_block->Apply();
d3d9_state_block->Release(); state_block->Release();
} }
bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device) { static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMAT format)
{
LPDIRECT3D9 pd3d = nullptr;
if (pDevice->GetDirect3D(&pd3d) != D3D_OK)
return false;
D3DDEVICE_CREATION_PARAMETERS param = {};
D3DDISPLAYMODE mode = {};
if (pDevice->GetCreationParameters(&param) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK)
{
pd3d->Release();
return false;
}
// Font texture should support linear filter, color blend and write to render-target
bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK;
pd3d->Release();
return support;
}
// Setup back-end capabilities flags bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
auto &io = ImGui::GetIO(); {
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx9"; io.BackendRendererName = "imgui_impl_dx9";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
bd->pd3dDevice = device;
bd->pd3dDevice->AddRef();
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
ImGui_ImplDX9_InitMultiViewportSupport();
g_pd3dDevice = device;
g_pd3dDevice->AddRef();
return true; return true;
} }
void ImGui_ImplDX9_Shutdown() { void ImGui_ImplDX9_Shutdown()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplDX9_ShutdownMultiViewportSupport();
ImGui_ImplDX9_InvalidateDeviceObjects(); ImGui_ImplDX9_InvalidateDeviceObjects();
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
if (g_pd3dDevice) { io.BackendRendererName = nullptr;
g_pd3dDevice->Release(); io.BackendRendererUserData = nullptr;
g_pd3dDevice = NULL; io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
platform_io.ClearRendererHandlers();
IM_DELETE(bd);
}
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h)
{
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
const bool convert_rgba_to_bgra = (!bd->HasRgbaSupport && tex_use_colors);
#else
const bool convert_rgba_to_bgra = false;
IM_UNUSED(tex_use_colors);
#endif
for (int y = 0; y < h; y++)
{
const ImU32* src_p = (const ImU32*)(const void*)((const unsigned char*)src + src_pitch * y);
ImU32* dst_p = (ImU32*)(void*)((unsigned char*)dst + dst_pitch * y);
if (convert_rgba_to_bgra)
for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy
*dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p);
else
memcpy(dst_p, src_p, w * 4); // Raw copy
} }
} }
static bool ImGui_ImplDX9_CreateFontsTexture() { void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
// Build texture atlas {
ImGuiIO &io = ImGui::GetIO(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
unsigned char *pixels;
int width, height, bytes_per_pixel;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
// Upload texture to graphics system if (tex->Status == ImTextureStatus_WantCreate)
g_FontTexture = NULL; {
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, // Create and upload new texture to graphics system
D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0) //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
return false; IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
D3DLOCKED_RECT tex_locked_rect; IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) LPDIRECT3DTEXTURE9 dx_tex = nullptr;
return false; HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr);
for (int y = 0; y < height; y++) if (hr < 0)
memcpy((unsigned char *) tex_locked_rect.pBits + tex_locked_rect.Pitch * y, {
pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel)); IM_ASSERT(hr >= 0 && "Backend failed to create texture!");
g_FontTexture->UnlockRect(0); return;
// Store our identifier
io.Fonts->TexID = (ImTextureID) g_FontTexture;
return true;
}
bool ImGui_ImplDX9_CreateDeviceObjects() {
if (!g_pd3dDevice) {
return false;
}
return ImGui_ImplDX9_CreateFontsTexture();
}
void ImGui_ImplDX9_InvalidateDeviceObjects() {
if (!g_pd3dDevice)
return;
if (g_pVB) {
g_pVB->Release();
g_pVB = NULL;
}
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
if (g_FontTexture) {
g_FontTexture->Release();
g_FontTexture = NULL;
ImGui::GetIO().Fonts->TexID = 0;
} // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
}
void ImGui_ImplDX9_NewFrame() {
if (!g_FontTexture) {
ImGui_ImplDX9_CreateDeviceObjects();
}
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(g_pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
} }
swap_chain->Release(); D3DLOCKED_RECT locked_rect;
if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK)
{
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height);
dx_tex->UnlockRect(0);
}
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)dx_tex);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)(intptr_t)tex->TexID;
RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) };
D3DLOCKED_RECT locked_rect;
if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK)
for (ImTextureRect& r : tex->Updates)
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4,
(ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h);
backend_tex->UnlockRect(0);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
if (tex->TexID != ImTextureID_Invalid)
if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID)
{
IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex);
backend_tex->Release();
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
}
tex->SetStatus(ImTextureStatus_Destroyed);
} }
} }
bool ImGui_ImplDX9_CreateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return false;
ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
return true;
}
void ImGui_ImplDX9_InvalidateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return;
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplDX9_UpdateTexture(tex);
}
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
}
void ImGui_ImplDX9_NewFrame()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?");
// IM_UNUSED(bd);
// spice ldj
{
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(bd->pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
}
swap_chain->Release();
}
}
}
//--------------------------------------------------------------------------------------------------------
// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
//--------------------------------------------------------------------------------------------------------
// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data.
struct ImGui_ImplDX9_ViewportData
{
IDirect3DSwapChain9* SwapChain;
D3DPRESENT_PARAMETERS d3dpp;
ImGui_ImplDX9_ViewportData() { SwapChain = nullptr; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); }
~ImGui_ImplDX9_ViewportData() { IM_ASSERT(SwapChain == nullptr); }
};
static void ImGui_ImplDX9_CreateWindow(ImGuiViewport* viewport)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = IM_NEW(ImGui_ImplDX9_ViewportData)();
viewport->RendererUserData = vd;
// PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL's WindowID).
// Some backends will leave PlatformHandleRaw == 0, in which case we assume PlatformHandle will contain the HWND.
HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle;
IM_ASSERT(hwnd != 0);
ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS));
vd->d3dpp.Windowed = TRUE;
vd->d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
vd->d3dpp.BackBufferWidth = (UINT)viewport->Size.x;
vd->d3dpp.BackBufferHeight = (UINT)viewport->Size.y;
vd->d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
vd->d3dpp.hDeviceWindow = hwnd;
vd->d3dpp.EnableAutoDepthStencil = FALSE;
vd->d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
vd->d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // Present without vsync
HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr);
IM_ASSERT(hr == D3D_OK);
IM_ASSERT(vd->SwapChain != nullptr);
}
static void ImGui_ImplDX9_DestroyWindow(ImGuiViewport* viewport)
{
// The main viewport (owned by the application) will always have RendererUserData == 0 since we didn't create the data for it.
if (ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData)
{
if (vd->SwapChain)
vd->SwapChain->Release();
vd->SwapChain = nullptr;
ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS));
IM_DELETE(vd);
}
viewport->RendererUserData = nullptr;
}
static void ImGui_ImplDX9_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
if (vd->SwapChain)
{
vd->SwapChain->Release();
vd->SwapChain = nullptr;
vd->d3dpp.BackBufferWidth = (UINT)size.x;
vd->d3dpp.BackBufferHeight = (UINT)size.y;
HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr);
IM_ASSERT(hr == D3D_OK);
}
}
static void ImGui_ImplDX9_RenderWindow(ImGuiViewport* viewport, void*)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
LPDIRECT3DSURFACE9 render_target = nullptr;
LPDIRECT3DSURFACE9 last_render_target = nullptr;
LPDIRECT3DSURFACE9 last_depth_stencil = nullptr;
vd->SwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &render_target);
bd->pd3dDevice->GetRenderTarget(0, &last_render_target);
bd->pd3dDevice->GetDepthStencilSurface(&last_depth_stencil);
bd->pd3dDevice->SetRenderTarget(0, render_target);
bd->pd3dDevice->SetDepthStencilSurface(nullptr);
if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
{
D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x*255.0f), (int)(clear_color.y*255.0f), (int)(clear_color.z*255.0f), (int)(clear_color.w*255.0f));
bd->pd3dDevice->Clear(0, nullptr, D3DCLEAR_TARGET, clear_col_dx, 1.0f, 0);
}
ImGui_ImplDX9_RenderDrawData(viewport->DrawData);
// Restore render target
bd->pd3dDevice->SetRenderTarget(0, last_render_target);
bd->pd3dDevice->SetDepthStencilSurface(last_depth_stencil);
render_target->Release();
last_render_target->Release();
if (last_depth_stencil) last_depth_stencil->Release();
}
static void ImGui_ImplDX9_SwapBuffers(ImGuiViewport* viewport, void*)
{
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
HRESULT hr = vd->SwapChain->Present(nullptr, nullptr, vd->d3dpp.hDeviceWindow, nullptr, 0);
// Let main application handle D3DERR_DEVICELOST by resetting the device.
IM_ASSERT(SUCCEEDED(hr) || hr == D3DERR_DEVICELOST);
}
static void ImGui_ImplDX9_InitMultiViewportSupport()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_CreateWindow = ImGui_ImplDX9_CreateWindow;
platform_io.Renderer_DestroyWindow = ImGui_ImplDX9_DestroyWindow;
platform_io.Renderer_SetWindowSize = ImGui_ImplDX9_SetWindowSize;
platform_io.Renderer_RenderWindow = ImGui_ImplDX9_RenderWindow;
platform_io.Renderer_SwapBuffers = ImGui_ImplDX9_SwapBuffers;
}
static void ImGui_ImplDX9_ShutdownMultiViewportSupport()
{
ImGui::DestroyPlatformWindows();
}
static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int i = 1; i < platform_io.Viewports.Size; i++)
if (!platform_io.Viewports[i]->RendererUserData)
ImGui_ImplDX9_CreateWindow(platform_io.Viewports[i]);
}
static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int i = 1; i < platform_io.Viewports.Size; i++)
if (platform_io.Viewports[i]->RendererUserData)
ImGui_ImplDX9_DestroyWindow(platform_io.Viewports[i]);
}
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE
+25 -12
View File
@@ -1,25 +1,38 @@
// dear imgui: Renderer for DirectX9 // dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// https://github.com/ocornut/imgui // Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once #pragma once
#include "imgui.h" // IMGUI_IMPL_API
#include "imgui.h" #ifndef IMGUI_DISABLE
struct IDirect3DDevice9; struct IDirect3DDevice9;
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device); // Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9* device);
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown(); IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame(); IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data); IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
// Use if you want to reset your rendering device without losing ImGui state. // Use if you want to reset your rendering device without losing Dear ImGui state.
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE
+13 -5
View File
@@ -15,7 +15,8 @@
#pragma once #pragma once
//---- Define assertion handler. Defaults to calling assert(). //---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. // - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
// - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) //#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts #define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
@@ -29,7 +30,6 @@
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
//---- Disable all of Dear ImGui or don't implement standard windows/tools. //---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
@@ -49,6 +49,7 @@
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). #define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyForever), remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features. //---- Enable Test Engine / Automation features.
@@ -59,12 +60,15 @@
//#define IMGUI_INCLUDE_IMGUI_USER_H //#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" //#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) //---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support.
#define IMGUI_USE_BGRA_PACKED_COLOR #define IMGUI_USE_BGRA_PACKED_COLOR
//---- Performance optimization for DX9 //---- Performance optimization for DX9
#define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; } #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate.
//#define IMGUI_USE_LEGACY_CRC32_ADLER
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) //---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32 //#define IMGUI_USE_WCHAR32
@@ -83,13 +87,13 @@
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). // Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience.
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE //#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) //---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE. // Only works in combination with IMGUI_ENABLE_FREETYPE.
// - lunasvg is currently easier to acquire/install, as e.g. it is part of vcpkg. // - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.
// - plutosvg will support more fonts and may load them faster. It currently requires to be built manually but it is fairly easy. See misc/freetype/README for instructions.
// - Both require headers to be available in the include path + program to be linked with the library code (not provided). // - Both require headers to be available in the include path + program to be linked with the library code (not provided).
// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) // - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG //#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
@@ -130,6 +134,10 @@
//#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak() //#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set.
// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use)
//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS
//---- Debug Tools: Enable slower asserts //---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID //#define IMGUI_DEBUG_PARANOID
+3439 -1953
View File
File diff suppressed because it is too large Load Diff
+1023 -517
View File
File diff suppressed because it is too large Load Diff
+2825 -2079
View File
File diff suppressed because it is too large Load Diff
+3216 -1153
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+258 -164
View File
@@ -1,6 +1,7 @@
// Mini memory editor for Dear ImGui (to embed in your game/tools) // Mini memory editor for Dear ImGui (to embed in your game/tools)
// Get latest version at http://www.github.com/ocornut/imgui_club // Get latest version at http://www.github.com/ocornut/imgui_club
// // Licensed under The MIT License (MIT)
// Right-click anywhere to access the Options menu! // Right-click anywhere to access the Options menu!
// You can adjust the keyboard repeat delay/rate in ImGuiIO. // You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity! // The code assume a mono-space font for simplicity!
@@ -39,10 +40,22 @@
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel] // - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel]
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width. // - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size. // - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
// - v0.51 (2024/02/22): fix for layout change in 1.89 when using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#34)
// - v0.52 (2024/03/08): removed unnecessary GetKeyIndex() calls, they are a no-op since 1.87.
// - v0.53 (2024/05/27): fixed right-click popup from not appearing when using DrawContents(). warning fixes. (#35)
// - v0.54 (2024/07/29): allow ReadOnly mode to still select and preview data. (#46) [@DeltaGW2])
// - v0.55 (2024/08/19): added BgColorFn to allow setting background colors independently from highlighted selection. (#27) [@StrikerX3]
// added MouseHoveredAddr public readable field. (#47, #27) [@StrikerX3]
// fixed a data preview crash with 1.91.0 WIP. fixed contiguous highlight color when using data preview.
// *BREAKING* added UserData field passed to all optional function handlers: ReadFn, WriteFn, HighlightFn, BgColorFn. (#50) [@silverweed]
// - v0.56 (2024/11/04): fixed MouseHovered, MouseHoveredAddr not being set when hovering a byte being edited. (#54)
// - v0.57 (2025/03/26): fixed warnings. using ImGui's ImSXX/ImUXX types instead of e.g. int32_t/uint32_t. (#56)
// - v0.58 (2025/03/31): fixed extraneous footer spacing (added in 0.51) breaking vertical auto-resize. (#53)
// - v0.59 (2025/04/08): fixed GotoAddrAndHighlight() not working if OptShowOptions is disabled.
// //
// Todo/Bugs: // TODO:
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day. // - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day.
// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own... // - PageUp/PageDown are not supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame. // - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame.
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom. // - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
@@ -51,15 +64,18 @@
#include <stdio.h> // sprintf, scanf #include <stdio.h> // sprintf, scanf
#include <stdint.h> // uint8_t, etc. #include <stdint.h> // uint8_t, etc.
#ifdef _MSC_VER #if defined(_MSC_VER) && !defined(snprintf)
#define _PRISizeT "I"
#define ImSnprintf _snprintf #define ImSnprintf _snprintf
#else #else
#define _PRISizeT "z"
#define ImSnprintf snprintf #define ImSnprintf snprintf
#endif #endif
#if defined(_MSC_VER) && !defined(__clang__)
#define _PRISizeT "I"
#else
#define _PRISizeT "z"
#endif
#ifdef _MSC_VER #if defined(_MSC_VER) || defined(_UCRT)
#pragma warning (push) #pragma warning (push)
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe. #pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif #endif
@@ -88,9 +104,17 @@ struct MemoryEditor
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr).
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes. ImU32 HighlightColor; // // background color of highlighted bytes.
ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes. // Function handlers
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting). ImU8 (*ReadFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* mem, size_t off, ImU8 d, void* user_data); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
ImU32 (*BgColorFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return custom background color of individual bytes.
void* UserData; // = NULL // user data forwarded to the function handlers
// Public read-only data
bool MouseHovered; // set when mouse is hovering a value.
size_t MouseHoveredAddr; // the address currently being hovered if MouseHovered is set.
// [Internal State] // [Internal State]
bool ContentsWidthChanged; bool ContentsWidthChanged;
@@ -101,7 +125,7 @@ struct MemoryEditor
char AddrInputBuf[32]; char AddrInputBuf[32];
size_t GotoAddr; size_t GotoAddr;
size_t HighlightMin, HighlightMax; size_t HighlightMin, HighlightMax;
int PreviewEndianess; int PreviewEndianness;
ImGuiDataType PreviewDataType; ImGuiDataType PreviewDataType;
MemoryEditor() MemoryEditor()
@@ -120,9 +144,11 @@ struct MemoryEditor
OptAddrDigitsCount = 0; OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f; OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50); HighlightColor = IM_COL32(255, 255, 255, 50);
ReadFn = NULL; ReadFn = nullptr;
WriteFn = NULL; WriteFn = nullptr;
HighlightFn = NULL; HighlightFn = nullptr;
BgColorFn = nullptr;
UserData = nullptr;
// State/Internals // State/Internals
ContentsWidthChanged = false; ContentsWidthChanged = false;
@@ -131,8 +157,10 @@ struct MemoryEditor
memset(DataInputBuf, 0, sizeof(DataInputBuf)); memset(DataInputBuf, 0, sizeof(DataInputBuf));
memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
GotoAddr = (size_t)-1; GotoAddr = (size_t)-1;
MouseHovered = false;
MouseHoveredAddr = 0;
HighlightMin = HighlightMax = (size_t)-1; HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianess = 0; PreviewEndianness = 0;
PreviewDataType = ImGuiDataType_S32; PreviewDataType = ImGuiDataType_S32;
} }
@@ -145,16 +173,16 @@ struct MemoryEditor
struct Sizes struct Sizes
{ {
int AddrDigitsCount; int AddrDigitsCount; // Number of digits required to represent maximum address.
float LineHeight; float LineHeight; // Height of each line (no spacing).
float GlyphWidth; float GlyphWidth; // Glyph width (assume mono-space).
float HexCellWidth; float HexCellWidth; // Width of a hex edit cell ~2.5f * GlypHWidth.
float SpacingBetweenMidCols; float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount).
float PosHexStart; float OffsetHexMinX;
float PosHexEnd; float OffsetHexMaxX;
float PosAsciiStart; float OffsetAsciiMinX;
float PosAsciiEnd; float OffsetAsciiMaxX;
float WindowWidth; float WindowWidth; // Ideal window width.
Sizes() { memset(this, 0, sizeof(*this)); } Sizes() { memset(this, 0, sizeof(*this)); }
}; };
@@ -170,17 +198,17 @@ struct MemoryEditor
s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space
s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere
s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth; s.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * Cols);
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
if (OptShowAscii) if (OptShowAscii)
{ {
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
if (OptMidColsCount > 0) if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; s.OffsetAsciiMaxX = s.OffsetAsciiMinX + Cols * s.GlyphWidth;
} }
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; s.WindowWidth = s.OffsetAsciiMaxX + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
} }
// Standalone Memory Editor window // Standalone Memory Editor window
@@ -194,8 +222,6 @@ struct MemoryEditor
Open = true; Open = true;
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
{ {
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("context");
DrawContents(mem_data, mem_size, base_display_addr); DrawContents(mem_data, mem_size, base_display_addr);
if (ContentsWidthChanged) if (ContentsWidthChanged)
{ {
@@ -217,6 +243,8 @@ struct MemoryEditor
CalcSizes(s, mem_size, base_display_addr); CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle(); ImGuiStyle& style = ImGui::GetStyle();
const ImVec2 contents_pos_start = ImGui::GetCursorScreenPos();
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window. // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move. // This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
const float height_separator = style.ItemSpacing.y; const float height_separator = style.ItemSpacing.y;
@@ -225,20 +253,21 @@ struct MemoryEditor
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1; footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
if (OptShowDataPreview) if (OptShowDataPreview)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3; footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); ImGui::BeginChild("##scrolling", ImVec2(-FLT_MIN, -footer_height), ImGuiChildFlags_None, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function. // We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
const ImVec2 avail_size = ImGui::GetContentRegionAvail();
const int line_total_count = (int)((mem_size + Cols - 1) / Cols); const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper; ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight); clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false; bool data_next = false;
if (ReadOnly || DataEditingAddr >= mem_size) if (DataEditingAddr >= mem_size)
DataEditingAddr = (size_t)-1; DataEditingAddr = (size_t)-1;
if (DataPreviewAddr >= mem_size) if (DataPreviewAddr >= mem_size)
DataPreviewAddr = (size_t)-1; DataPreviewAddr = (size_t)-1;
@@ -249,16 +278,16 @@ struct MemoryEditor
if (DataEditingAddr != (size_t)-1) if (DataEditingAddr != (size_t)-1)
{ {
// Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered) // Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; } if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; } else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols){ data_editing_addr_next = DataEditingAddr + Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; } else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; } else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
} }
// Draw vertical separator // Draw vertical separator
ImVec2 window_pos = ImGui::GetWindowPos(); ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii) if (OptShowAscii)
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border)); draw_list->AddLine(ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text); const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
@@ -268,36 +297,51 @@ struct MemoryEditor
const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x"; const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
MouseHovered = false;
MouseHoveredAddr = 0;
while (clipper.Step()) while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
{ {
size_t addr = (size_t)(line_i * Cols); size_t addr = (size_t)line_i * Cols;
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr); ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
// Draw Hexadecimal // Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++) for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{ {
float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; float byte_pos_x = s.OffsetHexMinX + s.HexCellWidth * n;
if (OptMidColsCount > 0) if (OptMidColsCount > 0)
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols; byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
ImGui::SameLine(byte_pos_x); ImGui::SameLine(byte_pos_x);
// Draw highlight // Draw highlight or custom background color
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); const bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); const bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, UserData));
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); const bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
ImU32 bg_color = 0;
bool is_next_byte_highlighted = false;
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
{ {
ImVec2 pos = ImGui::GetCursorScreenPos(); is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1, UserData)) || (addr + 1 < DataPreviewAddr + preview_data_type_size));
float highlight_width = s.GlyphWidth * 2; bg_color = HighlightColor;
bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); }
else if (BgColorFn != nullptr)
{
is_next_byte_highlighted = (addr + 1 < mem_size) && ((BgColorFn(mem_data, addr + 1, UserData) & IM_COL32_A_MASK) != 0);
bg_color = BgColorFn(mem_data, addr, UserData);
}
if (bg_color != 0)
{
float bg_width = s.GlyphWidth * 2;
if (is_next_byte_highlighted || (n + 1 == Cols)) if (is_next_byte_highlighted || (n + 1 == Cols))
{ {
highlight_width = s.HexCellWidth; bg_width = s.HexCellWidth;
if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0) if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0)
highlight_width += s.SpacingBetweenMidCols; bg_width += s.SpacingBetweenMidCols;
} }
draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor); ImVec2 pos = ImGui::GetCursorScreenPos();
draw_list->AddRectFilled(pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color);
} }
if (DataEditingAddr == addr) if (DataEditingAddr == addr)
@@ -308,17 +352,21 @@ struct MemoryEditor
if (DataEditingTakeFocus) if (DataEditingTakeFocus)
{ {
ImGui::SetKeyboardFocusHere(0); ImGui::SetKeyboardFocusHere(0);
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr); ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount, base_display_addr + addr);
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); ImSnprintf(DataInputBuf, 32, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
} }
struct UserData struct InputTextUserData
{ {
// FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here. // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
static int Callback(ImGuiInputTextCallbackData* data) static int Callback(ImGuiInputTextCallbackData* data)
{ {
UserData* user_data = (UserData*)data->UserData; InputTextUserData* user_data = (InputTextUserData*)data->UserData;
if (!data->HasSelection()) if (!data->HasSelection())
user_data->CursorPos = data->CursorPos; user_data->CursorPos = data->CursorPos;
#if IMGUI_VERSION_NUM < 19102
if (data->Flags & ImGuiInputTextFlags_ReadOnly)
return 0;
#endif
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
{ {
// When not editing a byte, always refresh its InputText content pulled from underlying memory data // When not editing a byte, always refresh its InputText content pulled from underlying memory data
@@ -334,39 +382,42 @@ struct MemoryEditor
char CurrentBufOverwrite[3]; // Input char CurrentBufOverwrite[3]; // Input
int CursorPos; // Output int CursorPos; // Output
}; };
UserData user_data; InputTextUserData input_text_user_data;
user_data.CursorPos = -1; input_text_user_data.CursorPos = -1;
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); ImSnprintf(input_text_user_data.CurrentBufOverwrite, 3, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways; ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
#if IMGUI_VERSION_NUM >= 18104 if (ReadOnly)
flags |= ImGuiInputTextFlags_AlwaysOverwrite; flags |= ImGuiInputTextFlags_ReadOnly;
#else flags |= ImGuiInputTextFlags_AlwaysOverwrite; // was ImGuiInputTextFlags_AlwaysInsertMode
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
#endif
ImGui::SetNextItemWidth(s.GlyphWidth * 2); ImGui::SetNextItemWidth(s.GlyphWidth * 2);
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data)) if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, InputTextUserData::Callback, &input_text_user_data))
data_write = data_next = true; data_write = data_next = true;
else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
DataEditingAddr = data_editing_addr_next = (size_t)-1; DataEditingAddr = data_editing_addr_next = (size_t)-1;
DataEditingTakeFocus = false; DataEditingTakeFocus = false;
if (user_data.CursorPos >= 2) if (input_text_user_data.CursorPos >= 2)
data_write = data_next = true; data_write = data_next = true;
if (data_editing_addr_next != (size_t)-1) if (data_editing_addr_next != (size_t)-1)
data_write = data_next = false; data_write = data_next = false;
unsigned int data_input_value = 0; unsigned int data_input_value = 0;
if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) if (!ReadOnly && data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
{ {
if (WriteFn) if (WriteFn)
WriteFn(mem_data, addr, (ImU8)data_input_value); WriteFn(mem_data, addr, (ImU8)data_input_value, UserData);
else else
mem_data[addr] = (ImU8)data_input_value; mem_data[addr] = (ImU8)data_input_value;
} }
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = addr;
}
ImGui::PopID(); ImGui::PopID();
} }
else else
{ {
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; ImU8 b = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
if (OptShowHexII) if (OptShowHexII)
{ {
@@ -386,10 +437,15 @@ struct MemoryEditor
else else
ImGui::Text(format_byte_space, b); ImGui::Text(format_byte_space, b);
} }
if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) if (ImGui::IsItemHovered())
{ {
DataEditingTakeFocus = true; MouseHovered = true;
data_editing_addr_next = addr; MouseHoveredAddr = addr;
if (ImGui::IsMouseClicked(0))
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
}
} }
} }
} }
@@ -397,15 +453,24 @@ struct MemoryEditor
if (OptShowAscii) if (OptShowAscii)
{ {
// Draw ASCII values // Draw ASCII values
ImGui::SameLine(s.PosAsciiStart); ImGui::SameLine(s.OffsetAsciiMinX);
ImVec2 pos = ImGui::GetCursorScreenPos(); ImVec2 pos = ImGui::GetCursorScreenPos();
addr = line_i * Cols; addr = (size_t)line_i * Cols;
const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x;
const size_t mouse_addr = (mouse_off_x >= 0.0f && mouse_off_x < s.OffsetAsciiMaxX - s.OffsetAsciiMinX) ? addr + (size_t)(mouse_off_x / s.GlyphWidth) : (size_t)-1;
ImGui::PushID(line_i); ImGui::PushID(line_i);
if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) if (ImGui::InvisibleButton("ascii", ImVec2(s.OffsetAsciiMaxX - s.OffsetAsciiMinX, s.LineHeight)))
{ {
DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); DataEditingAddr = DataPreviewAddr = mouse_addr;
DataEditingTakeFocus = true; DataEditingTakeFocus = true;
} }
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = mouse_addr;
}
ImGui::PopID(); ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size; n++, addr++) for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{ {
@@ -414,7 +479,11 @@ struct MemoryEditor
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
} }
unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; else if (BgColorFn)
{
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), BgColorFn(mem_data, addr, UserData));
}
unsigned char c = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
char display_c = (c < 32 || c >= 128) ? '.' : c; char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1); draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
pos.x += s.GlyphWidth; pos.x += s.GlyphWidth;
@@ -422,10 +491,14 @@ struct MemoryEditor
} }
} }
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
const float child_width = ImGui::GetWindowSize().x;
ImGui::EndChild(); ImGui::EndChild();
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
ImVec2 backup_pos = ImGui::GetCursorScreenPos();
ImGui::SetCursorPosX(s.WindowWidth); ImGui::SetCursorPosX(s.WindowWidth);
ImGui::Dummy(ImVec2(0.0f, 0.0f));
ImGui::SetCursorScreenPos(backup_pos);
if (data_next && DataEditingAddr + 1 < mem_size) if (data_next && DataEditingAddr + 1 < mem_size)
{ {
@@ -450,18 +523,28 @@ struct MemoryEditor
ImGui::Separator(); ImGui::Separator();
DrawPreviewLine(s, mem_data, mem_size, base_display_addr); DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
} }
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr) if (GotoAddr != (size_t)-1)
{ {
IM_UNUSED(mem_data); if (GotoAddr < mem_size)
ImGuiStyle& style = ImGui::GetStyle(); {
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; ImGui::BeginChild("##scrolling");
ImGui::SetScrollY((GotoAddr / Cols) * ImGui::GetTextLineHeight() - avail_size.y * 0.5f);
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
// Options menu const ImVec2 contents_pos_end(contents_pos_start.x + child_width, ImGui::GetCursorScreenPos().y);
if (ImGui::Button("Options")) //ImGui::GetForegroundDrawList()->AddRect(contents_pos_start, contents_pos_end, IM_COL32(255, 0, 0, 255));
ImGui::OpenPopup("context"); if (OptShowOptions)
if (ImGui::BeginPopup("context")) if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end))
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("OptionsPopup");
if (ImGui::BeginPopup("OptionsPopup"))
{ {
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f); ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; } if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; }
@@ -473,6 +556,17 @@ struct MemoryEditor
ImGui::EndPopup(); ImGui::EndPopup();
} }
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("OptionsPopup");
ImGui::SameLine(); ImGui::SameLine();
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
@@ -488,18 +582,11 @@ struct MemoryEditor
} }
} }
if (GotoAddr != (size_t)-1) //if (MouseHovered)
{ //{
if (GotoAddr < mem_size) // ImGui::SameLine();
{ // ImGui::Text("Hovered: %p", MouseHoveredAddr);
ImGui::BeginChild("##scrolling"); //}
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight());
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
} }
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr) void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
@@ -511,16 +598,21 @@ struct MemoryEditor
ImGui::Text("Preview as:"); ImGui::Text("Preview as:");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
static const ImGuiDataType supported_data_types[] = { ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16, ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64, ImGuiDataType_Float, ImGuiDataType_Double };
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest)) if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
{ {
for (int n = 0; n < ImGuiDataType_COUNT; n++) for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n)) {
PreviewDataType = (ImGuiDataType)n; ImGuiDataType data_type = supported_data_types[n];
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
PreviewDataType = data_type;
}
ImGui::EndCombo(); ImGui::EndCombo();
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0"); ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0");
char buf[128] = ""; char buf[128] = "";
float x = s.GlyphWidth * 6.0f; float x = s.GlyphWidth * 6.0f;
@@ -537,18 +629,19 @@ struct MemoryEditor
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
} }
// Utilities for Data Preview // Utilities for Data Preview (since we don't access imgui_internal.h)
// FIXME: This technically depends on ImGuiDataType order.
const char* DataTypeGetDesc(ImGuiDataType data_type) const const char* DataTypeGetDesc(ImGuiDataType data_type) const
{ {
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" }; const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs));
return descs[data_type]; return descs[data_type];
} }
size_t DataTypeGetSize(ImGuiDataType data_type) const size_t DataTypeGetSize(ImGuiDataType data_type) const
{ {
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) }; const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes));
return sizes[data_type]; return sizes[data_type];
} }
@@ -561,18 +654,18 @@ struct MemoryEditor
bool IsBigEndian() const bool IsBigEndian() const
{ {
uint16_t x = 1; ImU16 x = 1;
char c[2]; char c[2];
memcpy(c, &x, 2); memcpy(c, &x, 2);
return c[0] != 0; return c[0] != 0;
} }
static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{ {
if (is_little_endian) if (is_little_endian)
{ {
uint8_t* dst = (uint8_t*)_dst; ImU8* dst = (ImU8*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1; ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i) for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1); memcpy(dst++, src--, 1);
return _dst; return _dst;
@@ -583,7 +676,7 @@ struct MemoryEditor
} }
} }
static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{ {
if (is_little_endian) if (is_little_endian)
{ {
@@ -591,23 +684,23 @@ struct MemoryEditor
} }
else else
{ {
uint8_t* dst = (uint8_t*)_dst; ImU8* dst = (ImU8*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1; ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i) for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1); memcpy(dst++, src--, 1);
return _dst; return _dst;
} }
} }
void* EndianessCopy(void* dst, void* src, size_t size) const void* EndiannessCopy(void* dst, void* src, size_t size) const
{ {
static void* (*fp)(void*, void*, size_t, int) = NULL; static void* (*fp)(void*, void*, size_t, int) = nullptr;
if (fp == NULL) if (fp == nullptr)
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianess); return fp(dst, src, size, PreviewEndianness);
} }
const char* FormatBinary(const uint8_t* buf, int width) const const char* FormatBinary(const ImU8* buf, int width) const
{ {
IM_ASSERT(width <= 64); IM_ASSERT(width <= 64);
size_t out_n = 0; size_t out_n = 0;
@@ -627,19 +720,19 @@ struct MemoryEditor
// [Internal] // [Internal]
void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
{ {
uint8_t buf[8]; ImU8 buf[8];
size_t elem_size = DataTypeGetSize(data_type); size_t elem_size = DataTypeGetSize(data_type);
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
if (ReadFn) if (ReadFn)
for (int i = 0, n = (int)size; i < n; ++i) for (int i = 0, n = (int)size; i < n; ++i)
buf[i] = ReadFn(mem_data, addr + i); buf[i] = ReadFn(mem_data, addr + i, UserData);
else else
memcpy(buf, mem_data + addr, size); memcpy(buf, mem_data + addr, size);
if (data_format == DataFormat_Bin) if (data_format == DataFormat_Bin)
{ {
uint8_t binbuf[8]; ImU8 binbuf[8];
EndianessCopy(binbuf, buf, size); EndiannessCopy(binbuf, buf, size);
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8)); ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
return; return;
} }
@@ -649,84 +742,85 @@ struct MemoryEditor
{ {
case ImGuiDataType_S8: case ImGuiDataType_S8:
{ {
int8_t int8 = 0; ImS8 data = 0;
EndianessCopy(&int8, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
break; break;
} }
case ImGuiDataType_U8: case ImGuiDataType_U8:
{ {
uint8_t uint8 = 0; ImU8 data = 0;
EndianessCopy(&uint8, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
break; break;
} }
case ImGuiDataType_S16: case ImGuiDataType_S16:
{ {
int16_t int16 = 0; ImS16 data = 0;
EndianessCopy(&int16, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break; break;
} }
case ImGuiDataType_U16: case ImGuiDataType_U16:
{ {
uint16_t uint16 = 0; ImU16 data = 0;
EndianessCopy(&uint16, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break; break;
} }
case ImGuiDataType_S32: case ImGuiDataType_S32:
{ {
int32_t int32 = 0; ImS32 data = 0;
EndianessCopy(&int32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break; break;
} }
case ImGuiDataType_U32: case ImGuiDataType_U32:
{ {
uint32_t uint32 = 0; ImU32 data = 0;
EndianessCopy(&uint32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break; break;
} }
case ImGuiDataType_S64: case ImGuiDataType_S64:
{ {
int64_t int64 = 0; ImS64 data = 0;
EndianessCopy(&int64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break; break;
} }
case ImGuiDataType_U64: case ImGuiDataType_U64:
{ {
uint64_t uint64 = 0; ImU64 data = 0;
EndianessCopy(&uint64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break; break;
} }
case ImGuiDataType_Float: case ImGuiDataType_Float:
{ {
float float32 = 0.0f; float data = 0.0f;
EndianessCopy(&float32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break; break;
} }
case ImGuiDataType_Double: case ImGuiDataType_Double:
{ {
double float64 = 0.0; double data = 0.0;
EndianessCopy(&float64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break; break;
} }
default:
case ImGuiDataType_COUNT: case ImGuiDataType_COUNT:
break; break;
} // Switch } // Switch
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -315,7 +315,7 @@ static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0
if (node->y > min_y) { if (node->y > min_y) {
// raise min_y higher. // raise min_y higher.
// we've accounted for all waste up to min_y, // we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted // but we'll now add more waste for everything we've visited
waste_area += visited_width * (node->y - min_y); waste_area += visited_width * (node->y - min_y);
min_y = node->y; min_y = node->y;
// the first time through, visited_width might be reduced // the first time through, visited_width might be reduced
@@ -470,7 +470,7 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i
// insert the new node into the right starting point, and // insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be // let 'cur' point to the remaining nodes needing to be
// stiched back in // stitched back in
cur = *res.prev_link; cur = *res.prev_link;
if (cur->x < res.x) { if (cur->x < res.x) {
+113 -55
View File
@@ -5,7 +5,8 @@
// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783) // - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
// - Added name to struct or it may be forward declared in our code. // - Added name to struct or it may be forward declared in our code.
// - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925) // - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)
// Grep for [DEAR IMGUI] to find the changes. // - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
// Grep for [DEAR IMGUI] to find some changes.
// - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_* // - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
// stb_textedit.h - v1.14 - public domain - Sean Barrett // stb_textedit.h - v1.14 - public domain - Sean Barrett
@@ -39,6 +40,7 @@
// //
// VERSION HISTORY // VERSION HISTORY
// //
// !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
// 1.14 (2021-07-11) page up/down, various fixes // 1.14 (2021-07-11) page up/down, various fixes
// 1.13 (2019-02-07) fix bug in undo size management // 1.13 (2019-02-07) fix bug in undo size management
// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
@@ -141,12 +143,14 @@
// with previous char) // with previous char)
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
// (return type is int, -1 means not valid to insert) // (return type is int, -1 means not valid to insert)
// (not supported if you want to use UTF-8, see below)
// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
// as manually wordwrapping for end-of-line positioning // as manually wordwrapping for end-of-line positioning
// //
// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) try to insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
// returns number of characters actually inserted. [DEAR IMGUI]
// //
// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key
// //
@@ -178,6 +182,13 @@
// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
// //
// To support UTF-8:
//
// STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character
// STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character
// Do NOT define STB_TEXTEDIT_KEYTOTEXT.
// Instead, call stb_textedit_text() directly for text contents.
//
// Keyboard input must be encoded as a single integer value; e.g. a character code // Keyboard input must be encoded as a single integer value; e.g. a character code
// and some bitflags that represent shift states. to simplify the interface, SHIFT must // and some bitflags that represent shift states. to simplify the interface, SHIFT must
// be a bitflag, so we can test the shifted state of cursor movements to allow selection, // be a bitflag, so we can test the shifted state of cursor movements to allow selection,
@@ -250,8 +261,10 @@
// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are // if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
// transformed into text and stb_textedit_text() is automatically called. // transformed into text and stb_textedit_text() is automatically called.
// //
// text: [DEAR IMGUI] added 2024-09 // text: (added 2025)
// call this to text inputs sent to the textfield. // call this to directly send text input the textfield, which is required
// for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()
// cannot infer text length.
// //
// //
// When rendering, you can read the cursor position and selection state from // When rendering, you can read the cursor position and selection state from
@@ -400,6 +413,16 @@ typedef struct
#define IMSTB_TEXTEDIT_memmove memmove #define IMSTB_TEXTEDIT_memmove memmove
#endif #endif
// [DEAR IMGUI]
// Functions must be implemented for UTF8 support
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1)
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1)
#endif
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
@@ -407,7 +430,7 @@ typedef struct
// //
// traverse the layout to locate the nearest character to a display position // traverse the layout to locate the nearest character to a display position
static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y, int* out_side_on_line)
{ {
StbTexteditRow r; StbTexteditRow r;
int n = STB_TEXTEDIT_STRINGLEN(str); int n = STB_TEXTEDIT_STRINGLEN(str);
@@ -417,6 +440,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
r.x0 = r.x1 = 0; r.x0 = r.x1 = 0;
r.ymin = r.ymax = 0; r.ymin = r.ymax = 0;
r.num_chars = 0; r.num_chars = 0;
*out_side_on_line = 0;
// search rows to find one that straddles 'y' // search rows to find one that straddles 'y'
while (i < n) { while (i < n) {
@@ -436,7 +460,10 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
// below all text, return 'after' last character // below all text, return 'after' last character
if (i >= n) if (i >= n)
{
*out_side_on_line = 1;
return n; return n;
}
// check if it's before the beginning of the line // check if it's before the beginning of the line
if (x < r.x0) if (x < r.x0)
@@ -449,6 +476,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) { for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
float w = STB_TEXTEDIT_GETWIDTH(str, i, k); float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
if (x < prev_x+w) { if (x < prev_x+w) {
*out_side_on_line = (k == 0) ? 0 : 1;
if (x < prev_x+w/2) if (x < prev_x+w/2)
return k+i; return k+i;
else else
@@ -460,6 +488,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
} }
// if the last character is a newline, return that. otherwise return 'after' the last character // if the last character is a newline, return that. otherwise return 'after' the last character
*out_side_on_line = 1;
if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
return i+r.num_chars-1; return i+r.num_chars-1;
else else
@@ -471,6 +500,7 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
{ {
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
// goes off the top or bottom of the text // goes off the top or bottom of the text
int side_on_line;
if( state->single_line ) if( state->single_line )
{ {
StbTexteditRow r; StbTexteditRow r;
@@ -478,16 +508,18 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
y = r.ymin; y = r.ymin;
} }
state->cursor = stb_text_locate_coord(str, x, y); state->cursor = stb_text_locate_coord(str, x, y, &side_on_line);
state->select_start = state->cursor; state->select_start = state->cursor;
state->select_end = state->cursor; state->select_end = state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
} }
// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
{ {
int p = 0; int p = 0;
int side_on_line;
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
// goes off the top or bottom of the text // goes off the top or bottom of the text
@@ -501,8 +533,9 @@ static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *sta
if (state->select_start == state->select_end) if (state->select_start == state->select_end)
state->select_start = state->cursor; state->select_start = state->cursor;
p = stb_text_locate_coord(str, x, y); p = stb_text_locate_coord(str, x, y, &side_on_line);
state->cursor = state->select_end = p; state->cursor = state->select_end = p;
str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
} }
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@@ -552,6 +585,8 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING
STB_TEXTEDIT_LAYOUTROW(&r, str, i); STB_TEXTEDIT_LAYOUTROW(&r, str, i);
if (n < i + r.num_chars) if (n < i + r.num_chars)
break; break;
if (str->LastMoveDirectionLR == ImGuiDir_Right && str->Stb->cursor > 0 && str->Stb->cursor == i + r.num_chars && STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] Wrapping point handling
break;
if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line
break; // [DEAR IMGUI] break; // [DEAR IMGUI]
prev_start = i; prev_start = i;
@@ -648,15 +683,33 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt
} }
} }
// [DEAR IMGUI] // [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping.
// Functions must be implemented for UTF8 support #ifndef STB_TEXTEDIT_MOVELINESTART
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
// There is not necessarily a '[DEAR IMGUI]' at the usage sites. {
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX if (state->single_line)
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1) return 0;
while (cursor > 0) {
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, cursor);
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
break;
cursor = prev;
}
return cursor;
}
#define STB_TEXTEDIT_MOVELINESTART stb_textedit_move_line_start
#endif #endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX #ifndef STB_TEXTEDIT_MOVELINEEND
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1) static int stb_textedit_move_line_end(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
{
int n = STB_TEXTEDIT_STRINGLEN(str);
if (state->single_line)
return n;
while (cursor < n && STB_TEXTEDIT_GETCHAR(str, cursor) != STB_TEXTEDIT_NEWLINE)
cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, cursor);
return cursor;
}
#define STB_TEXTEDIT_MOVELINEEND stb_textedit_move_line_end
#endif #endif
#ifdef STB_TEXTEDIT_IS_SPACE #ifdef STB_TEXTEDIT_IS_SPACE
@@ -668,9 +721,9 @@ static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
#ifndef STB_TEXTEDIT_MOVEWORDLEFT #ifndef STB_TEXTEDIT_MOVEWORDLEFT
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c ) static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
{ {
--c; // always move at least one character c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character
while( c >= 0 && !is_word_boundary( str, c ) ) while (c >= 0 && !is_word_boundary(str, c))
--c; c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);
if( c < 0 ) if( c < 0 )
c = 0; c = 0;
@@ -684,9 +737,9 @@ static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c
static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c ) static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
{ {
const int len = STB_TEXTEDIT_STRINGLEN(str); const int len = STB_TEXTEDIT_STRINGLEN(str);
++c; // always move at least one character c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character
while( c < len && !is_word_boundary( str, c ) ) while( c < len && !is_word_boundary( str, c ) )
++c; c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);
if( c > len ) if( c > len )
c = len; c = len;
@@ -725,7 +778,8 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_delete_selection(str,state); stb_textedit_delete_selection(str,state);
// try to insert the characters // try to insert the characters
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len);
if (len) {
stb_text_makeundo_insert(state, state->cursor, len); stb_text_makeundo_insert(state, state->cursor, len);
state->cursor += len; state->cursor += len;
state->has_preferred_x = 0; state->has_preferred_x = 0;
@@ -739,6 +793,7 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
#define STB_TEXTEDIT_KEYTYPE int #define STB_TEXTEDIT_KEYTYPE int
#endif #endif
// API key: process text input
// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. // [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
{ {
@@ -749,14 +804,15 @@ static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* sta
if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
stb_text_makeundo_replace(str, state, state->cursor, 1, 1); stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) { text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
if (text_len) {
state->cursor += text_len; state->cursor += text_len;
state->has_preferred_x = 0; state->has_preferred_x = 0;
} }
} } else {
else {
stb_textedit_delete_selection(str, state); // implicitly clamps stb_textedit_delete_selection(str, state); // implicitly clamps
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) { text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
if (text_len) {
stb_text_makeundo_insert(state, state->cursor, text_len); stb_text_makeundo_insert(state, state->cursor, text_len);
state->cursor += text_len; state->cursor += text_len;
state->has_preferred_x = 0; state->has_preferred_x = 0;
@@ -771,6 +827,7 @@ retry:
switch (key) { switch (key) {
default: { default: {
#ifdef STB_TEXTEDIT_KEYTOTEXT #ifdef STB_TEXTEDIT_KEYTOTEXT
// This is not suitable for UTF-8 support.
int c = STB_TEXTEDIT_KEYTOTEXT(key); int c = STB_TEXTEDIT_KEYTOTEXT(key);
if (c > 0) { if (c > 0) {
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c; IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
@@ -911,15 +968,16 @@ retry:
// [DEAR IMGUI] // [DEAR IMGUI]
// going down while being on the last line shouldn't bring us to that line end // going down while being on the last line shouldn't bring us to that line end
if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) //if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
break; // break;
// now find character position down a row // now find character position down a row
state->cursor = start; state->cursor = start;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0; x = row.x0;
for (i=0; i < row.num_chars; ++i) { for (i=0; i < row.num_chars; ) {
float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break; break;
@@ -927,10 +985,13 @@ retry:
x += dx; x += dx;
if (x > goal_x) if (x > goal_x)
break; break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); i += next - state->cursor;
state->cursor = next;
} }
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
if (state->cursor == find.first_char + find.length)
str->LastMoveDirectionLR = ImGuiDir_Left;
state->has_preferred_x = 1; state->has_preferred_x = 1;
state->preferred_x = goal_x; state->preferred_x = goal_x;
@@ -980,8 +1041,9 @@ retry:
state->cursor = find.prev_first; state->cursor = find.prev_first;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0; x = row.x0;
for (i=0; i < row.num_chars; ++i) { for (i=0; i < row.num_chars; ) {
float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break; break;
@@ -989,10 +1051,15 @@ retry:
x += dx; x += dx;
if (x > goal_x) if (x > goal_x)
break; break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); i += next - state->cursor;
state->cursor = next;
} }
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
if (state->cursor == find.first_char)
str->LastMoveDirectionLR = ImGuiDir_Right;
else if (state->cursor == find.prev_first)
str->LastMoveDirectionLR = ImGuiDir_Left;
state->has_preferred_x = 1; state->has_preferred_x = 1;
state->preferred_x = goal_x; state->preferred_x = goal_x;
@@ -1002,10 +1069,15 @@ retry:
// go to previous line // go to previous line
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?) // (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;
while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) while (prev_scan > 0)
--prev_scan; {
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan);
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
break;
prev_scan = prev;
}
find.first_char = find.prev_first; find.first_char = find.prev_first;
find.prev_first = prev_scan; find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan);
} }
break; break;
} }
@@ -1079,10 +1151,7 @@ retry:
case STB_TEXTEDIT_K_LINESTART: case STB_TEXTEDIT_K_LINESTART:
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_move_to_first(state); stb_textedit_move_to_first(state);
if (state->single_line) state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -1090,13 +1159,9 @@ retry:
case STB_TEXTEDIT_K_LINEEND2: case STB_TEXTEDIT_K_LINEEND2:
#endif #endif
case STB_TEXTEDIT_K_LINEEND: { case STB_TEXTEDIT_K_LINEEND: {
int n = STB_TEXTEDIT_STRINGLEN(str);
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_move_to_first(state); stb_textedit_move_to_last(str, state);
if (state->single_line) state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
} }
@@ -1107,10 +1172,7 @@ retry:
case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_prep_selection_at_cursor(state); stb_textedit_prep_selection_at_cursor(state);
if (state->single_line) state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor;
state->select_end = state->cursor; state->select_end = state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -1119,13 +1181,9 @@ retry:
case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
#endif #endif
case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
int n = STB_TEXTEDIT_STRINGLEN(str);
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_prep_selection_at_cursor(state); stb_textedit_prep_selection_at_cursor(state);
if (state->single_line) state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor;
state->select_end = state->cursor; state->select_end = state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -1300,7 +1358,7 @@ static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
// check type of recorded action: // check type of recorded action:
if (u.insert_length) { if (u.insert_length) {
// easy case: was a deletion, so we need to insert n characters // easy case: was a deletion, so we need to insert n characters
STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); u.insert_length = STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
s->undo_char_point -= u.insert_length; s->undo_char_point -= u.insert_length;
} }
@@ -1351,7 +1409,7 @@ static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
if (r.insert_length) { if (r.insert_length) {
// easy case: need to insert n characters // easy case: need to insert n characters
STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
s->redo_char_point += r.insert_length; s->redo_char_point += r.insert_length;
} }
+3 -3
View File
@@ -886,7 +886,7 @@ STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, fl
// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap // xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap
STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff);
// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel // the same as stbtt_GetCodepointBitmap, but you can specify a subpixel
// shift for the character // shift for the character
STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);
@@ -4516,8 +4516,8 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
q2[0] = (float)x2; q2[0] = (float)x2;
q2[1] = (float)y2; q2[1] = (float)y2;
if (equal(q0,q1) || equal(q1,q2)) { if (equal(q0,q1) || equal(q1,q2)) {
x0 = (int)verts[i-1].x; x0 = (int)verts[i-1].x; //-V1048
y0 = (int)verts[i-1].y; y0 = (int)verts[i-1].y; //-V1048
x1 = (int)verts[i ].x; x1 = (int)verts[i ].x;
y1 = (int)verts[i ].y; y1 = (int)verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
+4
View File
@@ -65,6 +65,10 @@ namespace games::ccj {
libutils::load_library("libaio-iob2_video.dll"); libutils::load_library("libaio-iob2_video.dll");
libutils::load_library("win10actlog.dll"); libutils::load_library("win10actlog.dll");
// we do this to prevent this unexpectedly being unloaded
// which leads to D3D10Warp.dll_unloaded crash
libutils::load_library("d3d10warp.dll");
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11), detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig); (void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
@@ -552,18 +552,6 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
modified = true; modified = true;
} }
} }
if (!modified &&
(width > height) &&
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
log_misc(
"graphics::d3d9", "swapping width and height for mode {}, {}x{} @ {}Hz (-autoorientation)",
Mode, width, height, refresh);
pMode->Height = width;
pMode->Width = height;
modified = true;
}
} }
CHECK_RESULT(ret); CHECK_RESULT(ret);
+256 -81
View File
@@ -58,7 +58,6 @@ bool GRAPHICS_CAPTURE_CURSOR = false;
bool GRAPHICS_LOG_HRESULT = false; bool GRAPHICS_LOG_HRESULT = false;
bool GRAPHICS_SDVX_FORCE_720 = false; bool GRAPHICS_SDVX_FORCE_720 = false;
bool GRAPHICS_SHOW_CURSOR = false; bool GRAPHICS_SHOW_CURSOR = false;
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false; bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS; std::vector<HWND> GRAPHICS_WINDOWS;
UINT GRAPHICS_FORCE_REFRESH = 0; UINT GRAPHICS_FORCE_REFRESH = 0;
@@ -301,30 +300,6 @@ static BOOL WINAPI ClipCursor_hook(const RECT *lpRect) {
return ClipCursor_orig(lpRect); return ClipCursor_orig(lpRect);
} }
static void fix_up_rb_auto_rotate_dimensions(DWORD &w, DWORD &h) {
// reflec beat
// for whatever reason, rb creates the first window it weird dimensions (774x1389 for reflesia, for example)
// so doing ChangeDisplaySettings as-is ends up with DISP_CHANGE_BADMODE
// while a second window (with title D3DProxyWindow) does get created with correct dimensions, it's
// too late to rotate the window then; as a workaround force the display change now with patched up dimensions
bool changed = false;
if (w == 774) {
// LBR, MBR reflesia
w = 768;
h = 1360;
changed = true;
} else if (w == 1086) {
// MBR (before reflesia)
w = 1080;
h = 1920;
changed = true;
}
if (changed) {
log_misc("graphics", "fix auto-rotate dimensions for reflec beat: w={}, h={}", w, h);
}
}
static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight, DWORD dwStyle, int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
@@ -346,62 +321,6 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
fmt::ptr(hInstance), fmt::ptr(hInstance),
fmt::ptr(lpParam)); fmt::ptr(lpParam));
// set display orientation and/or refresh rate
// only set orientation when the target window is portrait
// (avoid doing this for SDVX subscreen which is in landscape, for example)
auto adjust_orientation =
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW);
// reflec beat
if (avs::game::is_model("KBR")) {
// KBR (rb1) launches landscape and draws rotated by itself, don't do any rotation
adjust_orientation = false;
} else if (avs::game::is_model({"LBR", "MBR"}) && window_name == "D3DProxyWindow") {
// do not auto-rotate for second window (D3DProxyWindow)
adjust_orientation = false;
}
if ((nHeight > nWidth && adjust_orientation) || GRAPHICS_FORCE_REFRESH > 0) {
DEVMODE mode {};
// get display settings
if (EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &mode)) {
if (adjust_orientation) {
DWORD orientation = GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ? DMDO_90 : DMDO_270;
log_misc(
"graphics",
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}, w={}, h={}",
orientation, nWidth, nHeight);
mode.dmPelsWidth = nWidth;
mode.dmPelsHeight = nHeight;
mode.dmDisplayOrientation = orientation;
// reflec beat
if (avs::game::is_model({"LBR", "MBR"})) {
fix_up_rb_auto_rotate_dimensions(mode.dmPelsWidth, mode.dmPelsHeight);
}
}
if (GRAPHICS_FORCE_REFRESH > 0) {
log_info(
"graphics",
"call ChangeDisplaySettings to force refresh rate: {} => {} Hz (-graphics-force-refresh)",
mode.dmDisplayFrequency,
GRAPHICS_FORCE_REFRESH);
mode.dmDisplayFrequency = GRAPHICS_FORCE_REFRESH;
}
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
log_warning("graphics", "ChangeDisplaySettings failed: {}", disp_res);
}
} else {
log_warning("graphics", "failed to get display settings");
}
}
// gfdm // gfdm
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) { if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
// set window name // set window name
@@ -1149,3 +1068,259 @@ std::string graphics_screenshot_genpath() {
id++; id++;
} }
} }
static std::string get_dmdo_string(DWORD dmdo) {
switch (dmdo) {
case DMDO_DEFAULT:
return "DMDO_DEFAULT";
case DMDO_90:
return "DMDO_90";
case DMDO_180:
return "DMDO_180";
case DMDO_270:
return "DMDO_270";
default:
return fmt::format("Unknown ({})", dmdo);
}
}
void change_primary_monitor(const std::string &monitor_name) {
log_misc("graphics", "try changing primary monitor to {}...", monitor_name);
// for WinXP, since these are Vista+ or 7+ APIs
const auto user32 = LoadLibraryA("user32.dll");
if (!user32) {
log_warning("graphics", "can't find user32.dll???");
return;
}
const auto GetDisplayConfigBufferSizes_addr =
reinterpret_cast<decltype(GetDisplayConfigBufferSizes) *>(
GetProcAddress(user32, "GetDisplayConfigBufferSizes"));
const auto QueryDisplayConfig_addr =
reinterpret_cast<decltype(QueryDisplayConfig) *>(
GetProcAddress(user32, "QueryDisplayConfig"));
const auto DisplayConfigGetDeviceInfo_addr =
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
const auto SetDisplayConfig_addr =
reinterpret_cast<decltype(SetDisplayConfig) *>(
GetProcAddress(user32, "SetDisplayConfig"));
if (GetDisplayConfigBufferSizes_addr == nullptr || QueryDisplayConfig_addr == nullptr ||
DisplayConfigGetDeviceInfo_addr == nullptr || SetDisplayConfig_addr == nullptr) {
log_warning("graphics", "cannot change primary monitor, OS does not support required APIs)");
return;
}
UINT32 path_count = 0;
UINT32 mode_count = 0;
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
bool succeeded = false;
// in a retry loop, try to query for display config
// retry loop is needed because it can fail with ERROR_INSUFFICIENT_BUFFER
for (int attempt = 0; attempt < 5; ++attempt) {
auto status = GetDisplayConfigBufferSizes_addr(QDC_DATABASE_CURRENT, &path_count, &mode_count);
if (status != ERROR_SUCCESS) {
log_warning("graphics", "GetDisplayConfigBufferSizes failed: {}", status);
return;
}
paths.resize(path_count);
modes.resize(mode_count);
DISPLAYCONFIG_TOPOLOGY_ID topology_id = DISPLAYCONFIG_TOPOLOGY_INTERNAL;
status = QueryDisplayConfig_addr(
QDC_DATABASE_CURRENT,
&path_count,
paths.data(),
&mode_count,
modes.data(),
&topology_id);
if (status == ERROR_SUCCESS) {
// Shrink to actual returned counts
paths.resize(path_count);
modes.resize(mode_count);
succeeded = true;
break;
}
if (status != ERROR_INSUFFICIENT_BUFFER) {
log_warning("graphics", "QueryDisplayConfig failed: {}", status);
return;
}
Sleep(500);
}
if (!succeeded) {
log_warning("graphics", "QueryDisplayConfig failed after reaching max retries");
return;
}
LONG x = 0;
LONG y = 0;
bool found = false;
// find the new main monitor
for (auto& mode : modes) {
if (mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
continue;
}
DISPLAYCONFIG_SOURCE_DEVICE_NAME name = {};
name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
name.header.size = sizeof(name);
name.header.adapterId = mode.adapterId;
name.header.id = mode.id;
if (DisplayConfigGetDeviceInfo_addr(&name.header) != ERROR_SUCCESS) {
continue;
}
const auto device_name = std::string(ws2s(name.viewGdiDeviceName));
if (monitor_name == device_name) {
x = mode.sourceMode.position.x;
y = mode.sourceMode.position.y;
found = true;
log_info(
"graphics",
"new main monitor target found: {}, old position: ({}, {})",
monitor_name,
x, y);
break;
}
}
if (!found) {
log_fatal(
"graphics",
"new main monitor target not found, check -mainmonitor option: {}",
monitor_name);
return;
}
// update monitor positions so that the new monitor is at (0, 0)
for (auto& mode : modes) {
if (mode.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
mode.sourceMode.position.x -= x;
mode.sourceMode.position.y -= y;
}
}
// finally, commit the new display config
const auto status = SetDisplayConfig_addr(
path_count,
paths.data(),
mode_count,
modes.data(),
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG);
if (status != ERROR_SUCCESS) {
log_fatal("graphics", "SetDisplayConfig failed, check -mainmonitor option: {}", status);
}
// a little extra time for windows to settle and redraw things
Sleep(2000);
}
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
// note: all of this is only being done for the primary motnior
// get current settings
DEVMODEA dm = {};
dm.dmSize = sizeof(dm);
if (!EnumDisplaySettingsExA(NULL, ENUM_CURRENT_SETTINGS, &dm, 0)) {
log_warning("graphics", "EnumDisplaySettingsExa failed {}", get_last_error_string());
return;
}
bool needs_update = false;
if (target_orientation.has_value()) {
// convert orientation values, and figure out if resolution needs to be swapped
bool rotate_resolution = false;
DWORD orientation = DMDO_DEFAULT;
switch (target_orientation.value()) {
case ORIENTATION_CW:
orientation = DMDO_90;
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
rotate_resolution = true;
}
break;
case ORIENTATION_CCW:
orientation = DMDO_270;
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
rotate_resolution = true;
}
break;
case ORIENTATION_FLIPPED:
orientation = DMDO_180;
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
rotate_resolution = true;
}
break;
default:
orientation = DMDO_DEFAULT;
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
rotate_resolution = true;
}
break;
}
// update orientation (and resolution if it must be swapped)
if (dm.dmDisplayOrientation != orientation) {
log_misc("graphics",
"current orientation {} => desired orientation {}",
get_dmdo_string(dm.dmDisplayOrientation), get_dmdo_string(orientation));
const DWORD originalWidth = dm.dmPelsWidth;
const DWORD originalHeight = dm.dmPelsHeight;
// change orientation
dm.dmDisplayOrientation = orientation;
dm.dmFields |= DM_DISPLAYORIENTATION;
// rotate resolution
if (rotate_resolution) {
dm.dmPelsWidth = originalHeight;
dm.dmPelsHeight = originalWidth;
dm.dmFields |= DM_PELSHEIGHT | DM_PELSWIDTH;
}
needs_update = true;
}
}
// update refresh rate
if (target_refresh_rate > 0) {
log_misc("graphics",
"current refresh rate {} => desired refresh rate {}",
dm.dmDisplayFrequency, target_refresh_rate);
dm.dmDisplayFrequency = target_refresh_rate;
dm.dmFields |= DM_DISPLAYFREQUENCY;
needs_update = true;
}
if (!needs_update) {
// nothing to do
return;
}
const auto result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
if (result != DISP_CHANGE_SUCCESSFUL) {
log_fatal(
"graphics",
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency,
result);
} else {
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency);
}
// sleep for a little bit after changing monitor settings to delay game launch
Sleep(1000);
}
+4 -1
View File
@@ -15,6 +15,7 @@ enum graphics_orientation {
ORIENTATION_CW = 0, ORIENTATION_CW = 0,
ORIENTATION_CCW = 1, ORIENTATION_CCW = 1,
ORIENTATION_NORMAL = 2, ORIENTATION_NORMAL = 2,
ORIENTATION_FLIPPED = 3
}; };
enum graphics_dx9on12_state { enum graphics_dx9on12_state {
@@ -29,7 +30,6 @@ extern bool GRAPHICS_LOG_HRESULT;
extern bool GRAPHICS_SDVX_FORCE_720; extern bool GRAPHICS_SDVX_FORCE_720;
extern bool GRAPHICS_SHOW_CURSOR; extern bool GRAPHICS_SHOW_CURSOR;
extern bool GRAPHICS_WINDOWED; extern bool GRAPHICS_WINDOWED;
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS; extern std::vector<HWND> GRAPHICS_WINDOWS;
extern UINT GRAPHICS_FORCE_REFRESH; extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB; extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
@@ -107,3 +107,6 @@ bool graphics_window_decoration_change_crashes_game();
bool graphics_window_resize_breaks_game(); bool graphics_window_resize_breaks_game();
bool graphics_window_move_and_resize_breaks_game(); bool graphics_window_move_and_resize_breaks_game();
void graphics_load_windowed_subscreen_parameters(); void graphics_load_windowed_subscreen_parameters();
void change_primary_monitor(const std::string &monitor_name);
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
+37 -16
View File
@@ -351,9 +351,9 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true; GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true; GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
} }
if (options[launcher::Options::DisplayAdapter].is_active() && if (options[launcher::Options::DX9DisplayAdapter].is_active() &&
options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) { options[launcher::Options::DX9DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32(); D3D9_ADAPTER = options[launcher::Options::DX9DisplayAdapter].value_uint32();
// when we fix up adapter numbers, we only fix the first adapter, and not any subsequent // when we fix up adapter numbers, we only fix the first adapter, and not any subsequent
// adapters, so we can't deal with multi-monitor games // adapters, so we can't deal with multi-monitor games
@@ -374,12 +374,15 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::AllowEA3Verbose].value_bool()) { if (options[launcher::Options::AllowEA3Verbose].value_bool()) {
avs::ea3::EA3_DEBUG_VERBOSE = true; avs::ea3::EA3_DEBUG_VERBOSE = true;
} }
std::optional<graphics_orientation> monitor_orientation;
if (options[launcher::Options::spice2x_AutoOrientation].is_active()) { if (options[launcher::Options::spice2x_AutoOrientation].is_active()) {
GRAPHICS_ADJUST_ORIENTATION = monitor_orientation =
(graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32(); (graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32();
} else if (options[launcher::Options::AdjustOrientation].value_bool()) { } else if (options[launcher::Options::AdjustOrientation].value_bool()) {
GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_CW; monitor_orientation = ORIENTATION_CW;
} }
if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) { if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) {
D3D9_DEVICE_HOOK_DISABLE = true; D3D9_DEVICE_HOOK_DISABLE = true;
// touch emulation gets disabled, might as well turn these on // touch emulation gets disabled, might as well turn these on
@@ -980,7 +983,7 @@ int main_implementation(int argc, char *argv[]) {
// do not explicitly set GRAPHICS_9_ON_12_STATE to default here; must respect // do not explicitly set GRAPHICS_9_ON_12_STATE to default here; must respect
// legacy Graphics9On12 option from above if set // legacy Graphics9On12 option from above if set
} }
if (options[launcher::Options::NoLegacy].value_bool() && !cfg::CONFIGURATOR_STANDALONE) { if (options[launcher::Options::NoLegacy].value_bool() && !cfg_run) {
rawinput::NOLEGACY = true; rawinput::NOLEGACY = true;
} }
if (options[launcher::Options::RichPresence].value_bool()) { if (options[launcher::Options::RichPresence].value_bool()) {
@@ -1507,6 +1510,7 @@ int main_implementation(int argc, char *argv[]) {
// early hooks // early hooks
for (auto &hook : early_hooks) { for (auto &hook : early_hooks) {
log_info("launcher", "loading early hook DLL {}", hook); log_info("launcher", "loading early hook DLL {}", hook);
libutils::print_dll_info(hook);
HMODULE module; HMODULE module;
if (!(module = libutils::try_library(hook))) { if (!(module = libutils::try_library(hook))) {
log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string()); log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string());
@@ -2132,6 +2136,12 @@ int main_implementation(int argc, char *argv[]) {
sysutils::print_gpus(); sysutils::print_gpus();
} }
// fix up monitor
if (options[launcher::Options::PrimaryMonitor].is_active()) {
change_primary_monitor(options[launcher::Options::PrimaryMonitor].value_text());
}
update_monitor_on_boot(monitor_orientation, GRAPHICS_FORCE_REFRESH);
// initialize raw input // initialize raw input
RI_MGR = std::make_unique<rawinput::RawInputManager>(); RI_MGR = std::make_unique<rawinput::RawInputManager>();
for (const auto &device : sextet_devices) { for (const auto &device : sextet_devices) {
@@ -2307,6 +2317,7 @@ int main_implementation(int argc, char *argv[]) {
// load hooks // load hooks
for (auto &hook : game_hooks) { for (auto &hook : game_hooks) {
log_info("launcher", "loading hook DLL {}", hook); log_info("launcher", "loading hook DLL {}", hook);
libutils::print_dll_info(hook);
HMODULE module; HMODULE module;
if (!(module = libutils::try_library(hook))) { if (!(module = libutils::try_library(hook))) {
log_warning("launcher", "failed to load hook {}: {}", hook, get_last_error_string()); log_warning("launcher", "failed to load hook {}: {}", hook, get_last_error_string());
@@ -2567,9 +2578,10 @@ void dump_button_bindings(std::vector<Button> *buttons) {
if (button->isNaive()) { if (button->isNaive()) {
log_misc( log_misc(
"rawinput", " [{}] dev=Naive, vkey={}", "rawinput", " [{}] dev=Naive, vkey={} ({})",
button->getName(), button->getName(),
button->getVKey() button->getVKey(),
button->getVKeyString()
); );
} else { } else {
log_misc( log_misc(
@@ -2582,16 +2594,25 @@ void dump_button_bindings(std::vector<Button> *buttons) {
} }
for (auto& alt : button->getAlternatives()) { for (auto& alt : button->getAlternatives()) {
if (alt.getVKey() == INVALID_VKEY) { if (!alt.isValid()) {
continue; continue;
} }
log_misc( if (alt.isNaive()) {
"rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}", log_misc(
button->getName(), "rawinput", " [{}] (alt) dev=Naive, vkey={} ({})",
alt.isNaive() ? "Naive" : alt.getDeviceIdentifier(), alt.getName(),
alt.getVKey(), alt.getVKey(),
static_cast<uint32_t>(alt.getAnalogType()) alt.getVKeyString()
); );
} else {
log_misc(
"rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}",
alt.getName(),
alt.getDeviceIdentifier(),
alt.getVKey(),
static_cast<uint32_t>(alt.getAnalogType())
);
}
} }
} }
} }
+33 -16
View File
@@ -23,6 +23,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
"Game Options", "Game Options",
"Common", "Common",
"Network", "Network",
"Monitor",
"Graphics (Common)", "Graphics (Common)",
"Graphics (Full Screen)", "Graphics (Full Screen)",
"Graphics (Windowed)", "Graphics (Windowed)",
@@ -206,9 +207,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Common", .category = "Common",
}, },
{ {
.title = "Monitor", // PrimaryMonitor
.title = "Change Main Monitor",
.name = "mainmonitor",
.desc = "Changes the primary monitor before launching the game. It will be restored on exit.\n\n"
"This may fail to work properly on hybrid laptops with iGPU + dGPU.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY2",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
// DX9DisplayAdapter
.title = "DX9 Primary Display Adapter Override",
.name = "monitor", .name = "monitor",
.desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n" .display_name = "dx9mainadapter",
.aliases = "dx9mainadapter",
.desc = "Prefer to use Change Main Monitor option instead of this one.\n\n"
"Sets the display that the game will be opened in, for multiple monitors.\n\n"
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n" "0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
"Not all games will respect this. Not recommended for multi-monitor games like Lightning Model / Valkyrie Model modes. " "Not all games will respect this. Not recommended for multi-monitor games like Lightning Model / Valkyrie Model modes. "
"Disable Full Screen Optimizations for best results.", "Disable Full Screen Optimizations for best results.",
@@ -216,21 +232,19 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Graphics (Full Screen)", .category = "Graphics (Full Screen)",
}, },
{ {
.title = "Only Use One Monitor", .title = "Only Use Main Monitor For Full Screen",
.name = "graphics-force-single-adapter", .name = "graphics-force-single-adapter",
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n" .desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n"
"May cause unstable framerate and desyncs, especially if monitors have different refresh rates!", "May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
.type = OptionType::Bool, .type = OptionType::Bool,
.category = "Graphics (Full Screen)", .category = "Monitor",
}, },
{ {
.title = "Force Monitor Refresh Rate", .title = "Monitor Refresh Rate",
.name = "graphics-force-refresh", .name = "graphics-force-refresh",
.desc = .desc = "Change the refresh rate for the primary monitor before launching the game. It will be restored on exit.",
"Attempt to change the refresh rate for the primary monitor before the game boots; "
"works in both full screen and windowed modes, but known to fail for some games.",
.type = OptionType::Integer, .type = OptionType::Integer,
.category = "Graphics (Common)", .category = "Monitor",
}, },
{ {
// FullscreenResolution // FullscreenResolution
@@ -1847,17 +1861,20 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// spice2x_AutoOrientation // spice2x_AutoOrientation
.title = "Auto-rotate Display", .title = "Rotate Monitor",
.name = "sp2x-autoorientation", .name = "sp2x-autoorientation",
.display_name = "autoorientation", .display_name = "autoorientation",
.aliases= "autoorientation", .aliases= "autoorientation",
.desc = "Automatically adjust the orientation of your display when launched. " .desc = "Change the orientation of the primary display before launching the game. It will be restored on exit",
"WARNING: game may launch at incorrect refresh rate! Use in combination with "
"-graphics-force-refresh and potentially either -9on12 or full-screen optimizations (FSO) to fix.",
.type = OptionType::Enum, .type = OptionType::Enum,
.category = "Graphics (Common)", .category = "Monitor",
// match graphics_orientation // match graphics_orientation enum
.elements = {{"0", "90 (CW)"}, {"1", "270 (CCW)"}}, .elements = {
{"0", "Portrait"},
{"1", "Portrait, Flipped"},
{"2", "Landscape"},
{"3", "Landscape, Flipped"}
},
}, },
{ {
.title = "AVS Log Level", .title = "AVS Log Level",
+2 -1
View File
@@ -26,7 +26,8 @@ namespace launcher {
ExecuteScript, ExecuteScript,
CaptureCursor, CaptureCursor,
ShowCursor, ShowCursor,
DisplayAdapter, PrimaryMonitor,
DX9DisplayAdapter,
GraphicsForceSingleAdapter, GraphicsForceSingleAdapter,
GraphicsForceRefresh, GraphicsForceRefresh,
FullscreenResolution, FullscreenResolution,
+13
View File
@@ -163,4 +163,17 @@ namespace ImGui {
ImGui::PopID(); ImGui::PopID();
return clicked; return clicked;
} }
void InvisibleTableRowSelectable() {
ImGui::TableSetColumnIndex(0);
ImGui::PushStyleColor(ImGuiCol_Header, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
ImGui::Selectable("##row", false,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
}
}
} }
+1
View File
@@ -20,4 +20,5 @@ namespace ImGui {
void TextTruncated(const std::string& p_text, float p_truncated_width); void TextTruncated(const std::string& p_text, float p_truncated_width);
bool DeleteButton(const std::string& tooltip); bool DeleteButton(const std::string& tooltip);
bool ClearButton(const std::string& tooltip); bool ClearButton(const std::string& tooltip);
void InvisibleTableRowSelectable();
} }
+167 -78
View File
@@ -15,6 +15,15 @@
#include "util/utils.h" #include "util/utils.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
#if !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) || \ #if !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) || \
!defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \ !defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \
!defined(IMGUI_USE_BGRA_PACKED_COLOR) || \ !defined(IMGUI_USE_BGRA_PACKED_COLOR) || \
@@ -33,6 +42,95 @@ static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
static double g_LastMouseMovement = 0.f; static double g_LastMouseMovement = 0.f;
static bool g_MouseCursorAutoHide = false; static bool g_MouseCursorAutoHide = false;
constexpr size_t VKEY_MAX = 255;
static std::array<bool, VKEY_MAX> g_KeysDown;
static std::array<bool, ImGuiMouseButton_COUNT> g_MouseDown;
static ImGuiKey get_imgui_key(int vkey) {
switch (vkey) {
case VK_TAB:
return ImGuiKey_Tab;
case VK_LEFT:
return ImGuiKey_LeftArrow;
case VK_RIGHT:
return ImGuiKey_RightArrow;
case VK_UP:
return ImGuiKey_UpArrow;
case VK_DOWN:
return ImGuiKey_DownArrow;
case VK_PRIOR:
return ImGuiKey_PageUp;
case VK_NEXT:
return ImGuiKey_PageDown;
case VK_HOME:
return ImGuiKey_Home;
case VK_END:
return ImGuiKey_End;
case VK_INSERT:
return ImGuiKey_Insert;
case VK_DELETE:
return ImGuiKey_Delete;
case VK_BACK:
return ImGuiKey_Backspace;
case VK_SPACE:
return ImGuiKey_Space;
case VK_RETURN:
return ImGuiKey_Enter;
case VK_ESCAPE:
return ImGuiKey_Escape;
case VK_SHIFT:
case VK_LSHIFT:
return ImGuiKey_LeftShift;
case VK_RSHIFT:
return ImGuiKey_RightShift;
case VK_CONTROL:
case VK_LCONTROL:
return ImGuiKey_LeftCtrl;
case VK_RCONTROL:
return ImGuiKey_RightCtrl;
case 'A':
case 'a':
return ImGuiKey_A;
case 'C':
case 'c':
return ImGuiKey_C;
case 'V':
case 'v':
return ImGuiKey_V;
case 'X':
case 'x':
return ImGuiKey_X;
case 'Y':
case 'y':
return ImGuiKey_Y;
case 'Z':
case 'z':
return ImGuiKey_Z;
default:
return ImGuiKey_None;
}
}
static ImGuiKey get_imgui_mod_key(int vkey) {
switch (vkey) {
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
return ImGuiMod_Shift;
return ImGuiMod_Shift;
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
return ImGuiMod_Ctrl;
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
return ImGuiMod_Alt;
default:
return ImGuiMod_None;
}
}
bool ImGui_ImplSpice_Init(HWND hWnd) { bool ImGui_ImplSpice_Init(HWND hWnd) {
log_misc("imgui_impl_spice", "init"); log_misc("imgui_impl_spice", "init");
@@ -59,30 +157,6 @@ bool ImGui_ImplSpice_Init(HWND hWnd) {
io.BackendPlatformName = "imgui_impl_spice"; io.BackendPlatformName = "imgui_impl_spice";
io.ConfigErrorRecoveryEnableTooltip = true; io.ConfigErrorRecoveryEnableTooltip = true;
// keyboard mapping
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR;
io.KeyMap[ImGuiKey_PageDown] = VK_NEXT;
io.KeyMap[ImGuiKey_Home] = VK_HOME;
io.KeyMap[ImGuiKey_End] = VK_END;
io.KeyMap[ImGuiKey_Insert] = VK_INSERT;
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
io.KeyMap[ImGuiKey_Backspace] = VK_BACK;
io.KeyMap[ImGuiKey_Space] = VK_SPACE;
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
io.KeyMap[ImGuiKey_KeypadEnter] = VK_RETURN;
io.KeyMap[ImGuiKey_A] = 'A';
io.KeyMap[ImGuiKey_C] = 'C';
io.KeyMap[ImGuiKey_V] = 'V';
io.KeyMap[ImGuiKey_X] = 'X';
io.KeyMap[ImGuiKey_Y] = 'Y';
io.KeyMap[ImGuiKey_Z] = 'Z';
// get display size // get display size
ImGui_ImplSpice_UpdateDisplaySize(); ImGui_ImplSpice_UpdateDisplaySize();
@@ -248,7 +322,7 @@ static void ImGui_ImplSpice_UpdateMousePos() {
} }
// delay press // delay press
io.MouseDown[0] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id; g_MouseDown[ImGuiMouseButton_Left] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id;
if (last_touch_id == ~0u) { if (last_touch_id == ~0u) {
last_touch_id = tp.id; last_touch_id = tp.id;
} }
@@ -275,38 +349,26 @@ void ImGui_ImplSpice_NewFrame() {
g_Time = current_time; g_Time = current_time;
// remember old state // remember old state
BYTE KeysDownOld[sizeof(io.KeysDown)]; std::array<BYTE, VKEY_MAX> KeysDownOld;
for (size_t i = 0; i < sizeof(io.KeysDown); i++) { for (size_t i = 0; i < VKEY_MAX; i++) {
KeysDownOld[i] = io.KeysDown[i] ? ~0 : 0; KeysDownOld[i] = g_KeysDown[i] ? ~0 : 0;
} }
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_LSHIFT];
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_RSHIFT]; const auto MouseDownOld = g_MouseDown;
// reset keys state // reset keys state
io.MouseWheel = 0; g_MouseDown.fill(false);
io.KeyCtrl = false; g_KeysDown.fill(false);
io.KeyShift = false;
io.KeyAlt = false;
io.KeySuper = false;
memset(io.KeysDown, false, sizeof(io.KeysDown));
memset(io.MouseDown, false, sizeof(io.MouseDown));
// early quit if window not in focus // early quit if window not in focus
if (!superexit::has_focus() || rawinput::OS_WINDOW_ACTIVE) { if (!superexit::has_focus() || rawinput::OS_WINDOW_ACTIVE) {
return; return;
} }
// read keyboard modifiers inputs
io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0;
io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0;
io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0;
io.KeySuper = (::GetKeyState(VK_LWIN) & 0x8000) != 0;
io.KeySuper |= (::GetKeyState(VK_RWIN) & 0x8000) != 0;
// apply windows mouse buttons // apply windows mouse buttons
io.MouseDown[0] |= (get_async_primary_mouse()) != 0; g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0;
io.MouseDown[1] |= (get_async_secondary_mouse()) != 0; g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0;
io.MouseDown[2] |= (GetAsyncKeyState(VK_MBUTTON)) != 0; g_MouseDown[ImGuiMouseButton_Middle] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
// read new keys state // read new keys state
static long mouse_wheel_last = 0; static long mouse_wheel_last = 0;
@@ -321,21 +383,21 @@ void ImGui_ImplSpice_NewFrame() {
// mouse button triggers // mouse button triggers
if (GetSystemMetrics(SM_SWAPBUTTON)) { if (GetSystemMetrics(SM_SWAPBUTTON)) {
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) { if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[0] = true; g_MouseDown[ImGuiMouseButton_Left] = true;
} }
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) { if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[1] = true; g_MouseDown[ImGuiMouseButton_Right] = true;
} }
} else { } else {
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) { if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[0] = true; g_MouseDown[ImGuiMouseButton_Left] = true;
} }
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) { if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[1] = true; g_MouseDown[ImGuiMouseButton_Right] = true;
} }
} }
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) { if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
io.MouseDown[2] = true; g_MouseDown[ImGuiMouseButton_Middle] = true;
} }
// final mouse wheel value should be all devices combined // final mouse wheel value should be all devices combined
@@ -346,33 +408,11 @@ void ImGui_ImplSpice_NewFrame() {
case rawinput::KEYBOARD: { case rawinput::KEYBOARD: {
// iterate all virtual key codes // iterate all virtual key codes
for (size_t vKey = 0; vKey < 256; vKey++) { for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
// get state (combined from all pages) // get state (combined from all pages)
auto &key_states = device.keyboardInfo->key_states; auto &key_states = device.keyboardInfo->key_states;
bool state = false;
for (size_t page_index = 0; page_index < 1024; page_index += 256) { for (size_t page_index = 0; page_index < 1024; page_index += 256) {
state |= key_states[page_index + vKey]; g_KeysDown[vKey] |= key_states[page_index + vKey];
}
// trigger
io.KeysDown[vKey] |= state;
// generate character input, but only if WM_CHAR didn't take over the
// functionality
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
UCHAR buf[2];
auto ret = ToAscii(
static_cast<UINT>(vKey),
0,
static_cast<const BYTE *>(KeysDownOld),
reinterpret_cast<LPWORD>(buf),
0);
if (ret > 0) {
for (int i = 0; i < ret; i++) {
overlay::OVERLAY->input_char(buf[i]);
}
}
} }
} }
break; break;
@@ -383,22 +423,71 @@ void ImGui_ImplSpice_NewFrame() {
} }
} }
// process keyboard input from all keyboard collapsed into one state (g_KeysDown)
for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
const bool state = g_KeysDown[vKey];
const auto imgui_key = get_imgui_key(vKey);
const auto changed =
(state && !KeysDownOld[vKey]) ||
(!state && KeysDownOld[vKey]);
if (imgui_key != ImGuiKey_None && changed) {
io.AddKeyEvent(imgui_key, state);
log_debug("imgui_impl_spice", "vkey {:#x} added as navigation event, state: {}", static_cast<uint64_t>(vKey), state);
// mod key must also be processed separately
const auto imgui_mod_key = get_imgui_mod_key(vKey);
if (imgui_mod_key != ImGuiMod_None) {
io.AddKeyEvent(imgui_mod_key, state);
}
}
// generate character input, but only if WM_CHAR didn't take over the functionality
// only detecting rising edges here - this means holding a key won't work
// (it's better than repeating a character input every frame - cost we pay for providing input
// on top of rawinput instead of WM_CHAR)
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
UCHAR buf[2];
auto ret = ToAscii(
static_cast<UINT>(vKey),
0,
static_cast<const BYTE *>(KeysDownOld.data()),
reinterpret_cast<LPWORD>(buf),
0);
if (ret > 0) {
for (int i = 0; i < ret; i++) {
overlay::OVERLAY->input_char(buf[i]);
log_debug("imgui_impl_spice", "vkey {:#x} inputted as character", vKey);
}
}
}
}
// set mouse wheel // set mouse wheel
auto wheel_diff = mouse_wheel - mouse_wheel_last; auto wheel_diff = mouse_wheel - mouse_wheel_last;
mouse_wheel_last = mouse_wheel; mouse_wheel_last = mouse_wheel;
io.MouseWheel = wheel_diff; io.AddMouseWheelEvent(0, wheel_diff);
// update OS mouse position // update OS mouse position
const auto old_mouse_pos = io.MousePos; const auto old_mouse_pos = io.MousePos;
ImGui_ImplSpice_UpdateMousePos(); ImGui_ImplSpice_UpdateMousePos();
const auto new_mouse_pos = io.MousePos; const auto new_mouse_pos = io.MousePos;
// update mouse buttons
// doing this after ImGui_ImplSpice_UpdateMousePos since it can set g_MouseDown for touch input
for (size_t i = 0; i < g_MouseDown.size(); i++) {
if (MouseDownOld[i] != g_MouseDown[i]) {
io.AddMouseButtonEvent(i, g_MouseDown[i]);
log_debug("imgui_impl_spice", "mouse button {} event", g_MouseDown[i]);
}
}
// automatically hide cursor // automatically hide cursor
if (g_MouseCursorAutoHide) { if (g_MouseCursorAutoHide) {
if (old_mouse_pos.x != new_mouse_pos.x || if (old_mouse_pos.x != new_mouse_pos.x ||
old_mouse_pos.y != new_mouse_pos.y || old_mouse_pos.y != new_mouse_pos.y ||
wheel_diff != 0 || wheel_diff != 0 ||
io.MouseDown[0] || io.MouseDown[1] || io.MouseDown[2]) { g_MouseDown[ImGuiMouseButton_Left] || g_MouseDown[ImGuiMouseButton_Right] || g_MouseDown[ImGuiMouseButton_Middle]) {
// mouse moved, update time and show the cursor // mouse moved, update time and show the cursor
g_LastMouseMovement = get_performance_milliseconds(); g_LastMouseMovement = get_performance_milliseconds();
+3 -3
View File
@@ -501,7 +501,7 @@ void paint_draw_cmd(
const SwOptions& options, const SwOptions& options,
Stats* stats) Stats* stats)
{ {
const auto texture = reinterpret_cast<const Texture*>(pcmd.TextureId); const auto texture = reinterpret_cast<const Texture*>(pcmd.GetTexID());
const auto offset = pcmd.IdxOffset; const auto offset = pcmd.IdxOffset;
// ImGui uses the first pixel for "white". // ImGui uses the first pixel for "white".
@@ -678,7 +678,7 @@ void bind_imgui_painting()
int font_width, font_height; int font_width, font_height;
io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height); io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height);
const auto texture = new Texture{tex_data, font_width, font_height}; const auto texture = new Texture{tex_data, font_width, font_height};
io.Fonts->TexID = reinterpret_cast<ImTextureID>(texture); io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(texture));
} }
static Stats s_stats; // TODO: pass as an argument? static Stats s_stats; // TODO: pass as an argument?
@@ -700,7 +700,7 @@ void paint_imgui(uint32_t* pixels, int width_pixels, int height_pixels, const Sw
void unbind_imgui_painting() void unbind_imgui_painting()
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
delete reinterpret_cast<Texture*>(io.Fonts->TexID); delete reinterpret_cast<Texture*>(io.Fonts->TexID.GetTexID());
io.Fonts = nullptr; io.Fonts = nullptr;
} }
+3 -3
View File
@@ -185,7 +185,7 @@ void overlay::SpiceOverlay::init() {
if (UI_SCALE_PERCENT.has_value()) { if (UI_SCALE_PERCENT.has_value()) {
const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f; const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f;
ImGui::GetStyle().ScaleAllSizes(scale); ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetIO().FontGlobalScale = scale; ImGui::GetStyle().FontScaleMain = scale;
} }
// based on CrimsonVesuvius theme from // based on CrimsonVesuvius theme from
@@ -194,7 +194,7 @@ void overlay::SpiceOverlay::init() {
// Text // Text
colors[ImGuiCol_Text] = ImVec4(1.00f, 0.90f, 0.90f, 1.00f); // Slight pinkish tint to off-white colors[ImGuiCol_Text] = ImVec4(1.00f, 0.90f, 0.90f, 1.00f); // Slight pinkish tint to off-white
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.40f, 0.40f, 1.00f); colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.26f, 0.26f, 1.00f);
// Backgrounds // Backgrounds
colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f); // Deep charcoal colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f); // Deep charcoal
@@ -283,7 +283,7 @@ void overlay::SpiceOverlay::init() {
io.FontAllowUserScaling = true; io.FontAllowUserScaling = true;
// add default font // add default font
io.Fonts->AddFontDefault(); io.Fonts->AddFontDefaultBitmap();
// add fallback fonts for missing glyph ranges // add fallback fonts for missing glyph ranges
ImFontConfig config {}; ImFontConfig config {};
+515 -190
View File
@@ -34,6 +34,7 @@
#include "util/resutils.h" #include "util/resutils.h"
#include "util/scope_guard.h" #include "util/scope_guard.h"
#include "util/time.h" #include "util/time.h"
#include "util/sysutils.h"
#include "util/utils.h" #include "util/utils.h"
#ifdef min #ifdef min
@@ -58,6 +59,8 @@ namespace overlay::windows {
const auto PROJECT_URL = "https://spice2x.github.io"; const auto PROJECT_URL = "https://spice2x.github.io";
constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f); constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f);
constexpr ImVec4 TEXT_COLOR_RED(1.f, 0.f, 0.f, 1.f);
constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512;
std::unique_ptr<AsioDriverList> asio_driver_list; std::unique_ptr<AsioDriverList> asio_driver_list;
@@ -202,25 +205,6 @@ namespace overlay::windows {
} }
} }
void Config::write_card(int player) {
// get path
auto bindings = ::Config::getInstance().getKeypadBindings(this->games_selected_name);
std::filesystem::path path;
if (!bindings.card_paths[player].empty()) {
path = bindings.card_paths[player];
} else {
path = player > 0 ? "card1.txt" : "card0.txt";
}
// write file
std::ofstream f(path);
if (f) {
f.write(this->keypads_card_number[player], strlen(this->keypads_card_number[player]));
f.close();
}
}
void Config::build_content() { void Config::build_content() {
// if standalone then fullscreen window // if standalone then fullscreen window
@@ -703,12 +687,18 @@ namespace overlay::windows {
// primary // primary
build_button(name, primary_button, &primary_button, button_it, button_it_max, 0); build_button(name, primary_button, &primary_button, button_it, button_it_max, 0);
ImGui::PushID(&primary_button);
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
// alternatives // alternatives
int alt_index = 1; // 0 is primary int alt_index = 1; // 0 is primary
for (auto &alt : primary_button.getAlternatives()) { for (auto &alt : primary_button.getAlternatives()) {
if (alt.isValid()) { if (alt.isValid()) {
build_button(name, primary_button, &alt, button_it, button_it_max, alt_index); build_button(name, primary_button, &alt, button_it, button_it_max, alt_index);
ImGui::PushID(&alt);
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
} }
alt_index++; alt_index++;
} }
@@ -864,6 +854,51 @@ namespace overlay::windows {
ImGui::HelpTooltip("Bind a button to a device using Windows RawInput API."); ImGui::HelpTooltip("Bind a button to a device using Windows RawInput API.");
} }
if (this->analog_as_button_warning_show_next_frame.first == button->getName() &&
this->analog_as_button_warning_show_next_frame.second == alt_index) {
this->analog_as_button_warning_show_next_frame = std::make_pair("", 0);
ImGui::OpenPopup("Warning##AnalogAsButtonWarning");
// stop bind-many
RI_MGR->devices_midi_freeze(false);
this->buttons_bind_active = false;
this->buttons_many_active = false;
}
if (ImGui::BeginPopupModal("Warning##AnalogAsButtonWarning", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted(
"You just bound an analog axis to a digital button.\n"
"\n"
"If your controller supports analog input (turntable,\n"
"knob, slider) you should instead use the Analog tab and\n"
"bind as an analog axis.");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// yes
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.2f, 0.9f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.7f, 0.2f, 1.0f));
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("I understand, undo binding")) {
clear_button(button, alt_index);
ImGui::CloseCurrentPopup();
}
ImGui::PopStyleColor(3);
// no
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Ignore and continue anyway")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
bind_button_popup(bind_name, button, button_it_max, alt_index); bind_button_popup(bind_name, button, button_it_max, alt_index);
// naive binding // naive binding
@@ -1208,6 +1243,22 @@ namespace overlay::windows {
buttons_bind_active = false; buttons_bind_active = false;
inc_buttons_many_index(button_it_max); inc_buttons_many_index(button_it_max);
RI_MGR->devices_midi_freeze(false); RI_MGR->devices_midi_freeze(false);
// usually, turntables are X, knobs are X and Y
// gamepad triggers are Z, and Rx/Ry are right thumb stick
// one day we will label all I/O modules and flag which one are button-as-analog
// so that we don't have to do string comparions like below
if ((value_name == "X" || value_name == "Y") &&
(button->getName().find("Press") == std::string::npos) && // museca
(button->getName().find("Slowdown") == std::string::npos) && // bishibashi
(button->getName().find("TT+") != std::string::npos ||
button->getName().find("TT-") != std::string::npos || // iidx
button->getName().find("Knob") != std::string::npos || // gitadora guitar
button->getName().find("Disk") != std::string::npos || // museca, bishibashi
button->getName().find("VOL-") != std::string::npos)) { // sdvx
this->analog_as_button_warning_show_next_frame =
std::make_pair(button->getName(), alt_index);
}
break; break;
} else if (diff > 0.3f) { } else if (diff > 0.3f) {
@@ -2127,6 +2178,9 @@ namespace overlay::windows {
edit_analog_popup(analog); edit_analog_popup(analog);
// row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable();
// clean up // clean up
ImGui::PopID(); ImGui::PopID();
} }
@@ -2143,9 +2197,8 @@ namespace overlay::windows {
auto analog_device_changed = ImGui::Combo( auto analog_device_changed = ImGui::Combo(
"Device", "Device",
&this->analogs_devices_selected, &this->analogs_devices_selected,
[](void* data, int i, const char **item) { [](void* data, int i) {
*item = ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str(); return ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str();
return true;
}, },
&this->analogs_devices, (int) this->analogs_devices.size()); &this->analogs_devices, (int) this->analogs_devices.size());
@@ -2264,9 +2317,8 @@ namespace overlay::windows {
// controls // controls
ImGui::Combo("Control", ImGui::Combo("Control",
&this->analogs_devices_control_selected, &this->analogs_devices_control_selected,
[](void* data, int i, const char **item) { [](void* data, int i) {
*item = ((std::vector<std::string>*) data)->at(i).c_str(); return ((std::vector<std::string>*) data)->at(i).c_str();
return true;
}, },
&control_names, control_names.size()); &control_names, control_names.size());
@@ -2747,13 +2799,21 @@ namespace overlay::windows {
} }
build_light(light, &light, i, 0); build_light(light, &light, i, 0);
ImGui::PushID(&light);
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
int alt_index = 1; int alt_index = 1;
for (auto &alt : light.getAlternatives()) { for (auto &alt : light.getAlternatives()) {
if (alt.isValid()) { if (alt.isValid()) {
build_light(light, &alt, i, alt_index); build_light(light, &alt, i, alt_index);
ImGui::PushID(&alt);
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
} }
alt_index++; alt_index++;
} }
} }
if (table_begin) { if (table_begin) {
ImGui::EndTable(); ImGui::EndTable();
@@ -2896,9 +2956,8 @@ namespace overlay::windows {
bool control_changed = false; bool control_changed = false;
if (ImGui::Combo("Device", if (ImGui::Combo("Device",
&this->lights_devices_selected, &this->lights_devices_selected,
[] (void* data, int i, const char **item) { [] (void* data, int i) {
*item = ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str(); return ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str();
return true;
}, },
&this->lights_devices, (int) this->lights_devices.size())) { &this->lights_devices, (int) this->lights_devices.size())) {
this->lights_devices_control_selected = 0; this->lights_devices_control_selected = 0;
@@ -2993,9 +3052,8 @@ namespace overlay::windows {
// controls // controls
if (ImGui::Combo("Light Control", if (ImGui::Combo("Light Control",
&this->lights_devices_control_selected, &this->lights_devices_control_selected,
[] (void* data, int i, const char **item) { [] (void* data, int i) {
*item = ((std::vector<std::string> *) data)->at(i).c_str(); return ((std::vector<std::string> *) data)->at(i).c_str();
return true;
}, },
&control_names, control_names.size())) { &control_names, control_names.size())) {
control_changed = true; control_changed = true;
@@ -3076,10 +3134,9 @@ namespace overlay::windows {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
bool device_changed = ImGui::Combo("##AutoMatchDevice", bool device_changed = ImGui::Combo("##AutoMatchDevice",
&this->auto_match_device_selected, &this->auto_match_device_selected,
[] (void* data, int i, const char **item) { [] (void* data, int i) {
auto *devs = (std::vector<rawinput::Device*>*) data; auto *devs = (std::vector<rawinput::Device*>*) data;
*item = devs->at(i)->desc.c_str(); return devs->at(i)->desc.c_str();
return true;
}, },
&this->auto_match_devices, &this->auto_match_devices,
(int) this->auto_match_devices.size()); (int) this->auto_match_devices.size());
@@ -3694,14 +3751,196 @@ namespace overlay::windows {
void Config::build_cards() { void Config::build_cards() {
constexpr float TEXT_INPUT_WIDTH = 240.f;
// early quit // early quit
if (this->games_selected < 0 || this->games_selected_name.empty()) { if (this->games_selected < 0 || this->games_selected_name.empty()) {
ImGui::Text("Please select a game first."); ImGui::Text("Please select a game first.");
return; return;
} }
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card overrides");
ImGui::Spacing(); ImGui::Spacing();
ImGui::SeparatorText("Card files (card0.txt / card1.txt)"); ImGui::TextUnformatted(
"Specify hardcoded card numbers here. This will always take priority when Insert Card is pressed.");
ImGui::Spacing();
// read in values from options
auto options = games::get_options(this->games_selected_name);
for (int player = 0; player < 2; player++) {
ImGui::PushID(("OverrideCardP" + to_string(player)).c_str());
// intentionally using the same buffer length as options tab
char buffer[OPTION_INPUT_TEXT_WIDTH];
bool card_changed = false;
// read in values from options
auto &option = (player == 0)
? options->at(launcher::Options::Player1Card)
: options->at(launcher::Options::Player2Card);
if (option.is_active()) {
strncpy(buffer, option.value.c_str(), sizeof(buffer) - 1);
} else {
buffer[0] = '\0';
}
const bool tree_open = ImGui::TreeNodeEx("##OverrideCardTree", ImGuiTreeNodeFlags_DefaultOpen);
if (this->keypads_card_override_valid[player]) {
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
}
ImGui::SameLine(0.0f, 0.0f);
ImGui::Text("Player %i (-card%i)", player + 1, player);
if (this->keypads_card_override_valid[player]) {
ImGui::PopStyleColor();
}
if (tree_open) {
// card number box
ImGui::PushStyleColor(
ImGuiCol_Text,
this->keypads_card_override_valid[player] ? ImVec4(1.f, 1.f, 1.f, 1.f) :
ImVec4(1.f, 0.f, 0.f, 1.f));
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
ImGui::BeginDisabled(option.disabled);
ImGui::InputTextWithHint("##OverrideCard", "E004010000000000",
buffer, sizeof(buffer) - 1,
ImGuiInputTextFlags_CharsUppercase |
ImGuiInputTextFlags_CharsHexadecimal |
ImGuiInputTextFlags_EscapeClearsAll);
ImGui::PopStyleColor();
if (ImGui::IsItemDeactivatedAfterEdit()) {
card_changed = true;
}
// check if valid on every frame;
// do this after the widget and remember the result for next frame
this->keypads_card_override_valid[player] = false;
const auto buffer_len = strlen(buffer);
if (buffer_len == 16) {
this->keypads_card_override_valid[player] = validate_ea_card(buffer);
} else if (buffer_len == 0) {
this->keypads_card_override_valid[player] = false;
}
ImGui::EndDisabled();
if (!option.disabled) {
// generate button
ImGui::SameLine();
if (ImGui::Button("Generate")) {
if (!this->keypads_card_overwrite_confirmed[player] && buffer_len > 0) {
ImGui::OpenPopup("Card Generator##GenerateCardConfirm");
} else {
generate_ea_card(buffer);
card_changed = true;
}
}
// clear button
if (option.is_active()) {
ImGui::SameLine();
if (ImGui::Button("Clear")) {
if (!this->keypads_card_overwrite_confirmed[player] && buffer_len > 0) {
ImGui::OpenPopup("Clear Card##ClearCardConfirm");
} else {
buffer[0] = '\0';
card_changed = true;
}
}
}
} else {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip(
"This option cannot be edited because it was overriden by command-line options.\n"
"Run spicecfg.exe to configure the options and then run spice(64).exe directly.");
}
}
// bad card number warning
if (!this->keypads_card_override_valid[player] && buffer_len > 0) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "Invalid card number");
}
if (ImGui::BeginPopupModal(
"Card Generator##GenerateCardConfirm",
nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted(
"What do you want to do?");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Discard current card and generate new one")) {
this->keypads_card_overwrite_confirmed[player] = true;
generate_ea_card(buffer);
card_changed = true;
ImGui::CloseCurrentPopup();
}
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Keep current card and cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginPopupModal(
"Clear Card##ClearCardConfirm",
nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted(
"This cannot be undone. Are you sure?");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Yes, delete card number")) {
this->keypads_card_overwrite_confirmed[player] = true;
buffer[0] = '\0';
card_changed = true;
ImGui::CloseCurrentPopup();
}
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("No, keep my card number")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::TreePop();
}
if (player == 0) {
ImGui::Spacing();
ImGui::Spacing();
}
ImGui::PopID();
if (card_changed) {
this->options_dirty = true;
option.value = buffer;
::Config::getInstance().updateBinding(games_list[games_selected], option);
}
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card from text files");
ImGui::Spacing();
ImGui::TextUnformatted(
"Use text files on disk; its content will be read when Insert Card is pressed.");
ImGui::Spacing(); ImGui::Spacing();
// get bindings and copy paths // get bindings and copy paths
@@ -3718,133 +3957,149 @@ namespace overlay::windows {
// card settings for each player // card settings for each player
for (int player = 0; player < 2; player++) { for (int player = 0; player < 2; player++) {
// check overrides first
const auto &override = (player == 0)
? options->at(launcher::Options::Player1Card)
: options->at(launcher::Options::Player2Card);
// custom ID and title // custom ID and title
ImGui::PushID(("KeypadP" + to_string(player)).c_str()); ImGui::PushID(("KeypadP" + to_string(player)).c_str());
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Player %i", player + 1); const bool tree_open = ImGui::TreeNodeEx("##KeypadTreeNode", ImGuiTreeNodeFlags_DefaultOpen);
const bool card_content_valid =
// card path !override.is_active() &&
std::string hint = "card" + to_string(player) + ".txt"; this->keypads_card_file_contents_valid[player] &&
if (ImGui::InputTextWithHint("Card Path", hint.c_str(), this->keypads_card_number[player][0] != 0;
this->keypads_card_path[player], sizeof(this->keypads_card_path[0]) - 1)) if (card_content_valid) {
{ ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
bindings.card_paths[player] = this->keypads_card_path[player]; }
bindings_updated = true; ImGui::SameLine(0.0f, 0.0f);
ImGui::Text("Player %i (card%i.txt)", player + 1, player);
if (card_content_valid) {
ImGui::PopStyleColor();
} }
// help marker if (tree_open) {
ImGui::SameLine(); if (override.is_active()) {
ImGui::HelpMarker("Leave this empty to use the card file in your game directory.\n" ImGui::TextDisabled("Disabled - override is in use.");
"Hint: You can place 'card0.txt' (P1) / 'card1.txt' (P2) into the root of your USB " } else {
"flash drive and it will trigger a card insert when you connect it!"); // card path
std::string hint = "using .\\card" + to_string(player) + ".txt (default)";
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
if (ImGui::InputTextWithHint("File Path", hint.c_str(),
this->keypads_card_path[player], sizeof(this->keypads_card_path[0]) - 1))
{
bindings.card_paths[player] = this->keypads_card_path[player];
bindings_updated = true;
}
// card path file selector // help marker
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Open...")) { ImGui::HelpMarker("Leave this empty to use the card file in your game directory.\n"
"Hint: You can place 'card0.txt' (P1) / 'card1.txt' (P2) into the root of your USB "
"flash drive and it will trigger a card insert when you connect it!");
// standalone version opens native file browser // card path file selector
if (cfg::CONFIGURATOR_STANDALONE && !keypads_card_select) { ImGui::SameLine();
if (ImGui::Button("Open...")) {
// run in separate thread otherwise we get a crash // standalone version opens native file browser
keypads_card_select_done = false; if (cfg::CONFIGURATOR_STANDALONE && !keypads_card_select) {
keypads_card_select = new std::thread([this, bindings, player, game] {
// open dialog to get path // run in separate thread otherwise we get a crash
auto ofn_path = std::make_unique<wchar_t[]>(512); keypads_card_select_done = false;
ofn_path[0] = L'\0'; keypads_card_select = new std::thread([this, bindings, player, game] {
OPENFILENAMEW ofn {};
ofn.lStructSize = sizeof(ofn);
ofn.lpstrFile = ofn_path.get();
ofn.nMaxFile = 512;
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR;
ofn.lpstrDefExt = L"txt";
ofn.lpstrInitialDir = L".";
// check for success // open dialog to get path
auto guard = rawinput::set_os_window_focus_guard(); auto ofn_path = std::make_unique<wchar_t[]>(512);
if (GetSaveFileNameW(&ofn)) { ofn_path[0] = L'\0';
OPENFILENAMEW ofn {};
ofn.lStructSize = sizeof(ofn);
ofn.lpstrFile = ofn_path.get();
ofn.nMaxFile = 512;
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR;
ofn.lpstrDefExt = L"txt";
ofn.lpstrInitialDir = L".";
// update card path // check for success
auto new_bindings = bindings; auto guard = rawinput::set_os_window_focus_guard();
new_bindings.card_paths[player] = std::filesystem::path(ofn_path.get()); if (GetSaveFileNameW(&ofn)) {
::Config::getInstance().updateBinding(game, new_bindings);
eamuse_update_keypad_bindings();
read_card(player); // update card path
} else { auto new_bindings = bindings;
auto error = CommDlgExtendedError(); new_bindings.card_paths[player] = std::filesystem::path(ofn_path.get());
if (error) { ::Config::getInstance().updateBinding(game, new_bindings);
log_warning("cfg", "failed to get save file name: {}", error); eamuse_update_keypad_bindings();
} else {
log_warning("cfg", "failed to get save file name"); read_card(player);
} } else {
auto error = CommDlgExtendedError();
if (error) {
log_warning("cfg", "failed to get save file name: {}", error);
} else {
log_warning("cfg", "failed to get save file name");
}
}
// clean up
keypads_card_select_done = true;
});
} }
// clean up // in-game version opens ImGui file browser
keypads_card_select_done = true; if (!cfg::CONFIGURATOR_STANDALONE && !this->keypads_card_select_browser[player].IsOpened()) {
}); this->keypads_card_select_browser[player].SetTitle("Card Select");
} this->keypads_card_select_browser[player].SetTypeFilters({".txt", "*"});
// this->keypads_card_select_browser[player].flags_ |= ImGuiFileBrowserFlags_EnterNewFilename;
// in-game version opens ImGui file browser this->keypads_card_select_browser[player].Open();
if (!cfg::CONFIGURATOR_STANDALONE && !this->keypads_card_select_browser[player].IsOpened()) { }
this->keypads_card_select_browser[player].SetTitle("Card Select");
this->keypads_card_select_browser[player].SetTypeFilters({".txt", "*"});
// this->keypads_card_select_browser[player].flags_ |= ImGuiFileBrowserFlags_EnterNewFilename;
this->keypads_card_select_browser[player].Open();
}
}
// clear button
if (!bindings.card_paths[player].empty()) {
ImGui::SameLine();
if (ImGui::Button("Clear")) {
bindings.card_paths[player] = "";
bindings_updated = true;
}
}
// verify card number
auto card_valid = true;
if (this->keypads_card_number[player][0] != 0) {
for (int n = 0; n < 16; n++) {
char c = this->keypads_card_number[player][n];
bool digit = c >= '0' && c <= '9';
bool character_big = c >= 'A' && c <= 'F';
bool character_small = c >= 'a' && c <= 'f';
if (!digit && !character_big && !character_small) {
card_valid = false;
} }
ImGui::SameLine();
if (ImGui::Button("Edit in Notepad")) {
std::filesystem::path path;
if (!bindings.card_paths[player].empty()) {
path = bindings.card_paths[player];
} else {
path = (player > 0) ? "card1.txt" : "card0.txt";
}
// using notepad here in case the file doesn't exist
launch_shell("notepad.exe", path.string().c_str());
}
// clear button
if (!bindings.card_paths[player].empty()) {
ImGui::SameLine();
if (ImGui::Button("Clear")) {
bindings.card_paths[player] = "";
bindings_updated = true;
}
}
// verify card number
this->keypads_card_file_contents_valid[player] = true;
if (this->keypads_card_number[player][0] != 0) {
this->keypads_card_file_contents_valid[player] =
validate_ea_card(this->keypads_card_number[player]);
}
// card number box
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
ImGui::BeginDisabled();
if (this->keypads_card_number[player][0] != 0) {
ImGui::PushStyleColor(
ImGuiCol_Text,
this->keypads_card_file_contents_valid[player] ?
TEXT_COLOR_GREEN : TEXT_COLOR_RED);
ImGui::Text("Card from file: %s", this->keypads_card_number[player]);
ImGui::PopStyleColor();
} else {
ImGui::TextColored(TEXT_COLOR_RED, "Failed to read file.");
}
ImGui::EndDisabled();
} }
}
// card number box ImGui::TreePop();
ImGui::PushStyleColor(ImGuiCol_Text,
card_valid ? ImVec4(1.f, 1.f, 1.f, 1.f) :
ImVec4(1.f, 0.f, 0.f, 1.f));
ImGui::InputTextWithHint("Card Number", "E004010000000000",
this->keypads_card_number[player], sizeof(this->keypads_card_number[0]) - 1,
ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsHexadecimal);
ImGui::PopStyleColor();
// write card after edit
if (ImGui::IsItemDeactivatedAfterEdit()) {
write_card(player);
read_card(1 - player);
}
// help marker
ImGui::SameLine();
ImGui::HelpMarker(
"Click on Generate button to randomize a valid card number and automatically it save to specified file.");
// generate button
ImGui::SameLine();
if (ImGui::Button("Generate")) {
// don't know why this file insists on using 18 chars to store the card ID
char new_card[17];
generate_ea_card(new_card);
strcpy_s(this->keypads_card_number[player], new_card);
write_card(player);
read_card(1 - player);
} }
// render card select browser // render card select browser
@@ -3881,12 +4136,14 @@ namespace overlay::windows {
} }
ImGui::Spacing(); ImGui::Spacing();
ImGui::SeparatorText("NFC card reader status"); ImGui::Separator();
ImGui::Spacing();
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "NFC card reader status");
ImGui::Spacing(); ImGui::Spacing();
if (cfg::CONFIGURATOR_STANDALONE) { if (cfg::CONFIGURATOR_STANDALONE) {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::TextWrapped("Test NFC card readers and card insertions over API"); ImGui::TextWrapped("Test NFC card readers and card insertions over API.");
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker( ImGui::HelpMarker(
"Enable card readers in Advanced tab, under Card Readers section, and restart. " "Enable card readers in Advanced tab, under Card Readers section, and restart. "
@@ -3897,31 +4154,35 @@ namespace overlay::windows {
// show scanned card numbers // show scanned card numbers
for (int player = 0; player < 2; player++) { for (int player = 0; player < 2; player++) {
ImGui::PushID(("CardReaderDisp" + to_string(player)).c_str()); ImGui::PushID(("CardReaderDisp" + to_string(player)).c_str());
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Last card detected for player %i", player + 1); if (ImGui::TreeNodeEx(
fmt::format("Last card detected for player {}", player + 1).c_str(),
ImGuiTreeNodeFlags_DefaultOpen)) {
char card_uid[8]; char card_uid[8];
const bool card_present = eamuse_scanned_card_peek_noninvasive(player, card_uid); const bool card_present = eamuse_scanned_card_peek_noninvasive(player, card_uid);
if (card_present) { if (card_present) {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
const auto card_str = bin2hex(card_uid, 8); const auto card_str = bin2hex(card_uid, 8);
ImGui::Text( ImGui::Text(
"%s %s %s %s", "%s %s %s %s",
card_str.substr(0, 4).c_str(), card_str.substr(0, 4).c_str(),
card_str.substr(4, 4).c_str(), card_str.substr(4, 4).c_str(),
card_str.substr(8, 4).c_str(), card_str.substr(8, 4).c_str(),
card_str.substr(12, 4).c_str() card_str.substr(12, 4).c_str()
); );
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Copy")) { if (ImGui::Button("Copy")) {
clipboard::copy_text(card_str); clipboard::copy_text(card_str);
}
ImGui::SameLine();
if (ImGui::Button("Clear")) {
eamuse_scanned_card_clear(player);
}
} else {
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled("Card not present");
} }
ImGui::SameLine(); ImGui::TreePop();
if (ImGui::Button("Clear")) {
eamuse_scanned_card_clear(player);
}
} else {
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled("Card not present");
} }
ImGui::PopID(); ImGui::PopID();
@@ -3938,7 +4199,9 @@ namespace overlay::windows {
} }
ImGui::Spacing(); ImGui::Spacing();
ImGui::SeparatorText("More tips"); ImGui::Separator();
ImGui::Spacing();
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "More tips");
ImGui::Spacing(); ImGui::Spacing();
ImGui::BeginDisabled(); ImGui::BeginDisabled();
ImGui::TextWrapped("To debug card reader issues, run spice.exe -cfg in command line and check the log."); ImGui::TextWrapped("To debug card reader issues, run spice.exe -cfg in command line and check the log.");
@@ -3948,6 +4211,19 @@ namespace overlay::windows {
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
bool Config::validate_ea_card(char card_number[16]) {
for (int n = 0; n < 16; n++) {
char c = card_number[n];
bool digit = c >= '0' && c <= '9';
bool character_big = c >= 'A' && c <= 'F';
bool character_small = c >= 'a' && c <= 'f';
if (!digit && !character_big && !character_small) {
return false;
}
}
return true;
}
std::string Config::build_option_value_picker_title(const OptionDefinition& definition) { std::string Config::build_option_value_picker_title(const OptionDefinition& definition) {
// need to make these all unique since they are also used as ID // need to make these all unique since they are also used as ID
// if not unique, append ## per ImGui rules to create unique ones // if not unique, append ## per ImGui rules to create unique ones
@@ -3962,6 +4238,8 @@ namespace overlay::windows {
return "File Picker"; return "File Picker";
case OptionPickerType::DirectoryPath: case OptionPickerType::DirectoryPath:
return "Folder Picker"; return "Folder Picker";
case OptionPickerType::Monitor:
return "Monitor Picker";
default: default:
return "Unknown Picker"; return "Unknown Picker";
}; };
@@ -4192,6 +4470,28 @@ namespace overlay::windows {
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
} else if (definition.picker == OptionPickerType::Monitor) {
const auto &monitors = sysutils::enumerate_monitors();
if (monitors.empty()) {
ImGui::TextUnformatted("Failed to fetch monitors. Requires Win7+");
} else {
ImGui::TextUnformatted("Pick from monitors:");
ImGui::SetNextItemWidth(300.f);
if (ImGui::BeginListBox("##monitors")) {
for (const auto &monitor : monitors) {
const bool is_selected = option.value == monitor.display_name;
auto friendly = wchar_to_u8(monitor.friendly_name.c_str());
if (ImGui::Selectable(
fmt::format("{} ({})", monitor.display_name, friendly).c_str(),
is_selected)) {
option.value = monitor.display_name;
}
}
ImGui::EndListBox();
}
}
} else { } else {
ImGui::TextUnformatted("No picker available for this option. How did you get here?"); ImGui::TextUnformatted("No picker available for this option. How did you get here?");
} }
@@ -4213,17 +4513,19 @@ namespace overlay::windows {
// render table // render table
// tables must share the same ID to have synced column settings // tables must share the same ID to have synced column settings
if (ImGui::BeginTable("OptionsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) { const int num_columns = (filter != nullptr) ? 3 : 2;
ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch); if (ImGui::BeginTable("OptionsTable", num_columns, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn( auto widget_col_width = 0;
"CMD Line Parameter", if (filter != nullptr) {
ImGuiTableColumnFlags_WidthFixed, ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(160));
overlay::apply_scaling(216)); ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn( widget_col_width = overlay::apply_scaling(220);
"Setting", } else {
ImGuiTableColumnFlags_WidthFixed, ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch);
overlay::apply_scaling(240)); widget_col_width = overlay::apply_scaling(264);
}
ImGui::TableSetupColumn("Setting", ImGuiTableColumnFlags_WidthFixed, widget_col_width);
// iterate options // iterate options
options_count = 0; options_count = 0;
for (auto &option : *options) { for (auto &option : *options) {
@@ -4271,10 +4573,24 @@ namespace overlay::windows {
ImGui::PushID(&option); ImGui::PushID(&option);
ImGui::TableNextRow(); ImGui::TableNextRow();
// category (only shown for search results)
if (filter != nullptr) {
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
if (definition.category.empty()) {
ImGui::TextDisabled("-");
} else {
ImGui::TextDisabled(definition.category.c_str());
}
}
// option name // option name
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Indent(INDENT); if (filter == nullptr) {
// add indent to make options align under category header
ImGui::Indent(INDENT);
}
if (option.is_active()) { if (option.is_active()) {
// active option // active option
if (option.disabled || definition.disabled) { if (option.disabled || definition.disabled) {
@@ -4290,21 +4606,22 @@ namespace overlay::windows {
// normal text // normal text
ImGui::TextUnformatted(definition.title.c_str()); ImGui::TextUnformatted(definition.title.c_str());
} }
ImGui::Unindent(INDENT); if (filter == nullptr) {
ImGui::Unindent(INDENT);
}
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip(definition.desc.c_str()); ImGui::HelpTooltip(definition.desc.c_str());
} }
// command line parameter // command line parameter
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
std::string param = "-"; std::string param = "-";
if (definition.display_name.empty()) { if (definition.display_name.empty()) {
param += definition.name; param += definition.name;
} else { } else {
param += definition.display_name; param += definition.display_name;
} }
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.f), "%s", param.c_str()); ImGui::SameLine();
ImGui::TextColored(ImVec4(0.27f, 0.27f, 0.27f, 1.f), " %s", param.c_str());
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
const auto help = const auto help =
param + param +
@@ -4325,6 +4642,10 @@ namespace overlay::windows {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
} }
if (definition.type != OptionType::Bool) {
// take up width of column, minus some room for clear button and Pick button
ImGui::SetNextItemWidth(widget_col_width - overlay::apply_scaling(70));
}
switch (definition.type) { switch (definition.type) {
case OptionType::Bool: { case OptionType::Bool: {
bool state = !option.value.empty(); bool state = !option.value.empty();
@@ -4404,7 +4725,7 @@ namespace overlay::windows {
break; break;
} }
case OptionType::Text: { case OptionType::Text: {
char buffer[512]; char buffer[OPTION_INPUT_TEXT_WIDTH];
strncpy(buffer, option.value.c_str(), sizeof(buffer) - 1); strncpy(buffer, option.value.c_str(), sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0'; buffer[sizeof(buffer) - 1] = '\0';
@@ -4499,7 +4820,7 @@ namespace overlay::windows {
if (option.disabled && !definition.disabled) { if (option.disabled && !definition.disabled) {
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker( ImGui::HelpMarker(
"This option can not be edited because it was overriden by command-line options.\n" "This option cannot be edited because it was overriden by command-line options.\n"
"Run spicecfg.exe to configure the options and then run spice(64).exe directly."); "Run spicecfg.exe to configure the options and then run spice(64).exe directly.");
} }
@@ -4541,6 +4862,9 @@ namespace overlay::windows {
} }
} }
// row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable();
// next item // next item
ImGui::PopID(); ImGui::PopID();
} }
@@ -5053,6 +5377,7 @@ namespace overlay::windows {
} }
} }
ImGui::InvisibleTableRowSelectable();
ImGui::PopID(); ImGui::PopID();
} }
+8 -2
View File
@@ -87,13 +87,16 @@ namespace overlay::windows {
bool auto_match_soft_enabled = true; bool auto_match_soft_enabled = true;
bool auto_match_p2 = false; bool auto_match_p2 = false;
// keypads tab // cards tab
int keypads_selected[2] {}; int keypads_selected[2] {};
char keypads_card_path[2][1024] {}; char keypads_card_path[2][1024] {};
std::thread *keypads_card_select = nullptr; std::thread *keypads_card_select = nullptr;
bool keypads_card_select_done = false; bool keypads_card_select_done = false;
ImGui::FileBrowser keypads_card_select_browser[2]; ImGui::FileBrowser keypads_card_select_browser[2];
char keypads_card_number[2][18] {}; char keypads_card_number[2][18] {};
bool keypads_card_override_valid[2] = { false, false };
bool keypads_card_file_contents_valid[2] = { false, false };
bool keypads_card_overwrite_confirmed[2];
// presets tab // presets tab
std::vector<ControllerTemplate> templates_cache; std::vector<ControllerTemplate> templates_cache;
@@ -135,6 +138,8 @@ namespace overlay::windows {
std::thread *file_picker_thread = nullptr; std::thread *file_picker_thread = nullptr;
bool file_picker_done = false; bool file_picker_done = false;
std::pair<std::string, int> analog_as_button_warning_show_next_frame = { "", 0 };
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1); void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
void build_button( void build_button(
const std::string &name, const std::string &name,
@@ -195,12 +200,13 @@ namespace overlay::windows {
void set_alternating_row_colors(const int row_index); void set_alternating_row_colors(const int row_index);
bool validate_ea_card(char card_number[16]);
public: public:
Config(SpiceOverlay *overlay); Config(SpiceOverlay *overlay);
~Config() override; ~Config() override;
void read_card(int player = -1); void read_card(int player = -1);
void write_card(int player);
void build_content() override; void build_content() override;
}; };
@@ -40,13 +40,17 @@ namespace overlay::windows {
ImGui::Dummy(ImVec2(12, 0)); ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine(); ImGui::SameLine();
ImGui::PushID("P1");
this->draw_buttons(0); this->draw_buttons(0);
ImGui::PopID();
ImGui::SameLine(); ImGui::SameLine();
ImGui::Dummy(ImVec2(12, 0)); ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine(); ImGui::SameLine();
ImGui::PushID("P2");
this->draw_buttons(1); this->draw_buttons(1);
ImGui::PopID();
} }
void DDRIOPanel::draw_buttons(const int p) { void DDRIOPanel::draw_buttons(const int p) {
@@ -844,6 +844,10 @@ namespace overlay::windows {
ImGui::TextUnformatted(patch_checked ? "ON" : "off"); ImGui::TextUnformatted(patch_checked ? "ON" : "off");
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
// row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable();
ImGui::PopID(); ImGui::PopID();
} }
+18 -2
View File
@@ -176,7 +176,7 @@ void rawinput::RawInputManager::input_hwnd_destroy() {
void rawinput::RawInputManager::devices_reload() { void rawinput::RawInputManager::devices_reload() {
this->devices_destruct(); this->devices_destruct();
log_info("rawinput", "reloading devices"); log_info("rawinput", "reloading devices...");
// scan for devices // scan for devices
this->devices_scan_rawinput(); this->devices_scan_rawinput();
@@ -199,6 +199,7 @@ void rawinput::RawInputManager::devices_reload() {
} }
void rawinput::RawInputManager::devices_scan_rawinput(const std::string &device_name) { void rawinput::RawInputManager::devices_scan_rawinput(const std::string &device_name) {
log_misc("rawinput", "scan rawinput devices...");
// get number of devices // get number of devices
UINT device_no = 0; UINT device_no = 0;
@@ -236,6 +237,8 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
return; return;
} }
log_misc("rawinput", "found rawinput device: {}", device_name);
// extract information out of name // extract information out of name
auto device_info = rawinput::RawInputManager::get_device_info(device_name); auto device_info = rawinput::RawInputManager::get_device_info(device_name);
auto device_description = rawinput::RawInputManager::rawinput_get_device_description(device_info, device_name); auto device_description = rawinput::RawInputManager::rawinput_get_device_description(device_info, device_name);
@@ -810,6 +813,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
} }
void rawinput::RawInputManager::devices_scan_midi() { void rawinput::RawInputManager::devices_scan_midi() {
log_misc("rawinput", "scan MIDI devices...");
// add midi devices // add midi devices
auto midi_device_count = midiInGetNumDevs(); auto midi_device_count = midiInGetNumDevs();
@@ -821,6 +825,9 @@ void rawinput::RawInputManager::devices_scan_midi() {
continue; continue;
} }
log_misc("rawinput", "found MIDI device: id {}, name {}, mid {}, pid {}",
midi_device_id, midi_device_caps.szPname, midi_device_caps.wMid, midi_device_caps.wPid);
// open device // open device
HMIDIIN midi_device_handle; HMIDIIN midi_device_handle;
if (midiInOpen(&midi_device_handle, if (midiInOpen(&midi_device_handle,
@@ -920,6 +927,7 @@ void rawinput::RawInputManager::devices_scan_midi() {
} }
void rawinput::RawInputManager::devices_scan_piuio() { void rawinput::RawInputManager::devices_scan_piuio() {
log_misc("rawinput", "scan PIUIO devices...");
// add device to vector first so pointer is valid // add device to vector first so pointer is valid
auto *new_piuio_device = new Device(); auto *new_piuio_device = new Device();
@@ -950,6 +958,8 @@ void rawinput::RawInputManager::devices_scan_piuio() {
} }
void rawinput::RawInputManager::devices_scan_smxstage() { void rawinput::RawInputManager::devices_scan_smxstage() {
log_misc("rawinput", "scan SMX Stage devices...");
auto *new_smxstage_device = new Device(); auto *new_smxstage_device = new Device();
new_smxstage_device->type = SMX_STAGE; new_smxstage_device->type = SMX_STAGE;
new_smxstage_device->name = "smxstage"; new_smxstage_device->name = "smxstage";
@@ -974,6 +984,8 @@ void rawinput::RawInputManager::devices_scan_smxstage() {
} }
void rawinput::RawInputManager::devices_scan_smxdedicab() { void rawinput::RawInputManager::devices_scan_smxdedicab() {
log_misc("rawinput", "scan SMX Dedicated Cabinet devices...");
auto *new_smxdedicab_device = new Device(); auto *new_smxdedicab_device = new Device();
new_smxdedicab_device->type = SMX_DEDICAB; new_smxdedicab_device->type = SMX_DEDICAB;
new_smxdedicab_device->name = "smxdedicab"; new_smxdedicab_device->name = "smxdedicab";
@@ -1218,6 +1230,8 @@ std::string rawinput::RawInputManager::rawinput_get_device_description(const raw
void rawinput::RawInputManager::sextet_register(const std::string &port_name, const std::string &alias, void rawinput::RawInputManager::sextet_register(const std::string &port_name, const std::string &alias,
bool warn) { bool warn) {
log_misc("rawinput", "checking for sextet-stream device on {}...", port_name);
// check for any sextet-stream devices // check for any sextet-stream devices
Device device {}; Device device {};
device.type = SEXTET_OUTPUT; device.type = SEXTET_OUTPUT;
@@ -1258,12 +1272,14 @@ void rawinput::RawInputManager::devices_remove(const std::string &name) {
} }
void rawinput::RawInputManager::devices_register() { void rawinput::RawInputManager::devices_register() {
// check input window // check input window
if (!this->input_hwnd) { if (!this->input_hwnd) {
log_warning("rawinput", "trying to register devices without input window"); log_warning("rawinput", "trying to register devices without input window");
return; return;
} }
log_misc("rawinput", "registering raw input devices...");
// register keyboard // register keyboard
RAWINPUTDEVICE keyboard_device{}; RAWINPUTDEVICE keyboard_device{};
+20
View File
@@ -0,0 +1,20 @@
1 VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "SpiceTools"
VALUE "FileDescription", "SpiceTools DLL Stub"
VALUE "FileVersion", "1.0.0.0"
VALUE "ProductName", "SpiceTools DLL Stub"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 1252
END
END
+106 -1
View File
@@ -9,6 +9,16 @@
#include "peb.h" #include "peb.h"
#include "util/fileutils.h" #include "util/fileutils.h"
#include "util/dependencies.h" #include "util/dependencies.h"
#include "util/unique_plain_ptr.h"
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
std::filesystem::path libutils::module_file_name(HMODULE module) { std::filesystem::path libutils::module_file_name(HMODULE module) {
std::wstring buf; std::wstring buf;
@@ -361,11 +371,106 @@ void libutils::check_duplicate_dlls() {
void libutils::warn_if_dll_exists(const std::string &file_name) { void libutils::warn_if_dll_exists(const std::string &file_name) {
if (fileutils::file_exists(MODULE_PATH / file_name)) { if (fileutils::file_exists(MODULE_PATH / file_name)) {
log_info("libutils", "found user-supplied {} in modules directory", file_name); log_info("libutils", "found user-supplied {} in modules directory", file_name);
libutils::print_dll_info(MODULE_PATH / file_name);
return; return;
} }
const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path(); const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path();
if (fileutils::file_exists(spice_bin_path / file_name)) { if (fileutils::file_exists(spice_bin_path / file_name)) {
log_info("libutils", "found user-supplied {} next to spice executable path", file_name); log_info("libutils", "found user-supplied {} next to spice executable path", file_name);
libutils::print_dll_info(spice_bin_path / file_name);
return; return;
} }
} }
void libutils::print_dll_info(std::filesystem::path filename) {
DWORD handle;
const auto size = GetFileVersionInfoSizeA(filename.string().c_str(), &handle);
if (size == 0) {
log_debug(
"libutils",
"GetFileVersionInfoSizeA failed for {}: {}",
filename.filename().string(),
get_last_error_string());
return;
}
auto data = util::make_unique_plain<VOID>(size);
if (!GetFileVersionInfoA(filename.string().c_str(), handle, size, data.get())) {
log_debug(
"libutils",
"GetFileVersionInfoA failed for {}: {}",
filename.filename().string(),
get_last_error_string());
return;
}
struct LANGANDCODEPAGE {
WORD wLanguage;
WORD wCodePage;
} *lpTranslate = nullptr;
UINT cbTranslate = 0;
if (!VerQueryValueA(data.get(), "\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate)) {
log_debug(
"libutils",
"VerQueryValueA failed for {}: {}",
filename.filename().string(),
get_last_error_string());
return;
}
if (cbTranslate == 0 || lpTranslate == nullptr) {
log_debug(
"libutils",
"VerQueryValueA returned invalid results for {}",
filename.filename().string());
return;
}
// pick language - prefer English
WORD lang = 0, codepage = 0;
for (UINT i = 0; i < cbTranslate / sizeof(*lpTranslate); i++) {
if (lpTranslate[i].wLanguage == 0x0409) { // en-us
lang = lpTranslate[i].wLanguage;
codepage = lpTranslate[i].wCodePage;
break;
}
}
if (lang == 0) {
lang = lpTranslate[0].wLanguage;
codepage = lpTranslate[0].wCodePage;
}
// StringFileInfo helper
auto query_string = [&](const char* key) -> std::string {
std::string subBlock = fmt::format(
"\\StringFileInfo\\{:04x}{:04x}\\{}",
lang, codepage, key
);
char* value = nullptr;
UINT size_out = 0;
if (VerQueryValueA(data.get(), subBlock.c_str(), (LPVOID*)&value, &size_out) && value) {
return value;
}
log_debug(
"libutils",
"VerQueryValueA({}) failed for {}: {}",
subBlock,
filename.filename().string(),
get_last_error_string());
return "";
};
const auto company_name = query_string("CompanyName");
const auto product_name = query_string("ProductName");
const auto version_str = query_string("FileVersion");
log_info(
"libutils",
"DLL info for {}: CompanyName = {}, ProductName = {}, Version = {}",
filename.filename().string(),
company_name.empty() ? "?" : company_name,
product_name.empty() ? "?" : product_name,
version_str.empty() ? "?" : version_str);
}
+2
View File
@@ -77,4 +77,6 @@ namespace libutils {
intptr_t rva2offset(const std::filesystem::path &path, intptr_t rva); intptr_t rva2offset(const std::filesystem::path &path, intptr_t rva);
intptr_t offset2rva(IMAGE_NT_HEADERS *nt_headers, intptr_t offset); intptr_t offset2rva(IMAGE_NT_HEADERS *nt_headers, intptr_t offset);
intptr_t offset2rva(const std::filesystem::path &path, intptr_t offset); intptr_t offset2rva(const std::filesystem::path &path, intptr_t offset);
void print_dll_info(std::filesystem::path filename);
} }
+6 -6
View File
@@ -62,9 +62,9 @@ void show_popup_for_crash() {
text += "Game has crashed.\n\n"; text += "Game has crashed.\n\n";
text += "Check log.txt and look for error messages near the end of file.\n\n"; text += "Check log.txt and look for error messages near the end of file.\n\n";
text += "Unsure what to do next?\n"; text += "Unsure what to do next?\n";
text += " * update spice2x to the latest version\n"; text += " * Update spice2x to the latest version\n";
text += " * check the FAQ on spice2x github wiki\n"; text += " * Check the FAQ on spice2x github wiki\n";
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n"; text += " * Do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds."; text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
show_popup(text); show_popup(text);
} }
@@ -76,9 +76,9 @@ void show_popup_for_fatal_error(std::string message) {
text += message; text += message;
text += "----------------------------------------------------------\n\n"; text += "----------------------------------------------------------\n\n";
text += "Unsure what to do next?\n"; text += "Unsure what to do next?\n";
text += " * update spice2x to the latest version\n"; text += " * Update spice2x to the latest version\n";
text += " * check the FAQ on spice2x github wiki\n"; text += " * Check the FAQ on spice2x github wiki\n";
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n"; text += " * Do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds."; text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
show_popup(text); show_popup(text);
} }
+108
View File
@@ -1,12 +1,15 @@
#include "sysutils.h" #include "sysutils.h"
#include <cstdlib> #include <cstdlib>
#include <mutex>
#define WIN32_NO_STATUS #define WIN32_NO_STATUS
#include <windows.h> #include <windows.h>
#undef WIN32_NO_STATUS #undef WIN32_NO_STATUS
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h"
#if 0 #if 0
#define log_dbug(module, format_str, ...) logger::push( \ #define log_dbug(module, format_str, ...) logger::push( \
@@ -222,6 +225,21 @@ namespace sysutils {
log_dbug("gpuinfo", "{} {} flags : 0x{:x}", prefix.c_str(), index, adapter->StateFlags); log_dbug("gpuinfo", "{} {} flags : 0x{:x}", prefix.c_str(), index, adapter->StateFlags);
if (!is_monitor) { if (!is_monitor) {
// get extended info for better friendly name of monitors
const auto &monitors = enumerate_monitors();
for (const auto& monitor : monitors) {
if (monitor.display_name == adapter->DeviceName) {
const auto friendly = ws2s(monitor.friendly_name.c_str());
log_misc(
"gpuinfo", "{} {} friendly name : {}",
prefix.c_str(),
index,
friendly);
break;
}
}
// resolution, refresh rate
DEVMODEA devmode = {}; DEVMODEA devmode = {};
devmode.dmSize = sizeof(devmode); devmode.dmSize = sizeof(devmode);
if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) { if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) {
@@ -236,11 +254,13 @@ namespace sysutils {
log_misc("gpuinfo", "EnumDisplaySettingsA failed"); log_misc("gpuinfo", "EnumDisplaySettingsA failed");
} }
// primary?
log_misc( log_misc(
"gpuinfo", "{} {} is primary : {}", "gpuinfo", "{} {} is primary : {}",
prefix.c_str(), prefix.c_str(),
index, index,
(adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no"); (adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no");
} }
} }
@@ -298,4 +318,92 @@ namespace sysutils {
return; return;
} }
} }
static std::vector<MonitorEntry> enumerate_monitors_internal() {
// for WinXP, since these are Vista+ or 7+ APIs
const auto user32 = LoadLibraryA("user32.dll");
if (!user32) {
log_warning("graphics", "can't find user32.dll???");
return {};
}
const auto GetDisplayConfigBufferSizes_addr =
reinterpret_cast<decltype(GetDisplayConfigBufferSizes) *>(
GetProcAddress(user32, "GetDisplayConfigBufferSizes"));
const auto QueryDisplayConfig_addr =
reinterpret_cast<decltype(QueryDisplayConfig) *>(
GetProcAddress(user32, "QueryDisplayConfig"));
const auto DisplayConfigGetDeviceInfo_addr =
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
if (GetDisplayConfigBufferSizes_addr == nullptr ||
QueryDisplayConfig_addr == nullptr ||
DisplayConfigGetDeviceInfo_addr == nullptr) {
log_warning("sysutils", "OS does not support display config APIs");
return {};
}
UINT32 path_count = 0;
UINT32 mode_count = 0;
auto status = GetDisplayConfigBufferSizes_addr(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count);
if (status != ERROR_SUCCESS) {
log_warning("sysutils", "GetDisplayConfigBufferSizes failed: {}", status);
return {};
}
std::vector<DISPLAYCONFIG_PATH_INFO> paths(path_count);
std::vector<DISPLAYCONFIG_MODE_INFO> modes(mode_count);
status = QueryDisplayConfig_addr(
QDC_ONLY_ACTIVE_PATHS,
&path_count,
paths.data(),
&mode_count,
modes.data(),
nullptr);
if (status != ERROR_SUCCESS) {
log_warning("sysutils", "QueryDisplayConfig failed: {}", status);
return {};
}
// shrink to actual returned items
paths.resize(path_count);
modes.resize(mode_count);
std::vector<MonitorEntry> result;
for (const auto& path : paths) {
MonitorEntry entry;
// device ID (\\.\DISPLAYn)
DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {};
source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
source_name.header.size = sizeof(source_name);
source_name.header.adapterId = path.sourceInfo.adapterId;
source_name.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo_addr(&source_name.header) != ERROR_SUCCESS) {
continue;
}
entry.display_name = ws2s(std::wstring(source_name.viewGdiDeviceName));
// friendly name
DISPLAYCONFIG_TARGET_DEVICE_NAME target_name = {};
target_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
target_name.header.size = sizeof(target_name);
target_name.header.adapterId = path.targetInfo.adapterId;
target_name.header.id = path.targetInfo.id;
if (DisplayConfigGetDeviceInfo_addr(&target_name.header) == ERROR_SUCCESS) {
entry.friendly_name = target_name.monitorFriendlyDeviceName;
} else {
entry.friendly_name = L"(unknown)";
}
// done
result.emplace_back(entry);
}
return result;
}
const std::vector<MonitorEntry>& enumerate_monitors() {
static const std::vector<MonitorEntry> monitors = enumerate_monitors_internal();
return monitors;
}
} }
+9 -1
View File
@@ -1,10 +1,18 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector>
#include <cstdint> #include <cstdint>
namespace sysutils { namespace sysutils {
void print_smbios(); void print_smbios();
void print_gpus(); void print_gpus();
void print_os(); void print_os();
}
struct MonitorEntry {
std::string display_name; // \\.\DISPLAY1
std::wstring friendly_name; // Dell S2204T; wstring so that it can be converted to utf8 or string
};
const std::vector<MonitorEntry> &enumerate_monitors();
}