diff --git a/CMakeLists.txt b/CMakeLists.txt index 803c30de..fd21b948 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,6 @@ option(HELLOIMGUI_HAS_NULL "Null rendering backend" OFF) # for testing and remo option(HELLOIMGUI_NULL_BACKEND "Use Null rendering/platform backend" OFF) # # do not remove this line (used by the script that generates the documentation) - #------------------------------------------------------------------------------ # Options / Freetype #------------------------------------------------------------------------------ @@ -297,6 +296,12 @@ mark_as_advanced(HELLO_IMGUI_IMGUI_SHARED) # option(HELLOIMGUI_USE_IMGUI_CMAKE_PACKAGE "Use imgui from cmake package (provided by vcpkg for example) * Must be on docking branch*" OFF) +#------------------------------------------------------------------------------ +# Options / Remoting +#------------------------------------------------------------------------------ +# Using https://github.com/sammyfreg/netImgui, you can use HelloImGui with remote rendering +# (Unsupported, and highly experimental. Requires a specific fork of netImgui) +option(HELLOIMGUI_WITH_NETIMGUI "Use netImgui for remote rendering" OFF) ############################################################################### # End of options diff --git a/CMakePresets.json b/CMakePresets.json index f96aafca..80848259 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -32,13 +32,6 @@ "HELLOIMGUI_HAS_NULL": "ON" } }, - { - "name": "build_null_backend", - "description": "Build with null backend.", - "cacheVariables": { - "HELLOIMGUI_NULL_BACKEND": "ON" - } - }, { "name": "build_vcpkg_default", "description": "Build with vcpkg default settings (opengl3 + sdl and glfw). You need to create a build directory and run from it", diff --git a/docs_src/generate_book.sh b/docs_src/_generate_book_impl.sh similarity index 100% rename from docs_src/generate_book.sh rename to docs_src/_generate_book_impl.sh diff --git a/docs_src/_toc.yml b/docs_src/_toc.yml index 8d260ba6..fce2e527 100644 --- a/docs_src/_toc.yml +++ b/docs_src/_toc.yml @@ -6,7 +6,7 @@ root: intro chapters: - file: get_started - - file: doc_api - file: doc_params + - file: doc_api - file: build diff --git a/docs_src/process_md_docs.py b/docs_src/process_md_docs.py new file mode 120000 index 00000000..1a883ac4 --- /dev/null +++ b/docs_src/process_md_docs.py @@ -0,0 +1 @@ +../tools/doc/process_md_docs.py \ No newline at end of file diff --git a/external/.gitignore b/external/.gitignore index fea25349..972ae40c 100644 --- a/external/.gitignore +++ b/external/.gitignore @@ -1,3 +1,4 @@ SDL SDL2-*/ qtimgui/ +netImgui/ diff --git a/hello_imgui_cmake/hello_imgui_build_lib.cmake b/hello_imgui_cmake/hello_imgui_build_lib.cmake index f53da250..cf2ed055 100644 --- a/hello_imgui_cmake/hello_imgui_build_lib.cmake +++ b/hello_imgui_cmake/hello_imgui_build_lib.cmake @@ -1031,6 +1031,42 @@ function(_him_fetch_glfw_if_needed) endfunction() +################################################################################################### +# Remoting with https://github.com/sammyfreg/netImgui: API = him_with_netimgui +################################################################################################### +function(him_with_netimgui) + target_compile_definitions(${HELLOIMGUI_TARGET} PUBLIC HELLOIMGUI_WITH_NETIMGUI) + +# message(STATUS "HelloImGui: downloading and building netImgui") +# include(FetchContent) +# # Set(FETCHCONTENT_QUIET FALSE) +# FetchContent_Declare(net_imgui +# GIT_REPOSITORY https://github.com/pthom/netImgui.git +# GIT_TAG cmake_multiplatform +# GIT_PROGRESS TRUE +# ) +# FetchContent_MakeAvailable(net_imgui) + + + # Add netImgui to the project + set(NETIMGUI_DIR ${HELLOIMGUI_BASEPATH}/external/netImgui CACHE STRING "" FORCE) + set(NETIMGUI_BUILD_IMGUI OFF CACHE BOOL "" FORCE) + set(NETIMGUI_BUILD_CLIENT ON CACHE BOOL "" FORCE) + set(NETIMGUI_BUILD_SERVER_LIB ON CACHE BOOL "" FORCE) + + #set(NETIMGUI_BUILD_SERVER_APP_SOKOL ON CACHE BOOL "" FORCE) + set(NETIMGUI_BUILD_SERVER_APP_SOKOL OFF CACHE BOOL "" FORCE) + set(NETIMGUI_SERVER_APP_BACKEND_GLFW_GL3 ON CACHE BOOL "" FORCE) + + set(NETIMGUI_BUILD_SAMPLES OFF CACHE BOOL "" FORCE) + add_subdirectory(${NETIMGUI_DIR} netimgui) + + target_link_libraries(${HELLOIMGUI_TARGET} PUBLIC net_imgui_client) + him_add_installable_dependency(net_imgui_client) +endfunction() + + + ################################################################################################### # Miscellaneous: API = him_add_misc_options ################################################################################################### @@ -1079,12 +1115,14 @@ function(him_log_configuration) # set imgui_source_dir to the relative path of HELLOIMGUI_IMGUI_SOURCE_DIR versus this project file(RELATIVE_PATH imgui_source_dir ${HELLOIMGUI_BASEPATH} ${HELLOIMGUI_IMGUI_SOURCE_DIR}) + # Use netImGui: ${HELLOIMGUI_WITH_NETIMGUI} + set(msg " =========================================================================== Hello ImGui build options: =========================================================================== - Platform Backend(s): ${active_platform_backends} - Rendering Backend(s): ${active_rendering_backends} + Platform Backend(s): ${active_platform_backends} + Rendering Backend(s): ${active_rendering_backends} --------------------------------------------------------------------------- Options: HELLOIMGUI_USE_FREETYPE: ${HELLOIMGUI_USE_FREETYPE} (${HELLOIMGUI_FREETYPE_SELECTED_INFO}) @@ -1185,6 +1223,10 @@ function(him_main_add_hello_imgui_library) target_compile_definitions(${HELLOIMGUI_TARGET} PUBLIC HELLOIMGUI_HAS_NULL) endif() + if (HELLOIMGUI_WITH_NETIMGUI) + him_with_netimgui() + endif() + him_add_apple_options() him_add_linux_options() him_add_windows_options() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e57ac50e..c979011f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,3 +6,4 @@ endif() if(HELLOIMGUI_BUILD_TESTS) add_subdirectory(hello_imgui_tests) endif() +add_subdirectory(hello_imgui_remote) diff --git a/src/hello_imgui/app_window_params.h b/src/hello_imgui/app_window_params.h index 1d2931a6..4c467f83 100644 --- a/src/hello_imgui/app_window_params.h +++ b/src/hello_imgui/app_window_params.h @@ -62,6 +62,8 @@ struct WindowGeometry // Size of the application window // used if fullScreenMode==NoFullScreen and sizeAuto==false. Default=(800, 600) + // The size will be handled as if it was specified for a 96PPI screen + // (i.e. a given size will correspond to the same physical size on different screens, whatever their DPI) ScreenSize size = DefaultWindowSize; // If sizeAuto=true, adapt the app window size to the presented widgets. diff --git a/src/hello_imgui/doc_api.md b/src/hello_imgui/doc_api.md index da526d15..75b4470f 100644 --- a/src/hello_imgui/doc_api.md +++ b/src/hello_imgui/doc_api.md @@ -57,8 +57,16 @@ float EmSize(float nbLines); # Load fonts See [hello_imgui_font.h](https://github.com/pthom/hello_imgui/blob/master/src/hello_imgui/hello_imgui_font.h). ```cpp + + // When loading fonts, use + // HelloImGui::LoadFont(..) + // or + // HelloImGui::LoadDpiResponsiveFont() // - // When loading fonts, use HelloImGui::LoadFont(fontFilename, fontSize, fontLoadingParams) + // Use these functions instead of ImGui::GetIO().Fonts->AddFontFromFileTTF(), + // because they will automatically adjust the font size to account for HighDPI, + // and will help you to get consistent font size across different OSes. + // // Font loading parameters: several options are available (color, merging, range, ...) struct FontLoadingParams @@ -101,14 +109,32 @@ See [hello_imgui_font.h](https://github.com/pthom/hello_imgui/blob/master/src/he ImFontConfig fontConfigFontAwesome = ImFontConfig(); }; - // When loading fonts, use HelloImGui::LoadFont(FontLoadingParams) - // =============================================================== - // instead of ImGui::GetIO().Fonts->AddFontFromFileTTF(), because it will - // automatically adjust the font size to account for HighDPI, and will spare - // you headaches when trying to get consistent font size across different OSes. - // see FontLoadingParams and ImFontConfig - ImFont* LoadFont(const std::string & fontFilename, float fontSize, - const FontLoadingParams & params = {}); + // A font that will be automatically resized to account for changes in DPI + // Use LoadAdaptiveFont instead of LoadFont to get this behavior. + // Fonts loaded with LoadAdaptiveFont will be reloaded during execution + // if ImGui::GetIO().FontGlobalScale is changed. + struct FontDpiResponsive + { + ImFont* font = nullptr; + std::string fontFilename; + float fontSize = 0.f; + FontLoadingParams fontLoadingParams; + }; + + + // Loads a font with the specified parameters + // (this font will not adapt to DPI changes after startup) + ImFont* LoadFont( + const std::string & fontFilename, float fontSize, + const FontLoadingParams & params = {}); + + // Loads a font with the specified parameters + // This font will adapt to DPI changes after startup. + // Only fonts loaded with LoadAdaptiveFont will adapt to DPI changes: + // avoid mixing LoadFont/LoadFontDpiResponsive) + FontDpiResponsive* LoadFontDpiResponsive( + const std::string & fontFilename, float fontSize, + const FontLoadingParams & params = {}); ``` @@ -266,6 +292,10 @@ ImGuiTestEngine* GetImGuiTestEngine(); // "Sdl - Vulkan" std::string GetBackendDescription(); +// `ChangeWindowSize(const ScreenSize &windowSize)`: sets the window size +// (useful if you want to change the window size during execution) +void ChangeWindowSize(const ScreenSize &windowSize); + ``` ---- @@ -408,42 +438,100 @@ void ShowAppMenu(RunnerParams & runnerParams); # Handling screens with high DPI -_Note: This part is relevant only for more advanced usages. If you use `HelloImGui::LoadFont()`, and always use `HelloImGui::EmToVec2()` to place widgets, you do not need to worry about DPI handling_ -## Details on DPI handling on different OS +_Note: This part is relevant only for more advanced usages. If you use `HelloImGui::LoadFont()`, + and always use `HelloImGui::EmToVec2()` to place widgets, you do not need to worry about DPI handling_ + +## OS specificities + +There are several important things to know about high-DPI handling within Hello ImGui and Dear ImGui: + +1. (virtual) screen coordinates vs (physical) pixels +2. DisplayFramebufferScale: Frame buffer size vs window size +3. FontGlobalScale: display-time font scaling factor +4. How to load fonts with the correct size +5. How to get similar window sizes on different OSes/DPI + + +## Screen coordinates + +Screen coordinates are the coordinates you use to place and size windows on the screen. + +**Screen coordinates do not always correspond to physical pixels** + +- On macOS/iOS retina screens, a screen coordinate corresponds typically + to 2x2 physical pixels (but this may vary if you change the display scaling) +- On most Linux distributions, whenever there is a high DPI screen + you can set the display scale. For example if you set the scale to 300%, + then a screen coordinate will correspond to 3x3 physical pixels +- On Windows, there are two possible situations: + - If the application is DPI aware, a screen coordinate corresponds to 1x1 physical pixel, + and you can use the full extent of your screen resolution. + - If the application is not DPI aware, a screen coordinate may correspond to 2x2 physical pixels + (if the display scaling is set to 200% for example). However, the rendering of your application + will be blurry and will not use the full extent of your screen resolution. + - Notes: + - Applications created with HelloImGui are DPI aware by default (when using glfw and sdl backends). + - SDL applications are normally not DPI aware. However, HelloImGui makes them DPI aware. + + +## DisplayFramebufferScale +`DisplayFramebufferScale` is the ratio between the frame buffer size and the window size. + +The frame buffer size is the size of the internal buffer used by the rendering backend. +It might be bigger than the actual window size. +`ImVec2 ImGui::GetIO().DisplayFramebufferScale` is a factor by which the frame buffer size is bigger than the window size. +It is set by the platform backend after it was initialized, and typically reflects the scaling ratio between +physical pixels and screen coordinates. + +Under windows, it will always be (1,1). Under macOS / linux, it will reflect the current display scaling. +It will typically be (2,2) on a macOS retina screen. + +Notes: +- You cannot change DisplayFramebufferScale manually, it will be reset at each new frame, by asking the platform backend. + + +## FontGlobalScale + +`ImGui::GetIO().FontGlobalScale` is a factor by which fonts glyphs should be scaled at rendering time. +It is typically 1 on windows, and 0.5 on macOS retina screens. -Let's consider screen whose physical pixel resolution is 3600x2000, but which will displayed with a scaling factor of 200%, so that widgets do not look too small on it. -The way it is handled depends on the OS: -- On MacOS, the screen will be seen as having a resolution of 1800x1000, and the OS handles the resizing by itself. -- On Linux, and on Windows if the application is DPI aware, the screen will be seen as having a resolution of 3600x2000. -- On Windows if the application is not DPI aware, the screen will be seen as having a resolution of 1800x1000 +## How to load fonts with the correct size -By default, if using the glfw backend, applications will be Dpi aware under windows. -Sdl applications are normally not Dpi aware. However HelloImGui makes them Dpi aware when using the sdl backend. +### Using HelloImGui (recommended) +[`HelloImGui::LoadFont()` and `HelloImGui::LoadFontDpiResponsive`](https://pthom.github.io/hello_imgui/book/doc_api.html#load-fonts) will load fonts + with the correct size, taking into account the DPI scaling. -## Dpi aware Font scaling +### Using Dear ImGui +`ImGui::GetIO().Fonts->AddFontFromFileTTF()` loads a font with a given size, in *physical pixels*. -`HelloImGui::LoadFont()` will load fonts with the correct size, taking into account the DPI scaling. +If for example, DisplayFramebufferScale is (2,2), and you load a font with a size of 16, it will by default be rendered + with size of 16 *virtual screen coordinate pixels* (i.e. 32 physical pixels). This will lead to blurry text. +To solve this, you should load your font with a size of 16 *virtual screen coordinate pixels* (i.e. 32 physical pixels), +and set `ImGui::GetIO().FontGlobalScale` to 0.5. -If you prefer to use `ImGui::GetIO().Fonts->AddFontFromFileTTF()`, there are two things to know: +Helpers if using `ImGui::GetIO().Fonts->AddFontFromFileTTF()`: +- `HelloImGui::ImGuiDefaultFontGlobalScale()` returns the default value that should be stored inside `ImGui::GetIO().FontGlobalScale`. +- `HelloImGui::DpiFontLoadingFactor()` returns a factor by which you shall multiply your font sizes when loading them. -1. You should adjust `ImGui::GetIO().FontGlobalScale`: -Under windows and linux, it should be is 1: no rescaling should be done by ImGui. -Under macOS and emscripten, it may need to bet set to 0.5 (for example it will be 0.5 if the dpi scaling is 200% -on a macOS retina screen) +## Reproducible physical window sizes (in mm or inches) -`HelloImGui::ImGuiDefaultFontGlobalScale()` returns the default value that should be stored inside `ImGui::GetIO().FontGlobalScale`. +### Using HelloImGui +Simply specify a window size that corresponds to theoretical 96 PPI screen (inside `RunnerParams.appWindowParams.windowGeometry.size`) +### Using your own code to create the backend window +If you prefer to create the window by yourself, its physical size in millimeters may vary widely, +depending on the OS and the current screen DPI setting. +Typically under Windows, your window may appear to be very small if your screen is high DPI. -2. You should adjust the font size when loading a font: +To get a similar window size on different OSes/DPI, you should multiply the window size by `HelloImGui::DpiWindowSizeFactor()`. -`HelloImGui::DpiFontLoadingFactor()` returns a factor by which you shall multiply your font sizes when loading them. +Note: DpiWindowSizeFactor() is equal to `CurrentScreenPixelPerInch / 96` under windows and linux, and always 1 under macOS. -`HelloImGui::DpiFontLoadingFactor()` corresponds to: -`DpiWindowSizeFactor() * 1.f / ImGui::GetIO().FontGlobalScale` +## Fine tune DPI Handling -where DpiWindowSizeFactor() is equal to `CurrentScreenPixelPerInch / 96` -under windows and linux, 1 under macOS +See [`HelloImGui::DpiAwareParams`](https://pthom.github.io/hello_imgui/book/doc_params.html#dpi-aware-params) +for more information on how to fine tune DPI handling when using Hello ImGui. diff --git a/src/hello_imgui/doc_api.src.md b/src/hello_imgui/doc_api.src.md index 15db606d..df8fd9ba 100644 --- a/src/hello_imgui/doc_api.src.md +++ b/src/hello_imgui/doc_api.src.md @@ -100,42 +100,4 @@ See [hello_imgui.h](https://github.com/pthom/hello_imgui/blob/master/src/hello_i # Handling screens with high DPI -_Note: This part is relevant only for more advanced usages. If you use `HelloImGui::LoadFont()`, and always use `HelloImGui::EmToVec2()` to place widgets, you do not need to worry about DPI handling_ - -## Details on DPI handling on different OS - -Let's consider screen whose physical pixel resolution is 3600x2000, but which will displayed with a scaling factor of 200%, so that widgets do not look too small on it. - -The way it is handled depends on the OS: -- On MacOS, the screen will be seen as having a resolution of 1800x1000, and the OS handles the resizing by itself. -- On Linux, and on Windows if the application is DPI aware, the screen will be seen as having a resolution of 3600x2000. -- On Windows if the application is not DPI aware, the screen will be seen as having a resolution of 1800x1000 - -By default, if using the glfw backend, applications will be Dpi aware under windows. -Sdl applications are normally not Dpi aware. However HelloImGui makes them Dpi aware when using the sdl backend. - - -## Dpi aware Font scaling - -`HelloImGui::LoadFont()` will load fonts with the correct size, taking into account the DPI scaling. - -If you prefer to use `ImGui::GetIO().Fonts->AddFontFromFileTTF()`, there are two things to know: - -1. You should adjust `ImGui::GetIO().FontGlobalScale`: - -Under windows and linux, it should be is 1: no rescaling should be done by ImGui. -Under macOS and emscripten, it may need to bet set to 0.5 (for example it will be 0.5 if the dpi scaling is 200% -on a macOS retina screen) - -`HelloImGui::ImGuiDefaultFontGlobalScale()` returns the default value that should be stored inside `ImGui::GetIO().FontGlobalScale`. - - -2. You should adjust the font size when loading a font: - -`HelloImGui::DpiFontLoadingFactor()` returns a factor by which you shall multiply your font sizes when loading them. - -`HelloImGui::DpiFontLoadingFactor()` corresponds to: -`DpiWindowSizeFactor() * 1.f / ImGui::GetIO().FontGlobalScale` - -where DpiWindowSizeFactor() is equal to `CurrentScreenPixelPerInch / 96` -under windows and linux, 1 under macOS +@import "dpi_aware.h" {md_id=HandlingScreenHighDPI} diff --git a/src/hello_imgui/doc_params.md b/src/hello_imgui/doc_params.md index c6490bbe..d93376ff 100644 --- a/src/hello_imgui/doc_params.md +++ b/src/hello_imgui/doc_params.md @@ -1,6 +1,27 @@ # Application parameters -_RunnerParams_ contains all the settings and callbacks in order to run an application. + +__HelloImGui::Run()__ will run an application with a single call. + +Three signatures are provided: + +* `HelloImGui::Run(RunnerParams &)`: full signature, the most customizable version. + Runs an application whose params and Gui are provided by runnerParams. + +* `HelloImGui::Run(const SimpleRunnerParams&)`: + Runs an application, using simpler params. + +* `HelloImGui::Run(guiFunction, windowTitle, windowSize, windowSizeAuto=false, restoreLastWindowGeometry=false, fpsIdle=10)` + Runs an application, by providing the Gui function, the window title, etc. + +Although the API is extremely simple, it is highly customizable, and you can set many options by filling +the elements in the `RunnerParams` struct, or in the simpler `SimpleRunnerParams`. + +__HelloImGui::GetRunnerParams()__ will return the runnerParams of the current application. + + +# Diagram + The diagram below summarize all the possible settings and callbacks (which are explained in detail later in this document). [![diagram](https://raw.githubusercontent.com/pthom/hello_imgui/master/src/hello_imgui/doc_src/hello_imgui_diagram.jpg)](https://raw.githubusercontent.com/pthom/hello_imgui/master/src/hello_imgui/doc_src/hello_imgui_diagram.jpg) @@ -49,6 +70,8 @@ struct SimpleRunnerParams // `windowSize`: _ScreenSize, default={800, 600}_. // Size of the window + // The size will be handled as if it was specified for a 96PPI screen + // (i.e. a given size will correspond to the same physical size on different screens, whatever their DPI) ScreenSize windowSize = DefaultWindowSize; // `fpsIdle`: _float, default=9_. @@ -131,7 +154,6 @@ struct RunnerParams RendererBackendType rendererBackendType = RendererBackendType::FirstAvailable; - // --------------- Settings ------------------- // `iniFolderType`: _IniFolderType, default = IniFolderType::CurrentFolder_ @@ -194,6 +216,9 @@ struct RunnerParams // Set the application refresh rate // (only used on emscripten: 0 stands for "let the app or the browser decide") int emscripten_fps = 0; + + // Parameters for Remote display (experimental, unsupported) + RemoteParams remoteParams; }; ``` @@ -212,6 +237,7 @@ enum class PlatformBackendType FirstAvailable, Glfw, Sdl, + Null }; // Rendering backend type (OpenGL3, Metal, Vulkan, DirectX11, DirectX12) @@ -224,6 +250,7 @@ enum class RendererBackendType Vulkan, DirectX11, DirectX12, + Null }; ``` @@ -580,6 +607,8 @@ struct WindowGeometry // Size of the application window // used if fullScreenMode==NoFullScreen and sizeAuto==false. Default=(800, 600) + // The size will be handled as if it was specified for a 96PPI screen + // (i.e. a given size will correspond to the same physical size on different screens, whatever their DPI) ScreenSize size = DefaultWindowSize; // If sizeAuto=true, adapt the app window size to the presented widgets. @@ -820,8 +849,9 @@ struct FpsIdling # Dpi Aware Params -See [dpi_aware.h](https://github.com/pthom/hello_imgui/blob/master/src/hello_imgui/dpi_aware.h) +Optionally, DPI parameters can be fine-tuned. For detailed info, see [handling screens with high dpi](https://pthom.github.io/hello_imgui/book/doc_api.html#handling-screens-with-high-dpi) +Source: [dpi_aware.h](https://github.com/pthom/hello_imgui/blob/master/src/hello_imgui/dpi_aware.h) ```cpp // @@ -854,11 +884,15 @@ See [dpi_aware.h](https://github.com/pthom/hello_imgui/blob/master/src/hello_img // dpiWindowSizeFactor=2 // fontRenderingScale=0.5 // +// For more information, see the documentation on DPI handling, here: https://pthom.github.io/hello_imgui/book/doc_api.html#handling-screens-with-high-dpi +// struct DpiAwareParams { // `dpiWindowSizeFactor` - // factor by which window size should be multiplied to get a similar - // visible size on different OSes. + // factor by which window size should be multiplied to get a similar + // physical size on different OSes (as if they were all displayed on a 96 PPI screen). + // This affects the size of native app windows, + // but *not* imgui Windows, and *not* the size of widgets and text. // In a standard environment (i.e. outside of Hello ImGui), an application with a size of 960x480 pixels, // may have a physical size (in mm or inches) that varies depending on the screen DPI, and the OS. // @@ -873,23 +907,36 @@ struct DpiAwareParams float dpiWindowSizeFactor = 0.0f; // `fontRenderingScale` - // factor (that is either 1 or < 1.) by which fonts glyphs should be - // scaled at rendering time. - // On macOS retina screens, it will be 0.5, since macOS APIs hide - // the real resolution of the screen. + // factor (that is either 1 or < 1.) by which fonts glyphs should be scaled at rendering time. + // On macOS retina screens, it will be 0.5, since macOS APIs hide the real resolution of the screen. + // Changing this value will *not* change the visible font size on the screen, however it will + // affect the size of the loaded glyphs. + // For example, if fontRenderingScale=0.5 (which is the default on a macOS retina screen), + // a font size of 16 will be loaded as if it was 32, and will be rendered at half size. + // This leads to a better rendering quality on some platforms. + // (This parameter will be used to set ImGui::GetIO().FontGlobalScale at startup) float fontRenderingScale = 0.0f; + // `onlyUseFontDpiResponsive` + // If true, guarantees that only HelloImGui::LoadDpiResponsiveFont will be used to load fonts. + // (also for the default font) + bool onlyUseFontDpiResponsive = false; + // `dpiFontLoadingFactor` - // factor by which font size should be multiplied at loading time to get a similar - // visible size on different OSes. - // The size will be equivalent to a size given for a 96 PPI screen - float DpiFontLoadingFactor() { return dpiWindowSizeFactor / fontRenderingScale;}; + // factor by which font size should be multiplied at loading time to get a similar + // visible size on different OSes. + // The size will be equivalent to a size given for a 96 PPI screen + float DpiFontLoadingFactor() const { + float r = dpiWindowSizeFactor / fontRenderingScale; + return r; + }; }; // ---------------------------------------------------------------------------- ``` + ---- # Docking diff --git a/src/hello_imgui/doc_params.src.md b/src/hello_imgui/doc_params.src.md index cc273de7..383a059e 100644 --- a/src/hello_imgui/doc_params.src.md +++ b/src/hello_imgui/doc_params.src.md @@ -1,6 +1,9 @@ # Application parameters -_RunnerParams_ contains all the settings and callbacks in order to run an application. +@import "hello_imgui.h" {md_id=HelloImGui::Run} + +# Diagram + The diagram below summarize all the possible settings and callbacks (which are explained in detail later in this document). [![diagram](https://raw.githubusercontent.com/pthom/hello_imgui/master/src/hello_imgui/doc_src/hello_imgui_diagram.jpg)](https://raw.githubusercontent.com/pthom/hello_imgui/master/src/hello_imgui/doc_src/hello_imgui_diagram.jpg) @@ -120,12 +123,14 @@ See [runner_params.h](https://github.com/pthom/hello_imgui/blob/master/src/hello # Dpi Aware Params -See [dpi_aware.h](https://github.com/pthom/hello_imgui/blob/master/src/hello_imgui/dpi_aware.h) +Optionally, DPI parameters can be fine-tuned. For detailed info, see [handling screens with high dpi](https://pthom.github.io/hello_imgui/book/doc_api.html#handling-screens-with-high-dpi) +Source: [dpi_aware.h](https://github.com/pthom/hello_imgui/blob/master/src/hello_imgui/dpi_aware.h) ```cpp @import "dpi_aware.h" {md_id=DpiAwareParams} ``` + ---- # Docking diff --git a/src/hello_imgui/dpi_aware.h b/src/hello_imgui/dpi_aware.h index cd48d60a..41939f90 100644 --- a/src/hello_imgui/dpi_aware.h +++ b/src/hello_imgui/dpi_aware.h @@ -35,11 +35,15 @@ namespace HelloImGui // dpiWindowSizeFactor=2 // fontRenderingScale=0.5 // +// For more information, see the documentation on DPI handling, here: https://pthom.github.io/hello_imgui/book/doc_api.html#handling-screens-with-high-dpi +// struct DpiAwareParams { // `dpiWindowSizeFactor` - // factor by which window size should be multiplied to get a similar - // visible size on different OSes. + // factor by which window size should be multiplied to get a similar + // physical size on different OSes (as if they were all displayed on a 96 PPI screen). + // This affects the size of native app windows, + // but *not* imgui Windows, and *not* the size of widgets and text. // In a standard environment (i.e. outside of Hello ImGui), an application with a size of 960x480 pixels, // may have a physical size (in mm or inches) that varies depending on the screen DPI, and the OS. // @@ -54,17 +58,29 @@ struct DpiAwareParams float dpiWindowSizeFactor = 0.0f; // `fontRenderingScale` - // factor (that is either 1 or < 1.) by which fonts glyphs should be - // scaled at rendering time. - // On macOS retina screens, it will be 0.5, since macOS APIs hide - // the real resolution of the screen. + // factor (that is either 1 or < 1.) by which fonts glyphs should be scaled at rendering time. + // On macOS retina screens, it will be 0.5, since macOS APIs hide the real resolution of the screen. + // Changing this value will *not* change the visible font size on the screen, however it will + // affect the size of the loaded glyphs. + // For example, if fontRenderingScale=0.5 (which is the default on a macOS retina screen), + // a font size of 16 will be loaded as if it was 32, and will be rendered at half size. + // This leads to a better rendering quality on some platforms. + // (This parameter will be used to set ImGui::GetIO().FontGlobalScale at startup) float fontRenderingScale = 0.0f; + // `onlyUseFontDpiResponsive` + // If true, guarantees that only HelloImGui::LoadDpiResponsiveFont will be used to load fonts. + // (also for the default font) + bool onlyUseFontDpiResponsive = false; + // `dpiFontLoadingFactor` - // factor by which font size should be multiplied at loading time to get a similar - // visible size on different OSes. - // The size will be equivalent to a size given for a 96 PPI screen - float DpiFontLoadingFactor() { return dpiWindowSizeFactor / fontRenderingScale;}; + // factor by which font size should be multiplied at loading time to get a similar + // visible size on different OSes. + // The size will be equivalent to a size given for a 96 PPI screen + float DpiFontLoadingFactor() const { + float r = dpiWindowSizeFactor / fontRenderingScale; + return r; + }; }; // ---------------------------------------------------------------------------- @@ -122,3 +138,108 @@ float DpiWindowSizeFactor(); float ImGuiDefaultFontGlobalScale(); } // namespace HelloImGui + +// ---------------------------------------------------------------------------- +// Handling screens with high DPI +// ---------------------------------------------------------------------------- +/* +@@md#HandlingScreenHighDPI + +_Note: This part is relevant only for more advanced usages. If you use `HelloImGui::LoadFont()`, + and always use `HelloImGui::EmToVec2()` to place widgets, you do not need to worry about DPI handling_ + +## OS specificities + +There are several important things to know about high-DPI handling within Hello ImGui and Dear ImGui: + +1. (virtual) screen coordinates vs (physical) pixels +2. DisplayFramebufferScale: Frame buffer size vs window size +3. FontGlobalScale: display-time font scaling factor +4. How to load fonts with the correct size +5. How to get similar window sizes on different OSes/DPI + + +## Screen coordinates + +Screen coordinates are the coordinates you use to place and size windows on the screen. + +**Screen coordinates do not always correspond to physical pixels** + +- On macOS/iOS retina screens, a screen coordinate corresponds typically + to 2x2 physical pixels (but this may vary if you change the display scaling) +- On most Linux distributions, whenever there is a high DPI screen + you can set the display scale. For example if you set the scale to 300%, + then a screen coordinate will correspond to 3x3 physical pixels +- On Windows, there are two possible situations: + - If the application is DPI aware, a screen coordinate corresponds to 1x1 physical pixel, + and you can use the full extent of your screen resolution. + - If the application is not DPI aware, a screen coordinate may correspond to 2x2 physical pixels + (if the display scaling is set to 200% for example). However, the rendering of your application + will be blurry and will not use the full extent of your screen resolution. + - Notes: + - Applications created with HelloImGui are DPI aware by default (when using glfw and sdl backends). + - SDL applications are normally not DPI aware. However, HelloImGui makes them DPI aware. + + +## DisplayFramebufferScale +`DisplayFramebufferScale` is the ratio between the frame buffer size and the window size. + +The frame buffer size is the size of the internal buffer used by the rendering backend. +It might be bigger than the actual window size. +`ImVec2 ImGui::GetIO().DisplayFramebufferScale` is a factor by which the frame buffer size is bigger than the window size. +It is set by the platform backend after it was initialized, and typically reflects the scaling ratio between +physical pixels and screen coordinates. + +Under windows, it will always be (1,1). Under macOS / linux, it will reflect the current display scaling. +It will typically be (2,2) on a macOS retina screen. + +Notes: +- You cannot change DisplayFramebufferScale manually, it will be reset at each new frame, by asking the platform backend. + + +## FontGlobalScale + +`ImGui::GetIO().FontGlobalScale` is a factor by which fonts glyphs should be scaled at rendering time. +It is typically 1 on windows, and 0.5 on macOS retina screens. + + +## How to load fonts with the correct size + +### Using HelloImGui (recommended) + +[`HelloImGui::LoadFont()` and `HelloImGui::LoadFontDpiResponsive`](https://pthom.github.io/hello_imgui/book/doc_api.html#load-fonts) will load fonts + with the correct size, taking into account the DPI scaling. + +### Using Dear ImGui +`ImGui::GetIO().Fonts->AddFontFromFileTTF()` loads a font with a given size, in *physical pixels*. + +If for example, DisplayFramebufferScale is (2,2), and you load a font with a size of 16, it will by default be rendered + with size of 16 *virtual screen coordinate pixels* (i.e. 32 physical pixels). This will lead to blurry text. +To solve this, you should load your font with a size of 16 *virtual screen coordinate pixels* (i.e. 32 physical pixels), +and set `ImGui::GetIO().FontGlobalScale` to 0.5. + +Helpers if using `ImGui::GetIO().Fonts->AddFontFromFileTTF()`: +- `HelloImGui::ImGuiDefaultFontGlobalScale()` returns the default value that should be stored inside `ImGui::GetIO().FontGlobalScale`. +- `HelloImGui::DpiFontLoadingFactor()` returns a factor by which you shall multiply your font sizes when loading them. + + +## Reproducible physical window sizes (in mm or inches) + +### Using HelloImGui +Simply specify a window size that corresponds to theoretical 96 PPI screen (inside `RunnerParams.appWindowParams.windowGeometry.size`) + +### Using your own code to create the backend window +If you prefer to create the window by yourself, its physical size in millimeters may vary widely, +depending on the OS and the current screen DPI setting. +Typically under Windows, your window may appear to be very small if your screen is high DPI. + +To get a similar window size on different OSes/DPI, you should multiply the window size by `HelloImGui::DpiWindowSizeFactor()`. + +Note: DpiWindowSizeFactor() is equal to `CurrentScreenPixelPerInch / 96` under windows and linux, and always 1 under macOS. + +## Fine tune DPI Handling + +See [`HelloImGui::DpiAwareParams`](https://pthom.github.io/hello_imgui/book/doc_params.html#dpi-aware-params) +for more information on how to fine tune DPI handling when using Hello ImGui. +@@md +*/ diff --git a/src/hello_imgui/hello_imgui.h b/src/hello_imgui/hello_imgui.h index aaea975a..8547e8dd 100644 --- a/src/hello_imgui/hello_imgui.h +++ b/src/hello_imgui/hello_imgui.h @@ -112,6 +112,10 @@ ImGuiTestEngine* GetImGuiTestEngine(); // "Sdl - Vulkan" std::string GetBackendDescription(); +// `ChangeWindowSize(const ScreenSize &windowSize)`: sets the window size +// (useful if you want to change the window size during execution) +void ChangeWindowSize(const ScreenSize &windowSize); + // @@md diff --git a/src/hello_imgui/hello_imgui_font.h b/src/hello_imgui/hello_imgui_font.h index ad6ff82a..c4c751e5 100644 --- a/src/hello_imgui/hello_imgui_font.h +++ b/src/hello_imgui/hello_imgui_font.h @@ -9,8 +9,16 @@ namespace HelloImGui using ImWcharPair = std::array; // @@md#Fonts + + // When loading fonts, use + // HelloImGui::LoadFont(..) + // or + // HelloImGui::LoadDpiResponsiveFont() // - // When loading fonts, use HelloImGui::LoadFont(fontFilename, fontSize, fontLoadingParams) + // Use these functions instead of ImGui::GetIO().Fonts->AddFontFromFileTTF(), + // because they will automatically adjust the font size to account for HighDPI, + // and will help you to get consistent font size across different OSes. + // // Font loading parameters: several options are available (color, merging, range, ...) struct FontLoadingParams @@ -53,17 +61,34 @@ namespace HelloImGui ImFontConfig fontConfigFontAwesome = ImFontConfig(); }; - // When loading fonts, use HelloImGui::LoadFont(FontLoadingParams) - // =============================================================== - // instead of ImGui::GetIO().Fonts->AddFontFromFileTTF(), because it will - // automatically adjust the font size to account for HighDPI, and will spare - // you headaches when trying to get consistent font size across different OSes. - // see FontLoadingParams and ImFontConfig - ImFont* LoadFont(const std::string & fontFilename, float fontSize, - const FontLoadingParams & params = {}); + // A font that will be automatically resized to account for changes in DPI + // Use LoadAdaptiveFont instead of LoadFont to get this behavior. + // Fonts loaded with LoadAdaptiveFont will be reloaded during execution + // if ImGui::GetIO().FontGlobalScale is changed. + struct FontDpiResponsive + { + ImFont* font = nullptr; + std::string fontFilename; + float fontSize = 0.f; + FontLoadingParams fontLoadingParams; + }; + - // @@md + // Loads a font with the specified parameters + // (this font will not adapt to DPI changes after startup) + ImFont* LoadFont( + const std::string & fontFilename, float fontSize, + const FontLoadingParams & params = {}); + // Loads a font with the specified parameters + // This font will adapt to DPI changes after startup. + // Only fonts loaded with LoadAdaptiveFont will adapt to DPI changes: + // avoid mixing LoadFont/LoadFontDpiResponsive) + FontDpiResponsive* LoadFontDpiResponsive( + const std::string & fontFilename, float fontSize, + const FontLoadingParams & params = {}); + + // @@md // // Deprecated API below, kept for compatibility (uses LoadFont internally) diff --git a/src/hello_imgui/impl/hello_imgui.cpp b/src/hello_imgui/impl/hello_imgui.cpp index aec1a945..5b2357a8 100644 --- a/src/hello_imgui/impl/hello_imgui.cpp +++ b/src/hello_imgui/impl/hello_imgui.cpp @@ -197,6 +197,8 @@ std::string PlatformBackendTypeToString(PlatformBackendType platformBackendType) return "Glfw"; else if (platformBackendType == PlatformBackendType::Sdl) return "Sdl"; + else if (platformBackendType == PlatformBackendType::Null) + return "Null"; else return "Unknown platform backend"; } @@ -213,6 +215,8 @@ std::string RendererBackendTypeToString(RendererBackendType rendererBackendType) return "DirectX11"; else if (rendererBackendType == RendererBackendType::DirectX12) return "DirectX12"; + else if (rendererBackendType == RendererBackendType::Null) + return "Null"; else return "Unknown renderer backend"; } @@ -220,6 +224,8 @@ std::string RendererBackendTypeToString(RendererBackendType rendererBackendType) std::string GetBackendDescription() { const auto& params = GetRunnerParams(); + if (params->remoteParams.enableRemoting) + return "Remote"; std::string platformBackend = PlatformBackendTypeToString(params->platformBackendType); std::string rendererBackend = RendererBackendTypeToString(params->rendererBackendType); return platformBackend + " - " + rendererBackend; @@ -233,6 +239,16 @@ ImGuiTestEngine* GetImGuiTestEngine() { return GHImGuiTestEngine; } ImGuiTestEngine* GetImGuiTestEngine() { return nullptr; } #endif +void ChangeWindowSize(const ScreenSize &windowSize) +{ + gLastRunner->ChangeWindowSize(windowSize); +} + +bool ShouldDisplayOnRemoteServer() +{ + return gLastRunner->ShouldDisplayOnRemoteServer(); +} + void SaveUserPref(const std::string& userPrefName, const std::string& userPrefContent) { diff --git a/src/hello_imgui/impl/hello_imgui_font.cpp b/src/hello_imgui/impl/hello_imgui_font.cpp index 4df4e2b9..30b07c43 100644 --- a/src/hello_imgui/impl/hello_imgui_font.cpp +++ b/src/hello_imgui/impl/hello_imgui_font.cpp @@ -9,6 +9,8 @@ #include "imgui_freetype.h" #endif +#include + #ifdef IOS #include "hello_imgui/internal/platform/getAppleBundleResourcePath.h" #endif @@ -132,7 +134,7 @@ namespace HelloImGui } - ImFont* LoadFont(const std::string & fontFilename, float fontSize_, const FontLoadingParams& params_) + ImFont* _LoadFontImpl(const std::string & fontFilename, float fontSize_, const FontLoadingParams& params_) { gDidCallHelloImGuiLoadFontTTF = true; @@ -209,7 +211,76 @@ namespace HelloImGui return font; } - ImFont* LoadFontTTF(const std::string & fontFilename, float fontSize, bool useFullGlyphRange, ImFontConfig config) + std::vector gAllDpiResponsiveFonts; + bool gWasLoadFontBareCalled = false; + bool gWasLoadFontDpiResponsiveCalled = false; + + + ImFont* LoadFont(const std::string & fontFilename, float fontSize_, const FontLoadingParams& params_) + { + IM_ASSERT((!gWasLoadFontDpiResponsiveCalled) && "If using LoadFontDpiResponsive(), only use it, and do not use LoadFont()!"); + + auto runnerParams = HelloImGui::GetRunnerParams(); + IM_ASSERT(! runnerParams->dpiAwareParams.onlyUseFontDpiResponsive && "If runnerParams->dpiAwareParams.onlyUseFontDpiResponsive is true, you must use LoadFontDpiResponsive() instead of LoadFont()"); + + gWasLoadFontBareCalled = true; + //printf("LoadFont(%s, %f)\n", fontFilename.c_str(), fontSize_); + return _LoadFontImpl(fontFilename, fontSize_, params_); + } + + FontDpiResponsive* LoadFontDpiResponsive(const std::string & fontFilename, float fontSize, + const FontLoadingParams & fontLoadingParams) + { + IM_ASSERT((!gWasLoadFontBareCalled) && "If using LoadFontDpiResponsive(), set runnerParams.dpiAwareParams.onlyUseFontDpiResponsive=true and do not not use LoadFont()!"); + gWasLoadFontDpiResponsiveCalled = true; + + // Ensure that we have enough capacity, so that pointers remain valid + constexpr size_t maxFonts = 100; + if (gAllDpiResponsiveFonts.capacity() == 0) + gAllDpiResponsiveFonts.reserve(maxFonts); + // Whine if we are about to exceed the capacity + IM_ASSERT(gAllDpiResponsiveFonts.size() < maxFonts - 1 && "Too many fonts loaded"); + // Insert a new element at the end of the vector + gAllDpiResponsiveFonts.push_back({ nullptr, fontFilename, fontSize, fontLoadingParams }); + + // Get the pointer to the newly inserted element (which we will return) + FontDpiResponsive* dpiResponsiveFont = &gAllDpiResponsiveFonts.back(); + + //printf("LoadFontDpiResponsive(%s, %f)\n", fontFilename.c_str(), fontSize); + dpiResponsiveFont->font = _LoadFontImpl(fontFilename, fontSize, fontLoadingParams); + dpiResponsiveFont->fontSize = fontSize; + dpiResponsiveFont->fontFilename = fontFilename; + dpiResponsiveFont->fontLoadingParams = fontLoadingParams; + return dpiResponsiveFont; + } + + bool _reloadAllDpiResponsiveFonts() + { + if (gWasLoadFontBareCalled) + { + fprintf(stderr, "_reloadAllDpiResponsiveFonts failed: ony call LoadFontDpiResponsive if you want this to work\n"); + return false; + } + if (gAllDpiResponsiveFonts.empty()) + return false; + printf("_reloadAllDpiResponsiveFonts\n"); + auto& imguiFonts = ImGui::GetIO().Fonts; + imguiFonts->Clear(); + for (auto & dpiResponsiveFont : gAllDpiResponsiveFonts) + { + float fontSize = dpiResponsiveFont.fontSize; + const std::string & fontFilename = dpiResponsiveFont.fontFilename; + const FontLoadingParams & fontLoadingParams = dpiResponsiveFont.fontLoadingParams; + ImFont* newFont = _LoadFontImpl(fontFilename, fontSize, fontLoadingParams); + dpiResponsiveFont.font = newFont; + } + bool buildSuccess = imguiFonts->Build(); + IM_ASSERT(buildSuccess && "_reloadAllDpiResponsiveFonts: Failed to build fonts"); + return true; + } + + + ImFont* LoadFontTTF(const std::string & fontFilename, float fontSize, bool useFullGlyphRange, ImFontConfig config) { FontLoadingParams fontLoadingParams; if (useFullGlyphRange) diff --git a/src/hello_imgui/impl/imgui_default_settings.cpp b/src/hello_imgui/impl/imgui_default_settings.cpp index 768478f4..38e03a03 100644 --- a/src/hello_imgui/impl/imgui_default_settings.cpp +++ b/src/hello_imgui/impl/imgui_default_settings.cpp @@ -14,7 +14,8 @@ namespace ImGuiDefaultSettings void LoadDefaultFont_WithFontAwesomeIcons() { - auto defaultIconFont = HelloImGui::GetRunnerParams()->callbacks.defaultIconFont; + auto runnerParams = HelloImGui::GetRunnerParams(); + auto defaultIconFont = runnerParams->callbacks.defaultIconFont; float fontSize = 15.f; std::string fontFilename = "fonts/DroidSans.ttf"; @@ -25,8 +26,14 @@ void LoadDefaultFont_WithFontAwesomeIcons() return; } - ImFont* font = LoadFont(fontFilename, fontSize); - if (defaultIconFont == HelloImGui::DefaultIconFont::NoIcons) + bool useDpiResponsiveFonts = runnerParams->dpiAwareParams.onlyUseFontDpiResponsive; + + if (useDpiResponsiveFonts) + LoadFontDpiResponsive(fontFilename, fontSize); + else + LoadFont(fontFilename, fontSize); + + if (defaultIconFont == HelloImGui::DefaultIconFont::NoIcons) return; std::string iconFontFile; @@ -43,8 +50,11 @@ void LoadDefaultFont_WithFontAwesomeIcons() HelloImGui::FontLoadingParams fontParams; fontParams.mergeToLastFont = true; fontParams.useFullGlyphRange = true; - font = LoadFont(iconFontFile, fontSize, fontParams); - (void) font; + + if (useDpiResponsiveFonts) + LoadFontDpiResponsive(iconFontFile, fontSize, fontParams); + else + LoadFont(iconFontFile, fontSize, fontParams); } void SetupDefaultImGuiConfig() diff --git a/src/hello_imgui/internal/backend_impls/abstract_runner.cpp b/src/hello_imgui/internal/backend_impls/abstract_runner.cpp index cef532b6..10820389 100644 --- a/src/hello_imgui/internal/backend_impls/abstract_runner.cpp +++ b/src/hello_imgui/internal/backend_impls/abstract_runner.cpp @@ -7,6 +7,7 @@ #include "hello_imgui/internal/menu_statusbar.h" #include "hello_imgui/internal/platform/ini_folder_locations.h" #include "hello_imgui/internal/inicpp.h" +#include "hello_imgui/internal/poor_man_log.h" #include "imgui.h" #include "hello_imgui/internal/imgui_global_context.h" // must be included before imgui_internal.h @@ -62,15 +63,28 @@ // +//#define ENABLE_DPI_LOG // Enable or disable logging for DPI info +//#define ENABLE_DPI_LOG + +#ifdef ENABLE_DPI_LOG +#define DpiLog PoorManLog +#else +#define DpiLog(...) +#endif + namespace HelloImGui { // Encapsulated inside hello_imgui_screenshot.cpp void setFinalAppWindowScreenshotRgbBuffer(const ImageBuffer& b); +// Encapsulated inside hello_imgui_font.cpp +bool _reloadAllDpiResponsiveFonts(); +bool ShouldDisplayOnRemoteServer(); + AbstractRunner::AbstractRunner(RunnerParams ¶ms_) - : params(params_) {} +: params(params_) {} #ifndef USEHACK @@ -133,6 +147,12 @@ bool AbstractRunner::WantAutoSize() #endif } +void AbstractRunner::ChangeWindowSize(HelloImGui::ScreenSize windowSize) +{ + auto bounds = mBackendWindowHelper->GetWindowBounds(mWindow); + bounds.size = windowSize; + mBackendWindowHelper->SetWindowBounds(mWindow, bounds); +} bool AbstractRunner::ShallSizeWindowRelativeTo96Ppi() { @@ -277,47 +297,98 @@ void ReadDpiAwareParams(const std::string& appIniSettingLocation, DpiAwareParams } +void _LogDpiParams(const std::string& origin, const HelloImGui::DpiAwareParams& dpiAwareParams) +{ + auto &io = ImGui::GetIO(); + std::stringstream msg; + DpiLog("DpiAwareParams: %s\n", origin.c_str()); + DpiLog(" dpiWindowSizeFactor=%f\n", dpiAwareParams.dpiWindowSizeFactor); + DpiLog(" fontRenderingScale=%f\n", dpiAwareParams.fontRenderingScale); + DpiLog(" DpiFontLoadingFactor()=%f\n", dpiAwareParams.DpiFontLoadingFactor()); + DpiLog(" (ImGui FontGlobalScale: %f)\n", io.FontGlobalScale); + DpiLog(" (ImGui DisplayFramebufferScale=%f, %f)\n", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); +} + + +bool AbstractRunner::CheckDpiAwareParamsChanges() +{ + auto& dpiAwareParams = params.dpiAwareParams; + auto& io = ImGui::GetIO(); + + // Check changes + bool didFontGlobalScaleChange = dpiAwareParams.fontRenderingScale != io.FontGlobalScale; + if (didFontGlobalScaleChange) + { + DpiLog("Warning: didFontGlobalScaleChange=:true\n"); + dpiAwareParams.fontRenderingScale = io.FontGlobalScale; + } + + bool didDpiAwareParamsChangeOnRemoteServer = mRemoteDisplayHandler.CheckDpiAwareParamsChanges(); + + if (didFontGlobalScaleChange || didDpiAwareParamsChangeOnRemoteServer) + { + DpiLog("New DpiAwareParams:\n"); + _LogDpiParams("_CheckDpiAwareParamsChanges (changed!)", dpiAwareParams); + return true; + } + else + return false; +} + + +float _DefaultOsFontRenderingScale() +{ + float fontSizeIncreaseFactor = 1.f; + + #ifdef __EMSCRIPTEN__ + // Query the brower to ask for devicePixelRatio + double windowDevicePixelRatio = EM_ASM_DOUBLE( { + var scale = window.devicePixelRatio; + return scale; + } + ); + printf("window.devicePixelRatio=%lf\n", windowDevicePixelRatio); + + fontSizeIncreaseFactor = windowDevicePixelRatio; + #endif + #ifdef HELLOIMGUI_MACOS + // Crisp fonts on macOS: + // cf https://github.com/ocornut/imgui/issues/5301 + // Issue with macOS is that it pretends screen has 2x fewer pixels than it actually does. + // This simplifies application development in most cases, but in our case we happen to render fonts at 1x scale + // while screen renders at 2x scale. + fontSizeIncreaseFactor = (float) NSScreen.mainScreen.backingScaleFactor; + #endif + + return 1.0f / fontSizeIncreaseFactor; +} + + void AbstractRunner::SetupDpiAwareParams() { ReadDpiAwareParams(IniSettingsLocation(params), ¶ms.dpiAwareParams); if (params.dpiAwareParams.dpiWindowSizeFactor == 0.f) { #ifdef HELLOIMGUI_HAS_DIRECTX11 + if (params.rendererBackendType == HelloImGui::RendererBackendType::DirectX11) + { // The current implementation of Dx11 backend does not support changing the window size params.dpiAwareParams.dpiWindowSizeFactor = 1.f; + } #endif params.dpiAwareParams.dpiWindowSizeFactor = mBackendWindowHelper->GetWindowSizeDpiScaleFactor(mWindow); } if (params.dpiAwareParams.fontRenderingScale == 0.f) { - float fontSizeIncreaseFactor = 1.f; - - #ifdef __EMSCRIPTEN__ - // Query the brower to ask for devicePixelRatio - double windowDevicePixelRatio = EM_ASM_DOUBLE( { - var scale = window.devicePixelRatio; - return scale; - } - ); - printf("window.devicePixelRatio=%lf\n", windowDevicePixelRatio); - - fontSizeIncreaseFactor = windowDevicePixelRatio; - #endif - #ifdef HELLOIMGUI_MACOS - // Crisp fonts on macOS: - // cf https://github.com/ocornut/imgui/issues/5301 - // Issue with macOS is that it pretends screen has 2x fewer pixels than it actually does. - // This simplifies application development in most cases, but in our case we happen to render fonts at 1x scale - // while screen renders at 2x scale. - fontSizeIncreaseFactor = (float) NSScreen.mainScreen.backingScaleFactor; - #endif - - params.dpiAwareParams.fontRenderingScale = 1.0f / fontSizeIncreaseFactor; + if (params.remoteParams.enableRemoting) + params.dpiAwareParams.fontRenderingScale = 1.f; + else + params.dpiAwareParams.fontRenderingScale = _DefaultOsFontRenderingScale(); } - ImGui::GetIO().FontGlobalScale = params.dpiAwareParams.fontRenderingScale; - ImGui::GetIO().DisplayFramebufferScale = mBackendWindowHelper->GetWindowScaleFactor(mWindow); + + _LogDpiParams("SetupDpiAwareParams", params.dpiAwareParams); } @@ -362,6 +433,7 @@ void AbstractRunner::HandleDpiOnSecondFrame() #endif // High DPI handling on windows & linux + if (!ShouldDisplayOnRemoteServer()) { float dpiScale = params.dpiAwareParams.dpiWindowSizeFactor; if ( dpiScale > 1.f) @@ -628,8 +700,8 @@ void AbstractRunner::Setup() // This should be done before Impl_LinkPlatformAndRenderBackends() // because, in the case of glfw ImGui_ImplGlfw_InstallCallbacks - // will chain the user callbacks with ImGui callbacks; and PostInit() - // is a good place for the user to install callbacks + // will chain the user callbacks with ImGui callbacks; + // and PostInit() is a good place for the user to install callbacks if (params.callbacks.PostInit_AddPlatformBackendCallbacks) params.callbacks.PostInit_AddPlatformBackendCallbacks(); @@ -680,8 +752,13 @@ void AbstractRunner::Setup() style.Colors[ImGuiCol_TitleBgCollapsed].w = 1.f; } params.callbacks.SetupImGuiStyle(); + + // Create a remote display handler if needed + mRemoteDisplayHandler.Create(); + mRemoteDisplayHandler.SendFonts(); } + void AbstractRunner::SetLayoutResetIfNeeded() { if (params.imGuiWindowParams.defaultImGuiWindowType == DefaultImGuiWindowType::ProvideFullScreenDockSpace) @@ -748,6 +825,7 @@ void AbstractRunner::RenderGui() void _UpdateFrameRateStats(); // See hello_imgui.cpp + void AbstractRunner::CreateFramesAndRender() { // Notes: @@ -775,11 +853,21 @@ void AbstractRunner::CreateFramesAndRender() // (it is here only to make the code more readable, and to enable to collapse blocks of code) constexpr bool foldable_region = true; - if (foldable_region) // Update frame rate stats - { - _UpdateFrameRateStats(); - // printf("Render frame %i, fps=%.1f\n", mIdxFrame, HelloImGui::FrameRate()); - } + // Will display on remote server if needed + mRemoteDisplayHandler.Heartbeat(); + + if (CheckDpiAwareParamsChanges()) // Reload fonts if DPI scale changed + { + if (_reloadAllDpiResponsiveFonts()) + { + DpiLog("_CheckDpiAwareParamsChanges returned true => reloaded all fonts\n"); + // cf https://github.com/ocornut/imgui/issues/6547: we need to recreate the rendering backend device objects + mRenderingBackendCallbacks->Impl_DestroyFontTexture(); + mRenderingBackendCallbacks->Impl_CreateFontTexture(); + + mRemoteDisplayHandler.SendFonts(); + } + } if (foldable_region) // basic layout checks { // SCOPED_RELEASE_GIL_ON_MAIN_THREAD start @@ -875,6 +963,13 @@ void AbstractRunner::CreateFramesAndRender() mBackendWindowHelper->ShowWindow(mWindow); } } + + // Transmit window size to remote server (if needed) + if ((mIdxFrame > 3) && params.remoteParams.transmitWindowSize) + { + auto windowSize = params.appWindowParams.windowGeometry.size; + mRemoteDisplayHandler.TransmitWindowSizeToDisplay(windowSize); + } } // SCOPED_RELEASE_GIL_ON_MAIN_THREAD end if(foldable_region) // Handle idling @@ -884,7 +979,7 @@ void AbstractRunner::CreateFramesAndRender() #ifndef __EMSCRIPTEN__ // Idling for non emscripten, where HelloImGui is responsible for the main loop. // This form of idling will call WaitForEventTimeout(), which may call sleep(): - IdleBySleeping(); + IdleBySleeping(); #endif // Poll Events (this fills GImGui.InputEventsQueue) @@ -901,7 +996,13 @@ void AbstractRunner::CreateFramesAndRender() #endif } // SCOPED_RELEASE_GIL_ON_MAIN_THREAD end - if (foldable_region) // Load additional fonts during execution + if (foldable_region) // Update frame rate stats + { + _UpdateFrameRateStats(); + // printf("Render frame %i, fps=%.1f\n", mIdxFrame, HelloImGui::FrameRate()); + } + + if (foldable_region) // Load additional fonts during execution { if (params.callbacks.LoadAdditionalFonts != nullptr) { @@ -912,6 +1013,8 @@ void AbstractRunner::CreateFramesAndRender() mRenderingBackendCallbacks->Impl_DestroyFontTexture(); mRenderingBackendCallbacks->Impl_CreateFontTexture(); params.callbacks.LoadAdditionalFonts = nullptr; + + mRemoteDisplayHandler.SendFonts(); } } @@ -927,6 +1030,7 @@ void AbstractRunner::CreateFramesAndRender() mRenderingBackendCallbacks->Impl_NewFrame_3D(); Impl_NewFrame_PlatformBackend(); + { // Workaround against SDL clock that sometimes leads to io.DeltaTime=0.f on emscripten // (which fails to an `IM_ASSERT(io.DeltaTime) > 0` in ImGui::NewFrame()) @@ -970,7 +1074,7 @@ void AbstractRunner::CreateFramesAndRender() // iii/ At the end of the second frame, we measure the size of the widgets and use it as the application window size, if the user required auto size // ==> Note: RenderGui() may measure the size of the window and resize it if mIdxFrame==1 // RenderGui may call many user callbacks, so it should not be inside SCOPED_RELEASE_GIL_ON_MAIN_THREAD - RenderGui(); + RenderGui(); if (params.callbacks.BeforeImGuiRender) params.callbacks.BeforeImGuiRender(); @@ -1011,9 +1115,18 @@ void AbstractRunner::IdleBySleeping() #ifdef HELLOIMGUI_WITH_TEST_ENGINE if (params.useImGuiTestEngine && TestEngineCallbacks::IsRunningTest()) return; - #endif - - assert(params.fpsIdling.fpsIdle >= 0.f); + #endif + + if (ShouldDisplayOnRemoteServer()) + { + // if displaying remote, the FPS is limited on the server to a value between 30 and 60 fps + // We cannot idle too slow, other the GUI becomes really sluggish + // Ideally, we should ask the server about it current refresh rate + // (At the moment, we can only deliver 30fps) + params.fpsIdling.fpsIdle = 30.f; + } + + assert(params.fpsIdling.fpsIdle >= 0.f); params.fpsIdling.isIdling = false; if ((params.fpsIdling.fpsIdle > 0.f) && params.fpsIdling.enableIdling) { @@ -1141,6 +1254,8 @@ void AbstractRunner::TearDown(bool gotException) if (params.useImGuiTestEngine) TestEngineCallbacks::TearDown_ImGuiContextDestroyed(); #endif + + mRemoteDisplayHandler.Shutdown(); } @@ -1153,4 +1268,11 @@ std::string AbstractRunner::LoadUserPref(const std::string& userPrefName) return HelloImGuiIniSettings::LoadUserPref(IniSettingsLocation(params), userPrefName); } +bool AbstractRunner::ShouldDisplayOnRemoteServer() +{ + return mRemoteDisplayHandler.ShouldDisplayOnRemoteServer(); +} + + + } // namespace HelloImGui diff --git a/src/hello_imgui/internal/backend_impls/abstract_runner.h b/src/hello_imgui/internal/backend_impls/abstract_runner.h index cf6bef1b..01f7d874 100644 --- a/src/hello_imgui/internal/backend_impls/abstract_runner.h +++ b/src/hello_imgui/internal/backend_impls/abstract_runner.h @@ -4,6 +4,7 @@ #include "hello_imgui/internal/backend_impls/backend_window_helper/backend_window_helper.h" #include "hello_imgui/internal/backend_impls/backend_window_helper/window_geometry_helper.h" #include "hello_imgui/internal/backend_impls/rendering_callbacks.h" +#include "hello_imgui/internal/backend_impls/remote_display_handler.h" #include "hello_imgui/runner_params.h" #include @@ -37,8 +38,11 @@ class AbstractRunner // For jupyter notebook, which displays a screenshot post execution ImageBuffer ScreenshotRgb() { return mRenderingBackendCallbacks->Impl_ScreenshotRgb_3D(); } + void ChangeWindowSize(ScreenSize windowSize); void LayoutSettings_SwitchLayout(const std::string& layoutName); + bool ShouldDisplayOnRemoteServer(); + void SaveUserPref(const std::string& userPrefName, const std::string& userPrefContent); std::string LoadUserPref(const std::string& userPrefName); @@ -83,6 +87,7 @@ class AbstractRunner void InitRenderBackendCallbacks(); void SetupDpiAwareParams(); + bool CheckDpiAwareParamsChanges(); void PrepareWindowGeometry(); void HandleDpiOnSecondFrame(); void MakeWindowSizeRelativeTo96Ppi_IfRequired(); @@ -110,6 +115,8 @@ class AbstractRunner // Callbacks related to the rendering backend (OpenGL, ...) RenderingCallbacksPtr mRenderingBackendCallbacks; + + RemoteDisplayHandler mRemoteDisplayHandler; }; diff --git a/src/hello_imgui/internal/backend_impls/backend_window_helper/backend_window_helper.h b/src/hello_imgui/internal/backend_impls/backend_window_helper/backend_window_helper.h index 4d6d8c3f..bbec66c9 100644 --- a/src/hello_imgui/internal/backend_impls/backend_window_helper/backend_window_helper.h +++ b/src/hello_imgui/internal/backend_impls/backend_window_helper/backend_window_helper.h @@ -61,8 +61,8 @@ namespace HelloImGui { namespace BackendApi virtual void WaitForEventTimeout(double timeout_seconds) = 0; - // Return the ratio FrameBufferSize / WindowSize - virtual ImVec2 GetWindowScaleFactor(WindowPointer window) = 0; + // (ImGui backends handle this by themselves) + //virtual ImVec2 GetDisplayFramebufferScale(WindowPointer window) = 0; // Return the ratio by which the window size should be scaled to account for HighDPI // (i.e. the same size given at creation create the same physical size in mm on the screen) diff --git a/src/hello_imgui/internal/backend_impls/backend_window_helper/glfw_window_helper.cpp b/src/hello_imgui/internal/backend_impls/backend_window_helper/glfw_window_helper.cpp index 71f4d4d2..a6d496a3 100644 --- a/src/hello_imgui/internal/backend_impls/backend_window_helper/glfw_window_helper.cpp +++ b/src/hello_imgui/internal/backend_impls/backend_window_helper/glfw_window_helper.cpp @@ -189,7 +189,7 @@ namespace HelloImGui { namespace BackendApi glfwWaitEventsTimeout(timeout_seconds); } - ImVec2 GlfwWindowHelper::GetWindowScaleFactor(HelloImGui::BackendApi::WindowPointer window) + ImVec2 _GetWindowContentScale(HelloImGui::BackendApi::WindowPointer window) { float x_scale, y_scale; glfwGetWindowContentScale((GLFWwindow *) window, &x_scale, &y_scale); @@ -201,7 +201,7 @@ namespace HelloImGui { namespace BackendApi #ifdef __APPLE__ return 1.f; #else - ImVec2 scale = GetWindowScaleFactor(window); + ImVec2 scale = _GetWindowContentScale(window); return scale.x; #endif } diff --git a/src/hello_imgui/internal/backend_impls/backend_window_helper/glfw_window_helper.h b/src/hello_imgui/internal/backend_impls/backend_window_helper/glfw_window_helper.h index a659386d..f7f35fa3 100644 --- a/src/hello_imgui/internal/backend_impls/backend_window_helper/glfw_window_helper.h +++ b/src/hello_imgui/internal/backend_impls/backend_window_helper/glfw_window_helper.h @@ -29,7 +29,6 @@ namespace HelloImGui { namespace BackendApi void WaitForEventTimeout(double timeout_seconds) override; - ImVec2 GetWindowScaleFactor(WindowPointer window) override; float GetWindowSizeDpiScaleFactor(WindowPointer window) override; void HideWindow(WindowPointer window) override; diff --git a/src/hello_imgui/internal/backend_impls/backend_window_helper/null_window_helper.h b/src/hello_imgui/internal/backend_impls/backend_window_helper/null_window_helper.h index fac9bc92..c5f7e31c 100644 --- a/src/hello_imgui/internal/backend_impls/backend_window_helper/null_window_helper.h +++ b/src/hello_imgui/internal/backend_impls/backend_window_helper/null_window_helper.h @@ -20,6 +20,7 @@ namespace HelloImGui { namespace BackendApi // It is only a class in order to enforce a consistent API between backends. public: WindowPointer CreateWindow(AppWindowParams &appWindowParams, const BackendOptions& backendOptions) override { + mWindowBounds.size = appWindowParams.windowGeometry.size; return nullptr; } @@ -28,20 +29,26 @@ namespace HelloImGui { namespace BackendApi bool IsWindowIconified(WindowPointer window) override { return false; } void RaiseWindow(WindowPointer window) override {} - ScreenBounds GetWindowBounds(WindowPointer window) override { return {}; } - void SetWindowBounds(WindowPointer window, ScreenBounds windowBounds) override {} + ScreenBounds GetWindowBounds(WindowPointer window) override { + return mWindowBounds; + } + void SetWindowBounds(WindowPointer window, ScreenBounds windowBounds) override { + mWindowBounds = windowBounds; + } void WaitForEventTimeout(double timeout_seconds) override { std::this_thread::sleep_for(std::chrono::milliseconds((int)(timeout_seconds * 1000))); } - ImVec2 GetWindowScaleFactor(WindowPointer window) override { return NullConfig::GetWindowScaleFactor(); } float GetWindowSizeDpiScaleFactor(WindowPointer window) override { return NullConfig::GetWindowSizeDpiScaleFactor(); } void HideWindow(WindowPointer window) override {} void ShowWindow(WindowPointer window) override {} bool IsWindowHidden(WindowPointer window) override { return false; } + private: + ScreenBounds mWindowBounds = {}; + }; }} // namespace HelloImGui { namespace BackendApi diff --git a/src/hello_imgui/internal/backend_impls/backend_window_helper/sdl_window_helper.cpp b/src/hello_imgui/internal/backend_impls/backend_window_helper/sdl_window_helper.cpp index 3bb13942..b5858301 100644 --- a/src/hello_imgui/internal/backend_impls/backend_window_helper/sdl_window_helper.cpp +++ b/src/hello_imgui/internal/backend_impls/backend_window_helper/sdl_window_helper.cpp @@ -200,15 +200,6 @@ namespace HelloImGui { namespace BackendApi SDL_WaitEventTimeout(NULL, timeout_ms); } - ImVec2 SdlWindowHelper::GetWindowScaleFactor(WindowPointer window) - { - int win_w, win_h, fb_w, fb_h; - SDL_GetWindowSize((SDL_Window *) window, &win_w, &win_h); - SDL_GL_GetDrawableSize((SDL_Window *) window, &fb_w, &fb_h); - return ImVec2((float)fb_w / win_w, (float)fb_h / win_h); - } - - float SdlWindowHelper::GetWindowSizeDpiScaleFactor(WindowPointer window) { #if TARGET_OS_MAC // is true for any software platform that's derived from macOS, which includes iOS, watchOS, and tvOS diff --git a/src/hello_imgui/internal/backend_impls/backend_window_helper/sdl_window_helper.h b/src/hello_imgui/internal/backend_impls/backend_window_helper/sdl_window_helper.h index 0546bd84..3633e97b 100644 --- a/src/hello_imgui/internal/backend_impls/backend_window_helper/sdl_window_helper.h +++ b/src/hello_imgui/internal/backend_impls/backend_window_helper/sdl_window_helper.h @@ -38,7 +38,6 @@ namespace HelloImGui { namespace BackendApi void WaitForEventTimeout(double timeout_seconds) override; - ImVec2 GetWindowScaleFactor(WindowPointer window) override; float GetWindowSizeDpiScaleFactor(WindowPointer window) override; void HideWindow(WindowPointer window) override; diff --git a/src/hello_imgui/internal/backend_impls/null_config.h b/src/hello_imgui/internal/backend_impls/null_config.h index ff5bf905..87a99862 100644 --- a/src/hello_imgui/internal/backend_impls/null_config.h +++ b/src/hello_imgui/internal/backend_impls/null_config.h @@ -21,7 +21,6 @@ namespace NullConfig return {GetScreenBounds()}; } - ImVec2 GetWindowScaleFactor() { return {1.f, 1.f}; } float GetWindowSizeDpiScaleFactor() { return 1.f; } } diff --git a/src/hello_imgui/internal/backend_impls/remote_display_handler.cpp b/src/hello_imgui/internal/backend_impls/remote_display_handler.cpp new file mode 100644 index 00000000..8f5f8d47 --- /dev/null +++ b/src/hello_imgui/internal/backend_impls/remote_display_handler.cpp @@ -0,0 +1,305 @@ +#ifdef HELLOIMGUI_WITH_NETIMGUI +#include "NetImgui_Api.h" +#endif + +#include "hello_imgui/internal/backend_impls/remote_display_handler.h" +#include "hello_imgui/hello_imgui.h" +#include "hello_imgui/internal/poor_man_log.h" +#include "hello_imgui/internal/clock_seconds.h" + +#include +#include + +#define ENABLE_NETIMGUI_LOG // Enable or disable logging for NetImgui remoting +#ifdef ENABLE_NETIMGUI_LOG +#define NetimguiLog PoorManLog +#else +#define NetimguiLog(...) +#endif + + +namespace HelloImGui +{ +#ifdef HELLOIMGUI_WITH_NETIMGUI + namespace NetimguiUtils + { + // ===================================================================================================================== + // + // ===================================================================================================================== + struct NetImGuiRaii { + NetImGuiRaii() { NetImgui::Startup(); } + ~NetImGuiRaii() { NetImgui::Shutdown(); } + }; + + + class NetImGuiWrapper + { + public: + NetImGuiWrapper() = default; + ~NetImGuiWrapper() = default; + + void HeartBeat() + { + if (mNetImguiRaii == nullptr) + { + InitiateConnection(); + return; + } + + int nbConnectionsPending = mNbConnectionsTentatives - mNbConnectionsSuccess - mNbConnectionsFailures; + IM_ASSERT(nbConnectionsPending == 0 || nbConnectionsPending == 1); + + if (nbConnectionsPending == 1) + { + if (NetImgui::IsConnected()) + { + LogStatus("Connection success...\n"); + ++ mNbConnectionsSuccess; + mLastConnectionSuccess = true; + } + else if (NetImgui::IsConnectionPending()) + { + LogStatus("Connection pending...\n"); + } + else + { + LogStatus("Connection failure...\n"); + ++ mNbConnectionsFailures; + mLastConnectionSuccess = false; + } + } + else // nbConnectionsPending == 0 + { + if (mLastConnectionSuccess) + { + if (!NetImgui::IsConnected()) + { + if (remoteParams().exitWhenServerDisconnected) + { + LogStatus("Connection lost... Exiting\n"); + runnerParams().appShallExit = true; + } + else + { + mTimePointConnectionEnded = HelloImGui::Internal::ClockSeconds(); + LogStatus("Connection lost... Reconnecting\n"); + InitiateConnection(); + } + } + } + else // Last connection was a failure + { + LogStatus("Last connection was a failure... Reconnecting\n"); + InitiateConnection(); + } + } + + // Exit if failure since too long + { + bool isDisconnected = !mLastConnectionSuccess; + if (isDisconnected) + { + double disconnectionTime = HelloImGui::Internal::ClockSeconds() - mTimePointConnectionEnded; + std::string msg = "Disconnected since " + std::to_string(disconnectionTime) + "s"; + LogStatus(msg); + bool isTooLong = disconnectionTime > remoteParams().durationMaxDisconnected; + if (isTooLong) + { + LogStatus("Too long disconnected... Exiting\n"); + runnerParams().appShallExit = true; + } + } + } + } + + void sendFonts() + { + _sendFonts_Impl(); + } + + bool isConnected() + { + return NetImgui::IsConnected(); + } + + private: + void InitiateConnection() + { + NetimguiLog("NetImGuiWrapper: InitiateConnection\n"); + mNetImguiRaii.release(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mNetImguiRaii = std::make_unique(); + NetImgui::ConnectToApp(clientName().c_str(), remoteParams().serverHost.c_str(), remoteParams().serverPort); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ++ mNbConnectionsTentatives; + _sendFonts_Impl(); + } + + std::string clientName() + { + std::string clientName = runnerParams().appWindowParams.windowTitle; + if (clientName.empty()) + clientName = "HelloImGui"; + clientName += "##" + std::to_string(ImGui::GetTime()); + return clientName; + } + + HelloImGui::RemoteParams& remoteParams() { return HelloImGui::GetRunnerParams()->remoteParams; } + HelloImGui::RunnerParams& runnerParams() { return *HelloImGui::GetRunnerParams(); } + + void _sendFonts_Impl() + { + const ImFontAtlas* pFonts = ImGui::GetIO().Fonts; + if( pFonts->TexPixelsAlpha8) // && (pFonts->TexPixelsAlpha8 != client.mpFontTextureData || client.mFontTextureID != pFonts->TexID )) + { + uint8_t* pPixelData(nullptr); int width(0), height(0); + ImGui::GetIO().Fonts->GetTexDataAsAlpha8(&pPixelData, &width, &height); + NetImgui::SendDataTexture(pFonts->TexID, pPixelData, static_cast(width), static_cast(height), NetImgui::eTexFormat::kTexFmtA8); + } + if( pFonts->TexPixelsRGBA32) // && (pFonts->TexPixelsAlpha8 != client.mpFontTextureData || client.mFontTextureID != pFonts->TexID )) + { + uint8_t* pPixelData(nullptr); int width(0), height(0); + ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pPixelData, &width, &height); + NetImgui::SendDataTexture(pFonts->TexID, pPixelData, static_cast(width), static_cast(height), NetImgui::eTexFormat::kTexFmtRGBA8); + } + } + + void LogStatus(const std::string& msg) + { + NetimguiLog("NetImGuiWrapper: %s\n", msg.c_str()); + } + + private: // Members + std::unique_ptr mNetImguiRaii; + int mNbConnectionsTentatives = 0; + int mNbConnectionsSuccess = 0; + int mNbConnectionsFailures = 0; + bool mLastConnectionSuccess = false; + double mTimePointConnectionEnded = 0.0; + }; + + std::unique_ptr gNetImGuiWrapper; + + + bool UpdateDpiAwareParams_Netimgui() + { + bool didFontLoadingRatioChangeOnRemoteServer = false; + auto& dpiAwareParams = HelloImGui::GetRunnerParams()->dpiAwareParams; + + float newFontLoadingRatio = NetImgui::GetFontSizeLoadingRatio(); + float currentFontLoadingRatio = dpiAwareParams.DpiFontLoadingFactor(); + if (fabs(currentFontLoadingRatio - newFontLoadingRatio) > 0.001f) + { + didFontLoadingRatioChangeOnRemoteServer = true; + + float oldDpiWindowSizeFactor = dpiAwareParams.dpiWindowSizeFactor; + float ratioScaling = newFontLoadingRatio / currentFontLoadingRatio; + dpiAwareParams.dpiWindowSizeFactor = dpiAwareParams.dpiWindowSizeFactor * ratioScaling; + ImGui::GetStyle().ScaleAllSizes(ratioScaling); + float new_diff = fabs(dpiAwareParams.DpiFontLoadingFactor() - newFontLoadingRatio); + IM_ASSERT(new_diff < 0.001f); + NetimguiLog("UpdateDpiAwareParams_Netimgui: didFontLoadingRatioChange=true \n" + " currentFontLoadingRatio=%f newFontLoadingRatio=%f\n" + " oldDpiWindowSizeFactor=%f newDpiWindowSizeFactor=%f\n", + currentFontLoadingRatio, newFontLoadingRatio, oldDpiWindowSizeFactor, dpiAwareParams.dpiWindowSizeFactor); + } + return didFontLoadingRatioChangeOnRemoteServer; + } + + // ===================================================================================================================== + // + // ===================================================================================================================== + } // namespace NetimguiUtils +#endif + + +bool RemoteDisplayHandler::IsConnectedToServer() +{ + #ifndef HELLOIMGUI_WITH_NETIMGUI + return false; + #else + if (!ShouldDisplayOnRemoteServer()) + return false; + return NetimguiUtils::gNetImGuiWrapper->isConnected(); + #endif +} + +bool RemoteDisplayHandler::ShouldDisplayOnRemoteServer() +{ + #ifdef HELLOIMGUI_WITH_NETIMGUI + return HelloImGui::GetRunnerParams()->remoteParams.enableRemoting; + #else + return false; + #endif +} + +bool RemoteDisplayHandler::CheckDpiAwareParamsChanges() +{ + if (!IsConnectedToServer()) + return false; + #ifdef HELLOIMGUI_WITH_NETIMGUI + return NetimguiUtils::UpdateDpiAwareParams_Netimgui(); + #else + return false; + #endif +} + +void RemoteDisplayHandler::Create() +{ + if (!ShouldDisplayOnRemoteServer()) + return; + IM_ASSERT(!IsConnectedToServer() && "RemoteDisplayHandler::Create: Already connected to server"); + #ifdef HELLOIMGUI_WITH_NETIMGUI + NetimguiUtils::gNetImGuiWrapper = std::make_unique(); + #endif +} + +void RemoteDisplayHandler::Shutdown() +{ + if (!IsConnectedToServer()) + return; + #ifdef HELLOIMGUI_WITH_NETIMGUI + NetimguiUtils::gNetImGuiWrapper.reset(); + #endif +} + +void RemoteDisplayHandler::Heartbeat() +{ + if (!ShouldDisplayOnRemoteServer()) + return; + #ifdef HELLOIMGUI_WITH_NETIMGUI + NetimguiUtils::gNetImGuiWrapper->HeartBeat(); + #endif +} + +void RemoteDisplayHandler::TransmitWindowSizeToDisplay(ScreenSize windowSize) +{ + if (IsConnectedToServer()) + { + static bool wasSizeTransmitted = false; + if (!wasSizeTransmitted) + { + wasSizeTransmitted = true; + // When running on a remote server, we do not know the window DPI factor, + // so we treat the window size as if it was 96PPI + uint16_t width_96PPI = (uint16_t)windowSize[0]; + uint16_t height_96PPI = (uint16_t)windowSize[1]; + #ifdef HELLOIMGUI_WITH_NETIMGUI + NetImgui::SetWindowSize_96PPI(width_96PPI, height_96PPI); + #endif + } + } +} + +void RemoteDisplayHandler::SendFonts() +{ + if (ShouldDisplayOnRemoteServer()) + { + #ifdef HELLOIMGUI_WITH_NETIMGUI + NetimguiUtils::gNetImGuiWrapper = std::make_unique(); + NetimguiUtils::gNetImGuiWrapper->sendFonts(); + #endif + } +} + +} \ No newline at end of file diff --git a/src/hello_imgui/internal/backend_impls/remote_display_handler.h b/src/hello_imgui/internal/backend_impls/remote_display_handler.h new file mode 100644 index 00000000..56c65423 --- /dev/null +++ b/src/hello_imgui/internal/backend_impls/remote_display_handler.h @@ -0,0 +1,41 @@ +#pragma once +#include "hello_imgui/screen_bounds.h" + +namespace HelloImGui +{ + class RemoteDisplayHandler + { + public: + // Will initialize the remote display handler + // (do nothing if no remote display is configured/compiled) + void Create(); + + // Will shut down the remote display handler + // (do nothing if no remote display is configured/compiled) + void Shutdown(); + + // Will update the remote display handler + // (do nothing if no remote display is configured/compiled) + void Heartbeat(); + + // Will send the window size to the remote display + // (do nothing if no remote display is configured/compiled) + void TransmitWindowSizeToDisplay(ScreenSize windowSize); + + // Will send the fonts to the remote display + // (do nothing if no remote display is configured/compiled) + void SendFonts(); + + // Returns true if the application should display on a remote server + // (return false if no remote display is configured/compiled) + bool ShouldDisplayOnRemoteServer(); + + // Returns true if the application is connected to a remote server + // (return false if no remote display is configured/compiled) + bool IsConnectedToServer(); + + // May update GetRunnerParams()->dpiAwareParams by interrogating the remote display + // (return false if no remote display is configured/compiled) + bool CheckDpiAwareParamsChanges(); + }; +} \ No newline at end of file diff --git a/src/hello_imgui/internal/backend_impls/rendering_vulkan_sdl.cpp b/src/hello_imgui/internal/backend_impls/rendering_vulkan_sdl.cpp index cbf3d9a9..98d08740 100644 --- a/src/hello_imgui/internal/backend_impls/rendering_vulkan_sdl.cpp +++ b/src/hello_imgui/internal/backend_impls/rendering_vulkan_sdl.cpp @@ -35,7 +35,7 @@ namespace HelloImGui // Create Window Surface VkSurfaceKHR surface; - VkResult err; + //VkResult err; if (SDL_Vulkan_CreateSurface(window, gVkGlobals.Instance, &surface) == 0) { IM_ASSERT(0 && "Failed to create Vulkan surface"); diff --git a/src/hello_imgui/internal/backend_impls/runner_factory.cpp b/src/hello_imgui/internal/backend_impls/runner_factory.cpp index 1ce35969..b80c52fa 100644 --- a/src/hello_imgui/internal/backend_impls/runner_factory.cpp +++ b/src/hello_imgui/internal/backend_impls/runner_factory.cpp @@ -39,10 +39,20 @@ void ChooseBackendTypesIfSelectedAsFirstAvailable(RunnerParams* runnerParams) } } +void ChooseNullBackendsIfUsingRemote(RunnerParams* runnerParams) +{ + if (runnerParams->remoteParams.enableRemoting) + { + runnerParams->platformBackendType = PlatformBackendType::Null; + runnerParams->rendererBackendType = RendererBackendType::Null; + } +} + std::unique_ptr FactorRunner(RunnerParams& params) { ChooseBackendTypesIfSelectedAsFirstAvailable(¶ms); + ChooseNullBackendsIfUsingRemote(¶ms); if (params.platformBackendType == PlatformBackendType::Glfw) { #ifdef HELLOIMGUI_USE_GLFW3 diff --git a/src/hello_imgui/internal/docking_details.cpp b/src/hello_imgui/internal/docking_details.cpp index 557f393b..218cda9d 100644 --- a/src/hello_imgui/internal/docking_details.cpp +++ b/src/hello_imgui/internal/docking_details.cpp @@ -13,6 +13,10 @@ namespace HelloImGui { +// From hello_imgui.cpp +bool ShouldDisplayOnRemoteServer(); + + void _Themes_MenuGui(RunnerParams& runnerParams); // see hello_imgui_themes.cpp std::map gImGuiSplitIDs; @@ -171,14 +175,15 @@ void MenuView_Misc(RunnerParams& runnerParams) if (ImGui::MenuItem("View Status bar##xxxx", nullptr, runnerParams.imGuiWindowParams.showStatusBar)) runnerParams.imGuiWindowParams.showStatusBar = ! runnerParams.imGuiWindowParams.showStatusBar; - if (ImGui::BeginMenu("FPS")) - { - if (ImGui::MenuItem("FPS in status bar##xxxx", nullptr, runnerParams.imGuiWindowParams.showStatus_Fps)) - runnerParams.imGuiWindowParams.showStatus_Fps = ! runnerParams.imGuiWindowParams.showStatus_Fps; + if (ImGui::BeginMenu("FPS")) + { + if (ImGui::MenuItem("FPS in status bar##xxxx", nullptr, runnerParams.imGuiWindowParams.showStatus_Fps)) + runnerParams.imGuiWindowParams.showStatus_Fps = ! runnerParams.imGuiWindowParams.showStatus_Fps; - ImGui::MenuItem("Enable Idling", nullptr, &runnerParams.fpsIdling.enableIdling); - ImGui::EndMenu(); - } + if (! ShouldDisplayOnRemoteServer()) + ImGui::MenuItem("Enable Idling", nullptr, &runnerParams.fpsIdling.enableIdling); + ImGui::EndMenu(); + } if (runnerParams.imGuiWindowParams.showMenu_View_Themes) Theme_MenuGui(runnerParams.imGuiWindowParams.tweakedTheme); diff --git a/src/hello_imgui/internal/menu_statusbar.cpp b/src/hello_imgui/internal/menu_statusbar.cpp index 563c9ccc..9d36144b 100644 --- a/src/hello_imgui/internal/menu_statusbar.cpp +++ b/src/hello_imgui/internal/menu_statusbar.cpp @@ -11,6 +11,8 @@ namespace HelloImGui { +bool ShouldDisplayOnRemoteServer(); // from hello_imgui.cpp + namespace Menu_StatusBar { @@ -97,17 +99,23 @@ void ShowStatusBar(RunnerParams & params) if (params.imGuiWindowParams.showStatus_Fps) { - float dy = ImGui::GetFontSize() * 0.15f; - - ImGui::SameLine(ImGui::GetIO().DisplaySize.x - 14.f * ImGui::GetFontSize()); - - const char* idlingInfo = params.fpsIdling.isIdling ? " (Idling)" : ""; - - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - dy); // The checkbox seems visually misaligned, let's fix this - ImGui::Checkbox("Enable idling", ¶ms.fpsIdling.enableIdling); - ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - dy); - ImGui::Text("FPS: %.1f%s", HelloImGui::FrameRate(), idlingInfo); + if (ShouldDisplayOnRemoteServer()) + { + ImGui::SameLine(ImGui::GetIO().DisplaySize.x - 5.f * ImGui::GetFontSize()); + ImGui::Text("FPS: %.1f", HelloImGui::FrameRate()); + } + else + { + float dy = ImGui::GetFontSize() * 0.15f; + + ImGui::SameLine(ImGui::GetIO().DisplaySize.x - 14.f * ImGui::GetFontSize()); + const char* idlingInfo = params.fpsIdling.isIdling ? " (Idling)" : ""; + ImGui::SetCursorPosY(ImGui::GetCursorPosY() - dy); // The checkbox seems visually misaligned, let's fix this + ImGui::Checkbox("Enable idling", ¶ms.fpsIdling.enableIdling); + ImGui::SameLine(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() - dy); + ImGui::Text("FPS: %.1f%s", HelloImGui::FrameRate(), idlingInfo); + } } ImGui::End(); diff --git a/src/hello_imgui/internal/poor_man_log.cpp b/src/hello_imgui/internal/poor_man_log.cpp new file mode 100644 index 00000000..3aad6f7b --- /dev/null +++ b/src/hello_imgui/internal/poor_man_log.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +#ifdef _MSC_VER +#include +#endif + + +namespace HelloImGui +{ +void PoorManLog(const char* msg, ...) +{ + va_list args; + va_start(args, msg); + char buffer[2000]; + vsnprintf(buffer, sizeof(buffer), msg, args); + va_end(args); + +#ifdef _MSC_VER + OutputDebugString(buffer); +#else + printf("%s", buffer); +#endif +} + +} diff --git a/src/hello_imgui/internal/poor_man_log.h b/src/hello_imgui/internal/poor_man_log.h new file mode 100644 index 00000000..e1594791 --- /dev/null +++ b/src/hello_imgui/internal/poor_man_log.h @@ -0,0 +1,4 @@ +namespace HelloImGui +{ + void PoorManLog(const char* msg, ...); +} diff --git a/src/hello_imgui/remote_params.h b/src/hello_imgui/remote_params.h new file mode 100644 index 00000000..a0eb5827 --- /dev/null +++ b/src/hello_imgui/remote_params.h @@ -0,0 +1,39 @@ +#pragma once +#include + +namespace HelloImGui +{ + + +// @@md#RemoteParams + +// RemoteParams is a struct that contains the settings for displaying the application on a remote device. +// using https://github.com/sammyfreg/netImgui +// This will only work if the application is compiled with the option -DHELLOIMGUI_WITH_NETIMGUI=ON +// Highly experimental and not supported. Requires a specific version of netImgui, which is not published. +struct RemoteParams +{ + bool enableRemoting = false; + + bool exitWhenServerDisconnected = false; + double durationMaxDisconnected = 30.0; + + // The server host (if empty, will use "localhost") + // The server is the app that simply displays the application on a remote device + std::string serverHost = "localhost"; + // The server port (default is 8888) + uint32_t serverPort = 8888; + + // The client port (default is 8889) + // This is unused since it is the client (i.e. the app logic) that connects to the server, + // using NetImgui::ConnectToApp + uint32_t clientPort = 8889; + + // If true, transmit the window size to the server + bool transmitWindowSize = false; +}; + +// @@md + + +} // namespace HelloImGui \ No newline at end of file diff --git a/src/hello_imgui/runner_params.h b/src/hello_imgui/runner_params.h index a7ea9c99..37a4d9ea 100644 --- a/src/hello_imgui/runner_params.h +++ b/src/hello_imgui/runner_params.h @@ -4,6 +4,7 @@ #include "hello_imgui/runner_callbacks.h" #include "hello_imgui/docking_params.h" #include "hello_imgui/backend_pointers.h" +#include "hello_imgui/remote_params.h" #include "hello_imgui/renderer_backend_options.h" #include "hello_imgui/dpi_aware.h" #include @@ -131,8 +132,8 @@ struct FpsIdling }; // @@md -// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // @@md#RunnerParams @@ -196,7 +197,6 @@ struct RunnerParams RendererBackendType rendererBackendType = RendererBackendType::FirstAvailable; - // --------------- Settings ------------------- // `iniFolderType`: _IniFolderType, default = IniFolderType::CurrentFolder_ @@ -259,6 +259,9 @@ struct RunnerParams // Set the application refresh rate // (only used on emscripten: 0 stands for "let the app or the browser decide") int emscripten_fps = 0; + + // Parameters for Remote display (experimental, unsupported) + RemoteParams remoteParams; }; // @@md @@ -315,6 +318,8 @@ struct SimpleRunnerParams // `windowSize`: _ScreenSize, default={800, 600}_. // Size of the window + // The size will be handled as if it was specified for a 96PPI screen + // (i.e. a given size will correspond to the same physical size on different screens, whatever their DPI) ScreenSize windowSize = DefaultWindowSize; // `fpsIdle`: _float, default=9_. diff --git a/src/hello_imgui_demos/hello_imgui_demo_classic/hello_imgui_demo_classic.main.cpp b/src/hello_imgui_demos/hello_imgui_demo_classic/hello_imgui_demo_classic.main.cpp index 864ed95e..8f237310 100644 --- a/src/hello_imgui_demos/hello_imgui_demo_classic/hello_imgui_demo_classic.main.cpp +++ b/src/hello_imgui_demos/hello_imgui_demo_classic/hello_imgui_demo_classic.main.cpp @@ -2,12 +2,12 @@ // Demonstrate how to load additional fonts (fonts - part 1/3) -ImFont * gCustomFont = nullptr; +HelloImGui::FontDpiResponsive * gCustomFont = nullptr; void MyLoadFonts() { - - HelloImGui::ImGuiDefaultSettings::LoadDefaultFont_WithFontAwesomeIcons(); // The font that is loaded first is the default font - gCustomFont = HelloImGui::LoadFont("fonts/Akronim-Regular.ttf", 40.f); // will be loaded from the assets folder + HelloImGui::GetRunnerParams()->dpiAwareParams.onlyUseFontDpiResponsive = true; + HelloImGui::ImGuiDefaultSettings::LoadDefaultFont_WithFontAwesomeIcons(); // The font that is loaded first is the default font + gCustomFont = HelloImGui::LoadFontDpiResponsive("fonts/Akronim-Regular.ttf", 40.f); // will be loaded from the assets folder } @@ -38,7 +38,7 @@ int main(int , char *[]) { ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. // Demo custom font usage (fonts - part 3/3) - ImGui::PushFont(gCustomFont); + ImGui::PushFont(gCustomFont->font); ImGui::Text("Custom font"); ImGui::PopFont(); @@ -76,7 +76,6 @@ int main(int , char *[]) { params.imGuiWindowParams.showMenuBar = true; params.imGuiWindowParams.showMenu_App = false; - HelloImGui::Run(params); return 0; } diff --git a/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp b/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp index f2422984..2de557b1 100644 --- a/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp +++ b/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp @@ -62,10 +62,10 @@ struct AppState MyAppSettings myAppSettings; // This values will be stored in the application settings - ImFont* TitleFont = nullptr; - ImFont* ColorFont = nullptr; - ImFont* EmojiFont = nullptr; - ImFont* LargeIconFont = nullptr; + HelloImGui::FontDpiResponsive *TitleFont; + HelloImGui::FontDpiResponsive *ColorFont; + HelloImGui::FontDpiResponsive *EmojiFont; + HelloImGui::FontDpiResponsive *LargeIconFont; }; @@ -74,24 +74,27 @@ struct AppState ////////////////////////////////////////////////////////////////////////// void LoadFonts(AppState& appState) // This is called by runnerParams.callbacks.LoadAdditionalFonts { - HelloImGui::GetRunnerParams()->callbacks.defaultIconFont = HelloImGui::DefaultIconFont::FontAwesome6; + auto runnerParams = HelloImGui::GetRunnerParams(); + runnerParams->dpiAwareParams.onlyUseFontDpiResponsive=true; + + runnerParams->callbacks.defaultIconFont = HelloImGui::DefaultIconFont::FontAwesome6; // First, load the default font (the default font should be loaded first) HelloImGui::ImGuiDefaultSettings::LoadDefaultFont_WithFontAwesomeIcons(); // Then load the other fonts - appState.TitleFont = HelloImGui::LoadFont("fonts/DroidSans.ttf", 18.f); + appState.TitleFont = HelloImGui::LoadFontDpiResponsive("fonts/DroidSans.ttf", 18.f); HelloImGui::FontLoadingParams fontLoadingParamsEmoji; fontLoadingParamsEmoji.useFullGlyphRange = true; - appState.EmojiFont = HelloImGui::LoadFont("fonts/NotoEmoji-Regular.ttf", 24.f, fontLoadingParamsEmoji); + appState.EmojiFont = HelloImGui::LoadFontDpiResponsive("fonts/NotoEmoji-Regular.ttf", 24.f, fontLoadingParamsEmoji); HelloImGui::FontLoadingParams fontLoadingParamsLargeIcon; fontLoadingParamsLargeIcon.useFullGlyphRange = true; - appState.LargeIconFont = HelloImGui::LoadFont("fonts/fontawesome-webfont.ttf", 24.f, fontLoadingParamsLargeIcon); + appState.LargeIconFont = HelloImGui::LoadFontDpiResponsive("fonts/fontawesome-webfont.ttf", 24.f, fontLoadingParamsLargeIcon); #ifdef IMGUI_ENABLE_FREETYPE // Found at https://www.colorfonts.wtf/ HelloImGui::FontLoadingParams fontLoadingParamsColor; fontLoadingParamsColor.loadColor = true; - appState.ColorFont = HelloImGui::LoadFont("fonts/Playbox/Playbox-FREE.otf", 24.f, fontLoadingParamsColor); + appState.ColorFont = HelloImGui::LoadFontDpiResponsive("fonts/Playbox/Playbox-FREE.otf", 24.f, fontLoadingParamsColor); #endif } @@ -139,7 +142,7 @@ void SaveMyAppSettings(const AppState& appState) // Display a button that will hide the application window void DemoHideWindow(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("Hide app window"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Hide app window"); ImGui::PopFont(); static double lastHideTime = -1.; if (ImGui::Button("Hide")) { @@ -169,7 +172,7 @@ void DemoShowAdditionalWindow(AppState& appState) // * either make them initially invisible, and exclude them from the view menu (such as shown here) // * or modify runnerParams.dockingParams.dockableWindows inside the callback RunnerCallbacks.PreNewFrame const char* windowName = "Additional Window"; - ImGui::PushFont(appState.TitleFont); ImGui::Text("Dynamically add window"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Dynamically add window"); ImGui::PopFont(); if (ImGui::Button("Show additional window")) { auto additionalWindowPtr = HelloImGui::GetRunnerParams()->dockingParams.dockableWindowOfName(windowName); @@ -185,7 +188,7 @@ void DemoShowAdditionalWindow(AppState& appState) void DemoLogs(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("Log Demo"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Log Demo"); ImGui::PopFont(); ImGui::BeginGroup(); // Edit a float using a slider from 0.0f to 1.0f @@ -209,7 +212,7 @@ void DemoLogs(AppState& appState) void DemoUserSettings(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("User settings"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("User settings"); ImGui::PopFont(); ImGui::BeginGroup(); ImGui::SetNextItemWidth(HelloImGui::EmSize(7.f)); ImGui::InputText("Name", &appState.myAppSettings.name); @@ -223,7 +226,7 @@ void DemoUserSettings(AppState& appState) void DemoRocket(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("Status Bar Demo"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Status Bar Demo"); ImGui::PopFont(); ImGui::BeginGroup(); if (appState.rocket_state == AppState::RocketState::Init) { @@ -260,7 +263,7 @@ void DemoRocket(AppState& appState) void DemoDockingFlags(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("Main dock space node flags"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Main dock space node flags"); ImGui::PopFont(); ImGui::TextWrapped(R"( This will edit the ImGuiDockNodeFlags for "MainDockSpace". Most flags are inherited by children dock spaces. @@ -295,13 +298,13 @@ Most flags are inherited by children dock spaces. void GuiWindowLayoutCustomization(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("Switch between layouts"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Switch between layouts"); ImGui::PopFont(); ImGui::Text("with the menu \"View/Layouts\""); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Each layout remembers separately the modifications applied by the user, \nand the selected layout is restored at startup"); ImGui::Separator(); - ImGui::PushFont(appState.TitleFont); ImGui::Text("Change the theme"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Change the theme"); ImGui::PopFont(); ImGui::Text("with the menu \"View/Theme\""); if (ImGui::IsItemHovered()) ImGui::SetTooltip("The selected theme is remembered and restored at startup"); @@ -314,7 +317,7 @@ void GuiWindowLayoutCustomization(AppState& appState) void DemoAssets(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("Image From Asset"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Image From Asset"); ImGui::PopFont(); HelloImGui::BeginGroupColumn(); ImGui::Dummy(HelloImGui::EmToVec2(0.f, 0.45f)); @@ -325,7 +328,7 @@ void DemoAssets(AppState& appState) void DemoFonts(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("Fonts"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Fonts"); ImGui::PopFont(); ImGui::TextWrapped("Mix icons " ICON_FA_FACE_SMILE " and text " ICON_FA_ROCKET ""); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Example with Font Awesome Icons"); @@ -334,7 +337,7 @@ void DemoFonts(AppState& appState) ImGui::BeginGroup(); { - ImGui::PushFont(appState.EmojiFont); + ImGui::PushFont(appState.EmojiFont->font); // ✌️ (Victory Hand Emoji) ImGui::Text(U8_TO_CHAR(u8"\U0000270C\U0000FE0F")); ImGui::SameLine(); @@ -361,7 +364,7 @@ void DemoFonts(AppState& appState) #ifdef IMGUI_ENABLE_FREETYPE ImGui::Text("Colored Fonts"); - ImGui::PushFont(appState.ColorFont); + ImGui::PushFont(appState.ColorFont->font); ImGui::Text("C O L O R !"); ImGui::PopFont(); if (ImGui::IsItemHovered()) @@ -371,7 +374,7 @@ void DemoFonts(AppState& appState) void DemoThemes(AppState& appState) { - ImGui::PushFont(appState.TitleFont); ImGui::Text("Themes"); ImGui::PopFont(); + ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Themes"); ImGui::PopFont(); auto& tweakedTheme = HelloImGui::GetRunnerParams()->imGuiWindowParams.tweakedTheme; ImGui::BeginGroup(); @@ -453,7 +456,7 @@ void ShowAppMenuItems() void ShowTopToolbar(AppState& appState) { - ImGui::PushFont(appState.LargeIconFont); + ImGui::PushFont(appState.LargeIconFont->font); if (ImGui::Button(ICON_FA_POWER_OFF)) HelloImGui::GetRunnerParams()->appShallExit = true; @@ -474,7 +477,7 @@ void ShowTopToolbar(AppState& appState) void ShowRightToolbar(AppState& appState) { - ImGui::PushFont(appState.LargeIconFont); + ImGui::PushFont(appState.LargeIconFont->font); if (ImGui::Button(ICON_FA_CIRCLE_ARROW_LEFT)) HelloImGui::Log(HelloImGui::LogLevel::Info, "Clicked on Circle left in the right toolbar"); diff --git a/src/hello_imgui_remote/CMakeLists.txt b/src/hello_imgui_remote/CMakeLists.txt new file mode 100644 index 00000000..eee882e8 --- /dev/null +++ b/src/hello_imgui_remote/CMakeLists.txt @@ -0,0 +1,4 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +if(HELLOIMGUI_WITH_NETIMGUI AND HELLOIMGUI_HAS_OPENGL3) + add_subdirectory(netimgui_remote_display) +endif() diff --git a/src/hello_imgui_remote/netimgui_remote_display/CMakeLists.txt b/src/hello_imgui_remote/netimgui_remote_display/CMakeLists.txt new file mode 100644 index 00000000..5bd8850c --- /dev/null +++ b/src/hello_imgui_remote/netimgui_remote_display/CMakeLists.txt @@ -0,0 +1,22 @@ +if (NOT HELLOIMGUI_WITH_NETIMGUI) + message(FATAL_ERROR "HELLOIMGUI_WITH_NETIMGUI is not enabled") +endif() +if (NOT HELLOIMGUI_HAS_OPENGL3) + message(FATAL_ERROR "HELLOIMGUI_HAS_OPENGL3 is not enabled, and required for hello_imgui_remote_display") +endif() + + +set(hal_gl3_cpp_file ${NETIMGUI_DIR}/Code/ServerApp/Source/GlfwGL3/NetImguiServer_HAL_GL3.cpp) +set(sources ${hal_gl3_cpp_file} netimgui_remote_display.cpp NetImguiServer_HAL_Glfw.cpp) +hello_imgui_add_app(netimgui_remote_display ${sources}) +target_compile_definitions(net_imgui_server_lib PUBLIC NETIMGUI_SERVER_APP_BACKEND_GLFW_GL3=1) +target_link_libraries(netimgui_remote_display PRIVATE net_imgui_server_lib) +if(NOT EMSCRIPTEN) + find_package(OpenGL REQUIRED) + target_link_libraries(netimgui_remote_display PRIVATE OpenGL::GL) +endif() + +if (EMSCRIPTEN) + # emcmake cmake .. -DCMAKE_BUILD_TYPE=Release -DHELLOIMGUI_WITH_NETIMGUI=ON -DHELLOIMGUI_EMSCRIPTEN_PTHREAD=ON + target_link_options(netimgui_remote_display PRIVATE -lwebsocket.js -sPROXY_POSIX_SOCKETS -pthread -sPTHREAD_POOL_SIZE=4 ) +endif() diff --git a/src/hello_imgui_remote/netimgui_remote_display/NetImguiServer_HAL_Glfw.cpp b/src/hello_imgui_remote/netimgui_remote_display/NetImguiServer_HAL_Glfw.cpp new file mode 100644 index 00000000..d7f90d29 --- /dev/null +++ b/src/hello_imgui_remote/netimgui_remote_display/NetImguiServer_HAL_Glfw.cpp @@ -0,0 +1,118 @@ +#include "NetImguiServer_App.h" + +#if HAL_API_PLATFORM_GLFW_GL3 + +#include +#include "hello_imgui/hello_imgui.h" +#include + +#ifdef _WIN32 +#include +#include +#endif + + +namespace NetImguiServer { namespace App +{ + +//================================================================================================= +// HAL STARTUP +// Additional initialisation that are platform specific +//================================================================================================= +bool HAL_Startup(const char* CmdLine) +{ + IM_UNUSED(CmdLine); + return true; +} + +//================================================================================================= +// HAL SHUTDOWN +// Prepare for shutdown of application, with platform specific code +//================================================================================================= +void HAL_Shutdown() +{ +} + +//================================================================================================= +// HAL GET SOCKET INFO +// Take a platform specific socket (based on the NetImguiNetworkXXX.cpp implementation) and +// fetch informations about the client IP connected +//================================================================================================= +bool HAL_GetSocketInfo(NetImgui::Internal::Network::SocketInfo* pClientSocket, char* pOutHostname, size_t HostNameLen, int& outPort) +{ +#ifdef _WIN32 + sockaddr socketAdr; + int sizeSocket(sizeof(sockaddr)); + SOCKET* pClientSocketWin = reinterpret_cast(pClientSocket); + if( getsockname(*pClientSocketWin, &socketAdr, &sizeSocket) == 0 ) + { + char zPortBuffer[32]; + if( getnameinfo(&socketAdr, sizeSocket, pOutHostname, static_cast(HostNameLen), zPortBuffer, sizeof(zPortBuffer), NI_NUMERICSERV) == 0 ) + { + outPort = atoi(zPortBuffer); + return true; + } + } +#endif + return false; +} + +//================================================================================================= +// HAL GET USER SETTING FOLDER +// Request the directory where to the 'shared config' clients should be saved +// Return 'nullptr' to disable this feature +//================================================================================================= +const char* HAL_GetUserSettingFolder() +{ + static std::string result = HelloImGui::IniFolderLocation(HelloImGui::IniFolderType::AppUserConfigFolder); + return result.c_str(); +} + +//================================================================================================= +// HAL GET CLIPBOARD UPDATED +// Detect when clipboard had a content change and we should refetch it on the Server and +// forward it to the Clients +//================================================================================================= +bool HAL_GetClipboardUpdated() +{ + auto updateClipboardEverySecond = []() -> bool + { + static std::chrono::steady_clock::time_point sLastCheck = std::chrono::steady_clock::now(); + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + if( (now - sLastCheck) > std::chrono::seconds(1) ) + { + sLastCheck = now; + return true; + } + else + { + return false; + } + }; + +#ifdef HELLOIMGUI_USE_GLFW3 + GLFWwindow* mainWindow = (GLFWwindow*)HelloImGui::GetRunnerParams()->backendPointers.glfwWindow; + if (! mainWindow) + return updateClipboardEverySecond(); + else + { + static std::string lastClipboardContent; + const char* clipboardContent = glfwGetClipboardString(mainWindow); + if (clipboardContent && lastClipboardContent != clipboardContent) + { + lastClipboardContent = clipboardContent; + return true; + } + else + { + return false; + } + } +#else + return updateClipboardEverySecond(); +#endif +} + +}} // namespace NetImguiServer { namespace App + +#endif // HAL_API_PLATFORM_WIN32DX11 \ No newline at end of file diff --git a/src/hello_imgui_remote/netimgui_remote_display/netimgui_remote_display.cpp b/src/hello_imgui_remote/netimgui_remote_display/netimgui_remote_display.cpp new file mode 100644 index 00000000..7308018b --- /dev/null +++ b/src/hello_imgui_remote/netimgui_remote_display/netimgui_remote_display.cpp @@ -0,0 +1,134 @@ +#include "NetImguiServer_App.h" +#include "NetImguiServer_UI.h" + + +// @SAMPLE_EDIT +// Note: The 'imgui_impl_opengl3_loader.h' included by Dear Imgui Backend has been replaced +// with our own version without stripped symbol. The server App needs a few extra +// function that are not shipped with Dear ImGui. +// The unstripped version comes from : https://github.com/dearimgui/gl3w_stripped/releases + +#include +#include "NetImguiServer_UI.h" +#include "hello_imgui/hello_imgui.h" +//#include "backends/imgui_impl_opengl3.cpp" +//#include "backends/imgui_impl_glfw.cpp" +////================================================================================================= + +// +//#include "imgui.h" +//#include "imgui_impl_glfw.h" +//#include "imgui_impl_opengl3.h" +//#include +//#if defined(IMGUI_IMPL_OPENGL_ES2) +//#include +//#endif +//#include // Will drag system OpenGL headers + +int main(int argc, char **argv) +{ + auto pass_cmd_line_args_to_server = [&]() + { + // Start the NetImgui server + std::string cmdArgs; + for (size_t i = 0; i < size_t(argc); ++i) + cmdArgs += std::string(argv[i]) + " "; + if (!cmdArgs.empty()) + cmdArgs.pop_back(); + if (! NetImguiServer::App::Startup(cmdArgs.c_str())) + exit(1); + }; + + // Runner Params + HelloImGui::RunnerParams runnerParams; + + //NetImguiServer::UI::SetAllowOnlyOneClient(); + //NetImguiServer::UI::SetHideMainMenu(); + + NetImguiServer::UI::SetCallbackWindowSize96PPI([](uint16_t width, uint16_t height) { + HelloImGui::ChangeWindowSize({(int)width, (int)height}); + }); + + runnerParams.callbacks.PostInit = [&pass_cmd_line_args_to_server]() { + pass_cmd_line_args_to_server(); + NetImguiServer::UI::SetUseServerDPISettings(HelloImGui::DpiFontLoadingFactor()); + }; + + runnerParams.callbacks.PreNewFrame = []() { + // Request each client to update their drawing content + NetImguiServer::App::UpdateRemoteContent(); + }; + + runnerParams.callbacks.ShowGui = []() { + ImVec4 clear_color = NetImguiServer::UI::DrawImguiContent(); + HelloImGui::GetRunnerParams()->imGuiWindowParams.backgroundColor = clear_color; + }; + + + runnerParams.callbacks.BeforeExit = []() { + NetImguiServer::App::Shutdown(); + }; + + runnerParams.imGuiWindowParams.defaultImGuiWindowType = HelloImGui::DefaultImGuiWindowType::NoDefaultWindow; + +// runnerParams.dpiAwareParams.dpiWindowSizeFactor = 1.0f; +// runnerParams.dpiAwareParams.fontRenderingScale = 1.0f; + + runnerParams.fpsIdling.fpsIdle = 30.f; + + // Start the HelloImGui runner + HelloImGui::Run(runnerParams); +} + +/* +int main(int argc, char **argv) +{ + + while (ok && !glfwWindowShouldClose(window)) + //========================================================================================= + { + glfwPollEvents(); + + + //========================================================================================= + // @SAMPLE_EDIT (Invoke our own Imgui content drawing, instead of the sample code) + clear_color = NetImguiServer::UI::DrawImguiContent(); + IM_UNUSED(show_demo_window); + IM_UNUSED(show_another_window); + //========================================================================================= + + // Rendering + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + // Update and Render additional Platform Windows + // (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere. + // For this specific demo app we could also call glfwMakeContextCurrent(window) directly) + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + GLFWwindow* backup_current_context = glfwGetCurrentContext(); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + glfwMakeContextCurrent(backup_current_context); + } + + glfwSwapBuffers(window); + } + + // Cleanup + NetImguiServer::App::Shutdown(); // @SAMPLE_EDIT (Deallocate resources) + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} +*/ diff --git a/tools/doc/process_md_docs.py b/tools/doc/process_md_docs.py index 9fd5f0cb..7366b421 100755 --- a/tools/doc/process_md_docs.py +++ b/tools/doc/process_md_docs.py @@ -134,5 +134,5 @@ def process_main_readme(repo_dir: str): process_md_file(hello_imgui_dir + "doc_params.src.md", hello_imgui_dir + "doc_params.md") process_md_file(hello_imgui_dir + "doc_api.src.md", hello_imgui_dir + "doc_api.md") - generate_book_script = repo_dir + "docs_src/generate_book.sh" + generate_book_script = repo_dir + "docs_src/_generate_book_impl.sh" os.system(generate_book_script)