diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 148e855648e4..2ade582964b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,13 @@ env: run-name: 'build@c++${{ inputs.cxxstd || 23 }}' jobs: + format-check: + uses: ./.github/workflows/clang-format.yml + with: + lint_mode: check_only + win32: + needs: [format-check] runs-on: windows-latest steps: - uses: actions/checkout@v6 @@ -55,6 +61,7 @@ jobs: path: | build/bin/cpp-tests/**/* win32-arm64: + needs: [format-check] runs-on: windows-11-arm steps: - uses: actions/checkout@v6 @@ -70,29 +77,33 @@ jobs: axmol new HelloCpp axmol -d .\HelloCpp -xc '-DAX_PREBUILT_DIR=build' -O3 winuwp: - runs-on: windows-latest - steps: - - uses: actions/checkout@v6 + needs: [format-check] + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 - - name: Build - shell: pwsh - run: .\tools\cmdline\axmol -p winuwp -a x64 -O3 -xc '-DAX_RENDER_API=d3d11;d3d12;gl' + - name: Build + shell: pwsh + run: .\tools\cmdline\axmol -p winuwp -a x64 -O3 -xc '-DAX_RENDER_API=d3d11;d3d12;gl' win32-clang: - runs-on: windows-latest - steps: - - uses: actions/checkout@v6 + needs: [format-check] + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 - - name: Build - shell: pwsh - run: .\tools\cmdline\axmol -p win32 -a 'x64' -cc clang -O3 -xc '-DAX_RENDER_API=d3d11;d3d12;gl;vk' + - name: Build + shell: pwsh + run: .\tools\cmdline\axmol -p win32 -a 'x64' -cc clang -O3 -xc '-DAX_RENDER_API=d3d11;d3d12;gl;vk' win32-dll: - runs-on: windows-latest - steps: - - uses: actions/checkout@v6 - - name: Build - shell: pwsh - run: .\tools\cmdline\axmol -p win32 -a x64 -dll -xc '-DAX_RENDER_API=d3d11;d3d12;gl;vk' + needs: [format-check] + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 + - name: Build + shell: pwsh + run: .\tools\cmdline\axmol -p win32 -a x64 -dll -xc '-DAX_RENDER_API=d3d11;d3d12;gl;vk' linux: + needs: [format-check] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -106,6 +117,7 @@ jobs: shell: pwsh run: ./tools/cmdline/axmol -p linux -a x64 -t 'cpp-tests,lua-tests' -xc '-DAX_RENDER_API=vk;gl' && ./tools/cmdline/axmol run -p linux -a x64 -t unit-tests -wait linux-arm64: + needs: [format-check] runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v6 @@ -119,6 +131,7 @@ jobs: shell: pwsh run: ./tools/cmdline/axmol -p linux -a arm64 -t 'cpp-tests,lua-tests' -xc '-DAX_RENDER_API=vk;gl' && ./tools/cmdline/axmol run -p linux -a arm64 -t unit-tests -wait osx-arm64: + needs: [format-check] runs-on: macos-15 steps: - uses: actions/checkout@v6 @@ -130,6 +143,7 @@ jobs: shell: pwsh run: ./tools/cmdline/axmol -p osx -a arm64 && ./tools/cmdline/axmol run -p osx -a arm64 -t unit-tests osx-x64: + needs: [format-check] runs-on: macos-15-intel steps: - uses: actions/checkout@v6 @@ -141,6 +155,7 @@ jobs: shell: pwsh run: ./tools/cmdline/axmol -p osx -a x64 android: + needs: [format-check] runs-on: ubuntu-latest strategy: matrix: @@ -166,6 +181,7 @@ jobs: templates/**/*.apk tests/**/*.apk ios-sim-x64: + needs: [format-check] runs-on: macos-15 strategy: matrix: @@ -184,6 +200,7 @@ jobs: shell: pwsh run: ./tools/cmdline/axmol -p $env:TARGET_OS -a 'x64' ios-sim-arm64: + needs: [format-check] runs-on: macos-15 strategy: matrix: @@ -203,6 +220,7 @@ jobs: # axmol cmdline can't guess ios arm64 as simulator, so need specify by option '-sdk' run: ./tools/cmdline/axmol -p $env:TARGET_OS -a 'arm64' -sdk 'simulator' wasm: + needs: [format-check] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -216,6 +234,7 @@ jobs: path: build_wasm/bin/**/* if-no-files-found: error wasm64: + needs: [format-check] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index a3da6293296f..6017fd065f91 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -1,10 +1,6 @@ name: clang-format on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: - - dev workflow_dispatch: inputs: lint_mode: @@ -16,6 +12,30 @@ on: - check_only - create_pr - auto_commit + + workflow_call: + inputs: + lint_mode: + description: 'Select lint mode: check_only, auto_commit, or create_pr' + required: false + default: 'check_only' + type: string + should_run: + description: 'Whether clang-format should actually run' + required: false + default: true + type: boolean + head_repo: + description: 'Repository to checkout' + required: false + default: '' + type: string + head_ref: + description: 'Ref to checkout' + required: false + default: '' + type: string + issue_comment: types: [created] # Listen for new comments on issues/PRs @@ -23,11 +43,19 @@ jobs: clang-format-lint: runs-on: ubuntu-latest # Run if: - # - PR event # - workflow_dispatch + # - pull_request/push from build.yml reusable workflow calls # - issue_comment on a PR with /clang-format AND commenter is halx99 + # + # Note: + # When called from build.yml, github.event_name keeps the caller event name + # such as pull_request, push, or workflow_dispatch. It is not workflow_call. + # The job itself must not be skipped when should_run is false; only the + # clang-format steps should be skipped so build.yml jobs that need + # [format-check] can continue. if: | (github.event_name == 'pull_request') || + (github.event_name == 'push') || (github.event_name == 'workflow_dispatch') || (github.event_name == 'issue_comment' && github.event.issue.pull_request != null && @@ -60,11 +88,13 @@ jobs: core.setOutput('head_ref', pr.head.ref); core.setOutput('head_repo', pr.head.repo.full_name); - # Determine lint_mode to auto_commit if triggered by /clang-format comment and store head commit sha + # Determine whether to run, checkout target, and lint mode - name: Prepare clang-format lint id: pp shell: pwsh run: | + $should_run = '${{ inputs.should_run || true }}' + if ($env:GITHUB_EVENT_NAME -eq 'issue_comment') { $head_repo = "${{ steps.pr.outputs.head_repo }}" $head_ref = "${{ steps.pr.outputs.head_ref }}" @@ -77,10 +107,12 @@ jobs: $head_repo = "${{ github.repository }}" $head_ref = "${{ github.head_ref || github.ref_name }}" } - if (!$head_repo -or !$head_ref) { + + if ($should_run -eq 'true' -and (!$head_repo -or !$head_ref)) { Write-Error "❌ head_repo or head_ref is empty" exit 1 } + if ($env:GITHUB_EVENT_NAME -eq 'issue_comment') { echo "Set lint_mode=auto_commit for ${{ github.event.comment.user.login }} Command **/clang-format**" $lint_mode = 'auto_commit' @@ -88,19 +120,28 @@ jobs: else { $lint_mode = "${{ inputs.lint_mode || 'check_only' }}" } + + echo "should_run=$should_run" >> ${env:GITHUB_OUTPUT} echo "head_repo=$head_repo" >> ${env:GITHUB_OUTPUT} echo "head_ref=$head_ref" >> ${env:GITHUB_OUTPUT} echo "lint_mode=$lint_mode" >> ${env:GITHUB_OUTPUT} - Write-Host "head_repo=$head_repo, head_ref=$head_ref, lint_mode=$lint_mode" + + Write-Host "should_run=$should_run, head_repo=$head_repo, head_ref=$head_ref, lint_mode=$lint_mode" + + - name: Skip clang-format + if: ${{ steps.pp.outputs.should_run != 'true' }} + run: echo "clang-format skipped for this caller event." # Checkout correct branch - uses: actions/checkout@v6 + if: ${{ steps.pp.outputs.should_run == 'true' }} with: repository: ${{ steps.pp.outputs.head_repo }} ref: ${{ steps.pp.outputs.head_ref }} token: ${{ secrets.AX_BOT_TOKEN || github.token }} - name: Run clang-format lint + if: ${{ steps.pp.outputs.should_run == 'true' }} uses: DoozyX/clang-format-lint-action@v0.20 with: source: './axmol ./extensions ./tests ./templates' @@ -111,7 +152,7 @@ jobs: # check_only mode - name: Check for uncommitted changes - if: ${{ steps.pp.outputs.lint_mode == 'check_only' }} + if: ${{ steps.pp.outputs.should_run == 'true' && steps.pp.outputs.lint_mode == 'check_only' }} shell: pwsh run: | git diff --quiet @@ -127,7 +168,7 @@ jobs: } - name: Prepare for create pull request - if: ${{ steps.pp.outputs.lint_mode == 'create_pr' }} + if: ${{ steps.pp.outputs.should_run == 'true' && steps.pp.outputs.lint_mode == 'create_pr' }} id: ppr shell: pwsh run: | @@ -137,7 +178,7 @@ jobs: # create_pr mode - name: Create pull request - if: ${{ steps.pp.outputs.lint_mode == 'create_pr' }} + if: ${{ steps.pp.outputs.should_run == 'true' && steps.pp.outputs.lint_mode == 'create_pr' }} id: cpr uses: peter-evans/create-pull-request@v8 with: @@ -162,14 +203,14 @@ jobs: draft: false - name: Check pull request outputs - if: ${{ steps.pp.outputs.lint_mode == 'create_pr' && steps.cpr.outputs.pull-request-number }} + if: ${{ steps.pp.outputs.should_run == 'true' && steps.pp.outputs.lint_mode == 'create_pr' && steps.cpr.outputs.pull-request-number }} run: | echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" - # auto_commit mode (including comment trigger) + # auto_commit mode, including comment trigger - name: Commit clang-format changes to PR source branch - if: ${{ steps.pp.outputs.lint_mode == 'auto_commit' }} + if: ${{ steps.pp.outputs.should_run == 'true' && steps.pp.outputs.lint_mode == 'auto_commit' }} uses: EndBug/add-and-commit@v10 with: author_name: axmol-bot diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 1f9d9ac95827..576e77232a04 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -354,6 +354,8 @@ if((WINDOWS AND NOT WINRT) OR MACOSX OR LINUX) list(APPEND _glfw_options "GLFW_BUILD_X11 ON") if(AX_ENABLE_WAYLAND) list(APPEND _glfw_options "GLFW_BUILD_WAYLAND ON") + else() + list(APPEND _glfw_options "GLFW_BUILD_WAYLAND OFF") endif() endif() ax_add_3rd(glfw OPTIONS ${_glfw_options}) diff --git a/3rdparty/README.md b/3rdparty/README.md index a0fed3bc63d2..06b0b0808d2b 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -81,7 +81,7 @@ ## glfw - [![Upstream](https://img.shields.io/github/v/release/glfw/glfw?label=Upstream)](https://github.com/glfw/glfw) -- Version: 3.5-2f3efb7 of https://github.com/axmolengine/glfw +- Version: 3.5-12d2696 of https://github.com/axmolengine/glfw - License: zlib ## ghc (iOS < 13 ONLY) diff --git a/3rdparty/glfw/src/cocoa_window.m b/3rdparty/glfw/src/cocoa_window.m index 4dc97622134c..3505de8aab56 100644 --- a/3rdparty/glfw/src/cocoa_window.m +++ b/3rdparty/glfw/src/cocoa_window.m @@ -654,6 +654,34 @@ - (void)keyUp:(NSEvent *)event _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); } +- (BOOL)performKeyEquivalent:(NSEvent *)event +{ + // HACK: Some key combinations are consumed before reaching keyDown: + // so we claim those events and emit them here + const int key = translateKey([event keyCode]); + const int mods = translateFlags([event modifierFlags]); + + if (mods & GLFW_MOD_CONTROL) + { + if (key == GLFW_KEY_TAB || key == GLFW_KEY_ESCAPE) + { + _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); + return YES; + } + } + + if (mods & GLFW_MOD_SUPER) + { + if (key == GLFW_KEY_PERIOD) + { + _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); + return YES; + } + } + + return [super performKeyEquivalent:event]; +} + - (void)scrollWheel:(NSEvent *)event { double deltaX = [event scrollingDeltaX]; diff --git a/CMakeOptions.md b/CMakeOptions.md index ead48642b041..12ef9ecc7b83 100644 --- a/CMakeOptions.md +++ b/CMakeOptions.md @@ -11,7 +11,7 @@ - AX_ENABLE_3D: whether to enable 3D support, default: `TRUE` - AX_ENABLE_PHYSICS_3D: whether to enable physics3d support, default: `TRUE` - AX_ENABLE_NAVMESH: whether to enable NavMesh support default: `TRUE` - - AX_ENABLE_MEDIA: whether to enable media support, default: `TRUE` + - AX_ENABLE_VIDEO: whether to enable video player, default: `TRUE` - AX_ENABLE_AUDIO: whether to enable audio support, default: `TRUE` - AX_ENABLE_CONSOLE: whether to enable debug tool console support, default: `TRUE` - AX_ENABLE_OPUS: whether to enable audio engine play .opus files support, default: `TRUE` diff --git a/INFRA.md b/INFRA.md index e78e2c46a60c..af4e2a86be55 100644 --- a/INFRA.md +++ b/INFRA.md @@ -3,7 +3,7 @@ ## Microsoft.Windows.CppWinRT - [![nuget](https://img.shields.io/nuget/v/Microsoft.Windows.CppWinRT?label=Upstream)](https://www.nuget.org/packages/Microsoft.Windows.CppWinRT) -- Version: 2.0.250303.1 +- Version: 3.0.260520.1 - License: MIT - Platform: WinRT/WinUWP - Manged by: `cmake/Modules/AXConfigDefine.cmake` diff --git a/axmol/2d/ActionInterval.cpp b/axmol/2d/ActionInterval.cpp index 75d768b5042a..996e55cd6747 100644 --- a/axmol/2d/ActionInterval.cpp +++ b/axmol/2d/ActionInterval.cpp @@ -36,7 +36,7 @@ THE SOFTWARE. #include "axmol/2d/SpriteFrame.h" #include "axmol/2d/ActionInstant.h" #include "axmol/base/Director.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/EventDispatcher.h" #include "axmol/platform/StdC.h" #include "axmol/base/ScriptSupport.h" @@ -2591,7 +2591,7 @@ void Animate::update(float t) if (!dict.empty()) { if (_frameDisplayedEvent == nullptr) - _frameDisplayedEvent = new EventCustom(AnimationFrameDisplayedNotification); + _frameDisplayedEvent = new CustomEvent(AnimationFrameDisplayedNotification); _frameDisplayedEventInfo.target = _target; _frameDisplayedEventInfo.userInfo = &dict; diff --git a/axmol/2d/ActionInterval.h b/axmol/2d/ActionInterval.h index da560753b716..efeaeee4cca8 100644 --- a/axmol/2d/ActionInterval.h +++ b/axmol/2d/ActionInterval.h @@ -41,7 +41,7 @@ namespace ax class Node; class SpriteFrame; -class EventCustom; +class CustomEvent; /** * @addtogroup actions @@ -1493,7 +1493,7 @@ class AX_DLL Animate : public ActionInterval unsigned int _executedLoops = 0; Animation* _animation = nullptr; - EventCustom* _frameDisplayedEvent = nullptr; + CustomEvent* _frameDisplayedEvent = nullptr; AnimationFrame::DisplayedEventInfo _frameDisplayedEventInfo; private: diff --git a/axmol/2d/CMakeLists.txt b/axmol/2d/CMakeLists.txt index 4efa262ee799..1269307a8572 100644 --- a/axmol/2d/CMakeLists.txt +++ b/axmol/2d/CMakeLists.txt @@ -29,7 +29,6 @@ set(_AX_2D_HEADER 2d/ClippingRectangleNode.h 2d/ActionEase.h 2d/ProtectedNode.h - 2d/TextFieldTTF.h 2d/AnimationCache.h 2d/FastTMXLayer.h 2d/FontAtlasCache.h @@ -116,7 +115,6 @@ set(_AX_2D_SRC 2d/SpriteFrameCache.cpp 2d/SpriteFrame.cpp 2d/AutoPolygon.cpp - 2d/TextFieldTTF.cpp 2d/TileMapAtlas.cpp # 2d/TMXLayer.cpp diff --git a/axmol/2d/ClippingRectangleNode.cpp b/axmol/2d/ClippingRectangleNode.cpp index 6b01bde3e047..f25685814ab0 100644 --- a/axmol/2d/ClippingRectangleNode.cpp +++ b/axmol/2d/ClippingRectangleNode.cpp @@ -78,8 +78,8 @@ void ClippingRectangleNode::onBeforeVisitScissor() parent = parent->getParent(); } - const Point pos = convertToWorldSpace(Point(_clippingRegion.origin.x, _clippingRegion.origin.y)); - RenderView* renderView = _director->getRenderView(); + const Point pos = convertToWorldSpace(Point(_clippingRegion.origin.x, _clippingRegion.origin.y)); + auto renderView = _director->getRenderView(); renderView->setScissorInPoints(pos.x, pos.y, _clippingRegion.size.width * scaleX, _clippingRegion.size.height * scaleY); } diff --git a/axmol/2d/DrawNode.cpp b/axmol/2d/DrawNode.cpp index e57e5d66803a..4d8c3a57cba1 100644 --- a/axmol/2d/DrawNode.cpp +++ b/axmol/2d/DrawNode.cpp @@ -30,7 +30,7 @@ #include "axmol/base/Environment.h" #include "axmol/renderer/Renderer.h" #include "axmol/base/Director.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/2d/ActionCatmullRom.h" #include "axmol/base/Utils.h" @@ -100,7 +100,7 @@ DrawNode::DrawNode() // TODO new-renderer: interface setupBuffer removal // Need to listen the event only when not use batchnode, because it will use VBO - // auto listener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom* event){ + // auto listener = CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent* event){ // /** listen the event that renderer was recreated on Android/WP8 */ // this->setupBuffer(); // }); diff --git a/axmol/2d/FontAtlas.cpp b/axmol/2d/FontAtlas.cpp index ab8b3711cfef..b57485f9b7f7 100644 --- a/axmol/2d/FontAtlas.cpp +++ b/axmol/2d/FontAtlas.cpp @@ -30,7 +30,7 @@ #include "axmol/2d/FontFreeType.h" #include "axmol/base/text_utils.h" #include "axmol/base/Director.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/base/EventType.h" @@ -160,7 +160,7 @@ FontAtlas::FontAtlas(Font* theFont, int atlasWidth, int atlasHeight, float scale #if AX_ENABLE_CONTEXT_LOSS_RECOVERY auto eventDispatcher = Director::getInstance()->getEventDispatcher(); - _rendererRecreatedListener = EventListenerCustom::create( + _rendererRecreatedListener = CustomEventListener::create( EVENT_RENDERER_RECREATED, AX_CALLBACK_1(FontAtlas::listenRendererRecreated, this)); eventDispatcher->addEventListenerWithFixedPriority(_rendererRecreatedListener, 1); #endif @@ -275,7 +275,7 @@ void FontAtlas::clearTexturesAtlas() } } -void FontAtlas::listenRendererRecreated(EventCustom* /*event*/) +void FontAtlas::listenRendererRecreated(CustomEvent* /*event*/) { clearTexturesAtlas(); } diff --git a/axmol/2d/FontAtlas.h b/axmol/2d/FontAtlas.h index fc33ef74e3d6..40d00389db32 100644 --- a/axmol/2d/FontAtlas.h +++ b/axmol/2d/FontAtlas.h @@ -46,8 +46,8 @@ namespace ax { class Font; -class EventCustom; -class EventListenerCustom; +class CustomEvent; +class CustomEventListener; class FontFreeType; struct FontLetterDefinition @@ -106,7 +106,7 @@ class AX_DLL FontAtlas : public Object /** listen the event that renderer was recreated on Android/WP8 It only has effect on Android and WP8. */ - void listenRendererRecreated(EventCustom* event); + void listenRendererRecreated(CustomEvent* event); /** Clear textures atlas. It will clear the textures atlas and if multiple texture exist in the FontAtlas. @@ -175,7 +175,7 @@ class AX_DLL FontAtlas : public Object int _letterEdgeExtend = 0; int _fontAscender = 0; - EventListenerCustom* _rendererRecreatedListener = nullptr; + CustomEventListener* _rendererRecreatedListener = nullptr; bool _antialiasEnabled = true; int _currLineHeight = 0; diff --git a/axmol/2d/Label.cpp b/axmol/2d/Label.cpp index 4ed93429c17a..a09a13a82970 100644 --- a/axmol/2d/Label.cpp +++ b/axmol/2d/Label.cpp @@ -42,14 +42,15 @@ #include "axmol/renderer/Renderer.h" #include "axmol/renderer/RenderCommand.h" #include "axmol/base/Director.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/Utils.h" #include "axmol/2d/FontFNT.h" #include "axmol/renderer/Shaders.h" #include "axmol/rhi/ProgramState.h" #include "axmol/renderer/ProgramStateRegistry.h" +#include "yasio/tlx/string_view.hpp" namespace ax { @@ -227,11 +228,31 @@ std::array Label::BatchCommand::getCommandArray() Label* Label::create() { - auto ret = new Label; + auto ret = new Label(); ret->autorelease(); return ret; } +Label* Label::create(std::string_view text, std::string_view fontName, float fontSize) +{ + if (FileUtils::getInstance()->isFileExist(fontName)) + { + if (tlx::ic::ends_with(fontName, ".fnt")) + { + return Label::createWithBMFont(fontName, text); + } + else + { + TTFConfig config(fontName, fontSize); + return Label::createWithTTF(config, text); + } + } + else + { + return Label::createWithSystemFont(text, fontName, fontSize); + } +} + Label* Label::createWithSystemFont(std::string_view text, std::string_view font, float fontSize, @@ -392,6 +413,31 @@ Label* Label::createWithCharMap(std::string_view charMapFile, int itemWidth, int return nullptr; } +void Label::setFontInfo(std::string_view fontName, float fontSize) +{ + auto prevLableType = _currentLabelType; + if (FileUtils::getInstance()->isFileExist(fontName)) + { + if (tlx::ic::ends_with(fontName, ".fnt"sv)) + { + setBMFontFilePath(fontName); + } + else + { + TTFConfig ttfConfig(fontName, fontSize, GlyphCollection::DYNAMIC); + setTTFConfig(ttfConfig); + } + } + else + { + setSystemFontName(fontName); + setSystemFontSize(fontSize); + + if (prevLableType == LabelType::STRING_TEXTURE) + requestSystemFontRefresh(); + } +} + bool Label::setCharMap(std::string_view plistFile) { auto newAtlas = FontAtlasCache::getFontAtlasCharMap(plistFile); @@ -496,7 +542,7 @@ Label::Label(TextHAlignment hAlignment /* = TextHAlignment::LEFT */, AX_SAFE_RETAIN(_debugDrawNode); #endif - _resetTextureListener = EventListenerCustom::create(FontAtlas::CMD_RESET_FONTATLAS, [this](EventCustom* event) { + _resetTextureListener = CustomEventListener::create(FontAtlas::CMD_RESET_FONTATLAS, [this](CustomEvent* event) { if (_fontAtlas && _currentLabelType == LabelType::TTF && event->getUserData() == _fontAtlas) { for (auto&& it : _letters) @@ -569,7 +615,6 @@ void Label::reset() _currLabelEffect = LabelEffect::NORMAL; _contentDirty = false; _numberOfLines = 0; - _lengthOfString = 0; _utf32Text.clear(); _utf8Text.clear(); @@ -907,15 +952,19 @@ bool Label::setBMFontFilePath(std::string_view bmfontFilePath, std::string_view void Label::setString(std::string_view text) { - if (text.compare(_utf8Text)) + if (text != _utf8Text) { - _utf8Text = text; - _contentDirty = true; - std::u32string utf32String; - if (text_utils::UTF8ToUTF32(_utf8Text, utf32String)) + if (text_utils::UTF8ToUTF32(text, utf32String)) { + _utf8Text = text; _utf32Text = utf32String; + + _contentDirty = true; + } + else + { + AXLOGE("Label: setString() - Invalid utf8 text: {}", text); } } } @@ -1006,7 +1055,7 @@ void Label::updateLabelLetters() letterIndex = it->first; letterSprite = (LabelLetter*)it->second; - if (letterIndex >= _lengthOfString) + if (letterIndex >= getCharCount()) { Node::removeChild(letterSprite, true); it = _letters.erase(it); @@ -1102,7 +1151,6 @@ void Label::alignText() bool Label::tryTextPlacement(float fontSize) { - _lengthOfString = 0; _textDesiredHeight = 0.f; _linesWidth.clear(); @@ -1212,7 +1260,7 @@ bool Label::updateQuads() batchNode->getTextureAtlas()->removeAllQuads(); } - for (int ctr = 0; ctr < _lengthOfString; ++ctr) + for (int ctr = 0; ctr < getCharCount(); ++ctr) { auto& letterInfo = _lettersInfo[ctr]; if (letterInfo.valid) @@ -2066,7 +2114,7 @@ void Label::updateEffectUniforms(BatchCommand& batch, void Label::draw(Renderer* renderer, const Mat4& transform, uint32_t flags) { - if (_batchNodes.empty() || _lengthOfString <= 0) + if (_batchNodes.empty() || _utf32Text.empty()) { return; } @@ -2297,7 +2345,7 @@ Sprite* Label::getLetter(int letterIndex) updateContent(); } - if (_textSprite == nullptr && letterIndex < _lengthOfString) + if (_textSprite == nullptr && letterIndex < getCharCount()) { const auto& letterInfo = _lettersInfo[letterIndex]; if (!letterInfo.valid || letterInfo.atlasIndex < 0) @@ -2424,25 +2472,24 @@ void Label::computeStringNumLines() _numberOfLines = quantityOfLines; } -int Label::getStringNumLines() +int Label::getLineCount() const { if (_contentDirty) { - updateContent(); + const_cast(this)->updateContent(); } if (_currentLabelType == LabelType::STRING_TEXTURE) { - computeStringNumLines(); + const_cast(this)->computeStringNumLines(); } return _numberOfLines; } -int Label::getStringLength() +int Label::getCharCount() const { - _lengthOfString = static_cast(_utf32Text.length()); - return _lengthOfString; + return static_cast(_utf32Text.length()); } // RGBA protocol @@ -2901,7 +2948,7 @@ void Label::updateFontScale() bool Label::multilineTextWrap(bool breakOnChar, bool ignoreOverflow) { - int textLen = getStringLength(); + int textLen = getCharCount(); int lineIndex = 0; float nextTokenX = 0.f; float nextTokenY = 0.f; @@ -3119,7 +3166,7 @@ bool Label::isHorizontalClamp() { bool letterClamp = false; - for (int ctr = 0; ctr < _lengthOfString; ++ctr) + for (int ctr = 0; ctr < getCharCount(); ++ctr) { if (_lettersInfo[ctr].valid) { diff --git a/axmol/2d/Label.h b/axmol/2d/Label.h index 94f8484eb6c9..39ffca2121af 100644 --- a/axmol/2d/Label.h +++ b/axmol/2d/Label.h @@ -91,7 +91,7 @@ typedef struct _ttfConfig class Sprite; class SpriteBatchNode; class DrawNode; -class EventListenerCustom; +class CustomEventListener; class TextureAtlas; /** @@ -148,6 +148,31 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol */ static Label* create(); + /** + * @brief Create a Label with automatic font type detection. + * + * This unified factory method creates a Label using the given text and font name. + * The fontName parameter can be: + * - A TTF font file path (e.g. "fonts/arial.ttf") + * - A system font name (e.g. "Arial", "Helvetica") + * - A BMFont file path (e.g. "fonts/myfont.fnt") + * + * Internally, the method will: + * - Use createWithBMFont() if fontName ends with ".fnt" + * - Use createWithTTF() if fontName exists as a TTF file + * - Otherwise, fall back to createWithSystemFont() + * + * @param text The text string to render. + * @param fontName The font resource identifier (TTF path, system font name, or BMFont path). + * @param fontSize The font size in points. + * @return A new Label instance, or nullptr if creation fails. + * + * @note This method simplifies font handling by providing a single entry point. + * Specific createWithTTF(), createWithSystemFont(), and createWithBMFont() + * methods remain available for explicit control. + */ + static Label* create(std::string_view text, std::string_view fontName, float fontSize); + /** * Allocates and initializes a Label, base on platform-dependent API. * @@ -297,6 +322,25 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol /// @{ /// @name Font methods + /** + * @brief Set font information for the Label. + * + * This method updates the Label's font using the given fontName and fontSize. + * The fontName parameter can be: + * - A TTF font file path (e.g. "fonts/arial.ttf") + * - A system font name (e.g. "Arial", "Helvetica") + * - A BMFont file path (e.g. "fonts/myfont.fnt") + * + * Internally, the method will: + * - Use BMFont if fontName ends with ".fnt" + * - Use TTF if fontName exists as a TTF file + * - Otherwise, fall back to system font + * + * @param fontName The font resource identifier (TTF path, system font name, or BMFont path). + * @param fontSize The font size in points. + */ + void setFontInfo(std::string_view fontName, float fontSize); + /** * Sets a new TTF configuration to Label. * @see `TTFConfig` @@ -376,15 +420,26 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol /** Return the text the Label is currently displaying.*/ std::string_view getString() const override { return _utf8Text; } + [[internal]] std::u32string_view getUTF32String() const { return _utf32Text; } + /** * Return the number of lines of text. */ - int getStringNumLines(); + int getLineCount() const; + AX_DEPRECATED(3.0) int getStringNumLines() { return getLineCount(); } + + /** + * @brief Returns the number of UTF-32 characters. + * + * @return int UTF-32 character count + */ + int getCharCount() const; + AX_DEPRECATED(3.0) int getStringLength() { return getCharCount(); } /** - * Return length of string. + * Return whether the text is empty. */ - int getStringLength(); + bool isEmpty() const { return _utf32Text.empty(); } /** * Sets the text color of Label. @@ -835,7 +890,6 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol float _glowRadius; float _systemFontSize; - int _lengthOfString; int _uniformEffectColor; int _uniformEffectType; // 0: None, 1: Outline, 2: Shadow; Only used when outline is enabled. int _uniformTextColor; @@ -908,7 +962,7 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol std::unordered_map _letters; - EventListenerCustom* _resetTextureListener; + CustomEventListener* _resetTextureListener; #if AX_LABEL_DEBUG_DRAW DrawNode* _debugDrawNode; diff --git a/axmol/2d/Layer.cpp b/axmol/2d/Layer.cpp index f1aeeb5430a6..6136c07f99d7 100644 --- a/axmol/2d/Layer.cpp +++ b/axmol/2d/Layer.cpp @@ -35,12 +35,12 @@ THE SOFTWARE. #include "axmol/renderer/Renderer.h" #include "axmol/base/Director.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventListenerTouch.h" -#include "axmol/base/EventTouch.h" -#include "axmol/base/EventKeyboard.h" -#include "axmol/base/EventListenerKeyboard.h" -#include "axmol/base/EventAcceleration.h" -#include "axmol/base/EventListenerAcceleration.h" +#include "axmol/base/PointerEventListener.h" +#include "axmol/base/PointerEvent.h" +#include "axmol/base/KeyboardEvent.h" +#include "axmol/base/KeyboardEventListener.h" +#include "axmol/base/AccelerationEvent.h" +#include "axmol/base/AccelerationEventListener.h" #include "axmol/base/text_utils.h" #include "axmol/rhi/Buffer.h" #include "axmol/renderer/Shaders.h" diff --git a/axmol/2d/Menu.cpp b/axmol/2d/Menu.cpp index 7c4c97151f32..34ddede606b2 100644 --- a/axmol/2d/Menu.cpp +++ b/axmol/2d/Menu.cpp @@ -28,8 +28,8 @@ THE SOFTWARE. #include "axmol/2d/Menu.h" #include "axmol/scene/Camera.h" #include "axmol/base/Director.h" -#include "axmol/base/Touch.h" -#include "axmol/base/EventListenerTouch.h" +#include "axmol/base/PointerEvent.h" +#include "axmol/base/PointerEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/base/text_utils.h" #include "axmol/platform/StdC.h" @@ -142,15 +142,14 @@ bool Menu::initWithArray(const Vector& arrayOfItems) // enable cascade color and opacity on menus setCascadeColorEnabled(true); - auto touchListener = EventListenerTouchOneByOne::create(); - touchListener->setSwallowTouches(true); + _pointerListener = PointerEventListener::create(); - touchListener->onTouchBegan = AX_CALLBACK_2(Menu::onTouchBegan, this); - touchListener->onTouchMoved = AX_CALLBACK_2(Menu::onTouchMoved, this); - touchListener->onTouchEnded = AX_CALLBACK_2(Menu::onTouchEnded, this); - touchListener->onTouchCancelled = AX_CALLBACK_2(Menu::onTouchCancelled, this); + _pointerListener->onPointerDown = AX_CALLBACK_1(Menu::onPointerDown, this); + _pointerListener->onPointerMove = AX_CALLBACK_1(Menu::onPointerMove, this); + _pointerListener->onPointerUp = AX_CALLBACK_1(Menu::onPointerUp, this); + _pointerListener->onPointerCancel = AX_CALLBACK_1(Menu::onPointerCancel, this); - _eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this); + _eventDispatcher->addEventListenerWithSceneGraphPriority(_pointerListener, this); return true; } @@ -194,10 +193,12 @@ void Menu::onExit() if (_selectedItem) { _selectedItem->unselected(); - _selectedItem = nullptr; } - _state = Menu::State::WAITING; + _selectedItem = nullptr; + _pressedItem = nullptr; + _selectedWithCamera = nullptr; + _state = Menu::State::WAITING; } Node::onExit(); @@ -212,82 +213,129 @@ void Menu::removeChild(Node* child, bool cleanup) _selectedItem = nullptr; } + if (_pressedItem == child) + { + _pressedItem = nullptr; + } + Node::removeChild(child, cleanup); } // Menu - Events -bool Menu::onTouchBegan(Touch* touch, Event* /*event*/) +bool Menu::onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) { - auto camera = Camera::getVisitingCamera(); + if (!event || !camera) + return false; + + if (!event->isPrimaryPressed()) + return false; + + if (_state != Menu::State::WAITING || !_visible || !_enabled) + return false; + + for (Node* c = this->_parent; c != nullptr; c = c->getParent()) + { + if (!c->isVisible()) + return false; + } + + _pressedItem = this->hitTestItem(event, camera, outHitPoint); + return _pressedItem != nullptr; +} + +bool Menu::onPointerDown(PointerEvent* event) +{ + if (!event || !event->isPrimaryPressed()) + return false; + + auto camera = event->getCamera(); if (_state != Menu::State::WAITING || !_visible || !_enabled || !camera) { + _pressedItem = nullptr; + _selectedItem = nullptr; return false; } - for (Node* c = this->_parent; c != nullptr; c = c->getParent()) + if (!_pressedItem) + return false; + + _state = Menu::State::TRACKING_TOUCH; + _selectedWithCamera = camera; + _selectedItem = _pressedItem; + + _selectedItem->selected(); + + return true; +} + +void Menu::onPointerMove(PointerEvent* event) +{ + if (_state != Menu::State::TRACKING_TOUCH || !_selectedWithCamera || !_pressedItem) + return; + + MenuItem* currentItem = this->hitTestItem(event, _selectedWithCamera, nullptr); + + if (currentItem == _pressedItem) { - if (c->isVisible() == false) + if (_selectedItem != _pressedItem) { - return false; + _selectedItem = _pressedItem; + _selectedItem->selected(); } } - _selectedItem = this->getItemForTouch(touch, camera); - - if (_selectedItem) + else { - _state = Menu::State::TRACKING_TOUCH; - _selectedWithCamera = camera; - _selectedItem->selected(); - - return true; + if (_selectedItem) + { + _selectedItem->unselected(); + _selectedItem = nullptr; + } } - - return false; } -void Menu::onTouchEnded(Touch* /*touch*/, Event* /*event*/) +void Menu::onPointerUp(PointerEvent* event) { - AXASSERT(_state == Menu::State::TRACKING_TOUCH, "[Menu ccTouchEnded] -- invalid state"); - this->retain(); + AXASSERT(_state == Menu::State::TRACKING_TOUCH, "[Menu onPointerUp] -- invalid state"); + + RefPtr guard(this); + + auto* itemToActivate = (_selectedItem == _pressedItem) ? _pressedItem : nullptr; + if (_selectedItem) { _selectedItem->unselected(); - _selectedItem->activate(); } + + _selectedItem = nullptr; + _pressedItem = nullptr; _state = Menu::State::WAITING; _selectedWithCamera = nullptr; - this->release(); + + if (itemToActivate) + { + RefPtr guardItem(itemToActivate); + itemToActivate->activate(); + } } -void Menu::onTouchCancelled(Touch* /*touch*/, Event* /*event*/) +void Menu::onPointerCancel(PointerEvent* /*event*/) { - AXASSERT(_state == Menu::State::TRACKING_TOUCH, "[Menu ccTouchCancelled] -- invalid state"); + AXASSERT(_state == Menu::State::TRACKING_TOUCH, "[Menu onPointerCancel] -- invalid state"); + this->retain(); + if (_selectedItem) { _selectedItem->unselected(); } - _state = Menu::State::WAITING; - this->release(); -} -void Menu::onTouchMoved(Touch* touch, Event* /*event*/) -{ - AXASSERT(_state == Menu::State::TRACKING_TOUCH, "[Menu ccTouchMoved] -- invalid state"); - MenuItem* currentItem = this->getItemForTouch(touch, _selectedWithCamera); - if (currentItem != _selectedItem) - { - if (_selectedItem) - { - _selectedItem->unselected(); - } - _selectedItem = currentItem; - if (_selectedItem) - { - _selectedItem->selected(); - } - } + _selectedItem = nullptr; + _pressedItem = nullptr; + _state = Menu::State::WAITING; + _selectedWithCamera = nullptr; + + this->release(); } // Menu - Alignment @@ -526,19 +574,19 @@ void Menu::alignItemsInRowsWithArray(const ValueVector& columns) } } -MenuItem* Menu::getItemForTouch(Touch* touch, const Camera* camera) +MenuItem* Menu::hitTestItem(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) { - Vec2 touchLocation = touch->getLocation(); + Vec2 touchLocation = event->getLocation(); for (const auto& item : _children) { MenuItem* child = dynamic_cast(item); - if (nullptr == child || false == child->isVisible() || false == child->isEnabled()) + if (!child || !child->isVisible() || !child->isEnabled()) { continue; } Rect rect; rect.size = child->getContentSize(); - if (isScreenPointInRect(touchLocation, camera, child->getWorldToNodeTransform(), rect, nullptr)) + if (camera->isWorldPointInRect(touchLocation, child->getWorldToNodeTransform(), rect, outHitPoint)) { return child; } diff --git a/axmol/2d/Menu.h b/axmol/2d/Menu.h index 5b5b99ee2734..4cb8af8a08fa 100644 --- a/axmol/2d/Menu.h +++ b/axmol/2d/Menu.h @@ -30,16 +30,18 @@ THE SOFTWARE. #include "axmol/2d/MenuItem.h" #include "axmol/2d/Layer.h" #include "axmol/base/Value.h" +#include "axmol/base/PointerEvent.h" namespace ax { -class Touch; /** * @addtogroup _2d * @{ */ +class PointerEventListener; + /** @brief A Menu for touch handling. * * Features and Limitation: @@ -133,10 +135,12 @@ class AX_DLL Menu : public Node */ virtual void setEnabled(bool value) { _enabled = value; }; - virtual bool onTouchBegan(Touch* touch, Event* event); - virtual void onTouchEnded(Touch* touch, Event* event); - virtual void onTouchCancelled(Touch* touch, Event* event); - virtual void onTouchMoved(Touch* touch, Event* event); + bool onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) override; + + virtual bool onPointerDown(PointerEvent* event); + virtual void onPointerMove(PointerEvent* event); + virtual void onPointerUp(PointerEvent* event); + virtual void onPointerCancel(PointerEvent* event); // overrides void removeChild(Node* child, bool cleanup) override; @@ -168,11 +172,14 @@ class AX_DLL Menu : public Node /** whether or not the menu will receive events */ bool _enabled; - virtual MenuItem* getItemForTouch(Touch* touch, const Camera* camera); + virtual MenuItem* hitTestItem(PointerEvent* event, const Camera* camera, Vec3* outHitPoint); State _state; MenuItem* _selectedItem; + MenuItem* _pressedItem{nullptr}; const Camera* _selectedWithCamera; + PointerEventListener* _pointerListener{nullptr}; + private: AX_DISALLOW_COPY_AND_ASSIGN(Menu); }; diff --git a/axmol/2d/MenuItem.cpp b/axmol/2d/MenuItem.cpp index 1b330cc0d887..4a804cd2403f 100644 --- a/axmol/2d/MenuItem.cpp +++ b/axmol/2d/MenuItem.cpp @@ -257,7 +257,7 @@ void MenuItemLabel::setEnabled(bool enabled) { if (_enabled != enabled) { - if (enabled == false) + if (!enabled) { _colorBackup = this->getColor(); this->setColor(_disabledColor); diff --git a/axmol/2d/ParticleSystemQuad.cpp b/axmol/2d/ParticleSystemQuad.cpp index 8c7462a8633f..40e6428dc055 100644 --- a/axmol/2d/ParticleSystemQuad.cpp +++ b/axmol/2d/ParticleSystemQuad.cpp @@ -39,7 +39,7 @@ THE SOFTWARE. #include "axmol/base/Director.h" #include "axmol/base/EventType.h" #include "axmol/base/Environment.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/base/text_utils.h" #include "axmol/renderer/Shaders.h" @@ -123,7 +123,7 @@ bool ParticleSystemQuad::initWithTotalParticles(int numberOfParticles) #if AX_ENABLE_CONTEXT_LOSS_RECOVERY // Need to listen the event only when not use batchnode, because it will use VBO - auto listener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, + auto listener = CustomEventListener::create(EVENT_RENDERER_RECREATED, AX_CALLBACK_1(ParticleSystemQuad::listenRendererRecreated, this)); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this); #endif @@ -772,7 +772,7 @@ void ParticleSystemQuad::setTotalParticles(int tp) resetSystem(); } -void ParticleSystemQuad::listenRendererRecreated(EventCustom* /*event*/) +void ParticleSystemQuad::listenRendererRecreated(CustomEvent* /*event*/) { // when comes to foreground in android, _buffersVBO and _VAOname is a wild handle // before recreating, we need to reset them to 0 diff --git a/axmol/2d/ParticleSystemQuad.h b/axmol/2d/ParticleSystemQuad.h index 4eb8e3be317d..6d95d7222aca 100644 --- a/axmol/2d/ParticleSystemQuad.h +++ b/axmol/2d/ParticleSystemQuad.h @@ -36,7 +36,7 @@ namespace ax { class SpriteFrame; -class EventCustom; +class CustomEvent; /** * @addtogroup _2d @@ -106,7 +106,7 @@ class AX_DLL ParticleSystemQuad : public ParticleSystem * * @param event the event that renderer was recreated on Android/WP8. */ - void listenRendererRecreated(EventCustom* event); + void listenRendererRecreated(CustomEvent* event); /** * @lua NA diff --git a/axmol/2d/RenderTexture.cpp b/axmol/2d/RenderTexture.cpp index e4f28c153a6b..aa20ea0765a0 100644 --- a/axmol/2d/RenderTexture.cpp +++ b/axmol/2d/RenderTexture.cpp @@ -32,7 +32,7 @@ THE SOFTWARE. #include "axmol/base/EventType.h" #include "axmol/base/Environment.h" #include "axmol/base/Director.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/renderer/Renderer.h" #include "axmol/scene/Camera.h" @@ -56,15 +56,15 @@ RenderTexture::RenderTexture() // Listen this event to save render texture before come to background. // Then it can be restored after coming to foreground on Android. auto toBackgroundListener = - EventListenerCustom::create(EVENT_COME_TO_BACKGROUND, AX_CALLBACK_1(RenderTexture::listenToBackground, this)); + CustomEventListener::create(EVENT_COME_TO_BACKGROUND, AX_CALLBACK_1(RenderTexture::listenToBackground, this)); _eventDispatcher->addEventListenerWithSceneGraphPriority(toBackgroundListener, this); auto toForegroundListener = - EventListenerCustom::create(EVENT_COME_TO_FOREGROUND, AX_CALLBACK_1(RenderTexture::listenToForeground, this)); + CustomEventListener::create(EVENT_COME_TO_FOREGROUND, AX_CALLBACK_1(RenderTexture::listenToForeground, this)); _eventDispatcher->addEventListenerWithSceneGraphPriority(toForegroundListener, this); // Listen this event to restored texture id after coming to foreground on GLES. - _rendererRecreatedListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { + _rendererRecreatedListener = CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { TextureSliceData emptyData[] = {TextureSliceData{}}; // Invalidate _depthStencilTexture contents and reinitializing GPU resources (e.g., after context loss) // Note: VolatileTextureMgr is responsible for resetting _colorTexture @@ -90,7 +90,7 @@ RenderTexture::~RenderTexture() AX_SAFE_RELEASE(_depthStencilTexture); } -void RenderTexture::listenToBackground(EventCustom* /*event*/) +void RenderTexture::listenToBackground(CustomEvent* /*event*/) { // We have not found a way to dispatch the enter background message before the texture data are destroyed. // So we disable this pair of message handler at present. @@ -117,7 +117,7 @@ void RenderTexture::listenToBackground(EventCustom* /*event*/) #endif } -void RenderTexture::listenToForeground(EventCustom* /*event*/) +void RenderTexture::listenToForeground(CustomEvent* /*event*/) { #if AX_ENABLE_CONTEXT_LOSS_RECOVERY _colorTexture->setAntiAliasTexParameters(); @@ -510,7 +510,7 @@ void RenderTexture::onSaveToFile(std::string filename, bool isRGBA, bool forceNo [self = RefPtr(this), image, _filename, isRGBA, forceNonPMA]() { image->reversePremultipliedAlpha(); - Director::getInstance()->getScheduler()->runOnAxmolThread([self, image, _filename, isRGBA] { + Director::getInstance()->postTask([self, image, _filename, isRGBA] { image->saveToFile(_filename, !isRGBA); if (self->_saveFileCallback) { diff --git a/axmol/2d/RenderTexture.h b/axmol/2d/RenderTexture.h index bd1f2259a430..461f47b71513 100644 --- a/axmol/2d/RenderTexture.h +++ b/axmol/2d/RenderTexture.h @@ -43,7 +43,7 @@ class Texture; class RenderTarget; } // namespace rhi -class EventCustom; +class CustomEvent; struct RenderTextureDesc { @@ -249,14 +249,14 @@ class AX_DLL RenderTexture : public Node * * @param event Event Custom. */ - void listenToBackground(EventCustom* event); + void listenToBackground(CustomEvent* event); /** Listen "come to foreground" message and restore the frame buffer object. * It only has effect on Android. * * @param event Event Custom. */ - void listenToForeground(EventCustom* event); + void listenToForeground(CustomEvent* event); /** Valid when "autoDraw" is true. * diff --git a/axmol/2d/TextFieldTTF.cpp b/axmol/2d/TextFieldTTF.cpp deleted file mode 100644 index e2320d430c5b..000000000000 --- a/axmol/2d/TextFieldTTF.cpp +++ /dev/null @@ -1,773 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010-2012 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#include - -#include "axmol/2d/TextFieldTTF.h" - -#include "axmol/base/Director.h" -#include "axmol/platform/FileUtils.h" -#include "axmol/base/text_utils.h" -#include "axmol/2d/Sprite.h" - -namespace ax -{ - -#define CURSOR_TIME_SHOW_HIDE 0.5f -#define CURSOR_DEFAULT_CHAR '|' -#define PASSWORD_STYLE_TEXT_DEFAULT "\xe2\x80\xa2" -static std::size_t _calcCharCount(const char* text) -{ - int n = 0; - char ch = 0; - while ((ch = *text)) - { - AX_BREAK_IF(!ch); - - if (0x80 != (0xC0 & ch)) - { - ++n; - } - ++text; - } - return n; -} - -bool TextFieldDelegate::onTextFieldAttachWithIME(TextFieldTTF* /*sender*/) -{ - return false; -} - -bool TextFieldDelegate::onTextFieldDetachWithIME(TextFieldTTF* /*sender*/) -{ - return false; -} - -bool TextFieldDelegate::onTextFieldInsertText(TextFieldTTF* /*sender*/, const char* /*text*/, size_t /*nLen*/) -{ - return false; -} - -bool TextFieldDelegate::onTextFieldDeleteBackward(TextFieldTTF* /*sender*/, const char* /*delText*/, size_t /*nLen*/) -{ - return false; -} - -bool TextFieldDelegate::onVisit(TextFieldTTF* /*sender*/, - Renderer* /*renderer*/, - const Mat4& /*transform*/, - uint32_t /*flags*/) -{ - return false; -} - -////////////////////////////////////////////////////////////////////////// -// constructor and destructor -////////////////////////////////////////////////////////////////////////// - -TextFieldTTF::TextFieldTTF() - : _delegate(0) - , _charCount(0) - , _inputText("") - , _placeHolder("") // prevent Label initWithString assertion - , _colorText(Color32::WHITE) - , _secureTextEntry(false) - , _passwordStyleText(PASSWORD_STYLE_TEXT_DEFAULT) - , _cursorEnabled(false) - , _cursorPosition(0) - , _cursorChar(CURSOR_DEFAULT_CHAR) - , _cursorShowingTime(0.0f) - , _isAttachWithIME(false) -{ - _colorSpaceHolder.r = _colorSpaceHolder.g = _colorSpaceHolder.b = 127; - _colorSpaceHolder.a = 255; -} - -TextFieldTTF::~TextFieldTTF() {} - -////////////////////////////////////////////////////////////////////////// -// static constructor -////////////////////////////////////////////////////////////////////////// - -TextFieldTTF* TextFieldTTF::textFieldWithPlaceHolder(std::string_view placeholder, - const Vec2& dimensions, - TextHAlignment alignment, - std::string_view fontName, - float fontSize) -{ - TextFieldTTF* ret = new TextFieldTTF(); - if (ret->initWithPlaceHolder("", dimensions, alignment, fontName, fontSize)) - { - ret->autorelease(); - if (placeholder.size() > 0) - { - ret->setPlaceHolder(placeholder); - } - return ret; - } - AX_SAFE_DELETE(ret); - return nullptr; -} - -TextFieldTTF* TextFieldTTF::textFieldWithPlaceHolder(std::string_view placeholder, - std::string_view fontName, - float fontSize) -{ - TextFieldTTF* ret = new TextFieldTTF(); - if (ret->initWithPlaceHolder("", fontName, fontSize)) - { - ret->autorelease(); - if (placeholder.size() > 0) - { - ret->setPlaceHolder(placeholder); - } - return ret; - } - AX_SAFE_DELETE(ret); - return nullptr; -} - -////////////////////////////////////////////////////////////////////////// -// initialize -////////////////////////////////////////////////////////////////////////// - -bool TextFieldTTF::initWithPlaceHolder(std::string_view placeholder, - const Vec2& dimensions, - TextHAlignment alignment, - std::string_view fontName, - float fontSize) -{ - setDimensions(dimensions.width, dimensions.height); - setAlignment(alignment, TextVAlignment::CENTER); - - return initWithPlaceHolder(placeholder, fontName, fontSize); -} -bool TextFieldTTF::initWithPlaceHolder(std::string_view placeholder, std::string_view fontName, float fontSize) -{ - _placeHolder = placeholder; - - do - { - // If fontName is ttf file and it corrected, use TTFConfig - if (FileUtils::getInstance()->isFileExist(fontName)) - { - TTFConfig ttfConfig(fontName, fontSize, GlyphCollection::DYNAMIC); - if (setTTFConfig(ttfConfig)) - { - break; - } - } - - setSystemFontName(fontName); - setSystemFontSize(fontSize); - - } while (false); - - setTextColorInternally(_colorSpaceHolder); - Label::setString(_placeHolder); - -#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC || AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 || \ - AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) - // On desktop default enable cursor - if (_currentLabelType == LabelType::TTF) - { - setCursorEnabled(true); - } -#endif - - return true; -} - -////////////////////////////////////////////////////////////////////////// -// IMEDelegate -////////////////////////////////////////////////////////////////////////// - -bool TextFieldTTF::attachWithIME() -{ - bool ret = IMEDelegate::attachWithIME(); - if (ret) - { - // open keyboard - auto renderView = _director->getRenderView(); - if (renderView) - renderView->setIMEKeyboardState(true); - } - return ret; -} - -bool TextFieldTTF::detachWithIME() -{ - bool ret = IMEDelegate::detachWithIME(); - if (ret) - { - // close keyboard - auto renderView = _director->getRenderView(); - if (renderView) - renderView->setIMEKeyboardState(false); - } - return ret; -} - -void TextFieldTTF::onExit() -{ - detachWithIME(); - Label::onExit(); -} - -void TextFieldTTF::didAttachWithIME() -{ - setAttachWithIME(true); -} - -void TextFieldTTF::didDetachWithIME() -{ - setAttachWithIME(false); -} - -bool TextFieldTTF::canAttachWithIME() -{ - return (_delegate) ? (!_delegate->onTextFieldAttachWithIME(this)) : true; -} - -bool TextFieldTTF::canDetachWithIME() -{ - return (_delegate) ? (!_delegate->onTextFieldDetachWithIME(this)) : true; -} - -void TextFieldTTF::insertText(const char* text, size_t len) -{ - std::string insert(text, len); - - // insert \n means input end - int pos = static_cast(insert.find(text_utils::AsciiCharacters::NewLine)); - if ((int)insert.npos != pos) - { - len = pos; - insert.erase(pos); - } - - if (len > 0) - { - if (_delegate && _delegate->onTextFieldInsertText(this, insert.c_str(), len)) - { - // delegate doesn't want to insert text - return; - } - - std::size_t countInsertChar = text_utils::countUTF8Chars(insert); - _charCount += countInsertChar; - - if (_cursorEnabled) - { - std::string sText; - sText.reserve(_inputText.length() + insert.length()); - sText += _inputText; - auto pos = text_utils::getUTF8ByteOffset(sText, _cursorPosition); - if (pos != std::string::npos) - sText.insert(pos, insert); - else - sText += insert; - - setCursorPosition(_cursorPosition + countInsertChar); - - setString(sText); - } - else - { - std::string sText(_inputText); - sText.append(insert); - setString(sText); - } - } - - if ((int)insert.npos == pos) - { - return; - } - - // '\n' inserted, let delegate process first - if (_delegate && _delegate->onTextFieldInsertText(this, "\n", 1)) - { - return; - } - - // if delegate hasn't processed, detach from IME by default - detachWithIME(); -} - -void TextFieldTTF::deleteBackward(size_t numChars) -{ - size_t len = _inputText.length(); - if (!len) - { - // there is no string - return; - } - - // Length of characters to delete is based on input editor, but the actual - // length of the displayed text may be less - numChars = std::min(numChars, len); - - size_t totalDeleteLen = 0; - for (auto i = 0; i < numChars; ++i) - { - // get the delete byte number - size_t deleteLen = 1; // default, erase 1 byte - - // Calculate the actual number of bytes to delete for a specific character - while (0x80 == (0xC0 & _inputText.at(len - totalDeleteLen - deleteLen))) - { - ++deleteLen; - } - totalDeleteLen += deleteLen; - } - - if (_delegate && _delegate->onTextFieldDeleteBackward(this, _inputText.c_str() + len - totalDeleteLen, - static_cast(totalDeleteLen))) - { - // delegate doesn't want to delete backwards - return; - } - - // if all text deleted, show placeholder string - if (len <= totalDeleteLen) - { - _inputText = ""; - _charCount = 0; - setCursorPosition(0); - setString(_inputText); - return; - } - - // set new input text - if (_cursorEnabled) - { - if (_cursorPosition) - { - setCursorPosition(_cursorPosition - 1); - - std::string sText(_inputText); - auto nb = text_utils::eraseUTF8CharAt(sText, _cursorPosition); - if (nb) - --_charCount; - - setString(sText); - } - } - else - { - std::string text(_inputText.c_str(), len - totalDeleteLen); - setString(text); - } -} - -std::string_view TextFieldTTF::getContentText() -{ - return _inputText; -} - -void TextFieldTTF::setCursorPosition(std::size_t cursorPosition) -{ - if (_cursorEnabled && cursorPosition <= (std::size_t)_charCount) - { - _cursorPosition = cursorPosition; - _cursorShowingTime = CURSOR_TIME_SHOW_HIDE * 2.0f; - } -} - -void TextFieldTTF::setCursorFromPoint(const Vec2& point, const Camera* camera) -{ - if (_cursorEnabled) - { - // Reset Label, no cursor - bool oldIsAttachWithIME = _isAttachWithIME; - _isAttachWithIME = false; - updateCursorDisplayText(); - - Rect rect; - rect.size = getContentSize(); - if (isScreenPointInRect(point, camera, getWorldToNodeTransform(), rect, nullptr)) - { - int latterPosition = 0; - for (; latterPosition < _lengthOfString; ++latterPosition) - { - if (_lettersInfo[latterPosition].valid && _lettersInfo[latterPosition].atlasIndex >= 0) - { - auto sprite = getLetter(latterPosition); - if (sprite) - { - rect.size = Vec2(sprite->getContentSize().width, _lineHeight); - if (isScreenPointInRect(point, camera, sprite->getWorldToNodeTransform(), rect, nullptr)) - { - setCursorPosition(latterPosition); - break; - } - } - } - } - if (latterPosition == _lengthOfString) - { - setCursorPosition(latterPosition); - } - } - - // Set cursor - _isAttachWithIME = oldIsAttachWithIME; - updateCursorDisplayText(); - } -} - -void TextFieldTTF::setAttachWithIME(bool isAttachWithIME) -{ - if (isAttachWithIME != _isAttachWithIME) - { - _isAttachWithIME = isAttachWithIME; - - if (_isAttachWithIME) - { - setCursorPosition(_charCount); - } - updateCursorDisplayText(); - } -} - -void TextFieldTTF::setTextColorInternally(const Color32& color) -{ - if (_currentLabelType == LabelType::BMFONT) - { - Label::setColor(color); - return; - } - - Label::setTextColor(color); -} - -void TextFieldTTF::setTextColor(const Color32& color) -{ - _colorText = color; - if (!_inputText.empty()) - { - setTextColorInternally(color); - } -} - -void TextFieldTTF::visit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) -{ - if (_delegate && _delegate->onVisit(this, renderer, parentTransform, parentFlags)) - { - return; - } - Label::visit(renderer, parentTransform, parentFlags); -} - -void TextFieldTTF::update(float delta) -{ - if (_cursorEnabled && _isAttachWithIME) - { - _cursorShowingTime -= delta; - if (_cursorShowingTime < -CURSOR_TIME_SHOW_HIDE) - { - _cursorShowingTime = CURSOR_TIME_SHOW_HIDE; - } - // before cursor inserted '\b', need next letter - auto sprite = getLetter((int)_cursorPosition + 1); - - if (sprite) - { - if (_currentLabelType == LabelType::BMFONT) - { - sprite->setColor(getColor()); - } - if (_cursorShowingTime >= 0.0f) - { - sprite->setOpacity(255); - } - else - { - sprite->setOpacity(0); - } - sprite->setDirty(true); - } - } -} - -const Color32& TextFieldTTF::getColorSpaceHolder() -{ - return _colorSpaceHolder; -} - -void TextFieldTTF::setColorSpaceHolder(const Color32& color) -{ - _colorSpaceHolder = color; - if (_inputText.empty()) - { - setTextColorInternally(_colorSpaceHolder); - } -} - -////////////////////////////////////////////////////////////////////////// -// properties -////////////////////////////////////////////////////////////////////////// - -// input text property -void TextFieldTTF::setString(std::string_view text) -{ - std::string displayText; - - std::size_t charCount = 0; - - if (!text.empty()) - { - _inputText = text; - displayText = _inputText; - charCount = _calcCharCount(_inputText.c_str()); - if (_secureTextEntry) - { - displayText = ""; - size_t length = charCount; - while (length) - { - displayText.append(_passwordStyleText); - --length; - } - } - } - else - { - _inputText = ""; - } - - if (_cursorEnabled && charCount != _charCount) - { - _cursorPosition = charCount; - } - - if (_cursorEnabled) - { - // Need for recreate all letters in Label - Label::removeAllChildrenWithCleanup(false); - } - - // if there is no input text, display placeholder instead - if (_inputText.empty() && (!_cursorEnabled || !_isAttachWithIME)) - { - setTextColorInternally(_colorSpaceHolder); - Label::setString(_placeHolder); - } - else - { - makeStringSupportCursor(displayText); - setTextColorInternally(_colorText); - Label::setString(displayText); - } - _charCount = charCount; -} - -void TextFieldTTF::appendString(std::string_view text) -{ - insertText(text.data(), text.length()); -} - -void TextFieldTTF::makeStringSupportCursor(std::string& displayText) -{ - if (_cursorEnabled && _isAttachWithIME) - { - if (displayText.empty()) - { - // \b - Next char not change x position - if (_currentLabelType == LabelType::TTF || _currentLabelType == LabelType::BMFONT) - displayText.push_back(text_utils::AsciiCharacters::NextCharNoChangeX); - displayText.push_back(_cursorChar); - } - else - { - auto numChars = text_utils::countUTF8Chars(displayText); - if (_cursorPosition > numChars) - _cursorPosition = numChars; - - std::string cursorChar; - // \b - Next char not change x position - if (_currentLabelType == LabelType::TTF || _currentLabelType == LabelType::BMFONT) - cursorChar.push_back(text_utils::AsciiCharacters::NextCharNoChangeX); - cursorChar.push_back(_cursorChar); - - auto offset = text_utils::getUTF8ByteOffset(displayText, _cursorPosition); - if (offset != std::string::npos) - displayText.insert(offset, cursorChar); - else - displayText += cursorChar; - } - } -} - -void TextFieldTTF::updateCursorDisplayText() -{ - // Update Label content - setString(_inputText); -} - -void TextFieldTTF::setCursorChar(char cursor) -{ - if (_cursorChar != cursor) - { - _cursorChar = cursor; - updateCursorDisplayText(); - } -} - -void TextFieldTTF::controlKey(EventKeyboard::KeyCode keyCode) -{ - if (_cursorEnabled) - { - switch (keyCode) - { - case EventKeyboard::KeyCode::KEY_HOME: - case EventKeyboard::KeyCode::KEY_KP_HOME: - setCursorPosition(0); - updateCursorDisplayText(); - break; - case EventKeyboard::KeyCode::KEY_END: - setCursorPosition(_charCount); - updateCursorDisplayText(); - break; - case EventKeyboard::KeyCode::KEY_DELETE: - case EventKeyboard::KeyCode::KEY_KP_DELETE: - if (_cursorPosition < (std::size_t)_charCount) - { - std::string sText(_inputText); - auto nb = text_utils::eraseUTF8CharAt(sText, _cursorPosition); - if (nb) - --_charCount; - setCursorPosition(_cursorPosition); - - setString(sText); - } - break; - case EventKeyboard::KeyCode::KEY_LEFT_ARROW: - if (_cursorPosition) - { - setCursorPosition(_cursorPosition - 1); - updateCursorDisplayText(); - } - break; - case EventKeyboard::KeyCode::KEY_RIGHT_ARROW: - if (_cursorPosition < (std::size_t)_charCount) - { - setCursorPosition(_cursorPosition + 1); - updateCursorDisplayText(); - } - break; - case EventKeyboard::KeyCode::KEY_ESCAPE: - detachWithIME(); - break; - default: - break; - } - } -} - -std::string_view TextFieldTTF::getString() const -{ - return _inputText; -} - -// place holder text property -void TextFieldTTF::setPlaceHolder(std::string_view text) -{ - _placeHolder = text; - if (_inputText.empty() && !_isAttachWithIME) - { - setTextColorInternally(_colorSpaceHolder); - Label::setString(_placeHolder); - } -} - -std::string_view TextFieldTTF::getPlaceHolder() const -{ - return _placeHolder; -} - -void TextFieldTTF::setCursorEnabled(bool enabled) -{ - if (_cursorEnabled == enabled) - { - return; - } - - _cursorEnabled = enabled; - if (_cursorEnabled) - { - _cursorPosition = _charCount; - if (_currentLabelType == LabelType::TTF || _currentLabelType == LabelType::BMFONT) - { - scheduleUpdate(); - } - return; - } - - _cursorPosition = 0; - if (_currentLabelType == LabelType::TTF || _currentLabelType == LabelType::BMFONT) - { - unscheduleUpdate(); - } -} - -// secureTextEntry -void TextFieldTTF::setSecureTextEntry(bool value) -{ - if (_secureTextEntry != value) - { - _secureTextEntry = value; - setString(_inputText); - } -} - -void TextFieldTTF::setPasswordTextStyle(std::string_view text) -{ - if (text.length() < 1) - { - return; - } - - if (text != _passwordStyleText) - { - _passwordStyleText = text; - setString(_inputText); - } -} - -std::string_view TextFieldTTF::getPasswordTextStyle() const -{ - return _passwordStyleText; -} - -bool TextFieldTTF::isSecureTextEntry() const -{ - return _secureTextEntry; -} - -} // namespace ax diff --git a/axmol/2d/TextFieldTTF.h b/axmol/2d/TextFieldTTF.h deleted file mode 100644 index 83994b2ce362..000000000000 --- a/axmol/2d/TextFieldTTF.h +++ /dev/null @@ -1,293 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010-2012 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -#include "axmol/2d/Label.h" -#include "axmol/base/IMEDelegate.h" - -/** - * @addtogroup ui - * @{ - */ -namespace ax -{ - -class TextFieldTTF; - -/** - * A input protocol for TextField. - * !!!DEPRECATED since axmol-2.1.3 - * Why DPRECATED? - * 1. lack of cursor support, cursor will overlap with text due to cursor share display text with input text. - * 2. many memory allocations when insert,delete text - * 3. ui::TextField depends on this class, it's not a good design, future we should implement a new ui::TextField which - * is don't depend on this class - * 4. The ui::TextFieldEx maybe a good start point to solve these problems. - */ -class AX_DLL TextFieldDelegate -{ -public: - /** - * Destructor for TextFieldDelegate. - */ - virtual ~TextFieldDelegate() {} - - /** - *@brief If the sender doesn't want to attach to the IME, return true. - */ - virtual bool onTextFieldAttachWithIME(TextFieldTTF* sender); - /** - *@brief If the sender doesn't want to detach from the IME, return true. - */ - virtual bool onTextFieldDetachWithIME(TextFieldTTF* sender); - - /** - *@brief If the sender doesn't want to insert the text, return true. - */ - virtual bool onTextFieldInsertText(TextFieldTTF* sender, const char* text, size_t nLen); - - /** - *@brief If the sender doesn't want to delete the delText, return true. - */ - virtual bool onTextFieldDeleteBackward(TextFieldTTF* sender, const char* delText, size_t nLen); - - /** - *@brief If the sender doesn't want to draw, return true. - */ - virtual bool onVisit(TextFieldTTF* sender, Renderer* renderer, const Mat4& transform, uint32_t flags); -}; - -/** - *@brief A simple text input field with TTF font. - */ -class AX_DLL TextFieldTTF : public Label, public IMEDelegate -{ -public: - /** - * Default constructor. - */ - TextFieldTTF(); - - /** - * Default destructor. - * @lua NA - */ - virtual ~TextFieldTTF(); - - /** Creates a TextFieldTTF from a fontname, alignment, dimension and font size. - */ - static TextFieldTTF* textFieldWithPlaceHolder(std::string_view placeholder, - const Vec2& dimensions, - TextHAlignment alignment, - std::string_view fontName, - float fontSize); - - /** Creates a TextFieldTTF from a fontname and font size. - */ - static TextFieldTTF* textFieldWithPlaceHolder(std::string_view placeholder, - std::string_view fontName, - float fontSize); - - /** Initializes the TextFieldTTF with a font name, alignment, dimension and font size. */ - bool initWithPlaceHolder(std::string_view placeholder, - const Vec2& dimensions, - TextHAlignment alignment, - std::string_view fontName, - float fontSize); - - /** Initializes the TextFieldTTF with a font name and font size. */ - bool initWithPlaceHolder(std::string_view placeholder, std::string_view fontName, float fontSize); - - /** - *@brief Open keyboard and receive input text. - */ - bool attachWithIME() override; - - /** - *@brief End text input and close keyboard. - */ - bool detachWithIME() override; - - ////////////////////////////////////////////////////////////////////////// - // properties - ////////////////////////////////////////////////////////////////////////// - /** - * @lua NA - */ - TextFieldDelegate* getDelegate() const { return _delegate; } - /** - * @lua NA - */ - void setDelegate(TextFieldDelegate* delegate) { _delegate = delegate; } - - /** - * Query the currently inputed character count. - *@return The total input character count. - */ - std::size_t getCharCount() const { return _charCount; } - - /** - * Query the color of place holder. - *@return The place holder color. - */ - virtual const Color32& getColorSpaceHolder(); - - /** - * Change the placeholder color. - *@param color The placeholder color in Color32. - */ - virtual void setColorSpaceHolder(const Color32& color); - - /** - * Change the color of input text. - *@param textColor The text color in Color32. - */ - void setTextColor(const Color32& textColor) override; - - /** - * Change input text of TextField. - *@param text The input text of TextField. - */ - void setString(std::string_view text) override; - - /** - * Append to input text of TextField. - *@param text The append text of TextField. - */ - virtual void appendString(std::string_view text); - - /** - * Query the input text of TextField. - *@return Get the input text of TextField. - */ - std::string_view getString() const override; - - /** - * Change placeholder text. - * place holder text displayed when there is no text in the text field. - *@param text The placeholder string. - */ - virtual void setPlaceHolder(std::string_view text); - - /** - * Query the placeholder string. - *@return The placeholder string. - */ - virtual std::string_view getPlaceHolder() const; - - /** - * Set enable secure text entry representation. - * If you want to display password in TextField, this option is very helpful. - *@param value Whether or not to display text with secure text entry. - */ - virtual void setSecureTextEntry(bool value); - virtual void setPasswordTextStyle(std::string_view text); - std::string_view getPasswordTextStyle() const; - - /** - * Query whether the currently display mode is secure text entry or not. - *@return Whether current text is displayed as secure text entry. - */ - virtual bool isSecureTextEntry() const; - - void visit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) override; - - void update(float delta) override; - - /** - * Set enable cursor use. - */ - void setCursorEnabled(bool enabled); - - /** - * Set char showing cursor. - */ - void setCursorChar(char cursor); - - /** - * Set cursor position, if enabled - */ - void setCursorPosition(std::size_t cursorPosition); - - /** - * Set cursor position to hit letter, if enabled - */ - void setCursorFromPoint(const Vec2& point, const Camera* camera); - -protected: - void onExit() override; - - ////////////////////////////////////////////////////////////////////////// - // IMEDelegate interface - ////////////////////////////////////////////////////////////////////////// - - bool canAttachWithIME() override; - bool canDetachWithIME() override; - void didAttachWithIME() override; - void didDetachWithIME() override; - void insertText(const char* text, size_t len) override; - void deleteBackward(size_t numChars) override; - std::string_view getContentText() override; - void controlKey(EventKeyboard::KeyCode keyCode) override; - - TextFieldDelegate* _delegate; - std::size_t _charCount; - - std::string _inputText; - - std::string _placeHolder; - Color32 _colorSpaceHolder; - Color32 _colorText; - - bool _secureTextEntry; - std::string _passwordStyleText; - - // Need use cursor - bool _cursorEnabled; - // Current position cursor - std::size_t _cursorPosition; - // Char showing cursor - char _cursorChar; - // >0 - show, <0 - hide - float _cursorShowingTime; - - bool _isAttachWithIME; - - void makeStringSupportCursor(std::string& displayText); - void updateCursorDisplayText(); - void setAttachWithIME(bool isAttachWithIME); - void setTextColorInternally(const Color32& color); - -private: - class LengthStack; - LengthStack* _lens; -}; - -} // namespace ax -// end of ui group -/// @} diff --git a/axmol/3d/Animate3D.cpp b/axmol/3d/Animate3D.cpp index 32513183adfa..9b09e4c1fcf7 100644 --- a/axmol/3d/Animate3D.cpp +++ b/axmol/3d/Animate3D.cpp @@ -29,7 +29,7 @@ #include "axmol/3d/Skeleton3D.h" #include "axmol/platform/FileUtils.h" #include "axmol/base/Environment.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/Director.h" #include "axmol/base/EventDispatcher.h" @@ -403,7 +403,7 @@ void Animate3D::update(float t) { auto& frameEvent = _keyFrameEvent[keyFrame.first]; if (frameEvent == nullptr) - frameEvent = new EventCustom(Animate3DDisplayedNotification); + frameEvent = new CustomEvent(Animate3DDisplayedNotification); auto eventInfo = &_displayedEventInfo[keyFrame.first]; eventInfo->target = _target; eventInfo->frame = keyFrame.first; diff --git a/axmol/3d/Animate3D.h b/axmol/3d/Animate3D.h index b2d15ecc0ae4..fb4949ab2b72 100644 --- a/axmol/3d/Animate3D.h +++ b/axmol/3d/Animate3D.h @@ -39,7 +39,7 @@ namespace ax class Bone3D; class MeshRenderer; -class EventCustom; +class CustomEvent; enum class Animate3DQuality { @@ -172,7 +172,7 @@ class AX_DLL Animate3D : public ActionInterval std::unordered_map _nodeCurves; std::unordered_map _keyFrameUserInfos; - std::unordered_map _keyFrameEvent; + std::unordered_map _keyFrameEvent; std::unordered_map _displayedEventInfo; // mesh animations diff --git a/axmol/3d/MeshVertexIndexData.cpp b/axmol/3d/MeshVertexIndexData.cpp index 5a7d5595e7d6..f88a04036491 100644 --- a/axmol/3d/MeshVertexIndexData.cpp +++ b/axmol/3d/MeshVertexIndexData.cpp @@ -35,8 +35,8 @@ #include "axmol/3d/Bundle3D.h" #include "axmol/base/Macros.h" -#include "axmol/base/EventCustom.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEvent.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/base/EventType.h" #include "axmol/base/Director.h" @@ -75,7 +75,7 @@ rhi::Buffer* MeshIndexData::getVertexBuffer() const MeshIndexData::MeshIndexData() { #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - _backToForegroundListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { + _backToForegroundListener = CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { _indexBuffer->updateData((void*)_indexData.data(), _indexData.size_bytes()); }); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, 1); @@ -183,7 +183,7 @@ bool MeshVertexData::hasVertexAttrib(shaderinfos::VertexKey attrib) const MeshVertexData::MeshVertexData() { #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - _backToForegroundListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { + _backToForegroundListener = CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { _vertexBuffer->updateData((void*)_vertexData.data(), _vertexData.size() * sizeof(_vertexData[0])); }); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, 1); diff --git a/axmol/3d/MeshVertexIndexData.h b/axmol/3d/MeshVertexIndexData.h index 121d024d9b8b..4561082431ff 100644 --- a/axmol/3d/MeshVertexIndexData.h +++ b/axmol/3d/MeshVertexIndexData.h @@ -97,7 +97,7 @@ class AX_DLL MeshIndexData : public Object friend class MeshVertexData; friend class MeshRenderer; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _backToForegroundListener = nullptr; + CustomEventListener* _backToForegroundListener = nullptr; #endif }; @@ -151,7 +151,7 @@ class AX_DLL MeshVertexData : public Object int _vertexCount = 0; // vertex count std::vector _vertexData; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _backToForegroundListener = nullptr; + CustomEventListener* _backToForegroundListener = nullptr; #endif }; diff --git a/axmol/3d/Ray.cpp b/axmol/3d/Ray.cpp index 9684974106d1..2062376af3b5 100644 --- a/axmol/3d/Ray.cpp +++ b/axmol/3d/Ray.cpp @@ -26,11 +26,11 @@ THE SOFTWARE. namespace ax { -Ray::Ray() : _direction(0, 0, 1) {} +Ray::Ray() : direction(0, 0, 1) {} Ray::Ray(const Ray& ray) { - set(ray._origin, ray._direction); + set(ray.origin, ray.direction); } Ray::Ray(const Vec3& origin, const Vec3& direction) @@ -48,8 +48,8 @@ bool Ray::intersects(const AABB& box, float* distance) const Vec3 hitpoint; const Vec3& min = box._min; const Vec3& max = box._max; - const Vec3& rayorig = _origin; - const Vec3& raydir = _direction; + const Vec3& rayorig = origin; + const Vec3& raydir = direction; // Check origin inside first if (rayorig > min && rayorig < max) @@ -167,8 +167,8 @@ bool Ray::intersects(const OBB& obb, float* distance) const aabb._max = obb._extents; Ray ray; - ray._direction = _direction; - ray._origin = _origin; + ray.direction = direction; + ray.origin = origin; Mat4 mat = Mat4::IDENTITY; mat.m[0] = obb._xAxis.x; @@ -196,31 +196,31 @@ bool Ray::intersects(const OBB& obb, float* distance) const float Ray::dist(const Plane& plane) const { - float ndd = Vec3::dot(plane.getNormal(), _direction); + float ndd = Vec3::dot(plane.getNormal(), direction); if (ndd == 0) return 0.0f; - float ndo = Vec3::dot(plane.getNormal(), _origin); + float ndo = Vec3::dot(plane.getNormal(), origin); return (plane.getDist() - ndo) / ndd; } Vec3 Ray::intersects(const Plane& plane) const { float dis = this->dist(plane); - return _origin + dis * _direction; + return origin + dis * direction; } -void Ray::set(const Vec3& origin, const Vec3& direction) +void Ray::set(const Vec3& orig, const Vec3& dir) { - _origin = origin; - _direction = direction; - _direction.normalize(); + this->origin = orig; + this->direction = dir; + this->direction.normalize(); } void Ray::transform(const Mat4& matrix) { - matrix.transformPoint(&_origin); - matrix.transformVector(&_direction); - _direction.normalize(); + matrix.transformPoint(&origin); + matrix.transformVector(&direction); + this->direction.normalize(); } } // namespace ax diff --git a/axmol/3d/Ray.h b/axmol/3d/Ray.h index 5c76f47388cd..945f88f56210 100644 --- a/axmol/3d/Ray.h +++ b/axmol/3d/Ray.h @@ -100,8 +100,8 @@ class AX_DLL Ray */ void transform(const Mat4& matrix); - Vec3 _origin; // The ray origin position. - Vec3 _direction; // The ray direction vector. + Vec3 origin; // The ray origin position. + Vec3 direction; // The ray direction vector. }; // end of 3d group diff --git a/axmol/3d/Terrain.cpp b/axmol/3d/Terrain.cpp index 49c28916d7b7..6c89aac7d984 100644 --- a/axmol/3d/Terrain.cpp +++ b/axmol/3d/Terrain.cpp @@ -253,7 +253,7 @@ Terrain::Terrain() { #if AX_ENABLE_CONTEXT_LOSS_RECOVERY _backToForegroundListener = - EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { reload(); }); + CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { reload(); }); _director->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, 1); #endif _dummyTexture = _director->getTextureCache()->getWhiteTexture(); @@ -511,11 +511,11 @@ bool Terrain::getIntersectionPoint(const Ray& ray_, Vec3& intersectionPoint) con { // convert ray from world space to local space Ray ray(ray_); - getWorldToNodeTransform().transformPoint(&(ray._origin)); + getWorldToNodeTransform().transformPoint(&(ray.origin)); std::set closeList; - Vec2 start = Vec2(ray_._origin.x, ray_._origin.z); - Vec2 dir = Vec2(ray._direction.x, ray._direction.z); + Vec2 start = Vec2(ray_.origin.x, ray_.origin.z); + Vec2 dir = Vec2(ray.direction.x, ray.direction.z); start = convertToTerrainSpace(start); start.x /= (_terrainData._chunkSize.width + 1); start.y /= (_terrainData._chunkSize.height + 1); @@ -542,7 +542,7 @@ bool Terrain::getIntersectionPoint(const Ray& ray_, Vec3& intersectionPoint) con { if (chunk->getIntersectPointWithRay(ray, tmpIntersectionPoint)) { - float dist = (ray._origin - tmpIntersectionPoint).length(); + float dist = (ray.origin - tmpIntersectionPoint).length(); if (intersectionDist > dist) { hasIntersect = true; @@ -1357,7 +1357,7 @@ bool Terrain::Chunk::getIntersectPointWithRay(const Ray& ray, Vec3& intersectPoi Vec3 p; if (triangle.getIntersectPoint(ray, p)) { - float dist = ray._origin.distance(p); + float dist = ray.origin.distance(p); if (dist < minDist) { intersectPoint = p; @@ -1735,7 +1735,7 @@ bool Terrain::Triangle::getIntersectPoint(const Ray& ray, Vec3& intersectPoint) // P Vec3 P; - Vec3::cross(ray._direction, E2, &P); + Vec3::cross(ray.direction, E2, &P); // determinant float det = E1.dot(P); @@ -1744,11 +1744,11 @@ bool Terrain::Triangle::getIntersectPoint(const Ray& ray, Vec3& intersectPoint) Vec3 T; if (det > 0) { - T = ray._origin - _p1; + T = ray.origin - _p1; } else { - T = _p1 - ray._origin; + T = _p1 - ray.origin; det = -det; } @@ -1768,7 +1768,7 @@ bool Terrain::Triangle::getIntersectPoint(const Ray& ray, Vec3& intersectPoint) Vec3::cross(T, E1, &Q); // Calculate v and make sure u + v <= 1 - v = ray._direction.dot(Q); + v = ray.direction.dot(Q); if (v < 0.0f || u + v > det) return false; @@ -1778,7 +1778,7 @@ bool Terrain::Triangle::getIntersectPoint(const Ray& ray, Vec3& intersectPoint) float fInvDet = 1.0f / det; t *= fInvDet; - intersectPoint = ray._origin + ray._direction * t; + intersectPoint = ray.origin + ray.direction * t; return true; } diff --git a/axmol/3d/Terrain.h b/axmol/3d/Terrain.h index 1cd72f7c2323..2b1981f9650f 100644 --- a/axmol/3d/Terrain.h +++ b/axmol/3d/Terrain.h @@ -37,7 +37,7 @@ THE SOFTWARE. #include "axmol/rhi/ProgramState.h" #include "axmol/3d/AABB.h" #include "axmol/3d/Ray.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" namespace ax @@ -584,7 +584,7 @@ class AX_DLL Terrain : public Node rhi::UniformLocation _mvpMatrixLocation; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _backToForegroundListener; + CustomEventListener* _backToForegroundListener; #endif }; diff --git a/axmol/CMakeLists.txt b/axmol/CMakeLists.txt index 5251763a09ad..388b40dc1def 100644 --- a/axmol/CMakeLists.txt +++ b/axmol/CMakeLists.txt @@ -177,7 +177,7 @@ cmake_dependent_option(AX_ENABLE_VLC_MEDIA "Enabling vlc media" OFF "(WIN32 AND cmake_dependent_option(AX_ENABLE_WAYLAND "Enabling linux wayland" OFF "LINUX" OFF) option(AX_ENABLE_PHYSICS_2D "Build Physics2D support" ON) -cmake_dependent_option(AX_ENABLE_MEDIA "Build media support" ON "AX_ENABLE_MFMEDIA OR AX_ENABLE_VLC_MEDIA OR APPLE OR ANDROID" OFF) +cmake_dependent_option(AX_ENABLE_VIDEO "Build video support" ON "AX_ENABLE_MFMEDIA OR AX_ENABLE_VLC_MEDIA OR APPLE OR ANDROID" OFF) option(AX_ENABLE_AUDIO "Build audio support" ON) option(AX_ENABLE_OPUS "Build with opus support" ON) option(AX_ENABLE_CONSOLE "Build axmol debug tool: console support" ON) @@ -292,7 +292,7 @@ if(AX_ENABLE_AUDIO) include(audio/CMakeLists.txt) endif() -if(AX_ENABLE_MEDIA) +if(AX_ENABLE_VIDEO) file(GLOB_RECURSE _AX_MEDIA_HEADER media/*.h) file(GLOB_RECURSE _AX_MEDIA_SRC media/*.cpp) @@ -349,7 +349,7 @@ if(AX_ENABLE_NAVMESH) list(APPEND _AX_SRC ${_AX_NAVMESH_SRC}) endif() -if(AX_ENABLE_MEDIA) +if(AX_ENABLE_VIDEO) list(APPEND _AX_HEADER ${_AX_MEDIA_HEADER}) list(APPEND _AX_SRC ${_AX_MEDIA_SRC}) endif() @@ -388,6 +388,11 @@ elseif(AX_ENABLE_MFMEDIA AND NOT WINRT) endif() endif() +if(IOS) + # Add Obj-C source files for iOS platform to avoid hack categories not working without -ObjC flag + target_link_options(${_AX_CORE_LIB} INTERFACE -ObjC) +endif() + ax_find_shaders(${_AX_ROOT}/axmol/renderer/shaders _builtin_shaders) set(_AX_BUILTIN_SHADERS ${_builtin_shaders} CACHE STATIC "" FORCE) @@ -435,7 +440,7 @@ ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_PHYSICS_2D) ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_3D) ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_PHYSICS_3D) ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_NAVMESH) -ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_MEDIA) +ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_VIDEO) ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_AUDIO) ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_CONSOLE) ax_config_pred(${_AX_CORE_LIB} AX_ENABLE_VR) diff --git a/axmol/audio/AudioCache.cpp b/axmol/audio/AudioCache.cpp index c4a7a37bfe14..a1a7ee011ba7 100644 --- a/axmol/audio/AudioCache.cpp +++ b/axmol/audio/AudioCache.cpp @@ -392,8 +392,7 @@ void AudioCache::invokingLoadCallbacks() } auto isDestroyed = _isDestroyed; - auto scheduler = Director::getInstance()->getScheduler(); - scheduler->runOnAxmolThread([&, isDestroyed]() { + Director::getInstance()->postTask([&, isDestroyed]() { if (*isDestroyed) { AXLOGV("invokingLoadCallbacks perform in axmol thread, AudioCache ({}) was destroyed!", fmt::ptr(this)); diff --git a/axmol/audio/AudioEngineImpl.cpp b/axmol/audio/AudioEngineImpl.cpp index 531377b97746..198e3c6e958d 100644 --- a/axmol/audio/AudioEngineImpl.cpp +++ b/axmol/audio/AudioEngineImpl.cpp @@ -243,12 +243,12 @@ static id s_AudioEngineSessionHandler = nullptr; # endif static void alcReopenDeviceOnAxmolThread() { - ax::Director::getInstance()->queueOperation([](void*) { + ax::Director::getInstance()->postTask([]() { auto alcReopenDeviceSOFTProc = (decltype(alcReopenDeviceSOFT)*)alcGetProcAddress(s_ALDevice, "alcReopenDeviceSOFT"); if (alcReopenDeviceSOFTProc) alcReopenDeviceSOFTProc(s_ALDevice, nullptr, nullptr); - }); + }, ax::Director::TaskTiming::FrameBoundary); } # if defined(ALC_SOFT_system_events) && (defined(_WIN32) || AX_TARGET_PLATFORM == AX_PLATFORM_MAC) @@ -655,7 +655,7 @@ void AudioEngineImpl::_play2d(AudioCache* cache, AUDIO_ID audioID) { if (player->play2d()) { - _scheduler->runOnAxmolThread([audioID]() { + Director::getInstance()->postTask([audioID]() { if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end()) { AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING; @@ -683,7 +683,7 @@ void AudioEngineImpl::_play3d(AudioCache* cache, int audioID) { if (player->play3d()) { - _scheduler->runOnAxmolThread([audioID]() { + Director::getInstance()->postTask([audioID]() { if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end()) { AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING; diff --git a/axmol/axmol.h b/axmol/axmol.h index d7e04c434a8f..25fd453f9dd3 100644 --- a/axmol/axmol.h +++ b/axmol/axmol.h @@ -42,8 +42,8 @@ THE SOFTWARE. #include "axmol/base/Logging.h" #include "axmol/base/Data.h" #include "axmol/base/Director.h" -#include "axmol/base/IMEDelegate.h" -#include "axmol/base/IMEDispatcher.h" +#include "axmol/base/InputDelegate.h" +#include "axmol/base/InputSystem.h" #include "axmol/base/Map.h" #include "axmol/base/Profiling.h" #include "axmol/base/Properties.h" @@ -62,22 +62,20 @@ THE SOFTWARE. #include "axmol/base/Utils.h" // EventDispatcher -#include "axmol/base/EventAcceleration.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/AccelerationEvent.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventFocus.h" -#include "axmol/base/EventKeyboard.h" -#include "axmol/base/EventListenerAcceleration.h" -#include "axmol/base/EventListenerCustom.h" -#include "axmol/base/EventListenerFocus.h" -#include "axmol/base/EventListenerKeyboard.h" -#include "axmol/base/EventListenerMouse.h" -#include "axmol/base/EventListenerController.h" -#include "axmol/base/EventListenerTouch.h" -#include "axmol/base/EventMouse.h" -#include "axmol/base/EventController.h" +#include "axmol/base/FocusEvent.h" +#include "axmol/base/KeyboardEvent.h" +#include "axmol/base/AccelerationEventListener.h" +#include "axmol/base/CustomEventListener.h" +#include "axmol/base/FocusEventListener.h" +#include "axmol/base/KeyboardEventListener.h" +#include "axmol/base/ControllerEventListener.h" +#include "axmol/base/PointerEventListener.h" +#include "axmol/base/ControllerEvent.h" #include "axmol/base/Controller.h" -#include "axmol/base/EventTouch.h" +#include "axmol/base/PointerEvent.h" #include "axmol/base/EventType.h" // math @@ -201,7 +199,7 @@ THE SOFTWARE. # include "axmol/platform/wasm/StdC-wasm.h" #endif // AX_TARGET_PLATFORM == AX_PLATFORM_WASM -#include "axmol/platform/RenderViewImpl.h" +#include "axmol/platform/RenderView.h" #if AX_ENABLE_GL # include "axmol/platform/GL.h" @@ -220,9 +218,6 @@ THE SOFTWARE. #include "axmol/2d/SpriteFrame.h" #include "axmol/2d/SpriteFrameCache.h" -// text_input_node -#include "axmol/2d/TextFieldTTF.h" - // textures #include "axmol/renderer/TextureAtlas.h" diff --git a/axmol/base/EventAcceleration.cpp b/axmol/base/AccelerationEvent.cpp similarity index 88% rename from axmol/base/EventAcceleration.cpp rename to axmol/base/AccelerationEvent.cpp index 10a088ed8ba6..3a33903812d8 100644 --- a/axmol/base/EventAcceleration.cpp +++ b/axmol/base/AccelerationEvent.cpp @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -23,11 +24,11 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/EventAcceleration.h" +#include "axmol/base/AccelerationEvent.h" namespace ax { -EventAcceleration::EventAcceleration(const Acceleration& acc) : Event(Type::ACCELERATION), _acc(acc) {} +AccelerationEvent::AccelerationEvent(const Acceleration& acc) : Event(Type::ACCELERATION), _acc(acc) {} } // namespace ax diff --git a/axmol/base/EventAcceleration.h b/axmol/base/AccelerationEvent.h similarity index 81% rename from axmol/base/EventAcceleration.h rename to axmol/base/AccelerationEvent.h index 4c54f018d5b1..c577a87b9cc8 100644 --- a/axmol/base/EventAcceleration.h +++ b/axmol/base/AccelerationEvent.h @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -36,23 +37,28 @@ namespace ax { -/** @class EventAcceleration +/** @class AccelerationEvent * @brief Accelerometer event. */ -class AX_DLL EventAcceleration : public Event +class AX_DLL AccelerationEvent : public Event { public: /** Constructor. * * @param acc A given Acceleration. */ - EventAcceleration(const Acceleration& acc); + AccelerationEvent(const Acceleration& acc); + + const Acceleration& getAcceleration() const { return _acc; } private: Acceleration _acc; - friend class EventListenerAcceleration; + friend class AccelerationEventListener; }; +// deprecated alias +using EventAcceleration = AccelerationEvent; + } // namespace ax // end of base group diff --git a/axmol/base/EventListenerAcceleration.cpp b/axmol/base/AccelerationEventListener.cpp similarity index 66% rename from axmol/base/EventListenerAcceleration.cpp rename to axmol/base/AccelerationEventListener.cpp index fffc79188253..e7448e4b4e70 100644 --- a/axmol/base/EventListenerAcceleration.cpp +++ b/axmol/base/AccelerationEventListener.cpp @@ -24,25 +24,25 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/EventListenerAcceleration.h" -#include "axmol/base/EventAcceleration.h" +#include "axmol/base/AccelerationEventListener.h" +#include "axmol/base/AccelerationEvent.h" #include "axmol/base/Logging.h" namespace ax { -const std::string_view EventListenerAcceleration::LISTENER_ID = "__ax_acceleration"sv; +const std::string_view AccelerationEventListener::LISTENER_ID = "__ax_acceleration"sv; -EventListenerAcceleration::EventListenerAcceleration() {} +AccelerationEventListener::AccelerationEventListener() {} -EventListenerAcceleration::~EventListenerAcceleration() +AccelerationEventListener::~AccelerationEventListener() { AXLOGV("In the destructor of AccelerationEventListener. {}", fmt::ptr(this)); } -EventListenerAcceleration* EventListenerAcceleration::create(const std::function& callback) +AccelerationEventListener* AccelerationEventListener::create(const std::function& callback) { - EventListenerAcceleration* ret = new EventListenerAcceleration(); + AccelerationEventListener* ret = new AccelerationEventListener(); if (ret->init(callback)) { ret->autorelease(); @@ -55,27 +55,27 @@ EventListenerAcceleration* EventListenerAcceleration::create(const std::function return ret; } -bool EventListenerAcceleration::init(const std::function& callback) +bool AccelerationEventListener::init(const std::function& callback) { auto listener = [this](Event* event) { - auto accEvent = static_cast(event); - this->onAccelerationEvent(&accEvent->_acc, event); + auto accEvent = static_cast(event); + this->onAcceleration(accEvent); }; if (EventListener::init(Type::ACCELERATION, LISTENER_ID, listener)) { - onAccelerationEvent = callback; + onAcceleration = callback; return true; } return false; } -EventListenerAcceleration* EventListenerAcceleration::clone() +AccelerationEventListener* AccelerationEventListener::clone() { - auto ret = new EventListenerAcceleration(); + auto ret = new AccelerationEventListener(); - if (ret->init(onAccelerationEvent)) + if (ret->init(onAcceleration)) { ret->autorelease(); } @@ -87,9 +87,9 @@ EventListenerAcceleration* EventListenerAcceleration::clone() return ret; } -bool EventListenerAcceleration::checkAvailable() +bool AccelerationEventListener::checkAvailable() { - AXASSERT(onAccelerationEvent, "onAccelerationEvent can't be nullptr!"); + AXASSERT(onAcceleration, "onAcceleration can't be nullptr!"); return true; } diff --git a/axmol/base/EventListenerAcceleration.h b/axmol/base/AccelerationEventListener.h similarity index 72% rename from axmol/base/EventListenerAcceleration.h rename to axmol/base/AccelerationEventListener.h index b60d1036506c..82b74713e6ee 100644 --- a/axmol/base/EventListenerAcceleration.h +++ b/axmol/base/AccelerationEventListener.h @@ -27,7 +27,7 @@ #pragma once #include "axmol/base/EventListener.h" -#include "axmol/base/Types.h" +#include "axmol/base/AccelerationEvent.h" /** * @addtogroup base @@ -36,11 +36,10 @@ namespace ax { - -/** @class EventListenerAcceleration +/** @class AccelerationEventListener * @brief Acceleration event listener. */ -class AX_DLL EventListenerAcceleration : public EventListener +class AX_DLL AccelerationEventListener : public EventListener { public: static const std::string_view LISTENER_ID; @@ -48,28 +47,31 @@ class AX_DLL EventListenerAcceleration : public EventListener /** Create a acceleration EventListener. * * @param callback The acceleration callback method. - * @return An autoreleased EventListenerAcceleration object. + * @return An autoreleased AccelerationEventListener object. */ - static EventListenerAcceleration* create(const std::function& callback); + static AccelerationEventListener* create(const std::function& callback); /** Destructor. */ - virtual ~EventListenerAcceleration(); + virtual ~AccelerationEventListener(); /// Overrides - EventListenerAcceleration* clone() override; + AccelerationEventListener* clone() override; bool checkAvailable() override; - EventListenerAcceleration(); + AccelerationEventListener(); - bool init(const std::function& callback); + bool init(const std::function& callback); private: - std::function onAccelerationEvent; + std::function onAcceleration; - friend class LuaEventListenerAcceleration; + friend class LuaAccelerationEventListener; }; +// deprecated alias +using EventListenerAcceleration = AccelerationEventListener; + } // namespace ax // end of base group diff --git a/axmol/base/CMakeLists.txt b/axmol/base/CMakeLists.txt index 53b918b1e5e5..0a57c165a85d 100644 --- a/axmol/base/CMakeLists.txt +++ b/axmol/base/CMakeLists.txt @@ -18,17 +18,15 @@ set(_AX_BASE_HEADER base/astc.h base/pvr.h base/Value.h - base/EventListenerMouse.h base/atitc.h - base/EventTouch.h + base/PointerEvent.h base/Data.h base/Macros.h - base/EventAcceleration.h - base/EventListenerKeyboard.h + base/AccelerationEvent.h + base/KeyboardEventListener.h base/Controller.h - base/Touch.h base/base64.h - base/EventListenerController.h + base/ControllerEventListener.h base/s3tc.h base/etc1.h base/etc2.h @@ -43,17 +41,17 @@ set(_AX_BASE_HEADER base/ObjectFactory.h base/Properties.h base/Vector.h - base/EventCustom.h - base/EventKeyboard.h + base/CustomEvent.h + base/KeyboardEvent.h base/NinePatchImageParser.h - base/EventListenerCustom.h + base/CustomEventListener.h base/EventDispatcher.h base/Utils.h - base/EventController.h + base/ControllerEvent.h base/RefPtr.h base/WeakPtr.h base/Director.h - base/EventListenerFocus.h + base/FocusEventListener.h base/UserDefault.h base/Config.h base/FPSImages.h @@ -61,21 +59,20 @@ set(_AX_BASE_HEADER base/Map.h base/text_utils.h base/ScriptSupport.h - base/EventFocus.h + base/FocusEvent.h base/Environment.h base/Protocols.h base/TGAlib.h - base/EventMouse.h - base/IMEDelegate.h + base/InputDelegate.h base/AutoreleasePool.h base/StencilStateManager.h - base/EventListenerTouch.h - base/EventListenerAcceleration.h + base/PointerEventListener.h + base/AccelerationEventListener.h base/firePngData.h base/EventListener.h base/Scheduler.h base/EventType.h - base/IMEDispatcher.h + base/InputSystem.h base/JsonWriter.h base/JobSystem.h ) @@ -90,30 +87,27 @@ set(_AX_BASE_SRC base/NinePatchImageParser.cpp base/Director.cpp base/Event.cpp - base/EventAcceleration.cpp - base/EventController.cpp - base/EventCustom.cpp + base/AccelerationEvent.cpp + base/ControllerEvent.cpp + base/CustomEvent.cpp base/EventDispatcher.cpp - base/EventFocus.cpp - base/EventKeyboard.cpp + base/FocusEvent.cpp + base/KeyboardEvent.cpp base/EventListener.cpp - base/EventListenerAcceleration.cpp - base/EventListenerController.cpp - base/EventListenerCustom.cpp - base/EventListenerFocus.cpp - base/EventListenerKeyboard.cpp - base/EventListenerMouse.cpp - base/EventListenerTouch.cpp - base/EventMouse.cpp - base/EventTouch.cpp - base/IMEDispatcher.cpp + base/AccelerationEventListener.cpp + base/ControllerEventListener.cpp + base/CustomEventListener.cpp + base/FocusEventListener.cpp + base/KeyboardEventListener.cpp + base/PointerEventListener.cpp + base/PointerEvent.cpp + base/InputSystem.cpp base/Profiling.cpp base/Properties.cpp base/Object.cpp base/WeakPtr.cpp base/Scheduler.cpp base/ScriptSupport.cpp - base/Touch.cpp base/UserDefault.cpp base/Value.cpp base/ObjectFactory.cpp diff --git a/axmol/base/Console.cpp b/axmol/base/Console.cpp index b58395d41818..09fd6c264ccf 100644 --- a/axmol/base/Console.cpp +++ b/axmol/base/Console.cpp @@ -941,8 +941,7 @@ void Console::commandAllocator(socket_native_type fd, std::string_view /*args*/) void Console::commandConfig(socket_native_type fd, std::string_view /*args*/) { - Scheduler* sched = Director::getInstance()->getScheduler(); - sched->runOnAxmolThread([=]() { + Director::getInstance()->postTask([=]() { Console::Utility::mydprintf(fd, "%s", Environment::getInstance()->getInfo().c_str()); Console::Utility::sendPrompt(fd); }); @@ -960,9 +959,8 @@ void Console::commandDebugMsgSubCommandOnOff(socket_native_type /*fd*/, std::str void Console::commandDirectorSubCommandPause(socket_native_type /*fd*/, std::string_view /*args*/) { - auto director = Director::getInstance(); - Scheduler* sched = director->getScheduler(); - sched->runOnAxmolThread([]() { Director::getInstance()->pause(); }); + auto director = Director::getInstance(); + director->postTask([]() { Director::getInstance()->pause(); }); } void Console::commandDirectorSubCommandResume(socket_native_type /*fd*/, std::string_view /*args*/) @@ -973,15 +971,14 @@ void Console::commandDirectorSubCommandResume(socket_native_type /*fd*/, std::st void Console::commandDirectorSubCommandStop(socket_native_type /*fd*/, std::string_view /*args*/) { - auto director = Director::getInstance(); - Scheduler* sched = director->getScheduler(); - sched->runOnAxmolThread([]() { Director::getInstance()->stopAnimation(); }); + auto director = Director::getInstance(); + director->postTask([]() { Director::getInstance()->stopAnimation(); }); } void Console::commandDirectorSubCommandStart(socket_native_type /*fd*/, std::string_view /*args*/) { auto director = Director::getInstance(); - director->startAnimation(); + director->postTask([]() { Director::getInstance()->startAnimation(); }); } void Console::commandDirectorSubCommandEnd(socket_native_type /*fd*/, std::string_view /*args*/) @@ -999,8 +996,7 @@ void Console::commandExit(socket_native_type fd, std::string_view /*args*/) void Console::commandFileUtils(socket_native_type fd, std::string_view /*args*/) { - Scheduler* sched = Director::getInstance()->getScheduler(); - sched->runOnAxmolThread(std::bind(&Console::printFileUtils, this, fd)); + Director::getInstance()->postTask(std::bind(&Console::printFileUtils, this, fd)); } void Console::commandFileUtilsSubCommandFlush(socket_native_type /*fd*/, std::string_view /*args*/) @@ -1015,10 +1011,9 @@ void Console::commandFps(socket_native_type fd, std::string_view /*args*/) void Console::commandFpsSubCommandOnOff(socket_native_type /*fd*/, std::string_view args) { - bool state = (args.compare("on") == 0); - Director* dir = Director::getInstance(); - Scheduler* sched = dir->getScheduler(); - sched->runOnAxmolThread(std::bind(&Director::setStatsDisplay, dir, state)); + bool state = (args.compare("on") == 0); + Director* director = Director::getInstance(); + director->postTask(std::bind(&Director::setStatsDisplay, director, state)); } void Console::commandHelp(socket_native_type fd, std::string_view /*args*/) @@ -1051,16 +1046,15 @@ void Console::commandProjection(socket_native_type fd, std::string_view /*args*/ void Console::commandProjectionSubCommand2d(socket_native_type /*fd*/, std::string_view /*args*/) { - auto director = Director::getInstance(); - Scheduler* sched = director->getScheduler(); - sched->runOnAxmolThread([=]() { director->setProjection(Director::Projection::_2D); }); + auto director = Director::getInstance(); + director->postTask([=]() { director->setProjection(Director::Projection::_2D); }); } void Console::commandProjectionSubCommand3d(socket_native_type /*fd*/, std::string_view /*args*/) { auto director = Director::getInstance(); Scheduler* sched = director->getScheduler(); - sched->runOnAxmolThread([=]() { director->setProjection(Director::Projection::_3D); }); + director->postTask([=]() { director->setProjection(Director::Projection::_3D); }); } void Console::commandResolution(socket_native_type /*fd*/, std::string_view args) @@ -1071,8 +1065,7 @@ void Console::commandResolution(socket_native_type /*fd*/, std::string_view args stream << args; stream >> width >> height >> policy; - Scheduler* sched = Director::getInstance()->getScheduler(); - sched->runOnAxmolThread([=]() { + Director::getInstance()->postTask([=]() { Director::getInstance()->getRenderView()->setDesignResolutionSize(width, height, static_cast(policy)); }); @@ -1104,14 +1097,12 @@ void Console::commandResolutionSubCommandEmpty(socket_native_type fd, std::strin void Console::commandSceneGraph(socket_native_type fd, std::string_view /*args*/) { - Scheduler* sched = Director::getInstance()->getScheduler(); - sched->runOnAxmolThread(std::bind(&Console::printSceneGraphBoot, this, fd)); + Director::getInstance()->postTask(std::bind(&Console::printSceneGraphBoot, this, fd)); } void Console::commandTextures(socket_native_type fd, std::string_view /*args*/) { - Scheduler* sched = Director::getInstance()->getScheduler(); - sched->runOnAxmolThread([=]() { + Director::getInstance()->postTask([=]() { Console::Utility::mydprintf(fd, "%s", Director::getInstance()->getTextureCache()->getCachedTextureInfo().c_str()); Console::Utility::sendPrompt(fd); @@ -1120,8 +1111,7 @@ void Console::commandTextures(socket_native_type fd, std::string_view /*args*/) void Console::commandTexturesSubCommandFlush(socket_native_type /*fd*/, std::string_view /*args*/) { - Scheduler* sched = Director::getInstance()->getScheduler(); - sched->runOnAxmolThread([]() { Director::getInstance()->getTextureCache()->removeAllTextures(); }); + Director::getInstance()->postTask([]() { Director::getInstance()->getTextureCache()->removeAllTextures(); }); } void Console::commandTouchSubCommandTap(socket_native_type fd, std::string_view args) @@ -1149,11 +1139,11 @@ void Console::commandTouchSubCommandTap(socket_native_type fd, std::string_view if (argi == 3) { std::srand((unsigned)time(nullptr)); - _touchId = rand(); - Scheduler* sched = Director::getInstance()->getScheduler(); - sched->runOnAxmolThread([&]() { - Director::getInstance()->getRenderView()->handleTouchesBegin(1, &_touchId, &x, &y); - Director::getInstance()->getRenderView()->handleTouchesEnd(1, &_touchId, &x, &y); + _touchId = rand(); + Director::getInstance()->postTask([this, x, y]() { + PointerInputState pointerDownData{.id = _touchId}; + InputSystem::getInstance()->handlePointerDown(Vec2{x, y}, pointerDownData); + InputSystem::getInstance()->handlePointerUp(Vec2{x, y}, pointerDownData); }); } else @@ -1197,10 +1187,10 @@ void Console::commandTouchSubCommandSwipe(socket_native_type fd, std::string_vie std::srand((unsigned)time(nullptr)); _touchId = rand(); - Scheduler* sched = Director::getInstance()->getScheduler(); - sched->runOnAxmolThread([x1, y1, this]() { + Director::getInstance()->postTask([x1, y1, this]() { float tempx = x1, tempy = y1; - Director::getInstance()->getRenderView()->handleTouchesBegin(1, &_touchId, &tempx, &tempy); + PointerInputState state{.id = _touchId}; + InputSystem::getInstance()->handlePointerDown(Vec2{x1, y1}, state); }); float dx = std::abs(x1 - x2); @@ -1227,9 +1217,9 @@ void Console::commandTouchSubCommandSwipe(socket_native_type fd, std::string_vie { _y_ -= dy / dx; } - sched->runOnAxmolThread([_x_, _y_, this]() { - float tempx = _x_, tempy = _y_; - Director::getInstance()->getRenderView()->handleTouchesMove(1, &_touchId, &tempx, &tempy); + Director::getInstance()->postTask([_x_, _y_, this]() { + PointerInputState pointerState; + InputSystem::getInstance()->handlePointerMove(Vec2{_x_, _y_}, pointerState); }); dx -= 1; } @@ -1254,17 +1244,17 @@ void Console::commandTouchSubCommandSwipe(socket_native_type fd, std::string_vie { _y_ -= 1; } - sched->runOnAxmolThread([_x_, _y_, this]() { - float tempx = _x_, tempy = _y_; - Director::getInstance()->getRenderView()->handleTouchesMove(1, &_touchId, &tempx, &tempy); + Director::getInstance()->postTask([_x_, _y_, this]() { + PointerInputState pointerState{.id = _touchId}; + InputSystem::getInstance()->handlePointerMove(Vec2{_x_, _y_}, pointerState); }); dy -= 1; } } - sched->runOnAxmolThread([x2, y2, this]() { - float tempx = x2, tempy = y2; - Director::getInstance()->getRenderView()->handleTouchesEnd(1, &_touchId, &tempx, &tempy); + Director::getInstance()->postTask([x2, y2, this]() { + PointerInputState pointerState; + InputSystem::getInstance()->handlePointerUp(Vec2{x2, y2}, pointerState); }); } else diff --git a/axmol/base/Console.h b/axmol/base/Console.h index 71837fcf6fa7..be3200f99cb9 100644 --- a/axmol/base/Console.h +++ b/axmol/base/Console.h @@ -58,7 +58,7 @@ namespace ax If the std::function<> needs to use the axmol API, it needs to call ``` - scheduler->runOnAxmolThread( ... ); + Director::getInstance()->postTask( ... ); ``` */ diff --git a/axmol/base/Controller-android.cpp b/axmol/base/Controller-android.cpp index 27721f410b99..678ee3da3559 100644 --- a/axmol/base/Controller-android.cpp +++ b/axmol/base/Controller-android.cpp @@ -32,7 +32,7 @@ # include "axmol/base/Macros.h" # include "axmol/base/Director.h" # include "axmol/platform/android/jni/JniHelper.h" -# include "axmol/base/EventController.h" +# include "axmol/base/ControllerEvent.h" namespace ax { diff --git a/axmol/base/Controller-apple.mm b/axmol/base/Controller-apple.mm index 64c5cf07e6cd..9482b577ed09 100644 --- a/axmol/base/Controller-apple.mm +++ b/axmol/base/Controller-apple.mm @@ -31,8 +31,8 @@ of this software and associated documentation files (the "Software"), to deal # include "axmol/base/Macros.h" # include "axmol/base/EventDispatcher.h" -# include "axmol/base/EventController.h" -# include "axmol/base/EventListenerController.h" +# include "axmol/base/ControllerEvent.h" +# include "axmol/base/ControllerEventListener.h" # include "axmol/base/Director.h" # include "axmol/2d/Label.h" diff --git a/axmol/base/Controller-linux-win32.cpp b/axmol/base/Controller-linux-win32.cpp index ee533b257150..22ab1d817dbc 100644 --- a/axmol/base/Controller-linux-win32.cpp +++ b/axmol/base/Controller-linux-win32.cpp @@ -33,7 +33,7 @@ THE SOFTWARE. # include "axmol/base/Macros.h" # include "axmol/base/Director.h" # include "axmol/base/Scheduler.h" -# include "axmol/base/EventController.h" +# include "axmol/base/ControllerEvent.h" # include "glfw3.h" namespace ax diff --git a/axmol/base/Controller.cpp b/axmol/base/Controller.cpp index fe231b20a909..12e356361c13 100644 --- a/axmol/base/Controller.cpp +++ b/axmol/base/Controller.cpp @@ -31,7 +31,7 @@ AX_TARGET_PLATFORM == AX_PLATFORM_WASM) # include "axmol/base/EventDispatcher.h" -# include "axmol/base/EventController.h" +# include "axmol/base/ControllerEvent.h" # include "axmol/base/Director.h" namespace ax @@ -75,9 +75,9 @@ void Controller::init() } _eventDispatcher = Director::getInstance()->getEventDispatcher(); - _connectEvent = new EventController(EventController::ControllerEventType::CONNECTION, this, false); - _keyEvent = new EventController(EventController::ControllerEventType::BUTTON_STATUS_CHANGED, this, 0); - _axisEvent = new EventController(EventController::ControllerEventType::AXIS_STATUS_CHANGED, this, 0); + _connectEvent = new ControllerEvent(ControllerEvent::ControllerEventType::CONNECTION, this, false); + _keyEvent = new ControllerEvent(ControllerEvent::ControllerEventType::BUTTON_STATUS_CHANGED, this, 0); + _axisEvent = new ControllerEvent(ControllerEvent::ControllerEventType::AXIS_STATUS_CHANGED, this, 0); } const Controller::KeyStatus& Controller::getKeyStatus(int keyCode) diff --git a/axmol/base/Controller.h b/axmol/base/Controller.h index 0eda46827be2..929a3313bb05 100644 --- a/axmol/base/Controller.h +++ b/axmol/base/Controller.h @@ -38,8 +38,8 @@ namespace ax { class ControllerImpl; -class EventListenerController; -class EventController; +class ControllerEventListener; +class ControllerEvent; class EventDispatcher; /** @@ -219,9 +219,9 @@ class AX_DLL Controller ControllerImpl* _impl; EventDispatcher* _eventDispatcher; - EventController* _connectEvent; - EventController* _keyEvent; - EventController* _axisEvent; + ControllerEvent* _connectEvent; + ControllerEvent* _keyEvent; + ControllerEvent* _axisEvent; # if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) // FIXME: Once GLFW 3.3 is bundled with cocos2d-x, remove these unordered @@ -242,7 +242,7 @@ class AX_DLL Controller # endif friend class ControllerImpl; - friend class EventListenerController; + friend class ControllerEventListener; }; // end group diff --git a/axmol/base/EventController.cpp b/axmol/base/ControllerEvent.cpp similarity index 88% rename from axmol/base/EventController.cpp rename to axmol/base/ControllerEvent.cpp index d7f128039354..d0f5b9146ea6 100644 --- a/axmol/base/EventController.cpp +++ b/axmol/base/ControllerEvent.cpp @@ -2,6 +2,7 @@ Copyright (c) 2014 cocos2d-x.org Copyright (c) 2014-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -24,12 +25,12 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/EventController.h" +#include "axmol/base/ControllerEvent.h" namespace ax { -EventController::EventController(ControllerEventType type, Controller* controller, int keyCode) +ControllerEvent::ControllerEvent(ControllerEventType type, Controller* controller, int keyCode) : Event(Type::GAME_CONTROLLER) , _controllerEventType(type) , _controller(controller) @@ -37,7 +38,7 @@ EventController::EventController(ControllerEventType type, Controller* controlle , _isConnected(true) {} -EventController::EventController(ControllerEventType type, Controller* controller, bool isConnected) +ControllerEvent::ControllerEvent(ControllerEventType type, Controller* controller, bool isConnected) : Event(Type::GAME_CONTROLLER) , _controllerEventType(type) , _controller(controller) diff --git a/axmol/base/EventController.h b/axmol/base/ControllerEvent.h similarity index 81% rename from axmol/base/EventController.h rename to axmol/base/ControllerEvent.h index dab00cded009..6e6dd6952bf3 100644 --- a/axmol/base/EventController.h +++ b/axmol/base/ControllerEvent.h @@ -2,6 +2,7 @@ Copyright (c) 2014 cocos2d-x.org Copyright (c) 2014-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -37,14 +38,14 @@ namespace ax { -/// @cond EventController +/// @cond ControllerEvent class Controller; -class EventListenerController; +class ControllerEventListener; -/** @class EventController +/** @class ControllerEvent * @brief Controller event. */ -class AX_DLL EventController : public Event +class AX_DLL ControllerEvent : public Event { public: /** ControllerEventType Controller event type.*/ @@ -55,22 +56,22 @@ class AX_DLL EventController : public Event AXIS_STATUS_CHANGED, }; - /** Create a EventController with controller event type, controller and key code. + /** Create a ControllerEvent with controller event type, controller and key code. * * @param type A given controller event type. * @param controller A given controller pointer. * @param keyCode A given key code. - * @return An autoreleased EventController object. + * @return An autoreleased ControllerEvent object. */ - EventController(ControllerEventType type, Controller* controller, int keyCode); - /** Create a EventController with controller event type, controller and whether or not is connected. + ControllerEvent(ControllerEventType type, Controller* controller, int keyCode); + /** Create a ControllerEvent with controller event type, controller and whether or not is connected. * * @param type A given controller event type. * @param controller A given controller pointer. * @param isConnected True if it is connected. - * @return An autoreleased EventController object. + * @return An autoreleased ControllerEvent object. */ - EventController(ControllerEventType type, Controller* controller, bool isConnected); + ControllerEvent(ControllerEventType type, Controller* controller, bool isConnected); /** Gets the event type of the controller. * @@ -103,9 +104,13 @@ class AX_DLL EventController : public Event int _keyCode; bool _isConnected; - friend class EventListenerController; + friend class ControllerEventListener; }; -/// @endcond EventController + +// deprecated alias +using EventController = ControllerEvent; + +/// @endcond ControllerEvent } // namespace ax // end of base group diff --git a/axmol/base/EventListenerController.cpp b/axmol/base/ControllerEventListener.cpp similarity index 69% rename from axmol/base/EventListenerController.cpp rename to axmol/base/ControllerEventListener.cpp index ed9e748df14f..0f923316be76 100644 --- a/axmol/base/EventListenerController.cpp +++ b/axmol/base/ControllerEventListener.cpp @@ -2,6 +2,7 @@ Copyright (c) 2014 cocos2d-x.org Copyright (c) 2014-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -24,19 +25,19 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/EventListenerController.h" -#include "axmol/base/EventController.h" +#include "axmol/base/ControllerEventListener.h" +#include "axmol/base/ControllerEvent.h" #include "axmol/base/Macros.h" #include "axmol/base/Controller.h" namespace ax { -const std::string_view EventListenerController::LISTENER_ID = "__ax_controller"sv; +const std::string_view ControllerEventListener::LISTENER_ID = "__ax_controller"sv; -EventListenerController* EventListenerController::create() +ControllerEventListener* ControllerEventListener::create() { - auto ret = new EventListenerController(); + auto ret = new ControllerEventListener(); if (ret->init()) { ret->autorelease(); @@ -48,53 +49,53 @@ EventListenerController* EventListenerController::create() return ret; } -bool EventListenerController::init() +bool ControllerEventListener::init() { auto listener = [this](Event* event) { - auto evtController = static_cast(event); + auto evtController = static_cast(event); switch (evtController->getControllerEventType()) { - case EventController::ControllerEventType::CONNECTION: + case ControllerEvent::ControllerEventType::CONNECTION: if (evtController->isConnected()) { if (this->onConnected) - this->onConnected(evtController->getController(), event); + this->onConnected(evtController); } else { if (this->onDisconnected) - this->onDisconnected(evtController->getController(), event); + this->onDisconnected(evtController); } break; - case EventController::ControllerEventType::BUTTON_STATUS_CHANGED: + case ControllerEvent::ControllerEventType::BUTTON_STATUS_CHANGED: { const auto& keyStatus = evtController->_controller->_allKeyStatus[evtController->_keyCode]; const auto& keyPrevStatus = evtController->_controller->_allKeyPrevStatus[evtController->_keyCode]; if (this->onKeyDown && keyStatus.isPressed && !keyPrevStatus.isPressed) { - this->onKeyDown(evtController->_controller, evtController->_keyCode, event); + this->onKeyDown(evtController); } else if (this->onKeyUp && !keyStatus.isPressed && keyPrevStatus.isPressed) { - this->onKeyUp(evtController->_controller, evtController->_keyCode, event); + this->onKeyUp(evtController); } else if (this->onKeyRepeat && keyStatus.isPressed && keyPrevStatus.isPressed) { - this->onKeyRepeat(evtController->_controller, evtController->_keyCode, event); + this->onKeyRepeat(evtController); } } break; - case EventController::ControllerEventType::AXIS_STATUS_CHANGED: + case ControllerEvent::ControllerEventType::AXIS_STATUS_CHANGED: { if (this->onAxisEvent) { - this->onAxisEvent(evtController->_controller, evtController->_keyCode, event); + this->onAxisEvent(evtController); } } break; default: - AXASSERT(false, "Invalid EventController type"); + AXASSERT(false, "Invalid ControllerEvent type"); break; } }; @@ -106,12 +107,12 @@ bool EventListenerController::init() return false; } -bool EventListenerController::checkAvailable() +bool ControllerEventListener::checkAvailable() { return true; } -EventListenerController* EventListenerController::clone() +ControllerEventListener* ControllerEventListener::clone() { return nullptr; } diff --git a/axmol/base/EventListenerController.h b/axmol/base/ControllerEventListener.h similarity index 70% rename from axmol/base/EventListenerController.h rename to axmol/base/ControllerEventListener.h index 7a2c532c53c3..66748042df76 100644 --- a/axmol/base/EventListenerController.h +++ b/axmol/base/ControllerEventListener.h @@ -2,6 +2,7 @@ Copyright (c) 2014 cocos2d-x.org Copyright (c) 2014-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -28,6 +29,7 @@ #include "axmol/platform/PlatformMacros.h" #include "axmol/base/EventListener.h" +#include "axmol/base/ControllerEvent.h" /** * @addtogroup base @@ -40,37 +42,40 @@ namespace ax class Event; class Controller; -/** @class EventListenerController +/** @class ControllerEventListener * @param Controller event listener. */ -class AX_DLL EventListenerController : public EventListener +class AX_DLL ControllerEventListener : public EventListener { public: static const std::string_view LISTENER_ID; /** Create a controller event listener. * - * @return An autoreleased EventListenerController object. + * @return An autoreleased ControllerEventListener object. */ - static EventListenerController* create(); + static ControllerEventListener* create(); /// Overrides bool checkAvailable() override; - EventListenerController* clone() override; + ControllerEventListener* clone() override; - std::function onConnected; - std::function onDisconnected; + std::function onConnected; + std::function onDisconnected; - std::function onKeyDown; - std::function onKeyUp; - std::function onKeyRepeat; + std::function onKeyDown; + std::function onKeyUp; + std::function onKeyRepeat; - std::function onAxisEvent; + std::function onAxisEvent; protected: bool init(); }; +// deprecated alias +using EventListenerController = ControllerEventListener; + } // namespace ax // end of base group diff --git a/axmol/base/EventCustom.cpp b/axmol/base/CustomEvent.cpp similarity index 89% rename from axmol/base/EventCustom.cpp rename to axmol/base/CustomEvent.cpp index 5849478e6057..2017a99d68b5 100644 --- a/axmol/base/EventCustom.cpp +++ b/axmol/base/CustomEvent.cpp @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -23,12 +24,12 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/Event.h" namespace ax { -EventCustom::EventCustom(std::string_view eventName) : Event(Type::CUSTOM), _userData(nullptr), _eventName(eventName) {} +CustomEvent::CustomEvent(std::string_view eventName) : Event(Type::CUSTOM), _userData(nullptr), _eventName(eventName) {} } // namespace ax diff --git a/axmol/base/EventCustom.h b/axmol/base/CustomEvent.h similarity index 90% rename from axmol/base/EventCustom.h rename to axmol/base/CustomEvent.h index 883f2bbf8215..2d36e7319502 100644 --- a/axmol/base/EventCustom.h +++ b/axmol/base/CustomEvent.h @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -36,17 +37,17 @@ namespace ax { -/** @class EventCustom +/** @class CustomEvent * @brief Custom event. */ -class AX_DLL EventCustom : public Event +class AX_DLL CustomEvent : public Event { public: /** Constructor. * * @param eventName A given name of the custom event. */ - EventCustom(std::string_view eventName); + CustomEvent(std::string_view eventName); /** Sets user data. * @@ -71,6 +72,9 @@ class AX_DLL EventCustom : public Event std::string _eventName; }; +// deprecated alias +using EventCustom = CustomEvent; + } // namespace ax // end of base group diff --git a/axmol/base/EventListenerCustom.cpp b/axmol/base/CustomEventListener.cpp similarity index 75% rename from axmol/base/EventListenerCustom.cpp rename to axmol/base/CustomEventListener.cpp index c8412ee35f46..22e850661780 100644 --- a/axmol/base/EventListenerCustom.cpp +++ b/axmol/base/CustomEventListener.cpp @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -23,18 +24,18 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/EventListenerCustom.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEventListener.h" +#include "axmol/base/CustomEvent.h" namespace ax { -EventListenerCustom::EventListenerCustom() : _onCustomEvent(nullptr) {} +CustomEventListener::CustomEventListener() : _onCustomEvent(nullptr) {} -EventListenerCustom* EventListenerCustom::create(std::string_view eventName, - const std::function& callback) +CustomEventListener* CustomEventListener::create(std::string_view eventName, + const std::function& callback) { - EventListenerCustom* ret = new EventListenerCustom(); + CustomEventListener* ret = new CustomEventListener(); if (ret->init(eventName, callback)) { ret->autorelease(); @@ -46,7 +47,7 @@ EventListenerCustom* EventListenerCustom::create(std::string_view eventName, return ret; } -bool EventListenerCustom::init(std::string_view listenerId, const std::function& callback) +bool CustomEventListener::init(std::string_view listenerId, const std::function& callback) { bool ret = false; @@ -55,7 +56,7 @@ bool EventListenerCustom::init(std::string_view listenerId, const std::function< auto listener = [this](Event* event) { if (_onCustomEvent != nullptr) { - _onCustomEvent(static_cast(event)); + _onCustomEvent(static_cast(event)); } }; @@ -66,9 +67,9 @@ bool EventListenerCustom::init(std::string_view listenerId, const std::function< return ret; } -EventListenerCustom* EventListenerCustom::clone() +CustomEventListener* CustomEventListener::clone() { - EventListenerCustom* ret = new EventListenerCustom(); + CustomEventListener* ret = new CustomEventListener(); if (ret->init(_listenerID, _onCustomEvent)) { ret->autorelease(); @@ -80,7 +81,7 @@ EventListenerCustom* EventListenerCustom::clone() return ret; } -bool EventListenerCustom::checkAvailable() +bool CustomEventListener::checkAvailable() { bool ret = false; if (EventListener::checkAvailable() && _onCustomEvent != nullptr) diff --git a/axmol/base/EventListenerCustom.h b/axmol/base/CustomEventListener.h similarity index 74% rename from axmol/base/EventListenerCustom.h rename to axmol/base/CustomEventListener.h index c5c800bcdf59..0d251932d3d7 100644 --- a/axmol/base/EventListenerCustom.h +++ b/axmol/base/CustomEventListener.h @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -35,21 +36,21 @@ namespace ax { -class EventCustom; +class CustomEvent; -/** @class EventListenerCustom +/** @class CustomEventListener * @brief Custom event listener. * @code Usage: * auto dispatcher = Director::getInstance()->getEventDispatcher(); * Adds a listener: * - * auto callback = [](EventCustom* event){ do_some_thing(); }; - * auto listener = EventListenerCustom::create(callback); + * auto callback = [](CustomEvent* event){ do_some_thing(); }; + * auto listener = CustomEventListener::create(callback); * dispatcher->addEventListenerWithSceneGraphPriority(listener, one_node); * * Dispatches a custom event: * - * EventCustom event("your_event_type"); + * CustomEvent event("your_event_type"); * dispatcher->dispatchEvent(&event); * * Removes a listener @@ -57,32 +58,35 @@ class EventCustom; * dispatcher->removeEventListener(listener); * \endcode */ -class AX_DLL EventListenerCustom : public EventListener +class AX_DLL CustomEventListener : public EventListener { public: /** Creates an event listener with type and callback. * @param eventName The type of the event. * @param callback The callback function when the specified event was emitted. - * @return An autoreleased EventListenerCustom object. + * @return An autoreleased CustomEventListener object. */ - static EventListenerCustom* create(std::string_view eventName, const std::function& callback); + static CustomEventListener* create(std::string_view eventName, const std::function& callback); /// Overrides bool checkAvailable() override; - EventListenerCustom* clone() override; + CustomEventListener* clone() override; /** Constructor */ - EventListenerCustom(); + CustomEventListener(); /** Initializes event with type and callback function */ - bool init(std::string_view listenerId, const std::function& callback); + bool init(std::string_view listenerId, const std::function& callback); protected: - std::function _onCustomEvent; + std::function _onCustomEvent; - friend class LuaEventListenerCustom; + friend class LuaCustomEventListener; }; +// deprecated alias +using EventListenerCustom = CustomEventListener; + } // namespace ax // end of base group diff --git a/axmol/base/Director.cpp b/axmol/base/Director.cpp index df9c003fb870..a1b6c1c8b932 100644 --- a/axmol/base/Director.cpp +++ b/axmol/base/Director.cpp @@ -53,7 +53,7 @@ THE SOFTWARE. #include "axmol/base/Scheduler.h" #include "axmol/base/Macros.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/Logging.h" #include "axmol/base/AutoreleasePool.h" #include "axmol/base/Environment.h" @@ -152,31 +152,31 @@ bool Director::init() _eventDispatcher = new EventDispatcher(); - _beforeSetNextScene = new EventCustom(EVENT_BEFORE_SET_NEXT_SCENE); + _beforeSetNextScene = new CustomEvent(EVENT_BEFORE_SET_NEXT_SCENE); _beforeSetNextScene->setUserData(this); - _afterSetNextScene = new EventCustom(EVENT_AFTER_SET_NEXT_SCENE); + _afterSetNextScene = new CustomEvent(EVENT_AFTER_SET_NEXT_SCENE); _afterSetNextScene->setUserData(this); - _eventAfterDraw = new EventCustom(EVENT_AFTER_DRAW); + _eventAfterDraw = new CustomEvent(EVENT_AFTER_DRAW); _eventAfterDraw->setUserData(this); - _eventBeforeDraw = new EventCustom(EVENT_BEFORE_DRAW); + _eventBeforeDraw = new CustomEvent(EVENT_BEFORE_DRAW); _eventBeforeDraw->setUserData(this); - _eventAfterVisit = new EventCustom(EVENT_AFTER_VISIT); + _eventAfterVisit = new CustomEvent(EVENT_AFTER_VISIT); _eventAfterVisit->setUserData(this); - _eventBeforeUpdate = new EventCustom(EVENT_BEFORE_UPDATE); + _eventBeforeUpdate = new CustomEvent(EVENT_BEFORE_UPDATE); _eventBeforeUpdate->setUserData(this); - _eventAfterUpdate = new EventCustom(EVENT_AFTER_UPDATE); + _eventAfterUpdate = new CustomEvent(EVENT_AFTER_UPDATE); _eventAfterUpdate->setUserData(this); - _eventProjectionChanged = new EventCustom(EVENT_PROJECTION_CHANGED); + _eventProjectionChanged = new CustomEvent(EVENT_PROJECTION_CHANGED); _eventProjectionChanged->setUserData(this); - _eventResetDirector = new EventCustom(EVENT_RESET); + _eventResetDirector = new CustomEvent(EVENT_RESET); _eventResetDirector->setUserData(this); - _eventDestroyDirector = new EventCustom(EVENT_DESTROY); + _eventDestroyDirector = new CustomEvent(EVENT_DESTROY); _eventDestroyDirector->setUserData(this); - _eventBeforeGfxDrop = new EventCustom(EVENT_BEFORE_GFX_DROP); + _eventBeforeGfxDrop = new CustomEvent(EVENT_BEFORE_GFX_DROP); _eventBeforeGfxDrop->setUserData(this); - _eventAfterGfxDrop = new EventCustom(EVENT_AFTER_GFX_DROP); + _eventAfterGfxDrop = new CustomEvent(EVENT_AFTER_GFX_DROP); _eventAfterGfxDrop->setUserData(this); // init TextureCache @@ -187,7 +187,7 @@ bool Director::init() #if AX_ENABLE_CONTEXT_LOSS_RECOVERY // listen the event that renderer was recreated on Android/WP8 - _rendererRecreatedListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { + _rendererRecreatedListener = CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { _isStatusLabelUpdated = true; // Force recreation of textures rhi::SamplerCache::getInstance()->rebuild(); rhi::ShaderCache::getInstance()->recompileAll(); @@ -320,6 +320,7 @@ void Director::drawScene() { _eventDispatcher->dispatchEvent(_eventBeforeUpdate); _scheduler->update(_deltaTime); + performFrameTasks(_nextUpdateTasks); _eventDispatcher->dispatchEvent(_eventAfterUpdate); } @@ -428,7 +429,7 @@ float Director::getDeltaTime() const return _deltaTime; } -void Director::setRenderView(RenderView* renderView) +void Director::setRenderView(RenderViewCore* renderView) { AXASSERT(renderView, "opengl view should not be null"); @@ -747,8 +748,15 @@ static void getViewProjMatrix(Mat4* transformOut) *transformOut = projection * modelview; } -Vec2 Director::screenToWorld(const Vec2& uiPoint) +Vec2 Director::screenToWorld(const Vec2& screenPoint) { + auto& viewScale = _renderView->getScale(); + auto& vp = _renderView->getViewportRect(); + + // Convert screen point to Axmol 2D(UI) point + float uiX = (screenPoint.x - vp.origin.x) / viewScale.x; + float uiY = (screenPoint.y - vp.origin.y) / viewScale.y; + Mat4 transform; getViewProjMatrix(&transform); @@ -758,24 +766,23 @@ Vec2 Director::screenToWorld(const Vec2& uiPoint) float zClip = transform.m[14] / transform.m[15]; Vec2 designSize = _renderView->getDesignResolutionSize(); - Vec4 clipCoord(2.0f * uiPoint.x / designSize.width - 1.0f, 1.0f - 2.0f * uiPoint.y / designSize.height, zClip, 1); + Vec4 clipCoord(2.0f * uiX / designSize.width - 1.0f, 1.0f - 2.0f * uiY / designSize.height, zClip, 1); - Vec4 glCoord; - // transformInv.transformPoint(clipCoord, &glCoord); - transformInv.transformVector(clipCoord, &glCoord); - float factor = 1.0f / glCoord.w; - return Vec2(glCoord.x * factor, glCoord.y * factor); + Vec4 uiPoint; + transformInv.transformVector(clipCoord, &uiPoint); + float factor = 1.0f / uiPoint.w; + return Vec2{uiPoint.x * factor, uiPoint.y * factor}; } -Vec2 Director::worldToScreen(const Vec2& glPoint) +Vec2 Director::worldToScreen(const Vec2& worldPoint) { Mat4 transform; getViewProjMatrix(&transform); Vec4 clipCoord; // Need to calculate the zero depth from the transform. - Vec4 glCoord(glPoint.x, glPoint.y, 0.0, 1); - transform.transformVector(glCoord, &clipCoord); + Vec4 worldCoord(worldPoint.x, worldPoint.y, 0.0, 1); + transform.transformVector(worldCoord, &clipCoord); /* BUG-FIX #5506 @@ -790,9 +797,14 @@ Vec2 Director::worldToScreen(const Vec2& glPoint) clipCoord.z = clipCoord.z / clipCoord.w; Vec2 designSize = _renderView->getDesignResolutionSize(); - float factor = 1.0f / glCoord.w; - return Vec2(designSize.width * (clipCoord.x * 0.5f + 0.5f) * factor, - designSize.height * (-clipCoord.y * 0.5f + 0.5f) * factor); + float factor = 1.0f / worldCoord.w; + float uiX = designSize.width * (clipCoord.x * 0.5f + 0.5f) * factor; + float uiY = designSize.height * (-clipCoord.y * 0.5f + 0.5f) * factor; + + // Convert Axmol 2D(UI) coordinate to screen-coordinate + auto& viewScale = _renderView->getScale(); + auto& vp = _renderView->getViewportRect(); + return Vec2{uiX * viewScale.x + vp.origin.x, uiY * viewScale.y + vp.origin.y}; } const Vec2& Director::getCanvasSize() const @@ -1204,7 +1216,7 @@ void Director::restartDirector() #if AX_ENABLE_CONTEXT_LOSS_RECOVERY // listen the event that renderer was recreated on Android/WP8 - _rendererRecreatedListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { + _rendererRecreatedListener = CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { _isStatusLabelUpdated = true; // Force recreation of textures }); @@ -1561,12 +1573,10 @@ JobHandle Director::runAsync(std::function task, std::function d if (!task) return {}; - RefPtr scheduler(_scheduler); - return _jobSystem->enqueue( - [task = std::move(task), done = std::move(done), scheduler = std::move(scheduler)]() mutable { + return _jobSystem->enqueue([task = std::move(task), done = std::move(done), this]() mutable { task(); if (done) - scheduler->runOnAxmolThread(std::move(done)); + this->postTask(std::move(done)); }); } @@ -1612,30 +1622,51 @@ void Director::startAnimation(SetIntervalReason reason) setNextDeltaTimeZero(true); } -void Director::queueOperation(AsyncOperation op, void* param) +void Director::postTask(std::function task, TaskTiming timing) { -#if defined(AX_PLATFORM_GLFW) - _operations.enqueue([=]() { op(param); }); -#else - _renderView->queueOperation(op, param); -#endif + if (!task) [[unlikely]] + return; + + if (timing == TaskTiming::NextUpdate) + _nextUpdateTasks.enqueue(std::move(task)); + else + { + _frameBoundaryTasks.enqueue(std::move(task)); + ApplicationCore* axmolApp = Application::getInstance(); + axmolApp->postBoundaryTaskSignal(); + } } -#if defined(AX_PLATFORM_GLFW) -void Director::processOperations() +void Director::clearPendingTasks(TaskTiming timing) { - std::function op; - while (_operations.try_dequeue(op)) - op(); + FrameTaskQueue dummyQueue; + if (timing == TaskTiming::NextUpdate) + _nextUpdateTasks.swap(dummyQueue); + else + _frameBoundaryTasks.swap(dummyQueue); } -#endif -void Director::renderFrame() +void Director::performFrameBoundaryTasks() { -#if defined(AX_PLATFORM_GLFW) - processOperations(); -#endif + performFrameTasks(_frameBoundaryTasks); +} +void Director::performFrameTasks(FrameTaskQueue& frameTasks) +{ + size_t count = frameTasks.size_approx(); + if (count > 0) + { + std::function op; + while (count-- > 0 && frameTasks.try_dequeue(op)) + { + op(); + op = nullptr; + } + } +} + +void Director::renderFrame() +{ if (_cleanupDirectorInNextLoop) { _cleanupDirectorInNextLoop = false; diff --git a/axmol/base/Director.h b/axmol/base/Director.h index 6847c2694fae..3a56ecfab99a 100644 --- a/axmol/base/Director.h +++ b/axmol/base/Director.h @@ -38,16 +38,16 @@ THE SOFTWARE. #include "axmol/base/Vector.h" #include "axmol/scene/Scene.h" #include "axmol/math/Math.h" -#include "axmol/platform/RenderView.h" -#if defined(AX_PLATFORM_GLFW) -# include "concurrentqueue/concurrentqueue.h" -#endif +#include "axmol/platform/RenderViewCore.h" +#include "concurrentqueue/concurrentqueue.h" #ifdef AX_ENABLE_CONSOLE # include "axmol/base/Console.h" #endif #include "axmol/base/JobSystem.h" +extern void _axmolPerformFrameBoundaryTasks(); + namespace ax { @@ -58,17 +58,18 @@ namespace ax /* Forward declarations. */ class LabelAtlas; -// class RenderView; class DirectorDelegate; class Node; class Scheduler; class ActionManager; class EventDispatcher; -class EventCustom; -class EventListenerCustom; +class CustomEvent; +class CustomEventListener; class TextureCache; class Renderer; class Camera; +class Application; +class ApplicationCore; /** @brief Class that creates and handles the main Window and manages how @@ -84,6 +85,8 @@ class Camera; */ class AX_DLL Director { + using FrameTaskQueue = moodycamel::ConcurrentQueue>; + public: /** Director will trigger an event before set next scene. */ static std::string_view EVENT_BEFORE_SET_NEXT_SCENE; @@ -132,6 +135,26 @@ class AX_DLL Director DEFAULT = _3D, }; + /** + * @brief Defines the execution timing for asynchronous tasks dispatched to the main execution thread. + */ + enum class TaskTiming + { + /** + * @brief Defer the task to the next main logic update tick (inside Scheduler::update). + * @note Recommended for 95% of standard gameplay logic, UI refreshes, and network response handlers. + */ + NextUpdate, + + /** + * @brief Defer the task to the absolute boundary of a frame (outside the main loop's update/visit/render + * stages). + * @note Reserved for heavy, structural operations such as scene destruction/replacement, + * massive resource purges, or critical RHI context resets on mobile platforms. + */ + FrameBoundary + }; + /** * Returns a shared instance of the director. */ @@ -178,12 +201,12 @@ class AX_DLL Director * Get the RenderView. * @lua NA */ - RenderView* getRenderView() { return _renderView; } + RenderViewCore* getRenderView() { return _renderView; } /** * Sets the RenderView. * @lua NA */ - void setRenderView(RenderView* renderView); + void setRenderView(RenderViewCore* renderView); /* * Gets singleton of TextureCache. @@ -259,7 +282,7 @@ class AX_DLL Director Rect getSafeAreaRect() const; /** - * Converts a point from screen coordinates to the rendering coordinate system. + * Converts a point from screen coordinates to the rendering 2d-coordinate system. * Useful for mapping (multi)touch input to the current scene layout, * taking into account orientation (portrait or landscape) and viewport settings. */ @@ -267,7 +290,7 @@ class AX_DLL Director AX_DEPRECATED(3.0) Vec2 convertToGL(const Vec2& point) { return screenToWorld(point); } /** - * Converts an rendering coordinate to a screen coordinate. + * Converts an rendering 2d-coordinate to a screen coordinate. * Useful to convert node points to window points for calls such as glScissor. */ Vec2 worldToScreen(const Vec2& point); @@ -426,7 +449,7 @@ class AX_DLL Director /** * @brief Run work on the JobSystem and optionally post a completion callback to the Axmol thread. * - * The task function runs on the JobSystem. After task returns, done is posted through Scheduler::runOnAxmolThread() + * The task function runs on the JobSystem. After task returns, done is posted through Director::postTask() * and therefore runs later on the Axmol thread. * * @param task Function executed by the JobSystem. @@ -546,10 +569,36 @@ class AX_DLL Director */ bool isChildrenIndexerEnabled() const { return _childrenIndexerEnabled; } - /** since Axmol-1.0 - * queue a priority operation in render thread, even through app in background + /** + * @brief Safely dispatches a callable task from any background thread (e.g., Java UI thread, + * network thread, or audio thread) to be executed on the main Axmol thread. + * * This API provides a high-performance, Zero-Allocation (Zero GC) pipeline across platforms by + * utilizing modern C++ move semantics internally, paired with an optimized FIFO signaling pipeline + * on Android to completely eliminate runtime heap allocations. + * * @param task The callable closure, lambda, or std::function to be executed. + * @param timing The specific execution timing when this task should be consumed. + * Defaults to TaskTiming::NextUpdate. + * * @code + * // Example 1: Standard asynchronous network callback (runs in the next logic tick) + * Director::getInstance()->postTask([=]() { + * this->updatePlayerGold(goldCount); + * }); + * * // Example 2: Critical structural engine operation (runs at the safe frame boundary) + * Director::getInstance()->postTask([=]() { + * Director::getInstance()->replaceScene(battleScene); + * }, Director::TaskTiming::FrameBoundary); + * @endcode + * @since axmol-3.0.0 + */ + void postTask(std::function task, TaskTiming timing = TaskTiming::NextUpdate); + + /** + * @brief Forcefully purges all pending tasks from the specified queue. + * @warning This is an extreme, engine-level utility (e.g., during full game reboot or director end). + * Calling this on FrameBoundary may leak graphics contexts or skip critical structural cleanups. + * Do NOT use this for standard gameplay object lifecycle management. */ - void queueOperation(AsyncOperation op, void* param = nullptr); + void clearPendingTasks(TaskTiming timing = TaskTiming::NextUpdate); /** * returns whether or not the Director is in a valid state @@ -557,6 +606,10 @@ class AX_DLL Director bool isValid() const { return !_invalid; } protected: + void performFrameBoundaryTasks(); + + static void performFrameTasks(FrameTaskQueue& frameTasks); + void reset(); /** @@ -564,10 +617,6 @@ class AX_DLL Director */ void setCanvasSize(const Vec2& canvasSize); -#if defined(AX_PLATFORM_GLFW) - void processOperations(); -#endif - virtual void startAnimation(SetIntervalReason reason); virtual void setAnimationInterval(float interval, SetIntervalReason reason); @@ -616,26 +665,26 @@ class AX_DLL Director @since v3.0 */ EventDispatcher* _eventDispatcher = nullptr; - EventCustom* _eventProjectionChanged = nullptr; - EventCustom* _eventBeforeDraw = nullptr; - EventCustom* _eventAfterDraw = nullptr; - EventCustom* _eventAfterVisit = nullptr; - EventCustom* _eventBeforeUpdate = nullptr; - EventCustom* _eventAfterUpdate = nullptr; - EventCustom* _beforeSetNextScene = nullptr; - EventCustom* _afterSetNextScene = nullptr; - EventCustom* _eventResetDirector = nullptr; - EventCustom* _eventDestroyDirector = nullptr; - EventCustom* _eventBeforeGfxDrop = nullptr; - EventCustom* _eventAfterGfxDrop = nullptr; + CustomEvent* _eventProjectionChanged = nullptr; + CustomEvent* _eventBeforeDraw = nullptr; + CustomEvent* _eventAfterDraw = nullptr; + CustomEvent* _eventAfterVisit = nullptr; + CustomEvent* _eventBeforeUpdate = nullptr; + CustomEvent* _eventAfterUpdate = nullptr; + CustomEvent* _beforeSetNextScene = nullptr; + CustomEvent* _afterSetNextScene = nullptr; + CustomEvent* _eventResetDirector = nullptr; + CustomEvent* _eventDestroyDirector = nullptr; + CustomEvent* _eventBeforeGfxDrop = nullptr; + CustomEvent* _eventAfterGfxDrop = nullptr; /* delta time since last tick to main loop */ float _deltaTime = 1e-6f; bool _deltaTimePassedByCaller = false; - /* The _renderView, where everything is rendered, RenderView is a abstract class,cocos2d-x provide RenderViewImpl + /* The _renderView, where everything is rendered, RenderViewCore is a abstract class,axmol provide RenderView which inherit from it as default renderer context,you can have your own by inherit from it*/ - RenderView* _renderView = nullptr; + RenderViewCore* _renderView = nullptr; JobSystem* _jobSystem = nullptr; @@ -710,17 +759,22 @@ class AX_DLL Director /* axmol thread id */ std::thread::id _axmol_thread_id; -#if defined(AX_PLATFORM_GLFW) - /* axmol priority operations in render thread for PC platforms */ - moodycamel::ConcurrentQueue> _operations; -#endif + /** @brief Thread-safe FIFO queue for tasks executed during the main logic update loop. */ + FrameTaskQueue _nextUpdateTasks; + + /** @brief Thread-safe FIFO queue for engine-level structural tasks executed at the frame boundary. */ + FrameTaskQueue _frameBoundaryTasks; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _rendererRecreatedListener = nullptr; + CustomEventListener* _rendererRecreatedListener = nullptr; #endif - // RenderView will recreate stats labels to fit visible rect - friend class RenderView; + // RenderViewCore will recreate stats labels to fit visible rect + friend class RenderViewCore; + friend class ApplicationCore; + friend class Application; + + friend void ::_axmolPerformFrameBoundaryTasks(); }; // end of base group diff --git a/axmol/base/Environment.cpp b/axmol/base/Environment.cpp index 4ead6b3aad61..455d21c5819b 100644 --- a/axmol/base/Environment.cpp +++ b/axmol/base/Environment.cpp @@ -28,7 +28,7 @@ THE SOFTWARE. #include "axmol/base/Environment.h" #include "axmol/platform/FileUtils.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/Director.h" #include "axmol/base/EventDispatcher.h" #include "axmol/rhi/DriverContext.h" @@ -62,7 +62,7 @@ Environment::Environment() , _maxSpotLightInShader(1) , _animate3DQuality(Animate3DQuality::QUALITY_LOW) { - _loadedEvent = new EventCustom(CONFIG_FILE_LOADED); + _loadedEvent = new CustomEvent(CONFIG_FILE_LOADED); } bool Environment::init() diff --git a/axmol/base/Environment.h b/axmol/base/Environment.h index 402ec843d90a..6d8451f61b91 100644 --- a/axmol/base/Environment.h +++ b/axmol/base/Environment.h @@ -41,7 +41,7 @@ THE SOFTWARE. namespace ax { -class EventCustom; +class CustomEvent; /** @class Environment * @brief Environment contains some Engine caps and user settings @@ -271,7 +271,7 @@ class AX_DLL Environment ValueMap _valueDict; - EventCustom* _loadedEvent; + CustomEvent* _loadedEvent; }; } // namespace ax diff --git a/axmol/base/Event.h b/axmol/base/Event.h index b56bcf9c9414..0ffbd8c4a00d 100644 --- a/axmol/base/Event.h +++ b/axmol/base/Event.h @@ -30,6 +30,7 @@ #include "axmol/platform/PlatformMacros.h" #include +#include /** * @addtogroup base @@ -41,6 +42,109 @@ namespace ax class Node; +enum class InputPhase +{ + PointerDown, + PointerUp, + PointerMove, + PointerCancel, + PointerScroll, + KeyDown, + KeyUp, + KeyRepeat, +}; + +enum class PointerType +{ + Mouse, + Touch, + Pen, +}; + +/** + * @brief Mouse / pen button indices. + * + * This enum-like struct defines integer indices for common mouse/pen buttons. + * The values are intended to be used as button indices (0..n-1) when reporting + * which single button triggered an event. Use -1 to indicate "no button" + * (for example, touch events or pure move events). + * + * Mapping convention: + * - 0 => left button + * - 1 => right button + * - 2 => middle button + * - n => additional buttons (if present) use subsequent indices + * + * @note Use the separate bitmask field (e.g., pressedButtons) to represent the + * current set of pressed buttons; bit i corresponds to button index i. + */ +struct InputButton +{ + enum + { + None = -1, //!< No button triggered (touch or no-button event) + Primary = 0, //!< Primary button: mouse left / pen tip + Secondary, //!< Secondary button: mouse right / pen barrel + Tertiary, //!< Tertiary button: mouse middle / pen eraser + // Alias + Left = Primary, + Right = Secondary, + Middle = Tertiary + // Additional indices may follow (4, 5, ...) for extra buttons + }; +}; + +/** + * @brief Internal representation of raw pointer input from the platform. + * + * This structure is used internally by the InputSystem to capture native + * input events (touch, mouse, pen, etc.) coming from the OS or windowing layer. + * Fields contain unprocessed, screen-space values and raw attributes such as + * pointer id, button index, pressed-button bitmask and pressure. + * + * @note Pointer id rules: + * - Touch / Pen: non-negative integer ids assigned per active contact by the platform. + * - Mouse: negative ids are reserved to represent mouse buttons (platform mapping): + * - -1 => left button + * - -2 => right button + * - -3 => middle button + * - Additional negative values may be reserved for extended mouse buttons. + * + * @note Button semantics: + * - The @c button field is a single button index indicating which button + * triggered this event. It uses the convention: + * - -1 (InputButton::None) => no button triggered (e.g., touch or pure move) + * - 0..n-1 => button index (0 typically = left, 1 = right, 2 = middle) + * - For PointerMove events the @c button value MUST be -1. + * - Use @c pressedButtons to inspect the current set of pressed buttons. + * + * @note pressedButtons bitmask: + * - Each bit corresponds to a button index: bit i represents whether button + * index i is currently pressed (1 = pressed, 0 = released). + * - Example: + * - bit 0 (1u << 0) => left button + * - bit 1 (1u << 1) => right button + * - bit 2 (1u << 2) => middle button + * - For touch events, @c pressedButtons is typically 0. + * + * @note Coordinate / transform: + * - Values @c x and @c y are raw native screen-space coordinates. The + * InputSystem is responsible for applying input scaling and transforming + * these into engine/view coordinates before constructing and dispatching + * a higher-level PointerEvent to listeners. + * + * @warning This type is internal to the input pipeline and should not be + * exposed directly to user code or public APIs. + */ +struct PointerInputState +{ + intptr_t id{-1}; //!< Unique identifier for the pointer (platform-provided or mapped). + float pressure{1.0f}; //!< Raw pressure value reported by the platform (if available). + int32_t button{InputButton::None}; //!< Triggering button index (0..n-1), or -1 when none (touch / no button). + uint32_t pressedButtons{0}; //!< Bitmask of currently pressed buttons; bit i corresponds to button index i. + PointerType type{PointerType::Mouse}; //!< Type of pointer (Mouse, Touch, Pen). +}; + /** @class Event * @brief Base class of all kinds of events. */ @@ -50,10 +154,9 @@ class AX_DLL Event : public Object /** Type Event type.*/ enum class Type { - TOUCH, + POINTER, KEYBOARD, ACCELERATION, - MOUSE, FOCUS, GAME_CONTROLLER, CUSTOM @@ -90,6 +193,17 @@ class AX_DLL Event : public Object */ Node* getCurrentTarget() { return _currentTarget; } + // Syntactic sugar for safe downcasting without explicit static_cast in business logic + template + [[nodiscard]] const _Ty* as() const + { + if constexpr (std::is_base_of_v) + { + return static_cast(this); + } + return nullptr; + } + protected: /** Sets current target */ void setCurrentTarget(Node* target) { _currentTarget = target; } diff --git a/axmol/base/EventDispatcher.cpp b/axmol/base/EventDispatcher.cpp index 9bd580325e9e..1ef9c461b43d 100644 --- a/axmol/base/EventDispatcher.cpp +++ b/axmol/base/EventDispatcher.cpp @@ -25,18 +25,18 @@ ****************************************************************************/ #include "axmol/base/EventDispatcher.h" #include - -#include "axmol/base/EventCustom.h" -#include "axmol/base/EventListenerTouch.h" -#include "axmol/base/EventListenerAcceleration.h" -#include "axmol/base/EventListenerMouse.h" -#include "axmol/base/EventListenerKeyboard.h" -#include "axmol/base/EventListenerCustom.h" -#include "axmol/base/EventListenerFocus.h" +#include + +#include "axmol/base/CustomEvent.h" +#include "axmol/base/PointerEventListener.h" +#include "axmol/base/AccelerationEventListener.h" +#include "axmol/base/KeyboardEventListener.h" +#include "axmol/base/CustomEventListener.h" +#include "axmol/base/FocusEventListener.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || \ AX_TARGET_PLATFORM == AX_PLATFORM_MAC || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || \ AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) -# include "axmol/base/EventListenerController.h" +# include "axmol/base/ControllerEventListener.h" #endif #include "axmol/scene/Scene.h" #include "axmol/base/Director.h" @@ -65,31 +65,73 @@ class DispatchGuard namespace ax { +namespace +{ +constexpr uint64_t CAPTURE_BUTTON_BITS = 8; +constexpr uint64_t CAPTURE_BUTTON_MASK = (1ull << CAPTURE_BUTTON_BITS) - 1ull; +constexpr uint64_t CAPTURE_POINTER_MASK = ~CAPTURE_BUTTON_MASK; + +static uint64_t makeCapturedButtonBits(int32_t button) +{ + return static_cast(static_cast(button + 1)) & CAPTURE_BUTTON_MASK; +} + +static uint64_t makeCapturedPointerPrefix(intptr_t pointerId) +{ + return (static_cast(pointerId) << CAPTURE_BUTTON_BITS) & CAPTURE_POINTER_MASK; +} + +static uint64_t makePointerCaptureId(intptr_t pointerId, int32_t button) +{ + return makeCapturedPointerPrefix(pointerId) | makeCapturedButtonBits(button); +} + +static bool isPointerCaptureIdForPointer(uint64_t key, intptr_t pointerId) +{ + return (key & CAPTURE_POINTER_MASK) == makeCapturedPointerPrefix(pointerId); +} + +static PointerEvent::CaptureBits makePointerCaptureBits(PointerEvent* event) +{ + // ensure non major touch pointer also fill capture bits when onPointerDown + auto bits = static_cast(PointerEvent::CAPTURED); + if (event->getPointerType() == PointerType::Touch) + { + if (event->isPrimary()) + bits |= PointerEvent::PRIMARY_CAPTURED; + } + else + { + auto button = event->getButton(); + if (button >= 0 && button < 31) + bits |= 1u << button; + } + return bits; +} +} // namespace + static EventListener::ListenerID __getListenerID(Event* event) { EventListener::ListenerID ret; switch (event->getType()) { case Event::Type::ACCELERATION: - ret = EventListenerAcceleration::LISTENER_ID; + ret = AccelerationEventListener::LISTENER_ID; break; case Event::Type::CUSTOM: { - auto customEvent = static_cast(event); + auto customEvent = static_cast(event); ret = customEvent->getEventName(); } break; case Event::Type::KEYBOARD: - ret = EventListenerKeyboard::LISTENER_ID; - break; - case Event::Type::MOUSE: - ret = EventListenerMouse::LISTENER_ID; + ret = KeyboardEventListener::LISTENER_ID; break; case Event::Type::FOCUS: - ret = EventListenerFocus::LISTENER_ID; + ret = FocusEventListener::LISTENER_ID; break; - case Event::Type::TOUCH: - // Touch listener is very special, it contains two kinds of listeners, EventListenerTouchOneByOne and + case Event::Type::POINTER: + // Touch listener is very special, it contains two kinds of listeners, PointerEventListener and // EventListenerTouchAllAtOnce. return UNKNOWN instead. AXASSERT(false, "Don't call this method if the event is for touch."); break; @@ -97,7 +139,7 @@ static EventListener::ListenerID __getListenerID(Event* event) AX_TARGET_PLATFORM == AX_PLATFORM_MAC || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || \ AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) case Event::Type::GAME_CONTROLLER: - ret = EventListenerController::LISTENER_ID; + ret = ControllerEventListener::LISTENER_ID; break; #endif default: @@ -415,6 +457,8 @@ void EventDispatcher::resumeEventListenersForTarget(Node* target, bool recursive void EventDispatcher::removeEventListenersForTarget(Node* target, bool recursive /* = false */) { + removeCapturedPointerListenersForTarget(target); + // Ensure the node is removed from these immediately also. // Don't want any dangling pointers or the possibility of dealing with deleted objects.. _nodePriorityMap.erase(target); @@ -651,11 +695,11 @@ void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener, addEventListener(listener); } -EventListenerCustom* EventDispatcher::addCustomEventListener(std::string_view eventName, - const std::function& callback, +CustomEventListener* EventDispatcher::addCustomEventListener(std::string_view eventName, + const std::function& callback, int priority) { - EventListenerCustom* listener = EventListenerCustom::create(eventName, callback); + CustomEventListener* listener = CustomEventListener::create(eventName, callback); addEventListenerWithFixedPriority(listener, priority); return listener; } @@ -681,6 +725,7 @@ void EventDispatcher::removeEventListener(EventListener* listener) if (l == listener) { AX_SAFE_RETAIN(l); + removeCapturedPointerListener(l); l->setRegistered(false); if (l->getAssociatedNode() != nullptr) { @@ -863,106 +908,6 @@ void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners, } } -void EventDispatcher::dispatchTouchEventToListeners(EventListenerVector* listeners, - const std::function& onEvent) -{ - bool shouldStopPropagation = false; - auto fixedPriorityListeners = listeners->getFixedPriorityListeners(); - auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners(); - - ssize_t i = 0; - // priority < 0 - if (fixedPriorityListeners) - { - AXASSERT(listeners->getGt0Index() <= static_cast(fixedPriorityListeners->size()), - "Out of range exception!"); - - if (!fixedPriorityListeners->empty()) - { - for (; i < listeners->getGt0Index(); ++i) - { - auto l = fixedPriorityListeners->at(i); - if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l)) - { - shouldStopPropagation = true; - break; - } - } - } - } - - auto scene = Director::getInstance()->getRunningScene(); - if (scene && sceneGraphPriorityListeners) - { - if (!shouldStopPropagation) - { - // priority == 0, scene graph priority - - // first, get all enabled, unPaused and registered listeners - std::vector sceneListeners; - for (auto&& l : *sceneGraphPriorityListeners) - { - if (l->isEnabled() && !l->isPaused() && l->isRegistered()) - { - sceneListeners.emplace_back(l); - } - } - // second, for all camera call all listeners - // get a copy of cameras, prevent it's been modified in listener callback - // if camera's depth is greater, process it earlier - auto cameras = scene->getCameras(); - for (auto rit = cameras.rbegin(), ritRend = cameras.rend(); rit != ritRend; ++rit) - { - Camera* camera = *rit; - if (camera->isVisible() == false) - { - continue; - } - - Camera::_visitingCamera = camera; - auto cameraFlag = (unsigned short)camera->getCameraFlag(); - for (auto&& l : sceneListeners) - { - if (nullptr == l->getAssociatedNode() || - 0 == (l->getAssociatedNode()->getCameraMask() & cameraFlag)) - { - continue; - } - if (onEvent(l)) - { - shouldStopPropagation = true; - break; - } - } - if (shouldStopPropagation) - { - break; - } - } - Camera::_visitingCamera = nullptr; - } - } - - if (fixedPriorityListeners) - { - if (!shouldStopPropagation) - { - // priority > 0 - ssize_t size = fixedPriorityListeners->size(); - for (; i < size; ++i) - { - auto l = fixedPriorityListeners->at(i); - - if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l)) - { - // shouldStopPropagation = true; - break; - } - } - } - } -} - void EventDispatcher::dispatchEvent(Event* event, bool forced) { if (!_isEnabled && !forced) @@ -972,14 +917,10 @@ void EventDispatcher::dispatchEvent(Event* event, bool forced) DispatchGuard guard(_inDispatch); - if (event->getType() == Event::Type::TOUCH) - { - dispatchTouchEvent(static_cast(event)); - return; - } - else if (event->getType() == Event::Type::MOUSE) + if (event->getType() == Event::Type::POINTER) { - dispatchMouseEvent(static_cast(event)); + dispatchPointerEvent(static_cast(event)); + updateListeners(event); return; } @@ -987,11 +928,6 @@ void EventDispatcher::dispatchEvent(Event* event, bool forced) sortEventListeners(listenerID); - auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners; - if (event->getType() == Event::Type::MOUSE) - { - pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners; - } auto iter = _listenerMap.find(listenerID); if (iter != _listenerMap.end()) { @@ -1003,7 +939,7 @@ void EventDispatcher::dispatchEvent(Event* event, bool forced) return event->isStopped(); }; - (this->*pfnDispatchEventToListeners)(listeners, onEvent); + dispatchEventToListeners(listeners, onEvent); } updateListeners(event); @@ -1011,7 +947,7 @@ void EventDispatcher::dispatchEvent(Event* event, bool forced) void EventDispatcher::dispatchCustomEvent(std::string_view eventName, void* optionalUserData, bool forced) { - EventCustom ev(eventName); + CustomEvent ev(eventName); ev.setUserData(optionalUserData); dispatchEvent(&ev, forced); } @@ -1021,300 +957,346 @@ bool EventDispatcher::hasEventListener(std::string_view listenerID) const return getListeners(listenerID) != nullptr; } -void EventDispatcher::dispatchTouchEvent(EventTouch* event) +bool EventDispatcher::dispatchCapturedPointerEvent(PointerEvent* event) { - sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID); - sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID); + auto dispatchCapturedEntry = [event](const PointerCaptureEntry& entry) { + auto* listener = entry.listener; + if (!listener || !listener->_isRegistered) + return false; - auto oneByOneListeners = getListeners(EventListenerTouchOneByOne::LISTENER_ID); - auto allAtOnceListeners = getListeners(EventListenerTouchAllAtOnce::LISTENER_ID); + RefPtr guard(listener); - // If there aren't any touch listeners, return directly. - if (nullptr == oneByOneListeners && nullptr == allAtOnceListeners) - return; + event->setCurrentTarget(listener->getAssociatedNode()); + event->setCamera(entry.camera); + event->setCaptureBits(entry.captureBits); - struct TouchContext - { - EventTouch* event; - Touch* touch; - std::vector* pTouches; - std::vector mutableTouches; - std::vector::iterator touchesIter; - bool isNeedsMutableSet; - bool isSwallowed; - }; + switch (event->getPhase()) + { + case InputPhase::PointerMove: + if (listener->onPointerMove) + listener->onPointerMove(event); + break; + + case InputPhase::PointerUp: + if (listener->onPointerUp) + listener->onPointerUp(event); + break; - TouchContext touchContext; - touchContext.event = event; - touchContext.isNeedsMutableSet = (oneByOneListeners && allAtOnceListeners); + case InputPhase::PointerCancel: + if (listener->onPointerCancel) + listener->onPointerCancel(event); + break; - const std::vector& originalTouches = event->getTouches(); - if (!touchContext.isNeedsMutableSet) - touchContext.pTouches = const_cast*>(&originalTouches); - else - { - touchContext.mutableTouches = originalTouches; - touchContext.pTouches = &touchContext.mutableTouches; - } + default: + break; + } - // - // process the target handlers 1st - // - if (oneByOneListeners) - { - touchContext.touchesIter = touchContext.pTouches->begin(); + return true; + }; - for (auto&& touch : originalTouches) + auto mergeCapturedEntry = [](tlx::inlined_vector& entries, + const PointerCaptureEntry& entry) { + auto found = std::find_if(entries.begin(), entries.end(), + [&entry](const auto& item) { return item.listener == entry.listener; }); + if (found != entries.end()) { - touchContext.isSwallowed = false; - touchContext.touch = touch; + found->captureBits = static_cast(found->captureBits | entry.captureBits); + if (!found->camera) + found->camera = entry.camera; + } + else + { + entries.emplace_back(entry); + } + }; - auto onTouchEvent = [this, &touchContext](EventListener* l) -> bool { // Return true to break - EventListenerTouchOneByOne* listener = static_cast(l); + const auto pointerId = event->getPointerId(); + tlx::inlined_vector entries; - // Skip if the listener was removed. - if (!listener->_isRegistered) - return false; + switch (event->getPhase()) + { + case InputPhase::PointerMove: + { + if (event->getPointerType() == PointerType::Touch) + { + auto iter = _capturedPointerListeners.find(makePointerCaptureId(pointerId, InputButton::None)); + if (iter == _capturedPointerListeners.end()) + return false; - const auto event = touchContext.event; - auto touch = touchContext.touch; - event->setCurrentTarget(listener->_node); + if (!dispatchCapturedEntry(iter->second)) + { + _capturedPointerListeners.erase(iter); + return false; + } - bool isClaimed = false; - std::vector::iterator removedIter; + return true; + } - EventTouch::EventCode eventCode = event->getEventCode(); + auto buttons = event->getPressedButtons(); + if (buttons == 0) + return false; - if (eventCode == EventTouch::EventCode::BEGAN) - { - if (listener->onTouchBegan) - { - isClaimed = listener->onTouchBegan(touch, event); - if (isClaimed && listener->_isRegistered) - { - listener->_claimedTouches.emplace_back(touch); - } - } - } - else if (!listener->_claimedTouches.empty() && - ((removedIter = std::find(listener->_claimedTouches.begin(), listener->_claimedTouches.end(), - touch)) != listener->_claimedTouches.end())) - { - isClaimed = true; - - switch (eventCode) - { - case EventTouch::EventCode::MOVED: - if (listener->onTouchMoved) - { - listener->onTouchMoved(touch, event); - } - break; - case EventTouch::EventCode::ENDED: - if (listener->onTouchEnded) - { - listener->onTouchEnded(touch, event); - } - if (listener->_isRegistered) - { - listener->_claimedTouches.erase(removedIter); - } - break; - case EventTouch::EventCode::CANCELLED: - if (listener->onTouchCancelled) - { - listener->onTouchCancelled(touch, event); - } - if (listener->_isRegistered) - { - listener->_claimedTouches.erase(removedIter); - } - break; - default: - AXASSERT(false, "The eventcode is invalid."); - break; - } - } + while (buttons != 0) + { + const auto button = static_cast(std::countr_zero(buttons)); + auto iter = _capturedPointerListeners.find(makePointerCaptureId(pointerId, button)); - // If the event was stopped, return directly. - if (event->isStopped()) + if (iter != _capturedPointerListeners.end()) + { + auto& entry = iter->second; + if (!entry.listener || !entry.listener->_isRegistered) { - updateListeners(event); - return true; + _capturedPointerListeners.erase(iter); } - - AXASSERT(touch->getID() == (*touchContext.touchesIter)->getID(), - "touches ID should be equal to mutableTouchesIter's ID."); - - if (isClaimed && listener->_isRegistered && listener->_needSwallow) + else { - if (touchContext.isNeedsMutableSet) - { - touchContext.touchesIter = touchContext.mutableTouches.erase(touchContext.touchesIter); - touchContext.isSwallowed = true; - } - return true; + mergeCapturedEntry(entries, entry); } + } - return false; - }; + buttons &= buttons - 1; + } - // - dispatchTouchEventToListeners(oneByOneListeners, onTouchEvent); - if (event->isStopped()) - { - return; - } + if (entries.empty()) + return false; - if (!touchContext.isSwallowed) - ++touchContext.touchesIter; + for (const auto& entry : entries) + { + dispatchCapturedEntry(entry); + if (event->isStopped()) + break; } + + return true; } - // - // process standard handlers 2nd - // - if (allAtOnceListeners && !touchContext.pTouches->empty()) + case InputPhase::PointerUp: { - auto onTouchesEvent = [this, &touchContext](EventListener* l) -> bool { - EventListenerTouchAllAtOnce* listener = static_cast(l); - // Skip if the listener was removed. - if (!listener->_isRegistered) - return false; + auto iter = _capturedPointerListeners.find(makePointerCaptureId(pointerId, event->getButton())); + if (iter == _capturedPointerListeners.end()) + return false; - auto& remainingTouches = *touchContext.pTouches; - const auto event = touchContext.event; + auto entry = iter->second; + _capturedPointerListeners.erase(iter); - event->setCurrentTarget(listener->_node); + return dispatchCapturedEntry(entry); + } - switch (event->getEventCode()) + case InputPhase::PointerCancel: + { + for (auto iter = _capturedPointerListeners.begin(); iter != _capturedPointerListeners.end();) + { + if (isPointerCaptureIdForPointer(iter->first, pointerId)) { - case EventTouch::EventCode::BEGAN: - if (listener->onTouchesBegan) - { - listener->onTouchesBegan(remainingTouches, event); - } - break; - case EventTouch::EventCode::MOVED: - if (listener->onTouchesMoved) - { - listener->onTouchesMoved(remainingTouches, event); - } - break; - case EventTouch::EventCode::ENDED: - if (listener->onTouchesEnded) - { - listener->onTouchesEnded(remainingTouches, event); - } - break; - case EventTouch::EventCode::CANCELLED: - if (listener->onTouchesCancelled) + auto& entry = iter->second; + if (entry.listener && entry.listener->_isRegistered) { - listener->onTouchesCancelled(remainingTouches, event); + mergeCapturedEntry(entries, entry); } - break; - default: - AXASSERT(false, "The eventcode is invalid."); - break; - } - // If the event was stopped, return directly. - if (event->isStopped()) - { - updateListeners(event); - return true; + iter = _capturedPointerListeners.erase(iter); + continue; } - return false; - }; - - dispatchTouchEventToListeners(allAtOnceListeners, onTouchesEvent); - if (event->isStopped()) - { - return; + ++iter; } + + if (entries.empty()) + return true; + + for (const auto& entry : entries) + dispatchCapturedEntry(entry); + + return true; } - updateListeners(event); + default: + return false; + } } -void EventDispatcher::dispatchMouseEvent(EventMouse* event) +void EventDispatcher::dispatchUncapturedPointerEvent(PointerEvent* event, PointerCaptureId eventCaptureId) { - sortEventListeners(EventListenerMouse::LISTENER_ID); + sortEventListeners(PointerEventListener::LISTENER_ID); - auto listeners = getListeners(EventListenerMouse::LISTENER_ID); - - // If there aren't any mouse listeners, return directly. - if (nullptr == listeners) + auto listeners = getListeners(PointerEventListener::LISTENER_ID); + if (!listeners) return; - auto onMouseEvent = [this, event](EventListener* l) -> bool { // Return true to break - EventListenerMouse* listener = static_cast(l); - - // Skip if the listener was removed. - if (!listener->_isRegistered) + // Helper lambda to dispatch uncaptured event to listener + auto onPointerEvent = [this, event, eventCaptureId](EventListener* l) -> bool { + auto* listener = static_cast(l); + if (!listener || !listener->_isRegistered) return false; - event->setCurrentTarget(listener->_node); + event->setCurrentTarget(listener->getAssociatedNode()); bool isClaimed = false; - switch (event->getMouseEventType()) + switch (event->getPhase()) { - case EventMouse::MouseEventType::MOUSE_UP: - if (listener->onMouseUp) - { - isClaimed = listener->onMouseUp(event); - } - break; - case EventMouse::MouseEventType::MOUSE_DOWN: - if (listener->onMouseDown) - { - isClaimed = listener->onMouseDown(event); - } - break; - case EventMouse::MouseEventType::MOUSE_MOVE: - if (listener->onMouseMove) + case InputPhase::PointerDown: + if (listener->onPointerDown) + isClaimed = listener->onPointerDown(event); + + if (isClaimed && listener->_isRegistered) { - isClaimed = listener->onMouseMove(event); + const auto captureBits = makePointerCaptureBits(event); + auto iter = _capturedPointerListeners.find(eventCaptureId); + if (iter == _capturedPointerListeners.end() || !iter->second.listener || + !iter->second.listener->_isRegistered) + _capturedPointerListeners[eventCaptureId] = + PointerCaptureEntry{listener, captureBits, event->getCamera()}; } break; - case EventMouse::MouseEventType::MOUSE_SCROLL: - if (listener->onMouseScroll) - { - isClaimed = listener->onMouseScroll(event); - } + + case InputPhase::PointerMove: + if (listener->onPointerMove) + listener->onPointerMove(event); break; - case EventMouse::MouseEventType::MOUSE_NONE: + + case InputPhase::PointerScroll: + if (listener->onPointerScroll) + isClaimed = listener->onPointerScroll(event); break; + default: - AXASSERT(false, "The type is invalid."); break; } - // If the event was stopped, return directly. - if (event->isStopped()) - { - updateListeners(event); + if (event->isStopped() || isClaimed) return true; + + return false; + }; + + /// Iterator listener to dispatch + bool shouldStopPropagation = false; + auto fixedPriorityListeners = listeners->getFixedPriorityListeners(); + auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners(); + + // Fixed-priority listeners are not camera/node based. + // Keep their old semantics and clear input camera context. + if (event) + event->setCamera(nullptr); + + ssize_t i = 0; + + // priority < 0 + if (fixedPriorityListeners) + { + AXASSERT(listeners->getGt0Index() <= static_cast(fixedPriorityListeners->size()), + "Out of range exception!"); + + if (!fixedPriorityListeners->empty()) + { + for (; i < listeners->getGt0Index(); ++i) + { + auto l = fixedPriorityListeners->at(i); + if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onPointerEvent(l)) + { + shouldStopPropagation = true; + break; + } + } } + } - if (isClaimed && listener->_isRegistered && listener->_needSwallow) + auto scene = Director::getInstance()->getRunningScene(); + if (scene && sceneGraphPriorityListeners && !shouldStopPropagation) + { + // priority == 0, scene graph priority + // + // New model: + // listener outer loop + // camera inner loop only for hit-test + // + // This guarantees the same scene graph listener receives onPointerDown + // at most once for a raw PointerDown event. + std::vector sceneListeners; + sceneListeners.reserve(sceneGraphPriorityListeners->size()); + + for (auto&& l : *sceneGraphPriorityListeners) { - return true; + if (l->isEnabled() && !l->isPaused() && l->isRegistered()) + { + l->retain(); + sceneListeners.emplace_back(l); + } } - return false; - }; + scene->retain(); + auto cameras = scene->getCameras(); + for (auto&& camera : cameras) + camera->retain(); - // - dispatchTouchEventToListeners(listeners, onMouseEvent); - if (event->isStopped()) + for (auto&& l : sceneListeners) + { + if (!l->isEnabled() || l->isPaused() || !l->isRegistered()) + continue; + + auto* target = l->getAssociatedNode(); + if (!target || _nodePriorityMap.find(target) == _nodePriorityMap.end()) + continue; + + auto* pointerListener = static_cast(l); + Camera* hitCamera = findHitCameraForListener(event, pointerListener, cameras); + if (!hitCamera) + continue; + + // Camera context for input callbacks. Do not use Camera::_visitingCamera. + event->setCamera(hitCamera); + + if (onPointerEvent(l)) + { + shouldStopPropagation = true; + break; + } + } + + if (event) + event->setCamera(nullptr); + + for (auto&& camera : cameras) + camera->release(); + + scene->release(); + + for (auto&& l : sceneListeners) + l->release(); + } + + // priority > 0 + if (fixedPriorityListeners && !shouldStopPropagation) { - return; + // Fixed-priority listeners are not camera/node based. + if (event) + event->setCamera(nullptr); + + ssize_t size = fixedPriorityListeners->size(); + for (; i < size; ++i) + { + auto l = fixedPriorityListeners->at(i); + + if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onPointerEvent(l)) + { + // shouldStopPropagation = true; + break; + } + } } +} - updateListeners(event); +void EventDispatcher::dispatchPointerEvent(PointerEvent* event) +{ + // Avoid carrying stale camera context into fixed-priority listeners or non-hit paths. + event->setCamera(nullptr); + + if (dispatchCapturedPointerEvent(event)) + return; + + const auto eventCaptureId = makePointerCaptureId(event->getPointerId(), event->getButton()); + dispatchUncapturedPointerEvent(event, eventCaptureId); } void EventDispatcher::updateListeners(Event* event) @@ -1387,10 +1369,9 @@ void EventDispatcher::updateListeners(Event* event) } }; - if (event->getType() == Event::Type::TOUCH) + if (event->getType() == Event::Type::POINTER) { - onUpdateListeners(EventListenerTouchOneByOne::LISTENER_ID); - onUpdateListeners(EventListenerTouchAllAtOnce::LISTENER_ID); + onUpdateListeners(PointerEventListener::LISTENER_ID); } else { @@ -1500,10 +1481,22 @@ void EventDispatcher::sortEventListenersOfSceneGraphPriority(std::string_view li visitTarget(rootNode, true); + auto getNodePriority = [this](const EventListener* listener) -> int { + if (!listener || !listener->isRegistered()) + return 0; + + auto node = listener->getAssociatedNode(); + if (!node) + return 0; + + auto iter = _nodePriorityMap.find(node); + return iter != _nodePriorityMap.end() ? iter->second : 0; + }; + // After sort: priority < 0, > 0 std::stable_sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), - [this](const EventListener* l1, const EventListener* l2) { - return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()]; + [&getNodePriority](const EventListener* l1, const EventListener* l2) { + return getNodePriority(l1) > getNodePriority(l2); }); #if DUMP_LISTENER_ITEM_PRIORITY_INFO @@ -1580,6 +1573,7 @@ void EventDispatcher::removeEventListenersForListenerID(std::string_view listene for (auto iter = listenerVector->begin(); iter != listenerVector->end();) { auto l = *iter; + removeCapturedPointerListener(l); l->setRegistered(false); if (l->getAssociatedNode() != nullptr) { @@ -1632,25 +1626,17 @@ void EventDispatcher::removeEventListenersForListenerID(std::string_view listene void EventDispatcher::removeEventListenersForType(EventListener::Type listenerType) { - if (listenerType == EventListener::Type::TOUCH_ONE_BY_ONE) + if (listenerType == EventListener::Type::POINTER) { - removeEventListenersForListenerID(EventListenerTouchOneByOne::LISTENER_ID); - } - else if (listenerType == EventListener::Type::TOUCH_ALL_AT_ONCE) - { - removeEventListenersForListenerID(EventListenerTouchAllAtOnce::LISTENER_ID); - } - else if (listenerType == EventListener::Type::MOUSE) - { - removeEventListenersForListenerID(EventListenerMouse::LISTENER_ID); + removeEventListenersForListenerID(PointerEventListener::LISTENER_ID); } else if (listenerType == EventListener::Type::ACCELERATION) { - removeEventListenersForListenerID(EventListenerAcceleration::LISTENER_ID); + removeEventListenersForListenerID(AccelerationEventListener::LISTENER_ID); } else if (listenerType == EventListener::Type::KEYBOARD) { - removeEventListenersForListenerID(EventListenerKeyboard::LISTENER_ID); + removeEventListenersForListenerID(KeyboardEventListener::LISTENER_ID); } else { @@ -1789,6 +1775,37 @@ void EventDispatcher::cleanToRemovedListeners() _toRemovedListeners.clear(); } +void EventDispatcher::removeCapturedPointerListener(EventListener* listener) +{ + if (listener == nullptr || listener->getType() != EventListener::Type::POINTER) + return; + + auto* pointerListener = static_cast(listener); + + for (auto iter = _capturedPointerListeners.begin(); iter != _capturedPointerListeners.end();) + { + if (iter->second.listener == pointerListener) + iter = _capturedPointerListeners.erase(iter); + else + ++iter; + } +} + +void EventDispatcher::removeCapturedPointerListenersForTarget(Node* target) +{ + if (target == nullptr) + return; + + for (auto iter = _capturedPointerListeners.begin(); iter != _capturedPointerListeners.end();) + { + auto* listener = iter->second.listener; + if (listener && listener->getAssociatedNode() == target) + iter = _capturedPointerListeners.erase(iter); + else + ++iter; + } +} + void EventDispatcher::releaseListener(EventListener* listener) { #if AX_ENABLE_GC_FOR_NATIVE_OBJECTS @@ -1801,4 +1818,46 @@ void EventDispatcher::releaseListener(EventListener* listener) AX_SAFE_RELEASE(listener); } +Camera* EventDispatcher::findHitCameraForListener(PointerEvent* event, + PointerEventListener* listener, + const std::vector& cameras) +{ + if (!event || !listener) + return nullptr; + + auto* target = listener->getAssociatedNode(); + if (!target) + return nullptr; + + for (auto rit = cameras.rbegin(), ritEnd = cameras.rend(); rit != ritEnd; ++rit) + { + Camera* camera = *rit; + if (!camera || !camera->isVisible()) + continue; + + auto cameraFlag = static_cast(camera->getCameraFlag()); + if ((target->getCameraMask() & cameraFlag) == 0) + continue; + + Vec3 hitPoint; + + if (listener->onPointerHitTest) + { + if (!listener->onPointerHitTest(event, camera, &hitPoint)) + continue; + } + else + { + if (!target->onPointerHitTest(event, camera, &hitPoint)) + continue; + } + + // TOOD: store hit point to event + + return camera; + } + + return nullptr; +} + } // namespace ax diff --git a/axmol/base/EventDispatcher.h b/axmol/base/EventDispatcher.h index 2e54c53e6910..c9c21daf4870 100644 --- a/axmol/base/EventDispatcher.h +++ b/axmol/base/EventDispatcher.h @@ -35,8 +35,10 @@ #include "axmol/platform/PlatformMacros.h" #include "axmol/base/EventListener.h" #include "axmol/base/Event.h" +#include "axmol/base/PointerEvent.h" #include "axmol/platform/StdC.h" #include "axmol/tlx/hlookup.hpp" +#include "axmol/tlx/inlined_vector.hpp" /** * @addtogroup base @@ -47,11 +49,12 @@ namespace ax { class Event; -class EventTouch; -class EventMouse; +class PointerEvent; class Node; -class EventCustom; -class EventListenerCustom; +class CustomEvent; +class CustomEventListener; +class PointerEventListener; +class Camera; /** @class EventDispatcher * @brief This class manages event listener subscriptions @@ -89,8 +92,8 @@ class AX_DLL EventDispatcher : public Object * @param callback A given callback method that associated the event name. * @return the generated event. Needed in order to remove the event from the dispatcher */ - EventListenerCustom* addCustomEventListener(std::string_view eventName, - const std::function& callback, + CustomEventListener* addCustomEventListener(std::string_view eventName, + const std::function& callback, int priority = 1); ///////////////////////////////////////////// @@ -280,13 +283,7 @@ class AX_DLL EventDispatcher : public Object */ void updateListeners(Event* event); - /** Touch event needs to be processed different with other events since it needs support ALL_AT_ONCE and ONE_BY_NONE - * mode. */ - void dispatchTouchEvent(EventTouch* event); - - /** Mouse Scroll event needs to be processed different with other events since it needs support ALL_AT_ONCE and - * ONE_BY_NONE mode. */ - void dispatchMouseEvent(EventMouse* event); + void dispatchPointerEvent(PointerEvent* event); /** Associates node with event listener */ void associateNodeAndEventListener(Node* node, EventListener* listener); @@ -297,19 +294,15 @@ class AX_DLL EventDispatcher : public Object /** Dispatches event to listeners with a specified listener type */ void dispatchEventToListeners(EventListenerVector* listeners, const std::function& onEvent); - /** Special version dispatchEventToListeners for touch/mouse event. - * - * Touch/mouse event process flow different with common event, - * for scene graph node listeners, touch event process flow should - * order by viewport/camera first, because the touch location convert - * to 3D world space is different by different camera. - * When listener process touch event, can get current camera by Camera::getVisitingCamera(). - */ - void dispatchTouchEventToListeners(EventListenerVector* listeners, - const std::function& onEvent); + void removeCapturedPointerListener(EventListener* listener); + void removeCapturedPointerListenersForTarget(Node* target); void releaseListener(EventListener* listener); + static Camera* findHitCameraForListener(PointerEvent* event, + PointerEventListener* listener, + const std::vector& cameras); + /// Priority dirty flag enum class DirtyFlag { @@ -329,6 +322,17 @@ class AX_DLL EventDispatcher : public Object /** Remove all listeners in _toRemoveListeners list and cleanup */ void cleanToRemovedListeners(); + using PointerCaptureId = uint64_t; + struct PointerCaptureEntry + { + PointerEventListener* listener{nullptr}; + PointerEvent::CaptureBits captureBits{PointerEvent::CAPTURE_NONE}; + const Camera* camera{nullptr}; + }; + + bool dispatchCapturedPointerEvent(PointerEvent* event); + void dispatchUncapturedPointerEvent(PointerEvent* event, PointerCaptureId captureId); + /** Listeners map */ tlx::string_map _listenerMap; @@ -336,13 +340,15 @@ class AX_DLL EventDispatcher : public Object tlx::string_map _priorityDirtyFlagMap; /** The map of node and event listeners */ - std::unordered_map*> _nodeListenersMap; + tlx::hash_map*> _nodeListenersMap; /** The map of node and its event priority */ - std::unordered_map _nodePriorityMap; + tlx::hash_map _nodePriorityMap; /** key: Global Z Order, value: Sorted Nodes */ - std::unordered_map> _globalZOrderNodeMap; + tlx::hash_map> _globalZOrderNodeMap; + + tlx::hash_map _capturedPointerListeners; /** The listeners to be added after dispatching event */ std::vector _toAddedListeners; diff --git a/axmol/base/EventListener.h b/axmol/base/EventListener.h index 06b5961167b6..2ac17b4c30a3 100644 --- a/axmol/base/EventListener.h +++ b/axmol/base/EventListener.h @@ -46,8 +46,8 @@ class Node; /** @class EventListener * @brief The base class of event listener. * If you need custom listener which with different callback, you need to inherit this class. - * For instance, you could refer to EventListenerAcceleration, EventListenerKeyboard, EventListenerTouchOneByOne, - * EventListenerCustom. + * For instance, you could refer to AccelerationEventListener, KeyboardEventListener, PointerEventListener, + * CustomEventListener. */ class AX_DLL EventListener : public Object { @@ -56,8 +56,7 @@ class AX_DLL EventListener : public Object enum class Type { UNKNOWN, - TOUCH_ONE_BY_ONE, - TOUCH_ALL_AT_ONCE, + POINTER, KEYBOARD, MOUSE, ACCELERATION, diff --git a/axmol/base/EventListenerTouch.cpp b/axmol/base/EventListenerTouch.cpp deleted file mode 100644 index fe7b88ef14f5..000000000000 --- a/axmol/base/EventListenerTouch.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - - https://axmol.dev/ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - ****************************************************************************/ - -#include "axmol/base/EventListenerTouch.h" -#include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventTouch.h" -#include "axmol/base/Touch.h" - -#include - -namespace ax -{ - -const std::string_view EventListenerTouchOneByOne::LISTENER_ID = "__ax_touch_one_by_one"sv; - -EventListenerTouchOneByOne::EventListenerTouchOneByOne() - : onTouchBegan(nullptr) - , onTouchMoved(nullptr) - , onTouchEnded(nullptr) - , onTouchCancelled(nullptr) - , _needSwallow(false) -{} - -EventListenerTouchOneByOne::~EventListenerTouchOneByOne() -{ - AXLOGV("In the destructor of EventListenerTouchOneByOne, {}", fmt::ptr(this)); -} - -bool EventListenerTouchOneByOne::init() -{ - if (EventListener::init(Type::TOUCH_ONE_BY_ONE, LISTENER_ID, nullptr)) - { - return true; - } - - return false; -} - -void EventListenerTouchOneByOne::setSwallowTouches(bool needSwallow) -{ - _needSwallow = needSwallow; -} - -bool EventListenerTouchOneByOne::isSwallowTouches() -{ - return _needSwallow; -} - -EventListenerTouchOneByOne* EventListenerTouchOneByOne::create() -{ - auto ret = new EventListenerTouchOneByOne(); - if (ret->init()) - { - ret->autorelease(); - } - else - { - AX_SAFE_DELETE(ret); - } - return ret; -} - -bool EventListenerTouchOneByOne::checkAvailable() -{ - // EventDispatcher will use the return value of 'onTouchBegan' to determine whether to pass following 'move', 'end' - // message to 'EventListenerTouchOneByOne' or not. So 'onTouchBegan' needs to be set. - if (onTouchBegan == nullptr) - { - AXASSERT(false, "Invalid EventListenerTouchOneByOne!"); - return false; - } - - return true; -} - -EventListenerTouchOneByOne* EventListenerTouchOneByOne::clone() -{ - auto ret = new EventListenerTouchOneByOne(); - if (ret->init()) - { - ret->autorelease(); - - ret->onTouchBegan = onTouchBegan; - ret->onTouchMoved = onTouchMoved; - ret->onTouchEnded = onTouchEnded; - ret->onTouchCancelled = onTouchCancelled; - - ret->_claimedTouches = _claimedTouches; - ret->_needSwallow = _needSwallow; - } - else - { - AX_SAFE_DELETE(ret); - } - return ret; -} - -///////// - -const std::string_view EventListenerTouchAllAtOnce::LISTENER_ID = "__ax_touch_all_at_once"sv; - -EventListenerTouchAllAtOnce::EventListenerTouchAllAtOnce() - : onTouchesBegan(nullptr), onTouchesMoved(nullptr), onTouchesEnded(nullptr), onTouchesCancelled(nullptr) -{} - -EventListenerTouchAllAtOnce::~EventListenerTouchAllAtOnce() -{ - AXLOGV("In the destructor of EventListenerTouchAllAtOnce, {}", fmt::ptr(this)); -} - -bool EventListenerTouchAllAtOnce::init() -{ - if (EventListener::init(Type::TOUCH_ALL_AT_ONCE, LISTENER_ID, nullptr)) - { - return true; - } - - return false; -} - -EventListenerTouchAllAtOnce* EventListenerTouchAllAtOnce::create() -{ - auto ret = new EventListenerTouchAllAtOnce(); - if (ret->init()) - { - ret->autorelease(); - } - else - { - AX_SAFE_DELETE(ret); - } - return ret; -} - -bool EventListenerTouchAllAtOnce::checkAvailable() -{ - if (onTouchesBegan == nullptr && onTouchesMoved == nullptr && onTouchesEnded == nullptr && - onTouchesCancelled == nullptr) - { - AXASSERT(false, "Invalid EventListenerTouchAllAtOnce!"); - return false; - } - - return true; -} - -EventListenerTouchAllAtOnce* EventListenerTouchAllAtOnce::clone() -{ - auto ret = new EventListenerTouchAllAtOnce(); - if (ret->init()) - { - ret->autorelease(); - - ret->onTouchesBegan = onTouchesBegan; - ret->onTouchesMoved = onTouchesMoved; - ret->onTouchesEnded = onTouchesEnded; - ret->onTouchesCancelled = onTouchesCancelled; - } - else - { - AX_SAFE_DELETE(ret); - } - return ret; -} - -} // namespace ax diff --git a/axmol/base/EventListenerTouch.h b/axmol/base/EventListenerTouch.h deleted file mode 100644 index 20e793cac930..000000000000 --- a/axmol/base/EventListenerTouch.h +++ /dev/null @@ -1,134 +0,0 @@ -/**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - - https://axmol.dev/ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - - ****************************************************************************/ - -#pragma once - -#include "axmol/base/EventListener.h" -#include - -/** - * @addtogroup base - * @{ - */ - -namespace ax -{ - -class Touch; - -/** @class EventListenerTouchOneByOne - * @brief Single touch event listener. - */ -class AX_DLL EventListenerTouchOneByOne : public EventListener -{ -public: - static const std::string_view LISTENER_ID; - - /** Create a one by one touch event listener. - */ - static EventListenerTouchOneByOne* create(); - - /** - * Destructor. - */ - virtual ~EventListenerTouchOneByOne(); - - /** Whether or not to swall touches. - * - * @param needSwallow True if needs to swall touches. - */ - void setSwallowTouches(bool needSwallow); - /** Is swall touches or not. - * - * @return True if needs to swall touches. - */ - bool isSwallowTouches(); - - /// Overrides - EventListenerTouchOneByOne* clone() override; - bool checkAvailable() override; - // - -public: - typedef std::function ccTouchBeganCallback; - typedef std::function ccTouchCallback; - - ccTouchBeganCallback onTouchBegan; - ccTouchCallback onTouchMoved; - ccTouchCallback onTouchEnded; - ccTouchCallback onTouchCancelled; - - EventListenerTouchOneByOne(); - bool init(); - -private: - std::vector _claimedTouches; - bool _needSwallow; - - friend class EventDispatcher; -}; - -/** @class EventListenerTouchAllAtOnce - * @brief Multiple touches event listener. - */ -class AX_DLL EventListenerTouchAllAtOnce : public EventListener -{ -public: - static const std::string_view LISTENER_ID; - - /** Create a all at once event listener. - * - * @return An autoreleased EventListenerTouchAllAtOnce object. - */ - static EventListenerTouchAllAtOnce* create(); - /** Destructor. - */ - virtual ~EventListenerTouchAllAtOnce(); - - /// Overrides - EventListenerTouchAllAtOnce* clone() override; - bool checkAvailable() override; - // -public: - typedef std::function&, Event*)> ccTouchesCallback; - - ccTouchesCallback onTouchesBegan; - ccTouchesCallback onTouchesMoved; - ccTouchesCallback onTouchesEnded; - ccTouchesCallback onTouchesCancelled; - - EventListenerTouchAllAtOnce(); - bool init(); - -private: - friend class EventDispatcher; -}; - -} // namespace ax - -// end of base group -/// @} diff --git a/axmol/base/EventMouse.cpp b/axmol/base/EventMouse.cpp deleted file mode 100644 index 54b539f3ca2e..000000000000 --- a/axmol/base/EventMouse.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - - https://axmol.dev/ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - - ****************************************************************************/ - -#include "axmol/base/EventMouse.h" -#include "axmol/base/Director.h" - -namespace ax -{ - -EventMouse::EventMouse() - : Event(Type::MOUSE) - , _mouseButton(MouseButton::BUTTON_UNSET) - , _scrollX(0.0f) - , _scrollY(0.0f) - , _startPointCaptured(false) {}; - -// returns the current touch location in screen coordinates -Vec2 EventMouse::getLocationInView() const -{ - return _point; -} - -// returns the previous touch location in screen coordinates -Vec2 EventMouse::getPreviousLocationInView() const -{ - return _prevPoint; -} - -// returns the start touch location in screen coordinates -Vec2 EventMouse::getStartLocationInView() const -{ - return _startPoint; -} - -// returns the current touch location in OpenGL coordinates -Vec2 EventMouse::getLocation() const -{ - return Director::getInstance()->screenToWorld(_point); -} - -// returns the previous touch location in OpenGL coordinates -Vec2 EventMouse::getPreviousLocation() const -{ - return Director::getInstance()->screenToWorld(_prevPoint); -} - -// returns the start touch location in OpenGL coordinates -Vec2 EventMouse::getStartLocation() const -{ - return Director::getInstance()->screenToWorld(_startPoint); -} - -// returns the delta position between the current location and the previous location in OpenGL coordinates -Vec2 EventMouse::getDelta() const -{ - return getLocation() - getPreviousLocation(); -} -} // namespace ax diff --git a/axmol/base/EventMouse.h b/axmol/base/EventMouse.h deleted file mode 100644 index 098a5a845e3d..000000000000 --- a/axmol/base/EventMouse.h +++ /dev/null @@ -1,197 +0,0 @@ -/**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - - https://axmol.dev/ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - - ****************************************************************************/ -#pragma once - -#include "axmol/base/Event.h" -#include "axmol/math/Math.h" - -/** - * @addtogroup base - * @{ - */ - -namespace ax -{ - -/** @class EventMouse - * @brief The mouse event. - */ -class AX_DLL EventMouse : public Event -{ -public: - /** - * MouseEventType Different types of MouseEvent. - */ - enum class MouseEventType - { - MOUSE_NONE, - MOUSE_DOWN, - MOUSE_UP, - MOUSE_MOVE, - MOUSE_SCROLL, - }; - - enum class MouseButton - { - BUTTON_UNSET = -1, - BUTTON_LEFT = 0, - BUTTON_RIGHT = 1, - BUTTON_MIDDLE = 2, - BUTTON_4 = 3, - BUTTON_5 = 4, - BUTTON_6 = 5, - BUTTON_7 = 6, - BUTTON_8 = 7 - }; - - /** Constructor. - * - * @param mouseEventCode A given mouse event type. - */ - EventMouse(); - - /** Get mouse event type. - * - * @return The type of the event. - */ - MouseEventType getMouseEventType() const { return _mouseEventType; } - - /** Set mouse scroll data. - * - * @param scrollX The scroll data of x axis. - * @param scrollY The scroll data of y axis. - */ - void setScrollData(float scrollX, float scrollY) - { - _scrollX = scrollX; - _scrollY = scrollY; - } - /** Get mouse scroll data of x axis. - * - * @return The scroll data of x axis. - */ - float getScrollX() const { return _scrollX; } - /** Get mouse scroll data of y axis. - * - * @return The scroll data of y axis. - */ - float getScrollY() const { return _scrollY; } - - /** Set the cursor position. - * - * @param x The x coordinate of cursor position. - * @param y The y coordinate of cursor position. - */ - [[internal]] void setMouseInfo(float x, float y, MouseButton button, MouseEventType type) - { - _prevPoint = _point; - _point.x = x; - _point.y = y; - _mouseButton = button; - _mouseEventType = type; - if (!_startPointCaptured) - { - _startPoint = _point; - _startPointCaptured = true; - } - } - - /** Set mouse button. - * - * @param button a given mouse button. - */ - void setMouseButton(MouseButton button) { _mouseButton = button; } - /** Get mouse button. - * - * @return The mouse button. - */ - MouseButton getMouseButton() const { return _mouseButton; } - /** Get the cursor position of x axis. - * - * @return The x coordinate of cursor position. - */ - AX_DEPRECATED(2.2) float getCursorX() const { return getLocation().x; } - /** Get the cursor position of y axis. - * - * @return The y coordinate of cursor position. - */ - AX_DEPRECATED(2.2) float getCursorY() const { return getLocation().y; } - - /** Returns the current touch location in OpenGL coordinates. - * - * @return The current touch location in OpenGL coordinates. - */ - Vec2 getLocation() const; - /** Returns the previous touch location in OpenGL coordinates. - * - * @return The previous touch location in OpenGL coordinates. - */ - Vec2 getPreviousLocation() const; - /** Returns the start touch location in OpenGL coordinates. - * - * @return The start touch location in OpenGL coordinates. - */ - Vec2 getStartLocation() const; - /** Returns the delta of 2 current touches locations in screen coordinates. - * - * @return The delta of 2 current touches locations in screen coordinates. - */ - Vec2 getDelta() const; - /** Returns the current touch location in screen coordinates. - * - * @return The current touch location in screen coordinates. - */ - Vec2 getLocationInView() const; - /** Returns the previous touch location in screen coordinates. - * - * @return The previous touch location in screen coordinates. - */ - Vec2 getPreviousLocationInView() const; - /** Returns the start touch location in screen coordinates. - * - * @return The start touch location in screen coordinates. - */ - Vec2 getStartLocationInView() const; - -private: - MouseEventType _mouseEventType{MouseEventType::MOUSE_NONE}; - MouseButton _mouseButton; - float _scrollX; - float _scrollY; - - bool _startPointCaptured; - Vec2 _startPoint; - Vec2 _point; - Vec2 _prevPoint; - - friend class EventListenerMouse; -}; - -} // namespace ax - -// end of base group -/// @} diff --git a/axmol/base/EventTouch.cpp b/axmol/base/EventTouch.cpp deleted file mode 100644 index 90f874b673a3..000000000000 --- a/axmol/base/EventTouch.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - https://axmol.dev/ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - ****************************************************************************/ - -#include "axmol/base/EventTouch.h" -#include "axmol/base/Touch.h" - -#include - -namespace ax -{ - -EventTouch::EventTouch() : Event(Type::TOUCH) -{ - _touches.reserve(MAX_TOUCHES); -} - -} // namespace ax diff --git a/axmol/base/EventTouch.h b/axmol/base/EventTouch.h deleted file mode 100644 index 88c60b6c126d..000000000000 --- a/axmol/base/EventTouch.h +++ /dev/null @@ -1,97 +0,0 @@ -/**************************************************************************** - Copyright (c) 2013-2016 Chukong Technologies Inc. - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - https://axmol.dev/ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - ****************************************************************************/ - -#pragma once - -#include "axmol/base/Event.h" -#include -#include - -/** - * @addtogroup base - * @{ - */ - -namespace ax -{ - -class Touch; - -/** @class EventTouch - * @brief Touch event. - */ -class AX_DLL EventTouch : public Event -{ -public: - static const int MAX_TOUCHES = 15; - - /** EventCode Touch event code.*/ - enum class EventCode - { - BEGAN, - MOVED, - ENDED, - CANCELLED - }; - - /** - * Constructor. - */ - EventTouch(); - - /** Get event code. - * - * @return The code of the event. - */ - EventCode getEventCode() const { return _eventCode; } - - /** Get the touches. - * - * @return The touches of the event. - */ - const std::vector& getTouches() const { return _touches; } - - /** Set the event code. - * - * @param eventCode A given EventCode. - */ - void setEventCode(EventCode eventCode) { _eventCode = eventCode; }; - /** Set the touches - * - * @param touches A given touches vector. - */ - void setTouches(std::span touches) { _touches.assign(touches.begin(), touches.end()); }; - -private: - EventCode _eventCode; - std::vector _touches; - - friend class RenderView; -}; - -} // namespace ax - -// end of base group -/// @} diff --git a/axmol/base/EventType.h b/axmol/base/EventType.h index a8ad9dc9c6c7..33e220e1b9e1 100644 --- a/axmol/base/EventType.h +++ b/axmol/base/EventType.h @@ -32,7 +32,7 @@ */ // The application will come to foreground. -// This message is posted in axmol/platform/android/jni/Java_dev_axmol_lib_AxmolRenderer.cpp. +// This message is posted in axmol/platform/android/jni/AxmolPlayerJNI.cpp. #define EVENT_COME_TO_FOREGROUND "event_come_to_foreground" // The renderer[android:GLSurfaceView.Renderer WP8:Cocos2dRenderer] was recreated. @@ -43,7 +43,7 @@ // The application will come to background. // This message is used for doing something before coming to background, such as save RenderTexture. -// This message is posted in axmol/platform/android/jni/Java_dev_axmol_lib_AxmolRenderer.cpp and +// This message is posted in axmol/platform/android/jni/AxmolPlayerJNI.cpp and // cocos\platform\wp8-xaml\cpp\Cocos2dRenderer.cpp. #define EVENT_COME_TO_BACKGROUND "event_come_to_background" diff --git a/axmol/base/EventFocus.cpp b/axmol/base/FocusEvent.cpp similarity index 93% rename from axmol/base/EventFocus.cpp rename to axmol/base/FocusEvent.cpp index 55907200acb8..1d2004569c58 100644 --- a/axmol/base/EventFocus.cpp +++ b/axmol/base/FocusEvent.cpp @@ -24,12 +24,12 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/EventFocus.h" +#include "axmol/base/FocusEvent.h" namespace ax { -EventFocus::EventFocus(ui::Widget* widgetLoseFocus, ui::Widget* widgetGetFocus) +FocusEvent::FocusEvent(ui::Widget* widgetLoseFocus, ui::Widget* widgetGetFocus) : Event(Type::FOCUS), _widgetGetFocus(widgetGetFocus), _widgetLoseFocus(widgetLoseFocus) {} diff --git a/axmol/base/EventFocus.h b/axmol/base/FocusEvent.h similarity index 89% rename from axmol/base/EventFocus.h rename to axmol/base/FocusEvent.h index 2055f332a2e5..948ea21104a3 100644 --- a/axmol/base/EventFocus.h +++ b/axmol/base/FocusEvent.h @@ -41,10 +41,10 @@ namespace ui class Widget; } -/** @class EventFocus +/** @class FocusEvent * @brief Focus event. */ -class AX_DLL EventFocus : public Event +class AX_DLL FocusEvent : public Event { public: /** Constructor. @@ -52,15 +52,18 @@ class AX_DLL EventFocus : public Event * @param widgetLoseFocus The widget which lose focus. * @param widgetGetFocus The widget which get focus. */ - EventFocus(ui::Widget* widgetLoseFocus, ui::Widget* widgetGetFocus); + FocusEvent(ui::Widget* widgetLoseFocus, ui::Widget* widgetGetFocus); private: ui::Widget* _widgetGetFocus; ui::Widget* _widgetLoseFocus; - friend class EventListenerFocus; + friend class FocusEventListener; }; +// deprecated alias +using EventFocus = FocusEvent; + } // namespace ax // end of base group diff --git a/axmol/base/EventListenerFocus.cpp b/axmol/base/FocusEventListener.cpp similarity index 75% rename from axmol/base/EventListenerFocus.cpp rename to axmol/base/FocusEventListener.cpp index e066fd126353..77a7f5360a17 100644 --- a/axmol/base/EventListenerFocus.cpp +++ b/axmol/base/FocusEventListener.cpp @@ -25,25 +25,25 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/EventListenerFocus.h" -#include "axmol/base/EventFocus.h" +#include "axmol/base/FocusEventListener.h" +#include "axmol/base/FocusEvent.h" #include "axmol/base/Macros.h" namespace ax { -const std::string_view EventListenerFocus::LISTENER_ID = "__ax_focus_event"sv; +const std::string_view FocusEventListener::LISTENER_ID = "__ax_focus_event"sv; -EventListenerFocus::EventListenerFocus() : onFocusChanged(nullptr) {} +FocusEventListener::FocusEventListener() : onFocusChanged(nullptr) {} -EventListenerFocus::~EventListenerFocus() +FocusEventListener::~FocusEventListener() { - AXLOGV("In the destructor of EventListenerFocus, {}", fmt::ptr(this)); + AXLOGV("In the destructor of FocusEventListener, {}", fmt::ptr(this)); } -EventListenerFocus* EventListenerFocus::create() +FocusEventListener* FocusEventListener::create() { - EventListenerFocus* ret = new EventListenerFocus; + FocusEventListener* ret = new FocusEventListener; if (ret->init()) { ret->autorelease(); @@ -53,9 +53,9 @@ EventListenerFocus* EventListenerFocus::create() return nullptr; } -EventListenerFocus* EventListenerFocus::clone() +FocusEventListener* FocusEventListener::clone() { - EventListenerFocus* ret = new EventListenerFocus; + FocusEventListener* ret = new FocusEventListener; if (ret->init()) { ret->autorelease(); @@ -69,10 +69,10 @@ EventListenerFocus* EventListenerFocus::clone() return ret; } -bool EventListenerFocus::init() +bool FocusEventListener::init() { auto listener = [this](Event* event) { - auto focusEvent = static_cast(event); + auto focusEvent = static_cast(event); onFocusChanged(focusEvent->_widgetLoseFocus, focusEvent->_widgetGetFocus); }; if (EventListener::init(Type::FOCUS, LISTENER_ID, listener)) @@ -82,11 +82,11 @@ bool EventListenerFocus::init() return false; } -bool EventListenerFocus::checkAvailable() +bool FocusEventListener::checkAvailable() { if (onFocusChanged == nullptr) { - AXASSERT(false, "Invalid EventListenerFocus!"); + AXASSERT(false, "Invalid FocusEventListener!"); return false; } diff --git a/axmol/base/EventListenerFocus.h b/axmol/base/FocusEventListener.h similarity index 84% rename from axmol/base/EventListenerFocus.h rename to axmol/base/FocusEventListener.h index 9fbe83084c79..59c2e7d1c8ab 100644 --- a/axmol/base/EventListenerFocus.h +++ b/axmol/base/FocusEventListener.h @@ -42,38 +42,41 @@ namespace ui class Widget; } -/** @class EventListenerFocus +/** @class FocusEventListener * @brief Focus event listener. */ -class AX_DLL EventListenerFocus : public EventListener +class AX_DLL FocusEventListener : public EventListener { public: static const std::string_view LISTENER_ID; /** Create a focus event listener. * - * @return An autoreleased EventListenerFocus object. + * @return An autoreleased FocusEventListener object. */ - static EventListenerFocus* create(); + static FocusEventListener* create(); /** Destructor. */ - virtual ~EventListenerFocus(); + virtual ~FocusEventListener(); /// Overrides - EventListenerFocus* clone() override; + FocusEventListener* clone() override; bool checkAvailable() override; // public: std::function onFocusChanged; - EventListenerFocus(); + FocusEventListener(); bool init(); friend class EventDispatcher; }; +// deprecated alias +using EventListenerFocus = FocusEventListener; + } // namespace ax // end of base group diff --git a/axmol/base/GameController.h b/axmol/base/GameController.h index 214fc97d578c..1b83fa83b771 100644 --- a/axmol/base/GameController.h +++ b/axmol/base/GameController.h @@ -28,5 +28,5 @@ /// @cond DO_NOT_SHOW #include "axmol/base/Controller.h" -#include "axmol/base/EventController.h" -#include "axmol/base/EventListenerController.h" +#include "axmol/base/ControllerEvent.h" +#include "axmol/base/ControllerEventListener.h" diff --git a/axmol/base/IMEDispatcher.cpp b/axmol/base/IMEDispatcher.cpp deleted file mode 100644 index 7c24cac92c7a..000000000000 --- a/axmol/base/IMEDispatcher.cpp +++ /dev/null @@ -1,337 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#include "axmol/base/IMEDispatcher.h" - -#include - -namespace ax -{ - -////////////////////////////////////////////////////////////////////////// -// add/remove delegate in IMEDelegate Cons/Destructor -////////////////////////////////////////////////////////////////////////// - -IMEDelegate::IMEDelegate() -{ - IMEDispatcher::sharedDispatcher()->addDelegate(this); -} - -IMEDelegate::~IMEDelegate() -{ - IMEDispatcher::sharedDispatcher()->removeDelegate(this); -} - -bool IMEDelegate::attachWithIME() -{ - return IMEDispatcher::sharedDispatcher()->attachDelegateWithIME(this); -} - -bool IMEDelegate::detachWithIME() -{ - return IMEDispatcher::sharedDispatcher()->detachDelegateWithIME(this); -} - -////////////////////////////////////////////////////////////////////////// - -typedef std::list DelegateList; -typedef std::list::iterator DelegateIter; - -////////////////////////////////////////////////////////////////////////// -// Delegate List manage class -////////////////////////////////////////////////////////////////////////// - -class IMEDispatcher::Impl -{ -public: - DelegateIter findDelegate(IMEDelegate* delegate) - { - DelegateIter end = _delegateList.end(); - for (DelegateIter iter = _delegateList.begin(); iter != end; ++iter) - { - if (delegate == *iter) - { - return iter; - } - } - return end; - } - - DelegateList _delegateList{}; - IMEDelegate* _delegateWithIme{nullptr}; -}; - -////////////////////////////////////////////////////////////////////////// -// Cons/Destructor -////////////////////////////////////////////////////////////////////////// - -IMEDispatcher::IMEDispatcher() : _impl(new IMEDispatcher::Impl) {} - -IMEDispatcher::~IMEDispatcher() -{ - AX_SAFE_DELETE(_impl); -} - -////////////////////////////////////////////////////////////////////////// -// Add/Attach/Remove IMEDelegate -////////////////////////////////////////////////////////////////////////// - -void IMEDispatcher::addDelegate(IMEDelegate* delegate) -{ - if (!delegate || !_impl) - { - return; - } - if (_impl->_delegateList.end() != _impl->findDelegate(delegate)) - { - // pDelegate already in list - return; - } - _impl->_delegateList.push_front(delegate); -} - -bool IMEDispatcher::attachDelegateWithIME(IMEDelegate* delegate) -{ - bool ret = false; - do - { - AX_BREAK_IF(!_impl || !delegate); - - DelegateIter end = _impl->_delegateList.end(); - DelegateIter iter = _impl->findDelegate(delegate); - - // if pDelegate is not in delegate list, return - AX_BREAK_IF(end == iter); - - if (_impl->_delegateWithIme) - { - if (_impl->_delegateWithIme != delegate) - { - // if old delegate canDetachWithIME return false - // or pDelegate canAttachWithIME return false, - // do nothing. - AX_BREAK_IF(!_impl->_delegateWithIme->canDetachWithIME() || !delegate->canAttachWithIME()); - - // detach first - IMEDelegate* oldDelegate = _impl->_delegateWithIme; - _impl->_delegateWithIme = 0; - oldDelegate->didDetachWithIME(); - - _impl->_delegateWithIme = *iter; - delegate->didAttachWithIME(); - } - ret = true; - break; - } - - // delegate hasn't attached to IME yet - AX_BREAK_IF(!delegate->canAttachWithIME()); - - _impl->_delegateWithIme = *iter; - delegate->didAttachWithIME(); - ret = true; - } while (0); - return ret; -} - -bool IMEDispatcher::detachDelegateWithIME(IMEDelegate* delegate) -{ - bool ret = false; - do - { - AX_BREAK_IF(!_impl || !delegate); - - // if pDelegate is not the current delegate attached to IME, return - AX_BREAK_IF(_impl->_delegateWithIme != delegate); - - AX_BREAK_IF(!delegate->canDetachWithIME()); - - _impl->_delegateWithIme = 0; - delegate->didDetachWithIME(); - ret = true; - } while (0); - return ret; -} - -void IMEDispatcher::removeDelegate(IMEDelegate* delegate) -{ - do - { - AX_BREAK_IF(!delegate || !_impl); - - DelegateIter iter = _impl->findDelegate(delegate); - DelegateIter end = _impl->_delegateList.end(); - AX_BREAK_IF(end == iter); - - if (_impl->_delegateWithIme) - - if (*iter == _impl->_delegateWithIme) - { - _impl->_delegateWithIme = 0; - } - _impl->_delegateList.erase(iter); - } while (0); -} - -////////////////////////////////////////////////////////////////////////// -// dispatch text message -////////////////////////////////////////////////////////////////////////// - -void IMEDispatcher::dispatchInsertText(const char* text, size_t len) -{ - do - { - AX_BREAK_IF(!_impl || !text || len <= 0); - - // there is no delegate attached to IME - AX_BREAK_IF(!_impl->_delegateWithIme); - - _impl->_delegateWithIme->insertText(text, len); - } while (0); -} - -void IMEDispatcher::dispatchDeleteBackward(int numChars) -{ - do - { - AX_BREAK_IF(!_impl); - - // there is no delegate attached to IME - AX_BREAK_IF(!_impl->_delegateWithIme); - - _impl->_delegateWithIme->deleteBackward(numChars); - } while (0); -} - -void IMEDispatcher::dispatchControlKey(EventKeyboard::KeyCode keyCode) -{ - do - { - AX_BREAK_IF(!_impl); - - // there is no delegate attached to IME - AX_BREAK_IF(!_impl->_delegateWithIme); - - _impl->_delegateWithIme->controlKey(keyCode); - } while (0); -} - -std::string_view IMEDispatcher::getContentText() -{ - if (_impl && _impl->_delegateWithIme) - { - return _impl->_delegateWithIme->getContentText(); - } - return STD_STRING_EMPTY; -} - -////////////////////////////////////////////////////////////////////////// -// dispatch keyboard message -////////////////////////////////////////////////////////////////////////// - -void IMEDispatcher::dispatchKeyboardWillShow(IMEKeyboardNotificationInfo& info) -{ - if (_impl) - { - IMEDelegate* delegate = nullptr; - DelegateIter last = _impl->_delegateList.end(); - for (DelegateIter first = _impl->_delegateList.begin(); first != last; ++first) - { - delegate = *(first); - if (delegate) - { - delegate->keyboardWillShow(info); - } - } - } -} - -void IMEDispatcher::dispatchKeyboardDidShow(IMEKeyboardNotificationInfo& info) -{ - if (_impl) - { - IMEDelegate* delegate = nullptr; - DelegateIter last = _impl->_delegateList.end(); - for (DelegateIter first = _impl->_delegateList.begin(); first != last; ++first) - { - delegate = *(first); - if (delegate) - { - delegate->keyboardDidShow(info); - } - } - } -} - -void IMEDispatcher::dispatchKeyboardWillHide(IMEKeyboardNotificationInfo& info) -{ - if (_impl) - { - IMEDelegate* delegate = nullptr; - DelegateIter last = _impl->_delegateList.end(); - for (DelegateIter first = _impl->_delegateList.begin(); first != last; ++first) - { - delegate = *(first); - if (delegate) - { - delegate->keyboardWillHide(info); - } - } - } -} - -void IMEDispatcher::dispatchKeyboardDidHide(IMEKeyboardNotificationInfo& info) -{ - if (_impl) - { - IMEDelegate* delegate = nullptr; - DelegateIter last = _impl->_delegateList.end(); - for (DelegateIter first = _impl->_delegateList.begin(); first != last; ++first) - { - delegate = *(first); - if (delegate) - { - delegate->keyboardDidHide(info); - } - } - } -} - -////////////////////////////////////////////////////////////////////////// -// protected member function -////////////////////////////////////////////////////////////////////////// - -////////////////////////////////////////////////////////////////////////// -// static member function -////////////////////////////////////////////////////////////////////////// - -IMEDispatcher* IMEDispatcher::sharedDispatcher() -{ - static IMEDispatcher s_instance; - return &s_instance; -} - -} // namespace ax diff --git a/axmol/base/IMEDispatcher.h b/axmol/base/IMEDispatcher.h deleted file mode 100644 index 71bb82ce9304..000000000000 --- a/axmol/base/IMEDispatcher.h +++ /dev/null @@ -1,139 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -#include "axmol/base/IMEDelegate.h" - -/** - * @addtogroup base - * @{ - */ -namespace ax -{ - -/** -@brief Input Method Edit Message Dispatcher. -*/ -class AX_DLL IMEDispatcher -{ -public: - /** - * @lua NA - */ - ~IMEDispatcher(); - - /** - * @brief Returns the shared IMEDispatcher object for the system. - * @lua NA - */ - static IMEDispatcher* sharedDispatcher(); - - /** - * @brief Dispatches the input text from IME. - * @lua NA - */ - void dispatchInsertText(const char* text, size_t len); - - /** - * @brief Dispatches the delete-backward operation. - * @lua NA - */ - void dispatchDeleteBackward(int numChars); - - /** - * @brief Dispatches the press control key operation. - * @lua NA - */ - void dispatchControlKey(EventKeyboard::KeyCode keyCode); - - /** - * @brief Get the content text from IMEDelegate, retrieved previously from IME. - * @lua NA - */ - std::string_view getContentText(); - - ////////////////////////////////////////////////////////////////////////// - // dispatch keyboard notification - ////////////////////////////////////////////////////////////////////////// - /** - * @lua NA - */ - void dispatchKeyboardWillShow(IMEKeyboardNotificationInfo& info); - /** - * @lua NA - */ - void dispatchKeyboardDidShow(IMEKeyboardNotificationInfo& info); - /** - * @lua NA - */ - void dispatchKeyboardWillHide(IMEKeyboardNotificationInfo& info); - /** - * @lua NA - */ - void dispatchKeyboardDidHide(IMEKeyboardNotificationInfo& info); - -protected: - friend class IMEDelegate; - - /** - *@brief Add delegate to receive IME messages. - *@param delegate A instance implements IMEDelegate delegate. - */ - void addDelegate(IMEDelegate* delegate); - - /** - *@brief Attach the Delegate to the IME. - *@param delegate A instance implements IMEDelegate delegate. - *@return If the old delegate can detach from the IME, and the new delegate - * can attach to the IME, return true, otherwise false. - */ - bool attachDelegateWithIME(IMEDelegate* delegate); - - /** - * Detach the delegate to the IME - *@see `attachDelegateWithIME(IMEDelegate*)` - *@param delegate A instance implements IMEDelegate delegate. - *@return Whether the IME is detached or not. - */ - bool detachDelegateWithIME(IMEDelegate* delegate); - - /** - *@brief Remove the delegate from the delegates which receive IME messages. - *@param delegate A instance implements the IMEDelegate delegate. - */ - void removeDelegate(IMEDelegate* delegate); - -private: - IMEDispatcher(); - - class Impl; - Impl* _impl; -}; - -} // namespace ax -// end of base group -/// @} diff --git a/axmol/base/IMEDelegate.h b/axmol/base/InputDelegate.h similarity index 55% rename from axmol/base/IMEDelegate.h rename to axmol/base/InputDelegate.h index 011e023a8966..8085f2ea8d07 100644 --- a/axmol/base/IMEDelegate.h +++ b/axmol/base/InputDelegate.h @@ -27,8 +27,9 @@ THE SOFTWARE. #pragma once #include +#include #include "axmol/math/Math.h" -#include "axmol/base/EventKeyboard.h" +#include "axmol/base/KeyboardEvent.h" /** * @addtogroup base @@ -42,27 +43,43 @@ namespace ax */ extern const std::string AX_DLL STD_STRING_EMPTY; +/** + * @enum EditAction + * @brief Edit actions that the platform view may query or request. + * + * @note Keep values stable across builds; new actions can be appended. + */ +enum class EditAction : int +{ + Copy = 0, /**< Copy the current selection to the clipboard. */ + Cut, /**< Copy the current selection to the clipboard and delete it. */ + Paste, /**< Insert clipboard contents at the caret. */ + SelectAll /**< Select all editable content in the current field. */ +}; + /** * Keyboard notification event type. */ -typedef struct +struct IMEKeyboardNotificationInfo { - Rect begin; // the soft keyboard rectangle when animation begins - Rect end; // the soft keyboard rectangle when animation ends - float duration; // the soft keyboard animation duration -} IMEKeyboardNotificationInfo; + // The soft keyboard geometry frame transformed into World Space (Origin at Bottom-Left) + ax::Rect keyboardFrame; + + // The soft keyboard animation duration in seconds + float duration; +}; /** - *@brief Input method editor delegate. + *@brief Input delegate. */ -class AX_DLL IMEDelegate +class AX_DLL InputDelegate { public: /** * Default constructor. * @lua NA */ - virtual ~IMEDelegate(); + virtual ~InputDelegate(); /** * Default destructor. @@ -77,17 +94,24 @@ class AX_DLL IMEDelegate virtual bool detachWithIME(); protected: - friend class IMEDispatcher; + friend class InputSystem; + + /** + * @brief IME hit-test. + * @param location Touch point in axmol world coordinates. + * @return true to keep the IME active; false otherwise. + */ + virtual bool hitTestWithIME(const Vec2& location) { return false; } /** @brief Decide if the delegate instance is ready to receive an IME message. - Called by IMEDispatcher. + Called by InputSystem. * @lua NA */ - virtual bool canAttachWithIME() { return false; } + virtual bool canAttachWithIME() const { return false; } /** - @brief When the delegate detaches from the IME, this method is called by IMEDispatcher. + @brief When the delegate detaches from the IME, this method is called by InputSystem. * @lua NA */ virtual void didAttachWithIME() {} @@ -96,37 +120,37 @@ class AX_DLL IMEDelegate @brief Decide if the delegate instance can stop receiving IME messages. * @lua NA */ - virtual bool canDetachWithIME() { return false; } + virtual bool canDetachWithIME() const { return false; } /** - @brief When the delegate detaches from the IME, this method is called by IMEDispatcher. + @brief When the delegate detaches from the IME, this method is called by InputSystem. * @lua NA */ virtual void didDetachWithIME() {} /** - @brief Called by IMEDispatcher when text input received from the IME. + @brief Called by InputSystem when text input received from the IME. * @lua NA */ - virtual void insertText(const char* /*text*/, size_t /*len*/) {} + virtual void insertText(std::string_view /*text*/) {} /** - @brief Called by IMEDispatcher after the user clicks the backward key. + @brief Called by InputSystem when the preedit text is updated. * @lua NA */ - virtual void deleteBackward(size_t numChars) {} + virtual void updatePreeditText(std::string_view /*text*/, int /*caretPos*/) {} /** - @brief Called by IMEDispatcher after the user press control key. + @brief Called by InputSystem after the user clicks the backward key. * @lua NA */ - virtual void controlKey(EventKeyboard::KeyCode /*keyCode*/) {} + virtual void deleteBackward(unsigned int numChars) {} /** - @brief Called by IMEDispatcher for text stored in delegate. + @brief Called by InputSystem after the user press control key. * @lua NA */ - virtual std::string_view getContentText() { return STD_STRING_EMPTY; } + virtual void controlKey(KeyboardEvent::KeyCode /*keyCode*/) {} ////////////////////////////////////////////////////////////////////////// // keyboard show/hide notification @@ -148,11 +172,28 @@ class AX_DLL IMEDelegate */ virtual void keyboardDidHide(IMEKeyboardNotificationInfo& /*info*/) {} + ////////////////////////////////////////////////////////////////////////// + // clipboard / edit actions + ////////////////////////////////////////////////////////////////////////// + + /** + * @brief Execute the requested edit action. + * + * Called on the main/UI thread when the user selects a menu item. The delegate + * should perform the actual behavior (clipboard I/O, model updates, deletion, + * insertion, selection changes, etc.). Implementations that need to do heavy + * work should quickly schedule asynchronous work and return to avoid blocking + * the UI. + * + * @param action The edit action to perform. + */ + virtual void performEditAction(EditAction action) {} + protected: /** * @lua NA */ - IMEDelegate(); + InputDelegate(); }; } // namespace ax diff --git a/axmol/base/InputSystem.cpp b/axmol/base/InputSystem.cpp new file mode 100644 index 000000000000..6a958c9249f7 --- /dev/null +++ b/axmol/base/InputSystem.cpp @@ -0,0 +1,682 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "axmol/base/InputSystem.h" + +#include "axmol/base/EventDispatcher.h" + +#include "axmol/base/PointerEvent.h" +#include "axmol/scene/Camera.h" + +#include +#include +#include + +// Touch bookkeeping was migrated into InputSystem private members. + +namespace ax +{ + +using EventDispatcher = EventDispatcher; // ensure symbol visibility + +InputSystem* InputSystem::_instance = nullptr; + +////////////////////////////////////////////////////////////////////////// +// add/remove delegate in InputDelegate Cons/Destructor +////////////////////////////////////////////////////////////////////////// + +InputDelegate::InputDelegate() +{ + InputSystem::getInstance()->addDelegate(this); +} + +InputDelegate::~InputDelegate() +{ + InputSystem::getInstance()->removeDelegate(this); +} + +bool InputDelegate::attachWithIME() +{ + return InputSystem::getInstance()->attachDelegateWithIME(this); +} + +bool InputDelegate::detachWithIME() +{ + return InputSystem::getInstance()->detachDelegateWithIME(this); +} + +////////////////////////////////////////////////////////////////////////// +// Cons/Destructor +////////////////////////////////////////////////////////////////////////// + +InputSystem::InputSystem() +{ + _pointerEvents.reserve(4); + _eventDispatcher = Director::getInstance()->getEventDispatcher(); +} + +InputSystem::~InputSystem() +{ + for (auto* event : _pointerEvents) + { + AX_SAFE_RELEASE(event); + } + _pointerEvents.clear(); +} + +Rect InputSystem::getNodeNativeWindowRect(Node* node) const +{ + // 1. Fetch the currently active running camera + auto camera = Camera::getVisitingCamera(); + if (!camera) + camera = Camera::getDefaultCamera(); + + // 2. Transform local bounds of the node directly into 3D World Space coordinates + auto worldLeftBottom = node->convertToWorldSpace(Vec2::ZERO); + auto worldRightTop = node->convertToWorldSpace(node->getContentSize()); + + // 3. Project world positions straight to absolute Top-Left screen pixels using our new robust Camera API + // (This automatically handles viewScale, window stretching, DPI scaling, and black bars offset) + Vec2 screenLeftBottom = camera->projectWorldToScreen(Vec3(worldLeftBottom.x, worldLeftBottom.y, 0.0f)); + Vec2 screenRightTop = camera->projectWorldToScreen(Vec3(worldRightTop.x, worldRightTop.y, 0.0f)); + + // 4. Construct the standard Top-Left Rect + // Note: Since screenSpace Y grows downwards, the "top" of the UI rect corresponds to the higher world position + // (screenRightTop.y) + float uiLeft = screenLeftBottom.x; + float uiTop = screenRightTop.y; + float uiWidth = screenRightTop.x - screenLeftBottom.x; + float uiHeight = screenLeftBottom.y - screenRightTop.y; + + if (_inputScale != 0.0f && _inputScale != 1.0f) + { + uiLeft /= _inputScale; + uiTop /= _inputScale; + uiWidth /= _inputScale; + uiHeight /= _inputScale; + } + + return Rect(uiLeft, uiTop, uiWidth, uiHeight); +} + +void InputSystem::setInputScale(float scale) +{ + _inputScale = std::clamp(scale, 1.0f, 100.0f); +} + +Vec2 InputSystem::screenToNative(const Vec2& point) const +{ + return Vec2{point.x / _inputScale, point.y / _inputScale}; +} + +Vec2 InputSystem::nativeToScreen(const Vec2& point) const +{ + return Vec2{point.x * _inputScale, point.y * _inputScale}; +} + +///////////////////////////////////////////////////////////////////////// +// touch/mouse/keyboard event + +////////////////////////////////////////////////////////////////////////// +// clipboard operations +////////////////////////////////////////////////////////////////////////// +void InputSystem::dispatchPerformEditAction(EditAction action) +{ + if (_delegateWithIme) + _delegateWithIme->performEditAction(action); +} + +bool InputSystem::hasAttachedDelegate() const +{ + return _delegateWithIme != nullptr; +} + +bool InputSystem::dispatchHitTestWithIME(const Vec2& location) +{ + if (_delegateWithIme) + return _delegateWithIme->hitTestWithIME(location); + return false; +} + +////////////////////////////////////////////////////////////////////////// +// Add/Attach/Remove InputDelegate +////////////////////////////////////////////////////////////////////////// + +InputSystem::DelegateIter InputSystem::findDelegate(InputDelegate* delegate) +{ + DelegateIter end = _delegateList.end(); + for (DelegateIter iter = _delegateList.begin(); iter != end; ++iter) + { + if (delegate == *iter) + { + return iter; + } + } + return end; +} + +void InputSystem::addDelegate(InputDelegate* delegate) +{ + if (!delegate) + { + return; + } + if (_delegateList.end() != findDelegate(delegate)) + { + // pDelegate already in list + return; + } + _delegateList.push_front(delegate); +} + +bool InputSystem::attachDelegateWithIME(InputDelegate* delegate) +{ + bool ret = false; + do + { + AX_BREAK_IF(!delegate); + + DelegateIter end = _delegateList.end(); + DelegateIter iter = findDelegate(delegate); + + // if pDelegate is not in delegate list, return + AX_BREAK_IF(end == iter); + + if (_delegateWithIme) + { + if (_delegateWithIme != delegate) + { + // if old delegate canDetachWithIME return false + // or pDelegate canAttachWithIME return false, + // do nothing. + AX_BREAK_IF(!_delegateWithIme->canDetachWithIME() || !delegate->canAttachWithIME()); + + // detach first + InputDelegate* oldDelegate = _delegateWithIme; + _delegateWithIme = 0; + oldDelegate->didDetachWithIME(); + + _delegateWithIme = *iter; + delegate->didAttachWithIME(); + } + ret = true; + break; + } + + // delegate hasn't attached to IME yet + AX_BREAK_IF(!delegate->canAttachWithIME()); + + _delegateWithIme = *iter; + delegate->didAttachWithIME(); + ret = true; + } while (0); + return ret; +} + +bool InputSystem::detachDelegateWithIME(InputDelegate* delegate) +{ + bool ret = false; + do + { + AX_BREAK_IF(!delegate || delegate != _delegateWithIme); + + AX_BREAK_IF(!delegate->canDetachWithIME()); + + _delegateWithIme = 0; + delegate->didDetachWithIME(); + ret = true; + } while (0); + return ret; +} + +void InputSystem::removeDelegate(InputDelegate* delegate) +{ + do + { + AX_BREAK_IF(!delegate); + + DelegateIter iter = findDelegate(delegate); + DelegateIter end = _delegateList.end(); + AX_BREAK_IF(end == iter); + + if (_delegateWithIme) + + if (*iter == _delegateWithIme) + { + _delegateWithIme = 0; + } + _delegateList.erase(iter); + } while (0); +} + +void InputSystem::dispatchUpdatePreedit(std::string_view preeditText, int caret) +{ + if (_delegateWithIme) + _delegateWithIme->updatePreeditText(preeditText, caret); +} + +////////////////////////////////////////////////////////////////////////// +// dispatch text message +////////////////////////////////////////////////////////////////////////// + +void InputSystem::dispatchInsertText(std::string_view text) +{ + if (_delegateWithIme && !text.empty()) + _delegateWithIme->insertText(text); +} + +void InputSystem::dispatchDeleteBackward(unsigned int numChars) +{ + if (_delegateWithIme) + _delegateWithIme->deleteBackward(numChars); +} + +void InputSystem::dispatchControlKey(KeyboardEvent::KeyCode keyCode) +{ + if (_delegateWithIme) + _delegateWithIme->controlKey(keyCode); +} + +////////////////////////////////////////////////////////////////////////// +// dispatch keyboard message +////////////////////////////////////////////////////////////////////////// + +void InputSystem::dispatchKeyboardWillShow(IMEKeyboardNotificationInfo& info) +{ + InputDelegate* delegate = nullptr; + DelegateIter last = _delegateList.end(); + for (DelegateIter first = _delegateList.begin(); first != last; ++first) + { + delegate = *(first); + if (delegate) + { + delegate->keyboardWillShow(info); + } + } +} + +void InputSystem::dispatchKeyboardDidShow(IMEKeyboardNotificationInfo& info) +{ + + InputDelegate* delegate = nullptr; + DelegateIter last = _delegateList.end(); + for (DelegateIter first = _delegateList.begin(); first != last; ++first) + { + delegate = *(first); + if (delegate) + { + delegate->keyboardDidShow(info); + } + } +} + +void InputSystem::dispatchKeyboardWillHide(IMEKeyboardNotificationInfo& info) +{ + + InputDelegate* delegate = nullptr; + DelegateIter last = _delegateList.end(); + for (DelegateIter first = _delegateList.begin(); first != last; ++first) + { + delegate = *(first); + if (delegate) + { + delegate->keyboardWillHide(info); + } + } +} + +void InputSystem::dispatchKeyboardDidHide(IMEKeyboardNotificationInfo& info) +{ + + InputDelegate* delegate = nullptr; + DelegateIter last = _delegateList.end(); + for (DelegateIter first = _delegateList.begin(); first != last; ++first) + { + delegate = *(first); + if (delegate) + { + delegate->keyboardDidHide(info); + } + } +} + +void InputSystem::dispatchEvent(Event* event, bool immediate) +{ + if (!event) + return; + + _eventDispatcher->dispatchEvent(event, immediate); +} + +// Platform-facing unified input handlers --------------------------------- + +void InputSystem::handleKeyEvent(KeyboardEvent::KeyCode keyCode, InputPhase phase) +{ + KeyboardEvent event(keyCode, phase); + dispatchEvent(&event); + bool stopped = event.isStopped(); + + if (phase != InputPhase::KeyUp && !stopped) + { + switch (keyCode) + { + case KeyboardEvent::KeyCode::KEY_BACKSPACE: + dispatchDeleteBackward(1); + break; + case KeyboardEvent::KeyCode::KEY_HOME: + case KeyboardEvent::KeyCode::KEY_KP_HOME: + case KeyboardEvent::KeyCode::KEY_DELETE: + case KeyboardEvent::KeyCode::KEY_KP_DELETE: + case KeyboardEvent::KeyCode::KEY_END: + case KeyboardEvent::KeyCode::KEY_LEFT_ARROW: + case KeyboardEvent::KeyCode::KEY_RIGHT_ARROW: + case KeyboardEvent::KeyCode::KEY_ESCAPE: + dispatchControlKey(keyCode); + break; + case KeyboardEvent::KeyCode::KEY_ENTER: + case KeyboardEvent::KeyCode::KEY_KP_ENTER: + dispatchInsertText("\n"sv); + break; + default: + break; + } + } +} + +////////////////////////////////////////////////////////////////////////// +// Touch handling migrated from RenderView +////////////////////////////////////////////////////////////////////////// + +void InputSystem::handlePointerDown(Vec2 point, const PointerInputState& state) +{ + if (!_interactive) + return; + + dispatchPointerEvent(InputPhase::PointerDown, point, state); +} + +void InputSystem::handlePointerMove(Vec2 point, const PointerInputState& state) +{ + if (!_interactive) + return; + + dispatchPointerEvent(InputPhase::PointerMove, point, state); +} + +void InputSystem::handlePointerUp(Vec2 point, const PointerInputState& state) +{ + if (!_interactive) + return; + + dispatchPointerEvent(InputPhase::PointerUp, point, state); + + auto remainingButtons = state.pressedButtons; + if (state.button >= 0) + remainingButtons &= ~(1u << state.button); + if (state.type == PointerType::Touch || remainingButtons == 0) + removePointerEvent(state.id); +} + +void InputSystem::handlePointerCancel(Vec2 point, const PointerInputState& state) +{ + if (!_interactive) + return; + + dispatchPointerEvent(InputPhase::PointerCancel, point, state); + removePointerEvent(state.id); +} + +void InputSystem::handlePointerScroll(Vec2 point, Vec2 scollDelat, const PointerInputState& state) +{ + if (!_interactive) + return; + + _lastPointerPosition = point; + + _scrollEvent.setPointerInfo(InputPhase::PointerScroll, nativeToScreen(point), state); + _scrollEvent.setScrollData(scollDelat); + dispatchEvent(&_scrollEvent); +} + +void InputSystem::setInteractive(bool interactive) +{ + if (_interactive == interactive) + return; + + _interactive = interactive; + + if (_interactive) + return; + + for (auto* event : _pointerEvents) + { + if (!event) + continue; + + PointerInputState state; + state.id = event->getPointerId(); + state.pressure = event->getPressure(); + state.button = event->getButton(); + state.pressedButtons = event->getPressedButtons(); + state.type = event->getPointerType(); + + event->setPointerInfo(InputPhase::PointerCancel, event->getScreenLocation(), state); + dispatchEvent(event); + AX_SAFE_RELEASE(event); + } + _pointerEvents.clear(); +} + +PointerEvent* InputSystem::findPointerEvent(intptr_t pointerId) const +{ + auto iter = std::find_if(_pointerEvents.begin(), _pointerEvents.end(), [pointerId](const PointerEvent* event) { + return event && event->getPointerId() == pointerId; + }); + return iter != _pointerEvents.end() ? *iter : nullptr; +} + +PointerEvent* InputSystem::fetchPointerEvent(intptr_t pointerId) +{ + if (auto event = findPointerEvent(pointerId)) + return event; + + auto* event = new PointerEvent(); + _pointerEvents.emplace_back(event); + return event; +} + +void InputSystem::removePointerEvent(intptr_t pointerId) +{ + auto iter = std::find_if(_pointerEvents.begin(), _pointerEvents.end(), [pointerId](const PointerEvent* event) { + return event && event->getPointerId() == pointerId; + }); + if (iter == _pointerEvents.end()) + return; + + AX_SAFE_RELEASE(*iter); + _pointerEvents.erase(iter); +} + +void InputSystem::dispatchPointerEvent(InputPhase phase, Vec2 point, const PointerInputState& state) +{ + _lastPointerPosition = point; + + PointerEvent* event = nullptr; + + if (phase == InputPhase::PointerDown) + { + if (state.type == PointerType::Touch) + { + auto tearEvent = findPointerEvent(state.id); + // ============================================================================ + // PASSIVE RECOVERY: Handles OS touch interceptions (e.g. 3-Finger Screenshot) + // ============================================================================ + if (tearEvent) + { + AXLOGW("[InputSystem] Touch mismatch: Tear pointer down caught for ID={}. Self-healing triggered.", + state.id); + resetInput(); + } + } + + bool isPrimary = state.type == PointerType::Mouse || _pointerEvents.empty(); + event = fetchPointerEvent(state.id); + event->setPrimary(isPrimary); + } + else + { + event = findPointerEvent(state.id); + if (!event) + { + if (phase != InputPhase::PointerMove) + { + AXLOGE("[InputSystem] Unexpected terminal phase [{}] without preceding Down stream.", + static_cast(phase)); + return; + } + if (state.type == PointerType::Touch) + { + AXLOGE("[InputSystem] Isolated touch move event discarded (missing down frame)."); + return; + } + event = &_isolatedMoveEvent; + } + } + + event->setPointerInfo(phase, nativeToScreen(point), state); + dispatchEvent(event); +} + +void InputSystem::resetInput() +{ + if (_pointerEvents.empty()) + return; + + AXLOGW("[InputSystem] Emergency flush: Purging {} stuck pointer events.", _pointerEvents.size()); + + // Note: Make sure EventDispatcher has a corresponding reset/clear method if needed, + // or we synthesize cancel events here and dispatch them. + for (auto* event : _pointerEvents) + { + if (event && event->getPhase() != InputPhase::PointerUp && event->getPhase() != InputPhase::PointerCancel) + { + // Repurpose the event as a Cancel signal to release UI/Virtual Joysticks + event->setPhase(InputPhase::PointerCancel); + _eventDispatcher->dispatchEvent(event); + } + } + + // 2. Safely release memory and clear our local tracking cache + for (auto event : _pointerEvents) + { + delete event; + } + _pointerEvents.clear(); +} + +////////////////////////////////////////////////////////////////////////// +// protected member function +////////////////////////////////////////////////////////////////////////// + +void InputSystem::onPlatformKeyboardWillShow(float rawX, float rawY, float rawWidth, float rawHeight, float duration) +{ + auto director = ax::Director::getInstance(); + + // Lambda to convert native platform pixels (Top-Left origin) into aligned World Space (Bottom-Left origin) + auto convertToAlignedWorldRect = [&](float rx, float ry, float rw, float rh) -> ax::Rect { + ax::Vec2 keyboardSize{rw, rh}; + ax::Vec2 keyboardPos{rx, ry}; + + // Convert raw physical pixels/points to screen logic pixels via the input gateway + keyboardSize = nativeToScreen(keyboardSize); + keyboardPos = nativeToScreen(keyboardPos); + + // Transform the relative screen size vector into World Space dimensions + ax::Vec2 worldSize = director->screenToWorld(keyboardSize) - director->screenToWorld(ax::Vec2::ZERO); + float worldW = std::abs(worldSize.x); + float worldH = std::abs(worldSize.y); + + // Transform the screen position to World Space and shift from Top-Left to Bottom-Left orientation + ax::Vec2 worldPos = director->screenToWorld(keyboardPos); + float worldX = worldPos.x; + float worldY = worldPos.y - worldH; + + return ax::Rect(worldX, worldY, worldW, worldH); + }; + + _cachedKeyboardNotifInfo.duration = duration; + // Map and cache the fully-expanded keyboard geometry frame + _cachedKeyboardNotifInfo.keyboardFrame = convertToAlignedWorldRect(rawX, rawY, rawWidth, rawHeight); + + this->dispatchKeyboardWillShow(_cachedKeyboardNotifInfo); +} + +void InputSystem::onPlatformKeyboardDidShow() +{ + // Asynchronously dispatch the fully shown state snapshot + this->dispatchKeyboardDidShow(_cachedKeyboardNotifInfo); +} + +void InputSystem::onPlatformKeyboardWillHide(float duration) +{ + _cachedKeyboardNotifInfo.duration = duration; + + // Extract geometry layout metrics from the current cached visible frame + float oldX = _cachedKeyboardNotifInfo.keyboardFrame.origin.x; + float oldY = _cachedKeyboardNotifInfo.keyboardFrame.origin.y; + float oldW = _cachedKeyboardNotifInfo.keyboardFrame.size.width; + float oldH = _cachedKeyboardNotifInfo.keyboardFrame.size.height; + + // Advance state machine: Shift target frame downwards by its height to represent the fully hidden state + _cachedKeyboardNotifInfo.keyboardFrame = ax::Rect(oldX, oldY - oldH, oldW, oldH); + + this->dispatchKeyboardWillHide(_cachedKeyboardNotifInfo); +} + +void InputSystem::onPlatformKeyboardDidHide() +{ + // Close the current IME lifecycle loop by dispatching the final hidden state token + this->dispatchKeyboardDidHide(_cachedKeyboardNotifInfo); +} + +////////////////////////////////////////////////////////////////////////// +// static member function +////////////////////////////////////////////////////////////////////////// + +InputSystem* InputSystem::getInstance() +{ + if (!_instance) [[unlikely]] + _instance = new InputSystem(); + return _instance; +} + +void InputSystem::destroyInstance() +{ + AX_SAFE_DELETE(_instance); +} + +} // namespace ax diff --git a/axmol/base/InputSystem.h b/axmol/base/InputSystem.h new file mode 100644 index 000000000000..078141398074 --- /dev/null +++ b/axmol/base/InputSystem.h @@ -0,0 +1,263 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "axmol/base/InputDelegate.h" +#include "axmol/base/PointerEvent.h" +#include "axmol/base/KeyboardEvent.h" + +/** + * @addtogroup base + * @{ + */ +namespace ax +{ + +class RenderViewCore; +class RenderView; +class Node; +class Event; +class EventDispatcher; + +/** +@brief Input Method Edit Message Dispatcher. +*/ +class AX_DLL InputSystem +{ + using DelegateList = std::list; + using DelegateIter = std::list::iterator; + friend class InputDelegate; + friend class RenderViewCore; + friend class RenderView; + +public: + /** + * @lua NA + */ + ~InputSystem(); + + /** + * @brief Returns the shared InputSystem object for the system. + * @lua NA + */ + static InputSystem* getInstance(); + static void destroyInstance(); + /** + * @brief Unified bridging utility to extract a node's UI bounding rect in native OS window coordinates. + */ + Rect getNodeNativeWindowRect(Node* node) const; + + Vec2 screenToNative(const Vec2& point) const; + Vec2 nativeToScreen(const Vec2& point) const; + + ///////////////////////////////////////////////////////////////////////////// + // pointer(mouse/touch) /keyboard event + ///////////////////////////////////////////////////////////////////////////// + + // handle key event + void handleKeyEvent(KeyboardEvent::KeyCode keyCode, InputPhase phase); + + // pointer input handling helpers migrated from RenderView + void handlePointerDown(Vec2 point, const PointerInputState& state); + void handlePointerMove(Vec2 point, const PointerInputState& state); + void handlePointerUp(Vec2 point, const PointerInputState& state); + void handlePointerCancel(Vec2 point, const PointerInputState& state); + + void handlePointerScroll(Vec2 point, Vec2 scrollDelat, const PointerInputState& state); + + /** + * Enable or disable pointer interactions. + * When disabled, active pointers are canceled and new pointer events are ignored. + */ + void setInteractive(bool interactive); + bool isInteractive() const { return _interactive; } + + /** + * @brief Dispatch a request to perform the given edit action. + * + * Called from the platform view on the main/UI thread. If no delegate is attached, + * this call is a no-op. + */ + void dispatchPerformEditAction(EditAction action); + + /** + * @brief Returns the delegate attached to the IME, or nullptr if no delegate is attached. + */ + bool hasAttachedDelegate() const; + + /** + * @brief Returns the delegate attached to the IME, or nullptr if no delegate is attached. + * @lua NA + */ + bool dispatchHitTestWithIME(const Vec2& location); + + /* + * @brief Dispatches the update-preedit-text message from IME. + * @lua NA + */ + void dispatchUpdatePreedit(std::string_view preeditText, int caret); + + /** + * @brief Dispatches the input text from IME. + * @lua NA + */ + void dispatchInsertText(std::string_view text); + + /** + * @brief Dispatches the delete-backward operation. + * @lua NA + */ + void dispatchDeleteBackward(unsigned int numChars); + + /** + * @brief Dispatches the press control key operation. + * @lua NA + */ + void dispatchControlKey(KeyboardEvent::KeyCode keyCode); + + ////////////////////////////////////////////////////////////////////////// + // dispatch keyboard notification + ////////////////////////////////////////////////////////////////////////// + /** + * @lua NA + */ + void dispatchKeyboardWillShow(IMEKeyboardNotificationInfo& info); + /** + * @lua NA + */ + void dispatchKeyboardDidShow(IMEKeyboardNotificationInfo& info); + /** + * @lua NA + */ + void dispatchKeyboardWillHide(IMEKeyboardNotificationInfo& info); + /** + * @lua NA + */ + void dispatchKeyboardDidHide(IMEKeyboardNotificationInfo& info); + + /** + * @brief Centralized event dispatch. InputSystem will apply input scaling + * to mouse/touch events before forwarding to EventDispatcher. + */ + void dispatchEvent(Event* event, bool immediate = false); + + void onPlatformKeyboardWillShow(float rawX, float rawY, float rawWidth, float rawHeight, float duration); + void onPlatformKeyboardDidShow(); + void onPlatformKeyboardWillHide(float duration); + void onPlatformKeyboardDidHide(); + + /** + * @brief Returns the last recorded pointer position. + * + * This value is expressed in the platform's raw screen/canvas coordinate system + * (native pixels as reported by the platform input layer). It represents the + * most recent pointer location prior to the current update and is shared for + * mouse, touch and pen input. + * + * @return Vec2 The last pointer position in screen (canvas) pixels. + * + * @note This position is in the platform/native screen coordinate space and + * has not been transformed by any world or UI layout conversions. + * If your code expects scaled or world coordinates, convert using the + * appropriate helper (for example, nativeToScreen / screenToWorld). + */ + Vec2 getLastPointerPosition() const { return _lastPointerPosition; } + + void resetInput(); + +protected: + InputSystem(); + + void setInputScale(float scale); + + DelegateIter findDelegate(InputDelegate* delegate); + + /** + *@brief Add delegate to receive IME messages. + *@param delegate A instance implements InputDelegate delegate. + */ + void addDelegate(InputDelegate* delegate); + + /** + *@brief Attach the Delegate to the IME. + *@param delegate A instance implements InputDelegate delegate. + *@return If the old delegate can detach from the IME, and the new delegate + * can attach to the IME, return true, otherwise false. + */ + bool attachDelegateWithIME(InputDelegate* delegate); + + /** + * Detach the delegate to the IME + *@see `attachDelegateWithIME(InputDelegate*)` + *@param delegate A instance implements InputDelegate delegate. + *@return Whether the IME is detached or not. + */ + bool detachDelegateWithIME(InputDelegate* delegate); + + /** + *@brief Remove the delegate from the delegates which receive IME messages. + *@param delegate A instance implements the InputDelegate delegate. + */ + void removeDelegate(InputDelegate* delegate); + + PointerEvent* findPointerEvent(intptr_t pointerId) const; + PointerEvent* fetchPointerEvent(intptr_t pointerId); + void removePointerEvent(intptr_t pointerId); + void dispatchPointerEvent(InputPhase phase, Vec2 point, const PointerInputState& state); + + // cached mouse position with inputScale applied + Vec2 _lastPointerPosition; // the original pointer position + std::vector _pointerEvents; + + PointerEvent _isolatedMoveEvent{}; + PointerEvent _scrollEvent{}; + + bool _interactive{true}; + + ax::IMEKeyboardNotificationInfo _cachedKeyboardNotifInfo; + + EventDispatcher* _eventDispatcher{nullptr}; + + // Input scale factor: + // - Always 1.0 on platforms with a 1:1 mapping between screen coordinates + // and physical pixels. + // - On other platforms, matches _renderScale to account for DPI scaling. + float _inputScale{1.0f}; + + DelegateList _delegateList{}; + InputDelegate* _delegateWithIme{nullptr}; + + static InputSystem* _instance; +}; + +} // namespace ax +// end of base group +/// @} diff --git a/axmol/base/EventKeyboard.cpp b/axmol/base/KeyboardEvent.cpp similarity index 87% rename from axmol/base/EventKeyboard.cpp rename to axmol/base/KeyboardEvent.cpp index 9f64b0d9dc8b..bf5450a038c8 100644 --- a/axmol/base/EventKeyboard.cpp +++ b/axmol/base/KeyboardEvent.cpp @@ -25,13 +25,13 @@ ****************************************************************************/ -#include "axmol/base/EventKeyboard.h" +#include "axmol/base/KeyboardEvent.h" namespace ax { -EventKeyboard::EventKeyboard(KeyCode keyCode, bool isKeyDown, bool isRepeat) - : Event(Type::KEYBOARD), _keyCode(keyCode), _isKeyDown(isKeyDown), _isRepeat(isRepeat) +KeyboardEvent::KeyboardEvent(KeyCode keyCode, InputPhase phase) + : Event(Type::KEYBOARD), _keyCode(keyCode), _phase(phase) {} } // namespace ax diff --git a/axmol/base/EventKeyboard.h b/axmol/base/KeyboardEvent.h similarity index 82% rename from axmol/base/EventKeyboard.h rename to axmol/base/KeyboardEvent.h index 6cb899efa906..41517e99e978 100644 --- a/axmol/base/EventKeyboard.h +++ b/axmol/base/KeyboardEvent.h @@ -37,10 +37,10 @@ namespace ax { -/** @class EventKeyboard +/** @class KeyboardEvent * @brief Keyboard event. */ -class AX_DLL EventKeyboard : public Event +class AX_DLL KeyboardEvent : public Event { public: /** @@ -226,18 +226,42 @@ class AX_DLL EventKeyboard : public Event * @param isKeyDown whether is key down event * @param isRepeat whether key down repeat */ - EventKeyboard(KeyCode keyCode, bool isKeyDown, bool isRepeat = false); + KeyboardEvent(KeyCode keyCode, InputPhase phase); - bool isRepeat() const { return _isRepeat; } + /** + * @brief Get the key code. + * + * @return The key code associated with this event. + */ + KeyCode getKeyCode() const { return _keyCode; } + + /** + * @brief Retrieve the phase of this keyboard event. + * + * Returns the input phase that describes the current state + * of the keyboard interaction, such as when a key is pressed, + * released, or held in repeat mode. + * + * @return InputPhase enumeration value indicating the event phase: + * - InputPhase::KeyDown when the key is pressed + * - InputPhase::KeyUp when the key is released + * - InputPhase::KeyRepeat when the key is repeating + * + * @note This reflects only the state of this KeyboardEvent + * instance and does not query the global keyboard state. + */ + InputPhase getPhase() const { return _phase; } private: KeyCode _keyCode; - bool _isKeyDown; - bool _isRepeat; + InputPhase _phase{InputPhase::KeyDown}; - friend class EventListenerKeyboard; + friend class KeyboardEventListener; }; +// deprecated +using EventKeyboard = KeyboardEvent; + } // namespace ax // end of base group diff --git a/axmol/base/EventListenerKeyboard.cpp b/axmol/base/KeyboardEventListener.cpp similarity index 65% rename from axmol/base/EventListenerKeyboard.cpp rename to axmol/base/KeyboardEventListener.cpp index 80ee766e18b7..6b4be49ab63a 100644 --- a/axmol/base/EventListenerKeyboard.cpp +++ b/axmol/base/KeyboardEventListener.cpp @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -24,28 +25,28 @@ ****************************************************************************/ -#include "axmol/base/EventListenerKeyboard.h" +#include "axmol/base/KeyboardEventListener.h" #include "axmol/base/Macros.h" namespace ax { -const std::string_view EventListenerKeyboard::LISTENER_ID = "__ax_keyboard"sv; +const std::string_view KeyboardEventListener::LISTENER_ID = "__ax_keyboard"sv; -bool EventListenerKeyboard::checkAvailable() +bool KeyboardEventListener::checkAvailable() { if (onKeyPressed == nullptr && onKeyReleased == nullptr) { - AXASSERT(false, "Invalid EventListenerKeyboard!"); + AXASSERT(false, "Invalid KeyboardEventListener!"); return false; } return true; } -EventListenerKeyboard* EventListenerKeyboard::create() +KeyboardEventListener* KeyboardEventListener::create() { - auto ret = new EventListenerKeyboard(); + auto ret = new KeyboardEventListener(); if (ret->init()) { ret->autorelease(); @@ -57,9 +58,9 @@ EventListenerKeyboard* EventListenerKeyboard::create() return ret; } -EventListenerKeyboard* EventListenerKeyboard::clone() +KeyboardEventListener* KeyboardEventListener::clone() { - auto ret = new EventListenerKeyboard(); + auto ret = new KeyboardEventListener(); if (ret->init()) { ret->autorelease(); @@ -73,21 +74,27 @@ EventListenerKeyboard* EventListenerKeyboard::clone() return ret; } -EventListenerKeyboard::EventListenerKeyboard() : onKeyPressed(nullptr), onKeyReleased(nullptr) {} +KeyboardEventListener::KeyboardEventListener() : onKeyPressed(nullptr), onKeyReleased(nullptr) {} -bool EventListenerKeyboard::init() +bool KeyboardEventListener::init() { auto listener = [this](Event* event) { - auto keyboardEvent = static_cast(event); - if (keyboardEvent->_isKeyDown) + auto keyboardEvent = static_cast(event); + switch (keyboardEvent->getPhase()) { - if (onKeyPressed != nullptr) - onKeyPressed(keyboardEvent->_keyCode, event); - } - else - { - if (onKeyReleased != nullptr) - onKeyReleased(keyboardEvent->_keyCode, event); + case InputPhase::KeyDown: + if (onKeyPressed) + onKeyPressed(keyboardEvent); + break; + case InputPhase::KeyUp: + if (onKeyReleased) + onKeyReleased(keyboardEvent); + break; + case InputPhase::KeyRepeat: + if (onKeyRepeat) + onKeyRepeat(keyboardEvent); + break; + default:; } }; diff --git a/axmol/base/EventListenerKeyboard.h b/axmol/base/KeyboardEventListener.h similarity index 73% rename from axmol/base/EventListenerKeyboard.h rename to axmol/base/KeyboardEventListener.h index 15198b2a4382..50386e614643 100644 --- a/axmol/base/EventListenerKeyboard.h +++ b/axmol/base/KeyboardEventListener.h @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -27,7 +28,7 @@ #pragma once #include "axmol/base/EventListener.h" -#include "axmol/base/EventKeyboard.h" +#include "axmol/base/KeyboardEvent.h" /** * @addtogroup base @@ -37,32 +38,34 @@ namespace ax { -class Event; - -/** @class EventListenerKeyboard +/** @class KeyboardEventListener * @brief Keyboard event listener. */ -class AX_DLL EventListenerKeyboard : public EventListener +class AX_DLL KeyboardEventListener : public EventListener { public: static const std::string_view LISTENER_ID; /** Create a keyboard event listener. * - * @return An autoreleased EventListenerKeyboard object. + * @return An autoreleased KeyboardEventListener object. */ - static EventListenerKeyboard* create(); + static KeyboardEventListener* create(); /// Overrides - EventListenerKeyboard* clone() override; + KeyboardEventListener* clone() override; bool checkAvailable() override; - std::function onKeyPressed; - std::function onKeyReleased; - EventListenerKeyboard(); + std::function onKeyPressed; + std::function onKeyReleased; + std::function onKeyRepeat; + KeyboardEventListener(); bool init(); }; +// deprecated +using EventListenerKeyboard = KeyboardEventListener; + } // namespace ax // end of base group diff --git a/axmol/base/Touch.cpp b/axmol/base/PointerEvent.cpp similarity index 55% rename from axmol/base/Touch.cpp rename to axmol/base/PointerEvent.cpp index 09faad3cd21c..67ac98af122c 100644 --- a/axmol/base/Touch.cpp +++ b/axmol/base/PointerEvent.cpp @@ -1,8 +1,6 @@ /**************************************************************************** - Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -25,64 +23,83 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/Touch.h" -#include "axmol/base/Director.h" +#include "axmol/base/PointerEvent.h" + +#include namespace ax { -// returns the current touch location in screen coordinates -Vec2 Touch::getLocationInView() const +PointerEvent::PointerEvent() : Event(Type::POINTER) {} + +void PointerEvent::setPointerInfo(InputPhase phase, Vec2 point, const PointerInputState& state) +{ + _isStopped = false; + _currentTarget = nullptr; + setCaptureBits(CAPTURE_NONE); + _prevPoint = _point; + + _phase = phase; + + _pointerType = state.type; + _button = state.button; + _pointerId = state.id; + _pressedButtons = state.pressedButtons; + + _point = point; + _pressure = state.pressure; + if (!_startPointCaptured) + { + _startPoint = _point; + _startPointCaptured = true; + _prevPoint = _point; + } +} + +Vec2 PointerEvent::getScreenLocation() const { return _point; } -// returns the previous touch location in screen coordinates -Vec2 Touch::getPreviousLocationInView() const +Vec2 PointerEvent::getPreviousScreenLocation() const { return _prevPoint; } -// returns the start touch location in screen coordinates -Vec2 Touch::getStartLocationInView() const +Vec2 PointerEvent::getStartScreenLocation() const { return _startPoint; } -// returns the current touch location in OpenGL coordinates -Vec2 Touch::getLocation() const +bool PointerEvent::isButtonPressed(int buttonIndex) const { - return Director::getInstance()->screenToWorld(_point); + return buttonIndex >= 0 ? ((1u << buttonIndex) & _pressedButtons) != 0 : false; } -// returns the previous touch location in OpenGL coordinates -Vec2 Touch::getPreviousLocation() const +bool PointerEvent::isPrimaryPressed() const { - return Director::getInstance()->screenToWorld(_prevPoint); + return _phase != InputPhase::PointerUp && + ((_pointerType == PointerType::Touch && _primary) || isButtonPressed(InputButton::Primary)); } -// returns the start touch location in OpenGL coordinates -Vec2 Touch::getStartLocation() const +Vec2 PointerEvent::getLocation() const { - return Director::getInstance()->screenToWorld(_startPoint); + return Director::getInstance()->screenToWorld(_point); } -// returns the delta position between the current location and the previous location in OpenGL coordinates -Vec2 Touch::getDelta() const +Vec2 PointerEvent::getPreviousLocation() const { - return getLocation() - getPreviousLocation(); + return Director::getInstance()->screenToWorld(_prevPoint); } -// Returns the current touch force for 3d touch. -float Touch::getCurrentForce() const +Vec2 PointerEvent::getStartLocation() const { - return _curForce; + return Director::getInstance()->screenToWorld(_startPoint); } -// Returns the maximum touch force for 3d touch. -float Touch::getMaxForce() const +Vec2 PointerEvent::getDelta() const { - return _maxForce; + return getLocation() - getPreviousLocation(); } } // namespace ax diff --git a/axmol/base/PointerEvent.h b/axmol/base/PointerEvent.h new file mode 100644 index 000000000000..a125bba02e07 --- /dev/null +++ b/axmol/base/PointerEvent.h @@ -0,0 +1,291 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + + https://axmol.dev/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#pragma once + +#include "axmol/base/Event.h" +#include "axmol/base/Object.h" +#include "axmol/math/Math.h" + +/** + * @addtogroup base + * @{ + */ + +namespace ax +{ + +class InputSystem; +class Camera; + +/** @class PointerEvent + * @brief Pointer event. + */ +class AX_DLL PointerEvent : public Event +{ + friend class InputSystem; + +public: + using CaptureBits = uint32_t; + + enum CaptureBit : CaptureBits + { + CAPTURE_NONE = 0, + CAPTURED = 1u << 31, + PRIMARY_CAPTURED = 1u << InputButton::Primary, + }; + + /** + * Constructor. + */ + PointerEvent(); + + /** Returns the current touch location in Axmol world 2d-coordinates. + * + * @return The current touch location in Axmol coordinates. + */ + Vec2 getLocation() const; + + /** Returns the previous touch location in Axmol world 2d-coordinates. + * + * @return The previous touch location in Axmol world 2d-coordinates. + */ + Vec2 getPreviousLocation() const; + + /** Returns the start touch location in Axmol world 2d-coordinates. + * + * @return The start touch location in Axmol world 2d-coordinates. + */ + Vec2 getStartLocation() const; + + /** Returns the delta of 2 current touches locations in Axmol world 2d-coordinates + * + * @return The delta position between the current location and the previous location in Axmol world + * 2d-coordinates + */ + Vec2 getDelta() const; + + /** Returns the current touch location in screen coordinates. + * + * @return The current touch location in screen coordinates. + */ + Vec2 getScreenLocation() const; + + /** Returns the previous touch location in screen coordinates. + * + * @return The previous touch location in screen coordinates. + */ + Vec2 getPreviousScreenLocation() const; + + /** Returns the start touch location in screen coordinates. + * + * @return The start touch location in screen coordinates. + */ + Vec2 getStartScreenLocation() const; + + /** Get mouse scroll data of axis. + * + * @return The scroll data of axis. + */ + const Vec2& getScrollDelta() const { return _scrollDelta; } + float getScrollX() const { return _scrollDelta.x; } + float getScrollY() const { return _scrollDelta.y; } + + /** Get pointer id. + * @lua getId + * + * @return The id of pointer. + */ + intptr_t getPointerId() const { return _pointerId; } + + /** + * @brief Get the pointer device type for this event. + * + * Returns the type of pointer that produced the event (for example, + * Mouse, Touch, or Pen). + * + * @return PointerType The pointer device type. + */ + PointerType getPointerType() const { return _pointerType; } + + /** + * @brief Get the index of the button that triggered this event. + * + * The value is a button index (0..n-1) when a physical button triggered + * the event. Use -1 to indicate "no button" (for example, touch events + * or pure move events). + * + * @return int The triggering button index, or -1 if none. + * + * @note This field represents the single button that caused this event. + * To inspect the current set of pressed buttons, use getPressedButtons(). + */ + int getButton() const { return _button; } + + /** + * @brief Get the bitmask of currently pressed buttons. + * + * Each bit corresponds to a button index: bit i represents whether + * button index i is currently pressed (1 = pressed, 0 = released). + * For example, bit 0 = left button, bit 1 = right button, bit 2 = middle button. + * + * @return uint32_t Bitmask of pressed buttons; 0 means no buttons pressed. + * + * @note The mapping between button indices and bits is: bit i <-> button index i. + * For touch events, this value is typically 0. + */ + uint32_t getPressedButtons() const { return _pressedButtons; } + + /** + * @brief Check whether specified button index pressed + */ + bool isButtonPressed(int buttonIndex) const; + + /** + * @brief Checks if the primary input is currently pressed. + * + * This method returns true when the primary input source is actively + * pressed and not in the PointerUp phase. + * + * - **Mouse**: True if the primary button (typically the left button) is pressed. + * - **Touch**: True if the first touch point is active (touch down). + * - **Pen**: True if the pen tip (primary button) is pressed. + * + * Use this method to detect the main interaction across devices + * (mouse left drag, first finger touch drag, or pen tip press). + * + * @return True if the primary input is pressed, false otherwise. + */ + bool isPrimaryPressed() const; + + /** + * @brief Checks whether this event was routed through pointer capture. + */ + bool isCaptured() const { return (_captureBits & CaptureBit::CAPTURED) != 0; } + + /** + * @brief Checks whether this event was routed through primary pointer capture. + * + * For touch this means the primary touch pointer was captured. For mouse + * and pen this means the primary button capture routed the event. + */ + bool isPrimaryCaptured() const { return (_captureBits & CaptureBit::PRIMARY_CAPTURED) != 0; } + + /** + * @brief Returns the normalized pressure applied to the touch surface. + * + * This value represents the force intensity of the touch, universally supported across + * multi-touch screens, digital styluses (e.g., Apple Pencil), and pressure-sensitive devices. + * + * @return A normalized float value typically ranging from 0.0f to 1.0f: + * - **0.0f**: No pressure applied or hovering. + * - **1.0f**: Standard full press intensity. + * - Values **> 1.0f** may be returned on platforms supporting deep press (e.g., iOS 3D Touch). + * + * @note For legacy devices or platforms lacking pressure-sensing hardware, this safely + * falls back to a constant **1.0f** to guarantee standard touch interactions. + */ + float getPressure() const { return _pressure; } + + /** + * @brief Get the phase of this pointer event. + * + * Returns the input phase that describes the current state + * of the pointer interaction, such as when a mouse button, + * touch, or pen contact is pressed, released, moved, or canceled. + * + * @return InputPhase enumeration value indicating the event phase: + * - InputPhase::PointerDown when the pointer makes contact + * - InputPhase::PointerUp when the pointer contact ends + * - InputPhase::PointerMove when the pointer moves + * - InputPhase::PointerCancel when the pointer interaction is canceled + * - InputPhase::PointerScroll when the pointer performs a scroll + * - InputPhase::PointerEnter when the pointer enters a region + * - InputPhase::PointerLeave when the pointer leaves a region + * + * @note This reflects only the state of this PointerEvent + * instance and does not query the global pointer state. + */ + InputPhase getPhase() const { return _phase; } + + /** + * @brief Indicates whether this pointer is the primary pointer. + * + * Returns true if the pointer is considered primary. The primary pointer + * is the one that controls the mouse cursor. For touch input, the first + * finger that touches the screen is marked as primary. For mouse input, + * the pointer is always primary. + * + * @return True if this is the primary pointer, false otherwise. + */ + bool isPrimary() const { return _primary; } + + /** Set the touch information. It always used to monitor touch event. + * + * @param id A given id + * @param point A given point in screen coordinate. + * @param pressure maximum possible force for 3d touch. + */ + [[internal]] void setPointerInfo(InputPhase phase, Vec2 point, const PointerInputState& inputState); + [[internal]] void setCaptureBits(CaptureBits bits) { _captureBits = bits; } + + /** Set mouse scroll data. + * + * @param scrollX The scroll data of x axis. + * @param scrollY The scroll data of y axis. + */ + [[internal]] void setScrollData(Vec2 delta) { _scrollDelta = delta; } + [[internal]] void setScrollData(float scrollX, float scrollY) { _scrollDelta = Vec2{scrollX, scrollY}; } + + [[internal]] void setCamera(const Camera* camera) { _camera = camera; } + + const Camera* getCamera() const { return _camera; } + +protected: + void setPhase(InputPhase phase) { _phase = phase; } + + void setPrimary(bool bval) { _primary = bval; } + const Camera* _camera{nullptr}; + intptr_t _pointerId{-1}; + InputPhase _phase{InputPhase::PointerDown}; + PointerType _pointerType{PointerType::Mouse}; + int _button{InputButton::Left}; + uint32_t _pressedButtons{0}; + Vec2 _startPoint; + Vec2 _point; + Vec2 _prevPoint; + Vec2 _scrollDelta; + // Vec2 _tilt; + float _pressure{1.0f}; + CaptureBits _captureBits{0}; + bool _startPointCaptured{false}; + bool _primary{true}; +}; + +} // namespace ax + +// end of base group +/// @} diff --git a/axmol/base/EventListenerMouse.cpp b/axmol/base/PointerEventListener.cpp similarity index 57% rename from axmol/base/EventListenerMouse.cpp rename to axmol/base/PointerEventListener.cpp index f7c5b257e57b..465229a54dca 100644 --- a/axmol/base/EventListenerMouse.cpp +++ b/axmol/base/PointerEventListener.cpp @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -21,34 +22,39 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ****************************************************************************/ -#include "axmol/base/EventListenerMouse.h" +#include "axmol/base/PointerEventListener.h" +#include "axmol/base/EventDispatcher.h" +#include "axmol/base/PointerEvent.h" + +#include namespace ax { -const std::string_view EventListenerMouse::LISTENER_ID = "__ax_mouse"sv; +const std::string_view PointerEventListener::LISTENER_ID = "__ax_pointer_listener"sv; -bool EventListenerMouse::checkAvailable() -{ - return true; -} +PointerEventListener::PointerEventListener() = default; -void EventListenerMouse::setSwallowMouse(bool needSwallow) +PointerEventListener::~PointerEventListener() { - _needSwallow = needSwallow; + AXLOGV("In the destructor of PointerEventListener, {}", fmt::ptr(this)); } -bool EventListenerMouse::isSwallowMouse() +bool PointerEventListener::init() { - return _needSwallow; + if (EventListener::init(Type::POINTER, LISTENER_ID, nullptr)) + { + return true; + } + + return false; } -EventListenerMouse* EventListenerMouse::create() +PointerEventListener* PointerEventListener::create() { - auto ret = new EventListenerMouse(); + auto ret = new PointerEventListener(); if (ret->init()) { ret->autorelease(); @@ -60,17 +66,30 @@ EventListenerMouse* EventListenerMouse::create() return ret; } -EventListenerMouse* EventListenerMouse::clone() +bool PointerEventListener::checkAvailable() { - auto ret = new EventListenerMouse(); + if (!onPointerDown && !onPointerMove && !onPointerUp && !onPointerCancel && !onPointerScroll) + { + AXASSERT(false, "Invalid PointerEventListener!"); + return false; + } + + return true; +} + +PointerEventListener* PointerEventListener::clone() +{ + auto ret = new PointerEventListener(); if (ret->init()) { ret->autorelease(); - ret->onMouseUp = onMouseUp; - ret->onMouseDown = onMouseDown; - ret->onMouseMove = onMouseMove; - ret->onMouseScroll = onMouseScroll; - ret->_needSwallow = _needSwallow; + + ret->onPointerHitTest = onPointerHitTest; + ret->onPointerDown = onPointerDown; + ret->onPointerMove = onPointerMove; + ret->onPointerUp = onPointerUp; + ret->onPointerCancel = onPointerCancel; + ret->onPointerScroll = onPointerScroll; } else { @@ -79,18 +98,4 @@ EventListenerMouse* EventListenerMouse::clone() return ret; } -EventListenerMouse::EventListenerMouse() - : onMouseDown(nullptr), onMouseUp(nullptr), onMouseMove(nullptr), onMouseScroll(nullptr), _needSwallow(false) -{} - -bool EventListenerMouse::init() -{ - if (EventListener::init(Type::MOUSE, LISTENER_ID, nullptr)) - { - return true; - } - - return false; -} - } // namespace ax diff --git a/axmol/base/EventListenerMouse.h b/axmol/base/PointerEventListener.h similarity index 65% rename from axmol/base/EventListenerMouse.h rename to axmol/base/PointerEventListener.h index a0ee33548bf5..aef46694b0f9 100644 --- a/axmol/base/EventListenerMouse.h +++ b/axmol/base/PointerEventListener.h @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2013-2016 Chukong Technologies Inc. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -27,7 +28,7 @@ #pragma once #include "axmol/base/EventListener.h" -#include "axmol/base/EventMouse.h" +#include /** * @addtogroup base @@ -37,48 +38,43 @@ namespace ax { -class Event; +class PointerEvent; -/** @class EventListenerMouse - * @brief Mouse event listener. +/** @class PointerEventListener + * @brief Single touch event listener. */ -class AX_DLL EventListenerMouse : public EventListener +class AX_DLL PointerEventListener : public EventListener { public: static const std::string_view LISTENER_ID; - /** Create a mouse event listener. - * - * @return An autoreleased EventListenerMouse object. + /** Create a one by one touch event listener. */ - static EventListenerMouse* create(); + static PointerEventListener* create(); - /** Whether or not to swall scrolls. - * - * @param needSwallow True if needs to swall scroll. + /** + * Destructor. */ - void setSwallowMouse(bool needSwallow); - /** Is swall scroll or not. - * - * @return True if needs to swall scroll. - */ - bool isSwallowMouse(); + virtual ~PointerEventListener(); /// Overrides - EventListenerMouse* clone() override; + PointerEventListener* clone() override; bool checkAvailable() override; + // - std::function onMouseDown; - std::function onMouseUp; - std::function onMouseMove; - std::function onMouseScroll; - - EventListenerMouse(); +public: + // Hit-test callback invoked only for scene-graph listeners + std::function onPointerHitTest; + std::function onPointerDown; + std::function onPointerMove; + std::function onPointerUp; + std::function onPointerCancel; + std::function onPointerScroll; + + PointerEventListener(); bool init(); private: - bool _needSwallow; - friend class EventDispatcher; }; diff --git a/axmol/base/Scheduler.cpp b/axmol/base/Scheduler.cpp index 4a3c9f0fe4a7..fa30c0416eab 100644 --- a/axmol/base/Scheduler.cpp +++ b/axmol/base/Scheduler.cpp @@ -224,10 +224,7 @@ Scheduler::Scheduler() #if AX_ENABLE_SCRIPT_BINDING , _scriptHandlerEntries(20) #endif -{ - // I don't expect to have more than 30 functions to all per frame - _actionsToPerform.reserve(30); -} +{} Scheduler::~Scheduler() { @@ -723,18 +720,6 @@ void Scheduler::resumeTargets(const std::set& targetsToResume) } } -void Scheduler::runOnAxmolThread(std::function action) -{ - std::lock_guard lock(_performMutex); - _actionsToPerform.emplace_back(std::move(action)); -} - -void Scheduler::removeAllPendingActions() -{ - std::unique_lock lock(_performMutex); - _actionsToPerform.clear(); -} - // main loop void Scheduler::update(float dt) { @@ -853,25 +838,6 @@ void Scheduler::update(float dt) } } #endif - // - // Functions allocated from another thread - // - - // Testing size is faster than locking / unlocking. - // And almost never there will be functions scheduled to be called. - if (!_actionsToPerform.empty()) - { - _performMutex.lock(); - // fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if - // new functions are added in callback, it will cause thread deadlock. - auto temp = std::move(_actionsToPerform); - _performMutex.unlock(); - - for (const auto& function : temp) - { - function(); - } - } } void Scheduler::schedule(SEL_SCHEDULE selector, diff --git a/axmol/base/Scheduler.h b/axmol/base/Scheduler.h index 6162994e2e98..d97a6b88bfef 100644 --- a/axmol/base/Scheduler.h +++ b/axmol/base/Scheduler.h @@ -458,21 +458,6 @@ class AX_DLL Scheduler : public Object */ void resumeTargets(const std::set& targetsToResume); - /** Calls a function on the axmol thread. Useful when you need to call a axmol function from another thread. - This function is thread safe. - @param function The function to be run in axmol thread. - @since axmol - */ - void runOnAxmolThread(std::function action); - - /** - * Remove all pending functions queued to be performed with Scheduler::runOnAxmolThread - * Functions unscheduled in this manner will not be executed - * This function is thread safe - * @since v3.14 - */ - void removeAllPendingActions(); - protected: /** Schedules the 'callback' function for a given target with a given priority. The 'callback' selector will be called every frame. @@ -523,10 +508,6 @@ class AX_DLL Scheduler : public Object #if AX_ENABLE_SCRIPT_BINDING Vector _scriptHandlerEntries; #endif - - // Used for "perform action" - std::vector> _actionsToPerform; - std::mutex _performMutex; }; // end of base group diff --git a/axmol/base/ScriptSupport.h b/axmol/base/ScriptSupport.h index f604625c2fc3..934b4836eaf8 100644 --- a/axmol/base/ScriptSupport.h +++ b/axmol/base/ScriptSupport.h @@ -28,9 +28,9 @@ #include "axmol/base/Config.h" #include "axmol/platform/Common.h" -#include "axmol/base/Touch.h" -#include "axmol/base/EventTouch.h" -#include "axmol/base/EventKeyboard.h" +#include "axmol/base/PointerEvent.h" +#include "axmol/base/PointerEvent.h" +#include "axmol/base/KeyboardEvent.h" #include "axmol/tlx/utility.hpp" #include #include @@ -324,51 +324,6 @@ struct SchedulerScriptData {} }; -/** - * For Lua, the TouchesScriptData is used to find the Lua function pointer by the nativeObject, then call the Lua - * function by push touches data and actionType into the Lua stack as the parameters when the touches event is - * triggered. - */ -struct TouchesScriptData -{ - /** - * The EventTouch::EventCode type. - * - * @lua NA - */ - EventTouch::EventCode actionType; - /** - * For Lua, it Used to find the Lua function pointer by the ScriptHandlerMgr. - * - * @lua NA - */ - void* nativeObject; - /** - * The vector of Touch.For Lua, it would be convert to the Lua table form to be pushed into the Lua stack. - * - * @lua NA - */ - const std::vector& touches; - /** - * event information, it is useless for Lua. - * - * @lua NA - */ - Event* event; - - /** - * Constructor of TouchesScriptData. - * - * @lua NA - */ - TouchesScriptData(EventTouch::EventCode inActionType, - void* inNativeObject, - const std::vector& inTouches, - Event* evt) - : actionType(inActionType), nativeObject(inNativeObject), touches(inTouches), event(evt) - {} -}; - /** * For Lua, the TouchScriptData is used to find the Lua function pointer by the nativeObject, then call the Lua function * by push touch data and actionType converted to string type into the Lua stack as the parameters when the touch event @@ -377,23 +332,17 @@ struct TouchesScriptData struct TouchScriptData { /** - * The EventTouch::EventCode type. + * The PointerEvent::EventCode type. * * @lua NA */ - EventTouch::EventCode actionType; + InputPhase actionType; /** * For Lua, it Used to find the Lua function pointer by the ScriptHandlerMgr. * * @lua NA */ void* nativeObject; - /** - * touch information. it would be in x,y form to push into the Lua stack. - * - * @lua NA - */ - Touch* touch; /** * event information,it is useless for Lua. * @@ -406,8 +355,8 @@ struct TouchScriptData * * @lua NA */ - TouchScriptData(EventTouch::EventCode inActionType, void* inNativeObject, Touch* inTouch, Event* evt) - : actionType(inActionType), nativeObject(inNativeObject), touch(inTouch), event(evt) + TouchScriptData(InputPhase inActionType, void* inNativeObject, Event* evt) + : actionType(inActionType), nativeObject(inNativeObject), event(evt) {} }; @@ -419,11 +368,11 @@ struct TouchScriptData struct KeypadScriptData { /** - * The specific type of EventKeyboard::KeyCode + * The specific type of KeyboardEvent::KeyCode * * @lua NA */ - EventKeyboard::KeyCode actionType; + KeyboardEvent::KeyCode actionType; /** * For Lua, it Used to find the Lua function pointer by the ScriptHandlerMgr. * @@ -436,7 +385,7 @@ struct KeypadScriptData * * @lua NA */ - KeypadScriptData(EventKeyboard::KeyCode inActionType, void* inNativeObject) + KeypadScriptData(KeyboardEvent::KeyCode inActionType, void* inNativeObject) : actionType(inActionType), nativeObject(inNativeObject) {} }; diff --git a/axmol/base/Touch.h b/axmol/base/Touch.h deleted file mode 100644 index bacc8e720af4..000000000000 --- a/axmol/base/Touch.h +++ /dev/null @@ -1,171 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010-2012 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -#include "axmol/base/Object.h" -#include "axmol/math/Math.h" - -namespace ax -{ - -/** - * @addtogroup base - * @{ - */ - -/** @class Touch - * @brief Encapsulates the Touch information, such as touch point, id and so on, - and provides the methods that commonly used. - */ -class AX_DLL Touch : public Object -{ -public: - /** - * Dispatch mode, how the touches are dispatched. - */ - enum class DispatchMode - { - ALL_AT_ONCE, /** All at once. */ - ONE_BY_ONE, /** One by one. */ - }; - - /** Constructor. - */ - Touch() : _id(0), _startPointCaptured(false), _curForce(0.f), _maxForce(0.f) {} - - /** Returns the current touch location in OpenGL coordinates. - * - * @return The current touch location in OpenGL coordinates. - */ - Vec2 getLocation() const; - /** Returns the previous touch location in OpenGL coordinates. - * - * @return The previous touch location in OpenGL coordinates. - */ - Vec2 getPreviousLocation() const; - /** Returns the start touch location in OpenGL coordinates. - * - * @return The start touch location in OpenGL coordinates. - */ - Vec2 getStartLocation() const; - /** Returns the delta of 2 current touches locations in screen coordinates. - * - * @return The delta of 2 current touches locations in screen coordinates. - */ - Vec2 getDelta() const; - /** Returns the current touch location in screen coordinates. - * - * @return The current touch location in screen coordinates. - */ - Vec2 getLocationInView() const; - /** Returns the previous touch location in screen coordinates. - * - * @return The previous touch location in screen coordinates. - */ - Vec2 getPreviousLocationInView() const; - /** Returns the start touch location in screen coordinates. - * - * @return The start touch location in screen coordinates. - */ - Vec2 getStartLocationInView() const; - - /** Set the touch information. It always used to monitor touch event. - * - * @param id A given id - * @param x A given x coordinate. - * @param y A given y coordinate. - */ - void setTouchInfo(int id, float x, float y) - { - _id = id; - _prevPoint = _point; - _point.x = x; - _point.y = y; - _curForce = 0.0f; - _maxForce = 0.0f; - if (!_startPointCaptured) - { - _startPoint = _point; - _startPointCaptured = true; - _prevPoint = _point; - } - } - - /** Set the touch information. It always used to monitor touch event. - * - * @param id A given id - * @param x A given x coordinate. - * @param y A given y coordinate. - * @param force Current force for 3d touch. - * @param maxForce maximum possible force for 3d touch. - */ - void setTouchInfo(int id, float x, float y, float force, float maxForce) - { - _id = id; - _prevPoint = _point; - _point.x = x; - _point.y = y; - _curForce = force; - _maxForce = maxForce; - if (!_startPointCaptured) - { - _startPoint = _point; - _startPointCaptured = true; - _prevPoint = _point; - } - } - /** Get touch id. - * @lua getId - * - * @return The id of touch. - */ - int getID() const { return _id; } - /** Returns the current touch force for 3d touch. - * - * @return The current touch force for 3d touch. - */ - float getCurrentForce() const; - /** Returns the maximum touch force for 3d touch. - * - * @return The maximum touch force for 3d touch. - */ - float getMaxForce() const; - -private: - int _id; - bool _startPointCaptured; - Vec2 _startPoint; - Vec2 _point; - Vec2 _prevPoint; - float _curForce; - float _maxForce; -}; - -// end of base group -/// @} - -} // namespace ax diff --git a/axmol/base/Utils.cpp b/axmol/base/Utils.cpp index d4e895941353..3d70f5caaab7 100644 --- a/axmol/base/Utils.cpp +++ b/axmol/base/Utils.cpp @@ -93,7 +93,7 @@ int nextPOT(int x) /* * Capture screen interface */ -static EventListenerCustom* s_captureScreenListener; +static CustomEventListener* s_captureScreenListener; void captureScreen(std::function)> imageCallback) { if (s_captureScreenListener) @@ -109,7 +109,7 @@ void captureScreen(std::function)> imageCallback) // !!!Metal: needs setFrameBufferOnly before draw const auto eventName = rhi::DriverContext::isMetal() ? Director::EVENT_BEFORE_DRAW : Director::EVENT_AFTER_DRAW; - s_captureScreenListener = eventDispatcher->addCustomEventListener(eventName, [=](EventCustom* /*event*/) { + s_captureScreenListener = eventDispatcher->addCustomEventListener(eventName, [=](CustomEvent* /*event*/) { eventDispatcher->removeEventListener(s_captureScreenListener); s_captureScreenListener = nullptr; // !!!GL: AFTER_DRAW and BEFORE_END_FRAME @@ -126,7 +126,7 @@ void captureScreen(std::function)> imageCallback) }); } -static std::unordered_map s_captureNodeListener; +static std::unordered_map s_captureNodeListener; void captureNode(Node* startNode, std::function)> imageCallback, float scale) { if (s_captureNodeListener.find(startNode) != s_captureNodeListener.end()) @@ -135,7 +135,7 @@ void captureNode(Node* startNode, std::function)> imageCallba return; } - auto callback = [startNode, scale, imageCallback](EventCustom* /*event*/) { + auto callback = [startNode, scale, imageCallback](CustomEvent* /*event*/) { auto director = Director::getInstance(); auto captureNodeListener = s_captureNodeListener[startNode]; director->getEventDispatcher()->removeEventListener((EventListener*)(captureNodeListener)); @@ -202,7 +202,7 @@ void captureScreen(std::function afterCap, std::st Director::getInstance()->getJobSystem()->enqueue( [_afterCap = std::move(_afterCap), image = std::move(image), _outfile = std::move(_outfile)]() mutable { bool ok = image->saveToFile(_outfile); - Director::getInstance()->getScheduler()->runOnAxmolThread( + Director::getInstance()->postTask( [ok, _afterCap = std::move(_afterCap), _outfile = std::move(_outfile)] { _afterCap(ok, _outfile); }); }); }); diff --git a/axmol/base/text_utils.cpp b/axmol/base/text_utils.cpp index 3624796a22dc..b49848157536 100644 --- a/axmol/base/text_utils.cpp +++ b/axmol/base/text_utils.cpp @@ -337,19 +337,19 @@ std::vector getChar16VectorFromUTF16String(const std::u16string& utf16 return std::vector(utf16.begin(), utf16.end()); } -size_t getCharacterCountInUTF8String(std::string_view utf8) +size_t getCharacterCountInUTF8String(std::string_view strUTF8) { - return countUTF8Chars(utf8); + return countUTF8Chars(strUTF8); } -size_t countUTF8Chars(std::string_view utf8) +size_t countUTF8Chars(std::string_view strUTF8) { int count = 0; - if (!utf8.empty()) + if (!strUTF8.empty()) { - const UTF8* source = (const UTF8*)utf8.data(); - const UTF8* sourceEnd = (const UTF8*)utf8.data() + utf8.length(); + const UTF8* source = (const UTF8*)strUTF8.data(); + const UTF8* sourceEnd = (const UTF8*)strUTF8.data() + strUTF8.length(); while (source != sourceEnd) { auto size = getUTF8SequenceSize(source, sourceEnd); @@ -366,6 +366,36 @@ size_t countUTF8Chars(std::string_view utf8) return count; } +UTF8CountResult countUTF8WithLimit(std::string_view strUTF8, size_t charLimit) +{ + UTF8CountResult result{}; + + if (!strUTF8.empty() && charLimit > 0) + { + const UTF8* const sourceStart = (const UTF8*)strUTF8.data(); + const UTF8* const sourceEnd = sourceStart + strUTF8.length(); + const UTF8* source = sourceStart; + + while (source != sourceEnd && result.charCount < charLimit) + { + auto size = getUTF8SequenceSize(source, sourceEnd); + if (size == 0) + { + // Invalid UTF-8 sequence found + result.success = false; + break; + } + source += size; + ++result.charCount; + } + + // Calculate total bytes consumed by subtracting pointers + result.byteCount = static_cast(source - sourceStart); + } + + return result; +} + size_t getUTF8ByteOffset(std::string_view utf8, size_t utf8CharOffset) { if (utf8CharOffset >= utf8.length()) diff --git a/axmol/base/text_utils.h b/axmol/base/text_utils.h index 450121745d7d..fd640e7d5cfa 100644 --- a/axmol/base/text_utils.h +++ b/axmol/base/text_utils.h @@ -25,15 +25,14 @@ THE SOFTWARE. ****************************************************************************/ -#ifndef AXMOL__TEXT_UTILS_H -#define AXMOL__TEXT_UTILS_H +#pragma once #include "axmol/platform/PlatformMacros.h" #include "axmol/tlx/format.hpp" #include #include #include -#include +#include #if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) # include @@ -75,6 +74,14 @@ inline std::string_view trim(std::string_view s) return ltrim(rtrim(s)); } +struct UTF8CountResult +{ + size_t charCount = 0; // Number of valid UTF-8 characters processed + size_t byteCount = 0; // Number of bytes consumed + bool success = true; // False if an invalid UTF-8 sequence was encountered + explicit operator bool() const { return success; } +}; + /** * @brief Converts from UTF8 string to UTF16 string. * @@ -204,7 +211,26 @@ AX_DLL bool isUnicodeNonBreaking(char32_t ch); * @param utf8 A UTF-8 encoded string view. * @return The number of Unicode code points in the input string. */ -AX_DLL size_t countUTF8Chars(std::string_view utf8); +AX_DLL size_t countUTF8Chars(std::string_view strUTF8); + +/** + * @brief Count UTF-8 characters and bytes up to a maximum character limit. + * + * This function scans a UTF-8 encoded string and counts both the number of + * characters and the number of bytes consumed until either the end of the + * string is reached or the specified maximum character limit is exceeded. + * + * @param strUTF8 The UTF-8 encoded input string. + * @param charLimit The maximum number of UTF-8 characters to process. + * + * @return A pair of integers: + * - first: The number of UTF-8 characters counted (up to charLimit). + * - second: The number of bytes consumed in the input string. + * + * @note If the input contains invalid UTF-8 sequences, behavior is undefined. + * This function does not perform full UTF-8 validation. + */ +AX_DLL UTF8CountResult countUTF8WithLimit(std::string_view strUTF8, size_t charLimit); /* * @brief Gets the byte offset of the UTF-8 character at the specified offset. @@ -299,5 +325,3 @@ class AX_DLL u8char_span } // namespace text_utils } // namespace ax - -#endif /** defined(AXMOL__TEXT_UTILS_H) */ diff --git a/axmol/network/WebSocket.cpp b/axmol/network/WebSocket.cpp index 41e283beee67..3ed64d7ca4fd 100644 --- a/axmol/network/WebSocket.cpp +++ b/axmol/network/WebSocket.cpp @@ -203,7 +203,7 @@ WebSocket::WebSocket() : _isDestroyed(std::make_shared>(false) std::shared_ptr> isDestroyed = _isDestroyed; _resetDirectorListener = Director::getInstance()->getEventDispatcher()->addCustomEventListener( - Director::EVENT_RESET, [this, isDestroyed](EventCustom*) { + Director::EVENT_RESET, [this, isDestroyed](CustomEvent*) { if (*isDestroyed) return; close(); diff --git a/axmol/network/WebSocket.h b/axmol/network/WebSocket.h index d03e472e3265..21423530b1ea 100644 --- a/axmol/network/WebSocket.h +++ b/axmol/network/WebSocket.h @@ -58,7 +58,7 @@ namespace ax { -class EventListenerCustom; +class CustomEventListener; namespace network { @@ -440,7 +440,7 @@ class AX_DLL WebSocket tlx::sbyte_buffer _receivedData; std::recursive_mutex _receivedDataMtx; - EventListenerCustom* _resetDirectorListener; + CustomEventListener* _resetDirectorListener; ConcurrentDeque _eventQueue; std::vector _headers; /// custom headers diff --git a/axmol/physics/2d/ContactEvent2D.cpp b/axmol/physics/2d/ContactEvent2D.cpp index 96f2a211127f..055a46c62ff3 100644 --- a/axmol/physics/2d/ContactEvent2D.cpp +++ b/axmol/physics/2d/ContactEvent2D.cpp @@ -28,14 +28,14 @@ # include "axmol/physics/2d/Rigidbody2D.h" # include "axmol/physics/2d/PhysicsUtility2D.h" -# include "axmol/base/EventCustom.h" +# include "axmol/base/CustomEvent.h" namespace ax { const char* CONTACT_2D_EVENT_NAME = "contact-2d"; -ContactEvent2D::ContactEvent2D() : EventCustom(CONTACT_2D_EVENT_NAME), _eventCode(EventCode::None), _result(true) {} +ContactEvent2D::ContactEvent2D() : CustomEvent(CONTACT_2D_EVENT_NAME), _eventCode(EventCode::None), _result(true) {} ContactEvent2D::~ContactEvent2D() {} @@ -147,12 +147,12 @@ ContactEventListener2D::~ContactEventListener2D() bool ContactEventListener2D::init() { - auto func = [this](EventCustom* event) -> void { onEvent(event); }; + auto func = [this](CustomEvent* event) -> void { onEvent(event); }; - return EventListenerCustom::init(CONTACT_2D_EVENT_NAME, func); + return CustomEventListener::init(CONTACT_2D_EVENT_NAME, func); } -void ContactEventListener2D::onEvent(EventCustom* event) +void ContactEventListener2D::onEvent(CustomEvent* event) { ContactEvent2D* contactEvent = dynamic_cast(event); diff --git a/axmol/physics/2d/ContactEvent2D.h b/axmol/physics/2d/ContactEvent2D.h index 2b2882aaba5a..9081d4aa4ff0 100644 --- a/axmol/physics/2d/ContactEvent2D.h +++ b/axmol/physics/2d/ContactEvent2D.h @@ -31,9 +31,9 @@ # include "axmol/base/Object.h" # include "axmol/math/Math.h" -# include "axmol/base/EventListenerCustom.h" +# include "axmol/base/CustomEventListener.h" # include "axmol/base/Event.h" -# include "axmol/base/EventCustom.h" +# include "axmol/base/CustomEvent.h" # include "yasio/object_pool.hpp" @@ -102,7 +102,7 @@ struct ContactInfo2D * It will created automatically when two shape contact with each other. And it will destroyed automatically when two shape separated. */ -class AX_DLL ContactEvent2D : public EventCustom +class AX_DLL ContactEvent2D : public CustomEvent { friend class ContactEventListener2D; friend class PhysicsWorld2D; @@ -178,7 +178,7 @@ class AX_DLL ContactEvent2D : public EventCustom }; /** Contact listener. It will receive all the contact callbacks. */ -class AX_DLL ContactEventListener2D : public EventListenerCustom +class AX_DLL ContactEventListener2D : public CustomEventListener { public: /** Create the listener. */ @@ -237,7 +237,7 @@ class AX_DLL ContactEventListener2D : public EventListenerCustom protected: bool init(); - void onEvent(EventCustom* event); + void onEvent(CustomEvent* event); protected: ContactEventListener2D(); diff --git a/axmol/physics/2d/PhysicsWorld2D.cpp b/axmol/physics/2d/PhysicsWorld2D.cpp index 4b3238ec6456..458912333bb2 100644 --- a/axmol/physics/2d/PhysicsWorld2D.cpp +++ b/axmol/physics/2d/PhysicsWorld2D.cpp @@ -41,7 +41,7 @@ # include "axmol/scene/Scene.h" # include "axmol/base/Director.h" # include "axmol/base/EventDispatcher.h" -# include "axmol/base/EventCustom.h" +# include "axmol/base/CustomEvent.h" # include "axmol/base/JobSystem.h" # include "box2d/constants.h" diff --git a/axmol/physics/3d/ContactEvent3D.cpp b/axmol/physics/3d/ContactEvent3D.cpp index 7cc75b098c85..b0d9802975c5 100644 --- a/axmol/physics/3d/ContactEvent3D.cpp +++ b/axmol/physics/3d/ContactEvent3D.cpp @@ -41,7 +41,7 @@ ContactEvent3D* ContactEvent3D::obtain(ContactInfo3D& info) return nullptr; } -ContactEvent3D::ContactEvent3D() : EventCustom(CONTACT_3D_EVENT_NAME) {} +ContactEvent3D::ContactEvent3D() : CustomEvent(CONTACT_3D_EVENT_NAME) {} bool ContactEvent3D::init(ContactInfo3D& info) { @@ -94,7 +94,7 @@ ContactEventListener3D* ContactEventListener3D::clone() return nullptr; } -void ContactEventListener3D::onEvent(EventCustom* event) +void ContactEventListener3D::onEvent(CustomEvent* event) { auto* contactEvent = static_cast(event); if (!contactEvent) @@ -129,8 +129,8 @@ void ContactEventListener3D::onEvent(EventCustom* event) bool ContactEventListener3D::init() { - auto func = [this](EventCustom* event) { onEvent(event); }; - return EventListenerCustom::init(CONTACT_3D_EVENT_NAME, func); + auto func = [this](CustomEvent* event) { onEvent(event); }; + return CustomEventListener::init(CONTACT_3D_EVENT_NAME, func); } } // namespace ax diff --git a/axmol/physics/3d/ContactEvent3D.h b/axmol/physics/3d/ContactEvent3D.h index 2606bf4e482a..c107b3341f6d 100644 --- a/axmol/physics/3d/ContactEvent3D.h +++ b/axmol/physics/3d/ContactEvent3D.h @@ -29,9 +29,9 @@ # include "axmol/base/Object.h" # include "axmol/math/Math.h" -# include "axmol/base/EventListenerCustom.h" +# include "axmol/base/CustomEventListener.h" # include "axmol/base/Event.h" -# include "axmol/base/EventCustom.h" +# include "axmol/base/CustomEvent.h" # include "yasio/object_pool.hpp" @@ -80,7 +80,7 @@ struct ContactInfo3D * It will created automatically when two shape contact with each other. And it will destroyed automatically when two shape separated. */ -class AX_DLL ContactEvent3D : public EventCustom +class AX_DLL ContactEvent3D : public CustomEvent { friend class ContactListener; friend class ContactEventListener3D; @@ -147,7 +147,7 @@ class AX_DLL ContactEvent3D : public EventCustom }; /** Contact listener. It will receive all the contact callbacks. */ -class AX_DLL ContactEventListener3D : public EventListenerCustom +class AX_DLL ContactEventListener3D : public CustomEventListener { public: /** Create the listener. */ @@ -184,7 +184,7 @@ class AX_DLL ContactEventListener3D : public EventListenerCustom protected: bool init(); - void onEvent(EventCustom* event); + void onEvent(CustomEvent* event); friend class PhysicsWorld3D; }; diff --git a/axmol/platform/ApplicationBase.cpp b/axmol/platform/ApplicationCore.cpp similarity index 51% rename from axmol/platform/ApplicationBase.cpp rename to axmol/platform/ApplicationCore.cpp index 9ee52e665c3b..2034203bf620 100644 --- a/axmol/platform/ApplicationBase.cpp +++ b/axmol/platform/ApplicationCore.cpp @@ -25,19 +25,58 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/platform/ApplicationBase.h" +#include "axmol/platform/ApplicationCore.h" #include "axmol/base/Director.h" namespace ax { -ContextAttrs ApplicationBase::_contextAttrs = ContextAttrs{}; +ContextAttrs ApplicationCore::s_contextAttrs = ContextAttrs{}; -ApplicationBase::~ApplicationBase() +Application* ApplicationCore::s_axmolApp = nullptr; +Director* ApplicationCore::s_director = nullptr; + +///////////////////////////////////////////////////////////////////////////////////////////////// +// static member function +////////////////////////////////////////////////////////////////////////////////////////////////// + +Application* ApplicationCore::getInstance() +{ + AX_ASSERT(s_axmolApp); + return s_axmolApp; +} + +ApplicationCore::ApplicationCore() +{ + s_director = Director::getInstance(); +} + +ApplicationCore::~ApplicationCore() { Director::destroyInstance(); } -void ApplicationBase::applicationScreenSizeChanged(int newWidth, int newHeight) {} +void ApplicationCore::applicationScreenSizeChanged(int newWidth, int newHeight) {} + +void ApplicationCore::setContextAttrs(const ContextAttrs& attrs) +{ + s_contextAttrs = attrs; + + // On macOS, the render scale mode is always physical, so we don't allow changing it to logical. + // On other platforms, if the user not explicitly sets a render scale mode, we use logical to avoid DPI scaling by + // default. +#if AX_TARGET_PLATFORM == AX_PLATFORM_MAC + s_contextAttrs.renderScaleMode = RenderScaleMode::Physical; +#else + if (attrs.renderScaleMode == RenderScaleMode::Default) + s_contextAttrs.renderScaleMode = RenderScaleMode::Logical; +#endif +} } // namespace ax + +// For wasm & android +void _axmolPerformFrameBoundaryTasks() +{ + ax::ApplicationCore::getDirector()->performFrameBoundaryTasks(); +} diff --git a/axmol/platform/ApplicationBase.h b/axmol/platform/ApplicationCore.h similarity index 88% rename from axmol/platform/ApplicationBase.h rename to axmol/platform/ApplicationCore.h index 0cd22e95af31..c73dfd254277 100644 --- a/axmol/platform/ApplicationBase.h +++ b/axmol/platform/ApplicationCore.h @@ -40,10 +40,13 @@ namespace ax * @{ */ -class RenderView; -class AX_DLL ApplicationBase +class RenderViewCore; +class Director; +class Application; +class AX_DLL ApplicationCore { - friend class RenderView; + friend class RenderViewCore; + friend class Director; public: /** Since WINDOWS and ANDROID are defined as macros, we could not just use these keywords in enumeration(Platform). @@ -67,10 +70,18 @@ class AX_DLL ApplicationBase Emscripten = Wasm }; + /** +@brief Get current application instance. +@return Current application instance pointer. +*/ + static Application* getInstance(); + + ApplicationCore(); + /** * @lua NA */ - virtual ~ApplicationBase(); + virtual ~ApplicationCore(); /** * @brief Implement Director and Scene init code here. @@ -153,9 +164,11 @@ class AX_DLL ApplicationBase */ virtual bool openURL(std::string_view url) = 0; - static void setContextAttrs(const ContextAttrs& attrs) { _contextAttrs = attrs; } + static void setContextAttrs(const ContextAttrs& attrs); - static const ContextAttrs& getContextAttrs() { return _contextAttrs; } + static const ContextAttrs& getContextAttrs() { return s_contextAttrs; } + + static Director* getDirector() { return s_director; } protected: /** @@ -171,10 +184,12 @@ class AX_DLL ApplicationBase */ virtual void applicationScreenSizeChanged(int newWidth, int newHeight); - static ContextAttrs _contextAttrs; -}; + virtual void postBoundaryTaskSignal() {}; -using ApplicationProtocol = ApplicationBase; + static ContextAttrs s_contextAttrs; + static Application* s_axmolApp; + static Director* s_director; +}; // end of platform group /** @} */ diff --git a/axmol/platform/CMakeLists.txt b/axmol/platform/CMakeLists.txt index cc24d81ae047..a268ad24fe48 100644 --- a/axmol/platform/CMakeLists.txt +++ b/axmol/platform/CMakeLists.txt @@ -27,7 +27,7 @@ if(ANDROID) set(_AX_PLATFORM_SPECIFIC_SRC platform/android/Application-android.cpp platform/android/Common-android.cpp - platform/android/RenderViewImpl-android.cpp + platform/android/RenderView-android.cpp platform/android/FileUtils-android.cpp ) elseif(WINDOWS) @@ -81,14 +81,14 @@ elseif(APPLE) platform/mac/Common-mac.mm platform/mac/Device-mac.mm ) - set_source_files_properties(platform/desktop/RenderViewImpl.cpp PROPERTIES LANGUAGE OBJCXX) + set_source_files_properties(platform/pc/RenderView-pc.cpp PROPERTIES LANGUAGE OBJCXX) elseif(IOS) set(_AX_PLATFORM_SPECIFIC_HEADER ${_AX_PLATFORM_SPECIFIC_HEADER} platform/ios/Application-ios.h platform/ios/DirectorCaller-ios.h platform/ios/RenderHostView-ios.h - platform/ios/RenderViewImpl-ios.h + platform/ios/RenderView-ios.h platform/ios/StdC-ios.h platform/ios/InputView-ios.h platform/ios/AxmolAppController.h @@ -101,7 +101,7 @@ elseif(APPLE) platform/ios/Device-ios.mm platform/ios/DirectorCaller-ios.mm platform/ios/RenderHostView-ios.mm - platform/ios/RenderViewImpl-ios.mm + platform/ios/RenderView-ios.mm platform/ios/Image-ios.mm platform/ios/InputView-ios.mm platform/ios/AxmolAppController.mm @@ -161,22 +161,23 @@ elseif(EMSCRIPTEN) endif() endif() -# Add desktop-specific sources for platforms where GLFW is available +# Add PC-specific sources for platforms where GLFW is available if((WIN32 AND NOT WINRT) OR LINUX OR MACOSX OR WASM) - list(APPEND _AX_PLATFORM_SPECIFIC_HEADER "platform/desktop/RenderViewImpl.h") - list(APPEND _AX_PLATFORM_SPECIFIC_SRC "platform/desktop/RenderViewImpl.cpp") - list(APPEND _AX_PLATFORM_SPECIFIC_SRC "platform/desktop/Device-desktop.cpp") + list(APPEND _AX_PLATFORM_SPECIFIC_HEADER "platform/pc/RenderView-pc.h") + list(APPEND _AX_PLATFORM_SPECIFIC_SRC "platform/pc/RenderView-pc.cpp") + list(APPEND _AX_PLATFORM_SPECIFIC_SRC "platform/pc/Device-pc.cpp") endif() set(_AX_PLATFORM_HEADER ${_AX_PLATFORM_SPECIFIC_HEADER} platform/Application.h - platform/ApplicationBase.h + platform/ApplicationCore.h platform/Common.h platform/Device.h platform/FileUtils.h platform/GL.h platform/RenderView.h + platform/RenderViewCore.h platform/Image.h platform/PlatformConfig.h platform/PlatformDefine.h @@ -191,10 +192,10 @@ set(_AX_PLATFORM_HEADER set(_AX_PLATFORM_SRC ${_AX_PLATFORM_SPECIFIC_SRC} platform/SAXParser.cpp - platform/RenderView.cpp + platform/RenderViewCore.cpp platform/FileUtils.cpp platform/Image.cpp platform/FileStream.cpp - platform/ApplicationBase.cpp + platform/ApplicationCore.cpp platform/CommandLineArgs.cpp ) diff --git a/axmol/platform/Device.h b/axmol/platform/Device.h index 381e757a5f86..e7d9652643f8 100644 --- a/axmol/platform/Device.h +++ b/axmol/platform/Device.h @@ -35,6 +35,7 @@ namespace ax { struct FontDefinition; +class Application; /** * @addtogroup platform @@ -83,12 +84,46 @@ class AX_DLL Device static constexpr int MAX_REFRESH_RATE = 1000; static constexpr int DEFAULT_REFRESH_RATE = 60; + /** + * Retrieves the current clipboard text. + * + * On native platforms (Windows, macOS, Linux, iOS, Android), this function + * executes synchronously and the clipboard text is available immediately. + * + * On WebAssembly (browser) platforms, clipboard access requires the + * asynchronous JavaScript Clipboard API. Therefore, the result is delivered + * via the provided callback once the browser resolves the request. + * + * If reading the clipboard fails or the clipboard is empty, the callback + * will be invoked with an empty string. + * + * @param callback Function to receive the clipboard text result. + * @since axmol-3.0.0 + */ + static void getClipboardText(std::function callback); + + /** + * Sets the clipboard text. + * @since axmol-3.0.0 + */ + static void setClipboardText(std::string_view text); + + /** + * Clears the clipboard content. + * @since axmol-3.0.0 + */ + static void clearClipboard(); + /** * Gets the DPI of device * @return The DPI of device. */ static int getDPI(); +#if AX_TARGET_PLATFORM == AX_PLATFORM_WINRT + static void setDPI(float dpi); +#endif + /** * Gets the device pixel ratio * @since axmol-2.1.0 @@ -285,7 +320,7 @@ class AX_DLL Device * from the supported mask is used as a fallback. * * The returned Orientation is guaranteed to be compatible with the supported - * orientation mask, and can be used by RenderViewImpl-ios to compute the + * orientation mask, and can be used by RenderView-ios to compute the * logical screen size and viewport before app->run(). * * @return Orientation The resolved final orientation for the application. @@ -306,6 +341,8 @@ class AX_DLL Device static int getDisplayRefreshRate(); private: + friend class Application; + AX_DISALLOW_IMPLICIT_CONSTRUCTORS(Device); }; diff --git a/axmol/platform/FileUtils.h b/axmol/platform/FileUtils.h index 0db36e6fad57..bf81d16d31ba 100644 --- a/axmol/platform/FileUtils.h +++ b/axmol/platform/FileUtils.h @@ -746,7 +746,7 @@ class AX_DLL FileUtils // As axmol uses c++17+, we will use std::bind to leverage move sematics to // move our arguments into our lambda, to potentially avoid copying. auto lambda = std::bind([](const T& actionIn, const R& callbackIn, const ARGS&... argsIn) { - Director::getInstance()->getScheduler()->runOnAxmolThread(std::bind(callbackIn, actionIn(argsIn...))); + Director::getInstance()->postTask(std::bind(callbackIn, actionIn(argsIn...))); }, std::forward(action), std::forward(callback), std::forward(args)...); Director::getInstance()->getJobSystem()->enqueue(std::move(lambda)); diff --git a/axmol/platform/RenderView.cpp b/axmol/platform/RenderView.cpp deleted file mode 100644 index 1bf377635353..000000000000 --- a/axmol/platform/RenderView.cpp +++ /dev/null @@ -1,615 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010-2012 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#include "axmol/platform/RenderView.h" - -#include "axmol/base/Touch.h" -#include "axmol/base/Director.h" -#include "axmol/base/EventDispatcher.h" -#include "axmol/scene/Camera.h" -#include "axmol/scene/Scene.h" -#include "axmol/renderer/Renderer.h" - -namespace ax -{ - -namespace -{ - -static Touch* g_touches[EventTouch::MAX_TOUCHES] = {nullptr}; -static unsigned int g_indexBitsUsed = 0; -// System touch pointer ID (It may not be ascending order number) <-> Ascending order number from 0 -static std::map g_touchIdReorderMap; - -static int getUnUsedIndex() -{ - int i; - int temp = g_indexBitsUsed; - - for (i = 0; i < EventTouch::MAX_TOUCHES; i++) - { - if (!(temp & 0x00000001)) - { - g_indexBitsUsed |= (1 << i); - return i; - } - - temp >>= 1; - } - - // all bits are used - return -1; -} - -static std::vector getAllTouchesVector() -{ - std::vector ret; - int i; - int temp = g_indexBitsUsed; - - ret.reserve(EventTouch::MAX_TOUCHES); - for (i = 0; i < EventTouch::MAX_TOUCHES; i++) - { - if (temp & 0x00000001) - { - ret.emplace_back(g_touches[i]); - } - temp >>= 1; - } - return ret; -} - -static void removeUsedIndexBit(int index) -{ - if (index < 0 || index >= EventTouch::MAX_TOUCHES) - { - return; - } - - unsigned int temp = 1 << index; - temp = ~temp; - g_indexBitsUsed &= temp; -} - -} // namespace - -RenderView::RenderView() - : _windowSize(0, 0) - , _designResolutionSize(0, 0) - , _viewScale(Vec2::ONE) - , _resolutionPolicy(ResolutionPolicy::UNKNOWN) - , _interactive(true) -{} - -RenderView::~RenderView() {} - -void RenderView::pollEvents() {} - -void RenderView::updateDesignResolution() -{ - if (_renderSize.width > 0 && _renderSize.height > 0 && _designResolutionSize.width > 0 && - _designResolutionSize.height > 0) - { - _viewScale = _renderSize / _designResolutionSize; - - if (_resolutionPolicy == ResolutionPolicy::NO_BORDER) - { - _viewScale.x = _viewScale.y = (std::max)(_viewScale.x, _viewScale.y); - } - - else if (_resolutionPolicy == ResolutionPolicy::SHOW_ALL) - { - _viewScale.x = _viewScale.y = (std::min)(_viewScale.x, _viewScale.y); - } - - else if (_resolutionPolicy == ResolutionPolicy::FIXED_HEIGHT) - { - _viewScale.x = _viewScale.y; - _designResolutionSize.width = ceilf(_renderSize.width / _viewScale.x); - } - - else if (_resolutionPolicy == ResolutionPolicy::FIXED_WIDTH) - { - _viewScale.y = _viewScale.x; - _designResolutionSize.height = ceilf(_renderSize.height / _viewScale.y); - } - - // calculate the rect of viewport - float viewportW = _designResolutionSize.width * _viewScale.x; - float viewportH = _designResolutionSize.height * _viewScale.y; - - _viewportRect.setRect((_renderSize.width - viewportW) / 2, (_renderSize.height - viewportH) / 2, viewportW, - viewportH); - - // reset director's member variables to fit visible rect - auto director = Director::getInstance(); - director->setCanvasSize(getDesignResolutionSize()); - director->setProjection(director->getProjection()); - } -} - -void RenderView::setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy) -{ - AXASSERT(resolutionPolicy != ResolutionPolicy::UNKNOWN, "should set resolutionPolicy"); - - if (width == 0.0f || height == 0.0f) - { - return; - } - - _designResolutionSize.set(width, height); - _resolutionPolicy = resolutionPolicy; - - if (!_isResolutionUpdateLocked) - updateDesignResolution(); -} - -const Vec2& RenderView::getDesignResolutionSize() const -{ - return _designResolutionSize; -} - -void RenderView::updateRenderSurface(float width, float height, uint8_t updateFlag) -{ - Vec2 value{width, height}; - - if (updateFlag & SurfaceUpdateFlag::WindowSizeChanged) - _windowSize = value; - - if (updateFlag & SurfaceUpdateFlag::RenderSizeChanged) - { - _isResolutionUpdateLocked = true; - - _renderSize = value; - - // If designResolutionSize hasn't been set, default to renderSize - if (_designResolutionSize.equals(Vec2::ZERO)) - _designResolutionSize = value; - - // Notify the application that the screen size has changed. - // This gives the user a chance to re-layout scene content or reset designResolutionSize if needed. - if (!(updateFlag & SurfaceUpdateFlag::SilentUpdate)) - ax::Application::getInstance()->applicationScreenSizeChanged(width, height); - - // then we update resolution and viewport - updateDesignResolution(); - - _isResolutionUpdateLocked = false; - } - - // check does all updateed - maybeDispatchResizeEvent(updateFlag); -} - -void RenderView::maybeDispatchResizeEvent(uint8_t updateFlag) -{ - const bool silentUpdate = (updateFlag & SurfaceUpdateFlag::SilentUpdate) != 0; - updateFlag &= ~SurfaceUpdateFlag::SilentUpdate; // Remove temporary flag - - _surfaceUpdateFlags |= updateFlag; - - constexpr uint8_t requiredFlags = SurfaceUpdateFlag::WindowSizeChanged | SurfaceUpdateFlag::RenderSizeChanged; - - const bool readyToDispatch = (_surfaceUpdateFlags == requiredFlags); - - if (readyToDispatch) - { - _surfaceUpdateFlags = 0; - if (!silentUpdate) - onSurfaceResized(); - } -} - -Rect RenderView::getVisibleRect() const -{ - Rect ret; - ret.size = getVisibleSize(); - ret.origin = getVisibleOrigin(); - return ret; -} - -Rect RenderView::getSafeAreaRect() const -{ - return getVisibleRect(); -} - -Vec2 RenderView::getVisibleSize() const -{ - if (_resolutionPolicy == ResolutionPolicy::NO_BORDER) - { - return Vec2(_renderSize.width / _viewScale.x, _renderSize.height / _viewScale.y); - } - else - { - return _designResolutionSize; - } -} - -Vec2 RenderView::getVisibleOrigin() const -{ - if (_resolutionPolicy == ResolutionPolicy::NO_BORDER) - { - return Vec2((_designResolutionSize.width - _renderSize.width / _viewScale.x) / 2, - (_designResolutionSize.height - _renderSize.height / _viewScale.y) / 2); - } - else - { - return Vec2::ZERO; - } -} - -void RenderView::setViewportInPoints(float x, float y, float w, float h) -{ - Viewport vp; - vp.x = (int)(x * _viewScale.x + _viewportRect.origin.x); - vp.y = (int)(y * _viewScale.y + _viewportRect.origin.y); - vp.width = (unsigned int)(w * _viewScale.x); - vp.height = (unsigned int)(h * _viewScale.y); - Camera::setDefaultViewport(vp); -} - -void RenderView::setScissorInPoints(float x, float y, float w, float h) -{ - setScissorRect((int)(x * _viewScale.x + _viewportRect.origin.x), (int)(y * _viewScale.y + _viewportRect.origin.y), - (unsigned int)(w * _viewScale.y), (unsigned int)(h * _viewScale.y)); -} - -Rect RenderView::getScissorInPoints() const -{ - auto& rect = getScissorRect(); - - float x = (rect.x - _viewportRect.origin.x) / _viewScale.x; - float y = (rect.y - _viewportRect.origin.y) / _viewScale.y; - float w = rect.width / _viewScale.x; - float h = rect.height / _viewScale.y; - return Rect(x, y, w, h); -} - -bool RenderView::isScissorEnabled() -{ - auto renderer = Director::getInstance()->getRenderer(); - return renderer->getScissorTest(); -} - -void RenderView::setViewName(std::string_view viewname) -{ - _viewName = viewname; -} - -std::string_view RenderView::getViewName() const -{ - return _viewName; -} - -void RenderView::handleTouchesBegin(int num, intptr_t ids[], float xs[], float ys[]) -{ - if (!_interactive) - return; - - EventTouch touchEvent; - - for (int i = 0; i < num; ++i) - { - auto id = ids[i]; - auto x = xs[i]; - auto y = ys[i]; - - auto iter = g_touchIdReorderMap.find(id); - - // it is a new touch - if (iter == g_touchIdReorderMap.end()) - { - auto unusedIndex = getUnUsedIndex(); - - // The touches is more than MAX_TOUCHES ? - if (unusedIndex == -1) - { - AXLOGD("The touches is more than MAX_TOUCHES, unusedIndex = {}", unusedIndex); - continue; - } - - Touch* touch = g_touches[unusedIndex] = new Touch(); - touch->setTouchInfo(unusedIndex, transformInputX(x), transformInputY(y)); - - AXLOGV("x = {} y = {}", touch->getLocationInView().x, touch->getLocationInView().y); - - g_touchIdReorderMap.emplace(id, unusedIndex); - touchEvent._touches.emplace_back(touch); - } - } - - if (touchEvent._touches.empty()) - { - AXLOGD("touchesBegan: size = 0"); - return; - } - - touchEvent._eventCode = EventTouch::EventCode::BEGAN; - auto dispatcher = Director::getInstance()->getEventDispatcher(); - dispatcher->dispatchEvent(&touchEvent); -} - -void RenderView::handleTouchesMove(int num, intptr_t ids[], float xs[], float ys[]) -{ - handleTouchesMove(num, ids, xs, ys, nullptr, nullptr); -} - -void RenderView::handleTouchesMove(int num, intptr_t ids[], float xs[], float ys[], float fs[], float ms[]) -{ - if (!_interactive) - return; - - EventTouch touchEvent; - - for (int i = 0; i < num; ++i) - { - auto id = ids[i]; - float x = xs[i]; - float y = ys[i]; - float force = fs ? fs[i] : 0.0f; - float maxForce = ms ? ms[i] : 0.0f; - - auto iter = g_touchIdReorderMap.find(id); - if (iter == g_touchIdReorderMap.end()) - { - AXLOGD("if the index doesn't exist, it is an error"); - continue; - } - - AXLOGV("Moving touches with id: {}, x={}, y={}, force={}, maxFource={}", (int)id, x, y, force, maxForce); - Touch* touch = g_touches[iter->second]; - if (touch) - { - touch->setTouchInfo(iter->second, transformInputX(x), transformInputY(y), force, maxForce); - - touchEvent._touches.emplace_back(touch); - } - else - { - // It is error, should return. - AXLOGD("Moving touches with id: {} error", static_cast(id)); - return; - } - } - - if (touchEvent._touches.empty()) - { - AXLOGD("touchesMoved: size = 0"); - return; - } - - touchEvent._eventCode = EventTouch::EventCode::MOVED; - auto dispatcher = Director::getInstance()->getEventDispatcher(); - dispatcher->dispatchEvent(&touchEvent); -} - -void RenderView::handleTouchesOfEndOrCancel(EventTouch::EventCode eventCode, - int num, - intptr_t ids[], - float xs[], - float ys[]) -{ - EventTouch touchEvent; - - for (int i = 0; i < num; ++i) - { - auto id = ids[i]; - auto x = xs[i]; - auto y = ys[i]; - - auto iter = g_touchIdReorderMap.find(id); - if (iter == g_touchIdReorderMap.end()) - { - AXLOGD("if the index doesn't exist, it is an error"); - continue; - } - - /* Add to the set to send to the director */ - Touch* touch = g_touches[iter->second]; - if (touch) - { - AXLOGV("Ending touches with id: {}, x={}, y={}", (int)id, x, y); - touch->setTouchInfo(iter->second, transformInputX(x), transformInputY(y)); - - touchEvent._touches.emplace_back(touch); - - g_touches[iter->second] = nullptr; - removeUsedIndexBit(iter->second); - - g_touchIdReorderMap.erase(id); - } - else - { - AXLOGD("Ending touches with id: {} error", static_cast(id)); - return; - } - } - - if (touchEvent._touches.empty()) - { - AXLOGD("touchesEnded or touchesCancel: size = 0"); - return; - } - - touchEvent._eventCode = eventCode; - auto dispatcher = Director::getInstance()->getEventDispatcher(); - dispatcher->dispatchEvent(&touchEvent); - - for (auto&& touch : touchEvent._touches) - { - // release the touch object. - touch->release(); - } -} - -void RenderView::handleTouchesEnd(int num, intptr_t ids[], float xs[], float ys[]) -{ - handleTouchesOfEndOrCancel(EventTouch::EventCode::ENDED, num, ids, xs, ys); -} - -void RenderView::handleTouchesCancel(int num, intptr_t ids[], float xs[], float ys[]) -{ - handleTouchesOfEndOrCancel(EventTouch::EventCode::CANCELLED, num, ids, xs, ys); -} - -const Rect& RenderView::getViewportRect() const -{ - return _viewportRect; -} - -std::vector RenderView::getAllTouches() const -{ - return getAllTouchesVector(); -} - -float RenderView::getScaleX() const -{ - return _viewScale.x; -} - -float RenderView::getScaleY() const -{ - return _viewScale.y; -} - -void RenderView::onSurfaceResized() -{ - int screenWidth = static_cast(_renderSize.width); - int screenHeight = static_cast(_renderSize.height); - - AXLOGD("RenderView::onSurfaceResized: ({}x{})", screenWidth, screenHeight); - - auto renderer = Director::getInstance()->getRenderer(); - if (renderer) - renderer->updateSurface(getNativeDisplay(), screenWidth, screenHeight); -#ifdef AX_ENABLE_VR - if (_vrRenderer) [[unlikely]] - _vrRenderer->onRenderViewResized(this); -#endif -} - -void RenderView::renderScene(Scene* scene, Renderer* renderer) -{ - AXASSERT(scene, "Invalid Scene"); - AXASSERT(renderer, "Invalid Renderer"); - -#ifdef AX_ENABLE_VR - if (_vrRenderer) [[unlikely]] - { - _vrRenderer->render(scene, renderer); - return; - } -#endif - - scene->render(renderer, Mat4::IDENTITY, nullptr); -} - -void RenderView::setScissorRect(float x, float y, float w, float h) -{ -#ifdef AX_ENABLE_VR - if (_vrRenderer) [[unlikely]] - { - _vrRenderer->setScissorRect(x, y, w, h); - return; - } -#endif - - Director::getInstance()->getRenderer()->setScissorRect(x, y, w, h); -} - -const ScissorRect& RenderView::getScissorRect() const -{ -#ifdef AX_ENABLE_VR - if (_vrRenderer) [[unlikely]] - return _vrRenderer->getScissorRect(); -#endif - - return Director::getInstance()->getRenderer()->getScissorRect(); -} - -#ifdef AX_ENABLE_VR -void RenderView::setVR(std::unique_ptr&& impl) -{ - if (_vrRenderer != impl) - { - if (_vrRenderer) - { - _vrRenderer->cleanup(); - _vrRenderer.reset(); - } - - if (impl) - impl->init(this); - - _vrRenderer = std::move(impl); - } -} -#endif - -void RenderView::queueOperation(AsyncOperation /*op*/, void* /*param*/) {} - -void RenderView::setInteractive(bool interactive) -{ - if (_interactive && !interactive) - { - cancelAllTouches(); - } - - _interactive = interactive; -} - -void RenderView::cancelAllTouches() -{ - EventTouch touchEvent; - touchEvent._touches = getAllTouchesVector(); - touchEvent._eventCode = EventTouch::EventCode::CANCELLED; - - if (touchEvent._touches.empty()) - { - AXLOGD("cancelling all touches: size = 0"); - return; - } - - auto dispatcher = Director::getInstance()->getEventDispatcher(); - dispatcher->dispatchEvent(&touchEvent); - - for (auto&& touch : touchEvent._touches) - { - // release the touch object. - touch->release(); - } - - g_touchIdReorderMap.clear(); - g_indexBitsUsed = 0; - - for (int i = 0; i < EventTouch::MAX_TOUCHES; ++i) - { - g_touches[i] = nullptr; - } -} - -} // namespace ax diff --git a/axmol/platform/RenderView.h b/axmol/platform/RenderView.h index b41bd1510c80..e35f52f17eee 100644 --- a/axmol/platform/RenderView.h +++ b/axmol/platform/RenderView.h @@ -1,7 +1,4 @@ /**************************************************************************** -Copyright (c) 2010-2012 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -26,524 +23,14 @@ THE SOFTWARE. ****************************************************************************/ #pragma once - -#include "axmol/base/Types.h" -#include "axmol/base/EventTouch.h" -#include "axmol/vr/VRBase.h" - -#include -#include -#include - -#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) -# include -#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) */ - -#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) || (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) -# define AX_ICON_SET_SUPPORT true -#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) || (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) */ - -namespace ax -{ -class Scene; -class Renderer; -class Director; - -using SurfaceHandle = rhi::SurfaceHandle; - -/** There are some Resolution Policy for Adapt to the screen. */ -enum class ResolutionPolicy -{ - /** The entire application is visible in the specified area without trying to preserve the original aspect ratio. - * Distortion can occur, and the application may appear stretched or compressed. - */ - EXACT_FIT, - /** The entire application fills the specified area, without distortion but possibly with some cropping, - * while maintaining the original aspect ratio of the application. - */ - NO_BORDER, - /** The entire application is visible in the specified area without distortion while maintaining the original - * aspect ratio of the application. Borders can appear on two sides of the application. - */ - SHOW_ALL, - /** The application takes the height of the design resolution size and modifies the width of the internal - * canvas so that it fits the aspect ratio of the device. - * No distortion will occur however you must make sure your application works on different - * aspect ratios. - */ - FIXED_HEIGHT, - /** The application takes the width of the design resolution size and modifies the height of the internal - * canvas so that it fits the aspect ratio of the device. - * No distortion will occur however you must make sure your application works on different - * aspect ratios. - */ - FIXED_WIDTH, - - UNKNOWN, -}; - -/** - * @addtogroup platform - * @{ - */ - -enum class WindowPlatform -{ - Unknown, // Unknown or unsupported platform - Win32, // Windows desktop applications using HWND - CoreWindow, // UWP or Xbox applications using CoreWindow/AppWindow - Cocoa, // macOS applications using NSWindow - X11, // Linux applications using the X11 window system - Wayland, // Linux applications using the Wayland protocol - UIKit, // iOS/tvOS applications using UIView/UIWindow - Android, // Android applications using SurfaceView or native window - Web // WebAssembly applications using HTML canvas or DOM -}; - -/** - * @brief By RenderView you can operate the frame information of EGL view through some function. - */ -class AX_DLL RenderView : public Object -{ - friend class Director; - -public: - enum SurfaceUpdateFlag : uint8_t - { - WindowSizeChanged = 1, // Indicates that window size has changed - RenderSizeChanged = 1 << 1, // Indicates that render surface size has changed - SilentUpdate = 1 << 2, // Temporary flag: suppresses event dispatch for this update only. - // Should be stripped before accumulating into persistent state. - AllUpdates = WindowSizeChanged | RenderSizeChanged, - AllUpdatesSilently = AllUpdates | SilentUpdate - }; - - /** - */ - RenderView(); - /** - * @lua NA - */ - virtual ~RenderView(); - - /** Force destroying EGL view, subclass must implement this method. - * - * @lua endToLua - */ - virtual void end() = 0; - - /** Get whether graphics context is ready, subclass must implement this method. */ - virtual bool isGfxContextReady() = 0; - - /** Exchanges the front and back buffers, subclass must implement this method. */ - virtual void swapBuffers() = 0; - - /** Open or close IME keyboard , subclass must implement this method. - * - * @param open Open or close IME keyboard. - */ - virtual void setIMEKeyboardState(bool open) = 0; - - /** When the window is closed, it will return false if the platforms is Ios or Android. - * If the platforms is windows or Mac,it will return true. - * - * @return In ios and android it will return false,if in windows or Mac it will return true. - */ - virtual bool windowShouldClose() { return false; }; - - /** Polls the events. */ - virtual void pollEvents(); - - virtual Vec2 getNativeWindowSize() const { return getWindowSize(); } - - /** - * Get the zoomed window size - * In general, it returns the screen size since the EGL view is a fullscreen view. - * - * @return The window size (aka logic size) - */ - const Vec2& getWindowSize() const { return _windowSize; } - - /** - * Set the zoomed window size - * - * @param width The width of the fram size. - * @param height The height of the fram size. - */ - virtual void setWindowSize(float, float) {} - - /** Set zoom factor for frame. This methods are for - * debugging big resolution (e.g.new ipad) app on desktop. - * - * @param zoomFactor The zoom factor for frame. - */ - virtual void setWindowZoomFactor(float /*zoomFactor*/) {} - - /** Get zoom factor for frame. This methods are for - * debugging big resolution (e.g.new ipad) app on desktop. - * - * @return The zoom factor for frame. - */ - virtual float getWindowZoomFactor() const { return 1.0; } - - const Vec2& getRenderSize() const { return _renderSize; } - -#ifndef _AX_GEN_SCRIPT_BINDINGS - /** - * implicit deprecated APIs, use getWindowSize instead - */ - AX_DEPRECATED(3.0) const Vec2& getFrameSize() const { return getWindowSize(); } - AX_DEPRECATED(3.0) void setFrameSize(float width, float height) { setWindowSize(width, height); } - AX_DEPRECATED(3.0) float getFrameZoomFactor() const { return getWindowZoomFactor(); } - AX_DEPRECATED(3.0) void setFrameZoomFactor(float zoomFactor) { setWindowZoomFactor(zoomFactor); } +#include "axmol/platform/PlatformConfig.h" + +#if AX_TARGET_PLATFORM == AX_PLATFORM_IOS +# include "axmol/platform/ios/RenderView-ios.h" +#elif AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID +# include "axmol/platform/android/RenderView-android.h" +#elif AX_TARGET_PLATFORM == AX_PLATFORM_WINRT +# include "axmol/platform/winrt/RenderView-winrt.h" +#else +# include "axmol/platform/pc/RenderView-pc.h" #endif - - /** - * Hide or Show the mouse cursor if there is one. - * - * @param isVisible Hide or Show the mouse cursor if there is one. - */ - virtual void setCursorVisible(bool /*isVisible*/) {} - - /** Get axmol render scale. - * - * @return The render scale fbSize/windowSize aka backing scale factor - * - * Notes: - * - On mobile platforms, this value is always 1.0f (no DPI scaling). - * - On PC platforms, when renderScaleMode == Physical, this value usually - * reflects the monitor's DPI scaling factor (e.g. Windows HiDPI). - */ - virtual float getRenderScale() const { return 1.0f; } - - /** - * Get the visible area size of render viewport. - * - * @return The visible area size of render viewport. - */ - virtual Vec2 getVisibleSize() const; - - /** - * Get the visible origin point of render viewport. - * - * @return The visible origin point of render viewport. - */ - virtual Vec2 getVisibleOrigin() const; - - /** - * Get the visible rectangle of render viewport. - * - * @return The visible rectangle of render viewport. - */ - virtual Rect getVisibleRect() const; - - /** - * Gets safe area rectangle - */ - virtual Rect getSafeAreaRect() const; - - /** - * Set the design resolution size. - * @param width Design resolution width. - * @param height Design resolution height. - * @param resolutionPolicy The resolution policy desired, you may choose: - * [1] EXACT_FIT Fill screen by stretch-to-fit: if the design resolution ratio of width to - * height is different from the screen resolution ratio, your game view will be stretched. [2] NO_BORDER Full screen - * without black border: if the design resolution ratio of width to height is different from the screen resolution - * ratio, two areas of your game view will be cut. [3] SHOW_ALL Full screen with black border: if the design - * resolution ratio of width to height is different from the screen resolution ratio, two black borders will be - * shown. - * @remark For applications with a static design resolution, this method should typically be called only once during - * initialization. - */ - virtual void setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy); - - /** Get design resolution size. - * Default resolution size is the same as 'getWindowSize'. - * - * @return The design resolution size. - */ - virtual const Vec2& getDesignResolutionSize() const; - - /** - * Set render view port rectangle with points. - * - * @param x Set the points of x. - * @param y Set the points of y. - * @param w Set the width of the view port - * @param h Set the Height of the view port. - */ - virtual void setViewportInPoints(float x, float y, float w, float h); - - /** - * Set Scissor rectangle with points. - * - * @param x Set the points of x. - * @param y Set the points of y. - * @param w Set the width of the view port - * @param h Set the Height of the view port. - */ - virtual void setScissorInPoints(float x, float y, float w, float h); - - /** - * Get whether GL_SCISSOR_TEST is enable. - * - * @return Whether GL_SCISSOR_TEST is enable. - */ - virtual bool isScissorEnabled(); - - /** - * Get the current scissor rectangle. - * - * @return The current scissor rectangle. - */ - virtual Rect getScissorInPoints() const; - - /* - * Sets the view name. will change the window title on desktop platform - * @param viewname A string will be set to the view as name. - */ - virtual void setViewName(std::string_view viewname); - - /** Get the view name. - * - * @return The view name. - */ - std::string_view getViewName() const; - - /** Touch events are handled by default; if you want to customize your handlers, please override this function. - * - * @param num The number of touch. - * @param ids The identity of the touch. - * @param xs The points of x. - * @param ys The points of y. - */ - virtual void handleTouchesBegin(int num, intptr_t ids[], float xs[], float ys[]); - - /** Touch events are handled by default; if you want to customize your handlers, please override this function. - * - * @param num The number of touch. - * @param ids The identity of the touch. - * @param xs The points of x. - * @param ys The points of y. - */ - virtual void handleTouchesMove(int num, intptr_t ids[], float xs[], float ys[]); - - /** Touch events are handled by default; if you want to customize your handlers, please override this function. - * - * @param num The number of touch. - * @param ids The identity of the touch. - * @param xs The points of x. - * @param ys The points of y. - * @param fs The force of 3d touches. - # @param ms The maximum force of 3d touches - */ - virtual void handleTouchesMove(int num, intptr_t ids[], float xs[], float ys[], float fs[], float ms[]); - - /** Touch events are handled by default; if you want to customize your handlers, please override this function. - * - * @param num The number of touch. - * @param ids The identity of the touch. - * @param xs The points of x. - * @param ys The points of y. - */ - virtual void handleTouchesEnd(int num, intptr_t ids[], float xs[], float ys[]); - - /** Touch events are handled by default; if you want to customize your handlers, please override this function. - * - * @param num The number of touch. - * @param ids The identity of the touch. - * @param xs The points of x. - * @param ys The points of y. - */ - virtual void handleTouchesCancel(int num, intptr_t ids[], float xs[], float ys[]); - - /** Set window icon (implemented for windows and linux). - * - * @param filename A path to image file, e.g., "icons/cusom.png". - */ - virtual void setIcon(std::string_view /*filename*/) const {}; - - /** Set window icon (implemented for windows and linux). - * Best icon (based on size) will be auto selected. - * - * @param filelist The array contains icons. - */ - virtual void setIcon(std::span /*filelist*/) const {}; - - /** Set default window icon (implemented for windows and linux). - * On windows it will use icon from .exe file (if included). - * On linux it will use default window icon. - */ - virtual void setDefaultIcon() const {}; - - /** - * Get the render view port rectangle. - * - * @return Return the render view port rectangle. - */ - const Rect& getViewportRect() const; - - /** - * Get list of all active touches. - * - * @return A list of all active touches. - */ - std::vector getAllTouches() const; - - /** - * Get scale factor of the horizontal direction. - * - * @return Scale factor of the horizontal direction. - */ - float getScaleX() const; - - /** - * Get scale factor of the vertical direction. - * - * @return Scale factor of the vertical direction. - */ - float getScaleY() const; - - /** Returns the current Resolution policy. - * - * @return The current Resolution policy. - */ - ResolutionPolicy getResolutionPolicy() const { return _resolutionPolicy; } - - /** - * @brief Get the Native Window object - * - * @return void* - * win32: HWND - * winrt: PresentTarget* - * linux: x11 or wayland window - * macOS: NSWindow* - * iOS: UIWindow* - */ - virtual void* getNativeWindow() const { return nullptr; } - - /** - * @brief Get the Native Display object - * - * @return void* - * linux: x11 or wayland display - * macOS: NSGLContext* - * iOS/tvOS: EARenderView* - */ - virtual SurfaceHandle getNativeDisplay() const { return nullptr; } - - /** - * @brief Get the Window Platform object - * - * @return WindowPlatform - */ - virtual WindowPlatform getWindowPlatform() const { return WindowPlatform::Unknown; }; - - /** - * Renders a Scene with a Renderer - * This method is called directly by the Director - */ - void renderScene(Scene* scene, Renderer* renderer); - - /** - * Enable or disable interactions. - * When disabled, it prevents touches to be dispatched and will cancel current touches - */ - void setInteractive(bool interactive); -#ifdef AX_ENABLE_VR - void setVR(std::unique_ptr&& impl); - const std::unique_ptr& getVR() const { return _vrRenderer; } -#endif - - /** - * @brief Updates the render surface size (framebuffer/render target) and synchronizes related view parameters. - * - * This method performs the following actions: - * - Sets `_renderSize` to the specified dimensions; - * - On mobile platforms (Android/iOS), `_windowSize` is synchronized to match `_renderSize`; - * - On desktop platforms, `_windowSize` is only initialized to `_renderSize` if it hasn't been set yet; - * - If `_designResolutionSize` is unset (`Vec2::ZERO`), it is initialized to `_renderSize`; - * - Calls `updateDesignResolution()` to recalculate `_viewScale` and `_viewportRect` based on the current - * `ResolutionPolicy`, and updates the Director's canvas size and projection matrix accordingly. - * - * @param width The target width of the render surface. Its meaning depends on `updateFlag`: - * it may represent the framebuffer width, logical window width, or design resolution width. - * @param height The target height of the render surface. Its meaning depends on `updateFlag`: - * it may represent the framebuffer height, logical window height, or design resolution height. - * @param updateFlag Optional flags to control which parts of the view should be updated. - * Defaults to `SurfaceUpdateFlag::AllUpdates`. - * - * @warning This method may initialize `_windowSize` and `_designResolutionSize` on first invocation. - * @note No update will occur if the given size is (0, 0). - * - * @internal This method is intended for internal use by platform-specific window or surface managers. - * It should be called when the native surface size changes (e.g., orientation change, resize event). - * - * @see updateDesignResolution(), setDesignResolutionSize() - */ - [[internal]] void updateRenderSurface(float width, float height, uint8_t updateFlag); - -protected: - float transformInputX(float x) { return (x - _viewportRect.origin.x) / _viewScale.x; } - float transformInputY(float y) { return (y - _viewportRect.origin.y) / _viewScale.y; } - - void maybeDispatchResizeEvent(uint8_t updateFlag); - - /** - * @brief Callback invoked after the RenderView size has changed and all related updates are complete. - * - * This method is called once the RenderView's dimensions have been updated and - * all dependent states (such as viewport, scaling, and layout) are ready. - * It serves as a notification hook for any components that need to respond - * to the final, settled render size. - */ - void onSurfaceResized(); - - void setScissorRect(float x, float y, float w, float h); - const ScissorRect& getScissorRect() const; - - /** - * queue a priority operation in render thread for non-PC platforms, even through app in background - * invoked by Director - */ - virtual void queueOperation(AsyncOperation op, void* param = nullptr); - - void updateDesignResolution(); - - void handleTouchesOfEndOrCancel(EventTouch::EventCode eventCode, int num, intptr_t ids[], float xs[], float ys[]); - - Vec2 _renderSize; - // The window size aka logic size, may scaled by windowZoomFactor in high DPI display - Vec2 _windowSize; - // resolution size, it is the size appropriate for the app resources. - Vec2 _designResolutionSize; - // the view port size - Rect _viewportRect; - // the view name - std::string _viewName; - - Vec2 _viewScale; - ResolutionPolicy _resolutionPolicy; - - // Flags indicating whether the window or framebuffer size was updated. - // On desktop platforms, callback order is: framebufferSize => windowSize. - // On WebAssembly, the order is reversed: windowSize => framebufferSize. - uint8_t _surfaceUpdateFlags{0}; - - bool _isResolutionUpdateLocked{false}; - -#ifdef AX_ENABLE_VR - std::unique_ptr _vrRenderer{nullptr}; -#endif - -private: - void cancelAllTouches(); - - bool _interactive; -}; - -// end of platform group -/// @} - -} // namespace ax diff --git a/axmol/platform/RenderViewCore.cpp b/axmol/platform/RenderViewCore.cpp new file mode 100644 index 000000000000..9875bfbd0771 --- /dev/null +++ b/axmol/platform/RenderViewCore.cpp @@ -0,0 +1,326 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "axmol/platform/RenderViewCore.h" + +#include "axmol/base/PointerEvent.h" +#include "axmol/base/Director.h" +#include "axmol/base/EventDispatcher.h" +#include "axmol/base/InputSystem.h" +#include "axmol/scene/Camera.h" +#include "axmol/scene/Scene.h" +#include "axmol/renderer/Renderer.h" + +namespace ax +{ +RenderViewCore::RenderViewCore() + : _windowSize(0, 0) + , _designResolutionSize(0, 0) + , _viewScale(Vec2::ONE) + , _resolutionPolicy(ResolutionPolicy::UNKNOWN) +{} + +RenderViewCore::~RenderViewCore() {} + +void RenderViewCore::pollEvents() {} + +void RenderViewCore::updateDesignResolution() +{ + if (_renderSize.width > 0 && _renderSize.height > 0 && _designResolutionSize.width > 0 && + _designResolutionSize.height > 0) + { + _viewScale = _renderSize / _designResolutionSize; + + if (_resolutionPolicy == ResolutionPolicy::NO_BORDER) + { + _viewScale.x = _viewScale.y = (std::max)(_viewScale.x, _viewScale.y); + } + + else if (_resolutionPolicy == ResolutionPolicy::SHOW_ALL) + { + _viewScale.x = _viewScale.y = (std::min)(_viewScale.x, _viewScale.y); + } + + else if (_resolutionPolicy == ResolutionPolicy::FIXED_HEIGHT) + { + _viewScale.x = _viewScale.y; + _designResolutionSize.width = ceilf(_renderSize.width / _viewScale.x); + } + + else if (_resolutionPolicy == ResolutionPolicy::FIXED_WIDTH) + { + _viewScale.y = _viewScale.x; + _designResolutionSize.height = ceilf(_renderSize.height / _viewScale.y); + } + + // calculate the rect of viewport + float viewportW = _designResolutionSize.width * _viewScale.x; + float viewportH = _designResolutionSize.height * _viewScale.y; + + _viewportRect.setRect((_renderSize.width - viewportW) / 2, (_renderSize.height - viewportH) / 2, viewportW, + viewportH); + + // reset director's member variables to fit visible rect + auto director = Director::getInstance(); + director->setCanvasSize(getDesignResolutionSize()); + director->setProjection(director->getProjection()); + } +} + +void RenderViewCore::setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy) +{ + AXASSERT(resolutionPolicy != ResolutionPolicy::UNKNOWN, "should set resolutionPolicy"); + + if (width == 0.0f || height == 0.0f) + { + return; + } + + _designResolutionSize.set(width, height); + _resolutionPolicy = resolutionPolicy; + + if (!_isResolutionUpdateLocked) + updateDesignResolution(); +} + +const Vec2& RenderViewCore::getDesignResolutionSize() const +{ + return _designResolutionSize; +} + +void RenderViewCore::updateRenderSurface(float width, float height, uint8_t updateFlag) +{ + Vec2 value{width, height}; + + if (updateFlag & SurfaceUpdateFlag::WindowSizeChanged) + _windowSize = value; + + if (updateFlag & SurfaceUpdateFlag::RenderSizeChanged) + { + _isResolutionUpdateLocked = true; + + _renderSize = value; + + // If designResolutionSize hasn't been set, default to renderSize + if (_designResolutionSize.equals(Vec2::ZERO)) + _designResolutionSize = value; + + // Notify the application that the screen size has changed. + // This gives the user a chance to re-layout scene content or reset designResolutionSize if needed. + if (!(updateFlag & SurfaceUpdateFlag::SilentUpdate)) + ax::Application::getInstance()->applicationScreenSizeChanged(width, height); + + // then we update resolution and viewport + updateDesignResolution(); + + _isResolutionUpdateLocked = false; + } + + // check does all updateed + maybeDispatchResizeEvent(updateFlag); +} + +void RenderViewCore::maybeDispatchResizeEvent(uint8_t updateFlag) +{ + const bool silentUpdate = (updateFlag & SurfaceUpdateFlag::SilentUpdate) != 0; + updateFlag &= ~SurfaceUpdateFlag::SilentUpdate; // Remove temporary flag + + _surfaceUpdateFlags |= updateFlag; + + constexpr uint8_t requiredFlags = SurfaceUpdateFlag::WindowSizeChanged | SurfaceUpdateFlag::RenderSizeChanged; + + const bool readyToDispatch = (_surfaceUpdateFlags == requiredFlags); + + if (readyToDispatch) + { + _surfaceUpdateFlags = 0; + if (!silentUpdate) + onSurfaceResized(); + } +} + +Rect RenderViewCore::getVisibleRect() const +{ + Rect ret; + ret.size = getVisibleSize(); + ret.origin = getVisibleOrigin(); + return ret; +} + +Rect RenderViewCore::getSafeAreaRect() const +{ + return getVisibleRect(); +} + +Vec2 RenderViewCore::getVisibleSize() const +{ + if (_resolutionPolicy == ResolutionPolicy::NO_BORDER) + { + return Vec2(_renderSize.width / _viewScale.x, _renderSize.height / _viewScale.y); + } + else + { + return _designResolutionSize; + } +} + +Vec2 RenderViewCore::getVisibleOrigin() const +{ + if (_resolutionPolicy == ResolutionPolicy::NO_BORDER) + { + return Vec2((_designResolutionSize.width - _renderSize.width / _viewScale.x) / 2, + (_designResolutionSize.height - _renderSize.height / _viewScale.y) / 2); + } + else + { + return Vec2::ZERO; + } +} + +void RenderViewCore::setViewportInPoints(float x, float y, float w, float h) +{ + Viewport vp; + vp.x = (int)(x * _viewScale.x + _viewportRect.origin.x); + vp.y = (int)(y * _viewScale.y + _viewportRect.origin.y); + vp.width = (unsigned int)(w * _viewScale.x); + vp.height = (unsigned int)(h * _viewScale.y); + Camera::setDefaultViewport(vp); +} + +void RenderViewCore::setScissorInPoints(float x, float y, float w, float h) +{ + setScissorRect((int)(x * _viewScale.x + _viewportRect.origin.x), (int)(y * _viewScale.y + _viewportRect.origin.y), + (unsigned int)(w * _viewScale.y), (unsigned int)(h * _viewScale.y)); +} + +Rect RenderViewCore::getScissorInPoints() const +{ + auto& rect = getScissorRect(); + + float x = (rect.x - _viewportRect.origin.x) / _viewScale.x; + float y = (rect.y - _viewportRect.origin.y) / _viewScale.y; + float w = rect.width / _viewScale.x; + float h = rect.height / _viewScale.y; + return Rect(x, y, w, h); +} + +bool RenderViewCore::isScissorEnabled() +{ + auto renderer = Director::getInstance()->getRenderer(); + return renderer->getScissorTest(); +} + +void RenderViewCore::setViewName(std::string_view viewname) +{ + _viewName = viewname; +} + +std::string_view RenderViewCore::getViewName() const +{ + return _viewName; +} + +const Rect& RenderViewCore::getViewportRect() const +{ + return _viewportRect; +} + +void RenderViewCore::onSurfaceResized() +{ + int screenWidth = static_cast(_renderSize.width); + int screenHeight = static_cast(_renderSize.height); + + AXLOGD("RenderViewCore::onSurfaceResized: ({}x{})", screenWidth, screenHeight); + + auto renderer = Director::getInstance()->getRenderer(); + if (renderer) + renderer->updateSurface(getNativeDisplay(), screenWidth, screenHeight); +#ifdef AX_ENABLE_VR + if (_vrRenderer) [[unlikely]] + _vrRenderer->onRenderViewResized(this); +#endif +} + +void RenderViewCore::renderScene(Scene* scene, Renderer* renderer) +{ + AXASSERT(scene, "Invalid Scene"); + AXASSERT(renderer, "Invalid Renderer"); + +#ifdef AX_ENABLE_VR + if (_vrRenderer) [[unlikely]] + { + _vrRenderer->render(scene, renderer); + return; + } +#endif + + scene->render(renderer, Mat4::IDENTITY, nullptr); +} + +void RenderViewCore::setScissorRect(float x, float y, float w, float h) +{ +#ifdef AX_ENABLE_VR + if (_vrRenderer) [[unlikely]] + { + _vrRenderer->setScissorRect(x, y, w, h); + return; + } +#endif + + Director::getInstance()->getRenderer()->setScissorRect(x, y, w, h); +} + +const ScissorRect& RenderViewCore::getScissorRect() const +{ +#ifdef AX_ENABLE_VR + if (_vrRenderer) [[unlikely]] + return _vrRenderer->getScissorRect(); +#endif + + return Director::getInstance()->getRenderer()->getScissorRect(); +} + +#ifdef AX_ENABLE_VR +void RenderViewCore::setVR(std::unique_ptr&& impl) +{ + if (_vrRenderer != impl) + { + if (_vrRenderer) + { + _vrRenderer->cleanup(); + _vrRenderer.reset(); + } + + if (impl) + impl->init(this); + + _vrRenderer = std::move(impl); + } +} +#endif + +} // namespace ax diff --git a/axmol/platform/RenderViewCore.h b/axmol/platform/RenderViewCore.h new file mode 100644 index 000000000000..4ea9a4182727 --- /dev/null +++ b/axmol/platform/RenderViewCore.h @@ -0,0 +1,520 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "axmol/base/Types.h" +#include "axmol/base/PointerEvent.h" +#include "axmol/base/KeyboardEvent.h" +#include "axmol/vr/VRBase.h" + +#include +#include +#include + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) +# include +#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) */ + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) || (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) +# define AX_ICON_SET_SUPPORT true +#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) || (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) */ + +namespace ax +{ +class Scene; +class Renderer; +class Director; + +using SurfaceHandle = rhi::SurfaceHandle; + +/** There are some Resolution Policy for Adapt to the screen. */ +enum class ResolutionPolicy +{ + /** The entire application is visible in the specified area without trying to preserve the original aspect ratio. + * Distortion can occur, and the application may appear stretched or compressed. + */ + EXACT_FIT, + /** The entire application fills the specified area, without distortion but possibly with some cropping, + * while maintaining the original aspect ratio of the application. + */ + NO_BORDER, + /** The entire application is visible in the specified area without distortion while maintaining the original + * aspect ratio of the application. Borders can appear on two sides of the application. + */ + SHOW_ALL, + /** The application takes the height of the design resolution size and modifies the width of the internal + * canvas so that it fits the aspect ratio of the device. + * No distortion will occur however you must make sure your application works on different + * aspect ratios. + */ + FIXED_HEIGHT, + /** The application takes the width of the design resolution size and modifies the height of the internal + * canvas so that it fits the aspect ratio of the device. + * No distortion will occur however you must make sure your application works on different + * aspect ratios. + */ + FIXED_WIDTH, + + UNKNOWN, +}; + +/** + * @addtogroup platform + * @{ + */ + +enum class WindowPlatform +{ + Unknown, // Unknown or unsupported platform + Win32, // Windows desktop applications using HWND + CoreWindow, // UWP or Xbox applications using CoreWindow/AppWindow + Cocoa, // macOS applications using NSWindow + X11, // Linux applications using the X11 window system + Wayland, // Linux applications using the Wayland protocol + UIKit, // iOS/tvOS applications using UIView/UIWindow + Android, // Android applications using SurfaceView or native window + Web // WebAssembly applications using HTML canvas or DOM +}; + +/** + * @brief By RenderViewCore you can operate the frame information of EGL view through some function. + */ +class AX_DLL RenderViewCore : public Object +{ + friend class Director; + +public: + enum SurfaceUpdateFlag : uint8_t + { + WindowSizeChanged = 1, // Indicates that window size has changed + RenderSizeChanged = 1 << 1, // Indicates that render surface size has changed + SilentUpdate = 1 << 2, // Temporary flag: suppresses event dispatch for this update only. + // Should be stripped before accumulating into persistent state. + AllUpdates = WindowSizeChanged | RenderSizeChanged, + AllUpdatesSilently = AllUpdates | SilentUpdate + }; + + /** + */ + RenderViewCore(); + /** + * @lua NA + */ + virtual ~RenderViewCore(); + + /** Force destroying EGL view, subclass must implement this method. + * + * @lua endToLua + */ + virtual void end() = 0; + + /** Get whether graphics context is ready, subclass must implement this method. */ + virtual bool isGfxContextReady() = 0; + + /** Exchanges the front and back buffers, subclass must implement this method. */ + virtual void swapBuffers() = 0; + + /** Open or close IME keyboard , subclass must implement this method. + * + * @param open Open or close IME keyboard. + */ + virtual void setIMEKeyboardState(bool open) = 0; + + /** When the window is closed, it will return false if the platforms is Ios or Android. + * If the platforms is windows or Mac,it will return true. + * + * @return In ios and android it will return false,if in windows or Mac it will return true. + */ + virtual bool windowShouldClose() { return false; }; + + /** Polls the events. */ + virtual void pollEvents(); + + virtual Vec2 getNativeWindowSize() const { return getWindowSize(); } + + /** + * Get the zoomed window size + * In general, it returns the screen size since the view is a fullscreen view. + * + * @return The window size (aka logic size) + */ + const Vec2& getWindowSize() const { return _windowSize; } + + /** + * Set the zoomed window size + * + * @param width The width of the fram size. + * @param height The height of the fram size. + */ + virtual void setWindowSize(float, float) {} + + /** Set zoom factor for frame. This methods are for + * debugging big resolution (e.g.new ipad) app on desktop. + * + * @param zoomFactor The zoom factor for frame. + */ + virtual void setWindowZoomFactor(float /*zoomFactor*/) {} + + /** Get zoom factor for frame. This methods are for + * debugging big resolution (e.g.new ipad) app on desktop. + * + * @return The zoom factor for frame. + */ + virtual float getWindowZoomFactor() const { return 1.0; } + + const Vec2& getRenderSize() const { return _renderSize; } + +#ifndef _AX_GEN_SCRIPT_BINDINGS + /** + * implicit deprecated APIs, use getWindowSize instead + */ + AX_DEPRECATED(3.0) const Vec2& getFrameSize() const { return getWindowSize(); } + AX_DEPRECATED(3.0) void setFrameSize(float width, float height) { setWindowSize(width, height); } + AX_DEPRECATED(3.0) float getFrameZoomFactor() const { return getWindowZoomFactor(); } + AX_DEPRECATED(3.0) void setFrameZoomFactor(float zoomFactor) { setWindowZoomFactor(zoomFactor); } +#endif + + /** + * Hide or Show the mouse cursor if there is one. + * + * @param isVisible Hide or Show the mouse cursor if there is one. + */ + virtual void setCursorVisible(bool /*isVisible*/) {} + + /** Get axmol render scale. + * + * @return The render scale fbSize/windowSize aka backing scale factor + * + * Notes: + * - On mobile platforms, this value is always 1.0f (no DPI scaling). + * - On PC platforms, when renderScaleMode == Physical, this value usually + * reflects the monitor's DPI scaling factor (e.g. Windows HiDPI). + */ + float getRenderScale() const { return _renderScale; } + + /** + * Get the visible area size of render viewport. + * + * @return The visible area size of render viewport. + */ + virtual Vec2 getVisibleSize() const; + + /** + * Get the visible origin point of render viewport. + * + * @return The visible origin point of render viewport. + */ + virtual Vec2 getVisibleOrigin() const; + + /** + * Get the visible rectangle of render viewport. + * + * @return The visible rectangle of render viewport. + */ + virtual Rect getVisibleRect() const; + + /** + * Gets safe area rectangle + */ + virtual Rect getSafeAreaRect() const; + + /** + * Set the design resolution size. + * @param width Design resolution width. + * @param height Design resolution height. + * @param resolutionPolicy The resolution policy desired, you may choose: + * [1] EXACT_FIT Fill screen by stretch-to-fit: if the design resolution ratio of width to + * height is different from the screen resolution ratio, your game view will be stretched. [2] NO_BORDER Full screen + * without black border: if the design resolution ratio of width to height is different from the screen resolution + * ratio, two areas of your game view will be cut. [3] SHOW_ALL Full screen with black border: if the design + * resolution ratio of width to height is different from the screen resolution ratio, two black borders will be + * shown. + * @remark For applications with a static design resolution, this method should typically be called only once during + * initialization. + */ + virtual void setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy); + + /** Get design resolution size. + * Default resolution size is the same as 'getWindowSize'. + * + * @return The design resolution size. + */ + virtual const Vec2& getDesignResolutionSize() const; + + /** + * Set render view port rectangle with points. + * + * @param x Set the points of x. + * @param y Set the points of y. + * @param w Set the width of the view port + * @param h Set the Height of the view port. + */ + virtual void setViewportInPoints(float x, float y, float w, float h); + + /** + * Set Scissor rectangle with points. + * + * @param x Set the points of x. + * @param y Set the points of y. + * @param w Set the width of the view port + * @param h Set the Height of the view port. + */ + virtual void setScissorInPoints(float x, float y, float w, float h); + + /** + * Get whether GL_SCISSOR_TEST is enable. + * + * @return Whether GL_SCISSOR_TEST is enable. + */ + virtual bool isScissorEnabled(); + + /** + * Get the current scissor rectangle. + * + * @return The current scissor rectangle. + */ + virtual Rect getScissorInPoints() const; + + /* + * Sets the view name. will change the window title on desktop platform + * @param viewname A string will be set to the view as name. + */ + virtual void setViewName(std::string_view viewname); + + /** Get the view name. + * + * @return The view name. + */ + std::string_view getViewName() const; + + /** Set window icon (implemented for windows and linux). + * + * @param filename A path to image file, e.g., "icons/cusom.png". + */ + virtual void setIcon(std::string_view /*filename*/) const {}; + + /** Set window icon (implemented for windows and linux). + * Best icon (based on size) will be auto selected. + * + * @param filelist The array contains icons. + */ + virtual void setIcon(std::span /*filelist*/) const {}; + + /** Set default window icon (implemented for windows and linux). + * On windows it will use icon from .exe file (if included). + * On linux it will use default window icon. + */ + virtual void setDefaultIcon() const {}; + + /** + * Get the render view port rectangle. + * + * @return Return the render view port rectangle. + */ + const Rect& getViewportRect() const; + + /** + * Get scale factor of the horizontal direction. + * + * @return Scale factor of the horizontal direction. + */ + float getScaleX() const { return _viewScale.x; } + + /** + * Get scale factor of the vertical direction. + * + * @return Scale factor of the vertical direction. + */ + float getScaleY() const { return _viewScale.y; } + + /* + * Get the scale factor of the horizontal and vertical direction. + * + * @return Scale factor of the horizontal and vertical direction. + */ + const Vec2& getScale() const { return _viewScale; } + + /** Returns the current Resolution policy. + * + * @return The current Resolution policy. + */ + ResolutionPolicy getResolutionPolicy() const { return _resolutionPolicy; } + + /** + * @brief Get the Native Window object + * + * @return void* + * win32: HWND + * winrt: PresentTarget* + * linux: x11 or wayland window + * macOS: NSWindow* + * iOS: UIWindow* + */ + virtual void* getNativeWindow() const { return nullptr; } + + /** + * @brief Get the Native Display object + * + * @return void* + * linux: x11 or wayland display + * macOS: NSGLContext* + * iOS/tvOS: EARenderViewBase* + */ + virtual SurfaceHandle getNativeDisplay() const { return nullptr; } + + /** + * @brief Get the Window Platform object + * + * @return WindowPlatform + */ + virtual WindowPlatform getWindowPlatform() const { return WindowPlatform::Unknown; }; + + /** + * Renders a Scene with a Renderer + * This method is called directly by the Director + */ + void renderScene(Scene* scene, Renderer* renderer); + +#ifdef AX_ENABLE_VR + void setVR(std::unique_ptr&& impl); + const std::unique_ptr& getVR() const { return _vrRenderer; } +#endif + + ////////////////////////////////////////////////////////////////////////// + // System edit menu (copy/cut/paste) API + ////////////////////////////////////////////////////////////////////////// + + /** + * @brief Show the system edit menu (Copy/Cut/Paste) at the given point. + * + * The point is specified in **screen coordinates** (engine pixels), + * not platform view points. Callers that have platform view coordinates must convert by + * dividing by the view's content scale factor before calling. + * + * This method must be called on the main/UI thread. Implementations should + * present the platform's standard edit menu at the requested location and + * route menu actions back to the engine (e.g. via InputSystem). + * + * @note Currently implemented only on iOS. On other platforms this method + * may be a no-op until a platform-specific implementation is provided. + * + * @param screenPoint Target location in screen coordinates. + * @param hasSelection Whether there is a text selection. + * @param hasText Whether the input field has text. + * @param readOnly Whether the current input field is read-only + */ + virtual void showContextMenu(const Vec2& /*screenPoint*/, bool hasText, bool hasSelection, bool readOnly) {} + + /** + * @brief Hide the system edit menu if visible. + * + * Must be safe to call on the main/UI thread. Implementations should dismiss + * any visible system edit menu. + * + * @note Currently implemented only on iOS. On other platforms this method + * may be a no-op until a platform-specific implementation is provided. + */ + virtual void hideContextMenu() {} + + /** + * @brief Updates the render surface size (framebuffer/render target) and synchronizes related view parameters. + * + * This method performs the following actions: + * - Sets `_renderSize` to the specified dimensions; + * - On mobile platforms (Android/iOS), `_windowSize` is synchronized to match `_renderSize`; + * - On desktop platforms, `_windowSize` is only initialized to `_renderSize` if it hasn't been set yet; + * - If `_designResolutionSize` is unset (`Vec2::ZERO`), it is initialized to `_renderSize`; + * - Calls `updateDesignResolution()` to recalculate `_viewScale` and `_viewportRect` based on the current + * `ResolutionPolicy`, and updates the Director's canvas size and projection matrix accordingly. + * + * @param width The target width of the render surface. Its meaning depends on `updateFlag`: + * it may represent the framebuffer width, logical window width, or design resolution width. + * @param height The target height of the render surface. Its meaning depends on `updateFlag`: + * it may represent the framebuffer height, logical window height, or design resolution height. + * @param updateFlag Optional flags to control which parts of the view should be updated. + * Defaults to `SurfaceUpdateFlag::AllUpdates`. + * + * @warning This method may initialize `_windowSize` and `_designResolutionSize` on first invocation. + * @note No update will occur if the given size is (0, 0). + * + * @internal This method is intended for internal use by platform-specific window or surface managers. + * It should be called when the native surface size changes (e.g., orientation change, resize event). + * + * @see updateDesignResolution(), setDesignResolutionSize() + */ + [[internal]] void updateRenderSurface(float width, float height, uint8_t updateFlag); + +protected: + void maybeDispatchResizeEvent(uint8_t updateFlag); + + /** + * @brief Callback invoked after the RenderViewCore size has changed and all related updates are complete. + * + * This method is called once the RenderViewCore's dimensions have been updated and + * all dependent states (such as viewport, scaling, and layout) are ready. + * It serves as a notification hook for any components that need to respond + * to the final, settled render size. + */ + void onSurfaceResized(); + + void setScissorRect(float x, float y, float w, float h); + const ScissorRect& getScissorRect() const; + + void updateDesignResolution(); + + float _renderScale{1.0f}; + + Vec2 _renderSize; + // The window size aka logic size, may scaled by windowZoomFactor in high DPI display + Vec2 _windowSize; + // resolution size, it is the size appropriate for the app resources. + Vec2 _designResolutionSize; + // the view port size + Rect _viewportRect; + // the view name + std::string _viewName; + + Vec2 _viewScale; + ResolutionPolicy _resolutionPolicy; + + // Flags indicating whether the window or framebuffer size was updated. + // On desktop platforms, callback order is: framebufferSize => windowSize. + // On WebAssembly, the order is reversed: windowSize => framebufferSize. + uint8_t _surfaceUpdateFlags{0}; + + bool _isResolutionUpdateLocked{false}; + +#ifdef AX_ENABLE_VR + std::unique_ptr _vrRenderer{nullptr}; +#endif +}; + +// end of platform group +/// @} + +} // namespace ax diff --git a/axmol/platform/RenderViewImpl.h b/axmol/platform/RenderViewImpl.h deleted file mode 100644 index aa7c96c1f02c..000000000000 --- a/axmol/platform/RenderViewImpl.h +++ /dev/null @@ -1,36 +0,0 @@ -/**************************************************************************** -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once -#include "axmol/platform/PlatformConfig.h" - -#if AX_TARGET_PLATFORM == AX_PLATFORM_IOS -# include "axmol/platform/ios/RenderViewImpl-ios.h" -#elif AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID -# include "axmol/platform/android/RenderViewImpl-android.h" -#elif AX_TARGET_PLATFORM == AX_PLATFORM_WINRT -# include "axmol/platform/winrt/RenderViewImpl-winrt.h" -#else -# include "axmol/platform/desktop/RenderViewImpl.h" -#endif diff --git a/axmol/platform/android/Application-android.cpp b/axmol/platform/android/Application-android.cpp index 215d23a562a4..02a3d5a4f4f0 100644 --- a/axmol/platform/android/Application-android.cpp +++ b/axmol/platform/android/Application-android.cpp @@ -39,20 +39,16 @@ static const char* applicationHelperClassName = "dev.axmol.lib.AxmolEngine"; namespace ax { - -// sharedApplication pointer -Application* Application::sm_pSharedApplication = nullptr; - Application::Application() { - CCAssert(!sm_pSharedApplication, ""); - sm_pSharedApplication = this; + CCAssert(!s_axmolApp, ""); + s_axmolApp = this; } Application::~Application() { - CCAssert(this == sm_pSharedApplication, ""); - sm_pSharedApplication = nullptr; + CCAssert(this == s_axmolApp, ""); + s_axmolApp = nullptr; } int Application::run() @@ -71,15 +67,6 @@ void Application::setAnimationInterval(float interval) JniHelper::callStaticVoidMethod("dev/axmol/lib/AxmolPlayer", "setAnimationInterval", interval); } -////////////////////////////////////////////////////////////////////////// -// static member function -////////////////////////////////////////////////////////////////////////// -Application* Application::getInstance() -{ - CCAssert(sm_pSharedApplication, ""); - return sm_pSharedApplication; -} - const char* Application::getCurrentLanguageCode() { static char code[3] = {0}; @@ -114,6 +101,11 @@ bool Application::openURL(std::string_view url) return JniHelper::callStaticBooleanMethod(applicationHelperClassName, "openURL", url); } +void Application::postBoundaryTaskSignal() +{ + JniHelper::callStaticVoidMethod(applicationHelperClassName, "postBoundaryTaskSignal"); +} + } // namespace ax #undef LOGD diff --git a/axmol/platform/android/Application-android.h b/axmol/platform/android/Application-android.h index 53267d43740a..f7b0644c213a 100644 --- a/axmol/platform/android/Application-android.h +++ b/axmol/platform/android/Application-android.h @@ -27,12 +27,12 @@ THE SOFTWARE. #pragma once #include "axmol/platform/Common.h" -#include "axmol/platform/ApplicationBase.h" +#include "axmol/platform/ApplicationCore.h" namespace ax { -class AX_DLL Application : public ApplicationBase +class AX_DLL Application : public ApplicationCore { public: /** @@ -54,12 +54,6 @@ class AX_DLL Application : public ApplicationBase */ int run(); - /** - @brief Get current application instance. - @return Current application instance pointer. - */ - static Application* getInstance(); - /** @brief Get current language config @return Current language config @@ -90,7 +84,7 @@ class AX_DLL Application : public ApplicationBase bool openURL(std::string_view url) override; protected: - static Application* sm_pSharedApplication; + void postBoundaryTaskSignal() override; }; } // namespace ax diff --git a/axmol/platform/android/Device-android.cpp b/axmol/platform/android/Device-android.cpp index 86ddfea058c5..30297565b9fa 100644 --- a/axmol/platform/android/Device-android.cpp +++ b/axmol/platform/android/Device-android.cpp @@ -38,6 +38,24 @@ static const char* deviceHelperClassName = "dev.axmol.lib.AxmolEngine"; namespace ax { +void Device::getClipboardText(std::function callback) +{ + if (!callback) + return; + std::string text = JniHelper::callStaticStringMethod(deviceHelperClassName, "getClipboardText"); + callback(text); +} + +void Device::setClipboardText(std::string_view text) +{ + JniHelper::callStaticVoidMethod(deviceHelperClassName, "setClipboardText", std::string{text}.c_str()); +} + +void Device::clearClipboard() +{ + JniHelper::callStaticVoidMethod(deviceHelperClassName, "clearClipboard"); +} + int Device::getDPI() { static int dpi = -1; diff --git a/axmol/platform/android/RenderViewImpl-android.cpp b/axmol/platform/android/RenderView-android.cpp similarity index 85% rename from axmol/platform/android/RenderViewImpl-android.cpp rename to axmol/platform/android/RenderView-android.cpp index a7ae93ea241b..870ace6fc6ba 100644 --- a/axmol/platform/android/RenderViewImpl-android.cpp +++ b/axmol/platform/android/RenderView-android.cpp @@ -24,7 +24,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/platform/android/RenderViewImpl-android.h" +#include "axmol/platform/android/RenderView-android.h" #include "axmol/base/Director.h" #include "axmol/base/Macros.h" #include "axmol/platform/android/jni/JniHelper.h" @@ -49,12 +49,12 @@ extern ANativeWindow* axmolGetANativeWindow(); namespace ax { -RenderViewImpl* RenderViewImpl::createWithRect(std::string_view viewName, - const Rect& rect, - float frameZoomFactor, - bool resizable) +RenderView* RenderView::createWithRect(std::string_view viewName, + const Rect& rect, + float frameZoomFactor, + bool resizable) { - auto ret = new RenderViewImpl; + auto ret = new RenderView; if (ret && ret->initWithRect(viewName, rect, frameZoomFactor, resizable)) { ret->autorelease(); @@ -64,9 +64,9 @@ RenderViewImpl* RenderViewImpl::createWithRect(std::string_view viewName, return nullptr; } -RenderViewImpl* RenderViewImpl::create(std::string_view viewName) +RenderView* RenderView::create(std::string_view viewName) { - auto ret = new RenderViewImpl; + auto ret = new RenderView; if (ret && ret->initWithFullScreen(viewName)) { ret->autorelease(); @@ -76,9 +76,9 @@ RenderViewImpl* RenderViewImpl::create(std::string_view viewName) return nullptr; } -RenderViewImpl* RenderViewImpl::createWithFullscreen(std::string_view viewName) +RenderView* RenderView::createWithFullscreen(std::string_view viewName) { - auto ret = new RenderViewImpl(); + auto ret = new RenderView(); if (ret && ret->initWithFullScreen(viewName)) { ret->autorelease(); @@ -88,24 +88,24 @@ RenderViewImpl* RenderViewImpl::createWithFullscreen(std::string_view viewName) return nullptr; } -RenderViewImpl::RenderViewImpl() {} +RenderView::RenderView() {} -RenderViewImpl::~RenderViewImpl() {} +RenderView::~RenderView() {} -void* RenderViewImpl::getNativeWindow() const +void* RenderView::getNativeWindow() const { return _nativeWindow; } -SurfaceHandle RenderViewImpl::getNativeDisplay() const +SurfaceHandle RenderView::getNativeDisplay() const { return _nativeDisplay; } -bool RenderViewImpl::initWithRect(std::string_view /*viewName*/, - const Rect& rect, - float /*frameZoomFactor*/, - bool /*resizable*/) +bool RenderView::initWithRect(std::string_view /*viewName*/, + const Rect& rect, + float /*frameZoomFactor*/, + bool /*resizable*/) { updateRenderSurface(rect.size.width, rect.size.height, SurfaceUpdateFlag::AllUpdatesSilently); @@ -117,7 +117,7 @@ bool RenderViewImpl::initWithRect(std::string_view /*viewName*/, return true; } -void RenderViewImpl::recreateVkSurface(bool needUpdateRenderSurface) +void RenderView::recreateVkSurface(bool needUpdateRenderSurface) { #if AX_ENABLE_VK auto _createSurface = [](VkInstance inst, void* window, VkSurfaceKHR* surface) { @@ -154,25 +154,25 @@ void RenderViewImpl::recreateVkSurface(bool needUpdateRenderSurface) #endif } -bool RenderViewImpl::initWithFullScreen(std::string_view viewName) +bool RenderView::initWithFullScreen(std::string_view viewName) { return true; } -bool RenderViewImpl::isGfxContextReady() +bool RenderView::isGfxContextReady() { return (_windowSize.width != 0 && _windowSize.height != 0); } -void RenderViewImpl::end() +void RenderView::end() { JniHelper::callStaticVoidMethod("dev.axmol.lib.AxmolEngine", "onExit"); release(); } -void RenderViewImpl::swapBuffers() {} +void RenderView::swapBuffers() {} -void RenderViewImpl::setIMEKeyboardState(bool bOpen) +void RenderView::setIMEKeyboardState(bool bOpen) { if (bOpen) { @@ -184,9 +184,20 @@ void RenderViewImpl::setIMEKeyboardState(bool bOpen) } } -Rect RenderViewImpl::getSafeAreaRect() const +void RenderView::showContextMenu(const Vec2& point, bool hasText, bool hasSelection, bool readOnly) { - Rect safeAreaRect = RenderView::getSafeAreaRect(); + JniHelper::callStaticVoidMethod("dev.axmol.lib.AxmolPlayer", "showContextMenu", point.x, point.y, hasText, + hasSelection, readOnly); +} + +void RenderView::hideContextMenu() +{ + JniHelper::callStaticVoidMethod("dev.axmol.lib.AxmolPlayer", "hideContextMenu"); +} + +Rect RenderView::getSafeAreaRect() const +{ + Rect safeAreaRect = RenderViewCore::getSafeAreaRect(); float deviceAspectRatio = 0; if (safeAreaRect.size.height > safeAreaRect.size.width) { @@ -326,10 +337,4 @@ Rect RenderViewImpl::getSafeAreaRect() const return safeAreaRect; } -void RenderViewImpl::queueOperation(void (*op)(void*), void* param) -{ - JniHelper::callStaticVoidMethod("dev.axmol.lib.AxmolEngine", "queueOperation", (jlong)(uintptr_t)op, - (jlong)(uintptr_t)param); -} - } // namespace ax diff --git a/axmol/platform/android/RenderViewImpl-android.h b/axmol/platform/android/RenderView-android.h similarity index 77% rename from axmol/platform/android/RenderViewImpl-android.h rename to axmol/platform/android/RenderView-android.h index b889be27cc81..b49794a9a587 100644 --- a/axmol/platform/android/RenderViewImpl-android.h +++ b/axmol/platform/android/RenderView-android.h @@ -28,21 +28,21 @@ THE SOFTWARE. #include "axmol/base/Object.h" #include "axmol/math/Math.h" -#include "axmol/platform/RenderView.h" +#include "axmol/platform/RenderViewCore.h" namespace ax { -class AX_DLL RenderViewImpl : public RenderView +class AX_DLL RenderView : public RenderViewCore { public: // static function - static RenderViewImpl* create(std::string_view viewname); - static RenderViewImpl* createWithRect(std::string_view viewName, - const Rect& rect, - float zoomFactor = 1.0f, - bool resizable = false); - static RenderViewImpl* createWithFullscreen(std::string_view viewName); + static RenderView* create(std::string_view viewname); + static RenderView* createWithRect(std::string_view viewName, + const Rect& rect, + float zoomFactor = 1.0f, + bool resizable = false); + static RenderView* createWithFullscreen(std::string_view viewName); bool isGfxContextReady() override; void end() override; @@ -50,8 +50,6 @@ class AX_DLL RenderViewImpl : public RenderView void setIMEKeyboardState(bool bOpen) override; Rect getSafeAreaRect() const override; - void queueOperation(void (*op)(void*), void* param) override; - WindowPlatform getWindowPlatform() const override { return WindowPlatform::Android; } void* getNativeWindow() const override; @@ -60,12 +58,15 @@ class AX_DLL RenderViewImpl : public RenderView [[internal]] void recreateVkSurface(bool needUpdateRenderSurface); protected: - RenderViewImpl(); - virtual ~RenderViewImpl(); + RenderView(); + virtual ~RenderView(); bool initWithRect(std::string_view viewName, const Rect& rect, float zoomFactor, bool resizable = false); bool initWithFullScreen(std::string_view viewName); + void showContextMenu(const Vec2& point, bool hasText, bool hasSelection, bool readOnly) override; + void hideContextMenu() override; + void* _nativeWindow{nullptr}; void* _nativeDisplay{nullptr}; }; diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolActivity.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolActivity.java index 821dedf9615d..59a619c95f70 100644 --- a/axmol/platform/android/java/src/dev/axmol/lib/AxmolActivity.java +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolActivity.java @@ -162,7 +162,7 @@ protected void onCreate(final Bundle savedInstanceState) { this.hideVirtualButton(); // Input mode - window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); onLoadNativeLibraries(); @@ -240,6 +240,8 @@ protected void onDestroy() { public void onWindowFocusChanged(boolean hasFocus) { Log.i(TAG, "onWindowFocusChanged() hasFocus=" + hasFocus); super.onWindowFocusChanged(hasFocus); + + runOnAxmolThread(() -> AxmolPlayer.nativeOnWindowFocusChanged(hasFocus)); } @Override diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolEditBox.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolEditBox.java index 950986c27256..fa73d2757b44 100644 --- a/axmol/platform/android/java/src/dev/axmol/lib/AxmolEditBox.java +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolEditBox.java @@ -26,20 +26,18 @@ of this software and associated documentation files (the "Software"), to deal package dev.axmol.lib; import android.content.Context; -import android.graphics.Color; import android.graphics.Typeface; import android.text.InputFilter; import android.text.InputType; import android.text.method.PasswordTransformationMethod; -import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; -import android.view.View; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.FrameLayout; -public class AxmolEditBox extends EditText { +import androidx.appcompat.widget.AppCompatEditText; + +public class AxmolEditBox extends AppCompatEditText { /** * The user is allowed to enter any text, including line breaks. */ diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolEngine.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolEngine.java index 9046971571a4..3c34e7b87938 100644 --- a/axmol/platform/android/java/src/dev/axmol/lib/AxmolEngine.java +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolEngine.java @@ -27,6 +27,8 @@ of this software and associated documentation files (the "Software"), to deal package dev.axmol.lib; import android.annotation.SuppressLint; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.Rect; @@ -104,6 +106,7 @@ public class AxmolEngine { private static boolean sCompassEnabled; private static boolean sActivityVisible; private static String sPackageName; + private static Context sAppContext = null; private static AppCompatActivity sActivity = null; private static AxmolEngineListener sAxmolEngineListener; private static Set onActivityResultListeners = new LinkedHashSet(); @@ -130,17 +133,19 @@ public static void runOnUiThread(final Runnable r) { sActivity.runOnUiThread(r); } - public static void queueOperation(final long op, final long param) { + @SuppressWarnings("unused") + public static void postBoundaryTaskSignal() { AxmolEngine.runOnAxmolThread(new Runnable() { @Override public void run() { - AxmolEngine.nativeCall0(op, param); + AxmolEngine.nativePerformFrameBoundaryTasks(); } }); } private static boolean sInitialized = false; public static void init(final AppCompatActivity activity) { + sAppContext = activity.getApplicationContext(); sActivity = activity; AxmolEngine.sAxmolEngineListener = (AxmolEngineListener)activity; @@ -218,7 +223,7 @@ public static AppCompatActivity getActivity() { return sActivity; } - public static Context getApplicationContext() { return sActivity != null ? sActivity.getApplicationContext() : null; } + public static Context getApplicationContext() { return sAppContext; } public static void addOnActivityResultListener(OnActivityResultListener listener) { onActivityResultListeners.add(listener); @@ -1006,6 +1011,47 @@ public void onAccuracyChanged(Sensor sensor, int accuracy) {} return result[0]; } + @SuppressWarnings("unused") + public static String getClipboardText() { + try { + ClipboardManager clipboard = (ClipboardManager) sAppContext.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = clipboard.getPrimaryClip(); + if (clip != null && clip.getItemCount() > 0) { + return clip.getItemAt(0).getText().toString(); + } + } catch (Exception e) { + // Safely returns empty string if anything goes wrong or if clipboard is empty + } + return ""; + } + + @SuppressWarnings("unused") + public static void setClipboardText(String text) { + try { + ClipboardManager clipboard = (ClipboardManager) sAppContext.getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText("Axmol Clipboard", text == null ? "" : text)); + } catch (Exception e) { + // Keeps the app alive if the Android clipboard service fails + } + } + + @SuppressWarnings("unused") + public static void clearClipboard() { + try { + ClipboardManager clipboard = (ClipboardManager) sAppContext.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipboard == null) return; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // API 28+ supports clearPrimaryClip() safely + clipboard.clearPrimaryClip(); + } else { + // Fallback for older Android versions + clipboard.setPrimaryClip(ClipData.newPlainText("", "")); + } + } catch (Exception e) { + // Prevents crashes if the system clipboard service fails + } + } + // =========================================================== // Native methods for AxmolEngine // =========================================================== @@ -1020,5 +1066,5 @@ public void onAccuracyChanged(Sensor sensor, int accuracy) {} // private static native void nativeSetAudioDeviceInfo(boolean isSupportLowLatency, int deviceSampleRate, int audioBufferSizeInFames); - public static native void nativeCall0(long func, long ud); + public static native void nativePerformFrameBoundaryTasks(); } diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolInputConnection.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolInputConnection.java new file mode 100644 index 000000000000..8caf6496bca0 --- /dev/null +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolInputConnection.java @@ -0,0 +1,114 @@ +/**************************************************************************** + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + + https://axmol.dev/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package dev.axmol.lib; + +import android.text.Editable; +import android.text.SpannableStringBuilder; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.BaseInputConnection; + +/** + * Custom InputConnection for the Axmol engine. + * Directly bridges the Android Input Method Framework (IMF) text stream to the C++ layer + * without relying on a hidden native EditText view container. + */ +public class AxmolInputConnection extends BaseInputConnection { + public AxmolInputConnection(View targetView, boolean fullEditor) { + super(targetView, fullEditor); + } + + /** + * Invoked when the system fully tears down this connection channel (keyboard goes away). + */ + @Override + public void closeConnection() { + super.closeConnection(); + } + + /** + * Called by the IME when the user commits text (e.g., presses space or selects a suggestion). + */ + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + if (text == null) return false; + final String committedStr = text.toString(); + + // Dispatch to Axmol GL/Vulkan thread safely to prevent OpenGL context crashes + AxmolEngine.runOnAxmolThread(() -> { + AxmolPlayer.nativeInsertText(committedStr); + }); + return true; + } + + /** + * Triggered when the user confirms the composing text (e.g., selects a word from candidate list). + * Overriding this ensures the custom text pipeline remains completely unblocked within IMF state machines. + */ + @Override + public boolean finishComposingText() { + // Return true to formally signal the IME that the custom field processed the finish request + return true; + } + + /** + * Called by the IME when the user hits the backspace key or requests backward deletion. + */ + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + if (beforeLength > 0) { + AxmolEngine.runOnAxmolThread(() -> { + AxmolPlayer.nativeDeleteBackward(beforeLength); + }); + return true; + } + return super.deleteSurroundingText(beforeLength, afterLength); + } + + /** + * Ensure hardware keyboard events or structural soft keys (like backspace/enter/numeric pads) + * can flow normally down into the target rendering surface view. + */ + @Override + public boolean sendKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_DEL) { + AxmolEngine.runOnAxmolThread(() -> { + AxmolPlayer.nativeDeleteBackward(1); + }); + return true; + } + if (keyCode == KeyEvent.KEYCODE_ENTER) { + AxmolEngine.runOnAxmolThread(() -> { + AxmolPlayer.nativeInsertText("\n"); + }); + return true; + } + } + + // Pass standard view key event dispatching directly to the active Host View + return super.sendKeyEvent(event); + } +} diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolKeyboardTracker.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolKeyboardTracker.java new file mode 100644 index 000000000000..cb222f39a897 --- /dev/null +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolKeyboardTracker.java @@ -0,0 +1,147 @@ +/**************************************************************************** + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + + https://axmol.dev/ + ****************************************************************************/ +package dev.axmol.lib; + +import android.annotation.TargetApi; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Build; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; +import java.util.List; + +/** + * Unified Keyboard Lifecycle and Geometry Tracker for Axmol Engine. + * Automatically routes between modern WindowInsets (API 30+) and Legacy Layout fallbacks (API 21+). + */ +public class AxmolKeyboardTracker { + + private static boolean mIsKeyboardVisible = false; + private static int mPreviousHeight = 0; + + /** + * Primary entry point. Register this on your root rendering window view. + * Fully safe to invoke across all API levels (21 to 30+). + */ + public static void register(final View targetView) { + if (targetView == null) return; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Android 11+ (API 30+) High-performance path + Api30Impl.register(targetView); + } else { + // Android 5.0 - 10.0 (API 21-29) Legacy fallback path + registerLegacyTracker(targetView); + } + } + + /** + * Legacy Fallback subsystem using traditional Global Layout Listeners. + * Kept inside the outer class as these APIs are safe down to API 1. + */ + private static void registerLegacyTracker(final View targetView) { + targetView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { + Rect visibleRect = new Rect(); + targetView.getWindowVisibleDisplayFrame(visibleRect); + + int totalScreenHeight = targetView.getRootView().getHeight(); + int activeKeyboardHeight = totalScreenHeight - visibleRect.bottom; + + // Standard safety check threshold (15% of screen height) + boolean isCurrentlyVisible = activeKeyboardHeight > (totalScreenHeight * 0.15); + + if (isCurrentlyVisible) { + if (!mIsKeyboardVisible || activeKeyboardHeight != mPreviousHeight) { + mIsKeyboardVisible = true; + mPreviousHeight = activeKeyboardHeight; + + final int finalHeight = activeKeyboardHeight; + AxmolEngine.runOnAxmolThread(() -> + AxmolPlayer.nativeSoftInputShow( + visibleRect.left, visibleRect.bottom, visibleRect.width(), finalHeight, 0.25f + ) + ); + } + } else { + if (mIsKeyboardVisible) { + mIsKeyboardVisible = false; + mPreviousHeight = 0; + AxmolEngine.runOnAxmolThread(() -> AxmolPlayer.nativeSoftInputHide(0.25f)); + } + } + }); + } + + /** + * Internal container isolating Android 11 (API 30) types. + * This prevents older Android 5.0 runtimes from crashing due to unresolved Class References. + */ + @TargetApi(Build.VERSION_CODES.R) + private static class Api30Impl { + private static float mAnimationDuration = 0.25f; + + static void register(final View targetView) { + // 1. Hook into the IME's underlying physics clock + targetView.setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback( + WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP) { + @Override + public void onPrepare(WindowInsetsAnimation animation) { + if ((animation.getTypeMask() & WindowInsets.Type.ime()) != 0) { + mAnimationDuration = animation.getDurationMillis() / 1000f; + if (mAnimationDuration <= 0) mAnimationDuration = 0.25f; + } + } + + @Override + public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation anim, WindowInsetsAnimation.Bounds bounds) { + return bounds; + } + + @Override + public WindowInsets onProgress(WindowInsets insets, List anims) { + return insets; + } + }); + + // 2. Extract pixel-perfect bounds without system navigation bar interference + targetView.setOnApplyWindowInsetsListener((v, insets) -> { + Insets imeInsets = insets.getInsets(WindowInsets.Type.ime()); + Insets systemBars = insets.getInsets(WindowInsets.Type.systemBars()); + + int keyboardHeight = imeInsets.bottom - systemBars.bottom; + boolean isVisible = insets.isVisible(WindowInsets.Type.ime()); + + Rect visibleRect = new Rect(); + targetView.getWindowVisibleDisplayFrame(visibleRect); + + if (isVisible && keyboardHeight > 0) { + if (!mIsKeyboardVisible || keyboardHeight != mPreviousHeight) { + mIsKeyboardVisible = true; + mPreviousHeight = keyboardHeight; + + final int finalHeight = keyboardHeight; + final float currentDuration = mAnimationDuration; + AxmolEngine.runOnAxmolThread(() -> + AxmolPlayer.nativeSoftInputShow( + visibleRect.left, visibleRect.bottom, visibleRect.width(), finalHeight, currentDuration + ) + ); + } + } else { + if (mIsKeyboardVisible) { + mIsKeyboardVisible = false; + mPreviousHeight = 0; + + final float currentDuration = mAnimationDuration; + AxmolEngine.runOnAxmolThread(() -> AxmolPlayer.nativeSoftInputHide(currentDuration)); + } + } + return v.onApplyWindowInsets(insets); + }); + } + } +} diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolPlayer.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolPlayer.java index 6cdeb24bc390..babfb0f25740 100644 --- a/axmol/platform/android/java/src/dev/axmol/lib/AxmolPlayer.java +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolPlayer.java @@ -35,6 +35,7 @@ of this software and associated documentation files (the "Software"), to deal import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; +import android.view.ActionMode; import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; @@ -46,8 +47,24 @@ public class AxmolPlayer extends FrameLayout { private static final String TAG = AxmolPlayer.class.getSimpleName(); - private final static int HANDLER_OPEN_IME_KEYBOARD = 2; - private final static int HANDLER_CLOSE_IME_KEYBOARD = 3; +// enum class EditAction : int +// { +// Copy = 0, /**< Copy the current selection to the clipboard. */ +// Cut, /**< Copy the current selection to the clipboard and delete it. */ +// Paste, /**< Insert clipboard contents at the caret. */ +// SelectAll /**< Select all editable content in the current field. */ +// }; + + // match with native enum class EditAction in axmol/base/InputDelegate.h + private final static int EDIT_ACTION_COPY = 0; + private final static int EDIT_ACTION_CUT = 1; + private final static int EDIT_ACTION_PASTE = 2; + private final static int EDIT_ACTION_SELECT_ALL = 3; + + private final static int IMM_OPEN_IME_KEYBOARD = 1; + private final static int IMM_CLOSE_IME_KEYBOARD = 2; + private static final int IMM_SHOW_CONTEXT_MENU = 4; + private static final int IMM_HIDE_CONTEXT_MENU = 5; // =========================================================== // Constants @@ -73,13 +90,10 @@ public class AxmolPlayer extends FrameLayout { private static boolean sNativeInitialized = false; - private AxmolEditBox mEditBox; - private TextInputListener mTextInputListener; - private AxmolRenderHost mRenderHost; // GLSurfaceView or SurfaceView private boolean mSoftKeyboardShown = false; - private boolean mMultipleTouchEnabled = true; + private boolean mHideNativeInputBar = true; private boolean mEnableForceDoLayout = false; @@ -93,6 +107,31 @@ public class AxmolPlayer extends FrameLayout { private int mLastSurfaceWidth = 0; private int mLastSurfaceHeight = 0; + // A reference to the active popup menu so we can dismiss it programmatically + private static ActionMode sCurrentActionMode = null; + + // =========================================================== + // High-Performance Touch Input Caching + // =========================================================== + private static final int MAX_TOUCHES = 10; + + // Pre-allocated arrays (Zero GC during touch events) + private final int[] mTouchIds = new int[MAX_TOUCHES]; + private final float[] mTouchXs = new float[MAX_TOUCHES]; + private final float[] mTouchYs = new float[MAX_TOUCHES]; + private final float[] mTouchPressures = new float[MAX_TOUCHES]; + + /** + * Data wrapper containing text component capabilities snapshot from C++ thread. + */ + private static class EditMenuParams { + float x; + float y; + boolean hasText; + boolean hasSelection; + boolean readOnly; + } + @SuppressWarnings("unused") public boolean isSoftKeyboardShown() { return mSoftKeyboardShown; @@ -102,17 +141,17 @@ public void setSoftKeyboardShown(boolean softKeyboardShown) { this.mSoftKeyboardShown = softKeyboardShown; } - @SuppressWarnings("unused") - public boolean isMultipleTouchEnabled() { - return mMultipleTouchEnabled; - } - public void setEnableForceDoLayout(boolean flag) { mEnableForceDoLayout = flag; } - public AxmolEditBox getEditText() { - return this.mEditBox; + public boolean isHideNativeInputBar() { + return mHideNativeInputBar; + } + + @SuppressWarnings("unused") + public void setHideNativeInputBar(boolean flag) { + mHideNativeInputBar = flag; } // =========================================================== @@ -141,51 +180,50 @@ protected void initView(Context ctx) { ); setLayoutParams(frameParams); - // Create hidden EditBox - mEditBox = new AxmolEditBox(ctx); - LayoutParams editParams = new LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT - ); - mEditBox.setLayoutParams(editParams); - mEditBox.setVisibility(View.GONE); - mEditBox.setImeOptions(EditorInfo.IME_ACTION_DONE); - addView(mEditBox); - - // Text input wrapper - mTextInputListener = new TextInputListener(this); - mEditBox.setOnEditorActionListener(mTextInputListener); - // Handler for IME open/close sHandler = new Handler(msg -> { + InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE); + View hostView = (View) mRenderHost; switch (msg.what) { - case HANDLER_OPEN_IME_KEYBOARD: - if (mEditBox != null) { - mEditBox.setVisibility(View.VISIBLE); - if (mEditBox.requestFocus()) { - mEditBox.removeTextChangedListener(mTextInputListener); - mEditBox.setText(""); - final String text = (String) msg.obj; - mEditBox.append(text); - mTextInputListener.setOriginText(text); - mEditBox.addTextChangedListener(mTextInputListener); - InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mEditBox, 0); - Log.d(TAG, "showSoftInput"); - } + case IMM_OPEN_IME_KEYBOARD: + hostView.setFocusable(true); + hostView.setFocusableInTouchMode(true); + hostView.requestFocus(); + + if (imm != null) { + // Target the showSoftInput directly onto the active rendering surface view + imm.showSoftInput(hostView, InputMethodManager.SHOW_IMPLICIT); + Log.d(TAG, "showSoftInput successfully bounded to mRenderHost surface"); + } + break; + case IMM_CLOSE_IME_KEYBOARD: + if (imm != null) { + imm.hideSoftInputFromWindow(getWindowToken(), 0); + Log.d(TAG, "HideSoftInput from AxmolPlayer"); + } + if (ctx instanceof AxmolActivity) { + ((AxmolActivity) ctx).hideVirtualButton(); } break; - case HANDLER_CLOSE_IME_KEYBOARD: - if (mEditBox != null) { - mEditBox.removeTextChangedListener(mTextInputListener); - InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mEditBox.getWindowToken(), 0); - requestFocus(); - mEditBox.setVisibility(View.GONE); - if (ctx instanceof AxmolActivity) { - ((AxmolActivity) ctx).hideVirtualButton(); + case IMM_SHOW_CONTEXT_MENU: + if (msg.obj instanceof EditMenuParams) { + // Safely handle UI drawing on the synchronized Android Main UI thread + showContextMenuOnUIThread((EditMenuParams) msg.obj); + } + break; + case IMM_HIDE_CONTEXT_MENU: + // Forcefully terminate the active context text action mode session safely + if (sCurrentActionMode != null) { + try { + sCurrentActionMode.finish(); + Log.d(TAG, "Native floating ActionMode successfully finished via hide handler."); + } catch (Exception e) { + // Defensive exception trap to handle edge cases where the window is already being torn down by the OS + Log.e(TAG, "Failed to finish sCurrentActionMode gracefully: " + e.getMessage()); + } finally { + // Ensure the static reference is always decoupled to prevent memory leaks + sCurrentActionMode = null; } - Log.d(TAG, "HideSoftInput"); } break; } @@ -212,12 +250,44 @@ private void initGLView(Context ctx) { AxmolSurfaceViewGL surfaceView = new AxmolSurfaceViewGL(this); mRenderHost = surfaceView; addView(surfaceView); + initKeyboardVisibilityTracker(surfaceView); } private void initVulkanView(Context ctx) { AxmolSurfaceViewVK surfaceView = new AxmolSurfaceViewVK(this); mRenderHost = surfaceView; addView(surfaceView); + initKeyboardVisibilityTracker(surfaceView); + } + + private void initKeyboardVisibilityTracker(View targetView) { + // One-liner implementation completely delegating to our unified tracker component + AxmolKeyboardTracker.register(targetView); + } + + @SuppressWarnings("unused") + public AxmolInputConnection createInputConnection(View targetView, EditorInfo outAttrs) { + // Set standard text input type + outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; + + // Modern IMEs require valid selection bounds to activate text input streaming. + // Without these, the IME might assume the field is un-editable and refuse to call commitText. + outAttrs.initialSelStart = 0; + outAttrs.initialSelEnd = 0; + outAttrs.initialCapsMode = 0; + + // Disable Extract Mode (fullscreen keyboard) in landscape orientation + if (isHideNativeInputBar()) { + outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI; + Log.d(TAG, "IME Extract Mode disabled dynamically via hideNativeInputBar=true"); + } + else { + outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; + Log.d(TAG, "IME Extract Mode allowed via hideNativeInputBar=false"); + } + + // Return the clean custom connection channel + return new AxmolInputConnection(targetView, false); } // =========================================================== @@ -293,31 +363,40 @@ public void run() { @SuppressWarnings("unused") public static void openIMEKeyboard() { - AxmolPlayer player = getInstance(); - if (player == null) return; - final Message msg = new Message(); - msg.what = HANDLER_OPEN_IME_KEYBOARD; - msg.obj = player.getContentText(); + final Message msg = Message.obtain(); + msg.what = IMM_OPEN_IME_KEYBOARD; sHandler.sendMessage(msg); } @SuppressWarnings("unused") public static void closeIMEKeyboard() { - final Message msg = new Message(); - msg.what = HANDLER_CLOSE_IME_KEYBOARD; + final Message msg = Message.obtain(); + msg.what = IMM_CLOSE_IME_KEYBOARD; sHandler.sendMessage(msg); } - public void insertText(final String text) { - AxmolPlayer.nativeInsertText(text); - } - - public void deleteBackward(int numChars) { - AxmolPlayer.nativeDeleteBackward(numChars); + @SuppressWarnings("unused") + public static void showContextMenu(float x, float y, boolean hasText, boolean hasSelection, boolean readOnly) { + if (sHandler == null) return; + + EditMenuParams params = new EditMenuParams(); + params.x = x; + params.y = y; + params.hasText = hasText; + params.hasSelection = hasSelection; + params.readOnly = readOnly; + + // Route the call securely across threads from JNI worker thread to Main UI thread + Message msg = Message.obtain(); + msg.what = IMM_SHOW_CONTEXT_MENU; + msg.obj = params; + sHandler.sendMessage(msg); } - private String getContentText() { - return AxmolPlayer.nativeGetContentText(); + @SuppressWarnings("unused") + public static void hideContextMenu() { + if (sHandler == null) return; + sHandler.sendEmptyMessage(IMM_HIDE_CONTEXT_MENU); } // =========================================================== @@ -325,13 +404,7 @@ private String getContentText() { // =========================================================== @Override - public boolean onTouchEvent(final MotionEvent pMotionEvent) { - // these data are used in ACTION_MOVE and ACTION_CANCEL - final int pointerNumber = pMotionEvent.getPointerCount(); - final int[] ids = new int[pointerNumber]; - final float[] xs = new float[pointerNumber]; - final float[] ys = new float[pointerNumber]; - + public boolean onTouchEvent(final MotionEvent motionEvent) { if (mSoftKeyboardShown) { InputMethodManager imm = (InputMethodManager) this.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); View view = ((Activity) this.getContext()).getCurrentFocus(); @@ -342,135 +415,68 @@ public boolean onTouchEvent(final MotionEvent pMotionEvent) { mSoftKeyboardShown = false; } - for (int i = 0; i < pointerNumber; i++) { - ids[i] = pMotionEvent.getPointerId(i); - xs[i] = pMotionEvent.getX(i); - ys[i] = pMotionEvent.getY(i); - } - - switch (pMotionEvent.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_POINTER_DOWN: - final int indexPointerDown = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - if (!mMultipleTouchEnabled && indexPointerDown != 0) { - break; - } - final int idPointerDown = pMotionEvent.getPointerId(indexPointerDown); - final float xPointerDown = pMotionEvent.getX(indexPointerDown); - final float yPointerDown = pMotionEvent.getY(indexPointerDown); + final int action = motionEvent.getActionMasked(); + final int pointerCount = Math.min(motionEvent.getPointerCount(), MAX_TOUCHES); - AxmolEngine.runOnAxmolThread(new Runnable() { - @Override - public void run() { - AxmolPlayer.nativeTouchesBegin(idPointerDown, xPointerDown, yPointerDown); - } - }); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: { + final int index = motionEvent.getActionIndex(); + final int id = motionEvent.getPointerId(index); + final float x = motionEvent.getX(index); + final float y = motionEvent.getY(index); + final float pressure = motionEvent.getPressure(index); // 🔴 Extract pressure + + nativeTouchBegin(id, x, y, pressure); break; + } - case MotionEvent.ACTION_DOWN: - // there are only one finger on the screen - final int idDown = pMotionEvent.getPointerId(0); - final float xDown = xs[0]; - final float yDown = ys[0]; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: { + final int index = motionEvent.getActionIndex(); + final int id = motionEvent.getPointerId(index); + final float x = motionEvent.getX(index); + final float y = motionEvent.getY(index); + final float pressure = motionEvent.getPressure(index); - AxmolEngine.runOnAxmolThread(new Runnable() { - @Override - public void run() { - AxmolPlayer.nativeTouchesBegin(idDown, xDown, yDown); - } - }); - break; + nativeTouchEnd(id, x, y, pressure); - case MotionEvent.ACTION_MOVE: - if (!mMultipleTouchEnabled) { - // handle only touch with id == 0 - for (int i = 0; i < pointerNumber; i++) { - if (ids[i] == 0) { - final int[] idsMove = new int[]{0}; - final float[] xsMove = new float[]{xs[i]}; - final float[] ysMove = new float[]{ys[i]}; - AxmolEngine.runOnAxmolThread(new Runnable() { - @Override - public void run() { - AxmolPlayer.nativeTouchesMove(idsMove, xsMove, ysMove); - } - }); - break; - } - } - } else { - AxmolEngine.runOnAxmolThread(new Runnable() { - @Override - public void run() { - AxmolPlayer.nativeTouchesMove(ids, xs, ys); - } - }); + if (action == MotionEvent.ACTION_UP) { + performClick(); // Accessibility requirement } break; + } - case MotionEvent.ACTION_POINTER_UP: - final int indexPointUp = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - if (!mMultipleTouchEnabled && indexPointUp != 0) { - break; + case MotionEvent.ACTION_MOVE: { + for (int i = 0; i < pointerCount; i++) { + mTouchIds[i] = motionEvent.getPointerId(i); + mTouchXs[i] = motionEvent.getX(i); + mTouchYs[i] = motionEvent.getY(i); + mTouchPressures[i] = motionEvent.getPressure(i); } - final int idPointerUp = pMotionEvent.getPointerId(indexPointUp); - final float xPointerUp = pMotionEvent.getX(indexPointUp); - final float yPointerUp = pMotionEvent.getY(indexPointUp); - - AxmolEngine.runOnAxmolThread(new Runnable() { - @Override - public void run() { - AxmolPlayer.nativeTouchesEnd(idPointerUp, xPointerUp, yPointerUp); - } - }); - break; - - case MotionEvent.ACTION_UP: - // there are only one finger on the screen - final int idUp = pMotionEvent.getPointerId(0); - final float xUp = xs[0]; - final float yUp = ys[0]; - - AxmolEngine.runOnAxmolThread(new Runnable() { - @Override - public void run() { - AxmolPlayer.nativeTouchesEnd(idUp, xUp, yUp); - } - }); + nativeTouchesMove(mTouchIds, mTouchXs, mTouchYs, mTouchPressures, pointerCount); break; + } - case MotionEvent.ACTION_CANCEL: - if (!mMultipleTouchEnabled) { - // handle only touch with id == 0 - for (int i = 0; i < pointerNumber; i++) { - if (ids[i] == 0) { - final int[] idsCancel = new int[]{0}; - final float[] xsCancel = new float[]{xs[i]}; - final float[] ysCancel = new float[]{ys[i]}; - AxmolEngine.runOnAxmolThread(new Runnable() { - @Override - public void run() { - AxmolPlayer.nativeTouchesCancel(idsCancel, xsCancel, ysCancel); - } - }); - break; - } - } - } else { - AxmolEngine.runOnAxmolThread(new Runnable() { - @Override - public void run() { - AxmolPlayer.nativeTouchesCancel(ids, xs, ys); - } - }); + case MotionEvent.ACTION_CANCEL: { + for (int i = 0; i < pointerCount; i++) { + mTouchIds[i] = motionEvent.getPointerId(i); + mTouchXs[i] = motionEvent.getX(i); + mTouchYs[i] = motionEvent.getY(i); + mTouchPressures[i] = motionEvent.getPressure(i); // 🔴 Populate pressure } + nativeTouchesCancel(mTouchIds, mTouchXs, mTouchYs, mTouchPressures, pointerCount); break; + } } - /* - if (BuildConfig.DEBUG) { - AxmolSurfaceViewGL.dumpMotionEvent(pMotionEvent); - } - */ + return true; + } + + // Suppress lint warning only + @Override + public boolean performClick() { + super.performClick(); return true; } @@ -647,6 +653,94 @@ public void run() { // Log.d(TAG, sb.toString()); // } + /** + * Spawns a platform standard Floating ActionMode window mapped directly to specific coordinates. + */ + private void showContextMenuOnUIThread(EditMenuParams p) { + // Prevent overlapping bar windows by closing the older instance first + if (sCurrentActionMode != null) { + sCurrentActionMode.finish(); + } + + if (!(mRenderHost instanceof View)) return; + View targetView = (View) mRenderHost; + + // Use standard Android Callback2 to gain leverage over the absolute positioning rect binding + android.view.ActionMode.Callback2 callback = new android.view.ActionMode.Callback2() { + @Override + public boolean onCreateActionMode(android.view.ActionMode mode, android.view.Menu menu) { + // Populate custom interaction choices dynamically based on security privileges snapshot + if (p.hasSelection) { + menu.add(0, android.R.id.copy, 0, android.R.string.copy); + if (!p.readOnly) { + menu.add(0, android.R.id.cut, 1, android.R.string.cut); + } + } + if (!p.readOnly) { + menu.add(0, android.R.id.paste, 2, android.R.string.paste); + } + if (p.hasText) { + menu.add(0, android.R.id.selectAll, 3, android.R.string.selectAll); + } + return true; + } + + @Override + public boolean onActionItemClicked(android.view.ActionMode mode, android.view.MenuItem item) { + int itemId = item.getItemId(); + // Safe tunnel execution back into the synchronized C++ Axmol processing loop + AxmolEngine.runOnAxmolThread(() -> { + if (itemId == android.R.id.copy) { + AxmolPlayer.nativePerformEditAction(EDIT_ACTION_COPY); + } else if (itemId == android.R.id.cut) { + AxmolPlayer.nativePerformEditAction(EDIT_ACTION_CUT); + } else if (itemId == android.R.id.paste) { + AxmolPlayer.nativePerformEditAction(EDIT_ACTION_PASTE); + } else if (itemId == android.R.id.selectAll) { + AxmolPlayer.nativePerformEditAction(EDIT_ACTION_SELECT_ALL); + } + }); + + mode.finish(); // Dismantle the bar after option is chosen + return true; + } + + @Override + public boolean onPrepareActionMode(android.view.ActionMode mode, android.view.Menu menu) { + return false; + } + + @Override + public void onDestroyActionMode(android.view.ActionMode mode) { + if (sCurrentActionMode == mode) { + sCurrentActionMode = null; + } + } + + /** + * CRUCIAL OVERRIDE: Bound by the OS to fetch the target rect coordinates. + * This explicitly instructs the floating toolbar bubble exactly where to sit. + */ + @Override + public void onGetContentRect(android.view.ActionMode mode, View view, android.graphics.Rect outRect) { + // Define a tiny virtual bounding bounding block surrounding the exact touch spot. + // OutRect coordinates must be mapped relative to the targetView local viewport bounds. + int centerX = (int) p.x; + int centerY = (int) p.y; + int targetRadius = 10; // Simple padding buffer radius around the cursor point + + outRect.set(centerX - targetRadius, centerY - targetRadius, + centerX + targetRadius, centerY + targetRadius); + } + }; + + // Initialize the platform native floating context action mode panel (Android 6.0 M and above) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + sCurrentActionMode = targetView.startActionMode(callback, android.view.ActionMode.TYPE_FLOATING); + Log.d("AxmolPlayer", "Triggered system native Floating ActionMode at focus bounds: X=" + p.x + ", Y=" + p.y); + } + } + // =========================================================== // Native methods for AxmolPlayer @@ -658,23 +752,28 @@ public void run() { public static native void nativeOnPause(); - public static native String nativeGetContentText(); - - public static native void nativeInsertText(String text); + public static native void nativePerformEditAction(int action); - public static native void nativeDeleteBackward(int numChars); + // New signature: Sends full layout boundaries and duration when keyboard opens + public static native void nativeSoftInputShow(float x, float y, float width, float height, float duration); - public static native void nativeTouchesBegin(final int id, final float x, final float y); + // Updated signature: Accepts animation duration when keyboard closes + public static native void nativeSoftInputHide(float duration); - public static native void nativeTouchesEnd(final int id, final float x, final float y); + public static native void nativeInsertText(String text); - public static native void nativeTouchesMove(final int[] ids, final float[] xs, final float[] ys); + public static native void nativeDeleteBackward(int numChars); - public static native void nativeTouchesCancel(final int[] ids, final float[] xs, final float[] ys); + public static native void nativeTouchBegin(int id, float x, float y, float pressure); + public static native void nativeTouchEnd(int id, float x, float y, float pressure); + public static native void nativeTouchesMove(int[] ids, float[] xs, float[] ys, float[] pressures, int size); + public static native void nativeTouchesCancel(int[] ids, float[] xs, float[] ys, float[] pressures, int size); public static native boolean nativeKeyEvent(final int keyCode, boolean isPressed); public static native void nativeOnSurfaceCreated(Object surface, final int width, final int height, boolean isWarmStart); public static native void nativeOnSurfaceChanged(final int width, final int height); + + public static native void nativeOnWindowFocusChanged(final boolean hasFocus); } diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolSurfaceViewGL.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolSurfaceViewGL.java index 7a0887998cd6..0a326329f52d 100644 --- a/axmol/platform/android/java/src/dev/axmol/lib/AxmolSurfaceViewGL.java +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolSurfaceViewGL.java @@ -25,6 +25,7 @@ of this software and associated documentation files (the "Software"), to deal import android.content.Context; import android.util.Log; +import android.view.inputmethod.EditorInfo; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; @@ -44,7 +45,6 @@ public AxmolSurfaceViewGL(AxmolPlayer player) { super(player.getContext()); init(player); } - private void init(AxmolPlayer player) { mPlayer = player; @@ -77,6 +77,27 @@ public void onSurfaceCreated(GL10 gl, EGLConfig config) { setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); } + /** + * Expressly inform the Android IMF that this Player container functions as a text editor, + * which is absolutely mandatory if we do not rely on an internal EditText instance. + */ + @Override + public boolean onCheckIsTextEditor() { + return mPlayer != null; + } + + /** + * Intercepted by Android OS when showSoftInput() is requested. + * Injects custom input connection and disables landscape fullscreen mode. + */ + @Override + public AxmolInputConnection onCreateInputConnection(EditorInfo outAttrs) { + if (mPlayer != null) { + return mPlayer.createInputConnection(this, outAttrs); + } + return null; + } + private class AxmolEGLConfigChooser implements GLSurfaceView.EGLConfigChooser { private int[] mConfigAttributes; private final int EGL_OPENGL_ES2_BIT = 0x04; diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolSurfaceViewVK.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolSurfaceViewVK.java index b7b7dcf2674c..0eabd47671f9 100644 --- a/axmol/platform/android/java/src/dev/axmol/lib/AxmolSurfaceViewVK.java +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolSurfaceViewVK.java @@ -23,9 +23,11 @@ of this software and associated documentation files (the "Software"), to deal ****************************************************************************/ package dev.axmol.lib; +import android.view.KeyEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.util.Log; +import android.view.inputmethod.EditorInfo; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -88,6 +90,27 @@ private void init(AxmolPlayer player) { } } + /** + * Expressly inform the Android IMF that this Player container functions as a text editor, + * which is absolutely mandatory if we do not rely on an internal EditText instance. + */ + @Override + public boolean onCheckIsTextEditor() { + return mPlayer != null; + } + + /** + * Intercepted by Android OS when showSoftInput() is requested. + * Injects custom input connection and disables landscape fullscreen mode. + */ + @Override + public AxmolInputConnection onCreateInputConnection(EditorInfo outAttrs) { + if (mPlayer != null) { + return mPlayer.createInputConnection(this, outAttrs); + } + return null; + } + @Override public void setRenderMode(int mode) { if (mRenderThread != null) { diff --git a/axmol/platform/android/java/src/dev/axmol/lib/AxmolWebView.java b/axmol/platform/android/java/src/dev/axmol/lib/AxmolWebView.java index 586b6e53dfef..8192d947a62b 100644 --- a/axmol/platform/android/java/src/dev/axmol/lib/AxmolWebView.java +++ b/axmol/platform/android/java/src/dev/axmol/lib/AxmolWebView.java @@ -1,5 +1,6 @@ /**************************************************************************** Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -29,6 +30,7 @@ of this software and associated documentation files (the "Software"), to deal import android.util.Log; import android.view.Gravity; import android.webkit.WebChromeClient; +import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; @@ -78,10 +80,14 @@ public AxmolWebView(Context context, int viewTag) { this.setFocusable(true); this.setFocusableInTouchMode(true); - this.getSettings().setSupportZoom(false); + WebSettings settings = getSettings(); - this.getSettings().setDomStorageEnabled(true); - this.getSettings().setJavaScriptEnabled(true); + settings.setSupportZoom(false); + settings.setDomStorageEnabled(true); + settings.setJavaScriptEnabled(true); + + settings.setSupportMultipleWindows(true); + settings.setAllowUniversalAccessFromFileURLs(true); // `searchBoxJavaBridge_` has big security risk. http://jvn.jp/en/jp/JVN53768697 try { @@ -92,7 +98,23 @@ public AxmolWebView(Context context, int viewTag) { } this.setWebViewClient(new AxmolWebViewClient()); - this.setWebChromeClient(new WebChromeClient()); +// this.setWebChromeClient(new WebChromeClient()); + + this.setWebChromeClient(new WebChromeClient() { + @Override + public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) { + String logMsg = consoleMessage.message() + " -- From line " + + consoleMessage.lineNumber() + " of " + + consoleMessage.sourceId(); + + if (consoleMessage.messageLevel() == android.webkit.ConsoleMessage.MessageLevel.ERROR) { + Log.e("AxmolWebView_JS", logMsg); + } else { + Log.d("AxmolWebView_JS", logMsg); + } + return true; + } + }); } public void setJavascriptInterfaceScheme(String scheme) { @@ -161,6 +183,11 @@ public void run() { } }); } + + @Override + public void onPageStarted(WebView view, String url, android.graphics.Bitmap favicon) { + super.onPageStarted(view, url, favicon); + } } public void setWebViewRect(int left, int top, int maxWidth, int maxHeight) { diff --git a/axmol/platform/android/java/src/dev/axmol/lib/TextInputListener.java b/axmol/platform/android/java/src/dev/axmol/lib/TextInputListener.java deleted file mode 100644 index 78ca6a937165..000000000000 --- a/axmol/platform/android/java/src/dev/axmol/lib/TextInputListener.java +++ /dev/null @@ -1,149 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010-2011 cocos2d-x.org -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ****************************************************************************/ -package dev.axmol.lib; - -import android.content.Context; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; - -public class TextInputListener implements TextWatcher, OnEditorActionListener { - // =========================================================== - // Constants - // =========================================================== - - private static final String TAG = TextInputListener.class.getSimpleName(); - - // =========================================================== - // Fields - // =========================================================== - - private final AxmolPlayer mAxmolPlayer; - private String mText; - private String mOriginText; - - // =========================================================== - // Constructors - // =========================================================== - - public TextInputListener(final AxmolPlayer player) { - this.mAxmolPlayer = player; - } - - // =========================================================== - // Getter & Setter - // =========================================================== - - private boolean isFullScreenEdit() { - final TextView textField = this.mAxmolPlayer.getEditText(); - final InputMethodManager imm = (InputMethodManager) textField.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - return imm.isFullscreenMode(); - } - - public void setOriginText(final String pOriginText) { - this.mOriginText = pOriginText; - } - - // =========================================================== - // Methods for/from SuperClass/Interfaces - // =========================================================== - - @Override - public void afterTextChanged(final Editable s) { - if (this.isFullScreenEdit()) { - return; - } - int old_i = 0; - int new_i = 0; - while (old_i < this.mText.length() && new_i < s.length()) { - if (this.mText.charAt(old_i) != s.charAt(new_i)) { - break; - } - old_i += 1; - new_i += 1; - } - - if (old_i < this.mText.length()) { - this.mAxmolPlayer.deleteBackward(this.mText.length() - old_i); - } - - int nModified = s.length() - new_i; - if (nModified > 0) { - final String insertText = s.subSequence(new_i, s.length()).toString(); - this.mAxmolPlayer.insertText(insertText); - } - - this.mText = s.toString(); - } - - @Override - public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) { - this.mText = pCharSequence.toString(); - } - - @Override - public void onTextChanged(final CharSequence pCharSequence, final int start, final int before, final int count) { - - } - - @Override - public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) { - if (this.mAxmolPlayer.getEditText() == pTextView && this.isFullScreenEdit()) { - // user press the action button, delete all old text and insert new text - if (null != mOriginText) { - if (!this.mOriginText.isEmpty()) { - this.mAxmolPlayer.deleteBackward(this.mOriginText.length()); - } - } - - String text = pTextView.getText().toString(); - - if (text != null) { - /* If user input nothing, translate "\n" to engine. */ - if ( text.compareTo("") == 0) { - text = "\n"; - } - - if ( '\n' != text.charAt(text.length() - 1)) { - text += '\n'; - } - } - - final String insertText = text; - this.mAxmolPlayer.insertText(insertText); - - } - - if (pActionID == EditorInfo.IME_ACTION_DONE) { - this.mAxmolPlayer.requestFocus(); - } - return false; - } - -} diff --git a/axmol/platform/android/javaactivity-android.cpp b/axmol/platform/android/javaactivity-android.cpp index 8079521fd1d7..10c9b45f4772 100644 --- a/axmol/platform/android/javaactivity-android.cpp +++ b/axmol/platform/android/javaactivity-android.cpp @@ -24,9 +24,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "axmol/platform/android/Application-android.h" -#include "axmol/platform/android/RenderViewImpl-android.h" +#include "axmol/platform/android/RenderView-android.h" #include "axmol/base/Director.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/EventType.h" #include "axmol/base/EventDispatcher.h" #include "axmol/renderer/TextureCache.h" diff --git a/axmol/platform/android/jni/AxmolAccelerometerJni.cpp b/axmol/platform/android/jni/AxmolAccelerometerJni.cpp index 10d6f04fc945..e9cedbdd4e5e 100644 --- a/axmol/platform/android/jni/AxmolAccelerometerJni.cpp +++ b/axmol/platform/android/jni/AxmolAccelerometerJni.cpp @@ -26,7 +26,7 @@ #include #include "axmol/base/Director.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventAcceleration.h" +#include "axmol/base/AccelerationEvent.h" #define TG3_GRAVITY_EARTH (9.80665f) @@ -42,7 +42,7 @@ Java_dev_axmol_lib_AxmolAccelerometer_onSensorChanged(JNIEnv*, jclass, jfloat x, a.z = -((double)z / TG3_GRAVITY_EARTH); a.timestamp = (double)timeStamp / 1e9; - EventAcceleration event(a); + AccelerationEvent event(a); Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); } } diff --git a/axmol/platform/android/jni/AxmolEngineJni.cpp b/axmol/platform/android/jni/AxmolEngineJni.cpp index 12dbe572fad7..2bc55c07a9ff 100644 --- a/axmol/platform/android/jni/AxmolEngineJni.cpp +++ b/axmol/platform/android/jni/AxmolEngineJni.cpp @@ -33,7 +33,7 @@ THE SOFTWARE. #include "axmol/platform/Application.h" #include "axmol/rhi/DriverContext.h" #include "axmol/base/EventType.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/EventDispatcher.h" #include "axmol/base/text_utils.h" @@ -47,7 +47,7 @@ static void* s_ctx = nullptr; static std::string s_apkPath; -using namespace ax; +extern void _axmolPerformFrameBoundaryTasks(); extern "C" { @@ -57,13 +57,13 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolEngine_nativeInit(JNIEnv* env, jobject context, jobject assetManager) { - JniHelper::setClassLoaderFrom(context); - FileUtilsAndroid::setAssetManagerFromJava(assetManager); + ax::JniHelper::setClassLoaderFrom(context); + ax::FileUtilsAndroid::setAssetManagerFromJava(assetManager); auto app = ax::Application::getInstance(); app->initContextAttrs(); - rhi::DriverContext::makeCurrentDriver(); + ax::rhi::DriverContext::makeCurrentDriver(); } JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolEngine_nativeSetEditTextDialogResult(JNIEnv* env, @@ -94,16 +94,14 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolEngine_nativeSetEditTextDialogRes } } -JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolEngine_nativeCall0(JNIEnv* env, jclass, jlong op, jlong param) +JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolEngine_nativePerformFrameBoundaryTasks(JNIEnv* env, jclass) { - auto operation = reinterpret_cast(static_cast(op)); - if (operation) - operation(reinterpret_cast(static_cast(param))); + _axmolPerformFrameBoundaryTasks(); } JNIEXPORT int JNICALL Java_dev_axmol_lib_AxmolEngine_nativeGetRenderAPI(JNIEnv* env, jclass) { - return (int)rhi::DriverContext::currentDriverType(); + return (int)ax::rhi::DriverContext::currentDriverType(); } JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolEngine_nativeRunOnAxmolThread(JNIEnv* env, jclass, jobject runnable) @@ -111,15 +109,15 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolEngine_nativeRunOnAxmolThread(JNI using jobject_type = std::remove_pointer_t; struct jobject_delete { - void operator()(jobject_type* __ptr) const _NOEXCEPT { JniHelper::getEnv()->DeleteGlobalRef(__ptr); } + void operator()(jobject_type* __ptr) const _NOEXCEPT { ax::JniHelper::getEnv()->DeleteGlobalRef(__ptr); } }; - ax::Director::getInstance()->getScheduler()->runOnAxmolThread( + ax::Director::getInstance()->postTask( [wrap = std::make_shared>(env->NewGlobalRef(runnable))] { - auto curEnv = JniHelper::getEnv(); + auto curEnv = ax::JniHelper::getEnv(); - JniMethodInfo mi; - if (JniHelper::getMethodInfo(mi, "java/lang/Runnable", "run", "()V")) + ax::JniMethodInfo mi; + if (ax::JniHelper::getMethodInfo(mi, "java/lang/Runnable", "run", "()V")) { curEnv->CallVoidMethod(wrap.get()->get(), mi.methodID); } @@ -129,7 +127,7 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolEngine_nativeRunOnAxmolThread(JNI JNIEXPORT jintArray JNICALL Java_dev_axmol_lib_AxmolEngine_nativeGetGLContextAttrs(JNIEnv* env, jclass) { auto app = ax::Application::getInstance(); - const auto& contextAttrs = Application::getContextAttrs(); + const auto& contextAttrs = ax::Application::getContextAttrs(); int tmp[7] = {contextAttrs.redBits, contextAttrs.greenBits, contextAttrs.blueBits, contextAttrs.alphaBits, contextAttrs.depthBits, contextAttrs.stencilBits, @@ -142,6 +140,8 @@ JNIEXPORT jintArray JNICALL Java_dev_axmol_lib_AxmolEngine_nativeGetGLContextAtt } } +namespace ax +{ const char* getApkPath() { if (s_apkPath.empty()) @@ -212,5 +212,7 @@ void conversionEncodingJNI(const char* src, int byteSize, const char* fromCharse } } +} // namespace ax + #undef LOGD #undef LOG_TAG diff --git a/axmol/platform/android/jni/AxmolEngineJni.h b/axmol/platform/android/jni/AxmolEngineJni.h index 9559c9a9971a..a1f6a11b8537 100644 --- a/axmol/platform/android/jni/AxmolEngineJni.h +++ b/axmol/platform/android/jni/AxmolEngineJni.h @@ -29,11 +29,18 @@ THE SOFTWARE. typedef void (*EditTextCallback)(const char* text, void* ctx); +namespace ax +{ + extern const char* getApkPath(); + extern std::string getPackageNameJNI(); + extern int getObbAssetFileDescriptorJNI(const char* path, int64_t* startOffset, int64_t* size); + extern void conversionEncodingJNI(const char* src, int byteSize, const char* fromCharset, char* dst, const char* newCharset); +} // namespace ax diff --git a/axmol/platform/android/jni/AxmolPlayerJni.cpp b/axmol/platform/android/jni/AxmolPlayerJni.cpp index e65c5cde1116..ce80e63ad9c1 100644 --- a/axmol/platform/android/jni/AxmolPlayerJni.cpp +++ b/axmol/platform/android/jni/AxmolPlayerJni.cpp @@ -22,18 +22,20 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/base/IMEDispatcher.h" +#include "axmol/base/InputSystem.h" #include "axmol/base/Director.h" #include "axmol/base/Scheduler.h" #include "axmol/base/EventType.h" -#include "axmol/base/EventCustom.h" +#include "axmol/base/CustomEvent.h" #include "axmol/base/EventDispatcher.h" #include "axmol/platform/Application.h" -#include "axmol/platform/android/RenderViewImpl-android.h" +#include "axmol/platform/android/RenderView-android.h" #include "axmol/base/text_utils.h" #include "axmol/platform/android/jni/JniHelper.h" #include "axmol/rhi/DriverContext.h" #include "axmol/renderer/TextureCache.h" +#include "axmol/tlx/static_vector.hpp" + #include #include @@ -41,6 +43,8 @@ using namespace ax; static ANativeWindow* s_nativeWindow; +static constexpr int AX_MAX_TOUCHES = 10; + ANativeWindow* axmolGetANativeWindow() { return s_nativeWindow; @@ -50,7 +54,7 @@ static void axmolDispatchContextLost(bool isWarmStart) { #if AX_ENABLE_RESTART_APPLICATION_ON_CONTEXT_LOST auto director = ax::Director::getInstance(); - ax::EventCustom recreatedEvent(EVENT_APP_RESTARTING); + ax::CustomEvent recreatedEvent(EVENT_APP_RESTARTING); director->getEventDispatcher()->dispatchEvent(&recreatedEvent, true); // Pop to root scene, replace with an empty scene, and clear all cached data before restarting @@ -65,11 +69,42 @@ static void axmolDispatchContextLost(bool isWarmStart) if (isWarmStart) { auto director = ax::Director::getInstance(); - ax::EventCustom warmStartEvent(EVENT_APP_WARM_START); + ax::CustomEvent warmStartEvent(EVENT_APP_WARM_START); director->getEventDispatcher()->dispatchEvent(&warmStartEvent, true); } } +#define KEYCODE_BACK 0x04 +#define KEYCODE_MENU 0x52 +#define KEYCODE_DPAD_UP 0x13 +#define KEYCODE_DPAD_DOWN 0x14 +#define KEYCODE_DPAD_LEFT 0x15 +#define KEYCODE_DPAD_RIGHT 0x16 +#define KEYCODE_ENTER 0x42 +#define KEYCODE_PLAY 0x7e +#define KEYCODE_DPAD_CENTER 0x17 + +static std::unordered_map g_keyCodeMap = { + {KEYCODE_BACK, ax::KeyboardEvent::KeyCode::KEY_ESCAPE}, + {KEYCODE_MENU, ax::KeyboardEvent::KeyCode::KEY_MENU}, + {KEYCODE_DPAD_UP, ax::KeyboardEvent::KeyCode::KEY_DPAD_UP}, + {KEYCODE_DPAD_DOWN, ax::KeyboardEvent::KeyCode::KEY_DPAD_DOWN}, + {KEYCODE_DPAD_LEFT, ax::KeyboardEvent::KeyCode::KEY_DPAD_LEFT}, + {KEYCODE_DPAD_RIGHT, ax::KeyboardEvent::KeyCode::KEY_DPAD_RIGHT}, + {KEYCODE_ENTER, ax::KeyboardEvent::KeyCode::KEY_ENTER}, + {KEYCODE_PLAY, ax::KeyboardEvent::KeyCode::KEY_PLAY}, + {KEYCODE_DPAD_CENTER, ax::KeyboardEvent::KeyCode::KEY_DPAD_CENTER}, + +}; + +struct TouchPoint +{ + intptr_t id; + float x; + float y; + float pressure; +}; + extern "C" { JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeOnSurfaceCreated(JNIEnv* env, @@ -101,24 +136,24 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeOnSurfaceCreated(JNI auto renderView = director->getRenderView(); if (!renderView) { - renderView = ax::RenderViewImpl::createWithRect( - "axmol3", Rect{ax::Rect{0, 0, static_cast(w), static_cast(h)}}); + renderView = ax::RenderView::createWithRect("axmol3", + Rect{ax::Rect{0, 0, static_cast(w), static_cast(h)}}); director->setRenderView(renderView); - auto app = ax::Application::getInstance(); - ax::Application::getInstance()->run(); + auto axmolApp = ax::ApplicationCore::getInstance(); + axmolApp->run(); } else { if (rhi::DriverContext::isVulkan()) { - static_cast(renderView)->recreateVkSurface(true); + static_cast(renderView)->recreateVkSurface(true); } else { axdrv->resetState(); director->resetMatrixStack(); - ax::EventCustom recreatedEvent(EVENT_RENDERER_RECREATED); + ax::CustomEvent recreatedEvent(EVENT_RENDERER_RECREATED); director->getEventDispatcher()->dispatchEvent(&recreatedEvent, true); director->setRenderDefaults(); #if AX_ENABLE_CONTEXT_LOSS_RECOVERY @@ -144,78 +179,138 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeRenderFrame(JNIEnv*, ax::Director::getInstance()->renderFrame(); } -JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeTouchesBegin(JNIEnv*, jclass, jint id, jfloat x, jfloat y) +JNIEXPORT void JNICALL +Java_dev_axmol_lib_AxmolPlayer_nativeTouchBegin(JNIEnv* env, jclass, jint id, jfloat x, jfloat y, jfloat pressure) { - intptr_t idlong = id; - ax::Director::getInstance()->getRenderView()->handleTouchesBegin(1, &idlong, &x, &y); + auto director = ax::Director::getInstance(); + + director->postTask( + [pos = Vec2{x, y}, state = ax::PointerInputState{.id = static_cast(id), + .pressure = static_cast(pressure), + .type = ax::PointerType::Touch}]() { + ax::InputSystem::getInstance()->handlePointerDown(pos, state); + }, + Director::TaskTiming::FrameBoundary); } -JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeTouchesEnd(JNIEnv*, jclass, jint id, jfloat x, jfloat y) +JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeOnWindowFocusChanged(JNIEnv* env, + jobject thiz, + jboolean has_focus) { - intptr_t idlong = id; - ax::Director::getInstance()->getRenderView()->handleTouchesEnd(1, &idlong, &x, &y); + if (!has_focus) + { + ax::InputSystem::getInstance()->resetInput(); + } } JNIEXPORT void JNICALL -Java_dev_axmol_lib_AxmolPlayer_nativeTouchesMove(JNIEnv* env, jclass, jintArray ids, jfloatArray xs, jfloatArray ys) +Java_dev_axmol_lib_AxmolPlayer_nativeTouchEnd(JNIEnv* env, jclass, jint id, jfloat x, jfloat y, jfloat pressure) { - int size = env->GetArrayLength(ids); - jint id[size]; - jfloat x[size]; - jfloat y[size]; - - env->GetIntArrayRegion(ids, 0, size, id); - env->GetFloatArrayRegion(xs, 0, size, x); - env->GetFloatArrayRegion(ys, 0, size, y); - - intptr_t idlong[size]; - for (int i = 0; i < size; i++) - idlong[i] = id[i]; + auto director = ax::Director::getInstance(); - ax::Director::getInstance()->getRenderView()->handleTouchesMove(size, idlong, x, y); + director->postTask( + [pos = Vec2{x, y}, state = ax::PointerInputState{.id = static_cast(id), + .pressure = static_cast(pressure), + .type = ax::PointerType::Touch}]() { + ax::InputSystem::getInstance()->handlePointerUp(pos, state); + }, + Director::TaskTiming::FrameBoundary); } -JNIEXPORT void JNICALL -Java_dev_axmol_lib_AxmolPlayer_nativeTouchesCancel(JNIEnv* env, jclass, jintArray ids, jfloatArray xs, jfloatArray ys) +// ============================================================================== +// 2. Multi-Pointer Events (Zero-Copy) +// ============================================================================== +JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeTouchesMove(JNIEnv* env, + jclass, + jintArray jIds, + jfloatArray jXs, + jfloatArray jYs, + jfloatArray jPressures, + jint size) { - int size = env->GetArrayLength(ids); - jint id[size]; - jfloat x[size]; - jfloat y[size]; + if (size <= 0) + return; - env->GetIntArrayRegion(ids, 0, size, id); - env->GetFloatArrayRegion(xs, 0, size, x); - env->GetFloatArrayRegion(ys, 0, size, y); + jint* ids = (jint*)env->GetPrimitiveArrayCritical(jIds, nullptr); + jfloat* xs = (jfloat*)env->GetPrimitiveArrayCritical(jXs, nullptr); + jfloat* ys = (jfloat*)env->GetPrimitiveArrayCritical(jYs, nullptr); + jfloat* pressures = (jfloat*)env->GetPrimitiveArrayCritical(jPressures, nullptr); - intptr_t idlong[size]; - for (int i = 0; i < size; i++) - idlong[i] = id[i]; + size = std::min(size, AX_MAX_TOUCHES); + + tlx::static_vector touchPoints; + if (ids && xs && ys && pressures) + { + for (int i = 0; i < size; ++i) + touchPoints.push_back( + {.id = static_cast(ids[i]), .x = xs[i], .y = ys[i], .pressure = pressures[i]}); + } - ax::Director::getInstance()->getRenderView()->handleTouchesCancel(size, idlong, x, y); + if (pressures) + env->ReleasePrimitiveArrayCritical(jPressures, pressures, JNI_ABORT); + if (ys) + env->ReleasePrimitiveArrayCritical(jYs, ys, JNI_ABORT); + if (xs) + env->ReleasePrimitiveArrayCritical(jXs, xs, JNI_ABORT); + if (ids) + env->ReleasePrimitiveArrayCritical(jIds, ids, JNI_ABORT); + + ax::Director::getInstance()->postTask([touchPoints = std::move(touchPoints)]() { + auto inputSys = ax::InputSystem::getInstance(); + for (auto& touchPoint : touchPoints) + { + auto state = ax::PointerInputState{ + .id = touchPoint.id, .pressure = touchPoint.pressure, .type = ax::PointerType::Touch}; + inputSys->handlePointerMove(Vec2(touchPoint.x, touchPoint.y), state); + } + }, Director::TaskTiming::FrameBoundary); } -#define KEYCODE_BACK 0x04 -#define KEYCODE_MENU 0x52 -#define KEYCODE_DPAD_UP 0x13 -#define KEYCODE_DPAD_DOWN 0x14 -#define KEYCODE_DPAD_LEFT 0x15 -#define KEYCODE_DPAD_RIGHT 0x16 -#define KEYCODE_ENTER 0x42 -#define KEYCODE_PLAY 0x7e -#define KEYCODE_DPAD_CENTER 0x17 +JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeTouchesCancel(JNIEnv* env, + jclass, + jintArray jIds, + jfloatArray jXs, + jfloatArray jYs, + jfloatArray jPressures, + jint size) +{ + if (size <= 0) + return; -static std::unordered_map g_keyCodeMap = { - {KEYCODE_BACK, ax::EventKeyboard::KeyCode::KEY_ESCAPE}, - {KEYCODE_MENU, ax::EventKeyboard::KeyCode::KEY_MENU}, - {KEYCODE_DPAD_UP, ax::EventKeyboard::KeyCode::KEY_DPAD_UP}, - {KEYCODE_DPAD_DOWN, ax::EventKeyboard::KeyCode::KEY_DPAD_DOWN}, - {KEYCODE_DPAD_LEFT, ax::EventKeyboard::KeyCode::KEY_DPAD_LEFT}, - {KEYCODE_DPAD_RIGHT, ax::EventKeyboard::KeyCode::KEY_DPAD_RIGHT}, - {KEYCODE_ENTER, ax::EventKeyboard::KeyCode::KEY_ENTER}, - {KEYCODE_PLAY, ax::EventKeyboard::KeyCode::KEY_PLAY}, - {KEYCODE_DPAD_CENTER, ax::EventKeyboard::KeyCode::KEY_DPAD_CENTER}, + jint* ids = (jint*)env->GetPrimitiveArrayCritical(jIds, nullptr); + jfloat* xs = (jfloat*)env->GetPrimitiveArrayCritical(jXs, nullptr); + jfloat* ys = (jfloat*)env->GetPrimitiveArrayCritical(jYs, nullptr); + jfloat* pressures = (jfloat*)env->GetPrimitiveArrayCritical(jPressures, nullptr); -}; + size = std::min(size, AX_MAX_TOUCHES); + + tlx::static_vector touchPoints; + if (ids && xs && ys && pressures) + { + for (int i = 0; i < size; ++i) + touchPoints.push_back( + {.id = static_cast(ids[i]), .x = xs[i], .y = ys[i], .pressure = pressures[i]}); + } + + if (pressures) + env->ReleasePrimitiveArrayCritical(jPressures, pressures, JNI_ABORT); + if (ys) + env->ReleasePrimitiveArrayCritical(jYs, ys, JNI_ABORT); + if (xs) + env->ReleasePrimitiveArrayCritical(jXs, xs, JNI_ABORT); + if (ids) + env->ReleasePrimitiveArrayCritical(jIds, ids, JNI_ABORT); + + ax::Director::getInstance()->postTask([touchPoints = std::move(touchPoints)]() { + auto inputSys = ax::InputSystem::getInstance(); + for (auto& touchPoint : touchPoints) + { + auto state = ax::PointerInputState{ + .id = touchPoint.id, .pressure = touchPoint.pressure, .type = ax::PointerType::Touch}; + inputSys->handlePointerCancel(Vec2(touchPoint.x, touchPoint.y), state); + } + }, Director::TaskTiming::FrameBoundary); +} JNIEXPORT jboolean JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeKeyEvent(JNIEnv*, jclass, @@ -228,8 +323,8 @@ JNIEXPORT jboolean JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeKeyEvent(JNIEnv* return JNI_FALSE; } - ax::EventKeyboard event(iterKeyCode->second, isPressed); - ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); + ax::InputSystem::getInstance()->handleKeyEvent(iterKeyCode->second, + isPressed ? InputPhase::KeyDown : InputPhase::KeyUp); return JNI_TRUE; } @@ -238,7 +333,7 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeOnPause(JNIEnv*, jcl if (Director::getInstance()->getRenderView()) { Application::getInstance()->applicationDidEnterBackground(); - ax::EventCustom backgroundEvent(EVENT_COME_TO_BACKGROUND); + ax::CustomEvent backgroundEvent(EVENT_COME_TO_BACKGROUND); ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&backgroundEvent, true); } } @@ -248,7 +343,7 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeOnResume(JNIEnv*, jc if (Director::getInstance()->getRenderView()) { Application::getInstance()->applicationWillEnterForeground(); - ax::EventCustom foregroundEvent(EVENT_COME_TO_FOREGROUND); + ax::CustomEvent foregroundEvent(EVENT_COME_TO_FOREGROUND); ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&foregroundEvent, true); } } @@ -256,17 +351,40 @@ JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeOnResume(JNIEnv*, jc JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeInsertText(JNIEnv* env, jclass, jstring text) { std::string strValue = ax::text_utils::getStringUTFCharsJNI(env, text); - ax::IMEDispatcher::sharedDispatcher()->dispatchInsertText(strValue.c_str(), strValue.size()); + ax::InputSystem::getInstance()->dispatchInsertText(strValue); } JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeDeleteBackward(JNIEnv*, jclass, jint numChars) { - ax::IMEDispatcher::sharedDispatcher()->dispatchDeleteBackward(numChars); + ax::InputSystem::getInstance()->dispatchDeleteBackward(static_cast(numChars)); +} + +JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeSoftInputShow(JNIEnv* env, + jclass, + jfloat x, + jfloat y, + jfloat width, + jfloat height, + jfloat duration) +{ + if (auto inputSys = ax::InputSystem::getInstance()) + { + inputSys->onPlatformKeyboardWillShow(x, y, width, height, duration); + inputSys->onPlatformKeyboardDidShow(); + } +} + +JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeSoftInputHide(JNIEnv*, jclass, float duration) +{ + if (auto inputSys = ax::InputSystem::getInstance()) + { + inputSys->onPlatformKeyboardWillHide(duration); + inputSys->onPlatformKeyboardDidHide(); + } } -JNIEXPORT jstring JNICALL Java_dev_axmol_lib_AxmolPlayer_nativeGetContentText(JNIEnv* env, jclass) +JNIEXPORT void JNICALL Java_dev_axmol_lib_AxmolPlayer_nativePerformEditAction(JNIEnv*, jclass, int action) { - auto pszText = ax::IMEDispatcher::sharedDispatcher()->getContentText(); - return ax::text_utils::newStringUTFJNI(env, pszText); + ax::InputSystem::getInstance()->dispatchPerformEditAction(static_cast(action)); } } diff --git a/axmol/platform/desktop/RenderViewImpl.cpp b/axmol/platform/desktop/RenderViewImpl.cpp deleted file mode 100644 index 40b0f4446811..000000000000 --- a/axmol/platform/desktop/RenderViewImpl.cpp +++ /dev/null @@ -1,1508 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010-2012 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -The RenderViewImpl for win32,linux,macos,wasm - -****************************************************************************/ - -#include "axmol/platform/desktop/RenderViewImpl.h" - -#include -#include - -#include "axmol/platform/Application.h" -#include "axmol/base/Director.h" -#include "axmol/base/Touch.h" -#include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventKeyboard.h" -#include "axmol/base/EventMouse.h" -#include "axmol/base/IMEDispatcher.h" -#include "axmol/base/Utils.h" -#include "axmol/base/text_utils.h" -#include "axmol/scene/Camera.h" -#if AX_ICON_SET_SUPPORT -# include "axmol/platform/Image.h" -#endif /* AX_ICON_SET_SUPPORT */ - -#include "axmol/renderer/Renderer.h" - -#if AX_ENABLE_MTL -# include -# include "axmol/rhi/metal/DriverMTL.h" -# include "axmol/rhi/metal/UtilsMTL.h" -#endif -#if AX_ENABLE_GL -# include "axmol/rhi/opengl/DriverGL.h" -# include "axmol/rhi/opengl/MacrosGL.h" -# include "axmol/rhi/opengl/OpenGLState.h" -#endif -#if AX_ENABLE_VK -# include "axmol/rhi/vulkan/DriverVK.h" -#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) - -#include "axmol/rhi/DriverContext.h" - -/** glfw3native.h */ -#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) -# ifndef GLFW_EXPOSE_NATIVE_WIN32 -# define GLFW_EXPOSE_NATIVE_WIN32 -# endif -# ifndef GLFW_EXPOSE_NATIVE_WGL -# define GLFW_EXPOSE_NATIVE_WGL -# endif -#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) */ - -#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) -# ifndef GLFW_EXPOSE_NATIVE_NSGL -# define GLFW_EXPOSE_NATIVE_NSGL -# endif -# ifndef GLFW_EXPOSE_NATIVE_COCOA -# define GLFW_EXPOSE_NATIVE_COCOA -# endif -#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) - -#if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) -# ifndef GLFW_EXPOSE_NATIVE_X11 -# define GLFW_EXPOSE_NATIVE_X11 -# endif -# ifndef GLFW_EXPOSE_NATIVE_WAYLAND -# define GLFW_EXPOSE_NATIVE_WAYLAND -# endif -#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) - -#if (AX_TARGET_PLATFORM != AX_PLATFORM_WASM) -# include -#endif - -#if defined(__EMSCRIPTEN__) -# include -#endif - -#ifndef NDEBUG -# include "axmol/base/Scheduler.h" -#endif - -#if defined(_WIN32) -# pragma comment(lib, "imm32.lib") -#endif - -namespace ax -{ - -using namespace rhi; - -#if defined(__EMSCRIPTEN__) -struct IVec2 -{ - int x{0}; - int y{0}; -}; -struct WebFullscreenState -{ - WebFullscreenState() - { - EmscriptenFullscreenChangeEvent fs; - if (emscripten_get_fullscreen_status(&fs) == EMSCRIPTEN_RESULT_SUCCESS) - { - isFullscreen = fs.isFullscreen; - } - } - - IVec2 windowedSize; - bool isFullscreen{false}; -}; -static std::unique_ptr s_fullscreenState; -#endif - -class GLFWEventHandler -{ -public: - static void onGLFWError(int errorID, const char* errorDesc) - { - if (_view) - _view->onGLFWError(errorID, errorDesc); - } - - static void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify) - { - if (_view) - _view->onGLFWMouseCallBack(window, button, action, modify); - } - - static void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y) - { - if (_view) - _view->onGLFWMouseMoveCallBack(window, x, y); - } -#if defined(__EMSCRIPTEN__) - static EM_BOOL onWebOrientationChangeCallback(int eventType, - const EmscriptenOrientationChangeEvent* e, - void* /*userData*/) - { - if (_view) - _view->onWebOrientationChangeCallback(eventType, e); - return EM_TRUE; - } - - static EM_BOOL onWebFullscreenCallback(int eventType, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) - { - if (_view) - _view->onWebFullscreenCallback(eventType, e); - return EM_TRUE; - } - - static EM_BOOL onWebTouchCallback(int eventType, const EmscriptenTouchEvent* e, void* /*userData*/) - { - if (_view) - _view->onWebTouchCallback(eventType, e); - return EM_FALSE; - } - - static void onWebClickCallback() - { - if (_view) - _view->onWebClickCallback(); - } -#endif - - static void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y) - { - if (_view) - _view->onGLFWMouseScrollCallback(window, x, y); - } - - static void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) - { - if (_view) - _view->onGLFWKeyCallback(window, key, scancode, action, mods); - } - - static void onGLFWCharCallback(GLFWwindow* window, unsigned int character) - { - if (_view) - _view->onGLFWCharCallback(window, character); - } - - static void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y) - { - if (_view) - _view->onGLFWWindowPosCallback(windows, x, y); - } - - static void onGLFWFramebufferSizeCallback(GLFWwindow* window, int width, int height) - { - if (_view) - _view->onGLFWFramebufferSizeCallback(window, width, height); - } - - static void onGLFWWindowSizeCallback(GLFWwindow* window, int width, int height) - { - if (_view) - _view->onGLFWWindowSizeCallback(window, width, height); - } - - static void setRenderViewImpl(RenderViewImpl* view) { _view = view; } - - static void onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified) - { - if (_view) - { - _view->onGLFWWindowIconifyCallback(window, iconified); - } - } - - static void onGLFWWindowFocusCallback(GLFWwindow* window, int focused) - { - if (_view) - { - _view->onGLFWWindowFocusCallback(window, focused); - } - } - - static void onGLFWWindowCloseCallback(GLFWwindow* window) - { - if (_view) - { - _view->onGLFWWindowCloseCallback(window); - } - } - -private: - static RenderViewImpl* _view; -}; -RenderViewImpl* GLFWEventHandler::_view = nullptr; - -const std::string_view RenderViewImpl::EVENT_WINDOW_POSITIONED = "_ax_window_positioned"sv; -const std::string_view RenderViewImpl::EVENT_WINDOW_RESIZED = "_ax_window_resized"sv; -const std::string_view RenderViewImpl::EVENT_WINDOW_FOCUSED = "_ax_window_focused"sv; -const std::string_view RenderViewImpl::EVENT_WINDOW_UNFOCUSED = "_ax_window_unfocused"sv; -const std::string_view RenderViewImpl::EVENT_WINDOW_CLOSE = "_ax_window_close"sv; - -//////////////////////////////////////////////////// - -struct keyCodeItem -{ - int glfwKeyCode; - EventKeyboard::KeyCode keyCode; -}; - -static std::unordered_map g_keyCodeMap; - -static keyCodeItem g_keyCodeStructArray[] = { - /* The unknown key */ - {GLFW_KEY_UNKNOWN, EventKeyboard::KeyCode::KEY_NONE}, - - /* Printable keys */ - {GLFW_KEY_SPACE, EventKeyboard::KeyCode::KEY_SPACE}, - {GLFW_KEY_APOSTROPHE, EventKeyboard::KeyCode::KEY_APOSTROPHE}, - {GLFW_KEY_COMMA, EventKeyboard::KeyCode::KEY_COMMA}, - {GLFW_KEY_MINUS, EventKeyboard::KeyCode::KEY_MINUS}, - {GLFW_KEY_PERIOD, EventKeyboard::KeyCode::KEY_PERIOD}, - {GLFW_KEY_SLASH, EventKeyboard::KeyCode::KEY_SLASH}, - {GLFW_KEY_0, EventKeyboard::KeyCode::KEY_0}, - {GLFW_KEY_1, EventKeyboard::KeyCode::KEY_1}, - {GLFW_KEY_2, EventKeyboard::KeyCode::KEY_2}, - {GLFW_KEY_3, EventKeyboard::KeyCode::KEY_3}, - {GLFW_KEY_4, EventKeyboard::KeyCode::KEY_4}, - {GLFW_KEY_5, EventKeyboard::KeyCode::KEY_5}, - {GLFW_KEY_6, EventKeyboard::KeyCode::KEY_6}, - {GLFW_KEY_7, EventKeyboard::KeyCode::KEY_7}, - {GLFW_KEY_8, EventKeyboard::KeyCode::KEY_8}, - {GLFW_KEY_9, EventKeyboard::KeyCode::KEY_9}, - {GLFW_KEY_SEMICOLON, EventKeyboard::KeyCode::KEY_SEMICOLON}, - {GLFW_KEY_EQUAL, EventKeyboard::KeyCode::KEY_EQUAL}, - {GLFW_KEY_A, EventKeyboard::KeyCode::KEY_A}, - {GLFW_KEY_B, EventKeyboard::KeyCode::KEY_B}, - {GLFW_KEY_C, EventKeyboard::KeyCode::KEY_C}, - {GLFW_KEY_D, EventKeyboard::KeyCode::KEY_D}, - {GLFW_KEY_E, EventKeyboard::KeyCode::KEY_E}, - {GLFW_KEY_F, EventKeyboard::KeyCode::KEY_F}, - {GLFW_KEY_G, EventKeyboard::KeyCode::KEY_G}, - {GLFW_KEY_H, EventKeyboard::KeyCode::KEY_H}, - {GLFW_KEY_I, EventKeyboard::KeyCode::KEY_I}, - {GLFW_KEY_J, EventKeyboard::KeyCode::KEY_J}, - {GLFW_KEY_K, EventKeyboard::KeyCode::KEY_K}, - {GLFW_KEY_L, EventKeyboard::KeyCode::KEY_L}, - {GLFW_KEY_M, EventKeyboard::KeyCode::KEY_M}, - {GLFW_KEY_N, EventKeyboard::KeyCode::KEY_N}, - {GLFW_KEY_O, EventKeyboard::KeyCode::KEY_O}, - {GLFW_KEY_P, EventKeyboard::KeyCode::KEY_P}, - {GLFW_KEY_Q, EventKeyboard::KeyCode::KEY_Q}, - {GLFW_KEY_R, EventKeyboard::KeyCode::KEY_R}, - {GLFW_KEY_S, EventKeyboard::KeyCode::KEY_S}, - {GLFW_KEY_T, EventKeyboard::KeyCode::KEY_T}, - {GLFW_KEY_U, EventKeyboard::KeyCode::KEY_U}, - {GLFW_KEY_V, EventKeyboard::KeyCode::KEY_V}, - {GLFW_KEY_W, EventKeyboard::KeyCode::KEY_W}, - {GLFW_KEY_X, EventKeyboard::KeyCode::KEY_X}, - {GLFW_KEY_Y, EventKeyboard::KeyCode::KEY_Y}, - {GLFW_KEY_Z, EventKeyboard::KeyCode::KEY_Z}, - {GLFW_KEY_LEFT_BRACKET, EventKeyboard::KeyCode::KEY_LEFT_BRACKET}, - {GLFW_KEY_BACKSLASH, EventKeyboard::KeyCode::KEY_BACK_SLASH}, - {GLFW_KEY_RIGHT_BRACKET, EventKeyboard::KeyCode::KEY_RIGHT_BRACKET}, - {GLFW_KEY_GRAVE_ACCENT, EventKeyboard::KeyCode::KEY_GRAVE}, - {GLFW_KEY_WORLD_1, EventKeyboard::KeyCode::KEY_GRAVE}, - {GLFW_KEY_WORLD_2, EventKeyboard::KeyCode::KEY_NONE}, - - /* Function keys */ - {GLFW_KEY_ESCAPE, EventKeyboard::KeyCode::KEY_ESCAPE}, - {GLFW_KEY_ENTER, EventKeyboard::KeyCode::KEY_ENTER}, - {GLFW_KEY_TAB, EventKeyboard::KeyCode::KEY_TAB}, - {GLFW_KEY_BACKSPACE, EventKeyboard::KeyCode::KEY_BACKSPACE}, - {GLFW_KEY_INSERT, EventKeyboard::KeyCode::KEY_INSERT}, - {GLFW_KEY_DELETE, EventKeyboard::KeyCode::KEY_DELETE}, - {GLFW_KEY_RIGHT, EventKeyboard::KeyCode::KEY_RIGHT_ARROW}, - {GLFW_KEY_LEFT, EventKeyboard::KeyCode::KEY_LEFT_ARROW}, - {GLFW_KEY_DOWN, EventKeyboard::KeyCode::KEY_DOWN_ARROW}, - {GLFW_KEY_UP, EventKeyboard::KeyCode::KEY_UP_ARROW}, - {GLFW_KEY_PAGE_UP, EventKeyboard::KeyCode::KEY_PG_UP}, - {GLFW_KEY_PAGE_DOWN, EventKeyboard::KeyCode::KEY_PG_DOWN}, - {GLFW_KEY_HOME, EventKeyboard::KeyCode::KEY_HOME}, - {GLFW_KEY_END, EventKeyboard::KeyCode::KEY_END}, - {GLFW_KEY_CAPS_LOCK, EventKeyboard::KeyCode::KEY_CAPS_LOCK}, - {GLFW_KEY_SCROLL_LOCK, EventKeyboard::KeyCode::KEY_SCROLL_LOCK}, - {GLFW_KEY_NUM_LOCK, EventKeyboard::KeyCode::KEY_NUM_LOCK}, - {GLFW_KEY_PRINT_SCREEN, EventKeyboard::KeyCode::KEY_PRINT}, - {GLFW_KEY_PAUSE, EventKeyboard::KeyCode::KEY_PAUSE}, - {GLFW_KEY_F1, EventKeyboard::KeyCode::KEY_F1}, - {GLFW_KEY_F2, EventKeyboard::KeyCode::KEY_F2}, - {GLFW_KEY_F3, EventKeyboard::KeyCode::KEY_F3}, - {GLFW_KEY_F4, EventKeyboard::KeyCode::KEY_F4}, - {GLFW_KEY_F5, EventKeyboard::KeyCode::KEY_F5}, - {GLFW_KEY_F6, EventKeyboard::KeyCode::KEY_F6}, - {GLFW_KEY_F7, EventKeyboard::KeyCode::KEY_F7}, - {GLFW_KEY_F8, EventKeyboard::KeyCode::KEY_F8}, - {GLFW_KEY_F9, EventKeyboard::KeyCode::KEY_F9}, - {GLFW_KEY_F10, EventKeyboard::KeyCode::KEY_F10}, - {GLFW_KEY_F11, EventKeyboard::KeyCode::KEY_F11}, - {GLFW_KEY_F12, EventKeyboard::KeyCode::KEY_F12}, - {GLFW_KEY_F13, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F14, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F15, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F16, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F17, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F18, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F19, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F20, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F21, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F22, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F23, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F24, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_F25, EventKeyboard::KeyCode::KEY_NONE}, - {GLFW_KEY_KP_0, EventKeyboard::KeyCode::KEY_0}, - {GLFW_KEY_KP_1, EventKeyboard::KeyCode::KEY_1}, - {GLFW_KEY_KP_2, EventKeyboard::KeyCode::KEY_2}, - {GLFW_KEY_KP_3, EventKeyboard::KeyCode::KEY_3}, - {GLFW_KEY_KP_4, EventKeyboard::KeyCode::KEY_4}, - {GLFW_KEY_KP_5, EventKeyboard::KeyCode::KEY_5}, - {GLFW_KEY_KP_6, EventKeyboard::KeyCode::KEY_6}, - {GLFW_KEY_KP_7, EventKeyboard::KeyCode::KEY_7}, - {GLFW_KEY_KP_8, EventKeyboard::KeyCode::KEY_8}, - {GLFW_KEY_KP_9, EventKeyboard::KeyCode::KEY_9}, - {GLFW_KEY_KP_DECIMAL, EventKeyboard::KeyCode::KEY_PERIOD}, - {GLFW_KEY_KP_DIVIDE, EventKeyboard::KeyCode::KEY_KP_DIVIDE}, - {GLFW_KEY_KP_MULTIPLY, EventKeyboard::KeyCode::KEY_KP_MULTIPLY}, - {GLFW_KEY_KP_SUBTRACT, EventKeyboard::KeyCode::KEY_KP_MINUS}, - {GLFW_KEY_KP_ADD, EventKeyboard::KeyCode::KEY_KP_PLUS}, - {GLFW_KEY_KP_ENTER, EventKeyboard::KeyCode::KEY_KP_ENTER}, - {GLFW_KEY_KP_EQUAL, EventKeyboard::KeyCode::KEY_EQUAL}, - {GLFW_KEY_LEFT_SHIFT, EventKeyboard::KeyCode::KEY_LEFT_SHIFT}, - {GLFW_KEY_LEFT_CONTROL, EventKeyboard::KeyCode::KEY_LEFT_CTRL}, - {GLFW_KEY_LEFT_ALT, EventKeyboard::KeyCode::KEY_LEFT_ALT}, - {GLFW_KEY_LEFT_SUPER, EventKeyboard::KeyCode::KEY_HYPER}, - {GLFW_KEY_RIGHT_SHIFT, EventKeyboard::KeyCode::KEY_RIGHT_SHIFT}, - {GLFW_KEY_RIGHT_CONTROL, EventKeyboard::KeyCode::KEY_RIGHT_CTRL}, - {GLFW_KEY_RIGHT_ALT, EventKeyboard::KeyCode::KEY_RIGHT_ALT}, - {GLFW_KEY_RIGHT_SUPER, EventKeyboard::KeyCode::KEY_HYPER}, - {GLFW_KEY_MENU, EventKeyboard::KeyCode::KEY_MENU}, - {GLFW_KEY_LAST, EventKeyboard::KeyCode::KEY_NONE}}; - -////////////////////////////////////////////////////////////////////////// -// implement RenderViewImpl -////////////////////////////////////////////////////////////////////////// - -static EventMouse::MouseButton checkMouseButton(GLFWwindow* window) -{ - EventMouse::MouseButton mouseButton{EventMouse::MouseButton::BUTTON_UNSET}; - if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) - { - mouseButton = static_cast(GLFW_MOUSE_BUTTON_LEFT); - } - else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) - { - mouseButton = static_cast(GLFW_MOUSE_BUTTON_RIGHT); - } - else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) - { - mouseButton = static_cast(GLFW_MOUSE_BUTTON_MIDDLE); - } - return mouseButton; -} - -RenderViewImpl::RenderViewImpl(bool initglfw) - : _captured(false), _renderScale(1.0f), _windowZoomFactor(1.0f), _mainWindow(nullptr), _monitor(nullptr) -{ - _viewName = "axmol3"; - g_keyCodeMap.clear(); - for (auto&& item : g_keyCodeStructArray) - { - g_keyCodeMap[item.glfwKeyCode] = item.keyCode; - } - - GLFWEventHandler::setRenderViewImpl(this); - if (initglfw) - { - glfwSetErrorCallback(GLFWEventHandler::onGLFWError); - glfwInit(); - } -} - -RenderViewImpl::~RenderViewImpl() -{ - AXLOGD("deallocing RenderViewImpl: {}", fmt::ptr(this)); - GLFWEventHandler::setRenderViewImpl(nullptr); - glfwTerminate(); -} - -void* RenderViewImpl::getNativeWindow() const -{ -#if AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 - return glfwGetWin32Window(_mainWindow); -#elif AX_TARGET_PLATFORM == AX_PLATFORM_MAC - return (void*)glfwGetCocoaWindow(_mainWindow); -#elif AX_TARGET_PLATFORM == AX_PLATFORM_LINUX - int platform = glfwGetPlatform(); - return platform == GLFW_PLATFORM_WAYLAND ? (void*)glfwGetWaylandWindow(_mainWindow) - : (void*)glfwGetX11Window(_mainWindow); -#else - return nullptr; -#endif -} - -SurfaceHandle RenderViewImpl::getNativeDisplay() const -{ - auto driverType = DriverContext::currentDriverType(); - if (driverType == DriverType::Vulkan) - return _vkSurface; - -#if AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 - return glfwGetWin32Window(_mainWindow); -#elif AX_TARGET_PLATFORM == AX_PLATFORM_MAC - return driverType == DriverType::Metal ? (void*)glfwGetCocoaView(_mainWindow) - : (void*)glfwGetNSGLContext(_mainWindow); - return (void*)glfwGetNSGLContext(_mainWindow); -#elif AX_TARGET_PLATFORM == AX_PLATFORM_LINUX - int platform = glfwGetPlatform(); - return platform == GLFW_PLATFORM_WAYLAND ? (void*)glfwGetWaylandDisplay() : (void*)glfwGetX11Display(); -#else - return nullptr; -#endif -} - -WindowPlatform RenderViewImpl::getWindowPlatform() const -{ -#if AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 - return WindowPlatform::Win32; -#elif AX_TARGET_PLATFORM == AX_PLATFORM_MAC - return WindowPlatform::Cocoa; -#elif AX_TARGET_PLATFORM == AX_PLATFORM_LINUX - int platform = glfwGetPlatform(); - return platform == GLFW_PLATFORM_WAYLAND ? WindowPlatform::Wayland : WindowPlatform::X11; -#elif AX_TARGET_PLATFORM == AX_PLATFORM_WASM - return WindowPlatform::Web; -#else - return WindowPlatform::Unknown; -#endif -} - -RenderViewImpl* RenderViewImpl::create(std::string_view viewName) -{ - return RenderViewImpl::create(viewName, false); -} - -RenderViewImpl* RenderViewImpl::create(std::string_view viewName, bool resizable) -{ - auto ret = new RenderViewImpl; - if (ret->initWithRect(viewName, ax::Rect(0, 0, 960, 640), 1.0f, resizable)) - { - ret->autorelease(); - return ret; - } - AX_SAFE_DELETE(ret); - return nullptr; -} - -RenderViewImpl* RenderViewImpl::createWithRect(std::string_view viewName, - const ax::Rect& rect, - float windowZoomFactor, - bool resizable) -{ - auto ret = new RenderViewImpl; - if (ret->initWithRect(viewName, rect, windowZoomFactor, resizable)) - { - ret->autorelease(); - return ret; - } - AX_SAFE_DELETE(ret); - return nullptr; -} - -RenderViewImpl* RenderViewImpl::createWithFullscreen(std::string_view viewName) -{ - auto ret = new RenderViewImpl(); - if (ret->initWithFullScreen(viewName)) - { - ret->autorelease(); - return ret; - } - AX_SAFE_DELETE(ret); - return nullptr; -} - -RenderViewImpl* RenderViewImpl::createWithFullscreen(std::string_view viewName, - const GLFWvidmode& videoMode, - GLFWmonitor* monitor) -{ - auto ret = new RenderViewImpl(); - if (ret->initWithFullscreen(viewName, videoMode, monitor)) - { - ret->autorelease(); - return ret; - } - AX_SAFE_DELETE(ret); - return nullptr; -} - -bool RenderViewImpl::initWithRect(std::string_view viewName, - const ax::Rect& rect, - float windowZoomFactor, - bool resizable) -{ - _viewName = viewName; - _windowZoomFactor = windowZoomFactor; - - Vec2 requestWinSize = rect.size * windowZoomFactor; - - // Try to initialize a high-performance graphics driver first. - // If any of the high-performance APIs (D3D11/D3D12/Vulkan/Metal) are enabled, - // the runtime will attempt initialization in the default priority order. - // If all attempts fail, OpenGL will then be explicitly selected as the fallback. - DriverContext::makeCurrentDriver(); - const auto fallbackGL = DriverContext::isOpenGL(); - if (fallbackGL) - { -#if AX_GLES_PROFILE - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, AX_GLES_PROFILE / AX_GLES_PROFILE_DEN); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); -#else - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // We want OpenGL 3.3 - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // We don't want the old OpenGL -#endif - } - else // Other Graphics driver, don't create gl context. - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - auto& contextAttrs = Application::getContextAttrs(); - - glfwWindowHint(GLFW_RESIZABLE, resizable ? GL_TRUE : GL_FALSE); - glfwWindowHint(GLFW_RED_BITS, contextAttrs.redBits); - glfwWindowHint(GLFW_GREEN_BITS, contextAttrs.greenBits); - glfwWindowHint(GLFW_BLUE_BITS, contextAttrs.blueBits); - glfwWindowHint(GLFW_ALPHA_BITS, contextAttrs.alphaBits); - glfwWindowHint(GLFW_DEPTH_BITS, contextAttrs.depthBits); - glfwWindowHint(GLFW_STENCIL_BITS, contextAttrs.stencilBits); - - glfwWindowHint(GLFW_SAMPLES, contextAttrs.multisamplingCount); - - const auto requireShowByUser = contextAttrs.visible; - glfwWindowHint(GLFW_VISIBLE, false); - glfwWindowHint(GLFW_DECORATED, contextAttrs.decorated); - -#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) - glfwWindowHintPointer(GLFW_WIN32_HWND_PARENT, contextAttrs.windowParent); -#endif - - _renderScaleMode = contextAttrs.renderScaleMode; -#if AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || \ - AX_TARGET_PLATFORM == AX_PLATFORM_WASM - // On Linux X11 platforms, GLFW does not support fractional DPI scaling (e.g., 1.5x). - // To ensure consistent rendering across high-DPI displays, we disable GLFW_SCALE_TO_MONITOR - // and apply custom scaling logic based on platform-specific DPI detection. - // GLFW_SCALE_TO_MONITOR support Win32, X11, Wasm - glfwWindowHint(GLFW_SCALE_TO_MONITOR, _renderScaleMode == RenderScaleMode::Physical ? GLFW_TRUE : GLFW_FALSE); -#endif - - _mainWindow = - glfwCreateWindow(static_cast(std::lround(requestWinSize.width)), - static_cast(std::lround(requestWinSize.height)), _viewName.c_str(), _monitor, nullptr); - if (_mainWindow == nullptr) - { - std::string message = "Can't create window"; - if (!_glfwError.empty()) - { - message.append("\nMore info: \n"); - message.append(_glfwError); - } - - showAlert(message, "Error launch application"); - utils::killCurrentProcess(); // kill current process, don't cause crash when driver issue. - return false; - } - - glfwSetWindowSizeLimits(_mainWindow, 1, 1, GLFW_DONT_CARE, GLFW_DONT_CARE); - -#if AX_ENABLE_GL - if (fallbackGL) - { - glfwMakeContextCurrent(_mainWindow); - DriverContext::activateCurrentDriver(); - - glfwSetWindowUserPointer(_mainWindow, gl::__state); - } -#endif - - if (requireShowByUser) - glfwShowWindow(_mainWindow); - - /* - * Note that the created window and context may differ from what you requested, - * as not all parameters and hints are - * [hard constraints](@ref window_hints_hard). This includes the size of the - * window, especially for full screen windows. To retrieve the actual - * attributes of the created window and context, use queries like @ref - * glfwGetWindowAttrib and @ref glfwGetWindowSize. - * - * see declaration glfwCreateWindow - */ - - int fbWidth, fbHeight; - glfwGetFramebufferSize(_mainWindow, &fbWidth, &fbHeight); - updateRenderSurface(fbWidth, fbHeight, SurfaceUpdateFlag::RenderSizeChanged | SurfaceUpdateFlag::SilentUpdate); - -#if AX_ENABLE_VK - if (DriverContext::isVulkan()) - { - auto _createSurface = [](VkInstance inst, void* window, VkSurfaceKHR* surface) { - return glfwCreateWindowSurface(inst, static_cast(window), nullptr, surface); - }; - auto driver = static_cast(axdrv); - const vk::SurfaceCreateInfo createInfo{ - .window = _mainWindow, .width = fbWidth, .height = fbHeight, .createFunc = _createSurface}; - bool ok = driver->recreateSurface(createInfo); - if (!ok) - { - AXLOGE("Failed to create Vulkan window surface."); - return false; - } - _vkSurface = driver->getSurface(); - } -#endif - - int w, h; - glfwGetWindowSize(_mainWindow, &w, &h); - updateScaledWindowSize(w, h, SurfaceUpdateFlag::WindowSizeChanged | SurfaceUpdateFlag::SilentUpdate); - - glfwSetMouseButtonCallback(_mainWindow, GLFWEventHandler::onGLFWMouseCallBack); - glfwSetCursorPosCallback(_mainWindow, GLFWEventHandler::onGLFWMouseMoveCallBack); -#if defined(__EMSCRIPTEN__) - s_fullscreenState = std::make_unique(); - // clang-format off - emscripten_set_orientationchange_callback(this, EM_TRUE, GLFWEventHandler::onWebOrientationChangeCallback); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, this, EM_TRUE, GLFWEventHandler::onWebFullscreenCallback); - - _isTouchDevice = !!EM_ASM_INT( - return window.matchMedia('(pointer: coarse)').matches && !window.matchMedia('(any-hover: hover)').matches; - ); - const auto maxTouchPoints = EM_ASM_INT( - return navigator.maxTouchPoints; - ); - AXLOGI("RenderViewImpl::initWithRect: isTouchDevice: {}, maxTouchPoints: {}", _isTouchDevice, maxTouchPoints); - if (_isTouchDevice) - { - const auto eventTarget = EMSCRIPTEN_EVENT_TARGET_WINDOW; - emscripten_set_touchstart_callback(eventTarget, this, EM_TRUE, GLFWEventHandler::onWebTouchCallback); - emscripten_set_touchend_callback(eventTarget, this, EM_TRUE, GLFWEventHandler::onWebTouchCallback); - emscripten_set_touchmove_callback(eventTarget, this, EM_TRUE, GLFWEventHandler::onWebTouchCallback); - emscripten_set_touchcancel_callback(eventTarget, this, EM_TRUE, GLFWEventHandler::onWebTouchCallback); - } - else - { - EM_ASM({ - document.addEventListener('click', function(event){ - Module.ccall("axmol_onwebclickcallback"); - }); - }); - } - // clang-format on -#endif - - glfwSetScrollCallback(_mainWindow, GLFWEventHandler::onGLFWMouseScrollCallback); - glfwSetCharCallback(_mainWindow, GLFWEventHandler::onGLFWCharCallback); - glfwSetKeyCallback(_mainWindow, GLFWEventHandler::onGLFWKeyCallback); - glfwSetWindowPosCallback(_mainWindow, GLFWEventHandler::onGLFWWindowPosCallback); - glfwSetFramebufferSizeCallback(_mainWindow, GLFWEventHandler::onGLFWFramebufferSizeCallback); - glfwSetWindowSizeCallback(_mainWindow, GLFWEventHandler::onGLFWWindowSizeCallback); - glfwSetWindowIconifyCallback(_mainWindow, GLFWEventHandler::onGLFWWindowIconifyCallback); - glfwSetWindowFocusCallback(_mainWindow, GLFWEventHandler::onGLFWWindowFocusCallback); - glfwSetWindowCloseCallback(_mainWindow, GLFWEventHandler::onGLFWWindowCloseCallback); - -#if AX_ENABLE_GL - if (fallbackGL) - { -# if !defined(__EMSCRIPTEN__) - glfwSwapInterval(contextAttrs.vsync ? 1 : 0); -# endif - // Will cause OpenGL error 0x0500 when use ANGLE-GLES on desktop -# if !AX_GLES_PROFILE - // Enable point size by default. -# if defined(GL_VERSION_2_0) - glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); -# else - glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); -# endif - if (contextAttrs.multisamplingCount > 0) - glEnable(GL_MULTISAMPLE); -# endif - CHECK_GL_ERROR_DEBUG(); - } -#endif - - setIMEKeyboardState(false); - - return true; -} - -bool RenderViewImpl::initWithFullScreen(std::string_view viewName) -{ - // Create fullscreen window on primary monitor at its current video mode. - _monitor = glfwGetPrimaryMonitor(); - if (nullptr == _monitor) - return false; - - const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); - - // These are soft constraints. If the video mode is retrieved at runtime, the resulting window and context should - // match these exactly. If invalid attribs are passed (eg. from an outdated cache), window creation will NOT fail - // but the actual window/context may differ. - glfwWindowHint(GLFW_REFRESH_RATE, videoMode->refreshRate); - glfwWindowHint(GLFW_RED_BITS, videoMode->redBits); - glfwWindowHint(GLFW_BLUE_BITS, videoMode->blueBits); - glfwWindowHint(GLFW_GREEN_BITS, videoMode->greenBits); - - return initWithRect(viewName, ax::Rect(0, 0, (float)videoMode->width, (float)videoMode->height), 1.0f, false); -} - -bool RenderViewImpl::initWithFullscreen(std::string_view viewname, const GLFWvidmode& videoMode, GLFWmonitor* monitor) -{ - // Create fullscreen on specified monitor at the specified video mode. - _monitor = monitor; - if (nullptr == _monitor) - return false; - - // These are soft constraints. If the video mode is retrieved at runtime, the resulting window and context should - // match these exactly. If invalid attribs are passed (eg. from an outdated cache), window creation will NOT fail - // but the actual window/context may differ. - glfwWindowHint(GLFW_REFRESH_RATE, videoMode.refreshRate); - glfwWindowHint(GLFW_RED_BITS, videoMode.redBits); - glfwWindowHint(GLFW_BLUE_BITS, videoMode.blueBits); - glfwWindowHint(GLFW_GREEN_BITS, videoMode.greenBits); - - return initWithRect(viewname, ax::Rect(0, 0, (float)videoMode.width, (float)videoMode.height), 1.0f, false); -} - -void RenderViewImpl::setViewName(std::string_view viewName) -{ - RenderView::setViewName(viewName); - if (_mainWindow) - glfwSetWindowTitle(_mainWindow, _viewName.c_str()); -} - -bool RenderViewImpl::isKeyPressed(int key) const -{ - return _mainWindow && glfwGetKey(_mainWindow, key) == GLFW_PRESS; -} - -bool RenderViewImpl::isGfxContextReady() -{ - return nullptr != _mainWindow; -} - -void RenderViewImpl::end() -{ - _vkSurface = nullptr; - - if (_mainWindow) - { - glfwSetWindowShouldClose(_mainWindow, 1); - _mainWindow = nullptr; - } - // Release self. Otherwise, RenderViewImpl could not be freed. - release(); -} - -void RenderViewImpl::swapBuffers() -{ -#if AX_ENABLE_GL - if (_mainWindow && DriverContext::isOpenGL()) - glfwSwapBuffers(_mainWindow); -#endif -} - -bool RenderViewImpl::windowShouldClose() -{ - if (_mainWindow) - return glfwWindowShouldClose(_mainWindow) ? true : false; - else - return true; -} - -void RenderViewImpl::pollEvents() -{ - glfwPollEvents(); -} - -void RenderViewImpl::setIMEKeyboardState(bool bOpen) -{ - if (!_mainWindow) - return; - -#if !defined(__EMSCRIPTEN__) - glfwSetInputMode(_mainWindow, GLFW_IME, bOpen ? 1 : 0); - - if (bOpen) - glfwSetPreeditCursorRectangle(_mainWindow, static_cast(_mouseX / _inputScale), - static_cast(_mouseY / _inputScale), 1, 20); -#else - // Wasm IME handling is managed by the browser. This API is currently a no-op. - AX_UNUSED_PARAM(bOpen); -#endif -} - -#if AX_ICON_SET_SUPPORT -void RenderViewImpl::setIcon(std::string_view filename) const -{ - this->setIcon({filename}); -} - -void RenderViewImpl::setIcon(std::span filelist) const -{ - if (filelist.empty()) - return; - std::vector icons; - for (auto& filename : filelist) - { - Image* icon = new Image(); - if (icon->initWithImageFile(filename)) - { - icons.emplace_back(icon); - } - else - { - AX_SAFE_DELETE(icon); - } - } - - if (icons.empty()) - return; // No valid images - size_t iconsCount = icons.size(); - auto images = new GLFWimage[iconsCount]; - for (size_t i = 0; i < iconsCount; i++) - { - auto& image = images[i]; - auto& icon = icons[i]; - image.width = icon->getWidth(); - image.height = icon->getHeight(); - image.pixels = icon->getData(); - }; - - GLFWwindow* window = this->getWindow(); - glfwSetWindowIcon(window, iconsCount, images); - - AX_SAFE_DELETE_ARRAY(images); - for (auto&& icon : icons) - { - AX_SAFE_DELETE(icon); - } -} - -void RenderViewImpl::setDefaultIcon() const -{ - GLFWwindow* window = this->getWindow(); - glfwSetWindowIcon(window, 0, nullptr); -} -#endif /* AX_ICON_SET_SUPPORT */ - -void RenderViewImpl::setCursorVisible(bool isVisible) -{ - if (_mainWindow == NULL) - return; - - if (isVisible) - glfwSetInputMode(_mainWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - else - glfwSetInputMode(_mainWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); -} - -float RenderViewImpl::getWindowZoomFactor() const -{ - return _windowZoomFactor; -} - -bool RenderViewImpl::isFullscreen() const -{ - return (_monitor != nullptr); -} - -void RenderViewImpl::setFullscreen() -{ - setFullscreen(-1, -1, -1); -} - -void RenderViewImpl::setFullscreen(int w, int h, int refreshRate) -{ - auto monitor = glfwGetPrimaryMonitor(); - if (nullptr == monitor || monitor == _monitor) - { - return; - } - this->setFullscreen(monitor, w, h, refreshRate); -} - -void RenderViewImpl::setFullscreen(int monitorIndex) -{ - setFullscreen(monitorIndex, -1, -1, -1); -} - -void RenderViewImpl::setFullscreen(int monitorIndex, int w, int h, int refreshRate) -{ - int count = 0; - GLFWmonitor** monitors = glfwGetMonitors(&count); - if (monitorIndex < 0 || monitorIndex >= count) - { - return; - } - GLFWmonitor* monitor = monitors[monitorIndex]; - if (nullptr == monitor || _monitor == monitor) - { - return; - } - this->setFullscreen(monitor, w, h, refreshRate); -} - -void RenderViewImpl::setFullscreen(GLFWmonitor* monitor, int w, int h, int refreshRate) -{ - _monitor = monitor; - - const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); - if (w == -1) - w = videoMode->width; - if (h == -1) - h = videoMode->height; - if (refreshRate == -1) - refreshRate = videoMode->refreshRate; - - glfwSetWindowMonitor(_mainWindow, _monitor, 0, 0, w, h, refreshRate); -} - -void RenderViewImpl::setWindowed(int width, int height, bool borderless) -{ - if (!this->isFullscreen()) - { - glfwSetWindowAttrib(_mainWindow, GLFW_DECORATED, borderless ? GLFW_FALSE : GLFW_TRUE); - - if (glfwGetWindowAttrib(_mainWindow, GLFW_MAXIMIZED)) - glfwRestoreWindow(_mainWindow); - this->setWindowSize((float)width, (float)height); - } - else - { - width *= _windowZoomFactor; - height *= _windowZoomFactor; - const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); - int xpos = 0, ypos = 0; - glfwGetMonitorPos(_monitor, &xpos, &ypos); - xpos += (int)((videoMode->width - width) * 0.5f); - ypos += (int)((videoMode->height - height) * 0.5f); - _monitor = nullptr; - glfwSetWindowAttrib(_mainWindow, GLFW_DECORATED, borderless ? GLFW_FALSE : GLFW_TRUE); - glfwSetWindowMonitor(_mainWindow, nullptr, xpos, ypos, width, height, GLFW_DONT_CARE); -#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) - // on mac window will sometimes lose title when windowed - glfwSetWindowTitle(_mainWindow, _viewName.c_str()); -#endif - } -} - -Vec2 RenderViewImpl::getNativeWindowSize() const -{ - if (_mainWindow != nullptr) - { - int w = 0, h = 0; - glfwGetWindowSize(_mainWindow, &w, &h); - return Vec2(w, h); - } - return Vec2{}; -} - -void RenderViewImpl::getWindowPosition(int* xpos, int* ypos) -{ - if (_mainWindow != nullptr && getWindowPlatform() != WindowPlatform::Wayland) - glfwGetWindowPos(_mainWindow, xpos, ypos); -} - -int RenderViewImpl::getMonitorCount() const -{ - int count = 0; - glfwGetMonitors(&count); - return count; -} - -Vec2 RenderViewImpl::getMonitorSize() const -{ - GLFWmonitor* monitor = _monitor; - if (nullptr == monitor) - { - GLFWwindow* window = this->getWindow(); - monitor = glfwGetWindowMonitor(window); - } - if (nullptr == monitor) - { - monitor = glfwGetPrimaryMonitor(); - } - if (nullptr != monitor) - { - const GLFWvidmode* videoMode = glfwGetVideoMode(monitor); - Vec2 size = Vec2((float)videoMode->width, (float)videoMode->height); - return size; - } - return Vec2::ZERO; -} - -void RenderViewImpl::setWindowSizeLimits(int minwidth, int minheight, int maxwidth, int maxheight) -{ - if (_mainWindow == NULL) - return; - - glfwSetWindowSizeLimits(_mainWindow, minwidth, minheight, maxwidth, maxheight); -} - -void RenderViewImpl::onGLFWFramebufferSizeCallback(GLFWwindow* window, int fbWidth, int fbHeight) -{ - AXLOGD("RenderViewImpl::onGLFWFramebufferSizeCallback: ({}, {})", fbWidth, fbHeight); - - updateRenderSurface(fbWidth, fbHeight, SurfaceUpdateFlag::RenderSizeChanged); -} - -void RenderViewImpl::onGLFWWindowSizeCallback(GLFWwindow* /*window*/, int w, int h) -{ - AXLOGD("RenderViewImpl::onGLFWWindowSizeCallback: ({}, {})", w, h); - - updateScaledWindowSize(w, h, SurfaceUpdateFlag::WindowSizeChanged); - - Size size(w, h); - - Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderViewImpl::EVENT_WINDOW_RESIZED, &size); -} - -void RenderViewImpl::setWindowZoomFactor(float zoomFactor) -{ - AXASSERT(zoomFactor > 0.0f, "zoomFactor must be larger than 0"); - - if (std::abs(_windowZoomFactor - zoomFactor) < FLT_EPSILON) - return; - - _windowZoomFactor = zoomFactor; - applyWindowSize(); -} - -void RenderViewImpl::setWindowSize(float width, float height) -{ - if (width == 0 || height == 0) - return; - Vec2 requestSize{width, height}; - if (requestSize.equals(_windowSize)) - return; - - _windowSize.set(width, height); - applyWindowSize(); -} - -void RenderViewImpl::updateScaledWindowSize(int w, int h, uint8_t updateFlag) -{ - updateRenderScale(); - - double scaledWidth = w / (double)_windowZoomFactor; - double scaledHeight = h / (double)_windowZoomFactor; - - // Translate to logical size on platforms where pixels and screen coordinates always map 1:1 (Win32, X11) - // Note: wasm coordinates not map 1:1 when _renderScaleMode is RenderScaleMode::Physical - if (_renderScaleMode == RenderScaleMode::Physical) - { - auto windowPlatform = getWindowPlatform(); - if (windowPlatform == WindowPlatform::Win32 || windowPlatform == WindowPlatform::X11) - { - const auto factor = (1 / (double)_renderScale); - scaledWidth *= factor; - scaledHeight *= factor; - } - } - - Vec2 scaledSize{static_cast(std::round(scaledWidth)), static_cast(std::round(scaledHeight))}; - if (!scaledSize.equals(_windowSize)) - updateRenderSurface(scaledSize.width, scaledSize.height, updateFlag); -} - -void RenderViewImpl::applyWindowSize() -{ - double unscaledWidth = _windowSize.width * _windowZoomFactor, - unscaledHeight = _windowSize.height * _windowZoomFactor; - // Translate to physical size on platforms where pixels and screen coordinates always map 1:1 - if (_renderScaleMode == RenderScaleMode::Physical) - { - auto windowPlatform = getWindowPlatform(); - if (windowPlatform == WindowPlatform::Win32 || windowPlatform == WindowPlatform::X11) - { - unscaledWidth *= _renderScale; - unscaledHeight *= _renderScale; - } - } - glfwSetWindowSize(_mainWindow, static_cast(std::lround(unscaledWidth)), - static_cast(std::lround(unscaledHeight))); - - // process platform that window size callback not trigger(wayland) - maybeDispatchResizeEvent(SurfaceUpdateFlag::WindowSizeChanged); -} - -/** - * Updates the render scale and input scale factors based on the current platform - * and render scale mode. - * - * - On platforms where screen coordinates map 1:1 to physical pixels (Win32, X11), - * high-DPI scaling is only applied when in Physical mode. In this case, _inputScale - * always 1.0 - * . - * - On other platforms (e.g., macOS, Wayland), _renderScale is still queried to adjust r - * endering for high-DPI displays (e.g., viewport size). and _inputScale shoud same with - * render scale to converts from screen coordinates to the render view's logical coordinate space - * - * This function uses glfwGetWindowContentScale() to retrieve the current content scale - * factor, which may change when moving the window between monitors with different DPI - * settings. - * - * renderScale: for computing logical window size - * inputScale: for transform input axis - */ -void RenderViewImpl::updateRenderScale() -{ - auto windowPlatform = getWindowPlatform(); - if (windowPlatform == WindowPlatform::Win32 || windowPlatform == WindowPlatform::X11 || - windowPlatform == WindowPlatform::Web) - { - if (_renderScaleMode == RenderScaleMode::Physical) - { - float ignoreVal; - glfwGetWindowContentScale(_mainWindow, &_renderScale, &ignoreVal); - _inputScale = windowPlatform != WindowPlatform::Web ? 1.0f : _renderScale; - } - else - { - _inputScale = _renderScale = 1.0f; - } - } - else - { - float ignoreVal; - glfwGetWindowContentScale(_mainWindow, &_renderScale, &ignoreVal); - _inputScale = _renderScale; - } -} - -void RenderViewImpl::onGLFWError(int errorID, const char* errorDesc) -{ - if (_mainWindow) - { - _glfwError = fmt::format("GLFWError #{} Happen, {}", errorID, errorDesc); - } - else - { - _glfwError.append(fmt::format("GLFWError #{} Happen, {}\n", errorID, errorDesc)); - } - AXLOGE("{}", _glfwError); -} - -void RenderViewImpl::onGLFWMouseCallBack(GLFWwindow* /*window*/, int button, int action, int /*modify*/) -{ - if (!_isTouchDevice) - { - if (GLFW_MOUSE_BUTTON_LEFT == button) - { - if (GLFW_PRESS == action) - { - _captured = true; - if (this->getViewportRect().equals(ax::Rect::ZERO) || - this->getViewportRect().containsPoint(Vec2(_mouseX, _mouseY))) - { - intptr_t id = 0; - this->handleTouchesBegin(1, &id, &_mouseX, &_mouseY); - } - } - else if (GLFW_RELEASE == action) - { - if (_captured) - { - _captured = false; - intptr_t id = 0; - this->handleTouchesEnd(1, &id, &_mouseX, &_mouseY); - } - } - } - } - - float cursorX = transformInputX(_mouseX); - float cursorY = transformInputY(_mouseY); - - if (GLFW_PRESS == action) - { - _currentMouseEvent.setMouseInfo(cursorX, cursorY, static_cast(button), - ax::EventMouse::MouseEventType::MOUSE_DOWN); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); - } - else if (GLFW_RELEASE == action) - { - _currentMouseEvent.setMouseInfo(cursorX, cursorY, static_cast(button), - ax::EventMouse::MouseEventType::MOUSE_UP); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); - } -} - -void RenderViewImpl::onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y) -{ - _mouseX = static_cast(x); - _mouseY = static_cast(y); - - _mouseX *= _inputScale; - _mouseY *= _inputScale; - - if (!_isTouchDevice) - { - if (_captured) - { - intptr_t id = 0; - this->handleTouchesMove(1, &id, &_mouseX, &_mouseY); - } - } - - float cursorX = transformInputX(_mouseX); - float cursorY = transformInputY(_mouseY); - - _currentMouseEvent.setMouseInfo(cursorX, cursorY, checkMouseButton(window), - ax::EventMouse::MouseEventType::MOUSE_MOVE); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); -} - -#if defined(__EMSCRIPTEN__) -void RenderViewImpl::onWebOrientationChangeCallback(int /*eventType*/, const EmscriptenOrientationChangeEvent* e) -{ - AXLOGD("onWebOrientationChangeCallback: orientationIndex:{}, orientationAngle:{}", e->orientationIndex, - e->orientationAngle); - - if (s_fullscreenState->isFullscreen) - { - int screenWidth = 0, screenHeight = 0; - emscripten_get_screen_size(&screenWidth, &screenHeight); - AXLOGD("Screen size after orientation change: ({}, {})", screenWidth, screenHeight); - glfwSetWindowSize(_mainWindow, screenWidth, screenHeight); - } - // else: browser handling canvas size -} - -void RenderViewImpl::onWebFullscreenCallback(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e) -{ - if (e->isFullscreen == s_fullscreenState->isFullscreen) - return; - - auto& windowedSize = s_fullscreenState->windowedSize; - s_fullscreenState->isFullscreen = e->isFullscreen; - if (e->isFullscreen) - { - glfwGetWindowSize(_mainWindow, &windowedSize.x, &windowedSize.y); - - AXLOGD("onWebFullscreenCallback: enter full screen: ({},{}) => ({},{})", windowedSize.x, windowedSize.y, - e->screenWidth, e->screenHeight); - glfwSetWindowSize(_mainWindow, e->screenWidth, e->screenHeight); - } - else - { - AXLOGD("onWebFullscreenCallback: exit full screen => ({},{}) => ({},{})", e->screenWidth, e->screenHeight, - windowedSize.x, windowedSize.y); - glfwSetWindowSize(_mainWindow, windowedSize.x, windowedSize.y); - } -} - -void RenderViewImpl::onWebTouchCallback(int eventType, const EmscriptenTouchEvent* touchEvent) -{ - float boundingX = EM_ASM_INT(return canvas.getBoundingClientRect().left); - float boundingY = EM_ASM_INT(return canvas.getBoundingClientRect().top); - int canvasWidth, canvasHeight; - emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); - double cssWidth, cssHeight; - emscripten_get_element_css_size("#canvas", &cssWidth, &cssHeight); - const auto zoomX = canvasWidth / cssWidth; - const auto zommY = canvasHeight / cssHeight; - - int numTouches = touchEvent->numTouches; - _touchesId.resize(numTouches); - _touchesX.resize(numTouches); - _touchesY.resize(numTouches); - for (int i = 0; i < numTouches; i++) - { - _touchesId[i] = (touchEvent->touches[i].identifier); - // convert coords screen(origin:left-top) to canvas - _touchesX[i] = ((touchEvent->touches[i].targetX - boundingX) * zoomX); - _touchesY[i] = ((touchEvent->touches[i].targetY - boundingY) * zommY); - } - if (numTouches) - { - switch (eventType) - { - case EMSCRIPTEN_EVENT_TOUCHSTART: - _captured = true; - handleTouchesBegin(numTouches, _touchesId.data(), _touchesX.data(), _touchesY.data()); - break; - case EMSCRIPTEN_EVENT_TOUCHEND: - handleTouchesEnd(numTouches, _touchesId.data(), _touchesX.data(), _touchesY.data()); - _captured = false; - break; - case EMSCRIPTEN_EVENT_TOUCHMOVE: - if (_captured) - handleTouchesMove(numTouches, _touchesId.data(), _touchesX.data(), _touchesY.data()); - break; - case EMSCRIPTEN_EVENT_TOUCHCANCEL: - handleTouchesCancel(numTouches, _touchesId.data(), _touchesX.data(), _touchesY.data()); - _captured = false; - break; - } - } -} - -void RenderViewImpl::onWebClickCallback() -{ - if (!_isTouchDevice) - { - if (_captured) - { - _captured = false; - intptr_t id = 0; - this->handleTouchesCancel(1, &id, &_mouseX, &_mouseY); - } - } -} -#endif - -void RenderViewImpl::onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y) -{ - x *= _inputScale; - y *= _inputScale; - - float cursorX = transformInputX(_mouseX); - float cursorY = transformInputY(_mouseY); - _currentMouseEvent.setScrollData((float)x, -(float)y); - _currentMouseEvent.setMouseInfo(cursorX, cursorY, checkMouseButton(window), - EventMouse::MouseEventType::MOUSE_SCROLL); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); -} - -void RenderViewImpl::onGLFWKeyCallback(GLFWwindow* /*window*/, int key, int /*scancode*/, int action, int /*mods*/) -{ - const auto isKeyDown = action != GLFW_RELEASE; - EventKeyboard event(g_keyCodeMap[key], isKeyDown, action == GLFW_REPEAT); - auto dispatcher = Director::getInstance()->getEventDispatcher(); - dispatcher->dispatchEvent(&event); - - if (isKeyDown && !event.isStopped()) - { - switch (g_keyCodeMap[key]) - { - case EventKeyboard::KeyCode::KEY_BACKSPACE: - IMEDispatcher::sharedDispatcher()->dispatchDeleteBackward(1); - break; - case EventKeyboard::KeyCode::KEY_HOME: - case EventKeyboard::KeyCode::KEY_KP_HOME: - case EventKeyboard::KeyCode::KEY_DELETE: - case EventKeyboard::KeyCode::KEY_KP_DELETE: - case EventKeyboard::KeyCode::KEY_END: - case EventKeyboard::KeyCode::KEY_LEFT_ARROW: - case EventKeyboard::KeyCode::KEY_RIGHT_ARROW: - case EventKeyboard::KeyCode::KEY_ESCAPE: - IMEDispatcher::sharedDispatcher()->dispatchControlKey(g_keyCodeMap[key]); - break; - default: - break; - } - } -} - -void RenderViewImpl::onGLFWCharCallback(GLFWwindow* /*window*/, unsigned int charCode) -{ - std::string utf8String; - text_utils::UTF32ToUTF8(std::u32string_view{(char32_t*)&charCode, (size_t)1}, utf8String); - static std::unordered_set controlUnicode = { - "\xEF\x9C\x80", // up - "\xEF\x9C\x81", // down - "\xEF\x9C\x82", // left - "\xEF\x9C\x83", // right - "\xEF\x9C\xA8", // delete - "\xEF\x9C\xA9", // home - "\xEF\x9C\xAB", // end - "\xEF\x9C\xAC", // pageup - "\xEF\x9C\xAD", // pagedown - "\xEF\x9C\xB9" // clear - }; - // Check for send control key - if (controlUnicode.find(utf8String) == controlUnicode.end()) - { - IMEDispatcher::sharedDispatcher()->dispatchInsertText(utf8String.c_str(), utf8String.size()); - } -} - -void RenderViewImpl::onGLFWWindowPosCallback(GLFWwindow* /*window*/, int x, int y) -{ - auto director = Director::getInstance(); - director->setViewport(); - - Vec2 pos(x, y); - director->getEventDispatcher()->dispatchCustomEvent(RenderViewImpl::EVENT_WINDOW_POSITIONED, &pos); -} - -void RenderViewImpl::onGLFWWindowIconifyCallback(GLFWwindow* /*window*/, int iconified) -{ - if (iconified == GL_TRUE) - { - Application::getInstance()->applicationDidEnterBackground(); - } - else - { - Application::getInstance()->applicationWillEnterForeground(); - } -} - -void RenderViewImpl::onGLFWWindowFocusCallback(GLFWwindow* /*window*/, int focused) -{ - if (focused == GL_TRUE) - { - Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderViewImpl::EVENT_WINDOW_FOCUSED, - nullptr); - } - else - { - Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderViewImpl::EVENT_WINDOW_UNFOCUSED, - nullptr); - } -} - -void RenderViewImpl::onGLFWWindowCloseCallback(GLFWwindow* window) -{ - bool isClose = true; - Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderViewImpl::EVENT_WINDOW_CLOSE, &isClose); - if (isClose == false) - { - glfwSetWindowShouldClose(window, 0); - } -} - -} // namespace ax - -#if defined(__EMSCRIPTEN__) -extern "C" { -void axmol_onwebclickcallback() -{ - ax::GLFWEventHandler::onWebClickCallback(); -} -} -#endif diff --git a/axmol/platform/ios/Application-ios.h b/axmol/platform/ios/Application-ios.h index 40548254198b..398e5594c0e1 100644 --- a/axmol/platform/ios/Application-ios.h +++ b/axmol/platform/ios/Application-ios.h @@ -27,12 +27,12 @@ THE SOFTWARE. #pragma once #include "axmol/platform/Common.h" -#include "axmol/platform/ApplicationBase.h" +#include "axmol/platform/ApplicationCore.h" namespace ax { -class AX_DLL Application : public ApplicationBase +class AX_DLL Application : public ApplicationCore { public: /** @@ -48,12 +48,6 @@ class AX_DLL Application : public ApplicationBase */ int run(); - /** - @brief Get the current application instance. - @return Current application instance pointer. - */ - static Application* getInstance(); - /** @brief Callback by Director for limit FPS. @param interval The time, expressed in seconds, between current frame and next. @@ -89,8 +83,8 @@ class AX_DLL Application : public ApplicationBase */ bool openURL(std::string_view url) override; -protected: - static Application* sm_pSharedApplication; +private: + void postBoundaryTaskSignal(); }; } // namespace ax diff --git a/axmol/platform/ios/Application-ios.mm b/axmol/platform/ios/Application-ios.mm index 5e41f621516c..8a9c7ec7701c 100644 --- a/axmol/platform/ios/Application-ios.mm +++ b/axmol/platform/ios/Application-ios.mm @@ -31,14 +31,12 @@ of this software and associated documentation files (the "Software"), to deal #import "axmol/math/Math.h" #import "axmol/platform/ios/DirectorCaller-ios.h" #import "axmol/base/Utils.h" +#import "axmol/base/Director.h" #include "AxmolAppController.h" namespace ax { - -Application* Application::sm_pSharedApplication = nullptr; - // Force the Objective‑C runtime to reference AxmolAppController, // ensuring the class symbol is linked into the final binary // (prevents the linker from stripping it out when inside a static library). @@ -46,14 +44,14 @@ of this software and associated documentation files (the "Software"), to deal Application::Application() { - AX_ASSERT(!sm_pSharedApplication); - sm_pSharedApplication = this; + AX_ASSERT(!s_axmolApp); + s_axmolApp = this; } Application::~Application() { - AX_ASSERT(this == sm_pSharedApplication); - sm_pSharedApplication = 0; + AX_ASSERT(this == s_axmolApp); + s_axmolApp = 0; } int Application::run() @@ -70,16 +68,6 @@ of this software and associated documentation files (the "Software"), to deal [[CCDirectorCaller sharedDirectorCaller] setAnimationInterval:interval]; } -///////////////////////////////////////////////////////////////////////////////////////////////// -// static member function -////////////////////////////////////////////////////////////////////////////////////////////////// - -Application* Application::getInstance() -{ - AX_ASSERT(sm_pSharedApplication); - return sm_pSharedApplication; -} - const char* Application::getCurrentLanguageCode() { static char code[3] = {0}; @@ -137,4 +125,11 @@ of this software and associated documentation files (the "Software"), to deal [application openURL:nsUrl options:@{} completionHandler:nil]; } +void Application::postBoundaryTaskSignal() +{ + [[NSOperationQueue mainQueue] addOperationWithBlock:^(void) { + ax::Director::getInstance()->performFrameBoundaryTasks(); + }]; +} + } // namespace ax diff --git a/axmol/platform/ios/AxmolAppController.mm b/axmol/platform/ios/AxmolAppController.mm index f4977ba92e4c..85f4b10bf75b 100644 --- a/axmol/platform/ios/AxmolAppController.mm +++ b/axmol/platform/ios/AxmolAppController.mm @@ -24,7 +24,7 @@ of this software and associated documentation files (the "Software"), to deal #import "axmol/platform/ios/AxmolAppController.h" #import "axmol/platform/ios/AxmolViewController.h" -#include "axmol/platform/ios/RenderViewImpl-ios.h" +#include "axmol/platform/ios/RenderView-ios.h" #include "axmol/base/Director.h" #include "axmol/platform/Application.h" @@ -43,14 +43,14 @@ - (UIViewController*)createRootViewController - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - auto axmolApp = Application::getInstance(); + auto axmolApp = ApplicationCore::getInstance(); // Initialize the Axmol Engine attributes axmolApp->initContextAttrs(); // Override point for customization after application launch. - auto renderView = ax::RenderViewImpl::createWithFullscreen("axmol3"); + auto renderView = ax::RenderView::createWithFullscreen("axmol3"); _viewController = [self createRootViewController]; renderView->showWindow(_viewController); diff --git a/axmol/platform/ios/AxmolViewController.mm b/axmol/platform/ios/AxmolViewController.mm index fc3c6c69dca8..f2053d642cfb 100644 --- a/axmol/platform/ios/AxmolViewController.mm +++ b/axmol/platform/ios/AxmolViewController.mm @@ -24,7 +24,7 @@ of this software and associated documentation files (the "Software"), to deal #import "axmol/platform/ios/AxmolViewController.h" #import "axmol/platform/ios/RenderHostView-ios.h" -#import "axmol/platform/ios/RenderViewImpl-ios.h" +#import "axmol/platform/ios/RenderView-ios.h" #include "axmol/platform/Device.h" #include "axmol/platform/Application.h" #include "axmol/base/Director.h" @@ -50,12 +50,12 @@ - (void)loadView // create platform render view auto r = [[UIScreen mainScreen] bounds]; RenderHostView* hostView = [RenderHostView viewWithFrame:r - pixelFormat:(int)RenderViewImpl::_pixelFormat - depthFormat:(int)RenderViewImpl::_depthFormat + pixelFormat:(int)RenderView::_pixelFormat + depthFormat:(int)RenderView::_depthFormat preserveBackbuffer:NO sharegroup:nil - multiSampling:RenderViewImpl::_multisamplingCount > 0 ? YES : NO - numberOfSamples:RenderViewImpl::_multisamplingCount]; + multiSampling:RenderView::_multisamplingCount > 0 ? YES : NO + numberOfSamples:RenderView::_multisamplingCount]; // Not available on tvOS #if !defined(AX_TARGET_OS_TVOS) diff --git a/axmol/platform/ios/Device-ios.mm b/axmol/platform/ios/Device-ios.mm index dee568b93be5..ec9ebbdaa81a 100644 --- a/axmol/platform/ios/Device-ios.mm +++ b/axmol/platform/ios/Device-ios.mm @@ -29,7 +29,7 @@ of this software and associated documentation files (the "Software"), to deal #include "axmol/platform/Device.h" #include "axmol/base/Types.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventAcceleration.h" +#include "axmol/base/AccelerationEvent.h" #include "axmol/base/Director.h" #include "axmol/platform/apple/Device-apple.h" @@ -304,7 +304,7 @@ - (void)accelerometer:(CMAccelerometerData*)accelerometerData NSAssert(false, @"unknown orientation"); } - ax::EventAcceleration event(*_acceleration); + ax::AccelerationEvent event(*_acceleration); auto dispatcher = ax::Director::getInstance()->getEventDispatcher(); dispatcher->dispatchEvent(&event); } @@ -995,4 +995,40 @@ static bool _initWithString(std::string_view text, return resolvedOrientation; } +void Device::getClipboardText(std::function callback) +{ + if (!callback) + return; +#if TARGET_OS_IOS + @autoreleasepool + { + NSString* text = [UIPasteboard generalPasteboard].string; + if (text) + callback(std::string_view([text UTF8String])); + else + callback(std::string_view{}); + } +#endif +} + +void Device::setClipboardText(std::string_view text) +{ +#if TARGET_OS_IOS + @autoreleasepool + { + NSString* s = [[NSString alloc] initWithBytes:text.data() length:text.length() encoding:NSUTF8StringEncoding]; + if (!s) + s = @""; + [UIPasteboard generalPasteboard].string = s; + } +#endif +} + +void Device::clearClipboard() +{ +#if TARGET_OS_IOS + [UIPasteboard generalPasteboard].string = nil; +#endif +} + } // namespace ax diff --git a/axmol/platform/ios/InputView-ios.h b/axmol/platform/ios/InputView-ios.h index e61d254cce69..f2ac4670cf0e 100644 --- a/axmol/platform/ios/InputView-ios.h +++ b/axmol/platform/ios/InputView-ios.h @@ -25,5 +25,11 @@ THE SOFTWARE. #import -@interface TextInputView : UIView +@interface InputHostView : UIView + +// Show the system edit menu (Copy/Paste/Cut) at the given view point (UIKit points). +// The view will NOT perform clipboard operations itself; it dispatches menu actions to InputSystem. +- (void)showContextMenu:(CGPoint)point hasText:(BOOL)hasText hasSelection:(BOOL)hasSelection readOnly:(BOOL)readOnly; +- (void)hideContextMenu; + @end diff --git a/axmol/platform/ios/InputView-ios.mm b/axmol/platform/ios/InputView-ios.mm index 3ffd437b7988..c2f421f4f8d9 100644 --- a/axmol/platform/ios/InputView-ios.mm +++ b/axmol/platform/ios/InputView-ios.mm @@ -22,30 +22,33 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ #import "axmol/platform/ios/InputView-ios.h" -#import "axmol/base/IMEDispatcher.h" +#import "axmol/base/InputSystem.h" #import "axmol/base/Director.h" -@interface TextInputView () +struct InputState +{ + bool hasText; + bool hasSelection; + bool readOnly; +}; + +@interface InputHostView () -@property(nonatomic) NSString* myMarkedText; +@property(nonatomic) NSString* markedText; +@property(nonatomic) struct InputState inputState; @end -@implementation TextInputView +@implementation InputHostView -@synthesize myMarkedText; -@synthesize hasText; -@synthesize beginningOfDocument; -@synthesize endOfDocument; -@synthesize markedTextStyle; -@synthesize tokenizer; @synthesize autocorrectionType; - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { - self.myMarkedText = nil; + self.contentScaleFactor = [[UIScreen mainScreen] scale]; + self.markedText = nil; self.autocorrectionType = UITextAutocorrectionTypeNo; } @@ -55,7 +58,7 @@ - (instancetype)initWithFrame:(CGRect)frame - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; // remove keyboard notification - [self.myMarkedText release]; + [self.markedText release]; [self removeFromSuperview]; [super dealloc]; } @@ -65,6 +68,25 @@ - (BOOL)canBecomeFirstResponder return YES; } +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event +{ + auto inputDisp = ax::InputSystem::getInstance(); + if (inputDisp->hasAttachedDelegate()) + { + ax::Vec2 pt{static_cast(point.x * [self contentScaleFactor]), + static_cast(point.y * [self contentScaleFactor])}; + auto director = ax::Director::getInstance(); + auto renderView = director->getRenderView(); + // convert axmol screen to world coordinate + pt = director->screenToWorld(pt); + + bool keep = inputDisp->dispatchHitTestWithIME(pt); + if (keep) + return NO; + } + return YES; +} + - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { [self resignFirstResponder]; @@ -93,23 +115,23 @@ - (UITextRange*)selectedTextRange - (void)deleteBackward { - if (nil != self.myMarkedText) + if (nil != self.markedText) { - [self.myMarkedText release]; - self.myMarkedText = nil; + [self.markedText release]; + self.markedText = nil; } - ax::IMEDispatcher::sharedDispatcher()->dispatchDeleteBackward(1); + ax::InputSystem::getInstance()->dispatchDeleteBackward(1u); } - (void)insertText:(nonnull NSString*)text { - if (nil != self.myMarkedText) + if (nil != self.markedText) { - [self.myMarkedText release]; - self.myMarkedText = nil; + [self.markedText release]; + self.markedText = nil; } const char* pszText = [text cStringUsingEncoding:NSUTF8StringEncoding]; - ax::IMEDispatcher::sharedDispatcher()->dispatchInsertText(pszText, strlen(pszText)); + ax::InputSystem::getInstance()->dispatchInsertText(std::string_view{pszText, strlen(pszText)}); } - (NSWritingDirection)baseWritingDirectionForPosition:(nonnull UITextPosition*)position @@ -204,22 +226,22 @@ - (void)setBaseWritingDirection:(NSWritingDirection)writingDirection forRange:(n - (void)setMarkedText:(nullable NSString*)markedText selectedRange:(NSRange)selectedRange { AXLOGD("setMarkedText"); - if (markedText == self.myMarkedText) + if (markedText == self.markedText) { return; } - if (nil != self.myMarkedText) + if (nil != self.markedText) { - [self.myMarkedText release]; + [self.markedText release]; } - self.myMarkedText = markedText; - [self.myMarkedText retain]; + self.markedText = markedText; + [self.markedText retain]; } - (UITextRange*)markedTextRange { AXLOGD("markedTextRange"); - if (nil != self.myMarkedText) + if (nil != self.markedText) { return [[[UITextRange alloc] init] autorelease]; } @@ -229,9 +251,9 @@ - (UITextRange*)markedTextRange - (nullable NSString*)textInRange:(nonnull UITextRange*)range { AXLOGD("textInRange"); - if (nil != self.myMarkedText) + if (nil != self.markedText) { - return self.myMarkedText; + return self.markedText; } return nil; } @@ -246,17 +268,136 @@ - (nullable UITextRange*)textRangeFromPosition:(nonnull UITextPosition*)fromPosi - (void)unmarkText { AXLOGD("unmarkText"); - if (nil == self.myMarkedText) + if (nil == self.markedText) { return; } - const char* pszText = [self.myMarkedText cStringUsingEncoding:NSUTF8StringEncoding]; - ax::IMEDispatcher::sharedDispatcher()->dispatchInsertText(pszText, strlen(pszText)); - [self.myMarkedText release]; - self.myMarkedText = nil; + const char* pszText = [self.markedText cStringUsingEncoding:NSUTF8StringEncoding]; + ax::InputSystem::getInstance()->dispatchInsertText(std::string_view{pszText, strlen(pszText)}); + [self.markedText release]; + self.markedText = nil; } - (void)encodeWithCoder:(nonnull NSCoder*)coder {} +#pragma mark - System edit menu (dispatch to engine) + +#if TARGET_OS_IOS + +// Show the system edit menu at the given UIKit point (points, not pixels). +// This view does NOT perform clipboard read/write itself; it dispatches actions to the engine. +// Insert into InputHostView implementation + +- (void)showContextMenu:(CGPoint)point hasText:(BOOL)hasText hasSelection:(BOOL)hasSelection readOnly:(BOOL)readOnly +{ + if (![self isFirstResponder]) + { + [self becomeFirstResponder]; + } + + _inputState.hasSelection = hasSelection; + _inputState.hasText = hasText; + _inputState.readOnly = readOnly; + + UIMenuController* menu = [UIMenuController sharedMenuController]; + CGRect targetRect = CGRectMake(point.x, point.y, 1.0f, 1.0f); + + // Ensure UIKit calls on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + if (@available(iOS 13.0, *)) + { + // iOS 13+ recommended API + [menu showMenuFromView:self rect:targetRect]; + } + else + { + // Fallback for older iOS + [menu setTargetRect:targetRect inView:self]; + [menu setMenuVisible:YES animated:YES]; + } + }); +} + +- (void)hideContextMenu +{ + UIMenuController* menu = [UIMenuController sharedMenuController]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (@available(iOS 13.0, *)) + { + [menu hideMenuFromView:self]; + } + else + { + [menu setMenuVisible:NO animated:YES]; + } + }); +} + +// Decide which actions are enabled. We query the engine synchronously for selection existence. +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +{ + if (action == @selector(copy:)) + { + return _inputState.hasSelection || _inputState.hasText; + } + else if (action == @selector(cut:)) + { + return _inputState.hasSelection && !_inputState.readOnly; + } + else if (action == @selector(selectAll:)) + { + return _inputState.hasText; + } + if (action == @selector(paste:)) + { + // Enable paste if UIPasteboard has a string OR let engine decide (we enable if pasteboard non-empty). + return !_inputState.readOnly && ([UIPasteboard generalPasteboard].string != nil); + } + return [super canPerformAction:action withSender:sender]; +} + +# pragma mark - Menu actions (dispatch to engine) + +// Copy: tell engine to perform copy (engine should obtain selection and write to UIPasteboard) +- (void)copy:(id)sender +{ + // Dispatch a copy request; engine will handle reading selection and writing to system clipboard. + ax::InputSystem::getInstance()->dispatchPerformEditAction(ax::EditAction::Copy); + [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES]; +} + +// Paste: ask engine to perform paste. We can either read UIPasteboard here and pass text, +// or let engine read the UIPasteboard itself. Here we dispatch a paste request so engine controls behavior. +- (void)paste:(id)sender +{ + ax::InputSystem::getInstance()->dispatchPerformEditAction(ax::EditAction::Paste); + [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES]; +} + +// Cut: tell engine to perform cut (engine should copy selection to clipboard and delete it) +- (void)cut:(id)sender +{ + ax::InputSystem::getInstance()->dispatchPerformEditAction(ax::EditAction::Cut); + [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES]; +} + +- (void)selectAll:(id)sender +{ + ax::InputSystem::getInstance()->dispatchPerformEditAction(ax::EditAction::SelectAll); + [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES]; +} + +#endif + +@synthesize endOfDocument; + +@synthesize hasText; + +@synthesize markedTextStyle; + +@synthesize tokenizer; + +@synthesize beginningOfDocument; + @end diff --git a/axmol/platform/ios/RenderHostView-ios.h b/axmol/platform/ios/RenderHostView-ios.h index 99f1506bc0b0..74dcdd9a101c 100644 --- a/axmol/platform/ios/RenderHostView-ios.h +++ b/axmol/platform/ios/RenderHostView-ios.h @@ -140,4 +140,8 @@ Copyright (C) 2008 Apple Inc. All Rights Reserved. - (void)showKeyboard; - (void)hideKeyboard; + +- (void)showContextMenu:(CGPoint)point hasText:(BOOL)hasText hasSelection:(BOOL)hasSelection readOnly:(BOOL)readOnly; +- (void)hideContextMenu; + @end diff --git a/axmol/platform/ios/RenderHostView-ios.mm b/axmol/platform/ios/RenderHostView-ios.mm index 0cad17857d0a..55722f9ffe66 100644 --- a/axmol/platform/ios/RenderHostView-ios.mm +++ b/axmol/platform/ios/RenderHostView-ios.mm @@ -56,9 +56,10 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE #import #import "axmol/base/Director.h" -#import "axmol/base/Touch.h" -#import "axmol/base/IMEDispatcher.h" +#import "axmol/base/PointerEvent.h" +#import "axmol/base/InputSystem.h" #import "axmol/platform/ios/InputView-ios.h" +#import "axmol/platform/ios/RenderView-ios.h" #if AX_ENABLE_MTL # import @@ -66,7 +67,6 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # import "axmol/rhi/metal/UtilsMTL.h" #endif #if AX_ENABLE_GL -# import "axmol/platform/ios/RenderViewImpl-ios.h" # import "axmol/platform/ios/ES3Renderer-ios.h" # import "axmol/platform/ios/OpenGL_Internal-ios.h" #endif @@ -80,15 +80,13 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE #define IOS_MAX_TOUCHES_COUNT 10 @interface RenderHostView () -@property(nonatomic) TextInputView* textInputView; +@property(nonatomic) InputHostView* inputHost; @property(nonatomic, readwrite, assign) BOOL isKeyboardShown; @property(nonatomic, copy) NSNotification* keyboardShowNotification; -@property(nonatomic, assign) CGRect savedBounds; @end @implementation RenderHostView -@synthesize backingSize = backingSize_; @synthesize pixelFormat = pixelformat_, depthFormat = depthFormat_; #if AX_ENABLE_GL @synthesize context = context_; @@ -96,7 +94,6 @@ @implementation RenderHostView @synthesize multiSampling = multiSampling_; @synthesize keyboardShowNotification = keyboardShowNotification_; @synthesize isKeyboardShown = isKeyboardShown_; -@synthesize savedBounds = savedBounds_; static ax::Rect convertKeyboardRectToViewport(CGRect rect, CGSize viewSize) { @@ -188,9 +185,8 @@ - (id)initWithFrame:(CGRect)frame if ((self = [super initWithFrame:frame])) { - self.textInputView = [[TextInputView alloc] initWithFrame:frame]; + self.inputHost = [[InputHostView alloc] initWithFrame:frame]; - savedBounds_ = [self bounds]; self.keyboardShowNotification = nil; if ([self respondsToSelector:@selector(setContentScaleFactor:)]) { @@ -226,12 +222,9 @@ - (id)initWithCoder:(NSCoder*)aDecoder { if ((self = [super initWithCoder:aDecoder])) { - self.textInputView = [[TextInputView alloc] initWithCoder:aDecoder]; - if (DriverContext::isMetal()) - { - backingSize_ = [self bounds].size; - } - else + self.inputHost = [[InputHostView alloc] initWithCoder:aDecoder]; +#if AX_ENABLE_GL + if (DriverContext::isOpenGL()) { CAEAGLLayer* eaglLayer = (CAEAGLLayer*)[self layer]; @@ -239,14 +232,13 @@ - (id)initWithCoder:(NSCoder*)aDecoder depthFormat_ = (int)ax::PixelFormat::D24S8; multiSampling_ = NO; requestedSamples_ = 0; - backingSize_ = [eaglLayer bounds].size; - if (![self setupSurfaceWithSharegroup:nil]) { [self release]; return nil; } } +#endif } return self; @@ -300,7 +292,7 @@ - (void)dealloc if (DriverContext::isOpenGL()) [renderer_ release]; #endif - [self.textInputView release]; + [self.inputHost release]; [super dealloc]; } @@ -310,24 +302,25 @@ - (void)layoutSubviews if (!director->isValid()) return; - savedBounds_ = [self bounds]; - self.textInputView.bounds = savedBounds_; - if (DriverContext::isMetal()) - { - backingSize_ = savedBounds_.size; - backingSize_.width *= self.contentScaleFactor; - backingSize_.height *= self.contentScaleFactor; - } - else - { + auto bounds = [self bounds]; + self.inputHost.bounds = bounds; + +// 2. Handle GL context resize if necessary #if AX_ENABLE_GL + if (DriverContext::isOpenGL()) + { [renderer_ resizeFromLayer:(CAEAGLLayer*)self.layer]; - backingSize_ = [renderer_ backingSize]; -#endif } - auto renderView = director->getRenderView(); +#endif + + auto renderView = static_cast(director->getRenderView()); if (renderView) - renderView->updateRenderSurface(backingSize_.width, backingSize_.height, ax::RenderView::AllUpdates); + { + auto& viewSize = bounds.size; + renderView->updateSurfaceMetrics( + ax::Vec2(static_cast(viewSize.width), static_cast(viewSize.height)), self.contentScaleFactor, + true); + } // Avoid flicker. Issue #350 if ([NSThread isMainThread]) @@ -402,40 +395,13 @@ - (void)swapBuffers #pragma mark RenderHostView - Point conversion -- (CGPoint)convertPointFromViewToSurface:(CGPoint)point -{ - CGRect bounds = [self bounds]; - - CGPoint ret; - ret.x = (point.x - bounds.origin.x) / bounds.size.width * backingSize_.width; - ret.y = (point.y - bounds.origin.y) / bounds.size.height * backingSize_.height; - - return ret; -} - -- (CGRect)convertRectFromViewToSurface:(CGRect)rect -{ - CGRect bounds = [self bounds]; - - CGRect ret; - ret.origin.x = (rect.origin.x - bounds.origin.x) / bounds.size.width * backingSize_.width; - ret.origin.y = (rect.origin.y - bounds.origin.y) / bounds.size.height * backingSize_.height; - ret.size.width = rect.size.width / bounds.size.width * backingSize_.width; - ret.size.height = rect.size.height / bounds.size.height * backingSize_.height; - - return ret; -} - // Pass the touches to the superview #pragma mark RenderHostView - Touch Delegate -- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event -{ - if (self.isKeyboardShown) - [self closeKeyboardOpenedByEditBox]; - UITouch* ids[IOS_MAX_TOUCHES_COUNT] = {0}; - float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; - float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f}; +- (void)handlePlatformTouches:(NSSet*)touches + dispatchAction:(void (^)(ax::InputSystem* sys, ax::Vec2 pt, ax::PointerInputState st))dispatchBlock +{ + auto inputSys = ax::InputSystem::getInstance(); int i = 0; for (UITouch* touch in touches) @@ -446,111 +412,69 @@ - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event break; } - ids[i] = touch; - xs[i] = [touch locationInView:[touch view]].x * self.contentScaleFactor; - ys[i] = [touch locationInView:[touch view]].y * self.contentScaleFactor; - ++i; - } + CGPoint nativePoint = [touch locationInView:self]; - auto renderView = ax::Director::getInstance()->getRenderView(); - renderView->handleTouchesBegin(i, (intptr_t*)ids, xs, ys); -} - -- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event -{ - UITouch* ids[IOS_MAX_TOUCHES_COUNT] = {0}; - float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; - float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f}; - float fs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; - float ms[IOS_MAX_TOUCHES_COUNT] = {0.0f}; + ax::Vec2 point{static_cast(nativePoint.x), static_cast(nativePoint.y)}; - int i = 0; - for (UITouch* touch in touches) - { - if (i >= IOS_MAX_TOUCHES_COUNT) + float pressure = 1.0f; + if (touch.maximumPossibleForce > 0.0f) { - AXLOGW("warning: touches more than 10, should adjust IOS_MAX_TOUCHES_COUNT"); - break; + pressure = static_cast(touch.force / touch.maximumPossibleForce); } - ids[i] = touch; - xs[i] = [touch locationInView:[touch view]].x * self.contentScaleFactor; - ys[i] = [touch locationInView:[touch view]].y * self.contentScaleFactor; -#if defined(__IPHONE_9_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0) - // running on iOS 9.0 or higher version - if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0f) - { - fs[i] = touch.force; - ms[i] = touch.maximumPossibleForce; - } -#endif + ax::PointerInputState state{.id = (intptr_t)touch, .pressure = pressure, .type = ax::PointerType::Touch}; + + dispatchBlock(inputSys, point, state); + ++i; } - - auto renderView = ax::Director::getInstance()->getRenderView(); - renderView->handleTouchesMove(i, (intptr_t*)ids, xs, ys, fs, ms); } -- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - UITouch* ids[IOS_MAX_TOUCHES_COUNT] = {0}; - float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; - float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f}; + if (self.isKeyboardShown) + [self closeKeyboardOpenedByEditBox]; - int i = 0; - for (UITouch* touch in touches) - { - if (i >= IOS_MAX_TOUCHES_COUNT) - { - AXLOGW("warning: touches more than 10, should adjust IOS_MAX_TOUCHES_COUNT"); - break; - } + [self handlePlatformTouches:touches + dispatchAction:^(ax::InputSystem* sys, ax::Vec2 pt, ax::PointerInputState st) { + sys->handlePointerDown(pt, st); + }]; +} - ids[i] = touch; - xs[i] = [touch locationInView:[touch view]].x * self.contentScaleFactor; - ys[i] = [touch locationInView:[touch view]].y * self.contentScaleFactor; - ++i; - } +- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event +{ + [self handlePlatformTouches:touches + dispatchAction:^(ax::InputSystem* sys, ax::Vec2 pt, ax::PointerInputState st) { + sys->handlePointerMove(pt, st); + }]; +} - auto renderView = ax::Director::getInstance()->getRenderView(); - renderView->handleTouchesEnd(i, (intptr_t*)ids, xs, ys); +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event +{ + [self handlePlatformTouches:touches + dispatchAction:^(ax::InputSystem* sys, ax::Vec2 pt, ax::PointerInputState st) { + sys->handlePointerUp(pt, st); + }]; } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { - UITouch* ids[IOS_MAX_TOUCHES_COUNT] = {0}; - float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; - float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f}; - - int i = 0; - for (UITouch* touch in touches) - { - if (i >= IOS_MAX_TOUCHES_COUNT) - { - AXLOGW("warning: touches more than 10, should adjust IOS_MAX_TOUCHES_COUNT"); - break; - } - - ids[i] = touch; - xs[i] = [touch locationInView:[touch view]].x * self.contentScaleFactor; - ys[i] = [touch locationInView:[touch view]].y * self.contentScaleFactor; - ++i; - } - - auto renderView = ax::Director::getInstance()->getRenderView(); - renderView->handleTouchesCancel(i, (intptr_t*)ids, xs, ys); + [self handlePlatformTouches:touches + dispatchAction:^(ax::InputSystem* sys, ax::Vec2 pt, ax::PointerInputState st) { + sys->handlePointerCancel(pt, st); + }]; } - (void)showKeyboard { - [self addSubview:self.textInputView]; - [self.textInputView becomeFirstResponder]; + [self addSubview:self.inputHost]; + [self.inputHost becomeFirstResponder]; } - (void)hideKeyboard { - [self.textInputView resignFirstResponder]; - [self.textInputView removeFromSuperview]; + [self.inputHost resignFirstResponder]; + [self.inputHost removeFromSuperview]; } - (void)doAnimationWhenKeyboardMoveWithDuration:(float)duration distance:(float)dis @@ -562,7 +486,7 @@ - (void)doAnimationWhenKeyboardMoveWithDuration:(float)duration distance:(float) dis *= renderView->getScaleY(); dis /= self.contentScaleFactor; - CGRect newFrame = savedBounds_; + CGRect newFrame = [self bounds]; newFrame.origin.y -= dis; [UIView animateWithDuration:duration @@ -625,70 +549,47 @@ - (void)onUIKeyboardNotification:(NSNotification*)notif NSString* type = notif.name; NSDictionary* info = [notif userInfo]; - CGRect begin = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; - CGRect end = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - double aniDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - - // Convert to current view's coordinate system - begin = [self convertRect:begin fromView:nil]; - end = [self convertRect:end fromView:nil]; - - auto renderView = ax::Director::getInstance()->getRenderView(); - float scaleX = renderView->getScaleX(); - float scaleY = renderView->getScaleY(); - - const auto backingScaleFactor = self.contentScaleFactor; + auto inputSys = ax::InputSystem::getInstance(); + if (!inputSys) + return; - // Convert to pixel coordinates - begin = CGRectApplyAffineTransform( - begin, CGAffineTransformScale(CGAffineTransformIdentity, backingScaleFactor, backingScaleFactor)); - end = CGRectApplyAffineTransform( - end, CGAffineTransformScale(CGAffineTransformIdentity, backingScaleFactor, backingScaleFactor)); + // Extract animation hardware clock duration from the notification payload + double aniDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + float duration = static_cast(aniDuration); - float offestY = renderView->getViewportRect().origin.y; - if (offestY < 0.0f) + // 1. Keyboard is about to slide up + if (UIKeyboardWillShowNotification == type) { - begin.origin.y += offestY; - begin.size.height -= offestY; - end.size.height -= offestY; - } - - // Convert to design resolution coordinates - begin = CGRectApplyAffineTransform(begin, - CGAffineTransformScale(CGAffineTransformIdentity, 1.0f / scaleX, 1.0f / scaleY)); - end = CGRectApplyAffineTransform(end, - CGAffineTransformScale(CGAffineTransformIdentity, 1.0f / scaleX, 1.0f / scaleY)); + // Extract the target raw keyboard bounds (End Frame) + CGRect end = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - // Fill notification info for Axmol IME dispatcher - auto boundSize = savedBounds_.size; - CGSize viewSize = - CGSizeMake(boundSize.width * backingScaleFactor / scaleX, boundSize.height * backingScaleFactor / scaleY); + // Convert frame coordinates to local view system (UIKit Points, Top-Left origin) + end = [self convertRect:end fromView:nil]; - ax::IMEKeyboardNotificationInfo notiInfo; - notiInfo.begin = convertKeyboardRectToViewport(begin, viewSize); - notiInfo.end = convertKeyboardRectToViewport(end, viewSize); - notiInfo.duration = aniDuration; - - ax::IMEDispatcher* dispatcher = ax::IMEDispatcher::sharedDispatcher(); - if (UIKeyboardWillShowNotification == type) - { - dispatcher->dispatchKeyboardWillShow(notiInfo); + // Pipe raw properties directly to C++ core layer for centralized coordinate/resolution mapping + inputSys->onPlatformKeyboardWillShow(static_cast(end.origin.x), static_cast(end.origin.y), + static_cast(end.size.width), static_cast(end.size.height), + duration); } + // 2. Keyboard expansion animation completed else if (UIKeyboardDidShowNotification == type) { self.isKeyboardShown = YES; - dispatcher->dispatchKeyboardDidShow(notiInfo); + inputSys->onPlatformKeyboardDidShow(); } + // 3. Keyboard is about to slide down else if (UIKeyboardWillHideNotification == type) { - dispatcher->dispatchKeyboardWillHide(notiInfo); + // No layout tracking required; C++ layer derives the dismissal path via cached frame + inputSys->onPlatformKeyboardWillHide(duration); } + // 4. Keyboard dismissal animation completed else if (UIKeyboardDidHideNotification == type) { self.isKeyboardShown = NO; - dispatcher->dispatchKeyboardDidHide(notiInfo); + inputSys->onPlatformKeyboardDidHide(); } -#endif +#endif /* !defined(AX_TARGET_OS_TVOS) */ } // Close the keyboard opened by EditBox @@ -710,4 +611,35 @@ - (void)closeKeyboardOpenedByEditBox } } +- (void)showContextMenu:(CGPoint)point hasText:(BOOL)hasText hasSelection:(BOOL)hasSelection readOnly:(BOOL)readOnly +{ + void (^showMenuBlock)(void) = ^{ + auto renderView = ax::Director::getInstance()->getRenderView(); + + CGPoint screenPointInPoints = CGPointMake(point.x, point.y); + + CGPoint hostPoint = [self.inputHost convertPoint:screenPointInPoints fromView:nil]; + + if (![self.inputHost isFirstResponder]) + { + [self.inputHost becomeFirstResponder]; + } + [self.inputHost showContextMenu:hostPoint hasText:hasText hasSelection:hasSelection readOnly:readOnly]; + }; + + if ([NSThread isMainThread]) + { + showMenuBlock(); + } + else + { + dispatch_async(dispatch_get_main_queue(), showMenuBlock); + } +} + +- (void)hideContextMenu +{ + [self.inputHost hideContextMenu]; +} + @end diff --git a/axmol/platform/ios/RenderViewImpl-ios.h b/axmol/platform/ios/RenderView-ios.h similarity index 74% rename from axmol/platform/ios/RenderViewImpl-ios.h rename to axmol/platform/ios/RenderView-ios.h index 91a328141c15..8810243217ff 100644 --- a/axmol/platform/ios/RenderViewImpl-ios.h +++ b/axmol/platform/ios/RenderView-ios.h @@ -28,27 +28,27 @@ #include "axmol/base/Object.h" #include "axmol/platform/Common.h" -#include "axmol/platform/RenderView.h" +#include "axmol/platform/RenderViewCore.h" namespace ax { /** Class that represent the OpenGL View */ -class AX_DLL RenderViewImpl : public RenderView +class AX_DLL RenderView : public RenderViewCore { public: - /** creates a RenderViewImpl with a title name in fullscreen mode */ - static RenderViewImpl* create(std::string_view viewName); + /** creates a RenderView with a title name in fullscreen mode */ + static RenderView* create(std::string_view viewName); - /** creates a RenderViewImpl with a title name, a rect and the zoom factor */ - static RenderViewImpl* createWithRect(std::string_view viewName, - const Rect& rect, - float zoomFactor = 1.0f, - bool resizable = false); + /** creates a RenderView with a title name, a rect and the zoom factor */ + static RenderView* createWithRect(std::string_view viewName, + const Rect& rect, + float zoomFactor = 1.0f, + bool resizable = false); - /** creates a RenderViewImpl with a name in fullscreen mode */ - static RenderViewImpl* createWithFullscreen(std::string_view viewName); + /** creates a RenderView with a name in fullscreen mode */ + static RenderView* createWithFullscreen(std::string_view viewName); static void choosePixelFormats(); static PixelFormat _pixelFormat; @@ -76,11 +76,14 @@ class AX_DLL RenderViewImpl : public RenderView Rect getSafeAreaRect() const override; - void queueOperation(void (*op)(void*), void* param) override; + void showContextMenu(const Vec2& point, bool hasText, bool hasSelection, bool readOnly) override; + void hideContextMenu() override; + + void updateSurfaceMetrics(const Vec2& viewSize, float renderScale, bool shouldNotify); protected: - RenderViewImpl(); - ~RenderViewImpl() override; + RenderView(); + ~RenderView() override; bool initWithRect(std::string_view viewName, const Rect& rect, float frameZoomFactor, bool resizable = false); bool initWithFullScreen(std::string_view viewName); diff --git a/axmol/platform/ios/RenderViewImpl-ios.mm b/axmol/platform/ios/RenderView-ios.mm similarity index 59% rename from axmol/platform/ios/RenderViewImpl-ios.mm rename to axmol/platform/ios/RenderView-ios.mm index 34ae4d7ecaad..d48f84892437 100644 --- a/axmol/platform/ios/RenderViewImpl-ios.mm +++ b/axmol/platform/ios/RenderView-ios.mm @@ -28,19 +28,20 @@ of this software and associated documentation files (the "Software"), to deal #include "axmol/platform/ios/RenderHostView-ios.h" #include "axmol/platform/ios/DirectorCaller-ios.h" -#include "axmol/platform/ios/RenderViewImpl-ios.h" +#include "axmol/platform/ios/RenderView-ios.h" #include "axmol/platform/ios/AxmolViewController.h" #include "axmol/platform/Application.h" #include "axmol/platform/Device.h" -#include "axmol/base/Touch.h" +#include "axmol/base/PointerEvent.h" #include "axmol/base/Director.h" +#include "axmol/base/InputSystem.h" namespace ax { -PixelFormat RenderViewImpl::_pixelFormat = PixelFormat::RGB565; -PixelFormat RenderViewImpl::_depthFormat = PixelFormat::D24S8; -int RenderViewImpl::_multisamplingCount = 0; +PixelFormat RenderView::_pixelFormat = PixelFormat::RGB565; +PixelFormat RenderView::_depthFormat = PixelFormat::D24S8; +int RenderView::_multisamplingCount = 0; /** * Adjusts a UIView's size to match the resolved device orientation. @@ -75,9 +76,9 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) return viewSize; } -RenderViewImpl* RenderViewImpl::create(std::string_view viewName) +RenderView* RenderView::create(std::string_view viewName) { - auto ret = new RenderViewImpl; + auto ret = new RenderView; if (ret->initWithFullScreen(viewName)) { ret->autorelease(); @@ -87,12 +88,12 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) return nullptr; } -RenderViewImpl* RenderViewImpl::createWithRect(std::string_view viewName, - const ax::Rect& rect, - float frameZoomFactor, - bool resizable) +RenderView* RenderView::createWithRect(std::string_view viewName, + const ax::Rect& rect, + float frameZoomFactor, + bool resizable) { - auto ret = new RenderViewImpl; + auto ret = new RenderView; if (ret->initWithRect(viewName, rect, frameZoomFactor, resizable)) { ret->autorelease(); @@ -102,9 +103,9 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) return nullptr; } -RenderViewImpl* RenderViewImpl::createWithFullscreen(std::string_view viewName) +RenderView* RenderView::createWithFullscreen(std::string_view viewName) { - auto ret = new RenderViewImpl(); + auto ret = new RenderView(); if (ret->initWithFullScreen(viewName)) { ret->autorelease(); @@ -114,7 +115,7 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) return nullptr; } -void RenderViewImpl::choosePixelFormats() +void RenderView::choosePixelFormats() { const auto& contextAttrs = Application::getContextAttrs(); @@ -149,14 +150,14 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) _multisamplingCount = contextAttrs.multisamplingCount; } -RenderViewImpl::RenderViewImpl() {} +RenderView::RenderView() {} -RenderViewImpl::~RenderViewImpl() {} +RenderView::~RenderView() {} -bool RenderViewImpl::initWithRect(std::string_view /*viewName*/, - const Rect& rect, - float frameZoomFactor, - bool /*resizable*/) +bool RenderView::initWithRect(std::string_view /*viewName*/, + const Rect& rect, + float frameZoomFactor, + bool /*resizable*/) { CGRect r = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); choosePixelFormats(); @@ -167,7 +168,7 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) return true; } -bool RenderViewImpl::initWithFullScreen(std::string_view viewName) +bool RenderView::initWithFullScreen(std::string_view viewName) { CGRect rect = [[UIScreen mainScreen] bounds]; Rect r; @@ -179,7 +180,7 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) return initWithRect(viewName, r, 1); } -void RenderViewImpl::setMultipleTouchEnabled(bool enabled) +void RenderView::setMultipleTouchEnabled(bool enabled) { #if !defined(AX_TARGET_OS_TVOS) [(__bridge RenderHostView*)_hostViewHandle setMultipleTouchEnabled:enabled]; @@ -188,7 +189,7 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) #endif } -void RenderViewImpl::showWindow(void* viewController) +void RenderView::showWindow(void* viewController) { auto window = (__bridge UIWindow*)_hostWindowHandle; auto controller = (__bridge AxmolViewController*)viewController; @@ -217,12 +218,10 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) auto hostView = controller.view; _hostViewHandle = controller.view; - const auto size = resolveViewSizeToOrientation([hostView bounds].size); - const auto backingScaleFactor = [hostView contentScaleFactor]; + const auto size = resolveViewSizeToOrientation([hostView bounds].size); - // simply set renderSize, renderSize to framebufferSize with renderScale=1.0 - updateRenderSurface(size.width * backingScaleFactor, size.height * backingScaleFactor, - SurfaceUpdateFlag::AllUpdatesSilently); + updateSurfaceMetrics(Vec2{static_cast(size.width), static_cast(size.height)}, + [hostView contentScaleFactor], false); #if !defined(AX_TARGET_OS_TVOS) [controller prefersStatusBarHidden]; @@ -235,12 +234,27 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) } } -bool RenderViewImpl::isGfxContextReady() +void RenderView::updateSurfaceMetrics(const Vec2& viewSize, float renderScale, bool shouldNotify) +{ + _renderScale = renderScale; + + // Inform InputSystem about the platform input scale so it can apply + // coordinate scaling centrally when dispatching input events. + InputSystem::getInstance()->setInputScale(renderScale); + + updateRenderSurface(viewSize.width, viewSize.height, SurfaceUpdateFlag::WindowSizeChanged); + + auto flags = shouldNotify ? SurfaceUpdateFlag::RenderSizeChanged + : (SurfaceUpdateFlag::RenderSizeChanged | SurfaceUpdateFlag::SilentUpdate); + updateRenderSurface(viewSize.width * _renderScale, viewSize.height * _renderScale, flags); +} + +bool RenderView::isGfxContextReady() { return _hostViewHandle != nullptr; } -void RenderViewImpl::end() +void RenderView::end() { [CCDirectorCaller destroy]; @@ -248,12 +262,12 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) release(); } -void RenderViewImpl::swapBuffers() +void RenderView::swapBuffers() { [(__bridge RenderHostView*)_hostViewHandle swapBuffers]; } -void RenderViewImpl::setIMEKeyboardState(bool open) +void RenderView::setIMEKeyboardState(bool open) { auto hostView = (__bridge RenderHostView*)_hostViewHandle; if (open) @@ -266,58 +280,71 @@ static CGSize resolveViewSizeToOrientation(CGSize viewSize) } } -Rect RenderViewImpl::getSafeAreaRect() const +Rect RenderView::getSafeAreaRect() const { RenderHostView* hostView = (__bridge RenderHostView*)_hostViewHandle; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 - float version = [[UIDevice currentDevice].systemVersion floatValue]; - if (version >= 11.0f) + if (@available(iOS 11.0, *)) { -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wpartial-availability" - UIEdgeInsets safeAreaInsets = hostView.safeAreaInsets; -# pragma clang diagnostic pop - - // Multiply contentScaleFactor since safeAreaInsets return points. - safeAreaInsets.left *= hostView.contentScaleFactor; - safeAreaInsets.right *= hostView.contentScaleFactor; - safeAreaInsets.top *= hostView.contentScaleFactor; - safeAreaInsets.bottom *= hostView.contentScaleFactor; - - // Get leftBottom and rightTop point in UI coordinates - Vec2 leftBottom = Vec2(safeAreaInsets.left, _windowSize.height - safeAreaInsets.bottom); - Vec2 rightTop = Vec2(_windowSize.width - safeAreaInsets.right, safeAreaInsets.top); - - // Convert a point from UI coordinates to which in design resolution coordinate. - leftBottom.x = (leftBottom.x - _viewportRect.origin.x) / _viewScale.x, - leftBottom.y = (leftBottom.y - _viewportRect.origin.y) / _viewScale.y; - rightTop.x = (rightTop.x - _viewportRect.origin.x) / _viewScale.x, - rightTop.y = (rightTop.y - _viewportRect.origin.y) / _viewScale.y; - - // Adjust points to make them inside design resolution - leftBottom.x = MAX(leftBottom.x, 0); - leftBottom.y = MIN(leftBottom.y, _designResolutionSize.height); - rightTop.x = MIN(rightTop.x, _designResolutionSize.width); - rightTop.y = MAX(rightTop.y, 0); - - // Convert to GL coordinates - leftBottom = Director::getInstance()->screenToWorld(leftBottom); - rightTop = Director::getInstance()->screenToWorld(rightTop); - - return Rect(leftBottom.x, leftBottom.y, rightTop.x - leftBottom.x, rightTop.y - leftBottom.y); + UIEdgeInsets insets = hostView.safeAreaInsets; + CGRect bounds = hostView.bounds; + + // 1. Industrial-grade Orientation Fix: Forcefully project mismatched + // vertical padding to the horizontal X-axis under strict landscape mode. + bool isLandscape = bounds.size.width > bounds.size.height; + float maxPadding = std::max({insets.left, insets.right, insets.top, insets.bottom}); + + float nativeMinX = (isLandscape && maxPadding > 0.0f) ? maxPadding : insets.left; + float nativeMinY = isLandscape ? 0.0f : insets.top; + float nativeMaxX = bounds.size.width - ((isLandscape && maxPadding > 0.0f) ? maxPadding : insets.right); + float nativeMaxY = bounds.size.height - (isLandscape ? 0.0f : insets.bottom); + + // 2. Map native logical boundaries directly to engine standard screen pixels + auto* inputSys = ax::InputSystem::getInstance(); + ax::Vec2 leftTop = inputSys->nativeToScreen(ax::Vec2(nativeMinX, nativeMinY)); + ax::Vec2 rightBottom = inputSys->nativeToScreen(ax::Vec2(nativeMaxX, nativeMaxY)); + + // 3. Convert standard screen pixels to standard Engine Target space via Director + auto* director = ax::Director::getInstance(); + + leftTop = director->screenToWorld(leftTop); + rightBottom = director->screenToWorld(rightBottom); + + // 4. Adjust points to make them inside design resolution + float minX = std::max(leftTop.x, 0.0f); + float minY = std::max(rightBottom.y, 0.0f); + float maxX = std::min(rightBottom.x, _designResolutionSize.width); + float maxY = std::min(leftTop.y, _designResolutionSize.height); + + auto safeArea = ax::Rect(minX, minY, maxX - minX, maxY - minY); + + AXLOGD("ios safe area: origin=({},{}), size=({},{})", safeArea.origin.x, safeArea.origin.y, safeArea.size.width, + safeArea.size.height); + return safeArea; } #endif - // If running on iOS devices lower than 11.0, return visiable rect instead. return RenderView::getSafeAreaRect(); } -void RenderViewImpl::queueOperation(void (*op)(void*), void* param) +void RenderView::showContextMenu(const Vec2& point, bool hasText, bool hasSelection, bool readOnly) +{ + auto position = InputSystem::getInstance()->screenToNative(point); + + // Implementation for showing system edit menu at the specified point + RenderHostView* hostView = (__bridge RenderHostView*)_hostViewHandle; + [hostView showContextMenu:CGPointMake(position.x, position.y) + hasText:hasText + hasSelection:hasSelection + readOnly:readOnly]; +} + +void RenderView::hideContextMenu() { - [[NSOperationQueue mainQueue] addOperationWithBlock:^(void) { - op(param); - }]; + // Implementation for hiding system edit menu + RenderHostView* hostView = (__bridge RenderHostView*)_hostViewHandle; + [hostView hideContextMenu]; } } // namespace ax diff --git a/axmol/platform/linux/Application-linux.cpp b/axmol/platform/linux/Application-linux.cpp index bd99e76e07bc..8b5b8512613b 100644 --- a/axmol/platform/linux/Application-linux.cpp +++ b/axmol/platform/linux/Application-linux.cpp @@ -36,19 +36,16 @@ THE SOFTWARE. namespace ax { -// sharedApplication pointer -Application* Application::sm_pSharedApplication = nullptr; - Application::Application() : _animationInterval(16666667) { - AX_ASSERT(!sm_pSharedApplication); - sm_pSharedApplication = this; + AX_ASSERT(!s_axmolApp); + s_axmolApp = this; } Application::~Application() { - AX_ASSERT(this == sm_pSharedApplication); - sm_pSharedApplication = nullptr; + AX_ASSERT(this == s_axmolApp); + s_axmolApp = nullptr; } int Application::run() @@ -72,6 +69,8 @@ int Application::run() { lastTime = std::chrono::steady_clock::now(); + director->performFrameBoundaryTasks(); + director->renderFrame(); renderView->pollEvents(); @@ -124,15 +123,6 @@ bool Application::openURL(std::string_view url) return system(op.c_str()) == 0; } -////////////////////////////////////////////////////////////////////////// -// static member function -////////////////////////////////////////////////////////////////////////// -Application* Application::getInstance() -{ - AX_ASSERT(sm_pSharedApplication); - return sm_pSharedApplication; -} - const char* Application::getCurrentLanguageCode() { static char code[3] = {0}; diff --git a/axmol/platform/linux/Application-linux.h b/axmol/platform/linux/Application-linux.h index 8431f0af55e3..2f0e26accd7e 100644 --- a/axmol/platform/linux/Application-linux.h +++ b/axmol/platform/linux/Application-linux.h @@ -27,7 +27,7 @@ THE SOFTWARE. #pragma once #include "axmol/platform/Common.h" -#include "axmol/platform/ApplicationBase.h" +#include "axmol/platform/ApplicationCore.h" #include #include @@ -35,7 +35,7 @@ namespace ax { class Rect; -class Application : public ApplicationBase +class Application : public ApplicationCore { public: /** @@ -57,12 +57,6 @@ class Application : public ApplicationBase */ int run(); - /** - @brief Get current application instance. - @return Current application instance pointer. - */ - static Application* getInstance(); - /* override functions */ LanguageType getCurrentLanguage() override; @@ -92,8 +86,6 @@ class Application : public ApplicationBase protected: std::chrono::nanoseconds _animationInterval; // nano seconds std::string _resourceRootPath; - - static Application* sm_pSharedApplication; }; } // namespace ax diff --git a/axmol/platform/linux/Device-linux.cpp b/axmol/platform/linux/Device-linux.cpp index 97d2ad4878e2..c8e0d4861324 100644 --- a/axmol/platform/linux/Device-linux.cpp +++ b/axmol/platform/linux/Device-linux.cpp @@ -36,6 +36,7 @@ THE SOFTWARE. #include #include #include +#include "GLFW/glfw3.h" #include "ft2build.h" #include FT_FREETYPE_H @@ -80,6 +81,28 @@ struct LineBreakLine namespace ax { +void Device::getClipboardText(std::function callback) +{ + if (!callback) + return; + auto text = glfwGetClipboardString(nullptr); + if (text) + callback(text); + else + callback(std::string_view{}); +} + +void Device::setClipboardText(std::string_view text) +{ + std::string tmp(text); + glfwSetClipboardString(nullptr, tmp.c_str()); +} + +void Device::clearClipboard() +{ + glfwSetClipboardString(nullptr, ""); +} + int Device::getDPI() { static int dpi = -1; diff --git a/axmol/platform/mac/Application-mac.h b/axmol/platform/mac/Application-mac.h index 25ba44549df6..420d0b0579fa 100644 --- a/axmol/platform/mac/Application-mac.h +++ b/axmol/platform/mac/Application-mac.h @@ -27,14 +27,14 @@ THE SOFTWARE. #pragma once #include "axmol/platform/Common.h" -#include "axmol/platform/ApplicationBase.h" +#include "axmol/platform/ApplicationCore.h" #include #include namespace ax { -class AX_DLL Application : public ApplicationBase +class AX_DLL Application : public ApplicationCore { public: /** @@ -57,12 +57,6 @@ class AX_DLL Application : public ApplicationBase */ int run(); - /** - @brief Get current application instance. - @return Current application instance pointer. - */ - static Application* getInstance(); - /** @brief Get current language config @return Current language config @@ -92,16 +86,9 @@ class AX_DLL Application : public ApplicationBase */ bool openURL(std::string_view url) override; - void setStartupScriptFilename(std::string_view startupScriptFile); - - std::string_view getStartupScriptFilename(); - protected: - static Application* sm_pSharedApplication; - std::chrono::nanoseconds _animationInterval; // nano second std::string _resourceRootPath; - std::string _startupScriptFilename; }; } // namespace ax diff --git a/axmol/platform/mac/Application-mac.mm b/axmol/platform/mac/Application-mac.mm index dad25112c087..4182ec169f5d 100644 --- a/axmol/platform/mac/Application-mac.mm +++ b/axmol/platform/mac/Application-mac.mm @@ -38,19 +38,16 @@ of this software and associated documentation files (the "Software"), to deal namespace ax { - -Application* Application::sm_pSharedApplication = nullptr; - Application::Application() : _animationInterval(16666667) { - AXASSERT(!sm_pSharedApplication, "sm_pSharedApplication already exist"); - sm_pSharedApplication = this; + AXASSERT(!s_axmolApp, "s_axmolApp already exist"); + s_axmolApp = this; } Application::~Application() { - AXASSERT(this == sm_pSharedApplication, "sm_pSharedApplication != this"); - sm_pSharedApplication = 0; + AXASSERT(this == s_axmolApp, "s_axmolApp != this"); + s_axmolApp = 0; } int Application::run() @@ -75,6 +72,7 @@ of this software and associated documentation files (the "Software"), to deal { lastTime = std::chrono::steady_clock::now(); + director->performFrameBoundaryTasks(); director->renderFrame(); renderView->pollEvents(); @@ -123,16 +121,6 @@ of this software and associated documentation files (the "Software"), to deal return ""; } -///////////////////////////////////////////////////////////////////////////////////////////////// -// static member function -////////////////////////////////////////////////////////////////////////////////////////////////// - -Application* Application::getInstance() -{ - AXASSERT(sm_pSharedApplication, "sm_pSharedApplication not set"); - return sm_pSharedApplication; -} - const char* Application::getCurrentLanguageCode() { static char code[3] = {0}; @@ -169,15 +157,4 @@ of this software and associated documentation files (the "Software"), to deal return [[NSWorkspace sharedWorkspace] openURL:nsUrl]; } -void Application::setStartupScriptFilename(std::string_view startupScriptFile) -{ - _startupScriptFilename = startupScriptFile; - std::replace(_startupScriptFilename.begin(), _startupScriptFilename.end(), '\\', '/'); -} - -std::string_view Application::getStartupScriptFilename() -{ - return _startupScriptFilename; -} - } // namespace ax diff --git a/axmol/platform/mac/Device-mac.mm b/axmol/platform/mac/Device-mac.mm index 32d064a8b4e8..3c0d0c621ae1 100644 --- a/axmol/platform/mac/Device-mac.mm +++ b/axmol/platform/mac/Device-mac.mm @@ -34,6 +34,49 @@ of this software and associated documentation files (the "Software"), to deal namespace ax { +void Device::getClipboardText(std::function callback) +{ + if (!callback) + return; + @autoreleasepool + { + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + NSString* s = [pb stringForType:NSPasteboardTypeString]; + if (!s) + { + callback(std::string_view{}); + return; + } + const char* utf8 = [s UTF8String]; + NSUInteger len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + callback(utf8 ? std::string_view(utf8, static_cast(len)) : std::string_view{}); + } +} + +void Device::setClipboardText(std::string_view text) +{ + @autoreleasepool + { + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + [pb clearContents]; + + NSString* s = [[NSString alloc] initWithBytes:text.data() + length:(NSUInteger)text.size() + encoding:NSUTF8StringEncoding]; + if (s) + [pb setString:s forType:NSPasteboardTypeString]; + } +} + +void Device::clearClipboard() +{ + @autoreleasepool + { + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + [pb clearContents]; + } +} + static NSAttributedString* __attributedStringWithFontSize(NSMutableAttributedString* attributedString, CGFloat fontSize) { { diff --git a/axmol/platform/desktop/Device-desktop.cpp b/axmol/platform/pc/Device-pc.cpp similarity index 100% rename from axmol/platform/desktop/Device-desktop.cpp rename to axmol/platform/pc/Device-pc.cpp diff --git a/axmol/platform/pc/RenderView-pc.cpp b/axmol/platform/pc/RenderView-pc.cpp new file mode 100644 index 000000000000..a1544d97a07c --- /dev/null +++ b/axmol/platform/pc/RenderView-pc.cpp @@ -0,0 +1,1894 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +The RenderView for win32,linux,macos,wasm + +****************************************************************************/ + +#include "axmol/platform/pc/RenderView-pc.h" + +#include +#include + +#include "axmol/platform/Application.h" +#include "axmol/base/Director.h" +#include "axmol/base/PointerEvent.h" +#include "axmol/base/EventDispatcher.h" +#include "axmol/base/KeyboardEvent.h" +#include "axmol/base/InputSystem.h" +#include "axmol/base/Utils.h" +#include "axmol/base/text_utils.h" +#include "axmol/scene/Camera.h" +#if AX_ICON_SET_SUPPORT +# include "axmol/platform/Image.h" +#endif /* AX_ICON_SET_SUPPORT */ + +#include "axmol/renderer/Renderer.h" + +#if AX_ENABLE_MTL +# include +# include "axmol/rhi/metal/DriverMTL.h" +# include "axmol/rhi/metal/UtilsMTL.h" +#endif +#if AX_ENABLE_GL +# include "axmol/rhi/opengl/DriverGL.h" +# include "axmol/rhi/opengl/MacrosGL.h" +# include "axmol/rhi/opengl/OpenGLState.h" +#endif +#if AX_ENABLE_VK +# include "axmol/rhi/vulkan/DriverVK.h" +#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + +#include "axmol/rhi/DriverContext.h" + +/** glfw3native.h */ +#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) +# ifndef GLFW_EXPOSE_NATIVE_WIN32 +# define GLFW_EXPOSE_NATIVE_WIN32 +# endif +# ifndef GLFW_EXPOSE_NATIVE_WGL +# define GLFW_EXPOSE_NATIVE_WGL +# endif +#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) */ + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) +# ifndef GLFW_EXPOSE_NATIVE_NSGL +# define GLFW_EXPOSE_NATIVE_NSGL +# endif +# ifndef GLFW_EXPOSE_NATIVE_COCOA +# define GLFW_EXPOSE_NATIVE_COCOA +# endif +#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) +# ifndef GLFW_EXPOSE_NATIVE_X11 +# define GLFW_EXPOSE_NATIVE_X11 +# endif +# ifndef GLFW_EXPOSE_NATIVE_WAYLAND +# define GLFW_EXPOSE_NATIVE_WAYLAND +# endif +#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) + +#if (AX_TARGET_PLATFORM != AX_PLATFORM_WASM) +# include +#endif + +#if defined(__EMSCRIPTEN__) +# include +#endif + +#ifndef NDEBUG +# include "axmol/base/Scheduler.h" +#endif + +#if defined(_WIN32) +# pragma comment(lib, "imm32.lib") +#endif + +#if defined(__linux__) +# pragma push_macro("None") +# undef None +#endif + +namespace ax +{ + +using namespace rhi; + +#if defined(__EMSCRIPTEN__) +struct IVec2 +{ + int x{0}; + int y{0}; +}; +struct WebFullscreenState +{ + WebFullscreenState() + { + EmscriptenFullscreenChangeEvent fs; + if (emscripten_get_fullscreen_status(&fs) == EMSCRIPTEN_RESULT_SUCCESS) + { + isFullscreen = fs.isFullscreen; + } + } + + IVec2 windowedSize; + bool isFullscreen{false}; +}; +static std::unique_ptr s_fullscreenState; +#endif + +class GLFWEventHandler +{ +public: + static void onGLFWError(int errorID, const char* errorDesc) + { + if (_view) + _view->onGLFWError(errorID, errorDesc); + } + + // WebAssembly: use w3c pointerevent and scroll event for mouse/touch/pen events, not use GLFW +#if defined(__EMSCRIPTEN__) + static EM_BOOL onWebOrientationChangeCallback(int eventType, + const EmscriptenOrientationChangeEvent* e, + void* /*userData*/) + { + if (_view) + _view->onWebOrientationChangeCallback(eventType, e); + return EM_TRUE; + } + + static EM_BOOL onWebFullscreenCallback(int eventType, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) + { + if (_view) + _view->onWebFullscreenCallback(eventType, e); + return EM_TRUE; + } +#else + static void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify) + { + if (_view) + _view->onGLFWMouseCallBack(window, button, action, modify); + } + + static void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y) + { + if (_view) + _view->onGLFWMouseMoveCallBack(window, x, y); + } + static void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y) + { + if (_view) + _view->onGLFWMouseScrollCallback(window, x, y); + } +#endif + + static void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) + { + if (_view) + _view->onGLFWKeyCallback(window, key, scancode, action, mods); + } + + static void onGLFWCharCallback(GLFWwindow* window, unsigned int character) + { + if (_view) + _view->onGLFWCharCallback(window, character); + } + + static void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y) + { + if (_view) + _view->onGLFWWindowPosCallback(windows, x, y); + } + + static void onGLFWFramebufferSizeCallback(GLFWwindow* window, int width, int height) + { + if (_view) + _view->onGLFWFramebufferSizeCallback(window, width, height); + } + + static void onGLFWWindowSizeCallback(GLFWwindow* window, int width, int height) + { + if (_view) + _view->onGLFWWindowSizeCallback(window, width, height); + } + + static void setRenderView(RenderView* view) { _view = view; } + + static void onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified) + { + if (_view) + { + _view->onGLFWWindowIconifyCallback(window, iconified); + } + } + + static void onGLFWWindowFocusCallback(GLFWwindow* window, int focused) + { + if (_view) + { + _view->onGLFWWindowFocusCallback(window, focused); + } + } + + static void onGLFWWindowCloseCallback(GLFWwindow* window) + { + if (_view) + { + _view->onGLFWWindowCloseCallback(window); + } + } + + static void onGLFWPreeditCallback(GLFWwindow* window, + int preedit_count, + unsigned int* preedit_string, + int block_count, + int* block_sizes, + int focused_block, + int caret) + { + if (_view) + { + _view->onGLFWPreeditCallback(window, preedit_count, preedit_string, block_count, block_sizes, focused_block, + caret); + } + } + +private: + static RenderView* _view; +}; +RenderView* GLFWEventHandler::_view = nullptr; + +const std::string_view RenderView::EVENT_WINDOW_POSITIONED = "_ax_window_positioned"sv; +const std::string_view RenderView::EVENT_WINDOW_RESIZED = "_ax_window_resized"sv; +const std::string_view RenderView::EVENT_WINDOW_FOCUSED = "_ax_window_focused"sv; +const std::string_view RenderView::EVENT_WINDOW_UNFOCUSED = "_ax_window_unfocused"sv; +const std::string_view RenderView::EVENT_WINDOW_CLOSE = "_ax_window_close"sv; +const std::string_view RenderView::EVENT_WINDOW_CURSOR_ENTER = "_ax_window_cursor_enter"sv; + +static constexpr intptr_t MOUSE_POINTER_ID = 0; + +//////////////////////////////////////////////////// + +struct KeyCodeItem +{ + int glfwKeyCode; + KeyboardEvent::KeyCode keyCode; +}; + +static constexpr KeyCodeItem s_keyCodeItems[] = { + /* The unknown key */ + {GLFW_KEY_UNKNOWN, KeyboardEvent::KeyCode::KEY_NONE}, + + /* Printable keys */ + {GLFW_KEY_SPACE, KeyboardEvent::KeyCode::KEY_SPACE}, + {GLFW_KEY_APOSTROPHE, KeyboardEvent::KeyCode::KEY_APOSTROPHE}, + {GLFW_KEY_COMMA, KeyboardEvent::KeyCode::KEY_COMMA}, + {GLFW_KEY_MINUS, KeyboardEvent::KeyCode::KEY_MINUS}, + {GLFW_KEY_PERIOD, KeyboardEvent::KeyCode::KEY_PERIOD}, + {GLFW_KEY_SLASH, KeyboardEvent::KeyCode::KEY_SLASH}, + {GLFW_KEY_0, KeyboardEvent::KeyCode::KEY_0}, + {GLFW_KEY_1, KeyboardEvent::KeyCode::KEY_1}, + {GLFW_KEY_2, KeyboardEvent::KeyCode::KEY_2}, + {GLFW_KEY_3, KeyboardEvent::KeyCode::KEY_3}, + {GLFW_KEY_4, KeyboardEvent::KeyCode::KEY_4}, + {GLFW_KEY_5, KeyboardEvent::KeyCode::KEY_5}, + {GLFW_KEY_6, KeyboardEvent::KeyCode::KEY_6}, + {GLFW_KEY_7, KeyboardEvent::KeyCode::KEY_7}, + {GLFW_KEY_8, KeyboardEvent::KeyCode::KEY_8}, + {GLFW_KEY_9, KeyboardEvent::KeyCode::KEY_9}, + {GLFW_KEY_SEMICOLON, KeyboardEvent::KeyCode::KEY_SEMICOLON}, + {GLFW_KEY_EQUAL, KeyboardEvent::KeyCode::KEY_EQUAL}, + {GLFW_KEY_A, KeyboardEvent::KeyCode::KEY_A}, + {GLFW_KEY_B, KeyboardEvent::KeyCode::KEY_B}, + {GLFW_KEY_C, KeyboardEvent::KeyCode::KEY_C}, + {GLFW_KEY_D, KeyboardEvent::KeyCode::KEY_D}, + {GLFW_KEY_E, KeyboardEvent::KeyCode::KEY_E}, + {GLFW_KEY_F, KeyboardEvent::KeyCode::KEY_F}, + {GLFW_KEY_G, KeyboardEvent::KeyCode::KEY_G}, + {GLFW_KEY_H, KeyboardEvent::KeyCode::KEY_H}, + {GLFW_KEY_I, KeyboardEvent::KeyCode::KEY_I}, + {GLFW_KEY_J, KeyboardEvent::KeyCode::KEY_J}, + {GLFW_KEY_K, KeyboardEvent::KeyCode::KEY_K}, + {GLFW_KEY_L, KeyboardEvent::KeyCode::KEY_L}, + {GLFW_KEY_M, KeyboardEvent::KeyCode::KEY_M}, + {GLFW_KEY_N, KeyboardEvent::KeyCode::KEY_N}, + {GLFW_KEY_O, KeyboardEvent::KeyCode::KEY_O}, + {GLFW_KEY_P, KeyboardEvent::KeyCode::KEY_P}, + {GLFW_KEY_Q, KeyboardEvent::KeyCode::KEY_Q}, + {GLFW_KEY_R, KeyboardEvent::KeyCode::KEY_R}, + {GLFW_KEY_S, KeyboardEvent::KeyCode::KEY_S}, + {GLFW_KEY_T, KeyboardEvent::KeyCode::KEY_T}, + {GLFW_KEY_U, KeyboardEvent::KeyCode::KEY_U}, + {GLFW_KEY_V, KeyboardEvent::KeyCode::KEY_V}, + {GLFW_KEY_W, KeyboardEvent::KeyCode::KEY_W}, + {GLFW_KEY_X, KeyboardEvent::KeyCode::KEY_X}, + {GLFW_KEY_Y, KeyboardEvent::KeyCode::KEY_Y}, + {GLFW_KEY_Z, KeyboardEvent::KeyCode::KEY_Z}, + {GLFW_KEY_LEFT_BRACKET, KeyboardEvent::KeyCode::KEY_LEFT_BRACKET}, + {GLFW_KEY_BACKSLASH, KeyboardEvent::KeyCode::KEY_BACK_SLASH}, + {GLFW_KEY_RIGHT_BRACKET, KeyboardEvent::KeyCode::KEY_RIGHT_BRACKET}, + {GLFW_KEY_GRAVE_ACCENT, KeyboardEvent::KeyCode::KEY_GRAVE}, + {GLFW_KEY_WORLD_1, KeyboardEvent::KeyCode::KEY_GRAVE}, + {GLFW_KEY_WORLD_2, KeyboardEvent::KeyCode::KEY_NONE}, + + /* Function keys */ + {GLFW_KEY_ESCAPE, KeyboardEvent::KeyCode::KEY_ESCAPE}, + {GLFW_KEY_ENTER, KeyboardEvent::KeyCode::KEY_ENTER}, + {GLFW_KEY_TAB, KeyboardEvent::KeyCode::KEY_TAB}, + {GLFW_KEY_BACKSPACE, KeyboardEvent::KeyCode::KEY_BACKSPACE}, + {GLFW_KEY_INSERT, KeyboardEvent::KeyCode::KEY_INSERT}, + {GLFW_KEY_DELETE, KeyboardEvent::KeyCode::KEY_DELETE}, + {GLFW_KEY_RIGHT, KeyboardEvent::KeyCode::KEY_RIGHT_ARROW}, + {GLFW_KEY_LEFT, KeyboardEvent::KeyCode::KEY_LEFT_ARROW}, + {GLFW_KEY_DOWN, KeyboardEvent::KeyCode::KEY_DOWN_ARROW}, + {GLFW_KEY_UP, KeyboardEvent::KeyCode::KEY_UP_ARROW}, + {GLFW_KEY_PAGE_UP, KeyboardEvent::KeyCode::KEY_PG_UP}, + {GLFW_KEY_PAGE_DOWN, KeyboardEvent::KeyCode::KEY_PG_DOWN}, + {GLFW_KEY_HOME, KeyboardEvent::KeyCode::KEY_HOME}, + {GLFW_KEY_END, KeyboardEvent::KeyCode::KEY_END}, + {GLFW_KEY_CAPS_LOCK, KeyboardEvent::KeyCode::KEY_CAPS_LOCK}, + {GLFW_KEY_SCROLL_LOCK, KeyboardEvent::KeyCode::KEY_SCROLL_LOCK}, + {GLFW_KEY_NUM_LOCK, KeyboardEvent::KeyCode::KEY_NUM_LOCK}, + {GLFW_KEY_PRINT_SCREEN, KeyboardEvent::KeyCode::KEY_PRINT}, + {GLFW_KEY_PAUSE, KeyboardEvent::KeyCode::KEY_PAUSE}, + {GLFW_KEY_F1, KeyboardEvent::KeyCode::KEY_F1}, + {GLFW_KEY_F2, KeyboardEvent::KeyCode::KEY_F2}, + {GLFW_KEY_F3, KeyboardEvent::KeyCode::KEY_F3}, + {GLFW_KEY_F4, KeyboardEvent::KeyCode::KEY_F4}, + {GLFW_KEY_F5, KeyboardEvent::KeyCode::KEY_F5}, + {GLFW_KEY_F6, KeyboardEvent::KeyCode::KEY_F6}, + {GLFW_KEY_F7, KeyboardEvent::KeyCode::KEY_F7}, + {GLFW_KEY_F8, KeyboardEvent::KeyCode::KEY_F8}, + {GLFW_KEY_F9, KeyboardEvent::KeyCode::KEY_F9}, + {GLFW_KEY_F10, KeyboardEvent::KeyCode::KEY_F10}, + {GLFW_KEY_F11, KeyboardEvent::KeyCode::KEY_F11}, + {GLFW_KEY_F12, KeyboardEvent::KeyCode::KEY_F12}, + {GLFW_KEY_F13, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F14, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F15, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F16, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F17, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F18, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F19, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F20, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F21, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F22, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F23, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F24, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_F25, KeyboardEvent::KeyCode::KEY_NONE}, + {GLFW_KEY_KP_0, KeyboardEvent::KeyCode::KEY_0}, + {GLFW_KEY_KP_1, KeyboardEvent::KeyCode::KEY_1}, + {GLFW_KEY_KP_2, KeyboardEvent::KeyCode::KEY_2}, + {GLFW_KEY_KP_3, KeyboardEvent::KeyCode::KEY_3}, + {GLFW_KEY_KP_4, KeyboardEvent::KeyCode::KEY_4}, + {GLFW_KEY_KP_5, KeyboardEvent::KeyCode::KEY_5}, + {GLFW_KEY_KP_6, KeyboardEvent::KeyCode::KEY_6}, + {GLFW_KEY_KP_7, KeyboardEvent::KeyCode::KEY_7}, + {GLFW_KEY_KP_8, KeyboardEvent::KeyCode::KEY_8}, + {GLFW_KEY_KP_9, KeyboardEvent::KeyCode::KEY_9}, + {GLFW_KEY_KP_DECIMAL, KeyboardEvent::KeyCode::KEY_PERIOD}, + {GLFW_KEY_KP_DIVIDE, KeyboardEvent::KeyCode::KEY_KP_DIVIDE}, + {GLFW_KEY_KP_MULTIPLY, KeyboardEvent::KeyCode::KEY_KP_MULTIPLY}, + {GLFW_KEY_KP_SUBTRACT, KeyboardEvent::KeyCode::KEY_KP_MINUS}, + {GLFW_KEY_KP_ADD, KeyboardEvent::KeyCode::KEY_KP_PLUS}, + {GLFW_KEY_KP_ENTER, KeyboardEvent::KeyCode::KEY_KP_ENTER}, + {GLFW_KEY_KP_EQUAL, KeyboardEvent::KeyCode::KEY_EQUAL}, + {GLFW_KEY_LEFT_SHIFT, KeyboardEvent::KeyCode::KEY_LEFT_SHIFT}, + {GLFW_KEY_LEFT_CONTROL, KeyboardEvent::KeyCode::KEY_LEFT_CTRL}, + {GLFW_KEY_LEFT_ALT, KeyboardEvent::KeyCode::KEY_LEFT_ALT}, + {GLFW_KEY_LEFT_SUPER, KeyboardEvent::KeyCode::KEY_HYPER}, + {GLFW_KEY_RIGHT_SHIFT, KeyboardEvent::KeyCode::KEY_RIGHT_SHIFT}, + {GLFW_KEY_RIGHT_CONTROL, KeyboardEvent::KeyCode::KEY_RIGHT_CTRL}, + {GLFW_KEY_RIGHT_ALT, KeyboardEvent::KeyCode::KEY_RIGHT_ALT}, + {GLFW_KEY_RIGHT_SUPER, KeyboardEvent::KeyCode::KEY_HYPER}, + {GLFW_KEY_MENU, KeyboardEvent::KeyCode::KEY_MENU}, + {GLFW_KEY_LAST, KeyboardEvent::KeyCode::KEY_NONE}}; + +// wasm input bridge +#if defined(__EMSCRIPTEN__) +extern "C" { + +/** + * Invoked by JavaScript to feed the finalized UTF-8 text into the engine's InputSystem. + * This allows any active text input control (like InputField) to automatically receive characters. + */ +EMSCRIPTEN_KEEPALIVE void axmol_onwebinserttext(const char* utf8Text, int length) +{ + if (utf8Text && length > 0) + { + // Forward the string to the global dispatcher, which handles the active focused widget + ax::InputSystem::getInstance()->dispatchInsertText(std::string_view(utf8Text, length)); + } +} + +EMSCRIPTEN_KEEPALIVE void axmol_onwebdeletebackward(int count) +{ + if (count > 0) + ax::InputSystem::getInstance()->dispatchDeleteBackward(static_cast(count)); +} + +EMSCRIPTEN_KEEPALIVE void +axmol_onwebpointerevent(int type, int id, float x, float y, float pressure, int pointerType, int button, int buttons) +{ + auto inputSystem = ax::InputSystem::getInstance(); + + // Map Web pointer types to the strictly defined Engine PointerType enum + ax::PointerType mappedType = ax::PointerType::Mouse; + if (pointerType == 1) + mappedType = ax::PointerType::Touch; + else if (pointerType == 2) + mappedType = ax::PointerType::Pen; + + // itmask Validation (Beautiful Coincidence) + // The W3C 'buttons' bitmask (1=Left, 2=Right, 4=Middle) perfectly aligns + // with the engine's '(1 << NativeButtonIndex)' logic: + // Left(0): 1<<0 = 1 | Right(1): 1<<1 = 2 | Middle(2): 1<<2 = 4 + // Therefore, we can safely cast and pass the raw JS bitmask directly. + uint32_t activeButtonsMask = static_cast(buttons); + + // Map button, buttons bitmask identical to axmol, but button not + if (button == 1) + button = 2; // Axmol Middle + else if(button == 2) + button = 1; // Axmol Right + + // Assemble the modernized cohesive PointerInputState + // W3C Pointer Events button values are aligned with both GLFW mouse button indices and Axmol’s InputButton + // enumeration. + ax::PointerInputState pointerState{.id = static_cast(id), + .pressure = pressure, + .button = button, + .pressedButtons = activeButtonsMask, + .type = mappedType}; + + // Bundle coordinates into a single Vector payload + ax::Vec2 pos(x, y); + + // 5. Dispatch seamlessly into the unified core engine pipeline + switch (type) + { + case 0: + inputSystem->handlePointerDown(pos, pointerState); + break; + case 1: + // Contract enforcement from Event.h: + // "For PointerMove events the button value MUST be -1." + // While W3C usually sends -1 for moves, we force it here to guarantee architecture safety. + pointerState.button = ax::InputButton::None; + inputSystem->handlePointerMove(pos, pointerState); + break; + case 2: + inputSystem->handlePointerUp(pos, pointerState); + break; + case 3: + inputSystem->handlePointerCancel(pos, pointerState); + break; + } +} + +// Expose a scroll entry point for the Web bridge. +// Parameters: +// id - pointer id (if available from the browser), otherwise 0 for mouse +// x, y - canvas-local coordinates (pixels) +// deltaX/Y - normalized scroll delta in pixels (bridge should normalize deltaMode) +// pointerType - 0=mouse,1=touch,2=pen +// buttons - current buttons bitmask (W3C 'buttons' bitmask) +EMSCRIPTEN_KEEPALIVE void +axmol_onwebpointerscroll(int id, float x, float y, float deltaX, float deltaY, int pointerType, int buttons) +{ + auto inputSystem = ax::InputSystem::getInstance(); + + // Map pointerType to engine enum + ax::PointerType mappedType = ax::PointerType::Mouse; + if (pointerType == 1) + mappedType = ax::PointerType::Touch; + else if (pointerType == 2) + mappedType = ax::PointerType::Pen; + + // Build a minimal PointerInputState for scroll events. + // button is None for pure scroll; pressedButtons carries the active buttons mask. + ax::PointerInputState pointerState{.id = static_cast(id), + .pressure = 0.0f, + .button = ax::InputButton::None, + .pressedButtons = static_cast(buttons), + .type = mappedType}; + + // Convert coordinates to Vec2 and forward to InputSystem + ax::Vec2 pos(x, y); + ax::Vec2 scrollDelta(deltaX, deltaY); + + // Forward to existing scroll handler + inputSystem->handlePointerScroll(pos, scrollDelta, pointerState); +} +} + +static bool isWebInputFieldProxyFocused() +{ + // clang-format off + return MAIN_THREAD_EM_ASM_INT({ + var proxy = Module['axmol_ime_proxy']; + return !!(proxy && document.activeElement === proxy && + proxy.getAttribute('data-mode') === 'inputfield'); + }) != 0; + // clang-format on +} + +static void initWebInputBridge() +{ + // clang-format off + + // Execute inline JavaScript on the main thread to dynamically bind standard + // W3C PointerEvents directly to the engine's active HTML5 Canvas element. + MAIN_THREAD_EM_ASM(({ + var canvas = Module['canvas']; + if (!canvas) { + console.error("[Axmol] Target Canvas not found. PointerEvents initialization aborted."); + return; + } + + // Enable programmatic focus (Required to steal focus from input box) + if (!canvas.hasAttribute("tabindex")) { + canvas.setAttribute("tabindex", "0"); + } + + // Obliterate the ugly browser focus outline completely + // This kills the blue/black glowing borders when canvas.focus() is called! + canvas.style.outline = "none"; + canvas.style.boxShadow = "none"; // Guards against some specific WebKit/Safari shadows + canvas.style.webkitTapHighlightColor = "rgba(0,0,0,0)"; // Disables flash on mobile touch + + // Centralized event translator and forwarder + // Centralized event translator and forwarder + var mousePointerActive = false; + + function dispatchToPointerSystem(e, eventType, forcedPointerType) { + var editboxInput = Module.axmol_editbox_input; + if (editboxInput && e.target === editboxInput) { + return; + } + + if (eventType === 0 && editboxInput && document.activeElement === editboxInput) { + if (canvas) { + canvas.focus(); + } + } + + // Keep preventDefault() active for general canvas clicks to guard the game + // against webpage zooming, scrolling, and blue highlight selections. + if (e.cancelable) { + // Suppress browser default behaviors (such as pinch-to-zoom, rubber-banding, or scrolling) + e.preventDefault(); + } + + // Transform viewport coordinates (clientX/Y) into precise, local Canvas pixel coordinates. + // This safely normalizes any CSS resizing, high-DPI Retina scaling, or full-screen layout shifts. + var rect = canvas.getBoundingClientRect(); + var canvasX = (e.clientX - rect.left); + var canvasY = (e.clientY - rect.top); + + // Handle pressure telemetry and deploy the architecture safety firewall: + // Non-pressure devices or standard mice return 0.0f by default during execution. + // If the pointer is actively pressed down and reports 0, forcefully elevate it to 1.0f. + var rawPressure = typeof e.pressure === 'number' ? e.pressure : 0.0; + if (e.buttons > 0 && rawPressure === 0) { + rawPressure = 1.0; + } + + var pointerType = forcedPointerType || e.pointerType || 'mouse'; + var pointerId = pointerType === 'mouse' ? 0 : e.pointerId; + + // Identify device type: 0 = mouse, 1 = touch, 2 = pen + var ptrType = 1; + if (pointerType === 'mouse') ptrType = 0; + else if (pointerType === 'pen') ptrType = 2; + + // Cross the WebAssembly boundary to shoot the telemetry directly into C++ core + // Appended parameters: ptrType (device type), e.button (triggering button), e.buttons (active button bitmask) + Module._axmol_onwebpointerevent(eventType, pointerId, canvasX, canvasY, rawPressure, ptrType, e.button, e.buttons || 0); + } + + // Bind Down Event: Triggers pointer locking/capturing for boundary-proof dragging + canvas.addEventListener('pointerdown', function(e) { + // Lock the pointer context to the canvas. Subsequent 'move' and 'up' events + // will continue tracking safely even if the user drags completely outside the browser window. + try { + canvas.setPointerCapture(e.pointerId); + } catch (err) { + // Fail-safe catch block for older or restrictive browser sandboxes + } + if (e.pointerType === 'mouse') { + return; + } + dispatchToPointerSystem(e, 0); + }, {passive: false}); + + // Bind Move Event: Streams high-frequency telemetry including structural pressure fluctuations + canvas.addEventListener('pointermove', function(e) { + dispatchToPointerSystem(e, 1); + }, {passive: false}); + + // Bind Up Event: Automatically triggers pointer release via browser specifications + canvas.addEventListener('pointerup', function(e) { + // Note: browser internally executes releasePointerCapture(e.pointerId) upon pointerup + if (e.pointerType === 'mouse') { + return; + } + dispatchToPointerSystem(e, 2); + }, {passive: false}); + + // Bind Cancel Event: Intercepts OS-level interruptions (e.g., system popups or notifications) + canvas.addEventListener('pointercancel', function(e) { + if (e.pointerType === 'mouse') { + return; + } + dispatchToPointerSystem(e, 3); + }, {passive: false}); + + // PointerEvents intentionally do not emit pointerdown/pointerup for every + // chorded mouse button transition. Use mouse down/up for per-button + // lifecycle parity with GLFW/native desktop, while pointermove remains the + // unified movement stream. + canvas.addEventListener('mousedown', function(e) { + mousePointerActive = true; + dispatchToPointerSystem(e, 0, 'mouse'); + }, {passive: false}); + + window.addEventListener('mouseup', function(e) { + if (!mousePointerActive) { + return; + } + dispatchToPointerSystem(e, 2, 'mouse'); + if ((e.buttons || 0) === 0) { + mousePointerActive = false; + } + }, {passive: false}); + + // Bind Wheel Event: normalize deltaMode and forward as PointerScroll to the engine. + // Note: wheel events may not always include pointerId; fall back to 0 (mouse). + canvas.addEventListener('wheel', function(e) { + // Prevent page scrolling / pinch-zoom when interacting with the canvas. + if (e.cancelable) e.preventDefault(); + + // Compute canvas-local coordinates + var rect = canvas.getBoundingClientRect(); + var canvasX = (e.clientX - rect.left); + var canvasY = (e.clientY - rect.top); + + // Normalize deltaMode to pixels: + // 0 = DOM_DELTA_PIXEL, 1 = DOM_DELTA_LINE, 2 = DOM_DELTA_PAGE + // Use reasonable fallbacks: line -> 16px, page -> viewport height. + var deltaX = e.deltaX; + var deltaY = e.deltaY; + if (e.deltaMode === 1) { // lines + var LINE_HEIGHT = 16; // conservative default line height in pixels + deltaX *= LINE_HEIGHT; + deltaY *= LINE_HEIGHT; + } else if (e.deltaMode === 2) { // pages + deltaX *= window.innerHeight; + deltaY *= window.innerHeight; + } + + // Determine pointerId if available (some browsers include pointerId on wheel events) + var pid = (typeof e.pointerId !== 'undefined') ? e.pointerId : 0; + + // Determine device type mapping (0=mouse,1=touch,2=pen) + var ptrType = 0; + if (e.pointerType === 'touch') ptrType = 1; + else if (e.pointerType === 'pen') ptrType = 2; + // If pointerType is not present on wheel events, assume mouse (0). + + // Forward normalized scroll to native layer + // Module._axmol_onwebpointerscroll(id, x, y, deltaX, deltaY, ptrType, buttons) + Module._axmol_onwebpointerscroll(pid, canvasX, canvasY, deltaX, deltaY, ptrType, e.buttons || 0); + }, { passive: false }); + + console.log("[Axmol] Unified PointerEvents successfully attached to Canvas. Legacy GLFW mouse/touch bypassed."); + })); + + // Inject the shadow HTML input bridge to intercept native browser IME events + MAIN_THREAD_EM_ASM(({ + // 1. Rename to standardized proxy name + if (!Module['axmol_ime_proxy']) { + + var stringToUTF8WithLen = function(str) { + var byteLen = lengthBytesUTF8(str); + var ptr = _malloc(byteLen + 1); + stringToUTF8(str, ptr, byteLen + 1); + return { ptr: ptr, length: byteLen }; + }; + Module.stringToUTF8WithLen = stringToUTF8WithLen; + + // Initialize as standard text input + var proxy = document.createElement('input'); + proxy.type = 'text'; + proxy.id = 'axmol_ime_proxy'; + + // Hide globally but keep it in DOM + proxy.style.position = 'absolute'; + proxy.style.opacity = '0'; + proxy.style.left = '-9999px'; + proxy.style.top = '-9999px'; + proxy.style.zIndex = '-1'; + proxy.style.pointerEvents = 'none'; // Ensure clicks pass through when in ghost mode + + document.body.appendChild(proxy); + Module['axmol_ime_proxy'] = proxy; + + var isComposing = false; + var suppressNextDeleteInput = false; + + proxy.addEventListener('compositionstart', function() { + isComposing = true; + }); + + proxy.addEventListener('compositionend', function(e) { + isComposing = false; + // Only dispatch IME if not hijacked by EditBox UI + if (e.data && proxy.getAttribute('data-mode') !== 'editbox') { + var result = stringToUTF8WithLen(e.data); + Module._axmol_onwebinserttext(result.ptr, result.length); + _free(result.ptr); + proxy.value = ""; + } + }); + + proxy.addEventListener('keydown', function(e) { + if (proxy.getAttribute('data-mode') !== 'inputfield') return; + if (isComposing) return; + + if (e.key === 'Backspace') { + if (e.cancelable) { + e.preventDefault(); + } + Module._axmol_onwebdeletebackward(1); + proxy.value = ""; + suppressNextDeleteInput = true; + setTimeout(function() { + suppressNextDeleteInput = false; + }, 0); + } + }, { passive: false }); + + proxy.addEventListener('beforeinput', function(e) { + if (proxy.getAttribute('data-mode') !== 'inputfield') return; + if (isComposing) return; + + if (e.inputType === 'deleteContentBackward') { + if (e.cancelable) { + e.preventDefault(); + } + if (suppressNextDeleteInput) { + suppressNextDeleteInput = false; + proxy.value = ""; + return; + } + Module._axmol_onwebdeletebackward(1); + proxy.value = ""; + } + }, { passive: false }); + + proxy.addEventListener('input', function(e) { + // Only dispatch regular input if not hijacked by EditBox UI + if (proxy.getAttribute('data-mode') === 'editbox') return; + + if (!isComposing && e.inputType !== 'deleteContentBackward') { + if (proxy.value) { + var result = stringToUTF8WithLen(proxy.value); + Module._axmol_onwebinserttext(result.ptr, result.length); + _free(result.ptr); + } + proxy.value = ""; + } else if (!isComposing && e.inputType === 'deleteContentBackward') { + if (suppressNextDeleteInput) { + suppressNextDeleteInput = false; + proxy.value = ""; + return; + } + Module._axmol_onwebdeletebackward(1); + proxy.value = ""; + } + }); + } + })); + // clang-format on +} + +#endif + +////////////////////////////////////////////////////////////////////////// +// implement RenderView +////////////////////////////////////////////////////////////////////////// + +RenderView::RenderView(bool initglfw) : _windowZoomFactor(1.0f), _mainWindow(nullptr), _monitor(nullptr) +{ + _viewName = "axmol3"; + for (auto&& item : s_keyCodeItems) + { + _keyCodeMap[item.glfwKeyCode] = item.keyCode; + } + + GLFWEventHandler::setRenderView(this); + if (initglfw) + { + glfwSetErrorCallback(GLFWEventHandler::onGLFWError); + glfwInit(); + } +} + +RenderView::~RenderView() +{ + AXLOGD("deallocing RenderView: {}", fmt::ptr(this)); + GLFWEventHandler::setRenderView(nullptr); + glfwTerminate(); +} + +void* RenderView::getNativeWindow() const +{ +#if AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 + return glfwGetWin32Window(_mainWindow); +#elif AX_TARGET_PLATFORM == AX_PLATFORM_MAC + return (void*)glfwGetCocoaWindow(_mainWindow); +#elif AX_TARGET_PLATFORM == AX_PLATFORM_LINUX +# if defined(AX_ENABLE_WAYLAND) + int platform = glfwGetPlatform(); + return platform == GLFW_PLATFORM_WAYLAND ? (void*)glfwGetWaylandWindow(_mainWindow) + : (void*)glfwGetX11Window(_mainWindow); +# else + return (void*)glfwGetX11Window(_mainWindow); +# endif +#else + return nullptr; +#endif +} + +SurfaceHandle RenderView::getNativeDisplay() const +{ + auto driverType = DriverContext::currentDriverType(); + if (driverType == DriverType::Vulkan) + return _vkSurface; + +#if AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 + return glfwGetWin32Window(_mainWindow); +#elif AX_TARGET_PLATFORM == AX_PLATFORM_MAC + return driverType == DriverType::Metal ? (void*)glfwGetCocoaView(_mainWindow) + : (void*)glfwGetNSGLContext(_mainWindow); + return (void*)glfwGetNSGLContext(_mainWindow); +#elif AX_TARGET_PLATFORM == AX_PLATFORM_LINUX +# if defined(AX_ENABLE_WAYLAND) + int platform = glfwGetPlatform(); + return platform == GLFW_PLATFORM_WAYLAND ? (void*)glfwGetWaylandDisplay() : (void*)glfwGetX11Display(); +# else + return (void*)glfwGetX11Display(); +# endif +#else + return nullptr; +#endif +} + +WindowPlatform RenderView::getWindowPlatform() const +{ +#if AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 + return WindowPlatform::Win32; +#elif AX_TARGET_PLATFORM == AX_PLATFORM_MAC + return WindowPlatform::Cocoa; +#elif AX_TARGET_PLATFORM == AX_PLATFORM_LINUX + int platform = glfwGetPlatform(); + return platform == GLFW_PLATFORM_WAYLAND ? WindowPlatform::Wayland : WindowPlatform::X11; +#elif AX_TARGET_PLATFORM == AX_PLATFORM_WASM + return WindowPlatform::Web; +#else + return WindowPlatform::Unknown; +#endif +} + +RenderView* RenderView::create(std::string_view viewName) +{ + return RenderView::create(viewName, false); +} + +RenderView* RenderView::create(std::string_view viewName, bool resizable) +{ + auto ret = new RenderView; + if (ret->initWithRect(viewName, ax::Rect(0, 0, 960, 640), 1.0f, resizable)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +RenderView* RenderView::createWithRect(std::string_view viewName, + const ax::Rect& rect, + float windowZoomFactor, + bool resizable) +{ + auto ret = new RenderView; + if (ret->initWithRect(viewName, rect, windowZoomFactor, resizable)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +RenderView* RenderView::createWithFullscreen(std::string_view viewName) +{ + auto ret = new RenderView(); + if (ret->initWithFullScreen(viewName)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +RenderView* RenderView::createWithFullscreen(std::string_view viewName, + const GLFWvidmode& videoMode, + GLFWmonitor* monitor) +{ + auto ret = new RenderView(); + if (ret->initWithFullscreen(viewName, videoMode, monitor)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +bool RenderView::initWithRect(std::string_view viewName, const ax::Rect& rect, float windowZoomFactor, bool resizable) +{ + _viewName = viewName; + _windowZoomFactor = windowZoomFactor; + + Vec2 requestWinSize = rect.size * windowZoomFactor; + + // Try to initialize a high-performance graphics driver first. + // If any of the high-performance APIs (D3D11/D3D12/Vulkan/Metal) are enabled, + // the runtime will attempt initialization in the default priority order. + // If all attempts fail, OpenGL will then be explicitly selected as the fallback. + DriverContext::makeCurrentDriver(); + const auto fallbackGL = DriverContext::isOpenGL(); + if (fallbackGL) + { +#if AX_GLES_PROFILE + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, AX_GLES_PROFILE / AX_GLES_PROFILE_DEN); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); +#else + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // We want OpenGL 3.3 + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // We don't want the old OpenGL +#endif + } + else // Other Graphics driver, don't create gl context. + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + auto& contextAttrs = Application::getContextAttrs(); + + glfwWindowHint(GLFW_RESIZABLE, resizable ? GL_TRUE : GL_FALSE); + glfwWindowHint(GLFW_RED_BITS, contextAttrs.redBits); + glfwWindowHint(GLFW_GREEN_BITS, contextAttrs.greenBits); + glfwWindowHint(GLFW_BLUE_BITS, contextAttrs.blueBits); + glfwWindowHint(GLFW_ALPHA_BITS, contextAttrs.alphaBits); + glfwWindowHint(GLFW_DEPTH_BITS, contextAttrs.depthBits); + glfwWindowHint(GLFW_STENCIL_BITS, contextAttrs.stencilBits); + + glfwWindowHint(GLFW_SAMPLES, contextAttrs.multisamplingCount); + + const auto requireShowByUser = contextAttrs.visible; + glfwWindowHint(GLFW_VISIBLE, false); + glfwWindowHint(GLFW_DECORATED, contextAttrs.decorated); + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) + glfwWindowHintPointer(GLFW_WIN32_HWND_PARENT, contextAttrs.windowParent); +#endif + + _renderScaleMode = contextAttrs.renderScaleMode; +#if AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || \ + AX_TARGET_PLATFORM == AX_PLATFORM_WASM + // On Linux X11 platforms, GLFW does not support fractional DPI scaling (e.g., 1.5x). + // To ensure consistent rendering across high-DPI displays, we disable GLFW_SCALE_TO_MONITOR + // and apply custom scaling logic based on platform-specific DPI detection. + // GLFW_SCALE_TO_MONITOR support Win32, X11, Wasm + glfwWindowHint(GLFW_SCALE_TO_MONITOR, _renderScaleMode == RenderScaleMode::Physical ? GLFW_TRUE : GLFW_FALSE); +#endif + + _mainWindow = + glfwCreateWindow(static_cast(std::lround(requestWinSize.width)), + static_cast(std::lround(requestWinSize.height)), _viewName.c_str(), _monitor, nullptr); + if (_mainWindow == nullptr) + { + std::string message = "Can't create window"; + if (!_glfwError.empty()) + { + message.append("\nMore info: \n"); + message.append(_glfwError); + } + + showAlert(message, "Error launch application"); + utils::killCurrentProcess(); // kill current process, don't cause crash when driver issue. + return false; + } + + glfwSetWindowSizeLimits(_mainWindow, 1, 1, GLFW_DONT_CARE, GLFW_DONT_CARE); + +#if AX_ENABLE_GL + if (fallbackGL) + { + glfwMakeContextCurrent(_mainWindow); + DriverContext::activateCurrentDriver(); + + glfwSetWindowUserPointer(_mainWindow, gl::__state); + } +#endif + + if (requireShowByUser) + glfwShowWindow(_mainWindow); + + /* + * Note that the created window and context may differ from what you requested, + * as not all parameters and hints are + * [hard constraints](@ref window_hints_hard). This includes the size of the + * window, especially for full screen windows. To retrieve the actual + * attributes of the created window and context, use queries like @ref + * glfwGetWindowAttrib and @ref glfwGetWindowSize. + * + * see declaration glfwCreateWindow + */ + + int fbWidth, fbHeight; + glfwGetFramebufferSize(_mainWindow, &fbWidth, &fbHeight); + updateRenderSurface(fbWidth, fbHeight, SurfaceUpdateFlag::RenderSizeChanged | SurfaceUpdateFlag::SilentUpdate); + +#if AX_ENABLE_VK + if (DriverContext::isVulkan()) + { + auto _createSurface = [](VkInstance inst, void* window, VkSurfaceKHR* surface) { + return glfwCreateWindowSurface(inst, static_cast(window), nullptr, surface); + }; + auto driver = static_cast(axdrv); + const vk::SurfaceCreateInfo createInfo{ + .window = _mainWindow, .width = fbWidth, .height = fbHeight, .createFunc = _createSurface}; + bool ok = driver->recreateSurface(createInfo); + if (!ok) + { + AXLOGE("Failed to create Vulkan window surface."); + return false; + } + _vkSurface = driver->getSurface(); + } +#endif + + int w, h; + glfwGetWindowSize(_mainWindow, &w, &h); + updateScaledWindowSize(w, h, SurfaceUpdateFlag::WindowSizeChanged | SurfaceUpdateFlag::SilentUpdate); + +#if defined(__EMSCRIPTEN__) + s_fullscreenState = std::make_unique(); + // clang-format off + emscripten_set_orientationchange_callback(this, EM_TRUE, GLFWEventHandler::onWebOrientationChangeCallback); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, this, EM_TRUE, GLFWEventHandler::onWebFullscreenCallback); + + _isTouchDevice = !!EM_ASM_INT( + return window.matchMedia('(pointer: coarse)').matches && !window.matchMedia('(any-hover: hover)').matches; + ); + const auto maxTouchPoints = EM_ASM_INT( + return navigator.maxTouchPoints; + ); + AXLOGI("RenderView::initWithRect: isTouchDevice: {}, maxTouchPoints: {}", _isTouchDevice, maxTouchPoints); + + initWebInputBridge(); + // clang-format on +#else + glfwSetMouseButtonCallback(_mainWindow, GLFWEventHandler::onGLFWMouseCallBack); + glfwSetCursorPosCallback(_mainWindow, GLFWEventHandler::onGLFWMouseMoveCallBack); + glfwSetScrollCallback(_mainWindow, GLFWEventHandler::onGLFWMouseScrollCallback); + glfwSetPreeditCallback(_mainWindow, GLFWEventHandler::onGLFWPreeditCallback); +#endif + + glfwSetCharCallback(_mainWindow, GLFWEventHandler::onGLFWCharCallback); + glfwSetKeyCallback(_mainWindow, GLFWEventHandler::onGLFWKeyCallback); + glfwSetWindowPosCallback(_mainWindow, GLFWEventHandler::onGLFWWindowPosCallback); + glfwSetFramebufferSizeCallback(_mainWindow, GLFWEventHandler::onGLFWFramebufferSizeCallback); + glfwSetWindowSizeCallback(_mainWindow, GLFWEventHandler::onGLFWWindowSizeCallback); + glfwSetWindowIconifyCallback(_mainWindow, GLFWEventHandler::onGLFWWindowIconifyCallback); + glfwSetWindowFocusCallback(_mainWindow, GLFWEventHandler::onGLFWWindowFocusCallback); + glfwSetWindowCloseCallback(_mainWindow, GLFWEventHandler::onGLFWWindowCloseCallback); + + glfwSetCursorEnterCallback(_mainWindow, [](GLFWwindow* window, int entered) { + bool flag = !!entered; + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(EVENT_WINDOW_CURSOR_ENTER, &flag); + }); + +#if AX_ENABLE_GL + if (fallbackGL) + { +# if !defined(__EMSCRIPTEN__) + glfwSwapInterval(contextAttrs.vsync ? 1 : 0); +# endif + // Will cause OpenGL error 0x0500 when use ANGLE-GLES on desktop +# if !AX_GLES_PROFILE + // Enable point size by default. +# if defined(GL_VERSION_2_0) + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); +# else + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); +# endif + if (contextAttrs.multisamplingCount > 0) + glEnable(GL_MULTISAMPLE); +# endif + CHECK_GL_ERROR_DEBUG(); + } +#endif + + setIMEKeyboardState(false); + + return true; +} + +bool RenderView::initWithFullScreen(std::string_view viewName) +{ + // Create fullscreen window on primary monitor at its current video mode. + _monitor = glfwGetPrimaryMonitor(); + if (nullptr == _monitor) + return false; + + const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); + + // These are soft constraints. If the video mode is retrieved at runtime, the resulting window and context should + // match these exactly. If invalid attribs are passed (eg. from an outdated cache), window creation will NOT fail + // but the actual window/context may differ. + glfwWindowHint(GLFW_REFRESH_RATE, videoMode->refreshRate); + glfwWindowHint(GLFW_RED_BITS, videoMode->redBits); + glfwWindowHint(GLFW_BLUE_BITS, videoMode->blueBits); + glfwWindowHint(GLFW_GREEN_BITS, videoMode->greenBits); + + return initWithRect(viewName, ax::Rect(0, 0, (float)videoMode->width, (float)videoMode->height), 1.0f, false); +} + +bool RenderView::initWithFullscreen(std::string_view viewname, const GLFWvidmode& videoMode, GLFWmonitor* monitor) +{ + // Create fullscreen on specified monitor at the specified video mode. + _monitor = monitor; + if (nullptr == _monitor) + return false; + + // These are soft constraints. If the video mode is retrieved at runtime, the resulting window and context should + // match these exactly. If invalid attribs are passed (eg. from an outdated cache), window creation will NOT fail + // but the actual window/context may differ. + glfwWindowHint(GLFW_REFRESH_RATE, videoMode.refreshRate); + glfwWindowHint(GLFW_RED_BITS, videoMode.redBits); + glfwWindowHint(GLFW_BLUE_BITS, videoMode.blueBits); + glfwWindowHint(GLFW_GREEN_BITS, videoMode.greenBits); + + return initWithRect(viewname, ax::Rect(0, 0, (float)videoMode.width, (float)videoMode.height), 1.0f, false); +} + +void RenderView::setViewName(std::string_view viewName) +{ + RenderViewCore::setViewName(viewName); + if (_mainWindow) + glfwSetWindowTitle(_mainWindow, _viewName.c_str()); +} + +bool RenderView::isKeyPressed(int key) const +{ + return _mainWindow && glfwGetKey(_mainWindow, key) == GLFW_PRESS; +} + +bool RenderView::isGfxContextReady() +{ + return nullptr != _mainWindow; +} + +void RenderView::end() +{ + _vkSurface = nullptr; + + if (_mainWindow) + { + glfwSetWindowShouldClose(_mainWindow, 1); + _mainWindow = nullptr; + } + // Release self. Otherwise, RenderView could not be freed. + release(); +} + +void RenderView::swapBuffers() +{ +#if AX_ENABLE_GL + if (_mainWindow && DriverContext::isOpenGL()) + glfwSwapBuffers(_mainWindow); +#endif +} + +bool RenderView::windowShouldClose() +{ + if (_mainWindow) + return glfwWindowShouldClose(_mainWindow) ? true : false; + else + return true; +} + +void RenderView::pollEvents() +{ + glfwPollEvents(); +} + +void RenderView::setIMEKeyboardState(bool bOpen) +{ + if (!_mainWindow) + return; + + auto lastPointerPosition = InputSystem::getInstance()->getLastPointerPosition(); + +#if !defined(__EMSCRIPTEN__) +# ifdef _WIN32 + ::glfwFocusWindow(_mainWindow); +# endif + glfwSetInputMode(_mainWindow, GLFW_IME, bOpen ? 1 : 0); + + if (bOpen) + glfwSetPreeditCursorRectangle(_mainWindow, static_cast(lastPointerPosition.x), + static_cast(lastPointerPosition.y), 1, 20); +#else + // clang-format off + // Synchronize browser DOM focus state with the engine's internal IME state + // We position the invisible proxy element at the specific mouse coordinates + EM_ASM({ + var proxy = Module['axmol_ime_proxy']; + var canvas = Module['canvas']; + + if (proxy && canvas) + { + if ($0) // bOpen == true + { + // 1. Get the bounding box of the canvas in the viewport to get absolute offset + var rect = canvas.getBoundingClientRect(); + + // 2. Calculate the position relative to the viewport: + // Canvas top-left + Mouse offset (passed as $1, $2) + var styleX = (rect.left + $1) + 'px'; + var styleY = (rect.top + $2) + 'px'; + + // 3. Set the proxy to fixed position at the exact mouse cursor location + proxy.style.position = 'fixed'; + proxy.style.left = styleX; + proxy.style.top = styleY; + proxy.style.zIndex = '9999'; + + // 4. Configure proxy for IME input (invisible, non-blocking) + proxy.setAttribute('data-mode', 'inputfield'); + proxy.style.opacity = "0"; + proxy.style.pointerEvents = "none"; + proxy.value = ""; + proxy.focus(); + } + else + { + // Only blur if InputField is currently holding the focus + if (proxy.getAttribute('data-mode') === 'inputfield') + { + proxy.blur(); + } + } + } + }, + bOpen, (int)lastPointerPosition.x, (int)lastPointerPosition.y); + // clang-format on +#endif +} + +#if AX_ICON_SET_SUPPORT +void RenderView::setIcon(std::string_view filename) const +{ + this->setIcon({filename}); +} + +void RenderView::setIcon(std::span filelist) const +{ + if (filelist.empty()) + return; + std::vector icons; + for (auto& filename : filelist) + { + Image* icon = new Image(); + if (icon->initWithImageFile(filename)) + { + icons.emplace_back(icon); + } + else + { + AX_SAFE_DELETE(icon); + } + } + + if (icons.empty()) + return; // No valid images + size_t iconsCount = icons.size(); + auto images = new GLFWimage[iconsCount]; + for (size_t i = 0; i < iconsCount; i++) + { + auto& image = images[i]; + auto& icon = icons[i]; + image.width = icon->getWidth(); + image.height = icon->getHeight(); + image.pixels = icon->getData(); + }; + + GLFWwindow* window = this->getWindow(); + glfwSetWindowIcon(window, iconsCount, images); + + AX_SAFE_DELETE_ARRAY(images); + for (auto&& icon : icons) + { + AX_SAFE_DELETE(icon); + } +} + +void RenderView::setDefaultIcon() const +{ + GLFWwindow* window = this->getWindow(); + glfwSetWindowIcon(window, 0, nullptr); +} +#endif /* AX_ICON_SET_SUPPORT */ + +void RenderView::setCursorVisible(bool isVisible) +{ + if (_mainWindow == NULL) + return; + + if (isVisible) + glfwSetInputMode(_mainWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + else + glfwSetInputMode(_mainWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); +} + +float RenderView::getWindowZoomFactor() const +{ + return _windowZoomFactor; +} + +bool RenderView::isFullscreen() const +{ + return (_monitor != nullptr); +} + +void RenderView::setFullscreen() +{ + setFullscreen(-1, -1, -1); +} + +void RenderView::setFullscreen(int w, int h, int refreshRate) +{ + auto monitor = glfwGetPrimaryMonitor(); + if (nullptr == monitor || monitor == _monitor) + { + return; + } + this->setFullscreen(monitor, w, h, refreshRate); +} + +void RenderView::setFullscreen(int monitorIndex) +{ + setFullscreen(monitorIndex, -1, -1, -1); +} + +void RenderView::setFullscreen(int monitorIndex, int w, int h, int refreshRate) +{ + int count = 0; + GLFWmonitor** monitors = glfwGetMonitors(&count); + if (monitorIndex < 0 || monitorIndex >= count) + { + return; + } + GLFWmonitor* monitor = monitors[monitorIndex]; + if (nullptr == monitor || _monitor == monitor) + { + return; + } + this->setFullscreen(monitor, w, h, refreshRate); +} + +void RenderView::setFullscreen(GLFWmonitor* monitor, int w, int h, int refreshRate) +{ + _monitor = monitor; + + const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); + if (w == -1) + w = videoMode->width; + if (h == -1) + h = videoMode->height; + if (refreshRate == -1) + refreshRate = videoMode->refreshRate; + + glfwSetWindowMonitor(_mainWindow, _monitor, 0, 0, w, h, refreshRate); +} + +void RenderView::setWindowed(int width, int height, bool borderless) +{ + if (!this->isFullscreen()) + { + glfwSetWindowAttrib(_mainWindow, GLFW_DECORATED, borderless ? GLFW_FALSE : GLFW_TRUE); + + if (glfwGetWindowAttrib(_mainWindow, GLFW_MAXIMIZED)) + glfwRestoreWindow(_mainWindow); + this->setWindowSize((float)width, (float)height); + } + else + { + width *= _windowZoomFactor; + height *= _windowZoomFactor; + const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); + int xpos = 0, ypos = 0; + glfwGetMonitorPos(_monitor, &xpos, &ypos); + xpos += (int)((videoMode->width - width) * 0.5f); + ypos += (int)((videoMode->height - height) * 0.5f); + _monitor = nullptr; + glfwSetWindowAttrib(_mainWindow, GLFW_DECORATED, borderless ? GLFW_FALSE : GLFW_TRUE); + glfwSetWindowMonitor(_mainWindow, nullptr, xpos, ypos, width, height, GLFW_DONT_CARE); +#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + // on mac window will sometimes lose title when windowed + glfwSetWindowTitle(_mainWindow, _viewName.c_str()); +#endif + } +} + +Vec2 RenderView::getNativeWindowSize() const +{ + if (_mainWindow != nullptr) + { + int w = 0, h = 0; + glfwGetWindowSize(_mainWindow, &w, &h); + return Vec2(w, h); + } + return Vec2{}; +} + +void RenderView::getWindowPosition(int* xpos, int* ypos) +{ + if (_mainWindow != nullptr && getWindowPlatform() != WindowPlatform::Wayland) + glfwGetWindowPos(_mainWindow, xpos, ypos); +} + +int RenderView::getMonitorCount() const +{ + int count = 0; + glfwGetMonitors(&count); + return count; +} + +Vec2 RenderView::getMonitorSize() const +{ + GLFWmonitor* monitor = _monitor; + if (nullptr == monitor) + { + GLFWwindow* window = this->getWindow(); + monitor = glfwGetWindowMonitor(window); + } + if (nullptr == monitor) + { + monitor = glfwGetPrimaryMonitor(); + } + if (nullptr != monitor) + { + const GLFWvidmode* videoMode = glfwGetVideoMode(monitor); + Vec2 size = Vec2((float)videoMode->width, (float)videoMode->height); + return size; + } + return Vec2::ZERO; +} + +void RenderView::setWindowSizeLimits(int minwidth, int minheight, int maxwidth, int maxheight) +{ + if (_mainWindow == NULL) + return; + + glfwSetWindowSizeLimits(_mainWindow, minwidth, minheight, maxwidth, maxheight); +} + +void RenderView::onGLFWFramebufferSizeCallback(GLFWwindow* window, int fbWidth, int fbHeight) +{ + AXLOGD("RenderView::onGLFWFramebufferSizeCallback: ({}, {})", fbWidth, fbHeight); + + updateRenderSurface(fbWidth, fbHeight, SurfaceUpdateFlag::RenderSizeChanged); +} + +void RenderView::onGLFWWindowSizeCallback(GLFWwindow* /*window*/, int w, int h) +{ + AXLOGD("RenderView::onGLFWWindowSizeCallback: ({}, {})", w, h); + + updateScaledWindowSize(w, h, SurfaceUpdateFlag::WindowSizeChanged); + + Size size(w, h); + + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderView::EVENT_WINDOW_RESIZED, &size); +} + +void RenderView::setWindowZoomFactor(float zoomFactor) +{ + AXASSERT(zoomFactor > 0.0f, "zoomFactor must be larger than 0"); + + if (std::abs(_windowZoomFactor - zoomFactor) < FLT_EPSILON) + return; + + _windowZoomFactor = zoomFactor; + applyWindowSize(); +} + +void RenderView::setWindowSize(float width, float height) +{ + if (width == 0 || height == 0) + return; + Vec2 requestSize{width, height}; + if (requestSize.equals(_windowSize)) + return; + + _windowSize.set(width, height); + applyWindowSize(); +} + +void RenderView::updateScaledWindowSize(int w, int h, uint8_t updateFlag) +{ + updateRenderScale(); + + double scaledWidth = w / (double)_windowZoomFactor; + double scaledHeight = h / (double)_windowZoomFactor; + + // Translate to logical size on platforms where pixels and screen coordinates always map 1:1 (Win32, X11) + // Note: wasm coordinates not map 1:1 when _renderScaleMode is RenderScaleMode::Physical + if (_renderScaleMode == RenderScaleMode::Physical) + { + auto windowPlatform = getWindowPlatform(); + if (windowPlatform == WindowPlatform::Win32 || windowPlatform == WindowPlatform::X11) + { + const auto factor = (1 / (double)_renderScale); + scaledWidth *= factor; + scaledHeight *= factor; + } + } + + Vec2 scaledSize{static_cast(std::round(scaledWidth)), static_cast(std::round(scaledHeight))}; + if (!scaledSize.equals(_windowSize)) + updateRenderSurface(scaledSize.width, scaledSize.height, updateFlag); +} + +void RenderView::applyWindowSize() +{ + double unscaledWidth = _windowSize.width * _windowZoomFactor, + unscaledHeight = _windowSize.height * _windowZoomFactor; + // Translate to physical size on platforms where pixels and screen coordinates always map 1:1 + if (_renderScaleMode == RenderScaleMode::Physical) + { + auto windowPlatform = getWindowPlatform(); + if (windowPlatform == WindowPlatform::Win32 || windowPlatform == WindowPlatform::X11) + { + unscaledWidth *= _renderScale; + unscaledHeight *= _renderScale; + } + } + glfwSetWindowSize(_mainWindow, static_cast(std::lround(unscaledWidth)), + static_cast(std::lround(unscaledHeight))); + + // process platform that window size callback not trigger(wayland) + maybeDispatchResizeEvent(SurfaceUpdateFlag::WindowSizeChanged); +} + +/** + * Updates the render scale and input scale factors based on the current platform + * and render scale mode. + * + * - On platforms where screen coordinates map 1:1 to physical pixels (Win32, X11), + * high-DPI scaling is only applied when in Physical mode. In this case, _inputScale + * always 1.0 + * . + * - On other platforms (e.g., macOS, Wayland), _renderScale is still queried to adjust r + * endering for high-DPI displays (e.g., viewport size). and _inputScale shoud same with + * render scale to converts from screen coordinates to the render view's logical coordinate space + * + * This function uses glfwGetWindowContentScale() to retrieve the current content scale + * factor, which may change when moving the window between monitors with different DPI + * settings. + * + * renderScale: for computing logical window size + * inputScale: for transform input axis + */ +void RenderView::updateRenderScale() +{ + float inputScale{1.0f}; + + auto windowPlatform = getWindowPlatform(); + if (windowPlatform == WindowPlatform::Win32 || windowPlatform == WindowPlatform::X11 || + windowPlatform == WindowPlatform::Web) + { + if (_renderScaleMode == RenderScaleMode::Physical) + { + float ignoreVal; + glfwGetWindowContentScale(_mainWindow, &_renderScale, &ignoreVal); + inputScale = windowPlatform != WindowPlatform::Web ? 1.0f : _renderScale; + } + else + { + inputScale = _renderScale = 1.0f; + } + } + else + { + float ignoreVal; + glfwGetWindowContentScale(_mainWindow, &_renderScale, &ignoreVal); + inputScale = _renderScale; + } + + // Update InputSystem with the computed input scale so it can apply the + // appropriate scaling when dispatching input events. + InputSystem::getInstance()->setInputScale(inputScale); +} + +void RenderView::onGLFWError(int errorID, const char* errorDesc) +{ + if (_mainWindow) + { + _glfwError = fmt::format("GLFWError #{} Happen, {}", errorID, errorDesc); + } + else + { + _glfwError.append(fmt::format("GLFWError #{} Happen, {}\n", errorID, errorDesc)); + } + AXLOGE("{}", _glfwError); +} + +#if !defined(__EMSCRIPTEN__) +void RenderView::onGLFWMouseCallBack(GLFWwindow* /*window*/, int button, int action, int /*mods*/) +{ + // TODO mods support + auto inputSystem = InputSystem::getInstance(); + + if (action == GLFW_PRESS) + { + _pressedButtons |= (1u << button); + + PointerInputState pointerData{ + .id = MOUSE_POINTER_ID, .button = button, .pressedButtons = _pressedButtons, .type = PointerType::Mouse}; + inputSystem->handlePointerDown(_mousePosition, pointerData); + } + else if (action == GLFW_RELEASE) + { + _pressedButtons &= ~(1u << button); + + PointerInputState pointerData{ + .id = MOUSE_POINTER_ID, .button = button, .pressedButtons = _pressedButtons, .type = PointerType::Mouse}; + inputSystem->handlePointerUp(_mousePosition, pointerData); + } +} + +void RenderView::onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y) +{ + _mousePosition.x = static_cast(x); + _mousePosition.y = static_cast(y); + + PointerInputState pointerState{.id = MOUSE_POINTER_ID, + .button = static_cast(InputButton::None), + .pressedButtons = _pressedButtons, + .type = PointerType::Mouse}; + InputSystem::getInstance()->handlePointerMove(_mousePosition, pointerState); +} + +void RenderView::onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y) +{ + PointerInputState pointerState{.id = MOUSE_POINTER_ID, + .button = InputButton::None, + .pressedButtons = _pressedButtons, + .type = PointerType::Mouse}; + + InputSystem::getInstance()->handlePointerScroll(_mousePosition, Vec2{static_cast(x), -static_cast(y)}, + pointerState); +} +#endif + +void RenderView::onGLFWKeyCallback(GLFWwindow* /*window*/, int key, int /*scancode*/, int action, int /*mods*/) +{ + auto keyCode = _keyCodeMap[key]; +#if defined(__EMSCRIPTEN__) + if (isWebInputFieldProxyFocused() && keyCode == KeyboardEvent::KeyCode::KEY_BACKSPACE) + return; +#endif + + InputPhase phase{}; + switch (action) + { + case GLFW_PRESS: + phase = InputPhase::KeyDown; + break; + case GLFW_RELEASE: + phase = InputPhase::KeyUp; + break; + case GLFW_REPEAT: + phase = InputPhase::KeyRepeat; + break; + } + + InputSystem::getInstance()->handleKeyEvent(keyCode, phase); +} + +void RenderView::onGLFWCharCallback(GLFWwindow* /*window*/, unsigned int charCode) +{ +#if defined(__EMSCRIPTEN__) + if (isWebInputFieldProxyFocused()) + return; +#endif + + // static std::unordered_set controlUnicode = { + // "\xEF\x9C\x80", // up + // "\xEF\x9C\x81", // down + // "\xEF\x9C\x82", // left + // "\xEF\x9C\x83", // right + // "\xEF\x9C\xA8", // delete + // "\xEF\x9C\xA9", // home + // "\xEF\x9C\xAB", // end + // "\xEF\x9C\xAC", // pageup + // "\xEF\x9C\xAD", // pagedown + // "\xEF\x9C\xB9" // clear + // }; + + static const std::unordered_set controlUnicode = { + U'\uF700', // up + U'\uF701', // down + U'\uF702', // left + U'\uF703', // right + U'\uF728', // delete + U'\uF729', // home + U'\uF72B', // end + U'\uF72C', // pageup + U'\uF72D', // pagedown + U'\uF739' // clear + }; + + // Check for send control key + + if (!controlUnicode.contains(static_cast(charCode))) + { + std::string utf8String; + char32_t codepoint = static_cast(charCode); + text_utils::UTF32ToUTF8(std::u32string_view{&codepoint, 1zu}, utf8String); + InputSystem::getInstance()->dispatchInsertText(utf8String); + } +} + +void RenderView::onGLFWPreeditCallback(GLFWwindow* window, + int preedit_count, + unsigned int* preedit_string, // UTF-32 + int /*block_count*/, + int* /*block_sizes*/, + int /*focused_block*/, + int caret) +{ + std::string utf8String; + + if (preedit_count > 0) + { + text_utils::UTF32ToUTF8( + std::u32string_view{std::bit_cast(preedit_string), static_cast(preedit_count)}, + utf8String); + } + + InputSystem::getInstance()->dispatchUpdatePreedit(utf8String, caret); +} + +void RenderView::onGLFWWindowPosCallback(GLFWwindow* /*window*/, int x, int y) +{ + auto director = Director::getInstance(); + director->setViewport(); + + Vec2 pos(x, y); + director->getEventDispatcher()->dispatchCustomEvent(RenderView::EVENT_WINDOW_POSITIONED, &pos); +} + +void RenderView::onGLFWWindowIconifyCallback(GLFWwindow* /*window*/, int iconified) +{ + if (iconified == GL_TRUE) + { + Application::getInstance()->applicationDidEnterBackground(); + } + else + { + Application::getInstance()->applicationWillEnterForeground(); + } +} + +void RenderView::onGLFWWindowFocusCallback(GLFWwindow* /*window*/, int focused) +{ + if (focused == GL_TRUE) + { + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderView::EVENT_WINDOW_FOCUSED, nullptr); + } + else + { + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderView::EVENT_WINDOW_UNFOCUSED, nullptr); + } +} + +void RenderView::onGLFWWindowCloseCallback(GLFWwindow* window) +{ + bool isClose = true; + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderView::EVENT_WINDOW_CLOSE, &isClose); + if (isClose == false) + { + glfwSetWindowShouldClose(window, 0); + } +} + +#if defined(__EMSCRIPTEN__) +void RenderView::onWebOrientationChangeCallback(int /*eventType*/, const EmscriptenOrientationChangeEvent* e) +{ + AXLOGD("onWebOrientationChangeCallback: orientationIndex:{}, orientationAngle:{}", e->orientationIndex, + e->orientationAngle); + + if (s_fullscreenState->isFullscreen) + { + int screenWidth = 0, screenHeight = 0; + emscripten_get_screen_size(&screenWidth, &screenHeight); + AXLOGD("Screen size after orientation change: ({}, {})", screenWidth, screenHeight); + glfwSetWindowSize(_mainWindow, screenWidth, screenHeight); + } + // else: browser handling canvas size +} + +void RenderView::onWebFullscreenCallback(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e) +{ + if (e->isFullscreen == s_fullscreenState->isFullscreen) + return; + + auto& windowedSize = s_fullscreenState->windowedSize; + s_fullscreenState->isFullscreen = e->isFullscreen; + if (e->isFullscreen) + { + glfwGetWindowSize(_mainWindow, &windowedSize.x, &windowedSize.y); + + AXLOGD("onWebFullscreenCallback: enter full screen: ({},{}) => ({},{})", windowedSize.x, windowedSize.y, + e->screenWidth, e->screenHeight); + glfwSetWindowSize(_mainWindow, e->screenWidth, e->screenHeight); + } + else + { + AXLOGD("onWebFullscreenCallback: exit full screen => ({},{}) => ({},{})", e->screenWidth, e->screenHeight, + windowedSize.x, windowedSize.y); + glfwSetWindowSize(_mainWindow, windowedSize.x, windowedSize.y); + } +} + +#endif + +} // namespace ax + +#if defined(__linux__) +# pragma pop_macro("None") +#endif diff --git a/axmol/platform/desktop/RenderViewImpl.h b/axmol/platform/pc/RenderView-pc.h similarity index 79% rename from axmol/platform/desktop/RenderViewImpl.h rename to axmol/platform/pc/RenderView-pc.h index 7174cec6eb76..34b9574c6074 100644 --- a/axmol/platform/desktop/RenderViewImpl.h +++ b/axmol/platform/pc/RenderView-pc.h @@ -29,15 +29,13 @@ THE SOFTWARE. #include "axmol/platform/GL.h" #include "axmol/base/Object.h" #include "axmol/platform/Common.h" -#include "axmol/platform/RenderView.h" -#include "axmol/base/EventMouse.h" +#include "axmol/platform/RenderViewCore.h" #if AX_ENABLE_VK # include "glad/vulkan.h" #endif #include "GLFW/glfw3.h" #if defined(__EMSCRIPTEN__) # include "axmol/tlx/vector.hpp" -struct EmscriptenMouseEvent; struct EmscriptenTouchEvent; struct EmscriptenFullscreenChangeEvent; struct EmscriptenOrientationChangeEvent; @@ -47,21 +45,21 @@ namespace ax { class GLFWEventHandler; -class AX_DLL RenderViewImpl : public RenderView +class AX_DLL RenderView : public RenderViewCore { friend class GLFWEventHandler; public: - static RenderViewImpl* create(std::string_view viewName); - static RenderViewImpl* create(std::string_view viewName, bool resizable); - static RenderViewImpl* createWithRect(std::string_view viewName, - const Rect& rect, - float zoomFactor = 1.0f, - bool resizable = false); - static RenderViewImpl* createWithFullscreen(std::string_view viewName); - static RenderViewImpl* createWithFullscreen(std::string_view viewName, - const GLFWvidmode& videoMode, - GLFWmonitor* monitor); + static RenderView* create(std::string_view viewName); + static RenderView* create(std::string_view viewName, bool resizable); + static RenderView* createWithRect(std::string_view viewName, + const Rect& rect, + float zoomFactor = 1.0f, + bool resizable = false); + static RenderView* createWithFullscreen(std::string_view viewName); + static RenderView* createWithFullscreen(std::string_view viewName, + const GLFWvidmode& videoMode, + GLFWmonitor* monitor); float getWindowZoomFactor() const override; // void centerWindow(); @@ -126,9 +124,6 @@ class AX_DLL RenderViewImpl : public RenderView */ void setCursorVisible(bool isVisible) override; - /** Get render scale */ - float getRenderScale() const override { return _renderScale; } - void* getNativeWindow() const override; SurfaceHandle getNativeDisplay() const override; WindowPlatform getWindowPlatform() const override; @@ -159,8 +154,8 @@ class AX_DLL RenderViewImpl : public RenderView bool isKeyPressed(int key) const; protected: - RenderViewImpl(bool initglfw = true); - ~RenderViewImpl() override; + RenderView(bool initglfw = true); + ~RenderView() override; bool initWithRect(std::string_view viewName, const Rect& rect, float zoomFactor, bool resizable); bool initWithFullScreen(std::string_view viewName); @@ -168,15 +163,15 @@ class AX_DLL RenderViewImpl : public RenderView // GLFW callbacks void onGLFWError(int errorID, const char* errorDesc); - void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify); - void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y); + #if defined(__EMSCRIPTEN__) void onWebOrientationChangeCallback(int eventType, const EmscriptenOrientationChangeEvent* e); void onWebFullscreenCallback(int eventType, const EmscriptenFullscreenChangeEvent* e); - void onWebTouchCallback(int eventType, const EmscriptenTouchEvent* touchEvent); - void onWebClickCallback(); -#endif +#else + void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify); + void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y); void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y); +#endif void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); void onGLFWCharCallback(GLFWwindow* window, unsigned int character); void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y); @@ -185,6 +180,13 @@ class AX_DLL RenderViewImpl : public RenderView void onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified); void onGLFWWindowFocusCallback(GLFWwindow* window, int focused); void onGLFWWindowCloseCallback(GLFWwindow* window); + void onGLFWPreeditCallback(GLFWwindow* window, + int preedit_count, + unsigned int* preedit_string, + int block_count, + int* block_sizes, + int focused_block, + int caret); protected: void updateScaledWindowSize(int w, int h, uint8_t updaetFlag); @@ -192,24 +194,18 @@ class AX_DLL RenderViewImpl : public RenderView /* resize platform window when user set zoomFactor, windowSize */ void applyWindowSize(); - bool _isTouchDevice = false; - bool _captured; - RenderScaleMode _renderScaleMode{}; - // Render scale factor: - // - Used to convert physical window size to logical size. - // - Also applied as the input scaling factor on platforms where - // screen coordinates do not map 1:1 to physical pixels. - float _renderScale{1.0f}; + float _windowZoomFactor; - // Input scale factor: - // - Always 1.0 on platforms with a 1:1 mapping between screen coordinates - // and physical pixels. - // - On other platforms, matches _renderScale to account for DPI scaling. - float _inputScale{1.0f}; + bool _isTouchDevice{false}; // Whether the current platform supports touch input. - float _windowZoomFactor; +#if !defined(__EMSCRIPTEN__) + Vec2 _mousePosition; // Current mouse position, used for synthesizing mouse events when touch events are cancelled. + uint32_t _pressedButtons{0}; +#endif + + std::unordered_map _keyCodeMap; GLFWwindow* _mainWindow; GLFWmonitor* _monitor; @@ -218,16 +214,6 @@ class AX_DLL RenderViewImpl : public RenderView std::string _glfwError; -#if defined(__EMSCRIPTEN__) - tlx::pod_vector _touchesId; - tlx::pod_vector _touchesX; - tlx::pod_vector _touchesY; -#endif - - float _mouseX{0.0f}; - float _mouseY{0.0f}; - EventMouse _currentMouseEvent{}; - public: // View will trigger an event when window is resized, gains or loses focus static const std::string_view EVENT_WINDOW_POSITIONED; @@ -235,10 +221,11 @@ class AX_DLL RenderViewImpl : public RenderView static const std::string_view EVENT_WINDOW_FOCUSED; static const std::string_view EVENT_WINDOW_UNFOCUSED; static const std::string_view EVENT_WINDOW_CLOSE; + static const std::string_view EVENT_WINDOW_CURSOR_ENTER; private: void updateRenderScale(); - AX_DISALLOW_COPY_AND_ASSIGN(RenderViewImpl); + AX_DISALLOW_COPY_AND_ASSIGN(RenderView); }; } // end of namespace ax diff --git a/axmol/platform/wasm/Application-wasm.cpp b/axmol/platform/wasm/Application-wasm.cpp index a4c6bfe22489..7e1231630d50 100644 --- a/axmol/platform/wasm/Application-wasm.cpp +++ b/axmol/platform/wasm/Application-wasm.cpp @@ -40,31 +40,33 @@ THE SOFTWARE. # include "axmol/tlx/utility.hpp" # include +extern void _axmolPerformFrameBoundaryTasks(); + extern void axmol_wasm_app_exit(); extern "C" { // -void axmol_hdoc_visibilitychange(bool hidden) +EMSCRIPTEN_KEEPALIVE void axmol_hdoc_visibilitychange(bool hidden) { - ax::EventCustom event(hidden ? EVENT_COME_TO_BACKGROUND : EVENT_COME_TO_FOREGROUND); + ax::CustomEvent event(hidden ? EVENT_COME_TO_BACKGROUND : EVENT_COME_TO_FOREGROUND); ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&event, true); } // webglcontextlost -void axmol_webglcontextlost() +EMSCRIPTEN_KEEPALIVE void axmol_webglcontextlost() { AXLOGI("receive event: webglcontextlost"); } // webglcontextrestored -void axmol_webglcontextrestored() +EMSCRIPTEN_KEEPALIVE void axmol_webglcontextrestored() { AXLOGI("receive event: webglcontextrestored"); auto director = ax::Director::getInstance(); axdrv->resetState(); director->resetMatrixStack(); - ax::EventCustom recreatedEvent(EVENT_RENDERER_RECREATED); + ax::CustomEvent recreatedEvent(EVENT_RENDERER_RECREATED); director->getEventDispatcher()->dispatchEvent(&recreatedEvent, true); director->setRenderDefaults(); # if AX_ENABLE_CONTEXT_LOSS_RECOVERY @@ -72,17 +74,17 @@ void axmol_webglcontextrestored() # endif } -void axmol_dev_pause() +EMSCRIPTEN_KEEPALIVE void axmol_dev_pause() { ax::DevToolsImpl::getInstance()->pause(); } -void axmol_dev_resume() +EMSCRIPTEN_KEEPALIVE void axmol_dev_resume() { ax::DevToolsImpl::getInstance()->resume(); } -void axmol_dev_step() +EMSCRIPTEN_KEEPALIVE void axmol_dev_step() { ax::DevToolsImpl::getInstance()->step(); } @@ -91,9 +93,6 @@ void axmol_dev_step() namespace ax { -// sharedApplication pointer -Application* Application::sm_pSharedApplication = nullptr; - static int64_t NANOSECONDSPERSECOND = 1000000000LL; static int64_t NANOSECONDSPERMICROSECOND = 1000000LL; static int64_t FPS_CONTROL_THRESHOLD = static_cast(1.0f / 1200.0f * NANOSECONDSPERSECOND); @@ -109,6 +108,7 @@ static void renderFrame(); static void updateFrame() { double now = emscripten_get_now(); // current time in ms + _axmolPerformFrameBoundaryTasks(); // Perform any pending frame boundary tasks before processing the next frame. // First frame: render immediately if (s_lastFrameTime <= 0.0) [[unlikely]] @@ -194,14 +194,14 @@ static void getCurrentLangISO2(char buf[16]) Application::Application() { - AX_ASSERT(!sm_pSharedApplication); - sm_pSharedApplication = this; + AX_ASSERT(!s_axmolApp); + s_axmolApp = this; } Application::~Application() { - AX_ASSERT(this == sm_pSharedApplication); - sm_pSharedApplication = nullptr; + AX_ASSERT(this == s_axmolApp); + s_axmolApp = nullptr; } int Application::run() @@ -264,15 +264,6 @@ bool Application::openURL(std::string_view url) return true; } -////////////////////////////////////////////////////////////////////////// -// static member function -////////////////////////////////////////////////////////////////////////// -Application* Application::getInstance() -{ - AX_ASSERT(sm_pSharedApplication); - return sm_pSharedApplication; -} - const char* Application::getCurrentLanguageCode() { static char code[3] = {0}; diff --git a/axmol/platform/wasm/Application-wasm.h b/axmol/platform/wasm/Application-wasm.h index b2416589f0ea..bc1346da93e6 100644 --- a/axmol/platform/wasm/Application-wasm.h +++ b/axmol/platform/wasm/Application-wasm.h @@ -31,14 +31,14 @@ THE SOFTWARE. #if AX_TARGET_PLATFORM == AX_PLATFORM_WASM # include "axmol/platform/Common.h" -# include "axmol/platform/ApplicationBase.h" +# include "axmol/platform/ApplicationCore.h" # include namespace ax { class Rect; -class Application : public ApplicationBase +class Application : public ApplicationCore { public: /** @@ -60,12 +60,6 @@ class Application : public ApplicationBase */ int run(); - /** - @brief Get current application instance. - @return Current application instance pointer. - */ - static Application* getInstance(); - /* override functions */ LanguageType getCurrentLanguage() override; @@ -94,8 +88,6 @@ class Application : public ApplicationBase protected: std::string _resourceRootPath; - - static Application* sm_pSharedApplication; }; } // namespace ax diff --git a/axmol/platform/wasm/Device-wasm.cpp b/axmol/platform/wasm/Device-wasm.cpp index a333ac8d89e5..73c8af459e07 100644 --- a/axmol/platform/wasm/Device-wasm.cpp +++ b/axmol/platform/wasm/Device-wasm.cpp @@ -33,10 +33,70 @@ THE SOFTWARE. # include # include +# include "GLFW/glfw3.h" + +# include +# include namespace ax { +// clang-format off + +// Global pointer just to bridge the active callback +static std::map> g_clipboard_callbacks; +static std::atomic g_next_clipboard_id{1}; + +extern "C" EMSCRIPTEN_KEEPALIVE void paste_bridge(uint64_t id, const char* text) { + auto it = g_clipboard_callbacks.find(id); + if (it != g_clipboard_callbacks.end()) { + it->second(text ? text : ""); + g_clipboard_callbacks.erase(it); + } +} + +void Device::getClipboardText(std::function callback) +{ + if (!callback) + return; + + // Save the callback pointer globally for the duration of the async event + uint64_t id = g_next_clipboard_id++; + g_clipboard_callbacks[id] = std::move(callback); + + EM_ASM({ + var handlerId = $0; + navigator.clipboard.readText() + .then(function(text) { + // Pass directly to WASM using the runtime helper + ccall('paste_bridge', null, ['uint64', 'string'], [handlerId, text]); + }) + .catch(function(err) { + ccall('paste_bridge', null, ['uint64', 'string'], [handlerId, ""]); + }); + }, id); +} + +void Device::setClipboardText(std::string_view text) +{ + EM_ASM_({ + const str = UTF8ToString($0, $1); + navigator.clipboard.writeText(str).catch(err => { + console.error("Clipboard write failed:", err); + }); + }, text.data(), text.size()); +} + +void Device::clearClipboard() +{ + EM_ASM({ + navigator.clipboard.writeText("").catch(err => { + console.error("Clipboard clear failed:", err); + }); + }); +} +// clang-format on + int Device::getDPI() { return static_cast(160 * Device::getPixelRatio()); @@ -90,8 +150,8 @@ Data Device::getTextureDataForText(std::string_view text, var strokeColor = UTF8ToString($9); var overflow = $10; - // use shared canvas - var canvas = Module.axmolSharedCanvas = Module.axmolSharedCanvas || document.createElement("canvas"); + // use axmol offscreen canvas to render text + var canvas = Module.axmol_offscreen_canvas = Module.axmol_offscreen_canvas || document.createElement("canvas"); var context = canvas.getContext('2d', { willReadFrequently: true }); // use alphabetic baseline for text rendering context.textBaseline = "alphabetic"; @@ -229,10 +289,10 @@ Data Device::getTextureDataForText(std::string_view text, textDefinition._overflow); width = EM_ASM_INT({ - return Module.axmolSharedCanvas.width; + return Module.axmol_offscreen_canvas.width; }); height = EM_ASM_INT({ - return Module.axmolSharedCanvas.height; + return Module.axmol_offscreen_canvas.height; }); // clang-format on diff --git a/axmol/platform/win32/Application-win32.cpp b/axmol/platform/win32/Application-win32.cpp index 46427b7f1b73..c49716540350 100644 --- a/axmol/platform/win32/Application-win32.cpp +++ b/axmol/platform/win32/Application-win32.cpp @@ -46,21 +46,18 @@ static void PVRFrameEnableControlWindow(bool bEnable); namespace ax { -// sharedApplication pointer -Application* Application::sm_pSharedApplication = nullptr; - Application::Application() : _instance(nullptr), _accelTable(nullptr) { _instance = GetModuleHandle(nullptr); _animationInterval.QuadPart = 0; - AX_ASSERT(!sm_pSharedApplication); - sm_pSharedApplication = this; + AX_ASSERT(!s_axmolApp); + s_axmolApp = this; } Application::~Application() { - AX_ASSERT(this == sm_pSharedApplication); - sm_pSharedApplication = nullptr; + AX_ASSERT(this == s_axmolApp); + s_axmolApp = nullptr; } int Application::run() @@ -107,6 +104,9 @@ int Application::run() while (!renderView->windowShouldClose()) { QueryPerformanceCounter(&nNow); + + director->performFrameBoundaryTasks(); + interval = nNow.QuadPart - nLast.QuadPart; if (interval >= _animationInterval.QuadPart) { @@ -146,15 +146,6 @@ void Application::setAnimationInterval(float interval) _animationInterval.QuadPart = (LONGLONG)(interval * freq.QuadPart); } -////////////////////////////////////////////////////////////////////////// -// static member function -////////////////////////////////////////////////////////////////////////// -Application* Application::getInstance() -{ - AX_ASSERT(sm_pSharedApplication); - return sm_pSharedApplication; -} - LanguageType Application::getCurrentLanguage() { LanguageType ret = LanguageType::ENGLISH; @@ -305,12 +296,6 @@ bool Application::openURL(std::string_view url) return (size_t)r > 32; } -void Application::setStartupScriptFilename(std::string_view startupScriptFile) -{ - _startupScriptFilename = startupScriptFile; - std::replace(_startupScriptFilename.begin(), _startupScriptFilename.end(), '\\', '/'); -} - } // namespace ax ////////////////////////////////////////////////////////////////////////// diff --git a/axmol/platform/win32/Application-win32.h b/axmol/platform/win32/Application-win32.h index bcca66642daa..7c5e3ea1de2a 100644 --- a/axmol/platform/win32/Application-win32.h +++ b/axmol/platform/win32/Application-win32.h @@ -28,13 +28,13 @@ THE SOFTWARE. #include "axmol/platform/StdC.h" #include "axmol/platform/Common.h" -#include "axmol/platform/ApplicationBase.h" +#include "axmol/platform/ApplicationCore.h" #include namespace ax { -class AX_DLL Application : public ApplicationBase +class AX_DLL Application : public ApplicationCore { public: /** @@ -50,12 +50,6 @@ class AX_DLL Application : public ApplicationBase */ int run(); - /** - @brief Get current application instance. - @return Current application instance pointer. - */ - static Application* getInstance(); - /* override functions */ void setAnimationInterval(float interval) override; @@ -80,18 +74,11 @@ class AX_DLL Application : public ApplicationBase */ virtual bool openURL(std::string_view url); - void setStartupScriptFilename(std::string_view startupScriptFile); - - std::string_view getStartupScriptFilename() { return _startupScriptFilename; } - protected: HINSTANCE _instance; HACCEL _accelTable; LARGE_INTEGER _animationInterval; std::string _resourceRootPath; - std::string _startupScriptFilename; - - static Application* sm_pSharedApplication; }; } // namespace ax diff --git a/axmol/platform/win32/Device-win32.cpp b/axmol/platform/win32/Device-win32.cpp index 0b7c06cb6801..903d7d32881b 100644 --- a/axmol/platform/win32/Device-win32.cpp +++ b/axmol/platform/win32/Device-win32.cpp @@ -31,6 +31,101 @@ THE SOFTWARE. namespace ax { +namespace +{ +struct ScopedClipboard +{ + ScopedClipboard() : _ok(::OpenClipboard(nullptr)) {} + ~ScopedClipboard() + { + if (_ok) + ::CloseClipboard(); + } + ScopedClipboard(const ScopedClipboard&) = delete; + ScopedClipboard& operator=(const ScopedClipboard&) = delete; + + explicit operator bool() const { return !!_ok; } + +private: + BOOL _ok; +}; +} // namespace + +void Device::getClipboardText(std::function callback) +{ + if (!callback) + return; + ScopedClipboard clipboard; + if (!clipboard) + { + callback(std::string_view{}); + return; + } + + std::string result; + HANDLE hData = GetClipboardData(CF_UNICODETEXT); + if (hData) + { + LPCWSTR pwsz = static_cast(GlobalLock(hData)); + if (pwsz) + { + result = ntcvt::from_chars(pwsz, CP_UTF8); + GlobalUnlock(hData); + } + } + + callback(result); +} + +void Device::setClipboardText(std::string_view text) +{ + // Convert to wide (UTF-16) + ScopedClipboard clipboard; + if (!clipboard) + return; + + // Empty clipboard first + if (!EmptyClipboard() || text.empty()) + { + return; + } + + // Allocate global memory for the wide string including null terminator + int cch = ::MultiByteToWideChar(CP_UTF8, 0, text.data(), text.size(), nullptr, 0); + if (cch <= 0) + { + return; + } + size_t bytes = (cch + 1) * sizeof(wchar_t); + HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, bytes); + if (!hGlob) + { + return; + } + + void* pGlob = GlobalLock(hGlob); + if (!pGlob) + { + GlobalFree(hGlob); + return; + } + + ::MultiByteToWideChar(CP_UTF8, 0, text.data(), text.size(), static_cast(pGlob), cch); + static_cast(pGlob)[cch] = L'\0'; + GlobalUnlock(hGlob); + + // Set clipboard data as CF_UNICODETEXT + if (!SetClipboardData(CF_UNICODETEXT, hGlob)) + GlobalFree(hGlob); +} + +void Device::clearClipboard() +{ + ScopedClipboard clipboard; + if (clipboard) + EmptyClipboard(); +} + int Device::getDPI() { static int dpi = -1; diff --git a/axmol/platform/winrt/Application-winrt.cpp b/axmol/platform/winrt/Application-winrt.cpp index 61fd36a6cc30..1a889e8c2373 100644 --- a/axmol/platform/winrt/Application-winrt.cpp +++ b/axmol/platform/winrt/Application-winrt.cpp @@ -25,24 +25,36 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "axmol/platform/PlatformConfig.h" -#include "axmol/platform/winrt/RenderViewImpl-winrt.h" +#include "axmol/platform/winrt/RenderView-winrt.h" #include "axmol/base/Director.h" #include #include "axmol/platform/FileUtils.h" +#include "axmol/platform/Device.h" #include "axmol/platform/winrt/WinRTUtils.h" #include "axmol/platform/Application.h" +#include "axmol/rhi/DriverContext.h" #include "pugixml/pugixml.hpp" #include "axmol/tlx/format.hpp" +#include "yasio/wtimer_hres.hpp" #include #include #include +#include +#include +#include +#include #include +#include using namespace Windows::UI::Core; using namespace Windows::Foundation; +using namespace Windows::Graphics::Display; +using namespace Windows::System::Threading; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; /** @brief This function change the PVRFrame show/hide setting in register. @@ -52,9 +64,6 @@ using namespace Windows::Foundation; namespace ax { -// sharedApplication pointer -Application* Application::sm_pSharedApplication = nullptr; - //////////////////////////////////////////////////////////////////////////////// // implement Application //////////////////////////////////////////////////////////////////////////////// @@ -62,17 +71,20 @@ Application* Application::sm_pSharedApplication = nullptr; // sharedApplication pointer Application* s_pSharedApplication = nullptr; -Application::Application() : m_openURLDelegate(nullptr) +Application::Application() : _openURLDelegate(nullptr) { - m_nAnimationInterval.QuadPart = 0; - AX_ASSERT(!sm_pSharedApplication); - sm_pSharedApplication = this; + _animationInterval.QuadPart = 0; + _freq.QuadPart = 0; + _last.QuadPart = 0; + _now.QuadPart = 0; + AX_ASSERT(!s_axmolApp); + s_axmolApp = this; } Application::~Application() { - AX_ASSERT(this == sm_pSharedApplication); - sm_pSharedApplication = nullptr; + AX_ASSERT(this == s_axmolApp); + s_axmolApp = nullptr; } int Application::run() @@ -91,55 +103,303 @@ int Application::run() return 0; } - QueryPerformanceCounter(&m_nLast); + QueryPerformanceCounter(&_last); + QueryPerformanceFrequency(&_freq); + + yasio::wtimer_hres timerResolution; + auto director = ax::Director::getInstance(); + auto dispatcher = _renderView->getDispatcher().get(); + + LONGLONG interval = 0LL; + LONG waitMS = 0L; + bool paused = false; + + auto drainBoundaryTasks = [this, director]() { + { + std::unique_lock lock(_loopMutex); + _boundaryTaskPending = false; + } + director->performFrameBoundaryTasks(); + }; + + while (isRenderLoopRunning()) + { + drainBoundaryTasks(); + + bool shouldSuspend = false; + { + std::unique_lock lock(_loopMutex); + shouldSuspend = _suspended; + } + + if (shouldSuspend) + { + if (!paused) + { + onPause(); + paused = true; + } + + while (isRenderLoopRunning()) + { + bool shouldResume = false; + bool shouldDrainBoundaryTasks = false; + { + std::unique_lock lock(_loopMutex); + _loopCondition.wait( + lock, [this]() { return !isRenderLoopRunning() || !_suspended || _boundaryTaskPending; }); + + if (!isRenderLoopRunning()) + break; + + shouldResume = !_suspended; + shouldDrainBoundaryTasks = _boundaryTaskPending; + if (shouldDrainBoundaryTasks) + _boundaryTaskPending = false; + } + + if (shouldDrainBoundaryTasks) + director->performFrameBoundaryTasks(); + + if (shouldResume) + break; + } + + if (!isRenderLoopRunning()) + break; + + onResume(); + paused = false; + QueryPerformanceCounter(&_last); + continue; + } + + QueryPerformanceCounter(&_now); + + interval = _now.QuadPart - _last.QuadPart; + if (interval >= _animationInterval.QuadPart) + { + _last.QuadPart = _now.QuadPart; + + director->renderFrame(); + _renderView->syncCursorVisibility(); + + if (_appShouldExit.load(std::memory_order_acquire)) + { + _requestedTerminate = true; + dispatcher.RunAsync(CoreDispatcherPriority::High, [this]() { + if (_renderView) + _renderView->terminateApp(); + }); + break; + } + + if (!_renderView->swapSurfaceBuffers()) + { + onPause(); + paused = true; + { + std::unique_lock lock(_loopMutex); + _deviceLost = true; + } + + dispatcher.RunAsync(CoreDispatcherPriority::High, [this]() { + if (_renderView) + _renderView->recoverFromLostDevice(); + + std::unique_lock lock(_loopMutex); + _deviceLost = false; + _loopCondition.notify_one(); + }); + + while (isRenderLoopRunning()) + { + bool shouldDrainBoundaryTasks = false; + bool shouldRecover = false; + { + std::unique_lock lock(_loopMutex); + _loopCondition.wait( + lock, [this]() { return !isRenderLoopRunning() || !_deviceLost || _boundaryTaskPending; }); + + if (!isRenderLoopRunning()) + break; + + shouldRecover = !_deviceLost; + shouldDrainBoundaryTasks = _boundaryTaskPending; + if (shouldDrainBoundaryTasks) + _boundaryTaskPending = false; + } + + if (shouldDrainBoundaryTasks) + director->performFrameBoundaryTasks(); + + if (shouldRecover) + break; + } + + if (!isRenderLoopRunning()) + break; + + _renderView->makeSurfaceCurrent(); + onDeviceLost(); + paused = false; + QueryPerformanceCounter(&_last); + } + } + else + { + // The precision of timer on Windows is set to highest (1ms) by 'timeBeginPeriod' from above code, + // but it's still not precise enough. For example, if the precision of timer is 1ms, + // Sleep(3) may make a sleep of 2ms or 4ms. Therefore, we subtract 1ms here to make Sleep time shorter. + // If 'waitMS' is equal or less than 1ms, don't sleep and run into next loop to + // boost CPU to next frame accurately. + waitMS = static_cast((_animationInterval.QuadPart - interval) * 1000LL / _freq.QuadPart - 1L); + if (waitMS > 1L) + { + std::unique_lock lock(_loopMutex); + _loopCondition.wait_for(lock, std::chrono::milliseconds(waitMS), [this]() { + return !isRenderLoopRunning() || _suspended || _boundaryTaskPending; + }); + } + } + } - RenderViewImpl::sharedRenderView()->Run(); return 0; } -bool Application::frameStep(const std::function& onFrame) +void Application::boot(SwapChainPanel const& panel) { - QueryPerformanceCounter(&m_nNow); - const auto interval = m_nNow.QuadPart - m_nLast.QuadPart; - if (interval > m_nAnimationInterval.QuadPart) + if (_renderLoopWorker != nullptr && _renderLoopWorker.Status() == AsyncStatus::Started) + return; + + _panel = panel; + resume(); + + _dispatcher = Window::Current().CoreWindow().Dispatcher(); + auto display = DisplayInformation::GetForCurrentView(); + Device::setDPI(display.LogicalDpi()); + _orientation = display.CurrentOrientation(); + auto panelWidth = panel.ActualWidth(); + auto panelHeight = panel.ActualHeight(); + + auto axmolWinRTMain = [this, panelWidth, panelHeight](Windows::Foundation::IAsyncAction const& /*action*/) { + auto dispatcher = _dispatcher.get(); + auto axmolApp = ax::Application::getInstance(); + + AX_ASSERT(!_renderView); + auto director = ax::Director::getInstance(); + AX_ASSERT(!director->getRenderView()); + + _renderLoopRunning = true; + _appShouldExit.store(false, std::memory_order_release); + _requestedTerminate = false; + + _renderView = RenderView::createWithRect( + "axmol3", Rect(0, 0, static_cast(panelWidth), static_cast(panelHeight))); + + if (rhi::DriverContext::isOpenGL()) + { + auto surfaceReady = std::make_shared>(); + auto surfaceFuture = surfaceReady->get_future(); + dispatcher.RunAsync(CoreDispatcherPriority::High, [this, surfaceReady]() { + try + { + _renderView->createRenderSurface(); + surfaceReady->set_value(); + } + catch (...) + { + surfaceReady->set_exception(std::current_exception()); + } + }); + surfaceFuture.get(); + + _renderView->makeSurfaceCurrent(); + rhi::DriverContext::activateCurrentDriver(); + } + + // must after egl surface created when not use d3d RHI + director->setRenderView(_renderView); + + _renderView->registerEventHandlers(); + + axmolApp->run(); + + if (_renderView && !_requestedTerminate) + { + auto surfaceDestroyed = std::make_shared>(); + auto destroyFuture = surfaceDestroyed->get_future(); + dispatcher.RunAsync(CoreDispatcherPriority::High, [this, surfaceDestroyed]() { + if (_renderView) + _renderView->destroyRenderSurface(); + surfaceDestroyed->set_value(); + }); + destroyFuture.get(); + } + + _renderLoopRunning = false; + }; + + _renderLoopWorker = ThreadPool::RunAsync(axmolWinRTMain, WorkItemPriority::High, WorkItemOptions::TimeSliced); +} + +void Application::shutdown() +{ + if (_renderLoopWorker) { - m_nLast.QuadPart = m_nNow.QuadPart; - return onFrame(); + _renderLoopWorker.Cancel(); + { + std::unique_lock lock(_loopMutex); + _suspended = false; + _deviceLost = false; + _boundaryTaskPending = true; + _renderLoopRunning = false; + _loopCondition.notify_all(); + } + _renderLoopWorker = nullptr; } - else +} + +void Application::suspend() +{ + std::unique_lock lock(_loopMutex); + _suspended = true; + _loopCondition.notify_one(); +} + +void Application::resume() +{ + std::unique_lock lock(_loopMutex); + if (_suspended) { - // The precision of timer on Windows is set to highest (1ms) by 'timeBeginPeriod' from above code, - // but it's still not precise enough. For example, if the precision of timer is 1ms, - // Sleep(3) may make a sleep of 2ms or 4ms. Therefore, we subtract 1ms here to make Sleep time shorter. - // If 'waitMS' is equal or less than 1ms, don't sleep and run into next loop to - // boost CPU to next frame accurately. - const auto waitMS = - static_cast((m_nAnimationInterval.QuadPart - interval) * 1000LL / m_nFreq.QuadPart - 1L); - if (waitMS > 1L) - Sleep(waitMS); - - return true; + _suspended = false; + _loopCondition.notify_one(); } } -void Application::setAnimationInterval(float interval) +void Application::requestQuit() +{ + std::unique_lock lock(_loopMutex); + _appShouldExit.store(true, std::memory_order_release); + _loopCondition.notify_one(); +} + +bool Application::isRenderLoopRunning() const { - QueryPerformanceFrequency(&m_nFreq); - m_nAnimationInterval.QuadPart = (LONGLONG)(interval * m_nFreq.QuadPart); + return _renderLoopRunning.load(std::memory_order_acquire); } -// void Application::setAnimationInterval(float interval, SetIntervalReason reason) -//{ -// setAnimationInterval(interval); -// } +void Application::postBoundaryTaskSignal() +{ + std::unique_lock lock(_loopMutex); + _boundaryTaskPending = true; + _loopCondition.notify_one(); +} -////////////////////////////////////////////////////////////////////////// -// static member function -////////////////////////////////////////////////////////////////////////// -Application* Application::getInstance() +void Application::setAnimationInterval(float interval) { - AX_ASSERT(sm_pSharedApplication); - return sm_pSharedApplication; + QueryPerformanceFrequency(&_freq); + _animationInterval.QuadPart = (LONGLONG)(interval * _freq.QuadPart); } const char* Application::getCurrentLanguageCode() @@ -173,7 +433,7 @@ std::string Application::getVersion() bool Application::openURL(std::string_view url) { - auto dispatcher = ax::RenderViewImpl::sharedRenderView()->getDispatcher(); + auto dispatcher = ax::RenderView::sharedRenderView()->getDispatcher(); dispatcher.get().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, DispatchedHandler([url]() { auto uri = Windows::Foundation::Uri(PlatformStringFromString(url)); Windows::System::Launcher::LaunchUriAsync(uri); @@ -181,10 +441,37 @@ bool Application::openURL(std::string_view url) return true; } -void Application::setStartupScriptFilename(const std::string& startupScriptFile) +void Application::onPause() +{ + applicationDidEnterBackground(); + ax::CustomEvent backgroundEvent(EVENT_COME_TO_BACKGROUND); + ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&backgroundEvent, true); +} + +void Application::onResume() +{ + auto director = ax::Director::getInstance(); + applicationWillEnterForeground(); + ax::CustomEvent foregroundEvent(EVENT_COME_TO_FOREGROUND); + ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&foregroundEvent, true); +} + +void Application::onDeviceLost() { - m_startupScriptFilename = startupScriptFile; - std::replace(m_startupScriptFilename.begin(), m_startupScriptFilename.end(), '\\', '/'); + onPause(); + + auto director = ax::Director::getInstance(); + + axdrv->resetState(); + ax::Director::getInstance()->resetMatrixStack(); + ax::CustomEvent recreatedEvent(EVENT_RENDERER_RECREATED); + director->getEventDispatcher()->dispatchEvent(&recreatedEvent, true); + director->setRenderDefaults(); +#if AX_ENABLE_CONTEXT_LOSS_RECOVERY + ax::VolatileTextureMgr::reloadAllTextures(); +#endif + + onResume(); } } // namespace ax diff --git a/axmol/platform/winrt/Application-winrt.h b/axmol/platform/winrt/Application-winrt.h index a685b665dd40..ffe295ea460b 100644 --- a/axmol/platform/winrt/Application-winrt.h +++ b/axmol/platform/winrt/Application-winrt.h @@ -31,15 +31,25 @@ THE SOFTWARE. # include "axmol/platform/StdC.h" # include "axmol/platform/Common.h" -# include "axmol/platform/ApplicationBase.h" -# include "axmol/platform/winrt/InputEvent.h" +# include "axmol/platform/ApplicationCore.h" # include # include +# include +# include +# include +# include + +# include +# include +# include +# include namespace ax { -class AX_DLL Application : public ApplicationBase +class RenderView; + +class AX_DLL Application : public ApplicationCore { public: Application(); @@ -50,20 +60,14 @@ class AX_DLL Application : public ApplicationBase */ int run(); - /** - * @brief frame step with FPS control - */ - bool frameStep(const std::function& onFrame); - - /** - @brief Get current application instance. - @return Current application instance pointer. - */ - static Application* getInstance(); + void boot(winrt::Windows::UI::Xaml::Controls::SwapChainPanel const& panel); + void shutdown(); + void suspend(); + void resume(); + void requestQuit(); /* override functions */ void setAnimationInterval(float interval) override; - // virtual void setAnimationInterval(float interval, SetIntervalReason reason) override; LanguageType getCurrentLanguage() override; const char* getCurrentLanguageCode() override; @@ -85,28 +89,45 @@ class AX_DLL Application : public ApplicationBase */ virtual bool openURL(std::string_view url); - /** - @brief Set the callback responsible for opening a URL. - @param del The delegate that will handle opening a URL. We can't pass back a Platform::String due to name clash. - */ - void SetXamlOpenURLDelegate(const std::function& del) { m_openURLDelegate = del; } +protected: + friend class RenderView; - void setStartupScriptFilename(const std::string& startupScriptFile); + void postBoundaryTaskSignal() override; - const std::string& getStartupScriptFilename(void) { return m_startupScriptFilename; } + void onPause(); + void onResume(); + void onDeviceLost(); -protected: - LARGE_INTEGER m_nAnimationInterval; - LARGE_INTEGER m_nFreq; - LARGE_INTEGER m_nLast; - LARGE_INTEGER m_nNow; + winrt::agile_ref getPanel() const { return _panel; } + winrt::agile_ref getDispatcher() const { return _dispatcher; } + winrt::Windows::Graphics::Display::DisplayOrientations getOrientation() const { return _orientation; } + + LARGE_INTEGER _animationInterval; + LARGE_INTEGER _freq; + LARGE_INTEGER _last; + LARGE_INTEGER _now; + + std::string _resourceRootPath; + + std::function _openURLDelegate; - std::string m_resourceRootPath; - std::string m_startupScriptFilename; + winrt::agile_ref _panel; + winrt::agile_ref _dispatcher; + winrt::Windows::Graphics::Display::DisplayOrientations _orientation{ + winrt::Windows::Graphics::Display::DisplayOrientations::Landscape}; + winrt::Windows::Foundation::IAsyncAction _renderLoopWorker{nullptr}; + RenderView* _renderView{nullptr}; - std::function m_openURLDelegate; + std::mutex _loopMutex; + std::condition_variable _loopCondition; + bool _suspended{false}; + bool _deviceLost{false}; + std::atomic_bool _appShouldExit{false}; + bool _requestedTerminate{false}; + bool _boundaryTaskPending{false}; + std::atomic_bool _renderLoopRunning{false}; - static Application* sm_pSharedApplication; + bool isRenderLoopRunning() const; }; } // namespace ax diff --git a/axmol/platform/winrt/Common-winrt.cpp b/axmol/platform/winrt/Common-winrt.cpp index 397e3c1a45ba..7db3e4c771c9 100644 --- a/axmol/platform/winrt/Common-winrt.cpp +++ b/axmol/platform/winrt/Common-winrt.cpp @@ -26,7 +26,7 @@ THE SOFTWARE. ****************************************************************************/ #include "axmol/platform/Common.h" #include "axmol/platform/StdC.h" -#include "axmol/platform/winrt/RenderViewImpl-winrt.h" +#include "axmol/platform/winrt/RenderView-winrt.h" #include "axmol/platform/winrt/WinRTUtils.h" #if defined(VLD_DEBUG_MEMORY) @@ -41,7 +41,7 @@ AlertResult showAlert(std::string_view msg, std::string_view title, AlertStyle s // Create the message dialog and set its content auto hmsg = PlatformStringFromString(msg); auto htitle = PlatformStringFromString(title); - return RenderViewImpl::sharedRenderView()->ShowAlertDialog(hmsg, htitle, style); + return RenderView::sharedRenderView()->showAlertDialog(hmsg, htitle, style); } } // namespace ax diff --git a/axmol/platform/winrt/Device-winrt.cpp b/axmol/platform/winrt/Device-winrt.cpp index 23198cb74aa9..40851ffda4f6 100644 --- a/axmol/platform/winrt/Device-winrt.cpp +++ b/axmol/platform/winrt/Device-winrt.cpp @@ -33,18 +33,25 @@ THE SOFTWARE. # include # include # include +# include # include "ntcvt/ntcvt.hpp" # include "axmol/platform/StdC.h" # include "axmol/platform/Device.h" # include "axmol/platform/FileUtils.h" # include "axmol/platform/winrt/WinRTUtils.h" -# include "axmol/platform/winrt/RenderViewImpl-winrt.h" +# include "axmol/platform/winrt/RenderView-winrt.h" # include +# include +# include +# include using namespace winrt; using namespace Windows::Graphics::Display; using namespace Windows::Devices::Sensors; using namespace Windows::Foundation; +using namespace Windows::UI::Core; +using namespace Windows::ApplicationModel::Core; +using namespace Windows::ApplicationModel::DataTransfer; # if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) using namespace Windows::Phone::Devices::Notification; @@ -53,9 +60,66 @@ using namespace Windows::Phone::Devices::Notification; namespace ax { +namespace +{ +std::atomic s_dpi{96.0f}; +} + +void Device::getClipboardText(std::function callback) +{ + if (!callback) + return; + + try + { + DataPackageView view = Clipboard::GetContent(); + if (!view) + return callback(std::string_view{}); + + hstring hs = view.GetTextAsync().get(); + callback(ntcvt::from_chars(std::wstring_view{hs.c_str(), hs.size()}, CP_UTF8)); + } + catch (hresult_error const&) + { + return callback(std::string_view{}); + } +} + +void Device::setClipboardText(std::string_view text) +{ + DataPackage dp; + dp.RequestedOperation(DataPackageOperation::Copy); + dp.SetText(winrt::to_hstring(text)); + + auto dispatcher = CoreApplication::MainView().CoreWindow().Dispatcher(); + dispatcher.RunAsync(CoreDispatcherPriority::Normal, [dp]() mutable { + try + { + Clipboard::SetContent(dp); + } + catch (hresult_error const& e) + {} + }); +} + +void Device::clearClipboard() +{ + try + { + Clipboard::Clear(); + } + catch (hresult_error const&) + {} +} + int Device::getDPI() { - return static_cast(ax::RenderViewImpl::sharedRenderView()->GetDPI()); + return static_cast(s_dpi.load(std::memory_order_acquire)); +} + +void Device::setDPI(float dpi) +{ + s_dpi.store(dpi, std::memory_order_release); } float Device::getPixelRatio() @@ -107,7 +171,7 @@ void Device::setAccelerometerEnabled(bool isEnabled) acc.z = reading.AccelerationZ(); acc.timestamp = 0; - auto orientation = RenderViewImpl::sharedRenderView()->getDeviceOrientation(); + auto orientation = RenderView::sharedRenderView()->getDeviceOrientation(); if (isWindowsPhone()) { @@ -171,8 +235,15 @@ void Device::setAccelerometerEnabled(bool isEnabled) } } - std::shared_ptr event(new AccelerometerEvent(acc)); - ax::RenderViewImpl::sharedRenderView()->QueueEvent(event); + // std::shared_ptr event(new AccelerometerEvent(acc)); + // ax::RenderView::sharedRenderView()->QueueEvent(event); + + Director::getInstance()->postTask([acc]() { + // std::shared_ptr event(new AccelerometerEvent(acc)); + // ax::RenderView::sharedRenderView()->QueueEvent(event); + ax::AccelerationEvent accEvent(acc); + InputSystem::getInstance()->dispatchEvent(&accEvent); + }); }); } } diff --git a/axmol/platform/winrt/xaml/EGLSurfaceProvider.cpp b/axmol/platform/winrt/EGLSurfaceProvider.cpp similarity index 81% rename from axmol/platform/winrt/xaml/EGLSurfaceProvider.cpp rename to axmol/platform/winrt/EGLSurfaceProvider.cpp index be8d360fbf85..c0b086fd531f 100644 --- a/axmol/platform/winrt/xaml/EGLSurfaceProvider.cpp +++ b/axmol/platform/winrt/EGLSurfaceProvider.cpp @@ -18,7 +18,7 @@ * specific language governing permissions and limitations under the License. */ -#include "axmol/platform/winrt/xaml/EGLSurfaceProvider.h" +#include "axmol/platform/winrt/EGLSurfaceProvider.h" #include @@ -26,17 +26,20 @@ using namespace Windows::UI::Xaml::Controls; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; -EGLSurfaceProvider::EGLSurfaceProvider() : mEglDisplay(EGL_NO_DISPLAY), mEglContext(EGL_NO_CONTEXT), mEglConfig(nullptr) +namespace ax { - Initialize(); + +EGLSurfaceProvider::EGLSurfaceProvider() : _eglDisplay(EGL_NO_DISPLAY), _eglContext(EGL_NO_CONTEXT), _eglConfig(nullptr) +{ + initialize(); } EGLSurfaceProvider::~EGLSurfaceProvider() { - Cleanup(); + cleanup(); } -void EGLSurfaceProvider::Initialize() +void EGLSurfaceProvider::initialize() { const EGLint configAttributes[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8, EGL_NONE}; @@ -121,33 +124,33 @@ void EGLSurfaceProvider::Initialize() // // This tries to initialize EGL to D3D11 Feature Level 10_0+. See above comment for details. - mEglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, defaultDisplayAttributes); - if (mEglDisplay == EGL_NO_DISPLAY) + _eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, defaultDisplayAttributes); + if (_eglDisplay == EGL_NO_DISPLAY) { throw winrt::hresult_error(E_FAIL, L"Failed to get EGL display"); } - if (eglInitialize(mEglDisplay, NULL, NULL) == EGL_FALSE) + if (eglInitialize(_eglDisplay, NULL, NULL) == EGL_FALSE) { // This tries to initialize EGL to D3D11 Feature Level 9_3, if 10_0+ is unavailable (e.g. on some mobile // devices). - mEglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, fl9_3DisplayAttributes); - if (mEglDisplay == EGL_NO_DISPLAY) + _eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, fl9_3DisplayAttributes); + if (_eglDisplay == EGL_NO_DISPLAY) { throw winrt::hresult_error(E_FAIL, L"Failed to get EGL display"); } - if (eglInitialize(mEglDisplay, NULL, NULL) == EGL_FALSE) + if (eglInitialize(_eglDisplay, NULL, NULL) == EGL_FALSE) { // This initializes EGL to D3D11 Feature Level 11_0 on WARP, if 9_3+ is unavailable on the default GPU. - mEglDisplay = + _eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, warpDisplayAttributes); - if (mEglDisplay == EGL_NO_DISPLAY) + if (_eglDisplay == EGL_NO_DISPLAY) { throw winrt::hresult_error(E_FAIL, L"Failed to get EGL display"); } - if (eglInitialize(mEglDisplay, NULL, NULL) == EGL_FALSE) + if (eglInitialize(_eglDisplay, NULL, NULL) == EGL_FALSE) { // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. throw winrt::hresult_error(E_FAIL, L"Failed to initialize EGL"); @@ -156,41 +159,41 @@ void EGLSurfaceProvider::Initialize() } EGLint numConfigs = 0; - if ((eglChooseConfig(mEglDisplay, configAttributes, &mEglConfig, 1, &numConfigs) == EGL_FALSE) || (numConfigs == 0)) + if ((eglChooseConfig(_eglDisplay, configAttributes, &_eglConfig, 1, &numConfigs) == EGL_FALSE) || (numConfigs == 0)) { throw winrt::hresult_error(E_FAIL, L"Failed to choose first EGLConfig"); } - mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, contextAttributes); - if (mEglContext == EGL_NO_CONTEXT) + _eglContext = eglCreateContext(_eglDisplay, _eglConfig, EGL_NO_CONTEXT, contextAttributes); + if (_eglContext == EGL_NO_CONTEXT) { throw winrt::hresult_error(E_FAIL, L"Failed to create EGL context"); } } -void EGLSurfaceProvider::Cleanup() +void EGLSurfaceProvider::cleanup() { - if (mEglDisplay != EGL_NO_DISPLAY && mEglContext != EGL_NO_CONTEXT) + if (_eglDisplay != EGL_NO_DISPLAY && _eglContext != EGL_NO_CONTEXT) { - eglDestroyContext(mEglDisplay, mEglContext); - mEglContext = EGL_NO_CONTEXT; + eglDestroyContext(_eglDisplay, _eglContext); + _eglContext = EGL_NO_CONTEXT; } - if (mEglDisplay != EGL_NO_DISPLAY) + if (_eglDisplay != EGL_NO_DISPLAY) { - eglTerminate(mEglDisplay); - mEglDisplay = EGL_NO_DISPLAY; + eglTerminate(_eglDisplay); + _eglDisplay = EGL_NO_DISPLAY; } } -void EGLSurfaceProvider::Reset() +void EGLSurfaceProvider::reset() { - Cleanup(); - Initialize(); + cleanup(); + initialize(); } -EGLSurface EGLSurfaceProvider::CreateSurface(SwapChainPanel const& panel, - const Size* renderSurfaceSize, +EGLSurface EGLSurfaceProvider::createSurface(SwapChainPanel const& panel, + const Windows::Foundation::Size* renderSurfaceSize, const float* resolutionScale) { if (!panel) @@ -232,7 +235,7 @@ EGLSurface EGLSurfaceProvider::CreateSurface(SwapChainPanel const& panel, } auto native_abi = winrt::get_abi(surfaceCreationProperties); - surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, (EGLNativeWindowType)native_abi, surfaceAttributes); + surface = eglCreateWindowSurface(_eglDisplay, _eglConfig, (EGLNativeWindowType)native_abi, surfaceAttributes); if (surface == EGL_NO_SURFACE) { throw winrt::hresult_error(E_FAIL, L"Failed to create EGL surface"); @@ -241,29 +244,31 @@ EGLSurface EGLSurfaceProvider::CreateSurface(SwapChainPanel const& panel, return surface; } -void EGLSurfaceProvider::GetSurfaceDimensions(const EGLSurface surface, EGLint* width, EGLint* height) +void EGLSurfaceProvider::getSurfaceDimensions(const EGLSurface surface, EGLint* width, EGLint* height) { - eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width); - eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height); + eglQuerySurface(_eglDisplay, surface, EGL_WIDTH, width); + eglQuerySurface(_eglDisplay, surface, EGL_HEIGHT, height); } -void EGLSurfaceProvider::DestroySurface(const EGLSurface surface) +void EGLSurfaceProvider::destroySurface(const EGLSurface surface) { - if (mEglDisplay != EGL_NO_DISPLAY && surface != EGL_NO_SURFACE) + if (_eglDisplay != EGL_NO_DISPLAY && surface != EGL_NO_SURFACE) { - eglDestroySurface(mEglDisplay, surface); + eglDestroySurface(_eglDisplay, surface); } } -void EGLSurfaceProvider::MakeCurrent(const EGLSurface surface) +void EGLSurfaceProvider::makeCurrent(const EGLSurface surface) { - if (eglMakeCurrent(mEglDisplay, surface, surface, mEglContext) == EGL_FALSE) + if (eglMakeCurrent(_eglDisplay, surface, surface, _eglContext) == EGL_FALSE) { throw winrt::hresult_error(E_FAIL, L"Failed to make EGLSurface current"); } } -EGLBoolean EGLSurfaceProvider::SwapBuffers(const EGLSurface surface) +EGLBoolean EGLSurfaceProvider::swapBuffers(const EGLSurface surface) { - return (eglSwapBuffers(mEglDisplay, surface)); + return (eglSwapBuffers(_eglDisplay, surface)); } + +} // namespace ax diff --git a/axmol/platform/winrt/xaml/EGLSurfaceProvider.h b/axmol/platform/winrt/EGLSurfaceProvider.h similarity index 75% rename from axmol/platform/winrt/xaml/EGLSurfaceProvider.h rename to axmol/platform/winrt/EGLSurfaceProvider.h index fa504348b783..ddc6add2c7b0 100644 --- a/axmol/platform/winrt/xaml/EGLSurfaceProvider.h +++ b/axmol/platform/winrt/EGLSurfaceProvider.h @@ -34,27 +34,31 @@ using namespace winrt; +namespace ax +{ + class EGLSurfaceProvider { public: EGLSurfaceProvider(); ~EGLSurfaceProvider(); - EGLSurface CreateSurface(Windows::UI::Xaml::Controls::SwapChainPanel const& panel, + EGLSurface createSurface(Windows::UI::Xaml::Controls::SwapChainPanel const& panel, const Windows::Foundation::Size* renderSurfaceSize, const float* renderResolutionScale); - void GetSurfaceDimensions(const EGLSurface surface, EGLint* width, EGLint* height); - void DestroySurface(const EGLSurface surface); - void MakeCurrent(const EGLSurface surface); - EGLBoolean SwapBuffers(const EGLSurface surface); - void Reset(); - void Cleanup(); + void getSurfaceDimensions(const EGLSurface surface, EGLint* width, EGLint* height); + void destroySurface(const EGLSurface surface); + void makeCurrent(const EGLSurface surface); + EGLBoolean swapBuffers(const EGLSurface surface); + void reset(); + void cleanup(); private: - void Initialize(); + void initialize(); private: - EGLDisplay mEglDisplay; - EGLContext mEglContext; - EGLConfig mEglConfig; + EGLDisplay _eglDisplay; + EGLContext _eglContext; + EGLConfig _eglConfig; }; +} // namespace ax diff --git a/axmol/platform/winrt/InputEvent.cpp b/axmol/platform/winrt/InputEvent.cpp deleted file mode 100644 index ea9dd1496b42..000000000000 --- a/axmol/platform/winrt/InputEvent.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/**************************************************************************** -Copyright (c) 2013 cocos2d-x.org -Copyright (c) Microsoft Open Technologies, Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#include "axmol/platform/winrt/InputEvent.h" -#include "axmol/platform/winrt/WinRTUtils.h" -#include "axmol/platform/winrt/RenderViewImpl-winrt.h" -#include "axmol/base/EventAcceleration.h" -#include "axmol/base/Director.h" -#include "axmol/base/EventDispatcher.h" -#include "axmol/base/IMEDispatcher.h" - -namespace ax -{ - -AccelerometerEvent::AccelerometerEvent(const Acceleration& event) : m_event(event) {} - -void AccelerometerEvent::execute() -{ - auto dispatcher = Director::getInstance()->getEventDispatcher(); - ax::EventAcceleration accEvent(m_event); - dispatcher->dispatchEvent(&accEvent); -} - -PointerEvent::PointerEvent(PointerEventType type, const Windows::UI::Core::PointerEventArgs& args) - : m_type(type), m_args(args) -{} - -void PointerEvent::execute() -{ - switch (m_type) - { - case PointerEventType::PointerPressed: - RenderViewImpl::sharedRenderView()->OnPointerPressed(m_args); - break; - case PointerEventType::PointerMoved: - RenderViewImpl::sharedRenderView()->OnPointerMoved(m_args); - break; - case PointerEventType::PointerReleased: - RenderViewImpl::sharedRenderView()->OnPointerReleased(m_args); - break; - case ax::MousePressed: - RenderViewImpl::sharedRenderView()->OnMousePressed(m_args); - break; - case ax::MouseMoved: - RenderViewImpl::sharedRenderView()->OnMouseMoved(m_args); - break; - case ax::MouseReleased: - RenderViewImpl::sharedRenderView()->OnMouseReleased(m_args); - break; - case ax::MouseWheelChanged: - RenderViewImpl::sharedRenderView()->OnMouseWheelChanged(m_args); - break; - } -} - -KeyboardEvent::KeyboardEvent(AxmolKeyEvent type) : m_type(type), m_text() {} - -KeyboardEvent::KeyboardEvent(AxmolKeyEvent type, const winrt::hstring& text) : m_type(type), m_text(text) {} - -void KeyboardEvent::execute() -{ - switch (m_type) - { - case AxmolKeyEvent::Text: - { - std::string utf8String = PlatformStringToString(m_text); - IMEDispatcher::sharedDispatcher()->dispatchInsertText(utf8String.c_str(), utf8String.size()); - break; - } - - default: - switch (m_type) - { - case AxmolKeyEvent::Escape: - // Director::getInstance()()->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked); - break; - case AxmolKeyEvent::Back: - IMEDispatcher::sharedDispatcher()->dispatchDeleteBackward(1); - break; - case AxmolKeyEvent::Enter: - IMEDispatcher::sharedDispatcher()->dispatchInsertText("\n", 1); - break; - default: - break; - } - break; - } -} - -WinRTKeyboardEvent::WinRTKeyboardEvent(WinRTKeyboardEventType type, const Windows::UI::Core::KeyEventArgs& args) - : m_type(type), m_key(args) -{} - -void WinRTKeyboardEvent::execute() -{ - RenderViewImpl::sharedRenderView()->OnWinRTKeyboardEvent(m_type, m_key); -} - -BackButtonEvent::BackButtonEvent() {} - -void BackButtonEvent::execute() -{ - RenderViewImpl::sharedRenderView()->OnBackKeyPress(); -} - -CustomInputEvent::CustomInputEvent(const std::function& fun) : m_fun(fun) {} - -void CustomInputEvent::execute() -{ - m_fun(); -} - -UIEditBoxEvent::UIEditBoxEvent( - const Windows::Foundation::IInspectable& sender, - const winrt::hstring& text, - const winrt::delegate& handle) - : m_sender(sender), m_text(text), m_handler(handle) -{} - -void UIEditBoxEvent::execute() -{ - if (m_handler) - { - m_handler(m_sender, m_text); - } -} - -UIEditBoxEndEvent::UIEditBoxEndEvent( - const Windows::Foundation::IInspectable& sender, - const winrt::hstring& text, - int action, - winrt::delegate& handle) - : m_sender(sender), m_text(text), m_action(action), m_handler(handle) -{} - -void UIEditBoxEndEvent::execute() -{ - if (m_handler) - { - EndEventArgs args(m_action, m_text); - m_handler(m_sender, args); - } -} - -} // namespace ax diff --git a/axmol/platform/winrt/InputEvent.h b/axmol/platform/winrt/InputEvent.h deleted file mode 100644 index f4faec43cc9f..000000000000 --- a/axmol/platform/winrt/InputEvent.h +++ /dev/null @@ -1,179 +0,0 @@ -/**************************************************************************** -Copyright (c) 2013 cocos2d-x.org -Copyright (c) Microsoft Open Technologies, Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -#include "axmol/platform/PlatformMacros.h" -#include "axmol/platform/winrt/InputEventTypes.h" -#include "axmol/base/EventKeyboard.h" -#include "axmol/base/Types.h" - -#include -#include -using namespace winrt; - -namespace ax -{ - -enum PointerEventType -{ - PointerPressed, - PointerMoved, - PointerReleased, - MousePressed, - MouseMoved, - MouseReleased, - MouseWheelChanged, -}; -enum MouseButton -{ - Left = 0, - Right = 1, - Middle = 2, - None -}; - -class AX_DLL InputEvent -{ -public: - InputEvent() {}; - virtual ~InputEvent() {}; - virtual void execute() = 0; -}; - -class AX_DLL AccelerometerEvent : public InputEvent -{ -public: - AccelerometerEvent(const ax::Acceleration& event); - virtual void execute(); - -private: - ax::Acceleration m_event; -}; - -class AX_DLL PointerEvent : public InputEvent -{ -public: - PointerEvent(PointerEventType type, const Windows::UI::Core::PointerEventArgs& args); - virtual void execute(); - -private: - PointerEventType m_type; - Windows::UI::Core::PointerEventArgs m_args; -}; - -class AX_DLL KeyboardEvent : public InputEvent - -{ -public: - KeyboardEvent(AxmolKeyEvent type); - KeyboardEvent(AxmolKeyEvent type, const winrt::hstring& text); - virtual void execute(); - -private: - AxmolKeyEvent m_type; - winrt::hstring m_text; -}; - -enum class WinRTKeyboardEventType -{ - Up, - Down -}; - -class AX_DLL WinRTKeyboardEvent : public InputEvent -{ -public: - WinRTKeyboardEvent(WinRTKeyboardEventType type, const Windows::UI::Core::KeyEventArgs& args); - virtual void execute(); - -private: - WinRTKeyboardEventType m_type; - Windows::UI::Core::KeyEventArgs m_key; -}; - -class AX_DLL BackButtonEvent : public InputEvent -{ -public: - BackButtonEvent(); - virtual void execute(); -}; - -class AX_DLL CustomInputEvent : public InputEvent -{ -public: - CustomInputEvent(const std::function&); - virtual void execute(); - -private: - std::function m_fun; -}; - -class UIEditBoxEvent : public ax::InputEvent -{ -public: - UIEditBoxEvent(const Windows::Foundation::IInspectable& sender, - const winrt::hstring& text, - const winrt::delegate& handle); - - virtual void execute(); - -protected: - Windows::Foundation::IInspectable m_sender; - winrt::hstring m_text; - winrt::delegate m_handler; -}; - -struct EndEventArgs -{ -public: - EndEventArgs(int action, const winrt::hstring& text) : m_text(text), m_action(action) {} - int GetAction() const { return m_action; } - const winrt::hstring& GetText() const { return m_text; } - -private: - int m_action; - winrt::hstring m_text; -}; - -class UIEditBoxEndEvent : public ax::InputEvent -{ -public: - UIEditBoxEndEvent(const Windows::Foundation::IInspectable& sender, - const winrt::hstring& text, - int action, - winrt::delegate& handle); - virtual void execute(); - -protected: - int m_action; - Windows::Foundation::IInspectable m_sender; - winrt::hstring m_text; - winrt::delegate m_handler; -}; - -} // namespace ax diff --git a/axmol/platform/winrt/InputEventTypes.h b/axmol/platform/winrt/InputEventTypes.h deleted file mode 100644 index 04c1db4a33ae..000000000000 --- a/axmol/platform/winrt/InputEventTypes.h +++ /dev/null @@ -1,41 +0,0 @@ -/**************************************************************************** -Copyright (c) 2013 cocos2d-x.org -Copyright (c) Microsoft Open Technologies, Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -namespace ax -{ - -enum class AxmolKeyEvent : int -{ - Text, - Escape, - Back, - Enter -}; - -} diff --git a/axmol/platform/winrt/Keyboard-winrt.cpp b/axmol/platform/winrt/Keyboard-winrt.cpp deleted file mode 100644 index f40ce549a23c..000000000000 --- a/axmol/platform/winrt/Keyboard-winrt.cpp +++ /dev/null @@ -1,355 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010-2012 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -* Portions Copyright (c) Microsoft Open Technologies, Inc. -* All Rights Reserved -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#include "axmol/platform/winrt/Keyboard-winrt.h" -#include "axmol/base/EventKeyboard.h" -#include "axmol/platform/winrt/RenderViewImpl-winrt.h" -#include "axmol/base/IMEDispatcher.h" -#include "axmol/base/Director.h" -#include "axmol/base/EventDispatcher.h" - -#include -#include -#include -#include - -using namespace winrt; -using namespace Windows::System; -using namespace Windows::System::Threading; -using namespace Windows::UI::Core; -using namespace Windows::UI::Input; -using namespace Windows::UI::Xaml; -using namespace Windows::UI::Xaml::Controls; -using namespace Windows::UI::Xaml::Input; - -namespace ax -{ - -struct keyCodeItem -{ - int key; - EventKeyboard::KeyCode keyCode; -}; - -static std::map g_keyCodeMap; - -// http://www.kbdedit.com/manual/low_level_vk_list.html -// https://msdn.microsoft.com/library/windows/apps/windows.system.virtualkey.aspx - -static keyCodeItem g_keyCodeStructArray[] = { - /* The unknown key */ - {(int)VirtualKey::None, EventKeyboard::KeyCode::KEY_NONE}, - /* Printable keys */ - {(int)VirtualKey::Space, EventKeyboard::KeyCode::KEY_SPACE}, - {(int)VK_OEM_7, EventKeyboard::KeyCode::KEY_APOSTROPHE}, - {(int)VK_OEM_COMMA, EventKeyboard::KeyCode::KEY_COMMA}, - {(int)VK_OEM_MINUS, EventKeyboard::KeyCode::KEY_MINUS}, - {(int)VK_OEM_PERIOD, EventKeyboard::KeyCode::KEY_PERIOD}, - {(int)VK_OEM_2, EventKeyboard::KeyCode::KEY_SLASH}, - {(int)VK_OEM_3, EventKeyboard::KeyCode::KEY_TILDE}, - - {(int)VirtualKey::Number0, EventKeyboard::KeyCode::KEY_0}, - {(int)VirtualKey::Number1, EventKeyboard::KeyCode::KEY_1}, - {(int)VirtualKey::Number2, EventKeyboard::KeyCode::KEY_2}, - {(int)VirtualKey::Number3, EventKeyboard::KeyCode::KEY_3}, - {(int)VirtualKey::Number4, EventKeyboard::KeyCode::KEY_4}, - {(int)VirtualKey::Number5, EventKeyboard::KeyCode::KEY_5}, - {(int)VirtualKey::Number6, EventKeyboard::KeyCode::KEY_6}, - {(int)VirtualKey::Number7, EventKeyboard::KeyCode::KEY_7}, - {(int)VirtualKey::Number8, EventKeyboard::KeyCode::KEY_8}, - {(int)VirtualKey::Number9, EventKeyboard::KeyCode::KEY_9}, - {(int)VK_OEM_1, EventKeyboard::KeyCode::KEY_SEMICOLON}, - {(int)VK_OEM_PLUS, EventKeyboard::KeyCode::KEY_EQUAL}, - {(int)VirtualKey::A, EventKeyboard::KeyCode::KEY_A}, - {(int)VirtualKey::B, EventKeyboard::KeyCode::KEY_B}, - {(int)VirtualKey::C, EventKeyboard::KeyCode::KEY_C}, - {(int)VirtualKey::D, EventKeyboard::KeyCode::KEY_D}, - {(int)VirtualKey::E, EventKeyboard::KeyCode::KEY_E}, - {(int)VirtualKey::F, EventKeyboard::KeyCode::KEY_F}, - {(int)VirtualKey::G, EventKeyboard::KeyCode::KEY_G}, - {(int)VirtualKey::H, EventKeyboard::KeyCode::KEY_H}, - {(int)VirtualKey::I, EventKeyboard::KeyCode::KEY_I}, - {(int)VirtualKey::J, EventKeyboard::KeyCode::KEY_J}, - {(int)VirtualKey::K, EventKeyboard::KeyCode::KEY_K}, - {(int)VirtualKey::L, EventKeyboard::KeyCode::KEY_L}, - {(int)VirtualKey::M, EventKeyboard::KeyCode::KEY_M}, - {(int)VirtualKey::N, EventKeyboard::KeyCode::KEY_N}, - {(int)VirtualKey::O, EventKeyboard::KeyCode::KEY_O}, - {(int)VirtualKey::P, EventKeyboard::KeyCode::KEY_P}, - {(int)VirtualKey::Q, EventKeyboard::KeyCode::KEY_Q}, - {(int)VirtualKey::R, EventKeyboard::KeyCode::KEY_R}, - {(int)VirtualKey::S, EventKeyboard::KeyCode::KEY_S}, - {(int)VirtualKey::T, EventKeyboard::KeyCode::KEY_T}, - {(int)VirtualKey::U, EventKeyboard::KeyCode::KEY_U}, - {(int)VirtualKey::V, EventKeyboard::KeyCode::KEY_V}, - {(int)VirtualKey::W, EventKeyboard::KeyCode::KEY_W}, - {(int)VirtualKey::X, EventKeyboard::KeyCode::KEY_X}, - {(int)VirtualKey::Y, EventKeyboard::KeyCode::KEY_Y}, - {(int)VirtualKey::Z, EventKeyboard::KeyCode::KEY_Z}, - {VK_OEM_4, EventKeyboard::KeyCode::KEY_LEFT_BRACKET}, - {VK_OEM_5, EventKeyboard::KeyCode::KEY_BACK_SLASH}, - {VK_OEM_6, EventKeyboard::KeyCode::KEY_RIGHT_BRACKET}, - // { GLFW_KEY_GRAVE_ACCENT , EventKeyboard::KeyCode::KEY_GRAVE }, - - /* Function keys */ - {(int)VirtualKey::Escape, EventKeyboard::KeyCode::KEY_ESCAPE}, - {(int)VirtualKey::Enter, EventKeyboard::KeyCode::KEY_ENTER}, - {(int)VirtualKey::Tab, EventKeyboard::KeyCode::KEY_TAB}, - {(int)VirtualKey::Back, EventKeyboard::KeyCode::KEY_BACKSPACE}, - {(int)VirtualKey::Insert, EventKeyboard::KeyCode::KEY_INSERT}, - {(int)VirtualKey::Delete, EventKeyboard::KeyCode::KEY_DELETE}, - {(int)VirtualKey::Right, EventKeyboard::KeyCode::KEY_RIGHT_ARROW}, - {(int)VirtualKey::Left, EventKeyboard::KeyCode::KEY_LEFT_ARROW}, - {(int)VirtualKey::Down, EventKeyboard::KeyCode::KEY_DOWN_ARROW}, - {(int)VirtualKey::Up, EventKeyboard::KeyCode::KEY_UP_ARROW}, - {VK_PRIOR, EventKeyboard::KeyCode::KEY_PG_UP}, - {VK_NEXT, EventKeyboard::KeyCode::KEY_PG_DOWN}, - {VK_HOME, EventKeyboard::KeyCode::KEY_HOME}, - {VK_END, EventKeyboard::KeyCode::KEY_END}, - {VK_CAPITAL, EventKeyboard::KeyCode::KEY_CAPS_LOCK}, - {VK_SCROLL, EventKeyboard::KeyCode::KEY_SCROLL_LOCK}, - {VK_NUMLOCK, EventKeyboard::KeyCode::KEY_NUM_LOCK}, - {VK_SNAPSHOT, EventKeyboard::KeyCode::KEY_PRINT}, - {VK_PAUSE, EventKeyboard::KeyCode::KEY_PAUSE}, - {(int)VirtualKey::F1, EventKeyboard::KeyCode::KEY_F1}, - {(int)VirtualKey::F2, EventKeyboard::KeyCode::KEY_F2}, - {(int)VirtualKey::F3, EventKeyboard::KeyCode::KEY_F3}, - {(int)VirtualKey::F4, EventKeyboard::KeyCode::KEY_F4}, - {(int)VirtualKey::F5, EventKeyboard::KeyCode::KEY_F5}, - {(int)VirtualKey::F6, EventKeyboard::KeyCode::KEY_F6}, - {(int)VirtualKey::F7, EventKeyboard::KeyCode::KEY_F7}, - {(int)VirtualKey::F8, EventKeyboard::KeyCode::KEY_F8}, - {(int)VirtualKey::F9, EventKeyboard::KeyCode::KEY_F9}, - {(int)VirtualKey::F10, EventKeyboard::KeyCode::KEY_F10}, - {(int)VirtualKey::F11, EventKeyboard::KeyCode::KEY_F11}, - {(int)VirtualKey::F12, EventKeyboard::KeyCode::KEY_F12}, - {(int)VirtualKey::F13, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F14, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F15, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F16, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F17, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F18, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F19, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F20, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F21, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F22, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F23, EventKeyboard::KeyCode::KEY_NONE}, - {(int)VirtualKey::F24, EventKeyboard::KeyCode::KEY_NONE}, - - {(int)VirtualKey::NumberPad0, EventKeyboard::KeyCode::KEY_0}, - {(int)VirtualKey::NumberPad1, EventKeyboard::KeyCode::KEY_1}, - {(int)VirtualKey::NumberPad2, EventKeyboard::KeyCode::KEY_2}, - {(int)VirtualKey::NumberPad3, EventKeyboard::KeyCode::KEY_3}, - {(int)VirtualKey::NumberPad4, EventKeyboard::KeyCode::KEY_4}, - {(int)VirtualKey::NumberPad5, EventKeyboard::KeyCode::KEY_5}, - {(int)VirtualKey::NumberPad6, EventKeyboard::KeyCode::KEY_6}, - {(int)VirtualKey::NumberPad7, EventKeyboard::KeyCode::KEY_7}, - {(int)VirtualKey::NumberPad8, EventKeyboard::KeyCode::KEY_8}, - {(int)VirtualKey::NumberPad9, EventKeyboard::KeyCode::KEY_9}, -#if 0 - { GLFW_KEY_KP_1, EventKeyboard::KeyCode::KEY_1 }, - { GLFW_KEY_KP_2, EventKeyboard::KeyCode::KEY_2 }, - { GLFW_KEY_KP_3, EventKeyboard::KeyCode::KEY_3 }, - { GLFW_KEY_KP_4, EventKeyboard::KeyCode::KEY_4 }, - { GLFW_KEY_KP_5, EventKeyboard::KeyCode::KEY_5 }, - { GLFW_KEY_KP_6, EventKeyboard::KeyCode::KEY_6 }, - { GLFW_KEY_KP_7, EventKeyboard::KeyCode::KEY_7 }, - { GLFW_KEY_KP_8, EventKeyboard::KeyCode::KEY_8 }, - { GLFW_KEY_KP_9, EventKeyboard::KeyCode::KEY_9 }, -#endif - {(int)VirtualKey::Decimal, EventKeyboard::KeyCode::KEY_PERIOD}, - {(int)VirtualKey::Divide, EventKeyboard::KeyCode::KEY_KP_DIVIDE}, - {(int)VirtualKey::Multiply, EventKeyboard::KeyCode::KEY_KP_MULTIPLY}, - {(int)VirtualKey::Subtract, EventKeyboard::KeyCode::KEY_KP_MINUS}, - {(int)VirtualKey::Add, EventKeyboard::KeyCode::KEY_KP_PLUS}, - //{ GLFW_KEY_KP_ENTER , EventKeyboard::KeyCode::KEY_KP_ENTER }, - //{ GLFW_KEY_KP_EQUAL , EventKeyboard::KeyCode::KEY_EQUAL }, - {(int)VirtualKey::Shift, EventKeyboard::KeyCode::KEY_LEFT_SHIFT}, - {(int)VirtualKey::Control, EventKeyboard::KeyCode::KEY_LEFT_CTRL}, - {VK_LMENU, EventKeyboard::KeyCode::KEY_LEFT_ALT}, - {(int)VirtualKey::LeftWindows, EventKeyboard::KeyCode::KEY_HYPER}, - {(int)VirtualKey::RightShift, EventKeyboard::KeyCode::KEY_RIGHT_SHIFT}, - {(int)VirtualKey::RightControl, EventKeyboard::KeyCode::KEY_RIGHT_CTRL}, - {VK_RMENU, EventKeyboard::KeyCode::KEY_RIGHT_ALT}, - {(int)VirtualKey::RightWindows, EventKeyboard::KeyCode::KEY_HYPER}, - {(int)VirtualKey::Menu, EventKeyboard::KeyCode::KEY_MENU}, - {(int)VirtualKey::LeftMenu, EventKeyboard::KeyCode::KEY_MENU}, - {(int)VirtualKey::RightMenu, EventKeyboard::KeyCode::KEY_MENU}}; - -KeyBoardWinRT::KeyBoardWinRT() -{ - g_keyCodeMap.clear(); - for (auto& item : g_keyCodeStructArray) - { - g_keyCodeMap[item.key] = item.keyCode; - } -} - -KeyBoardWinRT::~KeyBoardWinRT() {} - -void KeyBoardWinRT::ShowKeyboard(winrt::hstring const& text) -{ - auto panel = ax::RenderViewImpl::sharedRenderView()->getPanel(); - auto dispatcher = ax::RenderViewImpl::sharedRenderView()->getDispatcher(); - - if (dispatcher && panel) - { - // run on main UI thread - dispatcher.get().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [this, text, panel]() { - if (m_textBox == nullptr) - { - m_useInputMethod = false; - m_textBox = TextBox(); - m_textBox.Opacity(0.0); - m_textBox.Width(1); - m_textBox.Height(1); - m_textBox.TextChanged(TextChangedEventHandler(this, &KeyBoardWinRT::OnTextChanged)); - m_textBox.TextCompositionStarted(Windows::Foundation::TypedEventHandler< - Windows::UI::Xaml::Controls::TextBox, - Windows::UI::Xaml::Controls::TextCompositionStartedEventArgs>( - this, &KeyBoardWinRT::OnTextCompositionStarted)); - - m_textBox.TextCompositionEnded( - Windows::Foundation::TypedEventHandler( - this, &KeyBoardWinRT::OnTextCompositionEnded)); - panel.get().Children().Append(m_textBox); - } - m_textBox.SelectionLength(0); - m_textBox.SelectionStart(32768); - m_textBox.Focus(FocusState::Programmatic); - }); - } -} - -void KeyBoardWinRT::HideKeyboard(winrt::hstring const& text) -{ - auto panel = ax::RenderViewImpl::sharedRenderView()->getPanel(); - auto dispatcher = ax::RenderViewImpl::sharedRenderView()->getDispatcher(); - - if (dispatcher && panel) - { - // run on main UI thread - dispatcher.get().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, - DispatchedHandler([this, text, panel]() { - if (m_textBox != nullptr) - { - unsigned int index; - if (panel.get().Children().IndexOf(m_textBox, index)) - { - panel.get().Children().RemoveAt(index); - } - } - m_textBox = nullptr; - })); - } -} - -void KeyBoardWinRT::OnWinRTKeyboardEvent(WinRTKeyboardEventType type, KeyEventArgs const& args) -{ - const auto isKeyDown = type == WinRTKeyboardEventType::Down; - const auto isRepeat = (isKeyDown && args.KeyStatus().WasKeyDown); - - int key = static_cast(args.VirtualKey()); - auto it = g_keyCodeMap.find(key); - if (it != g_keyCodeMap.end()) - { - EventKeyboard::KeyCode keyCode = it->second; - - EventKeyboard event(keyCode, isKeyDown, isRepeat); - auto dispatcher = Director::getInstance()->getEventDispatcher(); - dispatcher->dispatchEvent(&event); - if (keyCode == EventKeyboard::KeyCode::KEY_ENTER) - { - IMEDispatcher::sharedDispatcher()->dispatchInsertText("\n", 1); - } - - if (isKeyDown && !event.isStopped()) - { - switch (keyCode) - { - case EventKeyboard::KeyCode::KEY_BACKSPACE: - IMEDispatcher::sharedDispatcher()->dispatchDeleteBackward(1); - break; - case EventKeyboard::KeyCode::KEY_HOME: - case EventKeyboard::KeyCode::KEY_KP_HOME: - case EventKeyboard::KeyCode::KEY_DELETE: - case EventKeyboard::KeyCode::KEY_KP_DELETE: - case EventKeyboard::KeyCode::KEY_END: - case EventKeyboard::KeyCode::KEY_LEFT_ARROW: - case EventKeyboard::KeyCode::KEY_RIGHT_ARROW: - case EventKeyboard::KeyCode::KEY_ESCAPE: - IMEDispatcher::sharedDispatcher()->dispatchControlKey(keyCode); - break; - default: - break; - } - } - } - else - { - AXLOGW("RenderViewImpl::OnWinRTKeyboardEvent Virtual Key Code {} not supported", key); - } -} - -void KeyBoardWinRT::OnTextChanged(const Windows::Foundation::IInspectable& sender, TextChangedEventArgs const& args) -{ - if (m_useInputMethod) - { - return; - } - auto text = m_textBox.Text(); - if (!text.empty()) - { - std::shared_ptr e(new ax::KeyboardEvent(AxmolKeyEvent::Text, text)); - ax::RenderViewImpl::sharedRenderView()->QueueEvent(e); - m_textBox.Text(L""); - } -} - -void KeyBoardWinRT::OnTextCompositionStarted(Windows::UI::Xaml::Controls::TextBox, - Windows::UI::Xaml::Controls::TextCompositionStartedEventArgs const& args) -{ - m_useInputMethod = true; -} - -void KeyBoardWinRT::OnTextCompositionEnded(Windows::UI::Xaml::Controls::TextBox, - Windows::UI::Xaml::Controls::TextCompositionEndedEventArgs const& args) -{ - m_useInputMethod = false; - auto text = m_textBox.Text(); - if (!text.empty()) - { - std::shared_ptr e(new ax::KeyboardEvent(AxmolKeyEvent::Text, text)); - ax::RenderViewImpl::sharedRenderView()->QueueEvent(e); - m_textBox.Text(L""); - } -} - -} // namespace ax diff --git a/axmol/platform/winrt/Keyboard-winrt.h b/axmol/platform/winrt/Keyboard-winrt.h deleted file mode 100644 index d2980027c47b..000000000000 --- a/axmol/platform/winrt/Keyboard-winrt.h +++ /dev/null @@ -1,63 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010-2012 cocos2d-x.org -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -* Portions Copyright (c) Microsoft Open Technologies, Inc. -* All Rights Reserved -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -#include -#include "axmol/platform/winrt/InputEvent.h" - -namespace ax -{ - -class KeyBoardWinRT -{ -public: - KeyBoardWinRT(); - virtual ~KeyBoardWinRT(); - - void ShowKeyboard(const winrt::hstring& text); - void HideKeyboard(const winrt::hstring& text); - -public: - void OnWinRTKeyboardEvent(WinRTKeyboardEventType type, Windows::UI::Core::KeyEventArgs const& args); - -private: - void OnTextChanged(const Windows::Foundation::IInspectable& sender, - Windows::UI::Xaml::Controls::TextChangedEventArgs const& args); - void OnTextCompositionStarted(Windows::UI::Xaml::Controls::TextBox, - Windows::UI::Xaml::Controls::TextCompositionStartedEventArgs const& args); - void OnTextCompositionEnded(Windows::UI::Xaml::Controls::TextBox, - Windows::UI::Xaml::Controls::TextCompositionEndedEventArgs const& args); - - Windows::UI::Xaml::Controls::TextBox m_textBox{nullptr}; - - bool m_useInputMethod; -}; - -} // namespace ax diff --git a/axmol/platform/winrt/RenderView-winrt.cpp b/axmol/platform/winrt/RenderView-winrt.cpp new file mode 100644 index 000000000000..8335d4369def --- /dev/null +++ b/axmol/platform/winrt/RenderView-winrt.cpp @@ -0,0 +1,971 @@ +/**************************************************************************** +Copyright (c) 2013 cocos2d-x.org +Copyright (c) Microsoft Open Technologies, Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "axmol/platform/winrt/RenderView-winrt.h" +#include "axmol/base/Macros.h" +#include "axmol/base/Director.h" +#include "axmol/base/PointerEvent.h" +#include "axmol/base/InputSystem.h" +#include "axmol/platform/Device.h" +#include "axmol/platform/winrt/Application-winrt.h" +#include "axmol/platform/winrt/WinRTUtils.h" +#include "axmol/base/EventDispatcher.h" +#include "axmol/rhi/DriverContext.h" +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace ax +{ + +using namespace Windows::System; +using namespace Windows::System::Threading; +using namespace Windows::Devices::Input; +using namespace Windows::Graphics::Display; +using namespace Windows::Foundation::Metadata; +using namespace Windows::UI::Core; +using namespace Windows::UI::Input; +using namespace Windows::UI::Input::Core; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Input; + +struct keyCodeItem +{ + int key; + KeyboardEvent::KeyCode keyCode; +}; + +// http://www.kbdedit.com/manual/low_level_vk_list.html +// https://msdn.microsoft.com/library/windows/apps/windows.system.virtualkey.aspx + +static constexpr keyCodeItem g_keyCodeStructArray[] = { + /* The unknown key */ + {(int)VirtualKey::None, KeyboardEvent::KeyCode::KEY_NONE}, + /* Printable keys */ + {(int)VirtualKey::Space, KeyboardEvent::KeyCode::KEY_SPACE}, + {(int)VK_OEM_7, KeyboardEvent::KeyCode::KEY_APOSTROPHE}, + {(int)VK_OEM_COMMA, KeyboardEvent::KeyCode::KEY_COMMA}, + {(int)VK_OEM_MINUS, KeyboardEvent::KeyCode::KEY_MINUS}, + {(int)VK_OEM_PERIOD, KeyboardEvent::KeyCode::KEY_PERIOD}, + {(int)VK_OEM_2, KeyboardEvent::KeyCode::KEY_SLASH}, + {(int)VK_OEM_3, KeyboardEvent::KeyCode::KEY_TILDE}, + + {(int)VirtualKey::Number0, KeyboardEvent::KeyCode::KEY_0}, + {(int)VirtualKey::Number1, KeyboardEvent::KeyCode::KEY_1}, + {(int)VirtualKey::Number2, KeyboardEvent::KeyCode::KEY_2}, + {(int)VirtualKey::Number3, KeyboardEvent::KeyCode::KEY_3}, + {(int)VirtualKey::Number4, KeyboardEvent::KeyCode::KEY_4}, + {(int)VirtualKey::Number5, KeyboardEvent::KeyCode::KEY_5}, + {(int)VirtualKey::Number6, KeyboardEvent::KeyCode::KEY_6}, + {(int)VirtualKey::Number7, KeyboardEvent::KeyCode::KEY_7}, + {(int)VirtualKey::Number8, KeyboardEvent::KeyCode::KEY_8}, + {(int)VirtualKey::Number9, KeyboardEvent::KeyCode::KEY_9}, + {(int)VK_OEM_1, KeyboardEvent::KeyCode::KEY_SEMICOLON}, + {(int)VK_OEM_PLUS, KeyboardEvent::KeyCode::KEY_EQUAL}, + {(int)VirtualKey::A, KeyboardEvent::KeyCode::KEY_A}, + {(int)VirtualKey::B, KeyboardEvent::KeyCode::KEY_B}, + {(int)VirtualKey::C, KeyboardEvent::KeyCode::KEY_C}, + {(int)VirtualKey::D, KeyboardEvent::KeyCode::KEY_D}, + {(int)VirtualKey::E, KeyboardEvent::KeyCode::KEY_E}, + {(int)VirtualKey::F, KeyboardEvent::KeyCode::KEY_F}, + {(int)VirtualKey::G, KeyboardEvent::KeyCode::KEY_G}, + {(int)VirtualKey::H, KeyboardEvent::KeyCode::KEY_H}, + {(int)VirtualKey::I, KeyboardEvent::KeyCode::KEY_I}, + {(int)VirtualKey::J, KeyboardEvent::KeyCode::KEY_J}, + {(int)VirtualKey::K, KeyboardEvent::KeyCode::KEY_K}, + {(int)VirtualKey::L, KeyboardEvent::KeyCode::KEY_L}, + {(int)VirtualKey::M, KeyboardEvent::KeyCode::KEY_M}, + {(int)VirtualKey::N, KeyboardEvent::KeyCode::KEY_N}, + {(int)VirtualKey::O, KeyboardEvent::KeyCode::KEY_O}, + {(int)VirtualKey::P, KeyboardEvent::KeyCode::KEY_P}, + {(int)VirtualKey::Q, KeyboardEvent::KeyCode::KEY_Q}, + {(int)VirtualKey::R, KeyboardEvent::KeyCode::KEY_R}, + {(int)VirtualKey::S, KeyboardEvent::KeyCode::KEY_S}, + {(int)VirtualKey::T, KeyboardEvent::KeyCode::KEY_T}, + {(int)VirtualKey::U, KeyboardEvent::KeyCode::KEY_U}, + {(int)VirtualKey::V, KeyboardEvent::KeyCode::KEY_V}, + {(int)VirtualKey::W, KeyboardEvent::KeyCode::KEY_W}, + {(int)VirtualKey::X, KeyboardEvent::KeyCode::KEY_X}, + {(int)VirtualKey::Y, KeyboardEvent::KeyCode::KEY_Y}, + {(int)VirtualKey::Z, KeyboardEvent::KeyCode::KEY_Z}, + {VK_OEM_4, KeyboardEvent::KeyCode::KEY_LEFT_BRACKET}, + {VK_OEM_5, KeyboardEvent::KeyCode::KEY_BACK_SLASH}, + {VK_OEM_6, KeyboardEvent::KeyCode::KEY_RIGHT_BRACKET}, + // { GLFW_KEY_GRAVE_ACCENT , KeyboardEvent::KeyCode::KEY_GRAVE }, + + /* Function keys */ + {(int)VirtualKey::Escape, KeyboardEvent::KeyCode::KEY_ESCAPE}, + {(int)VirtualKey::Enter, KeyboardEvent::KeyCode::KEY_ENTER}, + {(int)VirtualKey::Tab, KeyboardEvent::KeyCode::KEY_TAB}, + {(int)VirtualKey::Back, KeyboardEvent::KeyCode::KEY_BACKSPACE}, + {(int)VirtualKey::Insert, KeyboardEvent::KeyCode::KEY_INSERT}, + {(int)VirtualKey::Delete, KeyboardEvent::KeyCode::KEY_DELETE}, + {(int)VirtualKey::Right, KeyboardEvent::KeyCode::KEY_RIGHT_ARROW}, + {(int)VirtualKey::Left, KeyboardEvent::KeyCode::KEY_LEFT_ARROW}, + {(int)VirtualKey::Down, KeyboardEvent::KeyCode::KEY_DOWN_ARROW}, + {(int)VirtualKey::Up, KeyboardEvent::KeyCode::KEY_UP_ARROW}, + {VK_PRIOR, KeyboardEvent::KeyCode::KEY_PG_UP}, + {VK_NEXT, KeyboardEvent::KeyCode::KEY_PG_DOWN}, + {VK_HOME, KeyboardEvent::KeyCode::KEY_HOME}, + {VK_END, KeyboardEvent::KeyCode::KEY_END}, + {VK_CAPITAL, KeyboardEvent::KeyCode::KEY_CAPS_LOCK}, + {VK_SCROLL, KeyboardEvent::KeyCode::KEY_SCROLL_LOCK}, + {VK_NUMLOCK, KeyboardEvent::KeyCode::KEY_NUM_LOCK}, + {VK_SNAPSHOT, KeyboardEvent::KeyCode::KEY_PRINT}, + {VK_PAUSE, KeyboardEvent::KeyCode::KEY_PAUSE}, + {(int)VirtualKey::F1, KeyboardEvent::KeyCode::KEY_F1}, + {(int)VirtualKey::F2, KeyboardEvent::KeyCode::KEY_F2}, + {(int)VirtualKey::F3, KeyboardEvent::KeyCode::KEY_F3}, + {(int)VirtualKey::F4, KeyboardEvent::KeyCode::KEY_F4}, + {(int)VirtualKey::F5, KeyboardEvent::KeyCode::KEY_F5}, + {(int)VirtualKey::F6, KeyboardEvent::KeyCode::KEY_F6}, + {(int)VirtualKey::F7, KeyboardEvent::KeyCode::KEY_F7}, + {(int)VirtualKey::F8, KeyboardEvent::KeyCode::KEY_F8}, + {(int)VirtualKey::F9, KeyboardEvent::KeyCode::KEY_F9}, + {(int)VirtualKey::F10, KeyboardEvent::KeyCode::KEY_F10}, + {(int)VirtualKey::F11, KeyboardEvent::KeyCode::KEY_F11}, + {(int)VirtualKey::F12, KeyboardEvent::KeyCode::KEY_F12}, + {(int)VirtualKey::F13, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F14, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F15, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F16, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F17, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F18, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F19, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F20, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F21, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F22, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F23, KeyboardEvent::KeyCode::KEY_NONE}, + {(int)VirtualKey::F24, KeyboardEvent::KeyCode::KEY_NONE}, + + {(int)VirtualKey::NumberPad0, KeyboardEvent::KeyCode::KEY_0}, + {(int)VirtualKey::NumberPad1, KeyboardEvent::KeyCode::KEY_1}, + {(int)VirtualKey::NumberPad2, KeyboardEvent::KeyCode::KEY_2}, + {(int)VirtualKey::NumberPad3, KeyboardEvent::KeyCode::KEY_3}, + {(int)VirtualKey::NumberPad4, KeyboardEvent::KeyCode::KEY_4}, + {(int)VirtualKey::NumberPad5, KeyboardEvent::KeyCode::KEY_5}, + {(int)VirtualKey::NumberPad6, KeyboardEvent::KeyCode::KEY_6}, + {(int)VirtualKey::NumberPad7, KeyboardEvent::KeyCode::KEY_7}, + {(int)VirtualKey::NumberPad8, KeyboardEvent::KeyCode::KEY_8}, + {(int)VirtualKey::NumberPad9, KeyboardEvent::KeyCode::KEY_9}, +#if 0 + { GLFW_KEY_KP_1, KeyboardEvent::KeyCode::KEY_1 }, + { GLFW_KEY_KP_2, KeyboardEvent::KeyCode::KEY_2 }, + { GLFW_KEY_KP_3, KeyboardEvent::KeyCode::KEY_3 }, + { GLFW_KEY_KP_4, KeyboardEvent::KeyCode::KEY_4 }, + { GLFW_KEY_KP_5, KeyboardEvent::KeyCode::KEY_5 }, + { GLFW_KEY_KP_6, KeyboardEvent::KeyCode::KEY_6 }, + { GLFW_KEY_KP_7, KeyboardEvent::KeyCode::KEY_7 }, + { GLFW_KEY_KP_8, KeyboardEvent::KeyCode::KEY_8 }, + { GLFW_KEY_KP_9, KeyboardEvent::KeyCode::KEY_9 }, +#endif + {(int)VirtualKey::Decimal, KeyboardEvent::KeyCode::KEY_PERIOD}, + {(int)VirtualKey::Divide, KeyboardEvent::KeyCode::KEY_KP_DIVIDE}, + {(int)VirtualKey::Multiply, KeyboardEvent::KeyCode::KEY_KP_MULTIPLY}, + {(int)VirtualKey::Subtract, KeyboardEvent::KeyCode::KEY_KP_MINUS}, + {(int)VirtualKey::Add, KeyboardEvent::KeyCode::KEY_KP_PLUS}, + //{ GLFW_KEY_KP_ENTER , KeyboardEvent::KeyCode::KEY_KP_ENTER }, + //{ GLFW_KEY_KP_EQUAL , KeyboardEvent::KeyCode::KEY_EQUAL }, + {(int)VirtualKey::Shift, KeyboardEvent::KeyCode::KEY_LEFT_SHIFT}, + {(int)VirtualKey::Control, KeyboardEvent::KeyCode::KEY_LEFT_CTRL}, + {VK_LMENU, KeyboardEvent::KeyCode::KEY_LEFT_ALT}, + {(int)VirtualKey::LeftWindows, KeyboardEvent::KeyCode::KEY_HYPER}, + {(int)VirtualKey::RightShift, KeyboardEvent::KeyCode::KEY_RIGHT_SHIFT}, + {(int)VirtualKey::RightControl, KeyboardEvent::KeyCode::KEY_RIGHT_CTRL}, + {VK_RMENU, KeyboardEvent::KeyCode::KEY_RIGHT_ALT}, + {(int)VirtualKey::RightWindows, KeyboardEvent::KeyCode::KEY_HYPER}, + {(int)VirtualKey::Menu, KeyboardEvent::KeyCode::KEY_MENU}, + {(int)VirtualKey::LeftMenu, KeyboardEvent::KeyCode::KEY_MENU}, + {(int)VirtualKey::RightMenu, KeyboardEvent::KeyCode::KEY_MENU}}; + +RenderView* RenderView::s_renderView = nullptr; + +const std::string_view RenderView::EVENT_WINDOW_RESIZED = "_ax_window_resized"sv; + +namespace +{ +constexpr float WHEEL_DELTA_UNIT = 120.0f; + +PointerType toAxPointerType(PointerDeviceType type) +{ + switch (type) + { + case PointerDeviceType::Touch: + return PointerType::Touch; + case PointerDeviceType::Pen: + return PointerType::Pen; + case PointerDeviceType::Mouse: + default: + return PointerType::Mouse; + } +} + +int32_t toAxButton(PointerPointProperties const& properties) +{ + switch (properties.PointerUpdateKind()) + { + case PointerUpdateKind::LeftButtonPressed: + case PointerUpdateKind::LeftButtonReleased: + return InputButton::Left; + case PointerUpdateKind::RightButtonPressed: + case PointerUpdateKind::RightButtonReleased: + return InputButton::Right; + case PointerUpdateKind::MiddleButtonPressed: + case PointerUpdateKind::MiddleButtonReleased: + return InputButton::Middle; + case PointerUpdateKind::XButton1Pressed: + case PointerUpdateKind::XButton1Released: + return 3; + case PointerUpdateKind::XButton2Pressed: + case PointerUpdateKind::XButton2Released: + return 4; + default: + return InputButton::None; + } +} + +uint32_t toAxPressedButtons(PointerPointProperties const& properties) +{ + uint32_t buttons = 0; + if (properties.IsLeftButtonPressed()) + buttons |= 1u << InputButton::Left; + if (properties.IsRightButtonPressed()) + buttons |= 1u << InputButton::Right; + if (properties.IsMiddleButtonPressed()) + buttons |= 1u << InputButton::Middle; + if (properties.IsXButton1Pressed()) + buttons |= 1u << 3; + if (properties.IsXButton2Pressed()) + buttons |= 1u << 4; + return buttons; +} +} // namespace + +RenderView* RenderView::create(std::string_view viewName) +{ + return createWithRect(viewName, Rect::ZERO); +} + +RenderView* RenderView::createWithRect(std::string_view viewName, + const Rect& rect, + float frameZoomFactor, + bool /*resizable*/) +{ + auto ret = new RenderView(); + if (ret && ret->initWithRect(viewName, rect, frameZoomFactor)) + { + ret->autorelease(); + return ret; + } + + AX_SAFE_DELETE(ret); + return nullptr; +} + +RenderView* RenderView::createWithFullscreen(std::string_view viewName) +{ + return create(viewName); +} + +RenderView::RenderView() + : _isCursorVisible(true) + , _initialized(false) + , _width(0) + , _height(0) + , _dpi(0) + , _orientation(Windows::Graphics::Display::DisplayOrientations::Landscape) +{ + s_renderView = this; + _viewName = "axmol3"; + + _keyCodeMap.clear(); + for (auto& item : g_keyCodeStructArray) + { + _keyCodeMap[item.key] = item.keyCode; + } +} + +RenderView::~RenderView() +{ + if (_coreInput) + _coreInput.Dispatcher().StopProcessEvents(); + if (_inputLoopWorker) + _inputLoopWorker.Cancel(); + destroyRenderSurface(); + + AX_ASSERT(this == s_renderView); + s_renderView = nullptr; +} + +bool RenderView::initWithRect(std::string_view viewName, const Rect& rect, float /*frameZoomFactor*/) +{ + auto application = Application::getInstance(); + _panel = application->getPanel(); + _dispatcher = application->getDispatcher(); + _dpi = static_cast(Device::getDPI()); + _orientation = application->getOrientation(); + _width = rect.size.width; + _height = rect.size.height; + _initialized = true; + + updateRenderScale(); + if (_width > 0 && _height > 0) + { + updateRenderSurface(_width, _height, SurfaceUpdateFlag::WindowSizeChanged | SurfaceUpdateFlag::SilentUpdate); + updateRenderSurface(_width * _renderScale, _height * _renderScale, + SurfaceUpdateFlag::RenderSizeChanged | SurfaceUpdateFlag::SilentUpdate); + } + + setViewName(viewName); + + return true; +} + +void RenderView::setViewName(std::string_view viewName) +{ + RenderViewCore::setViewName(viewName); + + _dispatcher.get().RunAsync(CoreDispatcherPriority::Normal, [this]() { + using namespace Windows::UI::ViewManagement; + + ApplicationView appView = ApplicationView::GetForCurrentView(); + appView.Title(winrt::to_hstring(_viewName)); + }); +} + +void RenderView::setCursorVisible(bool isVisible) +{ + _isCursorVisible = isVisible; +} + +void RenderView::registerEventHandlers() +{ + if (!_dispatcher || !_panel) + return; + + _dispatcher.get().RunAsync(CoreDispatcherPriority::Normal, [this]() { + auto window = Window::Current().CoreWindow(); + window.VisibilityChanged({this, &RenderView::onVisibilityChanged}); + window.KeyDown({this, &RenderView::onKeyPressed}); + window.KeyUp({this, &RenderView::onKeyReleased}); + window.CharacterReceived({this, &RenderView::onCharacterReceived}); + + auto display = DisplayInformation::GetForCurrentView(); + display.OrientationChanged({this, &RenderView::onOrientationChanged}); + display.DpiChanged({this, &RenderView::onDpiChanged}); + + _panel.get().SizeChanged({this, &RenderView::onPanelSizeChanged}); + Window::Current().SetTitleBar(nullptr); + + if (ApiInformation::IsTypePresent(L"Windows.Phone.UI.Input.HardwareButtons")) + SystemNavigationManager::GetForCurrentView().BackRequested({this, &RenderView::onBackButtonPressed}); + + registerInput(); + }); +} + +void RenderView::registerInput() +{ + auto panel = _panel; + if (!panel) + return; + + if (_coreInput) + _coreInput.Dispatcher().StopProcessEvents(); + if (_inputLoopWorker) + _inputLoopWorker.Cancel(); + + auto workItemHandler = [this, panel](Windows::Foundation::IAsyncAction const&) { + _coreInput = panel.get().CreateCoreIndependentInputSource( + CoreInputDeviceTypes::Mouse | CoreInputDeviceTypes::Touch | CoreInputDeviceTypes::Pen); + + _coreInput.PointerPressed({this, &RenderView::onPointerPressed}); + _coreInput.PointerMoved({this, &RenderView::onPointerMoved}); + _coreInput.PointerReleased({this, &RenderView::onPointerReleased}); + _coreInput.PointerWheelChanged({this, &RenderView::onPointerWheelChanged}); + + if (!isCursorVisible()) + _coreInput.PointerCursor(nullptr); + + _coreInput.Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit); + }; + + _inputLoopWorker = ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::TimeSliced); +} + +void RenderView::syncCursorVisibility() +{ + if (_cursorVisible == isCursorVisible()) + return; + + registerInput(); + _cursorVisible = isCursorVisible(); +} + +void* RenderView::getNativeWindow() const +{ + return winrt::get_abi(_panel.get()); +} + +SurfaceHandle RenderView::getNativeDisplay() const +{ + return winrt::get_abi(_panel.get()); +} + +void RenderView::createRenderSurface() +{ +#if AX_ENABLE_GL + if (!rhi::DriverContext::isOpenGL()) + return; + + if (!_eglSurfaceProvider) + _eglSurfaceProvider = std::make_unique(); + + if (_eglSurface == EGL_NO_SURFACE) + { + // The app can configure the SwapChainPanel which may boost performance. + // By default, this template uses the default configuration. + _eglSurface = _eglSurfaceProvider->createSurface(_panel.get(), nullptr, nullptr); + } +#endif +} + +void RenderView::destroyRenderSurface() +{ +#if AX_ENABLE_GL + if (!rhi::DriverContext::isOpenGL()) + return; + + if (_eglSurfaceProvider) + _eglSurfaceProvider->destroySurface(_eglSurface); + + _eglSurface = EGL_NO_SURFACE; +#endif +} + +void RenderView::recoverFromLostDevice() +{ +#if AX_ENABLE_GL + if (rhi::DriverContext::isOpenGL()) + { + Concurrency::critical_section::scoped_lock lock(_eglSurfaceCriticalSection); + destroyRenderSurface(); + if (_eglSurfaceProvider) + _eglSurfaceProvider->reset(); + createRenderSurface(); + } +#endif +} + +void RenderView::terminateApp() +{ +#if AX_ENABLE_GL + if (rhi::DriverContext::isOpenGL()) + { + Concurrency::critical_section::scoped_lock lock(_eglSurfaceCriticalSection); + destroyRenderSurface(); + if (_eglSurfaceProvider) + _eglSurfaceProvider->cleanup(); + } +#endif + Windows::UI::Xaml::Application::Current().Exit(); +} + +void RenderView::makeSurfaceCurrent() +{ +#if AX_ENABLE_GL + if (rhi::DriverContext::isOpenGL() && _eglSurfaceProvider && _eglSurface != EGL_NO_SURFACE) + _eglSurfaceProvider->makeCurrent(_eglSurface); +#endif +} + +bool RenderView::swapSurfaceBuffers() +{ +#if AX_ENABLE_GL + if (rhi::DriverContext::isOpenGL()) + { + EGLBoolean result = GL_FALSE; + { + Concurrency::critical_section::scoped_lock lock(_eglSurfaceCriticalSection); + if (_eglSurfaceProvider) + result = _eglSurfaceProvider->swapBuffers(_eglSurface); + } + + return result == GL_TRUE; + } +#endif + return true; +} + +void RenderView::setIMEKeyboardState(bool bOpen) +{ + setIMEKeyboardState(bOpen, ""); +} + +AlertResult RenderView::showAlertDialog(const winrt::hstring& title, const winrt::hstring& message, AlertStyle style) +{ + using namespace winrt::Windows::UI::Core; + using namespace winrt::Windows::UI::Popups; + + if (!_dispatcher) + return AlertResult::No; + + bool isOnMainUIThread = _dispatcher.get().HasThreadAccess(); + bool canPromise = !isOnMainUIThread && bitmask::any(style, AlertStyle::RequireSync); + + auto promisePtr = std::make_shared>(); + auto future = promisePtr->get_future(); + + auto addCommand = [canPromise](MessageDialog& dlg, std::wstring_view btnTitle, AlertResult ret, + std::shared_ptr> promisePtr) { + dlg.Commands().Append(UICommand(btnTitle, [promisePtr, ret, canPromise](auto&&) { + if (canPromise) + { + try + { + promisePtr->set_value(ret); + } + catch (...) + {} + } + })); + }; + + auto showDialogAsync = [title, message, style, addCommand, promisePtr]() mutable { + MessageDialog dlg(message, title); + dlg.CancelCommandIndex(1); + + if (bitmask::any(style, AlertStyle::OkCancel)) + { + addCommand(dlg, L"OK", AlertResult::Ok, promisePtr); + addCommand(dlg, L"Cancel", AlertResult::Cancel, promisePtr); + } + else if (bitmask::any(style, AlertStyle::YesNo)) + { + addCommand(dlg, L"Yes", AlertResult::Yes, promisePtr); + addCommand(dlg, L"No", AlertResult::No, promisePtr); + } + else if (bitmask::any(style, AlertStyle::YesNoCancel)) + { + addCommand(dlg, L"Yes", AlertResult::Yes, promisePtr); + addCommand(dlg, L"No", AlertResult::No, promisePtr); + addCommand(dlg, L"Cancel", AlertResult::Cancel, promisePtr); + } + else + { + addCommand(dlg, L"OK", AlertResult::Ok, promisePtr); + } + + dlg.ShowAsync(); + }; + + if (!isOnMainUIThread) + { + _dispatcher.get().RunAsync(CoreDispatcherPriority::Normal, showDialogAsync); + } + else + { + showDialogAsync(); + } + + return canPromise ? future.get() : AlertResult::None; +} + +void RenderView::setIMEKeyboardState(bool bOpen, std::string_view str) +{ + if (bOpen) + { + showKeyboard(PlatformStringFromString(str)); + } + else + { + hideKeyboard(PlatformStringFromString(str)); + } +} + +void RenderView::swapBuffers() {} + +bool RenderView::isGfxContextReady() +{ + return true; +} + +void RenderView::end() +{ + Application::getInstance()->requestQuit(); +} + +void RenderView::onVisibilityChanged(Windows::UI::Core::CoreWindow const& /*sender*/, + Windows::UI::Core::VisibilityChangedEventArgs const& args) +{ + if (args.Visible()) + Application::getInstance()->resume(); + else + Application::getInstance()->suspend(); +} + +void RenderView::onWindowClosed(Windows::UI::Core::CoreWindow const& /*sender*/, + Windows::UI::Core::CoreWindowEventArgs const& /*args*/) +{ + Application::getInstance()->shutdown(); +} + +RenderView* RenderView::sharedRenderView() +{ + return s_renderView; +} + +void RenderView::updateOrientation(Windows::Graphics::Display::DisplayOrientations orientation) +{ + if (_orientation != orientation) + { + _orientation = orientation; + handleWindowResized(); + } +} + +void RenderView::updateWindowSize(float width, float height) +{ + if (width != _width || height != _height) + { + _width = width; + _height = height; + handleWindowResized(); + } +} + +void RenderView::setDPI(float dpi) +{ + bool inital = _dpi == 0; + if (_dpi != dpi) + { + Device::setDPI(dpi); + _dpi = dpi; + updateRenderScale(); + if (!inital) + { + updateRenderSurface(_width * _renderScale, _height * _renderScale, SurfaceUpdateFlag::RenderSizeChanged); + } + } +} + +void RenderView::handleWindowResized() +{ + updateRenderSurface(_width, _height, SurfaceUpdateFlag::WindowSizeChanged); + updateRenderSurface(_width * _renderScale, _height * _renderScale, SurfaceUpdateFlag::RenderSizeChanged); + + Size size(_width, _height); + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderView::EVENT_WINDOW_RESIZED, &size); +} + +void RenderView::updateRenderScale() +{ + if (!rhi::DriverContext::isOpenGL()) + _renderScale = Application::getContextAttrs().renderScaleMode == RenderScaleMode::Physical + ? (_dpi > 0 ? _dpi / 96.0f /* 96.0f: Standard DPI baseline */ : 1.0f) + : 1.0f; + else + _renderScale = 1.0f; + + InputSystem::getInstance()->setInputScale(_renderScale); +} + +void RenderView::onPanelSizeChanged(Windows::Foundation::IInspectable const& /*sender*/, + Windows::UI::Xaml::RoutedEventArgs const& /*args*/) +{ + if (_updateScheduled) + return; + + _updateScheduled = true; + auto panel = _panel.get(); + auto width = static_cast(panel.ActualWidth()); + auto height = static_cast(panel.ActualHeight()); + Director::getInstance()->postTask([this, width, height]() { + updateWindowSize(width, height); + _updateScheduled = false; + }, Director::TaskTiming::FrameBoundary); +} + +void RenderView::onPointerPressed(Windows::Foundation::IInspectable const& /*sender*/, PointerEventArgs const& args) +{ + handlePointerEvent(InputPhase::PointerDown, args); +} + +void RenderView::onPointerMoved(Windows::Foundation::IInspectable const& /*sender*/, PointerEventArgs const& args) +{ + handlePointerEvent(InputPhase::PointerMove, args); +} + +void RenderView::onPointerReleased(Windows::Foundation::IInspectable const& /*sender*/, PointerEventArgs const& args) +{ + handlePointerEvent(InputPhase::PointerUp, args); +} + +void RenderView::onPointerWheelChanged(Windows::Foundation::IInspectable const& /*sender*/, + PointerEventArgs const& args) +{ + handlePointerEvent(InputPhase::PointerScroll, args); +} + +void RenderView::onKeyPressed(CoreWindow const& /*sender*/, KeyEventArgs const& args) +{ + handleKeyboardEvent(ax::InputPhase::KeyDown, args); +} + +void RenderView::onKeyReleased(CoreWindow const& /*sender*/, KeyEventArgs const& args) +{ + handleKeyboardEvent(ax::InputPhase::KeyUp, args); +} + +void RenderView::onCharacterReceived(CoreWindow const& /*sender*/, CharacterReceivedEventArgs const& args) +{ + if (_textBox != nullptr) + return; + + auto codepoint = args.KeyCode(); + if (codepoint < 0x20 || codepoint == 0x7f) + return; + + std::wstring text; + if (codepoint <= 0xffff) + { + text.push_back(static_cast(codepoint)); + } + else + { + codepoint -= 0x10000; + text.push_back(static_cast(0xd800 + (codepoint >> 10))); + text.push_back(static_cast(0xdc00 + (codepoint & 0x3ff))); + } + + auto inputText = PlatformStringToString(winrt::hstring{text}); + Director::getInstance()->postTask([inputText = std::move(inputText)]() { + InputSystem::getInstance()->dispatchInsertText(inputText); + }, Director::TaskTiming::FrameBoundary); +} + +void RenderView::onOrientationChanged(DisplayInformation const& sender, + Windows::Foundation::IInspectable const& /*args*/) +{ + auto orientation = sender.CurrentOrientation(); + Director::getInstance()->postTask([this, orientation]() { updateOrientation(orientation); }, + Director::TaskTiming::FrameBoundary); +} + +void RenderView::onDpiChanged(DisplayInformation const& sender, Windows::Foundation::IInspectable const& /*args*/) +{ + auto dpi = sender.LogicalDpi(); + Director::getInstance()->postTask([this, dpi]() { setDPI(dpi); }, Director::TaskTiming::FrameBoundary); +} + +#if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) || _MSC_VER >= 1900 +void RenderView::onBackButtonPressed(Windows::Foundation::IInspectable const& /*sender*/, + BackRequestedEventArgs const& args) +{ + Director::getInstance()->postTask([]() { + InputSystem::getInstance()->handleKeyEvent(KeyboardEvent::KeyCode::KEY_ESCAPE, InputPhase::KeyUp); + }, Director::TaskTiming::FrameBoundary); + args.Handled(true); +} +#endif + +void RenderView::showKeyboard(winrt::hstring const& text) +{ + auto panel = _panel; + auto dispatcher = _dispatcher; + + if (dispatcher && panel) + { + // run on main UI thread + dispatcher.get().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [this, text, panel]() { + if (_textBox == nullptr) + { + _useInputMethod = false; + _textBox = TextBox(); + _textBox.Opacity(0.0); + _textBox.Width(1); + _textBox.Height(1); + _textBox.TextChanged(TextChangedEventHandler(this, &RenderView::onTextChanged)); + _textBox.TextCompositionStarted(Windows::Foundation::TypedEventHandler< + Windows::UI::Xaml::Controls::TextBox, + Windows::UI::Xaml::Controls::TextCompositionStartedEventArgs>( + this, &RenderView::onTextCompositionStarted)); + + _textBox.TextCompositionEnded( + Windows::Foundation::TypedEventHandler( + this, &RenderView::onTextCompositionEnded)); + panel.get().Children().Append(_textBox); + } + _textBox.SelectionLength(0); + _textBox.SelectionStart(32768); + _textBox.Focus(FocusState::Programmatic); + }); + } +} + +void RenderView::hideKeyboard(winrt::hstring const& text) +{ + auto panel = _panel; + auto dispatcher = _dispatcher; + + if (dispatcher && panel) + { + // run on main UI thread + dispatcher.get().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, + DispatchedHandler([this, text, panel]() { + if (_textBox != nullptr) + { + unsigned int index; + if (panel.get().Children().IndexOf(_textBox, index)) + { + panel.get().Children().RemoveAt(index); + } + } + _textBox = nullptr; + })); + } +} + +void RenderView::handlePointerEvent(ax::InputPhase phase, PointerEventArgs const& args) +{ + auto point = args.CurrentPoint(); + auto properties = point.Properties(); + auto position = point.Position(); + + Vec2 pos{static_cast(position.X), static_cast(position.Y)}; + PointerInputState state{.id = static_cast(point.PointerId()), + .pressure = properties.Pressure(), + .button = phase == InputPhase::PointerMove || phase == InputPhase::PointerScroll + ? InputButton::None + : toAxButton(properties), + .pressedButtons = toAxPressedButtons(properties), + .type = toAxPointerType(point.PointerDevice().PointerDeviceType())}; + + if (phase == InputPhase::PointerScroll) + { + Vec2 scrollDelta{0.0f, static_cast(properties.MouseWheelDelta()) / WHEEL_DELTA_UNIT}; + Director::getInstance()->postTask([pos, scrollDelta, state]() { + InputSystem::getInstance()->handlePointerScroll(pos, scrollDelta, state); + }, Director::TaskTiming::FrameBoundary); + return; + } + + Director::getInstance()->postTask([phase, pos, state]() { + auto inputSystem = InputSystem::getInstance(); + switch (phase) + { + case InputPhase::PointerDown: + inputSystem->handlePointerDown(pos, state); + break; + case InputPhase::PointerMove: + inputSystem->handlePointerMove(pos, state); + break; + case InputPhase::PointerUp: + inputSystem->handlePointerUp(pos, state); + break; + default: + break; + } + }, Director::TaskTiming::FrameBoundary); +} + +void RenderView::handleKeyboardEvent(ax::InputPhase phase, KeyEventArgs const& args) +{ + int key = static_cast(args.VirtualKey()); + auto it = _keyCodeMap.find(key); + if (it != _keyCodeMap.end()) + { + KeyboardEvent::KeyCode keyCode = it->second; + + const auto isKeyDown = phase == ax::InputPhase::KeyDown; + if (isKeyDown && args.KeyStatus().WasKeyDown) + phase = ax::InputPhase::KeyRepeat; + Director::getInstance()->postTask([keyCode, phase]() { + InputSystem::getInstance()->handleKeyEvent(keyCode, phase); + }, Director::TaskTiming::FrameBoundary); + } + else + { + AXLOGW("RenderView::onWinRTKeyboardEvent Virtual Key Code {} not supported", key); + } +} + +void RenderView::onTextChanged(const Windows::Foundation::IInspectable& sender, TextChangedEventArgs const& args) +{ + if (_useInputMethod) + { + return; + } + auto text = _textBox.Text(); + if (!text.empty()) + { + auto inputText = PlatformStringToString(text); + Director::getInstance()->postTask([inputText = std::move(inputText)]() { + InputSystem::getInstance()->dispatchInsertText(inputText); + }, Director::TaskTiming::FrameBoundary); + _textBox.Text(L""); + } +} + +void RenderView::onTextCompositionStarted(Windows::UI::Xaml::Controls::TextBox, + Windows::UI::Xaml::Controls::TextCompositionStartedEventArgs const& args) +{ + _useInputMethod = true; +} + +void RenderView::onTextCompositionEnded(Windows::UI::Xaml::Controls::TextBox, + Windows::UI::Xaml::Controls::TextCompositionEndedEventArgs const& args) +{ + _useInputMethod = false; + auto text = _textBox.Text(); + if (!text.empty()) + { + auto inputText = PlatformStringToString(text); + Director::getInstance()->postTask([inputText = std::move(inputText)]() { + InputSystem::getInstance()->dispatchInsertText(inputText); + }, Director::TaskTiming::FrameBoundary); + _textBox.Text(L""); + } +} + +} // namespace ax diff --git a/axmol/platform/winrt/RenderView-winrt.h b/axmol/platform/winrt/RenderView-winrt.h new file mode 100644 index 000000000000..5e7ac3cf5f23 --- /dev/null +++ b/axmol/platform/winrt/RenderView-winrt.h @@ -0,0 +1,207 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org +Copyright (c) Microsoft Open Technologies, Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "axmol/platform/winrt/StdC-winrt.h" +#include "axmol/platform/Common.h" +#include "axmol/platform/RenderViewCore.h" +#include "axmol/base/KeyboardEvent.h" +#include "axmol/base/InputSystem.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if AX_ENABLE_GL +# include "axmol/platform/winrt/EGLSurfaceProvider.h" +#endif + +using namespace winrt; + +namespace ax +{ + +class RenderView; +class Application; + +class AX_DLL RenderView : public RenderViewCore +{ +public: + static const std::string_view EVENT_WINDOW_RESIZED; + + static RenderView* create(std::string_view viewName); + static RenderView* createWithRect(std::string_view viewName, + const Rect& rect, + float zoomFactor = 1.0f, + bool resizable = false); + static RenderView* createWithFullscreen(std::string_view viewName); + + /* override functions */ + bool isGfxContextReady() override; + void end() override; + void swapBuffers() override; + + Windows::Graphics::Display::DisplayOrientations getDeviceOrientation() { return _orientation; }; + Size getRenerTargetSize() const { return Size(_width, _height); } + + void setIMEKeyboardState(bool bOpen) override; + void setIMEKeyboardState(bool bOpen, std::string_view str); + + /** + * Hide or Show the mouse cursor if there is one. + * + * @param isVisible Hide or Show the mouse cursor if there is one. + */ + void setCursorVisible(bool isVisible) override; + + bool isCursorVisible() { return _isCursorVisible; } + + winrt::agile_ref getDispatcher() const { return _dispatcher; } + winrt::agile_ref getPanel() const { return _panel; } + + void registerEventHandlers(); + void registerInput(); + void syncCursorVisibility(); + + void onVisibilityChanged(Windows::UI::Core::CoreWindow const& sender, + Windows::UI::Core::VisibilityChangedEventArgs const& args); + void onWindowClosed(Windows::UI::Core::CoreWindow const& sender, + Windows::UI::Core::CoreWindowEventArgs const& args); + + void handlePointerEvent(ax::InputPhase phase, Windows::UI::Core::PointerEventArgs const& args); + void handleKeyboardEvent(ax::InputPhase phase, Windows::UI::Core::KeyEventArgs const& args); + + AlertResult showAlertDialog(const winrt::hstring& title, const winrt::hstring& message, AlertStyle style); + + void updateOrientation(Windows::Graphics::Display::DisplayOrientations orientation); + void updateWindowSize(float width, float height); + + void setDPI(float dpi); + float getDPI() { return _dpi; } + + void createRenderSurface(); + void destroyRenderSurface(); + void recoverFromLostDevice(); + void terminateApp(); + void makeSurfaceCurrent(); + bool swapSurfaceBuffers(); + // static function + /** + @brief get the shared main open gl window + */ + static RenderView* sharedRenderView(); + + void* getNativeWindow() const override; + SurfaceHandle getNativeDisplay() const override; + WindowPlatform getWindowPlatform() const override { return WindowPlatform::CoreWindow; } + + void setViewName(std::string_view viewName) override; + +protected: + RenderView(); + ~RenderView() override; + + void showKeyboard(const winrt::hstring& text); + void hideKeyboard(const winrt::hstring& text); + + void onTextChanged(const Windows::Foundation::IInspectable& sender, + Windows::UI::Xaml::Controls::TextChangedEventArgs const& args); + void onTextCompositionStarted(Windows::UI::Xaml::Controls::TextBox, + Windows::UI::Xaml::Controls::TextCompositionStartedEventArgs const& args); + void onTextCompositionEnded(Windows::UI::Xaml::Controls::TextBox, + Windows::UI::Xaml::Controls::TextCompositionEndedEventArgs const& args); + + AX_DISALLOW_COPY_AND_ASSIGN(RenderView); + + bool initWithRect(std::string_view viewName, const Rect& rect, float frameZoomFactor); + + void handleWindowResized(); + void updateRenderScale(); + + void onPanelSizeChanged(Windows::Foundation::IInspectable const& sender, + Windows::UI::Xaml::RoutedEventArgs const& args); + void onPointerPressed(Windows::Foundation::IInspectable const& sender, + Windows::UI::Core::PointerEventArgs const& args); + void onPointerMoved(Windows::Foundation::IInspectable const& sender, + Windows::UI::Core::PointerEventArgs const& args); + void onPointerReleased(Windows::Foundation::IInspectable const& sender, + Windows::UI::Core::PointerEventArgs const& args); + void onPointerWheelChanged(Windows::Foundation::IInspectable const& sender, + Windows::UI::Core::PointerEventArgs const& args); + void onKeyPressed(Windows::UI::Core::CoreWindow const& sender, Windows::UI::Core::KeyEventArgs const& args); + void onKeyReleased(Windows::UI::Core::CoreWindow const& sender, Windows::UI::Core::KeyEventArgs const& args); + void onCharacterReceived(Windows::UI::Core::CoreWindow const& sender, + Windows::UI::Core::CharacterReceivedEventArgs const& args); + void onOrientationChanged(Windows::Graphics::Display::DisplayInformation const& sender, + Windows::Foundation::IInspectable const& args); + void onDpiChanged(Windows::Graphics::Display::DisplayInformation const& sender, + Windows::Foundation::IInspectable const& args); +#if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) || _MSC_VER >= 1900 + void onBackButtonPressed(Windows::Foundation::IInspectable const& sender, + Windows::UI::Core::BackRequestedEventArgs const& args); +#endif + + static RenderView* s_renderView; + + bool _isCursorVisible; + + float _width; + float _height; + float _dpi; + Windows::Graphics::Display::DisplayOrientations _orientation; + + bool _initialized; + winrt::agile_ref _dispatcher; + winrt::agile_ref _panel; + Windows::UI::Core::CoreIndependentInputSource _coreInput{nullptr}; + Windows::Foundation::IAsyncAction _inputLoopWorker{nullptr}; + +#if AX_ENABLE_GL + std::unique_ptr _eglSurfaceProvider; + EGLSurface _eglSurface{EGL_NO_SURFACE}; + Concurrency::critical_section _eglSurfaceCriticalSection{}; +#endif + + // keyboard support + std::unordered_map _keyCodeMap; + Windows::UI::Xaml::Controls::TextBox _textBox{nullptr}; + bool _useInputMethod{false}; + + bool _updateScheduled{false}; + bool _cursorVisible{true}; +}; + +} // namespace ax diff --git a/axmol/platform/winrt/RenderViewImpl-winrt.cpp b/axmol/platform/winrt/RenderViewImpl-winrt.cpp deleted file mode 100644 index 674e256de45b..000000000000 --- a/axmol/platform/winrt/RenderViewImpl-winrt.cpp +++ /dev/null @@ -1,639 +0,0 @@ -/**************************************************************************** -Copyright (c) 2013 cocos2d-x.org -Copyright (c) Microsoft Open Technologies, Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#include "axmol/platform/winrt/RenderViewImpl-winrt.h" -#include "axmol/base/Macros.h" -#include "axmol/base/Director.h" -#include "axmol/base/Touch.h" -#include "axmol/base/IMEDispatcher.h" -#include "axmol/base/EventListenerKeyboard.h" -#include "axmol/platform/winrt/Application-winrt.h" -#include "axmol/platform/winrt/WinRTUtils.h" -#include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventMouse.h" -#include "axmol/rhi/DriverContext.h" -#include - -#include -#include -#include -#include -#include -#include - -namespace ax -{ - -RenderViewImpl* RenderViewImpl::s_renderView = nullptr; - -const std::string_view RenderViewImpl::EVENT_WINDOW_RESIZED = "_ax_window_resized"sv; - -static EventMouse::MouseButton checkMouseButton(Windows::UI::Core::PointerEventArgs const& args) -{ - if (args.CurrentPoint().Properties().IsLeftButtonPressed()) - { - return EventMouse::MouseButton::BUTTON_LEFT; - } - else if (args.CurrentPoint().Properties().IsRightButtonPressed()) - { - return EventMouse::MouseButton::BUTTON_RIGHT; - } - else if (args.CurrentPoint().Properties().IsMiddleButtonPressed()) - { - return EventMouse::MouseButton::BUTTON_MIDDLE; - } - return EventMouse::MouseButton::BUTTON_UNSET; -} - -RenderViewImpl* RenderViewImpl::create(std::string_view viewName) -{ - auto ret = new RenderViewImpl; - if (ret && ret->initWithFullScreen(viewName)) - { - ret->autorelease(); - return ret; - } - - return nullptr; -} - -RenderViewImpl* RenderViewImpl::createWithRect(std::string_view viewName, - const Rect& rect, - float frameZoomFactor, - bool /*resizable*/) -{ - auto ret = new RenderViewImpl; - if (ret && ret->initWithRect(viewName, rect, frameZoomFactor)) - { - ret->autorelease(); - return ret; - } - - return nullptr; -} - -RenderViewImpl* RenderViewImpl::createWithFullscreen(std::string_view viewName) -{ - auto ret = new RenderViewImpl(); - if (ret->initWithFullScreen(viewName)) - { - ret->autorelease(); - return ret; - } - AX_SAFE_DELETE(ret); - return nullptr; -} - -RenderViewImpl::RenderViewImpl() - : _supportTouch(true) - , _isCursorVisible(true) - , m_lastPointValid(false) - , m_running(false) - , m_initialized(false) - , m_windowClosed(false) - , m_windowVisible(true) - , m_width(0) - , m_height(0) - , m_dpi(0) - , m_orientation(Windows::Graphics::Display::DisplayOrientations::Landscape) - , m_appShouldExit(false) - , _lastMouseButtonPressed(EventMouse::MouseButton::BUTTON_UNSET) -{ - s_renderView = this; - _viewName = "axmol3"; - m_keyboard = KeyBoardWinRT(); - - m_backButtonListener = EventListenerKeyboard::create(); - m_backButtonListener->onKeyReleased = AX_CALLBACK_2(RenderViewImpl::BackButtonListener, this); - Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(m_backButtonListener, INT_MAX); -} - -RenderViewImpl::~RenderViewImpl() -{ - AX_ASSERT(this == s_renderView); - s_renderView = nullptr; -} - -bool RenderViewImpl::initWithRect(std::string_view viewName, const Rect& rect, float /*frameZoomFactor*/) -{ - setViewName(viewName); - - m_width = rect.size.width; - m_height = rect.size.height; - - m_initialized = true; - - return true; -} - -bool RenderViewImpl::initWithFullScreen(std::string_view viewName) -{ - return initWithRect(viewName, Rect(0, 0, m_width, m_height), 1.0f); -} - -void ax::RenderViewImpl::setCursorVisible(bool isVisible) -{ - _isCursorVisible = isVisible; -} - -void RenderViewImpl::setDispatcher(winrt::agile_ref dispatcher) -{ - m_dispatcher = dispatcher; -} - -void RenderViewImpl::setPanel(winrt::agile_ref panel) -{ - m_panel = panel; -} - -void* RenderViewImpl::getNativeWindow() const -{ - return winrt::get_abi(m_panel.get()); -} - -SurfaceHandle RenderViewImpl::getNativeDisplay() const -{ - return winrt::get_abi(m_panel.get()); -} - -void RenderViewImpl::setIMEKeyboardState(bool bOpen) -{ - setIMEKeyboardState(bOpen, ""); -} - -AlertResult RenderViewImpl::ShowAlertDialog(const winrt::hstring& title, - const winrt::hstring& message, - AlertStyle style) -{ - using namespace winrt::Windows::UI::Core; - using namespace winrt::Windows::UI::Popups; - - if (!m_dispatcher) - return AlertResult::No; - - bool isOnMainUIThread = m_dispatcher.get().HasThreadAccess(); - bool canPromise = !isOnMainUIThread && bitmask::any(style, AlertStyle::RequireSync); - - auto promisePtr = std::make_shared>(); - auto future = promisePtr->get_future(); - - auto addCommand = [canPromise](MessageDialog& dlg, std::wstring_view btnTitle, AlertResult ret, - std::shared_ptr> promisePtr) { - dlg.Commands().Append(UICommand(btnTitle, [promisePtr, ret, canPromise](auto&&) { - if (canPromise) - { - try - { - promisePtr->set_value(ret); - } - catch (...) - {} - } - })); - }; - - auto showDialogAsync = [title, message, style, addCommand, promisePtr]() mutable { - MessageDialog dlg(message, title); - dlg.CancelCommandIndex(1); - - if (bitmask::any(style, AlertStyle::OkCancel)) - { - addCommand(dlg, L"OK", AlertResult::Ok, promisePtr); - addCommand(dlg, L"Cancel", AlertResult::Cancel, promisePtr); - } - else if (bitmask::any(style, AlertStyle::YesNo)) - { - addCommand(dlg, L"Yes", AlertResult::Yes, promisePtr); - addCommand(dlg, L"No", AlertResult::No, promisePtr); - } - else if (bitmask::any(style, AlertStyle::YesNoCancel)) - { - addCommand(dlg, L"Yes", AlertResult::Yes, promisePtr); - addCommand(dlg, L"No", AlertResult::No, promisePtr); - addCommand(dlg, L"Cancel", AlertResult::Cancel, promisePtr); - } - else - { - addCommand(dlg, L"OK", AlertResult::Ok, promisePtr); - } - - dlg.ShowAsync(); - }; - - if (!isOnMainUIThread) - { - m_dispatcher.get().RunAsync(CoreDispatcherPriority::Normal, showDialogAsync); - } - else - { - showDialogAsync(); - } - - return canPromise ? future.get() : AlertResult::None; -} - -void RenderViewImpl::setIMEKeyboardState(bool bOpen, std::string_view str) -{ - if (bOpen) - { - m_keyboard.ShowKeyboard(PlatformStringFromString(str)); - } - else - { - m_keyboard.HideKeyboard(PlatformStringFromString(str)); - } -} - -void RenderViewImpl::swapBuffers() {} - -bool RenderViewImpl::isGfxContextReady() -{ - return true; -} - -void RenderViewImpl::end() -{ - m_windowClosed = true; - m_appShouldExit = true; -} - -void RenderViewImpl::OnSuspending(Windows::Foundation::IInspectable const& sender, - Windows::ApplicationModel::SuspendingEventArgs const& args) -{} - -void RenderViewImpl::OnResuming(Windows::Foundation::IInspectable const& sender) {} - -// user pressed the Back Key on the phone -void RenderViewImpl::OnBackKeyPress() -{ - ax::EventKeyboard event(EventKeyboard::KeyCode::KEY_ESCAPE, true); - ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); -} - -void RenderViewImpl::BackButtonListener(EventKeyboard::KeyCode keyCode, Event* event) -{ - if (keyCode == EventKeyboard::KeyCode::KEY_ESCAPE) - { - AXLOGD("*********************************************************************"); - AXLOGD("RenderViewImpl::BackButtonListener: Exiting application!"); - AXLOGD(""); - AXLOGD("If you want to listen for Windows Phone back button events,"); - AXLOGD("add a listener for EventKeyboard::KeyCode::KEY_ESCAPE"); - AXLOGD("Make sure you call stopPropagation() on the Event if you don't"); - AXLOGD("want your app to exit when the back button is pressed."); - AXLOGD(""); - AXLOGD("For example, add the following to your scene..."); - AXLOGD("auto listener = EventListenerKeyboard::create();"); - AXLOGD("listener->onKeyReleased = AX_CALLBACK_2(HelloWorld::onKeyReleased, this);"); - AXLOGD("getEventDispatcher()->addEventListenerWithFixedPriority(listener, 1);"); - AXLOGD(""); - AXLOGD("void HelloWorld::onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event)"); - AXLOGD("{{"); - AXLOGD(" if (keyCode == EventKeyboard::KeyCode::KEY_ESCAPE)"); - AXLOGD(" {{"); - AXLOGD(" if (myAppShouldNotQuit) // or whatever logic you want..."); - AXLOGD(" {{"); - AXLOGD(" event->stopPropagation();"); - AXLOGD(" }}"); - AXLOGD(" }}"); - AXLOGD("}}"); - AXLOGD(""); - AXLOGD("You MUST call event->stopPropagation() if you don't want your app to quit!"); - AXLOGD("*********************************************************************"); - - Director::getInstance()->end(); - } -} - -bool RenderViewImpl::AppShouldExit() -{ - return m_appShouldExit; -} - -void RenderViewImpl::OnPointerPressed(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::PointerEventArgs const& args) -{ - OnPointerPressed(args); -} - -void RenderViewImpl::OnPointerPressed(Windows::UI::Core::PointerEventArgs const& args) -{ - intptr_t id = args.CurrentPoint().PointerId(); - Vec2 pt = GetPoint(args); - handleTouchesBegin(1, &id, &pt.x, &pt.y); -} - -void RenderViewImpl::OnPointerWheelChanged(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::PointerEventArgs const& args) -{ - float direction = (float)args.CurrentPoint().Properties().MouseWheelDelta(); - intptr_t id = 0; - Vec2 p(0.0f, 0.0f); - handleTouchesBegin(1, &id, &p.x, &p.y); - p.y += direction; - handleTouchesMove(1, &id, &p.x, &p.y); - handleTouchesEnd(1, &id, &p.x, &p.y); -} - -void RenderViewImpl::OnVisibilityChanged(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::VisibilityChangedEventArgs const& args) -{ - m_windowVisible = args.Visible(); -} - -void RenderViewImpl::OnWindowClosed(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::CoreWindowEventArgs const& args) -{ - m_windowClosed = true; -} - -void RenderViewImpl::OnPointerMoved(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::PointerEventArgs const& args) -{ - OnPointerMoved(args); -} - -void RenderViewImpl::OnPointerMoved(Windows::UI::Core::PointerEventArgs const& args) -{ - auto currentPoint = args.CurrentPoint(); - if (currentPoint.IsInContact()) - { - if (m_lastPointValid) - { - intptr_t id = args.CurrentPoint().PointerId(); - Vec2 p = GetPoint(args); - handleTouchesMove(1, &id, &p.x, &p.y); - } - m_lastPoint = currentPoint.Position(); - m_lastPointValid = true; - } - else - { - m_lastPointValid = false; - } -} - -void RenderViewImpl::OnPointerReleased(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::PointerEventArgs const& args) -{ - OnPointerReleased(args); -} - -void RenderViewImpl::OnPointerReleased(Windows::UI::Core::PointerEventArgs const& args) -{ - intptr_t id = args.CurrentPoint().PointerId(); - Vec2 pt = GetPoint(args); - handleTouchesEnd(1, &id, &pt.x, &pt.y); -} - -void ax::RenderViewImpl::OnMousePressed(Windows::UI::Core::PointerEventArgs const& args) -{ - Vec2 pt = GetPoint(args); - - // Emulated touch, if left mouse button - if (args.CurrentPoint().Properties().IsLeftButtonPressed()) - { - intptr_t id = 0; - handleTouchesBegin(1, &id, &pt.x, &pt.y); - } - - float x = transformInputX(pt.x); - float y = transformInputY(pt.y); - if (_lastMouseButtonPressed != EventMouse::MouseButton::BUTTON_UNSET) - { - _currentMouseEvent.setMouseInfo(x, y, _lastMouseButtonPressed, EventMouse::MouseEventType::MOUSE_UP); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); - } - - // Set current button - if (args.CurrentPoint().Properties().IsLeftButtonPressed()) - { - _lastMouseButtonPressed = EventMouse::MouseButton::BUTTON_LEFT; - } - else if (args.CurrentPoint().Properties().IsRightButtonPressed()) - { - _lastMouseButtonPressed = EventMouse::MouseButton::BUTTON_RIGHT; - } - else if (args.CurrentPoint().Properties().IsMiddleButtonPressed()) - { - _lastMouseButtonPressed = EventMouse::MouseButton::BUTTON_MIDDLE; - } - _currentMouseEvent.setMouseInfo(x, y, _lastMouseButtonPressed, EventMouse::MouseEventType::MOUSE_DOWN); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); -} - -void ax::RenderViewImpl::OnMouseMoved(Windows::UI::Core::PointerEventArgs const& args) -{ - Vec2 pt = GetPoint(args); - - // Emulated touch, if left mouse button - if (args.CurrentPoint().Properties().IsLeftButtonPressed()) - { - intptr_t id = 0; - handleTouchesMove(1, &id, &pt.x, &pt.y); - } - - _currentMouseEvent.setMouseInfo(transformInputX(pt.x), transformInputY(pt.y), checkMouseButton(args), - EventMouse::MouseEventType::MOUSE_MOVE); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); -} - -void ax::RenderViewImpl::OnMouseReleased(Windows::UI::Core::PointerEventArgs const& args) -{ - Vec2 pt = GetPoint(args); - - // Emulated touch, if left mouse button - if (_lastMouseButtonPressed == EventMouse::MouseButton::BUTTON_LEFT) - { - intptr_t id = 0; - handleTouchesEnd(1, &id, &pt.x, &pt.y); - } - - _currentMouseEvent.setMouseInfo(transformInputX(pt.x), transformInputY(pt.y), _lastMouseButtonPressed, - EventMouse::MouseEventType::MOUSE_UP); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); - - _lastMouseButtonPressed = EventMouse::MouseButton::BUTTON_UNSET; -} - -void ax::RenderViewImpl::OnMouseWheelChanged(Windows::UI::Core::PointerEventArgs const& args) -{ - Vec2 pt = GetPoint(args); - - // Because OpenGL and axmol uses different Y axis, we need to convert the coordinate here - float delta = static_cast(args.CurrentPoint().Properties().MouseWheelDelta()); - if (args.CurrentPoint().Properties().IsHorizontalMouseWheel()) - { - _currentMouseEvent.setScrollData(delta / WHEEL_DELTA, 0.0f); - } - else - { - _currentMouseEvent.setScrollData(0.0f, -delta / WHEEL_DELTA); - } - _currentMouseEvent.setMouseInfo(transformInputX(pt.x), transformInputY(pt.y), checkMouseButton(args), - EventMouse::MouseEventType::MOUSE_SCROLL); - Director::getInstance()->getEventDispatcher()->dispatchEvent(&_currentMouseEvent); -} - -RenderViewImpl* RenderViewImpl::sharedRenderView() -{ - return s_renderView; -} - -int RenderViewImpl::Run() -{ - // XAML version does not have a run loop - m_running = true; - return 0; -}; - -void RenderViewImpl::Render() -{ - OnRendering(); -} - -void RenderViewImpl::OnRendering() -{ - if (m_running && m_initialized) - { - Director::getInstance()->renderFrame(); - } -} - -// called by orientation change from WP8 XAML -void RenderViewImpl::UpdateOrientation(Windows::Graphics::Display::DisplayOrientations orientation) -{ - if (m_orientation != orientation) - { - m_orientation = orientation; - handleWindowResized(); - } -} - -// called by size change from WP8 XAML -void RenderViewImpl::UpdateForWindowSizeChange(float width, float height) -{ - if (width != m_width || height != m_height) - { - m_width = width; - m_height = height; - handleWindowResized(); - } -} - -void RenderViewImpl::SetDPI(float dpi) -{ - bool inital = m_dpi == 0; - if (m_dpi != dpi) - { - m_dpi = dpi; - updateRenderScale(); - if (!inital) - { - updateRenderSurface(m_width * _renderScale, m_height * _renderScale, SurfaceUpdateFlag::RenderSizeChanged); - } - } -} - -void RenderViewImpl::handleWindowResized() -{ - updateRenderSurface(m_width, m_height, SurfaceUpdateFlag::WindowSizeChanged); - updateRenderSurface(m_width * _renderScale, m_height * _renderScale, SurfaceUpdateFlag::RenderSizeChanged); - - Size size(m_width, m_height); - Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(RenderViewImpl::EVENT_WINDOW_RESIZED, &size); -} - -void RenderViewImpl::updateRenderScale() -{ - if (!rhi::DriverContext::isOpenGL()) - _renderScale = Application::getContextAttrs().renderScaleMode == RenderScaleMode::Physical - ? (m_dpi > 0 ? m_dpi / 96.0f /* 96.0f: Standard DPI baseline */ : 1.0f) - : 1.0f; - else - _renderScale = 1.0f; -} - -// CoreWindow manage logic window size = physics size / dpiScale, -// _renderScale is input scale -ax::Vec2 RenderViewImpl::TransformToOrientation(Windows::Foundation::Point const& p) -{ - return Vec2{p.X * _renderScale, p.Y * _renderScale}; -} - -Vec2 RenderViewImpl::GetPoint(Windows::UI::Core::PointerEventArgs const& args) -{ - return TransformToOrientation(args.CurrentPoint().Position()); -} - -void RenderViewImpl::QueueBackKeyPress() -{ - mInputEvents.push(std::make_shared()); -} - -void RenderViewImpl::QueuePointerEvent(PointerEventType type, Windows::UI::Core::PointerEventArgs const& args) -{ - mInputEvents.push(std::make_shared(type, args)); -} - -void RenderViewImpl::QueueWinRTKeyboardEvent(WinRTKeyboardEventType type, Windows::UI::Core::KeyEventArgs const& args) -{ - auto e = std::make_shared(type, args); - mInputEvents.push(e); -} - -void RenderViewImpl::OnWinRTKeyboardEvent(WinRTKeyboardEventType type, Windows::UI::Core::KeyEventArgs const& args) -{ - m_keyboard.OnWinRTKeyboardEvent(type, args); -} - -void RenderViewImpl::QueueEvent(std::shared_ptr& event) -{ - mInputEvents.push(event); -} - -void RenderViewImpl::ProcessEvents() -{ - std::shared_ptr e; - while (mInputEvents.try_pop(e)) - { - e->execute(); - } -} - -void RenderViewImpl::SetQueueOperationCb(std::function cb) -{ - mQueueOperationCb = std::move(cb); -} - -void RenderViewImpl::queueOperation(AsyncOperation op, void* param) -{ - if (mQueueOperationCb) - mQueueOperationCb(std::move(op), param); -} - -} // namespace ax diff --git a/axmol/platform/winrt/RenderViewImpl-winrt.h b/axmol/platform/winrt/RenderViewImpl-winrt.h deleted file mode 100644 index 640d787e3eb9..000000000000 --- a/axmol/platform/winrt/RenderViewImpl-winrt.h +++ /dev/null @@ -1,211 +0,0 @@ -/**************************************************************************** -Copyright (c) 2010 cocos2d-x.org -Copyright (c) Microsoft Open Technologies, Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -#include "axmol/platform/winrt/StdC-winrt.h" -#include "axmol/platform/Common.h" -#include "axmol/platform/winrt/Keyboard-winrt.h" -#include "axmol/platform/RenderView.h" -#include "axmol/base/EventKeyboard.h" -#include "axmol/base/EventMouse.h" - -#include -#include -#include - -#include -#include - -using namespace winrt; - -class AxmolRenderer; - -namespace ax -{ - -class RenderViewImpl; - -class AX_DLL RenderViewImpl : public RenderView -{ - friend class ::AxmolRenderer; - -public: - static const std::string_view EVENT_WINDOW_RESIZED; - - static RenderViewImpl* create(std::string_view viewName); - static RenderViewImpl* createWithRect(std::string_view viewName, - const Rect& rect, - float zoomFactor = 1.0f, - bool resizable = false); - static RenderViewImpl* createWithFullscreen(std::string_view viewName); - - /* override functions */ - bool isGfxContextReady() override; - void end() override; - void swapBuffers() override; - - Windows::Graphics::Display::DisplayOrientations getDeviceOrientation() { return m_orientation; }; - Size getRenerTargetSize() const { return Size(m_width, m_height); } - - void setIMEKeyboardState(bool bOpen) override; - void setIMEKeyboardState(bool bOpen, std::string_view str); - - /** - * Hide or Show the mouse cursor if there is one. - * - * @param isVisible Hide or Show the mouse cursor if there is one. - */ - void setCursorVisible(bool isVisible) override; - - bool isCursorVisible() { return _isCursorVisible; } - - void setDispatcher(winrt::agile_ref dispatcher); - winrt::agile_ref getDispatcher() const { return m_dispatcher; } - - void setPanel(winrt::agile_ref panel); - winrt::agile_ref getPanel() { return m_panel; } - - void OnPointerPressed(Windows::UI::Core::PointerEventArgs const& args); - void OnPointerMoved(Windows::UI::Core::PointerEventArgs const& args); - void OnPointerReleased(Windows::UI::Core::PointerEventArgs const& args); - - void OnMousePressed(Windows::UI::Core::PointerEventArgs const& args); - void OnMouseMoved(Windows::UI::Core::PointerEventArgs const& args); - void OnMouseReleased(Windows::UI::Core::PointerEventArgs const& args); - void OnMouseWheelChanged(Windows::UI::Core::PointerEventArgs const& args); - - void OnWinRTKeyboardEvent(WinRTKeyboardEventType type, Windows::UI::Core::KeyEventArgs const& args); - - void OnPointerPressed(Windows::UI::Core::CoreWindow const& sender, Windows::UI::Core::PointerEventArgs const& args); - void OnPointerWheelChanged(Windows::UI::Core::CoreWindow const&, Windows::UI::Core::PointerEventArgs const& args); - void OnPointerMoved(Windows::UI::Core::CoreWindow const&, Windows::UI::Core::PointerEventArgs const& args); - void OnPointerReleased(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::PointerEventArgs const& args); - void OnVisibilityChanged(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::VisibilityChangedEventArgs const& args); - void OnWindowClosed(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::CoreWindowEventArgs const& args); - void OnResuming(Windows::Foundation::IInspectable const& sender); - void OnSuspending(Windows::Foundation::IInspectable const& sender, - Windows::ApplicationModel::SuspendingEventArgs const& args); - void OnBackKeyPress(); - bool AppShouldExit(); - void BackButtonListener(ax::EventKeyboard::KeyCode keyCode, ax::Event* event); - - void QueueBackKeyPress(); - void QueuePointerEvent(PointerEventType type, Windows::UI::Core::PointerEventArgs const& args); - void QueueWinRTKeyboardEvent(WinRTKeyboardEventType type, Windows::UI::Core::KeyEventArgs const& args); - void QueueEvent(std::shared_ptr& event); - - AlertResult ShowAlertDialog(const winrt::hstring& title, const winrt::hstring& message, AlertStyle style); - - int Run(); - void Render(); - - void UpdateOrientation(Windows::Graphics::Display::DisplayOrientations orientation); - void UpdateForWindowSizeChange(float width, float height); - - void SetDPI(float dpi); - float GetDPI() { return m_dpi; } - // static function - /** - @brief get the shared main open gl window - */ - static RenderViewImpl* sharedRenderView(); - - void ProcessEvents(); - - void queueOperation(AsyncOperation op, void* param) override; - - void SetQueueOperationCb(std::function cb); - - void* getNativeWindow() const override; - SurfaceHandle getNativeDisplay() const override; - WindowPlatform getWindowPlatform() const override { return WindowPlatform::CoreWindow; } - - float getRenderScale() const override { return _renderScale; } - -protected: - RenderViewImpl(); - ~RenderViewImpl() override; - - AX_DISALLOW_COPY_AND_ASSIGN(RenderViewImpl); - - bool initWithRect(std::string_view viewName, const Rect& rect, float frameZoomFactor); - bool initWithFullScreen(std::string_view viewName); - - static RenderViewImpl* s_renderView; - - bool _supportTouch; - bool _isCursorVisible; - - void OnRendering(); - - void handleWindowResized(); - void updateRenderScale(); - - ax::Vec2 TransformToOrientation(Windows::Foundation::Point const& point); - ax::Vec2 GetPoint(Windows::UI::Core::PointerEventArgs const& args); - - Windows::Foundation::Rect m_windowBounds{}; - winrt::event_token m_eventToken; - Windows::Foundation::Point m_lastPoint{}; - - EventMouse _currentMouseEvent; - - float _renderScale{1.0f}; - - float m_width; - float m_height; - float m_dpi; - Windows::Graphics::Display::DisplayOrientations m_orientation; - Windows::Foundation::Rect m_keyboardRect{}; - - bool m_lastPointValid; - bool m_windowClosed; - bool m_windowVisible; - // PointerReleased for mouse not send button id, need save in PointerPressed last button - EventMouse::MouseButton _lastMouseButtonPressed; - - bool m_running; - bool m_initialized; - bool m_appShouldExit; - - Concurrency::concurrent_queue> mInputEvents; - - std::function mQueueOperationCb; - - winrt::agile_ref m_dispatcher; - winrt::agile_ref m_panel; - - KeyBoardWinRT m_keyboard; - - ax::EventListenerKeyboard* m_backButtonListener; -}; - -} // namespace ax diff --git a/axmol/platform/winrt/xaml/AxmolRenderer.cpp b/axmol/platform/winrt/xaml/AxmolRenderer.cpp deleted file mode 100644 index 309ec66dbd1e..000000000000 --- a/axmol/platform/winrt/xaml/AxmolRenderer.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2010-2014 - cocos2d-x community - * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - * Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - * - * https://axmol.dev/ - * - * Portions Copyright (c) Microsoft Open Technologies, Inc. - * All Rights Reserved - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -#include "axmol/platform/winrt/xaml/AxmolRenderer.h" -#include "axmol/platform/winrt/RenderViewImpl-winrt.h" -#include "axmol/platform/Application.h" -#include "axmol/renderer/TextureCache.h" -#include "axmol/base/Director.h" -#include "axmol/base/EventType.h" -#include "axmol/base/EventCustom.h" -#include "axmol/base/EventDispatcher.h" -#include "axmol/rhi/DriverContext.h" - -// These are used by the shader compilation methods. -#include -#include -#include - -using namespace Windows::UI::Core; -using namespace Windows::UI::Xaml::Controls; -using namespace Windows::Graphics::Display; -using namespace ax; - -AxmolRenderer::AxmolRenderer(int width, - int height, - float dpi, - DisplayOrientations orientation, - CoreDispatcher const& dispatcher, - SwapChainPanel const& panel) - : m_width(width), m_height(height), m_dpi(dpi), m_dispatcher(dispatcher), m_panel(panel), m_orientation(orientation) -{} - -AxmolRenderer::~AxmolRenderer() {} - -void AxmolRenderer::Resume() -{ - auto director = ax::Director::getInstance(); - auto appInstance = Application::getInstance(); - - auto renderView = static_cast(director->getRenderView()); - if (!renderView) - { - renderView = RenderViewImpl::create("axmol3"); - renderView->setPanel(m_panel); - renderView->SetDPI(m_dpi); - renderView->UpdateOrientation(m_orientation); - renderView->UpdateForWindowSizeChange(m_width, m_height); - renderView->setDispatcher(m_dispatcher); - director->setRenderView(renderView); - appInstance->run(); - } - - appInstance->applicationWillEnterForeground(); - ax::EventCustom foregroundEvent(EVENT_COME_TO_FOREGROUND); - ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&foregroundEvent, true); -} - -void AxmolRenderer::Pause() -{ - if (Director::getInstance()->getRenderView()) - { - Application::getInstance()->applicationDidEnterBackground(); - ax::EventCustom backgroundEvent(EVENT_COME_TO_BACKGROUND); - ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&backgroundEvent, true); - } -} - -bool AxmolRenderer::AppShouldExit() -{ - return RenderViewImpl::sharedRenderView()->AppShouldExit(); -} - -void AxmolRenderer::DeviceLost() -{ - Pause(); - - auto director = ax::Director::getInstance(); - if (director->getRenderView()) - { - axdrv->resetState(); - ax::Director::getInstance()->resetMatrixStack(); - ax::EventCustom recreatedEvent(EVENT_RENDERER_RECREATED); - director->getEventDispatcher()->dispatchEvent(&recreatedEvent, true); - director->setRenderDefaults(); -#if AX_ENABLE_CONTEXT_LOSS_RECOVERY - ax::VolatileTextureMgr::reloadAllTextures(); -#endif - - Application::getInstance()->applicationWillEnterForeground(); - ax::EventCustom foregroundEvent(EVENT_COME_TO_FOREGROUND); - ax::Director::getInstance()->getEventDispatcher()->dispatchEvent(&foregroundEvent, true); - } -} - -void AxmolRenderer::SetQueueOperationCb(std::function cb) -{ - RenderViewImpl::sharedRenderView()->SetQueueOperationCb(std::move(cb)); -} - -void AxmolRenderer::Draw(size_t width, size_t height, float dpi, DisplayOrientations orientation) -{ - auto renderView = RenderViewImpl::sharedRenderView(); - - if (orientation != m_orientation) - { - m_orientation = orientation; - renderView->UpdateOrientation(orientation); - } - - if (width != m_width || height != m_height) - { - m_width = width; - m_height = height; - renderView->UpdateForWindowSizeChange(static_cast(width), static_cast(height)); - } - - if (dpi != m_dpi) - { - m_dpi = dpi; - renderView->SetDPI(m_dpi); - } - - renderView->ProcessEvents(); - renderView->Render(); -} - -void AxmolRenderer::QueuePointerEvent(ax::PointerEventType type, Windows::UI::Core::PointerEventArgs const& args) -{ - RenderViewImpl::sharedRenderView()->QueuePointerEvent(type, args); -} - -void AxmolRenderer::QueueBackButtonEvent() -{ - RenderViewImpl::sharedRenderView()->QueueBackKeyPress(); -} - -void AxmolRenderer::QueueKeyboardEvent(WinRTKeyboardEventType type, Windows::UI::Core::KeyEventArgs const& args) -{ - RenderViewImpl::sharedRenderView()->QueueWinRTKeyboardEvent(type, args); -} diff --git a/axmol/platform/winrt/xaml/AxmolRenderer.h b/axmol/platform/winrt/xaml/AxmolRenderer.h deleted file mode 100644 index db82a08343ef..000000000000 --- a/axmol/platform/winrt/xaml/AxmolRenderer.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2010-2014 - cocos2d-x community - * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - * Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - * - * https://axmol.dev/ - * - * Portions Copyright (c) Microsoft Open Technologies, Inc. - * All Rights Reserved - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -#pragma once - -#include "axmol/platform/winrt/InputEvent.h" -#include "axmol/base/Types.h" - -#include -#include -#include - -using namespace winrt; - -class AppDelegate; - -class AxmolRenderer -{ -public: - AxmolRenderer(int width, - int height, - float dpi, - Windows::Graphics::Display::DisplayOrientations orientation, - Windows::UI::Core::CoreDispatcher const& dispatcher, - Windows::UI::Xaml::Controls::SwapChainPanel const& panel); - AxmolRenderer(const AxmolRenderer&) = delete; - ~AxmolRenderer(); - void SetQueueOperationCb(std::function op); - void Draw(size_t width, size_t height, float dpi, Windows::Graphics::Display::DisplayOrientations orientation); - void QueuePointerEvent(ax::PointerEventType type, Windows::UI::Core::PointerEventArgs const& args); - void QueueKeyboardEvent(ax::WinRTKeyboardEventType type, Windows::UI::Core::KeyEventArgs const& args); - void QueueBackButtonEvent(); - - void Pause(); - void Resume(); - void DeviceLost(); - bool AppShouldExit(); - -private: - int m_width; - int m_height; - float m_dpi; - - winrt::agile_ref m_dispatcher; - winrt::agile_ref m_panel; - Windows::Graphics::Display::DisplayOrientations m_orientation; -}; diff --git a/axmol/platform/winrt/xaml/SwapChainPage.cpp b/axmol/platform/winrt/xaml/SwapChainPage.cpp index db1146b6ca0b..be3f5a8ec2f5 100644 --- a/axmol/platform/winrt/xaml/SwapChainPage.cpp +++ b/axmol/platform/winrt/xaml/SwapChainPage.cpp @@ -17,42 +17,20 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ + #include "axmol/platform/winrt/xaml/SwapChainPage.h" #include "SwapChainPage.g.cpp" #include "AppDelegate.h" -#include "axmol/platform/winrt/RenderViewImpl-winrt.h" #include "axmol/platform/Application.h" #include "axmol/rhi/DriverContext.h" -#include "yasio/wtimer_hres.hpp" - -#include -#include -#include -#include -#include +#include -using namespace ax; using namespace winrt; -using namespace Concurrency; using namespace Windows::Foundation; -using namespace Windows::Graphics::Display; -using namespace Windows::System::Threading; -using namespace Windows::UI::Core; -using namespace Windows::UI::Input; -using namespace Windows::UI::Input::Core; using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Controls; -using namespace Windows::UI::Xaml::Controls::Primitives; -using namespace Windows::UI::Xaml::Data; -using namespace Windows::UI::Xaml::Input; -using namespace Windows::UI::Xaml::Media; -using namespace Windows::UI::Xaml::Navigation; - -#if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) || _MSC_VER >= 1900 -using namespace Windows::UI::Input; -#endif namespace { @@ -62,12 +40,6 @@ std::unique_ptr appDelegate; namespace winrt::AxmolAppWinRT::implementation { SwapChainPage::SwapChainPage() - : m_coreInput(nullptr) - , m_dpi(0.0f) - , m_deviceLost(false) - , m_cursorVisible(true) - , m_visible(false) - , m_orientation(DisplayOrientations::Landscape) { appDelegate.reset(new AppDelegate()); ax::Application::getInstance()->initContextAttrs(); @@ -76,482 +48,28 @@ SwapChainPage::SwapChainPage() // If any of the high-performance APIs (D3D11/D3D12/Vulkan/Metal) are enabled, // the runtime will attempt initialization in the default priority order. // If all attempts fail, OpenGL will then be explicitly selected as the fallback. - rhi::DriverContext::makeCurrentDriver(); - m_fallbackGL = rhi::DriverContext::isOpenGL(); + ax::rhi::DriverContext::makeCurrentDriver(); -#if AX_ENABLE_GL - if (m_fallbackGL) - m_eglSurfaceProvider = new EGLSurfaceProvider(); -#endif InitializeComponent(); - Windows::UI::Core::CoreWindow window = Windows::UI::Xaml::Window::Current().CoreWindow(); - - window.VisibilityChanged({this, &SwapChainPage::OnVisibilityChanged}); - - window.KeyDown({this, &SwapChainPage::OnKeyPressed}); - - window.KeyUp({this, &SwapChainPage::OnKeyReleased}); - - window.CharacterReceived({this, &SwapChainPage::_OnCharacterReceived}); - - DisplayInformation currentDisplayInformation = DisplayInformation::GetForCurrentView(); - - currentDisplayInformation.OrientationChanged({this, &SwapChainPage::OnOrientationChanged}); - - m_orientation = currentDisplayInformation.CurrentOrientation(); - - this->Loaded({this, &SwapChainPage::OnPageLoaded}); - swapChainPanel().SizeChanged({this, &SwapChainPage::OnPanelSizeChanged}); - - // If we have a phone contract, hide the status bar - Window::Current().SetTitleBar(nullptr); - - if (Windows::Foundation::Metadata::ApiInformation::IsTypePresent(L"Windows.Phone.UI.Input.HardwareButtons")) - { - SystemNavigationManager::GetForCurrentView().BackRequested({this, &SwapChainPage::OnBackButtonPressed}); - } - - CreateInput(); -} - -void SwapChainPage::CreateInput() -{ - // Register our SwapChainPanel to get independent input pointer events - auto workItemHandler = ([this](IAsyncAction const&) { - // The CoreIndependentInputSource will raise pointer events for the specified device types on whichever thread - // it's created on. - m_coreInput = swapChainPanel().CreateCoreIndependentInputSource(Windows::UI::Core::CoreInputDeviceTypes::Mouse | - Windows::UI::Core::CoreInputDeviceTypes::Touch | - Windows::UI::Core::CoreInputDeviceTypes::Pen); - - // Register for pointer events, which will be raised on the background thread. - m_coreInput.PointerPressed({this, &SwapChainPage::_OnPointerPressed}); - m_coreInput.PointerMoved({this, &SwapChainPage::_OnPointerMoved}); - m_coreInput.PointerReleased({this, &SwapChainPage::_OnPointerReleased}); - m_coreInput.PointerWheelChanged({this, &SwapChainPage::_OnPointerWheelChanged}); - - if (RenderViewImpl::sharedRenderView() && !RenderViewImpl::sharedRenderView()->isCursorVisible()) - { - m_coreInput.PointerCursor(nullptr); - } - - // Begin processing input messages as they're delivered. - m_coreInput.Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit); - }); - - // Run task on a dedicated high priority background thread. - m_inputLoopWorker = ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::TimeSliced); + Loaded({this, &SwapChainPage::OnPageLoaded}); + Unloaded({this, &SwapChainPage::OnPageUnloaded}); } SwapChainPage::~SwapChainPage() { - StopRenderLoop(); - DestroyRenderSurface(); -} - -void SwapChainPage::OnPageLoaded(Windows::Foundation::IInspectable const& /*sender*/, - Windows::UI::Xaml::RoutedEventArgs const& e) -{ - // The SwapChainPanel has been created and arranged in the page layout, so we can start render. - CreateRenderSurface(); - StartRenderLoop(); - - m_visible = true; -} - -void SwapChainPage::OnPanelSizeChanged(Windows::Foundation::IInspectable const& /*sender*/, - Windows::UI::Xaml::RoutedEventArgs const& /*e*/) -{ - if (!m_updateScheduled) - { - m_updateScheduled = true; - swapChainPanel().Dispatcher().RunAsync(CoreDispatcherPriority::Low, [this]() { - m_updateScheduled = false; - UpdatePanelSize(); - }); - } -} - -void SwapChainPage::CreateRenderSurface() -{ - UpdatePanelSize(); -#if AX_ENABLE_GL - if (!m_fallbackGL) - return; - if (m_eglSurfaceProvider && m_eglSurface == EGL_NO_SURFACE) - { - // The app can configure the SwapChainPanel which may boost performance. - // By default, this template uses the default configuration. - m_eglSurface = m_eglSurfaceProvider->CreateSurface(swapChainPanel(), nullptr, nullptr); - - // You can configure the SwapChainPanel to render at a lower resolution and be scaled up to - // the swapchain panel size. This scaling is often free on mobile hardware. - // - // One way to configure the SwapChainPanel is to specify precisely which resolution it should render at. - // Size custom_eglSurfaceSize = Size(800, 600); - // m_eglSurface = m_eglSurfaceProvider->CreateSurface(swapChainPanel, &custom_eglSurfaceSize, nullptr); - // - // Another way is to tell the SwapChainPanel to render at a certain scale factor compared to its size. - // e.g. if the SwapChainPanel is 1920x1280 then setting a factor of 0.5f will make the app render at 960x640 - // float customResolutionScale = 0.5f; - // m_eglSurface = m_eglSurfaceProvider->CreateSurface(swapChainPanel, nullptr, &customResolutionScale); - // - } -#endif -} - -void SwapChainPage::UpdatePanelSize() -{ - auto panel = swapChainPanel(); - m_panelWidth = panel.ActualWidth(); - m_panelHeight = panel.ActualHeight(); -} - -void SwapChainPage::DestroyRenderSurface() -{ -#if AX_ENABLE_GL - if (!m_fallbackGL) - return; - if (m_eglSurfaceProvider) - { - m_eglSurfaceProvider->DestroySurface(m_eglSurface); - delete m_eglSurfaceProvider; - m_eglSurfaceProvider = nullptr; - } - - m_eglSurface = EGL_NO_SURFACE; -#endif -} - -void SwapChainPage::RecoverFromLostDevice() -{ -#if AX_ENABLE_GL - if (m_fallbackGL) - { - critical_section::scoped_lock lock(m_eglSurfaceCriticalSection); - DestroyRenderSurface(); - m_eglSurfaceProvider->Reset(); - CreateRenderSurface(); - } -#endif - - std::unique_lock locker(m_sleepMutex); - m_deviceLost = false; - m_sleepCondition.notify_one(); -} - -void SwapChainPage::TerminateApp() -{ -#if AX_ENABLE_GL - if (m_fallbackGL) - { - critical_section::scoped_lock lock(m_eglSurfaceCriticalSection); - - if (m_eglSurfaceProvider) - { - m_eglSurfaceProvider->DestroySurface(m_eglSurface); - m_eglSurfaceProvider->Cleanup(); - } - } -#endif - Windows::UI::Xaml::Application::Current().Exit(); -} - -void SwapChainPage::ProcessOperations() -{ - std::function op; - while (m_operations.try_pop(op)) - op(); -} - -void SwapChainPage::StartRenderLoop() -{ - // If the render loop is already running then do not start another thread. - if (m_renderLoopWorker != nullptr && m_renderLoopWorker.Status() == Windows::Foundation::AsyncStatus::Started) - { - return; - } - - DisplayInformation currentDisplayInformation = DisplayInformation::GetForCurrentView(); - m_dpi = currentDisplayInformation.LogicalDpi(); - - auto dispatcher = Windows::UI::Xaml::Window::Current().CoreWindow().Dispatcher(); - - // Create a task for rendering that will be run on a background thread. - auto renderFrame = ([this, dispatcher](Windows::Foundation::IAsyncAction const& action) { - if (!m_renderer) - { - m_renderer = std::make_shared(m_panelWidth, m_panelHeight, m_dpi, m_orientation, dispatcher, - swapChainPanel()); - } - -#if AX_ENABLE_GL - if (m_fallbackGL) - { - m_eglSurfaceProvider->MakeCurrent(m_eglSurface); - rhi::DriverContext::activateCurrentDriver(); - } -#endif - - // !!!Start the engine renderer on the render thread so that WICImageDecoder - // initializes COM in multi-threaded apartment (MTA) mode. - m_renderer->Resume(); - - void* thiz = (void*)this; - m_renderer->SetQueueOperationCb([thiz](ax::AsyncOperation op, void* param) { - auto thisUnsafe = reinterpret_cast(thiz); - thisUnsafe->m_operations.push([=]() { op(param); }); - thisUnsafe->m_sleepCondition.notify_one(); - }); - - // the actual render frame function - std::function frameFunc = [&]() { - if (!m_visible) - { - m_renderer->Pause(); - } - - // wait until app is visible again or thread is cancelled - while (!m_visible) - { - std::unique_lock lock(m_sleepMutex); - m_sleepCondition.wait(lock); - - if (action.Status() != Windows::Foundation::AsyncStatus::Started) - { - return false; // thread was cancelled. Exit thread - } - - if (m_visible) - { - m_renderer->Resume(); - } - else // spurious wake up - { - ProcessOperations(); - continue; - } - } - - ProcessOperations(); - - m_renderer->Draw(static_cast(m_panelWidth), static_cast(m_panelHeight), m_dpi, - m_orientation); - - // Recreate input dispatch - if (RenderViewImpl::sharedRenderView() && - m_cursorVisible != RenderViewImpl::sharedRenderView()->isCursorVisible()) - { - CreateInput(); - m_cursorVisible = RenderViewImpl::sharedRenderView()->isCursorVisible(); - } - - if (m_renderer->AppShouldExit()) - { - // run on main UI thread - auto thiz = this; - swapChainPanel().Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, - ([thiz]() { thiz->TerminateApp(); })); - - return false; - } - -#if AX_ENABLE_GL - if (rhi::DriverContext::isOpenGL()) - { - EGLBoolean result = GL_FALSE; - { - critical_section::scoped_lock lock(m_eglSurfaceCriticalSection); - result = m_eglSurfaceProvider->SwapBuffers(m_eglSurface); - } - - if (result != GL_TRUE) - { - // The call to eglSwapBuffers was not be successful (i.e. due to Device Lost) - // If the call fails, then we must reinitialize EGL and the GL resources. - m_renderer->Pause(); - m_deviceLost = true; - - // XAML objects like the SwapChainPanel must only be manipulated on the UI thread. - auto thiz = this; - swapChainPanel().Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, - ([thiz]() { thiz->RecoverFromLostDevice(); })); - - // wait until OpenGL is reset or thread is cancelled - while (m_deviceLost) - { - std::unique_lock lock(m_sleepMutex); - m_sleepCondition.wait(lock); - - if (action.Status() != Windows::Foundation::AsyncStatus::Started) - { - return false; // thread was cancelled. Exit thread - } - - if (!m_deviceLost) - { - m_eglSurfaceProvider->MakeCurrent(m_eglSurface); - m_renderer->DeviceLost(); - } - else // spurious wake up - { - continue; - } - } - } - } -#endif - - return true; - }; - - // Sets Sleep(aka NtDelayExecution) resolution to 1ms - yasio::wtimer_hres __timer_hres_man; - auto application = ax::Application::getInstance(); - while (action.Status() == Windows::Foundation::AsyncStatus::Started) - { - if (!application->frameStep(frameFunc)) - return; - } - }); - - // Run task on a dedicated high priority background thread. - m_renderLoopWorker = Windows::System::Threading::ThreadPool::RunAsync( - renderFrame, Windows::System::Threading::WorkItemPriority::High, - Windows::System::Threading::WorkItemOptions::TimeSliced); -} - -void SwapChainPage::StopRenderLoop() -{ - if (m_renderLoopWorker) - { - m_renderLoopWorker.Cancel(); - std::unique_lock locker(m_sleepMutex); - m_sleepCondition.notify_one(); - m_renderLoopWorker = nullptr; - } -} - -void SwapChainPage::_OnPointerPressed(Windows::Foundation::IInspectable const& sender, PointerEventArgs const& args) -{ - bool isMouseEvent = - args.CurrentPoint().PointerDevice().PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse; - if (m_renderer) - { - m_renderer->QueuePointerEvent(isMouseEvent ? PointerEventType::MousePressed : PointerEventType::PointerPressed, - args); - } -} - -void SwapChainPage::_OnPointerMoved(Windows::Foundation::IInspectable const& sender, PointerEventArgs const& args) -{ - bool isMouseEvent = - args.CurrentPoint().PointerDevice().PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse; - if (m_renderer) - { - m_renderer->QueuePointerEvent(isMouseEvent ? PointerEventType::MouseMoved : PointerEventType::PointerMoved, - args); - } -} - -void SwapChainPage::_OnPointerReleased(Windows::Foundation::IInspectable const& sender, PointerEventArgs const& args) -{ - bool isMouseEvent = - args.CurrentPoint().PointerDevice().PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse; - - if (m_renderer) - { - m_renderer->QueuePointerEvent( - isMouseEvent ? PointerEventType::MouseReleased : PointerEventType::PointerReleased, args); - } + ax::Application::getInstance()->shutdown(); } -void SwapChainPage::_OnPointerWheelChanged(Windows::Foundation::IInspectable const& /*sender*/, - PointerEventArgs const& args) +void SwapChainPage::OnPageLoaded(IInspectable const&, RoutedEventArgs const&) { - bool isMouseEvent = - args.CurrentPoint().PointerDevice().PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse; - if (m_renderer && isMouseEvent) - { - m_renderer->QueuePointerEvent(PointerEventType::MouseWheelChanged, args); - } + m_swapChainPanel = FindName(L"swapChainPanel").as(); + ax::Application::getInstance()->boot(m_swapChainPanel); } -void SwapChainPage::OnKeyPressed(CoreWindow const& sender, KeyEventArgs const& args) -{ - if (m_renderer) - { - m_renderer->QueueKeyboardEvent(WinRTKeyboardEventType::Down, args); - } -} - -void SwapChainPage::_OnCharacterReceived(CoreWindow const& /*sender*/, CharacterReceivedEventArgs const& /*args*/) {} - -void SwapChainPage::OnKeyReleased(CoreWindow const& /*sender*/, KeyEventArgs const& args) -{ - if (m_renderer) - { - m_renderer->QueueKeyboardEvent(WinRTKeyboardEventType::Up, args); - } -} - -void SwapChainPage::OnOrientationChanged(DisplayInformation const& sender, - Windows::Foundation::IInspectable const& /*args*/) -{ - m_orientation = sender.CurrentOrientation(); -} - -void SwapChainPage::SetVisibility(bool isVisible) -{ - if (isVisible) - { - if (!m_visible) - { - std::unique_lock locker(m_sleepMutex); - m_visible = true; - m_sleepCondition.notify_one(); - } - } - else - { - m_visible = false; - } -} - -void SwapChainPage::OnVisibilityChanged(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::VisibilityChangedEventArgs const& args) -{ - if (args.Visible()) - { - SetVisibility(true); - } - else - { - SetVisibility(false); - } -} - -#if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) || _MSC_VER >= 1900 -/* -We set args->Handled = true to prevent the app from quitting when the back button is pressed. -This is because this back button event happens on the XAML UI thread and not the axmol UI thread. -We need to give the game developer a chance to decide to exit the app depending on where they -are in their game. They can receive the back button event by listening for the -EventKeyboard::KeyCode::KEY_ESCAPE event. - -The default behavior is to exit the app if the EventKeyboard::KeyCode::KEY_ESCAPE event -is not handled by the game. -*/ -void SwapChainPage::OnBackButtonPressed(Windows::Foundation::IInspectable const& sender, - BackRequestedEventArgs const& args) +void SwapChainPage::OnPageUnloaded(IInspectable const&, RoutedEventArgs const&) { - if (m_renderer) - { - m_renderer->QueueBackButtonEvent(); - args.Handled(true); - } + ax::Application::getInstance()->shutdown(); } -#endif } // namespace winrt::AxmolAppWinRT::implementation diff --git a/axmol/platform/winrt/xaml/SwapChainPage.h b/axmol/platform/winrt/xaml/SwapChainPage.h index 2ded6c613f72..73ad37483a55 100644 --- a/axmol/platform/winrt/xaml/SwapChainPage.h +++ b/axmol/platform/winrt/xaml/SwapChainPage.h @@ -20,111 +20,26 @@ #pragma once -#include "SwapChainPage.g.h" - -#include -#include -#include -#include -#include - -#include "axmol/platform/winrt/xaml/AxmolRenderer.h" - -#include -#include #include -#include -#if AX_ENABLE_GL -# include "axmol/platform/winrt/xaml/EGLSurfaceProvider.h" -#endif +#include "SwapChainPage.g.h" -using namespace winrt; +#include +#include namespace winrt::AxmolAppWinRT::implementation { struct SwapChainPage : SwapChainPageT { -public: SwapChainPage(); ~SwapChainPage() override; - void SetVisibility(bool isVisible); - - void ProcessOperations(); - - void OnPageLoaded(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e); - void OnPanelSizeChanged(Windows::Foundation::IInspectable const& sender, - Windows::UI::Xaml::RoutedEventArgs const& e); - void OnVisibilityChanged(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::VisibilityChangedEventArgs const& args); -#if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) || _MSC_VER >= 1900 - void OnBackButtonPressed(Windows::Foundation::IInspectable const& sender, - Windows::UI::Core::BackRequestedEventArgs const& args); -#endif - void CreateRenderSurface(); - void DestroyRenderSurface(); - void RecoverFromLostDevice(); - void TerminateApp(); - void StartRenderLoop(); - void StopRenderLoop(); - - void CreateInput(); - -#if AX_ENABLE_GL - EGLSurfaceProvider* m_eglSurfaceProvider{nullptr}; - EGLSurface m_eglSurface{EGL_NO_SURFACE}; // This surface is associated with a swapChainPanel on the page - Concurrency::critical_section m_eglSurfaceCriticalSection{}; -#endif - std::shared_ptr m_renderer{}; - Windows::Foundation::IAsyncAction m_renderLoopWorker{}; - - // Track user input on a background worker thread. - Windows::Foundation::IAsyncAction m_inputLoopWorker{}; - Windows::UI::Core::CoreIndependentInputSource m_coreInput{nullptr}; - - // Independent touch and pen handling functions. - // !!!Note: cppwinrt generator will Xaml::RoutedEventArgs, so add underline prefix - void _OnPointerPressed(Windows::Foundation::IInspectable const& sender, - Windows::UI::Core::PointerEventArgs const& args); - void _OnPointerMoved(Windows::Foundation::IInspectable const& sender, - Windows::UI::Core::PointerEventArgs const& args); - void _OnPointerReleased(Windows::Foundation::IInspectable const& sender, - Windows::UI::Core::PointerEventArgs const& args); - void _OnPointerWheelChanged(Windows::Foundation::IInspectable const& sender, - Windows::UI::Core::PointerEventArgs const& args); - - // Independent keyboard handling functions. - void OnKeyPressed(Windows::UI::Core::CoreWindow const& sender, Windows::UI::Core::KeyEventArgs const& args); - void OnKeyReleased(Windows::UI::Core::CoreWindow const& sender, Windows::UI::Core::KeyEventArgs const& args); - - void _OnCharacterReceived(Windows::UI::Core::CoreWindow const& sender, - Windows::UI::Core::CharacterReceivedEventArgs const& args); - - void OnOrientationChanged(Windows::Graphics::Display::DisplayInformation const& sender, - Windows::Foundation::IInspectable const& args); - - float m_dpi; - bool m_deviceLost; - bool m_visible; - bool m_cursorVisible; - bool m_fallbackGL{false}; - - Windows::Graphics::Display::DisplayOrientations m_orientation; - - std::mutex m_sleepMutex; + void OnPageLoaded(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void OnPageUnloaded(Windows::Foundation::IInspectable const& sender, + Windows::UI::Xaml::RoutedEventArgs const& args); private: - // must call at UI thread - void UpdatePanelSize(); - - std::condition_variable m_sleepCondition; - - Concurrency::concurrent_queue> m_operations; - - double m_panelWidth{0}; - double m_panelHeight{0}; - bool m_updateScheduled{false}; + Windows::UI::Xaml::Controls::SwapChainPanel m_swapChainPanel{nullptr}; }; } // namespace winrt::AxmolAppWinRT::implementation diff --git a/axmol/renderer/MeshCommand.cpp b/axmol/renderer/MeshCommand.cpp index 09ec1df11915..f61bd1509947 100644 --- a/axmol/renderer/MeshCommand.cpp +++ b/axmol/renderer/MeshCommand.cpp @@ -27,8 +27,8 @@ #include "axmol/base/Macros.h" #include "axmol/base/Environment.h" #include "axmol/base/Director.h" -#include "axmol/base/EventCustom.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEvent.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/base/EventType.h" #include "axmol/2d/Light.h" @@ -53,7 +53,7 @@ MeshCommand::MeshCommand() _is3D = true; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY // listen the event that renderer was recreated on Android/WP8 - _rendererRecreatedListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, + _rendererRecreatedListener = CustomEventListener::create(EVENT_RENDERER_RECREATED, AX_CALLBACK_1(MeshCommand::listenRendererRecreated, this)); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_rendererRecreatedListener, -1); #endif @@ -82,7 +82,7 @@ MeshCommand::~MeshCommand() } #if AX_ENABLE_CONTEXT_LOSS_RECOVERY -void MeshCommand::listenRendererRecreated(EventCustom* event) {} +void MeshCommand::listenRendererRecreated(CustomEvent* event) {} #endif } // namespace ax diff --git a/axmol/renderer/MeshCommand.h b/axmol/renderer/MeshCommand.h index 89001e358a35..70e1c1312c83 100644 --- a/axmol/renderer/MeshCommand.h +++ b/axmol/renderer/MeshCommand.h @@ -36,8 +36,8 @@ namespace ax { -class EventListenerCustom; -class EventCustom; +class CustomEventListener; +class CustomEvent; class Material; // it is a common mesh @@ -74,12 +74,12 @@ class AX_DLL MeshCommand : public CustomCommand void init(float globalZOrder, const Mat4& transform); #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - void listenRendererRecreated(EventCustom* event); + void listenRendererRecreated(CustomEvent* event); #endif protected: #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _rendererRecreatedListener; + CustomEventListener* _rendererRecreatedListener; #endif }; diff --git a/axmol/renderer/Renderer.cpp b/axmol/renderer/Renderer.cpp index dd29f30d186b..9e50bb0bc794 100644 --- a/axmol/renderer/Renderer.cpp +++ b/axmol/renderer/Renderer.cpp @@ -40,7 +40,7 @@ #include "axmol/base/Environment.h" #include "axmol/base/Director.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventType.h" #include "axmol/scene/Camera.h" #include "axmol/scene/Scene.h" @@ -818,7 +818,7 @@ bool Renderer::checkVisibility(const Mat4& transform, const Vec2& size) float hSizeY = size.height / 2; Vec3 v3p(hSizeX, hSizeY, 0); transform.transformPoint(&v3p); - Vec2 v2p = Camera::getVisitingCamera()->projectGL(v3p); + Vec2 v2p = Camera::getVisitingCamera()->projectWorldToCanvas(v3p); // convert content size to world coordinates float wshw = std::max(fabsf(hSizeX * transform.m[0] + hSizeY * transform.m[4]), diff --git a/axmol/renderer/Renderer.h b/axmol/renderer/Renderer.h index d2985da3a949..36b121cfcf0d 100644 --- a/axmol/renderer/Renderer.h +++ b/axmol/renderer/Renderer.h @@ -58,7 +58,7 @@ class RenderTarget; struct PixelBufferDesc; } // namespace rhi -class EventListenerCustom; +class CustomEventListener; class TrianglesCommand; class MeshCommand; class GroupCommand; @@ -133,7 +133,7 @@ Whenever possible prefer to use `TrianglesCommand` objects since the renderer wi */ class AX_DLL Renderer { - friend class RenderView; + friend class RenderViewCore; public: /**The max number of vertices in a vertex buffer object.*/ diff --git a/axmol/renderer/TextureAtlas.cpp b/axmol/renderer/TextureAtlas.cpp index 6d1973366bcf..1fc73ea8ac9f 100644 --- a/axmol/renderer/TextureAtlas.cpp +++ b/axmol/renderer/TextureAtlas.cpp @@ -37,7 +37,7 @@ THE SOFTWARE. #include "axmol/base/Director.h" #include "axmol/base/Environment.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/renderer/TextureCache.h" #include "axmol/renderer/Renderer.h" #include "axmol/renderer/Texture2D.h" diff --git a/axmol/renderer/TextureAtlas.h b/axmol/renderer/TextureAtlas.h index 4a984979075d..6dd2ecc2bf1f 100644 --- a/axmol/renderer/TextureAtlas.h +++ b/axmol/renderer/TextureAtlas.h @@ -38,8 +38,8 @@ namespace ax { class Texture2D; -class EventCustom; -class EventListenerCustom; +class CustomEvent; +class CustomEventListener; /** * @addtogroup _2d @@ -239,7 +239,7 @@ class AX_DLL TextureAtlas : public Object V3F_T2F_C4B_Quad* _quads = nullptr; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _rendererRecreatedListener = nullptr; + CustomEventListener* _rendererRecreatedListener = nullptr; #endif }; diff --git a/axmol/rhi/DriverContext.cpp b/axmol/rhi/DriverContext.cpp index 03504f1ef9b1..d180873d15f7 100644 --- a/axmol/rhi/DriverContext.cpp +++ b/axmol/rhi/DriverContext.cpp @@ -1,5 +1,5 @@ #include "axmol/platform/PlatformMacros.h" -#include "axmol/platform/ApplicationBase.h" +#include "axmol/platform/ApplicationCore.h" #include "axmol/rhi/DriverContext.h" #include "axmol/rhi/DriverFactory.h" #include "axmol/tlx/inlined_vector.hpp" @@ -63,7 +63,7 @@ int DriverContext::getDriverPriority(DriverType driverType) void DriverContext::makeCurrentDriver() { - auto& contextAttrs = ApplicationBase::getContextAttrs(); + auto& contextAttrs = ApplicationCore::getContextAttrs(); tlx::inlined_vector, (int)DriverType::Count> factories; diff --git a/axmol/rhi/DriverContext.h b/axmol/rhi/DriverContext.h index 5e54edf82cf3..4cdfc5f11883 100644 --- a/axmol/rhi/DriverContext.h +++ b/axmol/rhi/DriverContext.h @@ -51,16 +51,17 @@ class AX_DLL DriverContext * to the next available backend (e.g. OpenGL ES). * * @note This call is optional. If not invoked, Vulkan will be considered - * on all devices that report support, regardless of OS version. + * only on devices running Android 12 (API level 31) or higher, + * which is the default minimum requirement. * * @warning To ensure the restriction takes effect, this function should * be invoked as early as possible (e.g. in the application - * delegate's constructor/initContextAttrs), before any rendering context or - * driver initialization occurs. + * delegate's constructor/initContextAttrs), before any rendering + * context or driver initialization occurs. * - * @param apiLevel The minimum Android API level (Default is 31 for Android 12) - * required to allow Vulkan usage. - * refers: + * @param apiLevel The minimum Android API level required to allow Vulkan usage. + * Default is 31 (Android 12). + * References: * - https://apilevels.com/ * - https://developer.android.com/tools/releases/platforms */ diff --git a/axmol/rhi/ProgramState.cpp b/axmol/rhi/ProgramState.cpp index d47d6e5a5756..f5b834b7bc9d 100644 --- a/axmol/rhi/ProgramState.cpp +++ b/axmol/rhi/ProgramState.cpp @@ -170,8 +170,8 @@ bool ProgramState::init(Program* program) } #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - _backToForegroundListener = EventListenerCustom::create( - EVENT_RENDERER_RECREATED, [this](EventCustom*) { this->remapTextureRuntimeLocations(); }); + _backToForegroundListener = CustomEventListener::create( + EVENT_RENDERER_RECREATED, [this](CustomEvent*) { this->remapTextureRuntimeLocations(); }); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, -1); #endif diff --git a/axmol/rhi/ProgramState.h b/axmol/rhi/ProgramState.h index 709b308775a3..b3b260518eca 100644 --- a/axmol/rhi/ProgramState.h +++ b/axmol/rhi/ProgramState.h @@ -33,7 +33,7 @@ #include #include "axmol/platform/PlatformMacros.h" #include "axmol/base/Object.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/rhi/RHITypes.h" #include "axmol/rhi/Program.h" #include "axmol/renderer/VertexLayoutManager.h" @@ -340,7 +340,7 @@ class AX_DLL ProgramState : public Object uint64_t _batchId = -1; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _backToForegroundListener{nullptr}; + CustomEventListener* _backToForegroundListener{nullptr}; #endif bool _isBatchable = false; diff --git a/axmol/rhi/opengl/BufferGL.cpp b/axmol/rhi/opengl/BufferGL.cpp index 2b5579a22ab8..3ecaba7090cb 100644 --- a/axmol/rhi/opengl/BufferGL.cpp +++ b/axmol/rhi/opengl/BufferGL.cpp @@ -59,7 +59,7 @@ BufferImpl::BufferImpl(std::size_t size, BufferType type, BufferUsage usage, con #if AX_ENABLE_CONTEXT_LOSS_RECOVERY _backToForegroundListener = - EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { this->reloadBuffer(); }); + CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { this->reloadBuffer(); }); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, -1); #endif } diff --git a/axmol/rhi/opengl/BufferGL.h b/axmol/rhi/opengl/BufferGL.h index b07924b9901b..9d43c01eae7b 100644 --- a/axmol/rhi/opengl/BufferGL.h +++ b/axmol/rhi/opengl/BufferGL.h @@ -26,7 +26,7 @@ #include "axmol/rhi/Buffer.h" #include "axmol/platform/GL.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include @@ -91,7 +91,7 @@ class BufferImpl : public Buffer void fillBuffer(const void* data, std::size_t offset, std::size_t size); bool _bufferAlreadyFilled = false; - EventListenerCustom* _backToForegroundListener = nullptr; + CustomEventListener* _backToForegroundListener = nullptr; #endif GLuint _buffer = 0; std::size_t _bufferAllocated = 0; diff --git a/axmol/rhi/opengl/ProgramGL.cpp b/axmol/rhi/opengl/ProgramGL.cpp index e7ad382c5765..02251a0a9ad8 100644 --- a/axmol/rhi/opengl/ProgramGL.cpp +++ b/axmol/rhi/opengl/ProgramGL.cpp @@ -43,7 +43,7 @@ ProgramImpl::ProgramImpl(Data& vsData, Data& fsData) : Program(vsData, fsData) compileProgram(); #if AX_ENABLE_CONTEXT_LOSS_RECOVERY _backToForegroundListener = - EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { this->reloadProgram(); }); + CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { this->reloadProgram(); }); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, -1); #endif } diff --git a/axmol/rhi/opengl/ProgramGL.h b/axmol/rhi/opengl/ProgramGL.h index 4696cad50f17..a9326efb919f 100644 --- a/axmol/rhi/opengl/ProgramGL.h +++ b/axmol/rhi/opengl/ProgramGL.h @@ -27,7 +27,7 @@ #include "axmol/rhi/RHITypes.h" #include "axmol/base/Object.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/platform/GL.h" #include "axmol/rhi/Program.h" #include "axmol/rhi/DriverContext.h" @@ -85,7 +85,7 @@ class ProgramImpl : public Program UniformBufferVector _uniformBuffers; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _backToForegroundListener = nullptr; + CustomEventListener* _backToForegroundListener = nullptr; #endif }; // end of _opengl group diff --git a/axmol/rhi/opengl/RenderContextGL.h b/axmol/rhi/opengl/RenderContextGL.h index deccc347c5a0..fdb7655248d7 100644 --- a/axmol/rhi/opengl/RenderContextGL.h +++ b/axmol/rhi/opengl/RenderContextGL.h @@ -27,7 +27,7 @@ #include "axmol/rhi/RHITypes.h" #include "axmol/rhi/RenderContext.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/platform/GL.h" #include "axmol/platform/StdC.h" @@ -195,7 +195,7 @@ class RenderContextImpl : public RenderContext GLboolean _alphaTestEnabled = false; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _backToForegroundListener = nullptr; + CustomEventListener* _backToForegroundListener = nullptr; #endif }; diff --git a/axmol/rhi/opengl/RenderTargetGL.cpp b/axmol/rhi/opengl/RenderTargetGL.cpp index 122e79f4b63a..9879e6682ed8 100644 --- a/axmol/rhi/opengl/RenderTargetGL.cpp +++ b/axmol/rhi/opengl/RenderTargetGL.cpp @@ -37,7 +37,7 @@ RenderTargetImpl::RenderTargetImpl(DriverImpl* driver, bool defaultRenderTarget) { glGenFramebuffers(1, &_FBO); #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - _rendererRecreatedListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { + _rendererRecreatedListener = CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { glGenFramebuffers(1, &_FBO); _dirtyFlags = TargetBufferFlags::ALL; }); diff --git a/axmol/rhi/opengl/RenderTargetGL.h b/axmol/rhi/opengl/RenderTargetGL.h index 82273c592f90..e9209e641977 100644 --- a/axmol/rhi/opengl/RenderTargetGL.h +++ b/axmol/rhi/opengl/RenderTargetGL.h @@ -28,7 +28,7 @@ class RenderTargetImpl : public RenderTarget GLuint _FBO = 0; tlx::pod_vector _GLbufs; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _rendererRecreatedListener{nullptr}; + CustomEventListener* _rendererRecreatedListener{nullptr}; #endif }; diff --git a/axmol/rhi/opengl/TextureGL.cpp b/axmol/rhi/opengl/TextureGL.cpp index 9eb074e0ecc9..72c961abc60f 100644 --- a/axmol/rhi/opengl/TextureGL.cpp +++ b/axmol/rhi/opengl/TextureGL.cpp @@ -24,7 +24,7 @@ ****************************************************************************/ #include "axmol/rhi/opengl/TextureGL.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/base/EventType.h" #include "axmol/base/Director.h" diff --git a/axmol/rhi/opengl/TextureGL.h b/axmol/rhi/opengl/TextureGL.h index 1f81e3bc4556..0f3bf5e62693 100644 --- a/axmol/rhi/opengl/TextureGL.h +++ b/axmol/rhi/opengl/TextureGL.h @@ -27,7 +27,7 @@ #include "axmol/rhi/Texture.h" #include "axmol/platform/GL.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/rhi/opengl/OpenGLState.h" diff --git a/axmol/rhi/vulkan/TextureVK.h b/axmol/rhi/vulkan/TextureVK.h index d83d8868a675..2afe980acdaf 100644 --- a/axmol/rhi/vulkan/TextureVK.h +++ b/axmol/rhi/vulkan/TextureVK.h @@ -24,7 +24,7 @@ #pragma once #include "axmol/rhi/Texture.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include #include diff --git a/axmol/scene/Camera.cpp b/axmol/scene/Camera.cpp index c4d86a70a3e5..f24c9aa8a0fb 100644 --- a/axmol/scene/Camera.cpp +++ b/axmol/scene/Camera.cpp @@ -103,6 +103,7 @@ Camera::Camera() { // minggo comment // _frustum.setClipZ(true); + _renderView = _director->getRenderView(); } Camera::~Camera() @@ -286,11 +287,14 @@ bool Camera::initOrthographic(float zoomX, float zoomY, float nearPlane, float f return true; } -Vec2 Camera::project(const Vec3& src) const +Vec2 Camera::projectWorldToScreen(const Vec3& src) const { Vec2 screenPos; - auto worldSize = _director->getCanvasSize(); + // 1. Fetch the full viewport rect which contains the physical origin (black bars offset) and size + auto& viewport = _renderView->getViewportRect(); + auto& vpSize = viewport.size; + Vec4 clipPos; getViewProjectionMatrix().transformVector(Vec4(src.x, src.y, src.z, 1.0f), &clipPos); @@ -298,82 +302,74 @@ Vec2 Camera::project(const Vec3& src) const float ndcX = clipPos.x / clipPos.w; float ndcY = clipPos.y / clipPos.w; - screenPos.x = (ndcX + 1.0f) * 0.5f * worldSize.width; - screenPos.y = (1.0f - (ndcY + 1.0f) * 0.5f) * worldSize.height; - return screenPos; -} - -Vec2 Camera::projectGL(const Vec3& src) const -{ - Vec2 screenPos; + // 2. Calculate the local coordinates relative to the active viewport area + float localX = (ndcX + 1.0f) * 0.5f * vpSize.width; + float localY = (1.0f - (ndcY + 1.0f) * 0.5f) * vpSize.height; - auto worldSize = _director->getCanvasSize(); - Vec4 clipPos; - getViewProjectionMatrix().transformVector(Vec4(src.x, src.y, src.z, 1.0f), &clipPos); + // 3. Counter stretching bars using uniform physical pixel metrics. + float renderHeight = _renderView->getRenderSize().height; + if (renderHeight == 0.0f) + renderHeight = vpSize.height; // Fallback container - if (clipPos.w == 0.0f) - AXLOGW("WARNING: Camera's clip position w is 0.0! a black screen should be expected."); + float viewportTopOffset = renderHeight - (viewport.origin.y + vpSize.height); - float ndcX = clipPos.x / clipPos.w; - float ndcY = clipPos.y / clipPos.w; + screenPos.x = localX + viewport.origin.x; + screenPos.y = localY + viewportTopOffset; - screenPos.x = (ndcX + 1.0f) * 0.5f * worldSize.width; - screenPos.y = (ndcY + 1.0f) * 0.5f * worldSize.height; return screenPos; } -Vec3 Camera::unproject(const Vec3& src) const +Vec3 Camera::deprojectScreenToWorld(const Vec3& src) const { - Vec3 dst; - unproject(_director->getCanvasSize(), &src, &dst); - return dst; -} + // 1. Fetch the full viewport rect to account for asymmetric window stretching bars + auto& viewport = _renderView->getViewportRect(); + auto& vpSize = viewport.size; -Vec3 Camera::unprojectGL(const Vec3& src) const -{ - Vec3 dst; - unprojectGL(_director->getCanvasSize(), &src, &dst); - return dst; -} + // 2. Counter stretching bars: Subtract the physical offset caused by black bars + float localX = src.x - viewport.origin.x; -void Camera::unproject(const Vec2& viewport, const Vec3* src, Vec3* dst) const -{ - AXASSERT(src && dst, "vec3 can not be null"); + // Convert Bottom-Left axmol viewport origin Y to Top-Left Window origin Y offset + float renderHeight = _renderView->getRenderSize().height; + if (renderHeight == 0.0f) + renderHeight = vpSize.height; // Fallback container + + float viewportTopOffset = renderHeight - (viewport.origin.y + vpSize.height); + float localY = src.y - viewportTopOffset; - Vec4 screen(src->x / viewport.width, ((viewport.height - src->y)) / viewport.height, src->z, 1.0f); - screen.x = screen.x * 2.0f - 1.0f; - screen.y = screen.y * 2.0f - 1.0f; - screen.z = screen.z * 2.0f - 1.0f; + // 3. Perform standard NDC mapping within the normalized viewport dimensions [0, 1] -> [-1, 1] + Vec4 result(localX / vpSize.width, (vpSize.height - localY) / vpSize.height, src.z, 1.0f); + result.x = result.x * 2.0f - 1.0f; + result.y = result.y * 2.0f - 1.0f; + result.z = result.z * 2.0f - 1.0f; - getViewProjectionMatrix().getInversed().transformVector(screen, &screen); - if (screen.w != 0.0f) + getViewProjectionMatrix().getInversed().transformVector(result, &result); + if (result.w != 0.0f) { - screen.x /= screen.w; - screen.y /= screen.w; - screen.z /= screen.w; + result.x /= result.w; + result.y /= result.w; + result.z /= result.w; } - dst->set(screen.x, screen.y, screen.z); + return Vec3{result.x, result.y, result.z}; } -void Camera::unprojectGL(const Vec2& viewport, const Vec3* src, Vec3* dst) const +Vec2 Camera::projectWorldToCanvas(const Vec3& src) const { - AXASSERT(src && dst, "vec3 can not be null"); + Vec2 screenPos; - Vec4 screen(src->x / viewport.width, src->y / viewport.height, src->z, 1.0f); - screen.x = screen.x * 2.0f - 1.0f; - screen.y = screen.y * 2.0f - 1.0f; - screen.z = screen.z * 2.0f - 1.0f; + auto&& canvasSize = _director->getCanvasSize(); + Vec4 clipPos; + getViewProjectionMatrix().transformVector(Vec4(src.x, src.y, src.z, 1.0f), &clipPos); - getViewProjectionMatrix().getInversed().transformVector(screen, &screen); - if (screen.w != 0.0f) - { - screen.x /= screen.w; - screen.y /= screen.w; - screen.z /= screen.w; - } + if (clipPos.w == 0.0f) + AXLOGW("WARNING: Camera's clip position w is 0.0! a black screen should be expected."); + + float ndcX = clipPos.x / clipPos.w; + float ndcY = clipPos.y / clipPos.w; - dst->set(screen.x, screen.y, screen.z); + screenPos.x = (ndcX + 1.0f) * 0.5f * canvasSize.width; + screenPos.y = (ndcY + 1.0f) * 0.5f * canvasSize.height; + return screenPos; } #if defined(AX_ENABLE_3D) @@ -562,4 +558,59 @@ bool Camera::isBrushValid() return _clearBrush != nullptr && _clearBrush->isValid(); } +#if defined(AX_ENABLE_3D) +Ray Camera::screenToRay(const Vec2& screenPoint) const +{ + Vec3 nearP = deprojectScreenToWorld(Vec3(screenPoint.x, screenPoint.y, 0.0f)); + Vec3 farP = deprojectScreenToWorld(Vec3(screenPoint.x, screenPoint.y, 1.0f)); + Vec3 dir = (farP - nearP); + dir.normalize(); + return Ray{nearP, dir}; +} +#endif + +bool Camera::isWorldPointInRect(const Vec2& pt, const Mat4& w2l, const Rect& rect, Vec3* p) const +{ + if (rect.size.width <= 0 || rect.size.height <= 0) + return false; + + // first, convert pt to near/far plane, get Pn and Pf + Vec3 Pn(pt.x, pt.y, -1), Pf(pt.x, pt.y, 1); + + // then convert Pn and Pf to node space + w2l.transformPoint(&Pn); + w2l.transformPoint(&Pf); + + // Pn and Pf define a line Q(t) = D + t * E which D = Pn + auto E = Pf - Pn; + + // second, get three points which define content plane + // these points define a plane P(u, w) = A + uB + wC + Vec3 A = Vec3(rect.origin.x, rect.origin.y, 0); + Vec3 B(rect.origin.x + rect.size.width, rect.origin.y, 0); + Vec3 C(rect.origin.x, rect.origin.y + rect.size.height, 0); + B = B - A; + C = C - A; + + // the line Q(t) intercept with plane P(u, w) + // calculate the intercept point P = Q(t) + // (BxC).A - (BxC).D + // t = ----------------- + // (BxC).E + Vec3 BxC; + Vec3::cross(B, C, &BxC); + auto BxCdotE = BxC.dot(E); + if (BxCdotE == 0) + { + return false; + } + auto t = (BxC.dot(A) - BxC.dot(Pn)) / BxCdotE; + Vec3 P = Pn + t * E; + if (p) + { + *p = P; + } + return rect.containsPoint(Vec2(P.x, P.y)); +} + } // namespace ax diff --git a/axmol/scene/Camera.h b/axmol/scene/Camera.h index f7827bc66089..4b9f85eacb97 100644 --- a/axmol/scene/Camera.h +++ b/axmol/scene/Camera.h @@ -31,6 +31,7 @@ THE SOFTWARE. #include "axmol/scene/Node.h" #if defined(AX_ENABLE_3D) # include "axmol/3d/Frustum.h" +# include "axmol/3d/Ray.h" #endif #include "axmol/renderer/QuadCommand.h" #include "axmol/renderer/CustomCommand.h" @@ -40,6 +41,7 @@ namespace ax { class Scene; +class RenderView; class CameraBackgroundBrush; /** @@ -140,59 +142,65 @@ class AX_DLL Camera : public Node /**get view projection matrix*/ const Mat4& getViewProjectionMatrix() const; - /* convert the specified point in 3D world-space coordinates into the screen-space coordinates. +#if defined(AX_ENABLE_3D) + /** + * @brief Converts a 2D screen point into a 3D ray in world space. + * + * This function serves as the core gateway for 3D raycast picking. It unprojects + * a 2D screen coordinate to the near plane (Z=0) and far plane (Z=1) in world space + * using the current camera's viewport, view matrix, and projection matrix to construct a 3D ray. + * + * @param screenPoint The 2D screen coordinate. Must comply with the new input system + * specification: origin at top-left, with the Y-axis increasing downwards. + * + * @return A Ray structure representing the constructed 3D ray. + * - Ray.origin: The starting point of the ray, located on the near clipping plane in world space. + * - Ray.direction: The normalized direction vector of the ray. + * + * @note The function automatically handles the Y-axis viewport coordinate conversion from + * the new system's top-left origin (Y-down) to the underlying graphics API's (OpenGL/Vulkan) + * bottom-left origin (Y-up). Callers do not need to manually flip the Y-axis. * - * Origin point at left top corner in screen-space. - * @param src The world-space position. - * @return The screen-space position. + * @see Director::screenToWorld */ - Vec2 project(const Vec3& src) const; + Ray screenToRay(const Vec2& screenPoint) const; +#endif - /* convert the specified point in 3D world-space coordinates into the GL-screen-space coordinates. + /** + * Convert the specified point in 3D world-space coordinates into the screen-space coordinates. + * + * The screen-space coordinate system has its origin point at the left top corner. + * This corresponds to the native platform/window input coordinates. * - * Origin point at left bottom corner in GL-screen-space. * @param src The 3D world-space position. - * @return The GL-screen-space position. + * @return The screen-space position (left-top origin). */ - Vec2 projectGL(const Vec3& src) const; + Vec2 projectWorldToScreen(const Vec3& src) const; /** * Convert the specified point of screen-space coordinate into the 3D world-space coordinate. * - * Origin point at left top corner in screen-space. - * @param src The screen-space position. - * @return The 3D world-space position. - */ - Vec3 unproject(const Vec3& src) const; - - /** - * Convert the specified point of GL-screen-space coordinate into the 3D world-space coordinate. + * The screen-space coordinate system has its origin point at the left top corner. * - * Origin point at left bottom corner in GL-screen-space. - * @param src The GL-screen-space position. + * @param src The screen-space position (left-top origin). * @return The 3D world-space position. */ - Vec3 unprojectGL(const Vec3& src) const; + Vec3 deprojectScreenToWorld(const Vec3& src) const; /** - * Convert the specified point of screen-space coordinate into the 3D world-space coordinate. + * @brief Converts a 3D world-space coordinate into the 2D legacy Canvas coordinate space. * - * Origin point at left top corner in screen-space. - * @param size The window size to use. - * @param src The screen-space position. - * @param dst The 3D world-space position. - */ - void unproject(const Vec2& size, const Vec3* src, Vec3* dst) const; - - /** - * Convert the specified point of GL-screen-space coordinate into the 3D world-space coordinate. + * This function maps a 3D position into the logical design resolution space used by the + * 2D UI hierarchy (e.g., Node, Widget, Sprite). The returned coordinate system has its + * origin (0, 0) at the **bottom-left corner** of the design resolution canvas. + * + * @note This replaces the legacy 'projectWorldToViewport' which was a misnomer, as it + * scales against Director::getCanvasSize() rather than the actual RHI physical viewport. * - * Origin point at left bottom corner in GL-screen-space. - * @param size The window size to use. - * @param src The GL-screen-space position. - * @param dst The 3D world-space position. + * @param src The 3D world-space position to be projected. + * @return The 2D logical canvas-space position (bottom-left origin). */ - void unprojectGL(const Vec2& size, const Vec3* src, Vec3* dst) const; + Vec2 projectWorldToCanvas(const Vec3& src) const; #if defined(AX_ENABLE_3D) /** @@ -344,10 +352,31 @@ class AX_DLL Camera : public Node bool initOrthographic(float zoomX, float zoomY, float nearPlane, float farPlane); void applyViewport(); + /** + * Checks whether a 2D world/canvas point hits a local content rectangle. + * + * The input point is interpreted as a point in world/canvas XY space, not + * native screen space. The function builds a line from (pt.x, pt.y, -1) to + * (pt.x, pt.y, 1), transforms it into node local space, intersects it with + * the local z = 0 plane, and checks whether the intersection lies inside rect. + * + * @param pt Point in 2D world/canvas coordinates. + * @param w2l World-to-local transform. + * @param rect Rectangle in local space. + * @param p Optional local-space intersection point. + */ + bool isWorldPointInRect(const Vec2& pt, const Mat4& w2l, const Rect& rect, Vec3* p) const; + bool isWorldPointInRect(const Vec2& pt, const Mat4& w2l, const Rect& rect) const + { + return isWorldPointInRect(pt, w2l, rect, nullptr); + } + protected: static Camera* _visitingCamera; static Viewport _defaultViewport; + RenderViewCore* _renderView{nullptr}; + //* Scene that owns this camera. Scene* _scene = nullptr; Mat4 _projection; diff --git a/axmol/scene/CameraBackgroundBrush.cpp b/axmol/scene/CameraBackgroundBrush.cpp index 2fd711c8b087..1a02b24f8211 100644 --- a/axmol/scene/CameraBackgroundBrush.cpp +++ b/axmol/scene/CameraBackgroundBrush.cpp @@ -37,8 +37,8 @@ #include "axmol/renderer/Shaders.h" #if AX_ENABLE_CONTEXT_LOSS_RECOVERY -# include "axmol/base/EventCustom.h" -# include "axmol/base/EventListenerCustom.h" +# include "axmol/base/CustomEvent.h" +# include "axmol/base/CustomEventListener.h" # include "axmol/base/EventType.h" # include "axmol/base/EventDispatcher.h" #endif @@ -94,7 +94,7 @@ CameraBackgroundDepthBrush::CameraBackgroundDepthBrush() { #if AX_ENABLE_CONTEXT_LOSS_RECOVERY _backToForegroundListener = - EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { initBuffer(); }); + CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { initBuffer(); }); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, -1); #endif } @@ -270,7 +270,7 @@ CameraBackgroundSkyBoxBrush::CameraBackgroundSkyBoxBrush() { #if AX_ENABLE_CONTEXT_LOSS_RECOVERY _backToForegroundListener = - EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom*) { initBuffer(); }); + CustomEventListener::create(EVENT_RENDERER_RECREATED, [this](CustomEvent*) { initBuffer(); }); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, -1); #endif } diff --git a/axmol/scene/CameraBackgroundBrush.h b/axmol/scene/CameraBackgroundBrush.h index 8790ed68a0d1..7af7e23a1a13 100644 --- a/axmol/scene/CameraBackgroundBrush.h +++ b/axmol/scene/CameraBackgroundBrush.h @@ -28,7 +28,7 @@ #include "axmol/base/Types.h" #include "axmol/base/Object.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/renderer/QuadCommand.h" #include "axmol/renderer/CustomCommand.h" #include "axmol/renderer/GroupCommand.h" @@ -170,7 +170,7 @@ class AX_DLL CameraBackgroundDepthBrush : public CameraBackgroundBrush protected: #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _backToForegroundListener; + CustomEventListener* _backToForegroundListener; #endif void initBuffer(); @@ -230,7 +230,7 @@ class AX_DLL CameraBackgroundColorBrush : public CameraBackgroundDepthBrush }; class TextureCube; -class EventListenerCustom; +class CustomEventListener; /** * Skybox brush clear buffer with a skybox @@ -299,7 +299,7 @@ class AX_DLL CameraBackgroundSkyBoxBrush : public CameraBackgroundBrush TextureCube* _texture; #if AX_ENABLE_CONTEXT_LOSS_RECOVERY - EventListenerCustom* _backToForegroundListener; + CustomEventListener* _backToForegroundListener; #endif private: diff --git a/axmol/scene/Node.cpp b/axmol/scene/Node.cpp index 6c108018ac0b..d9937d12f657 100644 --- a/axmol/scene/Node.cpp +++ b/axmol/scene/Node.cpp @@ -225,7 +225,7 @@ void Node::cleanup() // NOTE: Although it was correct that removing event listeners associated with current node in Node::cleanup. // But it broke the compatibility to the versions before v3.16 . // User code may call `node->removeFromParent(true)` which will trigger node's cleanup method, when the node - // is added to scene again, event listeners like EventListenerTouchOneByOne will be lost. + // is added to scene again, event listeners like PointerEventListener will be lost. // In fact, user's code should use `node->removeFromParent(false)` in order not to do a cleanup and just remove node // from its parent. For more discussion about why we revert this change is at // https://github.com/cocos2d/cocos2d-x/issues/18104. We need to consider more before we want to correct the old and @@ -803,6 +803,13 @@ Rect Node::getBoundingBox() const return RectApplyAffineTransform(rect, getNodeToParentAffineTransform()); } +Rect Node::getWorldBoundingBox() const +{ + auto& contentSize = getContentSize(); + Rect rect = Rect(0, 0, contentSize.width, contentSize.height); + return RectApplyTransform(rect, getNodeToWorldTransform()); +} + // MARK: Children logic // lazy allocs @@ -1920,15 +1927,15 @@ Vec2 Node::convertToScreenSpace(const Vec2& nodePoint) const return _director->worldToScreen(worldPoint); } -// convenience methods which take a Touch instead of Vec2 -Vec2 Node::convertTouchToNodeSpace(Touch* touch) const +// convenience methods which take a PointerEvent instead of Vec2 +Vec2 Node::convertPointerToNodeSpace(PointerEvent* event) const { - return this->convertToNodeSpace(touch->getLocation()); + return this->convertToNodeSpace(event->getLocation()); } -Vec2 Node::convertTouchToNodeSpaceAR(Touch* touch) const +Vec2 Node::convertPointerToNodeSpaceAR(PointerEvent* event) const { - Vec2 point = touch->getLocation(); + Vec2 point = event->getLocation(); return this->convertToNodeSpaceAR(point); } @@ -2187,54 +2194,6 @@ void Node::disableCascadeColor() } } -bool isScreenPointInRect(const Vec2& pt, const Camera* camera, const Mat4& w2l, const Rect& rect, Vec3* p) -{ - if (nullptr == camera || rect.size.width <= 0 || rect.size.height <= 0) - { - return false; - } - - // first, convert pt to near/far plane, get Pn and Pf - Vec3 Pn(pt.x, pt.y, -1), Pf(pt.x, pt.y, 1); - Pn = camera->unprojectGL(Pn); - Pf = camera->unprojectGL(Pf); - - // then convert Pn and Pf to node space - w2l.transformPoint(&Pn); - w2l.transformPoint(&Pf); - - // Pn and Pf define a line Q(t) = D + t * E which D = Pn - auto E = Pf - Pn; - - // second, get three points which define content plane - // these points define a plane P(u, w) = A + uB + wC - Vec3 A = Vec3(rect.origin.x, rect.origin.y, 0); - Vec3 B(rect.origin.x + rect.size.width, rect.origin.y, 0); - Vec3 C(rect.origin.x, rect.origin.y + rect.size.height, 0); - B = B - A; - C = C - A; - - // the line Q(t) intercept with plane P(u, w) - // calculate the intercept point P = Q(t) - // (BxC).A - (BxC).D - // t = ----------------- - // (BxC).E - Vec3 BxC; - Vec3::cross(B, C, &BxC); - auto BxCdotE = BxC.dot(E); - if (BxCdotE == 0) - { - return false; - } - auto t = (BxC.dot(A) - BxC.dot(Pn)) / BxCdotE; - Vec3 P = Pn + t * E; - if (p) - { - *p = P; - } - return rect.containsPoint(Vec2(P.x, P.y)); -} - void Node::applyMaskOnEnter(bool applyChildren) { _childFollowCameraMask = applyChildren; @@ -2305,4 +2264,15 @@ rhi::ProgramState* Node::getProgramState() const return _programState; } +bool Node::onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) +{ + if (!event || !camera || !isVisible()) + return false; + + Rect rect; + rect.size = getContentSize(); + + return camera->isWorldPointInRect(event->getLocation(), getWorldToNodeTransform(), rect, outHitPoint); +} + } // namespace ax diff --git a/axmol/scene/Node.h b/axmol/scene/Node.h index 8c49c925e280..3eb03061b20d 100644 --- a/axmol/scene/Node.h +++ b/axmol/scene/Node.h @@ -44,7 +44,7 @@ namespace ax { class GridBase; -class Touch; +class PointerEvent; class Action; class LabelProtocol; class Scheduler; @@ -57,6 +57,7 @@ class Renderer; class Director; class Material; class Camera; +class PointerEvent; class Rigidbody2D; namespace rhi @@ -79,6 +80,7 @@ enum }; class EventListener; +class EventDispatcher; typedef std::map NodeIndexerMap_t; @@ -114,6 +116,8 @@ Node and override `draw`. class AX_DLL Node : public Object { + friend class EventDispatcher; + public: /** Default tag used for all the nodes */ static const int INVALID_TAG = -1; @@ -1147,6 +1151,13 @@ class AX_DLL Node : public Object */ virtual Rect getBoundingBox() const; + /** + * Returns an AABB (axis-aligned bounding-box) in its world's coordinate system. + * + * @return An AABB (axis-aligned bounding-box) in its world's coordinate system + */ + virtual Rect getWorldBoundingBox() const; + /** Set event dispatcher for scene. * * @param dispatcher The event dispatcher of scene. @@ -1586,7 +1597,7 @@ class AX_DLL Node : public Object * @param touch A given touch. * @return A point in world space coordinates. */ - Vec2 convertTouchToNodeSpace(Touch* touch) const; + Vec2 convertPointerToNodeSpace(PointerEvent* event) const; /** * converts a Touch (world coordinates) into a local coordinate. This method is AR (Anchor Relative). @@ -1594,7 +1605,7 @@ class AX_DLL Node : public Object * @param touch A given touch. * @return A point in world space coordinates, anchor relative. */ - Vec2 convertTouchToNodeSpaceAR(Touch* touch) const; + Vec2 convertPointerToNodeSpaceAR(PointerEvent* event) const; /** * Gets position of node in world space. @@ -1907,6 +1918,42 @@ class AX_DLL Node : public Object void updateParentChildrenIndexer(int tag); void updateParentChildrenIndexer(std::string_view name); + /** + * Performs pointer hit testing for this node under the specified camera. + * + * This function is used by EventDispatcher before dispatching pointer events + * to scene-graph-priority PointerEventListener instances. The listener will + * receive the pointer event only if this function returns true for one of the + * candidate cameras. + * + * The default implementation is expected to test the node's local content + * rectangle against the pointer's 2D world/canvas position, usually from + * PointerEvent::getLocation(). + * + * Derived classes may override this function to provide custom hit testing, + * such as clipping-aware UI hit testing, non-rectangular 2D hit areas, terrain + * picking, mesh picking, or physics ray casting. + * + * For 3D picking, implementations should typically use + * PointerEvent::getScreenLocation() together with Camera::screenToRay(). + * + * @param event The pointer event being tested. + * @param camera The candidate camera used for this hit test. + * @param outHitPoint Optional output parameter for the hit point. When provided, + * ``` + implementations should store the hit point in this node's + ``` + * ``` + local coordinate space. Pass nullptr if the hit point is + ``` + * ``` + not needed. + ``` + * + * @return true if the pointer hits this node for the specified camera, false otherwise. + */ + virtual bool onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint); + private: void addChildHelper(Node* child, int localZOrder, int tag, std::string_view name, bool setTag); @@ -2051,23 +2098,6 @@ inline _Ty* Component::getComponent() const return _owner ? _owner->template getComponent<_Ty>() : nullptr; } -/** - * This is a helper function, checks a GL screen point is in content rectangle space. - * - * The content rectangle defined by origin(0,0) and content size. - * This function convert GL screen point to near and far planes as points Pn and Pf, - * then calculate the intersect point P which the line PnPf intersect with content rectangle. - * If P in content rectangle means this node be hit. - * - * @param pt The point in GL screen space. - * @param camera Which camera used to unproject pt to near/far planes. - * @param w2l World to local transform matrix, used to convert Pn and Pf to rectangle space. - * @param rect The test rectangle in local space. - * @parma p Point to a Vec3 for store the intersect point, if don't need them set to nullptr. - * @return true if the point is in content rectangle, false otherwise. - */ -bool AX_DLL isScreenPointInRect(const Vec2& pt, const Camera* camera, const Mat4& w2l, const Rect& rect, Vec3* p); - // end of _2d group /// @} diff --git a/axmol/scene/Scene.cpp b/axmol/scene/Scene.cpp index 12dc27959f04..2c5ac653c752 100644 --- a/axmol/scene/Scene.cpp +++ b/axmol/scene/Scene.cpp @@ -31,7 +31,7 @@ THE SOFTWARE. #include "axmol/base/Director.h" #include "axmol/scene/Camera.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventListenerCustom.h" +#include "axmol/base/CustomEventListener.h" #include "axmol/base/text_utils.h" #include "axmol/renderer/Renderer.h" @@ -161,7 +161,7 @@ std::string Scene::getDescription() const return fmt::format("", _tag); } -void Scene::onProjectionChanged(EventCustom* /*event*/) +void Scene::onProjectionChanged(CustomEvent* /*event*/) { if (_defaultCamera) { diff --git a/axmol/scene/Scene.h b/axmol/scene/Scene.h index 03dca8caf2c0..ed111e04e0d8 100644 --- a/axmol/scene/Scene.h +++ b/axmol/scene/Scene.h @@ -39,8 +39,8 @@ class Director; class Camera; class BaseLight; class Renderer; -class EventListenerCustom; -class EventCustom; +class CustomEventListener; +class CustomEvent; #if defined(AX_ENABLE_PHYSICS_2D) class PhysicsWorld2D; #endif @@ -176,7 +176,7 @@ class AX_DLL Scene : public Node private: void initDefaultCamera(); - void onProjectionChanged(EventCustom* event); + void onProjectionChanged(CustomEvent* event); protected: void tick(float delta); @@ -207,7 +207,7 @@ class AX_DLL Scene : public Node bool _fixedUpdateEnabled{true}; - EventListenerCustom* _event; + CustomEventListener* _event; std::vector _lights; diff --git a/axmol/tlx/inlined_vector.hpp b/axmol/tlx/inlined_vector.hpp index bb6ea06d5bf0..d3a731299a4e 100644 --- a/axmol/tlx/inlined_vector.hpp +++ b/axmol/tlx/inlined_vector.hpp @@ -21,6 +21,7 @@ ****************************************************************************/ #pragma once #include +#include #include "yasio/tlx/memory.hpp" namespace tlx @@ -95,6 +96,7 @@ class inlined_vector if (this != &other) { clear(); + _Tidy(); _Assign(other); } return *this; @@ -256,6 +258,57 @@ class inlined_vector --_Mylast; } + iterator erase(const_iterator _Where) + { + auto& _My_data = _Mypair._Myval2; + + _TLX_VERIFY(_Where >= cbegin() && _Where < cend(), "inlined_vector erase iterator out of range"); + + const auto _Off = static_cast(_Where - cbegin()); + pointer _Erase_pos = _My_data._Myfirst + _Off; + + return erase(_Erase_pos, _Erase_pos + 1); + } + + iterator erase(const_iterator _First, const_iterator _Last) + { + auto& _My_data = _Mypair._Myval2; + + _TLX_VERIFY(_First >= cbegin() && _First <= cend(), "inlined_vector erase iterator out of range"); + _TLX_VERIFY(_Last >= cbegin() && _Last <= cend(), "inlined_vector erase iterator out of range"); + _TLX_VERIFY(_First <= _Last, "inlined_vector erase invalid range"); + + const auto _First_off = static_cast(_First - cbegin()); + const auto _Last_off = static_cast(_Last - cbegin()); + + pointer _Erase_first = _My_data._Myfirst + _First_off; + pointer _Erase_last = _My_data._Myfirst + _Last_off; + + if (_Erase_first == _Erase_last) + return _Erase_first; + + pointer _New_last = _Erase_first; + + if (_Erase_last != _My_data._Mylast) + { + if constexpr (std::is_trivially_copyable_v<_Ty>) + { + const auto _Move_count = static_cast(_My_data._Mylast - _Erase_last); + ::memmove(_Erase_first, _Erase_last, _Move_count * sizeof(_Ty)); + _New_last = _Erase_first + _Move_count; + } + else + { + _New_last = std::move(_Erase_last, _My_data._Mylast, _Erase_first); + } + } + + _TLX destroy_range(_New_last, _My_data._Mylast, _Getal()); + _My_data._Mylast = _New_last; + + return _Erase_first; + } + void swap(inlined_vector& other) { if (this == std::addressof(other)) diff --git a/axmol/tlx/static_vector.hpp b/axmol/tlx/static_vector.hpp new file mode 100644 index 000000000000..470dece84c94 --- /dev/null +++ b/axmol/tlx/static_vector.hpp @@ -0,0 +1,255 @@ +/**************************************************************************** + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + + https://axmol.dev/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tlx +{ + +template +class static_vector +{ +public: + static_assert(_Capacity > 0, "static_vector capacity must be > 0"); + + using value_type = _Ty; + using size_type = std::size_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = value_type*; + using const_iterator = const value_type*; + + constexpr static_vector() noexcept : _size(0) {} + static_vector(const static_vector& other) + { + if constexpr (std::is_trivially_copyable_v<_Ty>) + { + _size = other._size; + ::memcpy(data(), other.data(), _size * sizeof(_Ty)); + } + else + { + _size = 0; + for (size_type i = 0; i < other._size; ++i) + emplace_back(other[i]); + } + } + + static_vector(static_vector&& other) + { + if constexpr (std::is_trivially_copyable_v<_Ty>) + { + _size = other._size; + ::memcpy(data(), other.data(), _size * sizeof(_Ty)); + other._size = 0; + } + else + { + _size = 0; + for (size_type i = 0; i < other._size; ++i) + emplace_back(std::move(other[i])); + other.clear(); + } + } + + static_vector& operator=(const static_vector& other) + { + if (this != &other) + { + clear(); + if constexpr (std::is_trivially_copyable_v<_Ty>) + { + _size = other._size; + ::memcpy(data(), other.data(), _size * sizeof(_Ty)); + } + else + { + for (size_type i = 0; i < other._size; ++i) + emplace_back(other[i]); + } + } + return *this; + } + + static_vector& operator=(static_vector&& other) + { + if (this != &other) + { + clear(); + if constexpr (std::is_trivially_copyable_v<_Ty>) + { + _size = other._size; + ::memcpy(data(), other.data(), _size * sizeof(_Ty)); + + other._size = 0; + } + else + { + for (size_type i = 0; i < other._size; ++i) + emplace_back(std::move(other[i])); + other.clear(); + } + } + return *this; + } + ~static_vector() noexcept { clear(); } + constexpr size_type capacity() const noexcept { return _Capacity; } + constexpr size_type size() const noexcept { return _size; } + constexpr bool empty() const noexcept { return _size == 0; } + pointer data() noexcept { return reinterpret_cast(_buffer); } + const_pointer data() const noexcept { return reinterpret_cast(_buffer); } + + reference operator[](size_type idx) { return *element_at(idx); } + const_reference operator[](size_type idx) const { return *element_at(idx); } + + reference at(size_type idx) + { + if (idx >= _size) + throw std::out_of_range("static_vector::at"); + return *element_at(idx); + } + + const_reference at(size_type idx) const + { + if (idx >= _size) + throw std::out_of_range("static_vector::at"); + return *element_at(idx); + } + + iterator begin() noexcept { return data(); } + iterator end() noexcept { return data() + _size; } + const_iterator begin() const noexcept { return data(); } + const_iterator end() const noexcept { return data() + _size; } + const_iterator cbegin() const noexcept { return data(); } + const_iterator cend() const noexcept { return data() + _size; } + + void clear() noexcept + { + if constexpr (!std::is_trivially_destructible_v<_Ty>) + { + for (size_type i = _size; i > 0; --i) + std::destroy_at(data() + i - 1); + } + _size = 0; + } + + template + _Ty& emplace_back(Args&&... args) + { + if (_size >= _Capacity) + throw std::length_error("static_vector capacity exceeded"); + _Ty* obj = ::new (static_cast(data() + _size)) _Ty(std::forward(args)...); + ++_size; + return *obj; + } + + void push_back(const _Ty& v) { emplace_back(v); } + void push_back(_Ty&& v) { emplace_back(std::move(v)); } + + void pop_back() + { + if (_size == 0) + return; + if constexpr (!std::is_trivially_destructible_v<_Ty>) + std::destroy_at(data() + _size - 1); + --_size; + } + + void resize(size_type newSize) + { + if (newSize > _Capacity) + throw std::length_error("static_vector capacity exceeded"); + if constexpr (std::is_trivially_copyable_v<_Ty> && std::is_trivially_default_constructible_v<_Ty>) + { + if (_size < newSize) + std::memset(data() + _size, 0, (newSize - _size) * sizeof(_Ty)); + _size = newSize; + } + else + { + while (_size < newSize) + emplace_back(); + while (_size > newSize) + pop_back(); + } + } + + void swap(static_vector& other) + { + if constexpr (std::is_trivially_copyable_v<_Ty>) + { + const size_type minsz = std::min(_size, other._size); + for (size_type i = 0; i < minsz; ++i) + std::swap(data()[i], other.data()[i]); + if (_size > other._size) + ::memcpy(other.data() + minsz, data() + minsz, (_size - other._size) * sizeof(_Ty)); + else if (other._size > _size) + ::memcpy(data() + minsz, other.data() + minsz, (other._size - _size) * sizeof(_Ty)); + std::swap(_size, other._size); + } + else + { + const size_type minsz = std::min(_size, other._size); + for (size_type i = 0; i < minsz; ++i) + std::swap((*this)[i], other[i]); + if (_size > other._size) + { + for (size_type i = minsz; i < _size; ++i) + other.emplace_back(std::move((*this)[i])); + for (size_type i = _size; i > other._size; --i) + pop_back(); + } + else if (other._size > _size) + { + for (size_type i = minsz; i < other._size; ++i) + emplace_back(std::move(other[i])); + for (size_type i = other._size; i > _size; --i) + other.pop_back(); + } + } + } + +protected: + pointer element_at(size_type idx) noexcept + { + return std::launder(reinterpret_cast(_buffer + sizeof(_Ty) * idx)); + } + + const_pointer element_at(size_type idx) const noexcept + { + return std::launder(reinterpret_cast(_buffer + sizeof(_Ty) * idx)); + } + + alignas(alignof(_Ty)) unsigned char _buffer[sizeof(_Ty) * _Capacity]; + size_type _size; +}; + +} // namespace tlx diff --git a/axmol/ui/UIAbstractCheckButton.cpp b/axmol/ui/AbstractCheckButton.cpp similarity index 97% rename from axmol/ui/UIAbstractCheckButton.cpp rename to axmol/ui/AbstractCheckButton.cpp index f0b84d36cdc8..13513ee0d6ee 100644 --- a/axmol/ui/UIAbstractCheckButton.cpp +++ b/axmol/ui/AbstractCheckButton.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIAbstractCheckButton.h" +#include "axmol/ui/AbstractCheckButton.h" #include "axmol/2d/Sprite.h" #include "axmol/renderer/Shaders.h" #include "axmol/renderer/ProgramStateRegistry.h" @@ -70,7 +70,7 @@ AbstractCheckButton::AbstractCheckButton() , _backGroundDisabledFileName("") , _frontCrossDisabledFileName("") { - setTouchEnabled(true); + setPointerEnabled(true); } AbstractCheckButton::~AbstractCheckButton() {} @@ -107,7 +107,7 @@ bool AbstractCheckButton::init() return false; } -void AbstractCheckButton::initRenderer() +void AbstractCheckButton::initRenderNode() { _backGroundBoxRenderer = Sprite::create(); _backGroundSelectedBoxRenderer = Sprite::create(); @@ -161,7 +161,7 @@ void AbstractCheckButton::setupBackgroundTexture() this->updateChildrenDisplayedRGBA(); - updateContentSizeWithTextureSize(_backGroundBoxRenderer->getContentSize()); + updateContentSize(); _backGroundBoxRendererAdaptDirty = true; } @@ -405,7 +405,7 @@ void AbstractCheckButton::onSizeChanged() _frontCrossDisabledRendererAdaptDirty = true; } -void AbstractCheckButton::adaptRenderers() +void AbstractCheckButton::updateLayout() { if (_backGroundBoxRendererAdaptDirty) { @@ -434,19 +434,19 @@ void AbstractCheckButton::adaptRenderers() } } -Vec2 AbstractCheckButton::getVirtualRendererSize() const +Vec2 AbstractCheckButton::resolvePreferredSize(const Vec2& /*sizeHint*/) const { return _backGroundBoxRenderer->getContentSize(); } -Node* AbstractCheckButton::getVirtualRenderer() +Node* AbstractCheckButton::getRenderNode() { return _backGroundBoxRenderer; } void AbstractCheckButton::backGroundTextureScaleChangedWithSize() { - if (_ignoreSize) + if (_autoSize) { _backGroundBoxRenderer->setScale(1.0f); _backgroundTextureScaleX = _backgroundTextureScaleY = 1.0f; @@ -472,7 +472,7 @@ void AbstractCheckButton::backGroundTextureScaleChangedWithSize() void AbstractCheckButton::backGroundSelectedTextureScaleChangedWithSize() { - if (_ignoreSize) + if (_autoSize) { _backGroundSelectedBoxRenderer->setScale(1.0f); } @@ -494,7 +494,7 @@ void AbstractCheckButton::backGroundSelectedTextureScaleChangedWithSize() void AbstractCheckButton::frontCrossTextureScaleChangedWithSize() { - if (_ignoreSize) + if (_autoSize) { _frontCrossRenderer->setScale(1.0f); } @@ -516,7 +516,7 @@ void AbstractCheckButton::frontCrossTextureScaleChangedWithSize() void AbstractCheckButton::backGroundDisabledTextureScaleChangedWithSize() { - if (_ignoreSize) + if (_autoSize) { _backGroundBoxDisabledRenderer->setScale(1.0f); } @@ -538,7 +538,7 @@ void AbstractCheckButton::backGroundDisabledTextureScaleChangedWithSize() void AbstractCheckButton::frontCrossDisabledTextureScaleChangedWithSize() { - if (_ignoreSize) + if (_autoSize) { _frontCrossDisabledRenderer->setScale(1.0f); } diff --git a/axmol/ui/UIAbstractCheckButton.h b/axmol/ui/AbstractCheckButton.h similarity index 97% rename from axmol/ui/UIAbstractCheckButton.h rename to axmol/ui/AbstractCheckButton.h index 0ad318290b70..105758fda4c0 100644 --- a/axmol/ui/UIAbstractCheckButton.h +++ b/axmol/ui/AbstractCheckButton.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" /** @@ -123,8 +123,8 @@ class AX_GUI_DLL AbstractCheckButton : public Widget void setSelected(bool selected); // override functions - Vec2 getVirtualRendererSize() const override; - Node* getVirtualRenderer() override; + Vec2 resolvePreferredSize(const Vec2& sizeHint) const override; + Node* getRenderNode() override; /** When user pressed the CheckBox, the button will zoom to a scale. * The final scale of the CheckBox equals (CheckBox original scale + _zoomScale) @@ -195,7 +195,7 @@ class AX_GUI_DLL AbstractCheckButton : public Widget */ virtual ~AbstractCheckButton(); - void initRenderer() override; + void initRenderNode() override; void onPressStateChangedToNormal() override; void onPressStateChangedToPressed() override; void onPressStateChangedToDisabled() override; @@ -222,7 +222,7 @@ class AX_GUI_DLL AbstractCheckButton : public Widget void frontCrossDisabledTextureScaleChangedWithSize(); void copySpecialProperties(Widget* model) override; - void adaptRenderers() override; + void updateLayout() override; protected: Sprite* _backGroundBoxRenderer; diff --git a/axmol/ui/UIButton.cpp b/axmol/ui/Button.cpp similarity index 92% rename from axmol/ui/UIButton.cpp rename to axmol/ui/Button.cpp index 7a433e2cbe0f..417effe1e3fc 100644 --- a/axmol/ui/UIButton.cpp +++ b/axmol/ui/Button.cpp @@ -24,8 +24,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIButton.h" -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/Button.h" +#include "axmol/ui/Scale9Sprite.h" #include "axmol/2d/Label.h" #include "axmol/2d/Sprite.h" #include "axmol/2d/ActionInterval.h" @@ -53,7 +53,7 @@ Button::Button() , _buttonDisabledRenderer(nullptr) , _titleRenderer(nullptr) , _zoomScale(0.1f) - , _prevIgnoreSize(true) + , _prevAutoSize(true) , _scale9Enabled(false) , _pressedActionEnabled(false) , _capInsetsNormal(Rect::ZERO) @@ -72,7 +72,7 @@ Button::Button() , _pressedTexType(TextureResType::LOCAL) , _disabledTexType(TextureResType::LOCAL) { - setTouchEnabled(true); + setPointerEnabled(true); } Button::~Button() @@ -133,7 +133,7 @@ bool Button::init() return false; } -void Button::initRenderer() +void Button::initRenderNode() { _buttonNormalRenderer = Scale9Sprite::create(); _buttonClickedRenderer = Scale9Sprite::create(); @@ -213,13 +213,13 @@ void Button::setScale9Enabled(bool able) if (_scale9Enabled) { - bool ignoreBefore = _ignoreSize; - ignoreContentAdaptWithSize(false); - _prevIgnoreSize = ignoreBefore; + bool autoSizeBefore = _autoSize; + setAutoSize(false); + _prevAutoSize = autoSizeBefore; } else { - ignoreContentAdaptWithSize(_prevIgnoreSize); + setAutoSize(_prevAutoSize); } setCapInsetsNormalRenderer(_capInsetsNormal); @@ -239,18 +239,12 @@ bool Button::isScale9Enabled() const return _scale9Enabled; } -void Button::ignoreContentAdaptWithSize(bool ignore) +void Button::setAutoSize(bool autoSize) { - if (_unifySize) + if (!_scale9Enabled || (_scale9Enabled && !autoSize)) { - this->updateContentSize(); - return; - } - - if (!_scale9Enabled || (_scale9Enabled && !ignore)) - { - Widget::ignoreContentAdaptWithSize(ignore); - _prevIgnoreSize = ignore; + Widget::setAutoSize(autoSize); + _prevAutoSize = autoSize; } } @@ -289,7 +283,7 @@ void Button::loadTextureNormal(std::string_view normal, TextureResType texType) } } // FIXME: https://github.com/cocos2d/cocos2d-x/issues/12249 - if (!_ignoreSize && _customSize.equals(Vec2::ZERO)) + if (!_autoSize && _customSize.equals(Vec2::ZERO)) { _customSize = _buttonNormalRenderer->getContentSize(); } @@ -302,17 +296,8 @@ void Button::setupNormalTexture(bool textureLoaded) this->updateChildrenDisplayedRGBA(); - if (_unifySize) - { - if (!_scale9Enabled) - { - updateContentSizeWithTextureSize(this->getNormalSize()); - } - } - else - { - updateContentSizeWithTextureSize(_normalTextureSize); - } + updateContentSize(); + _normalTextureLoaded = textureLoaded; _normalTextureAdaptDirty = true; } @@ -491,16 +476,8 @@ void Button::onPressStateChangedToNormal() if (nullptr != _titleRenderer) { _titleRenderer->stopAllActions(); - if (_unifySize) - { - Action* zoomTitleAction = ScaleTo::create(ZOOM_ACTION_TIME_STEP, 1.0f, 1.0f); - _titleRenderer->runAction(zoomTitleAction); - } - else - { - _titleRenderer->setScaleX(1.0f); - _titleRenderer->setScaleY(1.0f); - } + _titleRenderer->setScaleX(1.0f); + _titleRenderer->setScaleY(1.0f); } } } @@ -592,25 +569,14 @@ void Button::updateTitleLocation() void Button::updateContentSize() { - if (_unifySize) + if (!_autoSize) { - if (_scale9Enabled) - { - ProtectedNode::setContentSize(_customSize); - } - else - { - Vec2 s = getNormalSize(); - ProtectedNode::setContentSize(s); - } + ProtectedNode::setContentSize(_customSize); onSizeChanged(); return; } - if (_ignoreSize) - { - this->setContentSize(getVirtualRendererSize()); - } + this->setContentSize(resolvePreferredSize(_customSize)); } void Button::onSizeChanged() @@ -625,7 +591,7 @@ void Button::onSizeChanged() _disabledTextureAdaptDirty = true; } -void Button::adaptRenderers() +void Button::updateLayout() { if (_normalTextureAdaptDirty) { @@ -646,13 +612,8 @@ void Button::adaptRenderers() } } -Vec2 Button::getVirtualRendererSize() const +Vec2 Button::resolvePreferredSize(const Vec2& /*sizeHint*/) const { - if (_unifySize) - { - return this->getNormalSize(); - } - if (nullptr != _titleRenderer) { Vec2 titleSize = _titleRenderer->getContentSize(); @@ -664,7 +625,7 @@ Vec2 Button::getVirtualRendererSize() const return _normalTextureSize; } -Node* Button::getVirtualRenderer() +Node* Button::getRenderNode() { if (_bright) { @@ -686,21 +647,21 @@ Node* Button::getVirtualRenderer() void Button::normalTextureScaleChangedWithSize() { - _buttonNormalRenderer->setPreferredSize(_contentSize); + _buttonNormalRenderer->setContentSize(_contentSize); _buttonNormalRenderer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); } void Button::pressedTextureScaleChangedWithSize() { - _buttonClickedRenderer->setPreferredSize(_contentSize); + _buttonClickedRenderer->setContentSize(_contentSize); _buttonClickedRenderer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); } void Button::disabledTextureScaleChangedWithSize() { - _buttonDisabledRenderer->setPreferredSize(_contentSize); + _buttonDisabledRenderer->setContentSize(_contentSize); _buttonDisabledRenderer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); } @@ -880,7 +841,7 @@ void Button::copySpecialProperties(Widget* widget) Button* button = dynamic_cast(widget); if (button) { - _prevIgnoreSize = button->_prevIgnoreSize; + _prevAutoSize = button->_prevAutoSize; setScale9Enabled(button->_scale9Enabled); // clone the inner sprite: https://github.com/cocos2d/cocos2d-x/issues/16924 diff --git a/axmol/ui/UIButton.h b/axmol/ui/Button.h similarity index 97% rename from axmol/ui/UIButton.h rename to axmol/ui/Button.h index b1be7b33662f..2ed926faaf9b 100644 --- a/axmol/ui/UIButton.h +++ b/axmol/ui/Button.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" /** @@ -190,9 +190,9 @@ class AX_GUI_DLL Button : public Widget void setPressedActionEnabled(bool enabled); // override methods - void ignoreContentAdaptWithSize(bool ignore) override; - Vec2 getVirtualRendererSize() const override; - Node* getVirtualRenderer() override; + void setAutoSize(bool autoSize) override; + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; + Node* getRenderNode() override; std::string getDescription() const override; /** @@ -322,7 +322,7 @@ class AX_GUI_DLL Button : public Widget virtual Vec2 getNormalTextureSize() const; protected: - void initRenderer() override; + void initRenderNode() override; void onPressStateChangedToNormal() override; void onPressStateChangedToPressed() override; void onPressStateChangedToDisabled() override; @@ -339,9 +339,9 @@ class AX_GUI_DLL Button : public Widget void pressedTextureScaleChangedWithSize(); void disabledTextureScaleChangedWithSize(); - void adaptRenderers() override; + void updateLayout() override; virtual void updateTitleLocation(); - void updateContentSize(); + void updateContentSize() override; virtual void createTitleRenderer(); bool createTitleRendererIfNull(); @@ -357,7 +357,7 @@ class AX_GUI_DLL Button : public Widget Label* _titleRenderer; float _zoomScale; - bool _prevIgnoreSize; + bool _prevAutoSize; bool _scale9Enabled; bool _pressedActionEnabled; diff --git a/axmol/ui/CMakeLists.txt b/axmol/ui/CMakeLists.txt index 869b1fafefc6..f8ce5baaae42 100644 --- a/axmol/ui/CMakeLists.txt +++ b/axmol/ui/CMakeLists.txt @@ -1,119 +1,119 @@ if(WINDOWS) if(NOT WINRT) set(_AX_UI_SPECIFIC_HEADER - ui/UIEditBox/UIEditBoxImpl-win32.h + ui/EditBox/EditBoxImpl-win32.h ) set(_AX_UI_SPECIFIC_SRC - ui/UIEditBox/UIEditBoxImpl-win32.cpp + ui/EditBox/EditBoxImpl-win32.cpp ) else() set(_AX_UI_SPECIFIC_HEADER - ui/UIEditBox/UIEditBoxImpl-winrt.h + ui/EditBox/EditBoxImpl-winrt.h ) set(_AX_UI_SPECIFIC_SRC - ui/UIEditBox/UIEditBoxImpl-winrt.cpp + ui/EditBox/EditBoxImpl-winrt.cpp ) endif() if(AX_ENABLE_MSEDGE_WEBVIEW2) - list(APPEND _AX_UI_SPECIFIC_HEADER ui/UIWebView/UIWebViewImpl-win32.h ui/UIWebView/UIWebView.h) - list(APPEND _AX_UI_SPECIFIC_SRC ui/UIWebView/UIWebViewImpl-win32.cpp ui/UIWebView/UIWebView.cpp) + list(APPEND _AX_UI_SPECIFIC_HEADER ui/WebView/WebViewImpl-win32.h ui/WebView/WebView.h) + list(APPEND _AX_UI_SPECIFIC_SRC ui/WebView/WebViewImpl-win32.cpp ui/WebView/WebView.cpp) endif() elseif(APPLE) if(MACOSX) - set(_AX_UI_SPECIFIC_HEADER - ui/UIEditBox/UIEditBoxImpl-mac.h - ui/UIEditBox/Mac/UIPasswordTextField.h - ui/UIEditBox/Mac/UIMultilineTextField.h - ui/UIEditBox/Mac/UITextInput.h - ui/UIEditBox/Mac/UIEditBoxMac.h - ui/UIEditBox/Mac/UISingleLineTextField.h - ui/UIEditBox/Mac/UITextFieldFormatter.h - ) + set(_AX_UI_SPECIFIC_HEADER + ui/EditBox/EditBoxImpl-mac.h + ui/EditBox/Mac/PasswordTextField.h + ui/EditBox/Mac/MultilineTextField.h + ui/EditBox/Mac/TextInput.h + ui/EditBox/Mac/EditBoxMac.h + ui/EditBox/Mac/SingleLineTextField.h + ui/EditBox/Mac/TextFieldFormatter.h + ) set(_AX_UI_SPECIFIC_SRC - ui/UIEditBox/UIEditBoxImpl-mac.mm - ui/UIEditBox/Mac/UIEditBoxMac.mm - ui/UIEditBox/Mac/UIMultilineTextField.m - ui/UIEditBox/Mac/UIPasswordTextField.m - ui/UIEditBox/Mac/UISingleLineTextField.m - ui/UIEditBox/Mac/UITextFieldFormatter.m + ui/EditBox/EditBoxImpl-mac.mm + ui/EditBox/Mac/EditBoxMac.mm + ui/EditBox/Mac/MultilineTextField.m + ui/EditBox/Mac/PasswordTextField.m + ui/EditBox/Mac/SingleLineTextField.m + ui/EditBox/Mac/TextFieldFormatter.m ) elseif(IOS) if(TVOS) set(_AX_UI_SPECIFIC_HEADER - ui/UIEditBox/UIEditBoxImpl-ios.h - ui/UIEditBox/iOS/UIEditBoxIOS.h - ui/UIEditBox/iOS/UIMultilineTextField.h - ui/UIEditBox/iOS/UITextInput.h - ui/UIEditBox/iOS/UITextView+UITextInput.h - ui/UIEditBox/iOS/UITextField+UITextInput.h - ui/UIEditBox/iOS/UISingleLineTextField.h + ui/EditBox/EditBoxImpl-ios.h + ui/EditBox/iOS/EditBoxIOS.h + ui/EditBox/iOS/MultilineTextField.h + ui/EditBox/iOS/TextInput.h + ui/EditBox/iOS/TextView.h + ui/EditBox/iOS/TextField.h + ui/EditBox/iOS/SingleLineTextField.h ) set(_AX_UI_SPECIFIC_SRC - ui/UIEditBox/UIEditBoxImpl-ios.mm - ui/UIEditBox/iOS/UIEditBoxIOS.mm - ui/UIEditBox/iOS/UIMultilineTextField.mm - ui/UIEditBox/iOS/UISingleLineTextField.mm - ui/UIEditBox/iOS/UITextField+UITextInput.mm - ui/UIEditBox/iOS/UITextView+UITextInput.mm + ui/EditBox/EditBoxImpl-ios.mm + ui/EditBox/iOS/EditBoxIOS.mm + ui/EditBox/iOS/MultilineTextField.mm + ui/EditBox/iOS/SingleLineTextField.mm + ui/EditBox/iOS/TextField.mm + ui/EditBox/iOS/TextView.mm ) else() set(_AX_UI_SPECIFIC_HEADER - ui/UIWebView/UIWebView.h - ui/UIWebView/UIWebViewImpl-ios.h - ui/UIEditBox/UIEditBoxImpl-ios.h - ui/UIEditBox/iOS/UIEditBoxIOS.h - ui/UIEditBox/iOS/UIMultilineTextField.h - ui/UIEditBox/iOS/UITextInput.h - ui/UIEditBox/iOS/UITextView+UITextInput.h - ui/UIEditBox/iOS/UITextField+UITextInput.h - ui/UIEditBox/iOS/UISingleLineTextField.h + ui/WebView/WebView.h + ui/WebView/WebViewImpl-ios.h + ui/EditBox/EditBoxImpl-ios.h + ui/EditBox/iOS/EditBoxIOS.h + ui/EditBox/iOS/MultilineTextField.h + ui/EditBox/iOS/TextInput.h + ui/EditBox/iOS/TextView.h + ui/EditBox/iOS/TextField.h + ui/EditBox/iOS/SingleLineTextField.h ) set(_AX_UI_SPECIFIC_SRC - ui/UIWebView/UIWebView.mm - ui/UIWebView/UIWebViewImpl-ios.mm - ui/UIEditBox/UIEditBoxImpl-ios.mm - ui/UIEditBox/iOS/UIEditBoxIOS.mm - ui/UIEditBox/iOS/UIMultilineTextField.mm - ui/UIEditBox/iOS/UISingleLineTextField.mm - ui/UIEditBox/iOS/UITextField+UITextInput.mm - ui/UIEditBox/iOS/UITextView+UITextInput.mm + ui/WebView/WebView.mm + ui/WebView/WebViewImpl-ios.mm + ui/EditBox/EditBoxImpl-ios.mm + ui/EditBox/iOS/EditBoxIOS.mm + ui/EditBox/iOS/MultilineTextField.mm + ui/EditBox/iOS/SingleLineTextField.mm + ui/EditBox/iOS/TextField.mm + ui/EditBox/iOS/TextView.mm ) endif() endif() elseif(LINUX) set(_AX_UI_SPECIFIC_HEADER - ui/UIEditBox/UIEditBoxImpl-linux.h - ui/UIWebView/UIWebView.h - ui/UIWebView/UIWebViewImpl-linux.h + ui/EditBox/EditBoxImpl-linux.h + ui/WebView/WebView.h + ui/WebView/WebViewImpl-linux.h ) set(_AX_UI_SPECIFIC_SRC - ui/UIEditBox/UIEditBoxImpl-linux.cpp - ui/UIWebView/UIWebViewImpl-linux.cpp - ui/UIWebView/UIWebView.cpp + ui/EditBox/EditBoxImpl-linux.cpp + ui/WebView/WebViewImpl-linux.cpp + ui/WebView/WebView.cpp ) elseif(EMSCRIPTEN) set(_AX_UI_SPECIFIC_SRC - ui/UIEditBox/UIEditBoxImpl-wasm.cpp + ui/EditBox/EditBoxImpl-wasm.cpp ) elseif(ANDROID) set(_AX_UI_SPECIFIC_HEADER - ui/UIWebView/UIWebView.h - ui/UIWebView/UIWebViewImpl-android.h - ui/UIEditBox/UIEditBoxImpl-android.h + ui/WebView/WebView.h + ui/WebView/WebViewImpl-android.h + ui/EditBox/EditBoxImpl-android.h ) set(_AX_UI_SPECIFIC_SRC - ui/UIEditBox/UIEditBoxImpl-android.cpp - ui/UIWebView/UIWebViewImpl-android.cpp + ui/EditBox/EditBoxImpl-android.cpp + ui/WebView/WebViewImpl-android.cpp # it's special for android, not a common file - ui/UIWebView/UIWebView.cpp + ui/WebView/WebView.cpp ) endif() -if(AX_ENABLE_MEDIA) - set(_AX_UI_SPECIFIC_HEADER ui/UIMediaPlayer.h ${_AX_UI_SPECIFIC_HEADER}) - set(_AX_UI_SPECIFIC_SRC ui/UIMediaPlayer.cpp ${_AX_UI_SPECIFIC_SRC}) +if(AX_ENABLE_VIDEO) + set(_AX_UI_SPECIFIC_HEADER ui/VideoPlayer.h ${_AX_UI_SPECIFIC_HEADER}) + set(_AX_UI_SPECIFIC_SRC ui/VideoPlayer.cpp ${_AX_UI_SPECIFIC_SRC}) endif() set(_AX_UI_HEADER @@ -121,69 +121,69 @@ set(_AX_UI_HEADER ui/axmol-ui.h ui/GUIDefine.h ui/GUIExport.h - ui/UIAbstractCheckButton.h - ui/UIButton.h - ui/UICheckBox.h - ui/UIHBox.h + ui/AbstractCheckButton.h + ui/Button.h + ui/CheckBox.h + ui/HBox.h ui/UIHelper.h - ui/UIImageView.h - ui/UILayout.h - ui/UILayoutComponent.h - ui/UILayoutManager.h - ui/UILayoutParameter.h - ui/UIListView.h - ui/UILoadingBar.h - ui/UIPageView.h - ui/UIPageViewIndicator.h - ui/UIRadioButton.h - ui/UIRelativeBox.h - ui/UIRichText.h - ui/UIScale9Sprite.h - ui/UIScrollView.h - ui/UIScrollViewBar.h - ui/UISlider.h - ui/UITabControl.h - ui/UIText.h - ui/UITextAtlas.h - ui/UITextBMFont.h - ui/UITextField.h - ui/UITextFieldEx.h - ui/UIVBox.h - ui/UIWidget.h + ui/ImageView.h + ui/LayoutGroup.h + ui/LayoutComponent.h + ui/LayoutManager.h + ui/LayoutParameter.h + ui/ListView.h + ui/LoadingBar.h + ui/PageView.h + ui/PageViewIndicator.h + ui/RadioButton.h + ui/RelativeBox.h + ui/RichText.h + ui/Scale9Sprite.h + ui/ScrollView.h + ui/ScrollViewBar.h + ui/Slider.h + ui/TabView.h + ui/Text.h + ui/TextAtlas.h + ui/TextBMFont.h + ui/InputField.h + ui/VBox.h + ui/Widget.h + ui/EditBox/EditBox.h + ui/EditBox/EditBoxImpl-common.h ) set(_AX_UI_SRC ${_AX_UI_SPECIFIC_SRC} ui/axmol-ui.cpp - ui/UIButton.cpp - ui/UIAbstractCheckButton.cpp - ui/UICheckBox.cpp - ui/UIRadioButton.cpp - ui/UIHBox.cpp + ui/Button.cpp + ui/AbstractCheckButton.cpp + ui/CheckBox.cpp + ui/RadioButton.cpp + ui/HBox.cpp ui/UIHelper.cpp - ui/UIImageView.cpp - ui/UILayout.cpp - ui/UILayoutManager.cpp - ui/UILayoutParameter.cpp - ui/UIListView.cpp - ui/UILoadingBar.cpp - ui/UIPageView.cpp - ui/UIPageViewIndicator.cpp - ui/UIRelativeBox.cpp - ui/UIRichText.cpp - ui/UIScale9Sprite.cpp - ui/UIScrollView.cpp - ui/UIScrollViewBar.cpp - ui/UISlider.cpp - ui/UIText.cpp - ui/UITextAtlas.cpp - ui/UITextBMFont.cpp - ui/UITextField.cpp - ui/UIVBox.cpp - ui/UIWidget.cpp - ui/UIEditBox/UIEditBox.cpp - ui/UILayoutComponent.cpp - ui/UIEditBox/UIEditBoxImpl-common.cpp - ui/UITabControl.cpp - ui/UITextFieldEx.cpp + ui/ImageView.cpp + ui/LayoutGroup.cpp + ui/LayoutManager.cpp + ui/LayoutParameter.cpp + ui/ListView.cpp + ui/LoadingBar.cpp + ui/PageView.cpp + ui/PageViewIndicator.cpp + ui/RelativeBox.cpp + ui/RichText.cpp + ui/Scale9Sprite.cpp + ui/ScrollView.cpp + ui/ScrollViewBar.cpp + ui/Slider.cpp + ui/Text.cpp + ui/TextAtlas.cpp + ui/TextBMFont.cpp + ui/InputField.cpp + ui/VBox.cpp + ui/Widget.cpp + ui/EditBox/EditBox.cpp + ui/LayoutComponent.cpp + ui/EditBox/EditBoxImpl-common.cpp + ui/TabView.cpp ) diff --git a/axmol/ui/UICheckBox.cpp b/axmol/ui/CheckBox.cpp similarity index 81% rename from axmol/ui/UICheckBox.cpp rename to axmol/ui/CheckBox.cpp index de2fd3c9230c..4a3573ca2cbd 100644 --- a/axmol/ui/UICheckBox.cpp +++ b/axmol/ui/CheckBox.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UICheckBox.h" +#include "axmol/ui/CheckBox.h" namespace ax { @@ -34,10 +34,6 @@ namespace ui IMPLEMENT_CLASS_GUI_INFO(CheckBox) -CheckBox::CheckBox() : _checkBoxEventListener(nullptr) {} - -CheckBox::~CheckBox() {} - CheckBox* CheckBox::create() { CheckBox* widget = new CheckBox(); @@ -79,11 +75,11 @@ CheckBox* CheckBox::create(std::string_view backGround, std::string_view cross, return nullptr; } -void CheckBox::onTouchEnded(Touch* touch, Event* unusedEvent) +void CheckBox::onPointerUp(PointerEvent* event) { bool highlight = _highlight; - AbstractCheckButton::onTouchEnded(touch, unusedEvent); + AbstractCheckButton::onPointerUp(event); if (highlight) { @@ -102,24 +98,22 @@ void CheckBox::onTouchEnded(Touch* touch, Event* unusedEvent) void CheckBox::dispatchSelectChangedEvent(bool selected) { - EventType eventType = (selected ? EventType::SELECTED : EventType::UNSELECTED); - + auto eventType = selected ? EventType::SELECTED : EventType::UNSELECTED; this->retain(); - if (_checkBoxEventCallback) + if (_eventCallback) { - _checkBoxEventCallback(this, eventType); + _eventCallback(this, eventType); } - if (_ccEventCallback) + if (_customEventCallback) { - _ccEventCallback(this, static_cast(eventType)); + _customEventCallback(this, static_cast(eventType)); } - this->release(); } -void CheckBox::addEventListener(const ccCheckBoxCallback& callback) +void CheckBox::addEventListener(const CheckBoxCallback& callback) { - _checkBoxEventCallback = callback; + _eventCallback = callback; } std::string CheckBox::getDescription() const @@ -138,9 +132,7 @@ void CheckBox::copySpecialProperties(Widget* widget) if (checkBox) { AbstractCheckButton::copySpecialProperties(widget); - _checkBoxEventListener = checkBox->_checkBoxEventListener; - _checkBoxEventCallback = checkBox->_checkBoxEventCallback; - _ccEventCallback = checkBox->_ccEventCallback; + _eventCallback = checkBox->_eventCallback; } } diff --git a/axmol/ui/UICheckBox.h b/axmol/ui/CheckBox.h similarity index 80% rename from axmol/ui/UICheckBox.h rename to axmol/ui/CheckBox.h index 9599836bddb3..98687bdc14e3 100644 --- a/axmol/ui/UICheckBox.h +++ b/axmol/ui/CheckBox.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIAbstractCheckButton.h" +#include "axmol/ui/AbstractCheckButton.h" #include "axmol/ui/GUIExport.h" /** @@ -49,33 +49,21 @@ class AX_GUI_DLL CheckBox : public AbstractCheckButton public: /** - * CheckBox event type, currently only "selected" and "unselected" event are cared. + * CheckBox event type, currently only selected and unselected events are used. */ enum class EventType { SELECTED, UNSELECTED }; - - /** - * A callback which will be called after certain CheckBox event issue. - * @see `CheckBox::EventType` - */ - typedef std::function ccCheckBoxCallback; + using CheckBoxCallback = std::function; /** * Default constructor. * * @lua new */ - CheckBox(); - - /** - * Default destructor. - * - * @lua NA - */ - virtual ~CheckBox(); + CheckBox() = default; /** * Create and return a empty CheckBox instance pointer. @@ -114,15 +102,14 @@ class AX_GUI_DLL CheckBox : public AbstractCheckButton TextureResType texType = TextureResType::LOCAL); /** - *Add a callback function which would be called when checkbox is selected or unselected. - *@param callback A std::function with type @see `ccCheckBoxCallback` + * Add a callback function which would be called when CheckBox is selected or unselected. */ - void addEventListener(const ccCheckBoxCallback& callback); + void addEventListener(const CheckBoxCallback& callback); // override functions std::string getDescription() const override; - void onTouchEnded(Touch* touch, Event* unusedEvent) override; + void onPointerUp(PointerEvent* event) override; protected: void dispatchSelectChangedEvent(bool selected) override; @@ -130,11 +117,7 @@ class AX_GUI_DLL CheckBox : public AbstractCheckButton Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; -protected: - // if you use the old event callback, it will retain the _checkBoxEventListener - Object* _checkBoxEventListener; - - ccCheckBoxCallback _checkBoxEventCallback; + CheckBoxCallback _eventCallback; }; } // namespace ui diff --git a/axmol/ui/UIEditBox/UIEditBox.cpp b/axmol/ui/EditBox/EditBox.cpp similarity index 94% rename from axmol/ui/UIEditBox/UIEditBox.cpp rename to axmol/ui/EditBox/EditBox.cpp index 807125338d04..49128546033b 100644 --- a/axmol/ui/UIEditBox/UIEditBox.cpp +++ b/axmol/ui/EditBox/EditBox.cpp @@ -25,8 +25,8 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIEditBox/UIEditBox.h" -#include "axmol/ui/UIEditBox/UIEditBoxImpl.h" +#include "axmol/ui/EditBox/EditBox.h" +#include "axmol/ui/EditBox/EditBoxImpl-common.h" #include "axmol/ui/UIHelper.h" namespace ax @@ -47,6 +47,12 @@ EditBox::EditBox() EditBox::~EditBox() { + if (_windowResizeListener) + { + _eventDispatcher->removeEventListener(_windowResizeListener); + AX_SAFE_RELEASE_NULL(_windowResizeListener); + } + AX_SAFE_DELETE(_editBoxImpl); #if AX_ENABLE_SCRIPT_BINDING unregisterScriptEditBoxHandler(); @@ -135,7 +141,7 @@ bool EditBox::initWithSizeAndBackgroundSprite(const Vec2& size, this->setContentSize(size); - this->setTouchEnabled(true); + this->setPointerEnabled(true); return true; } @@ -164,14 +170,14 @@ bool EditBox::initWithSizeAndTexture(const Vec2& size, loadTextures(normalImage, pressedImage, disabledImage, texType); this->setContentSize(size); - this->setTouchEnabled(true); + this->setPointerEnabled(true); return true; } return false; } -void EditBox::initRenderer() +void EditBox::initRenderNode() { _normalRenderer = Scale9Sprite::create(); _pressedRenderer = Scale9Sprite::create(); @@ -220,7 +226,7 @@ void EditBox::loadTextureNormal(std::string_view normal, TextureResType texType) } } // FIXME: https://github.com/cocos2d/cocos2d-x/issues/12249 - if (!_ignoreSize && _customSize.equals(Vec2::ZERO)) + if (!_autoSize && _customSize.equals(Vec2::ZERO)) { _customSize = _normalRenderer->getContentSize(); } @@ -720,7 +726,7 @@ void EditBox::onSizeChanged() _disabledTextureAdaptDirty = true; } -void EditBox::adaptRenderers() +void EditBox::updateLayout() { if (_normalTextureAdaptDirty) { @@ -743,19 +749,19 @@ void EditBox::adaptRenderers() void EditBox::normalTextureScaleChangedWithSize() { - _normalRenderer->setPreferredSize(_contentSize); + _normalRenderer->setContentSize(_contentSize); _normalRenderer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); } void EditBox::pressedTextureScaleChangedWithSize() { - _pressedRenderer->setPreferredSize(_contentSize); + _pressedRenderer->setContentSize(_contentSize); _pressedRenderer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); } void EditBox::disabledTextureScaleChangedWithSize() { - _disabledRenderer->setPreferredSize(_contentSize); + _disabledRenderer->setContentSize(_contentSize); _disabledRenderer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); } @@ -776,7 +782,7 @@ std::string EditBox::getDescription() const void EditBox::draw(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) { Widget::draw(renderer, parentTransform, parentFlags); - if (_editBoxImpl != nullptr) + if (_editBoxImpl) { _editBoxImpl->draw(renderer, parentTransform, parentFlags & FLAGS_TRANSFORM_DIRTY); } @@ -785,18 +791,33 @@ void EditBox::draw(Renderer* renderer, const Mat4& parentTransform, uint32_t par void EditBox::onEnter() { Widget::onEnter(); - if (_editBoxImpl != nullptr) + if (_editBoxImpl) { _editBoxImpl->onEnter(); } #if (AX_TARGET_PLATFORM == AX_PLATFORM_IOS || AX_TARGET_PLATFORM == AX_PLATFORM_MAC) this->schedule(AX_SCHEDULE_SELECTOR(EditBox::updatePosition), CHECK_EDITBOX_POSITION_INTERVAL); #endif + +#ifdef AX_PLATFORM_PC + if (!_windowResizeListener) + { + _windowResizeListener = _director->getEventDispatcher()->addCustomEventListener( + RenderView::EVENT_WINDOW_RESIZED, [this](CustomEvent*) { + if (_editBoxImpl) + { + auto rect = ui::Helper::getNodeNativeWindowRect(this); + static_cast(_editBoxImpl)->updateNativeFrame(rect); + } + }); + _windowResizeListener->retain(); + } +#endif } void EditBox::updatePosition(float dt) { - if (nullptr != _editBoxImpl) + if (_editBoxImpl) { _editBoxImpl->updatePosition(dt); } @@ -805,36 +826,28 @@ void EditBox::updatePosition(float dt) void EditBox::onExit() { Widget::onExit(); - if (_editBoxImpl != nullptr) + if (_editBoxImpl) { // remove system edit control _editBoxImpl->closeKeyboard(); } } -static Rect getRect(Node* pNode) -{ - Vec2 contentSize = pNode->getContentSize(); - Rect rect = Rect(0, 0, contentSize.width, contentSize.height); - return RectApplyTransform(rect, pNode->getNodeToWorldTransform()); -} - void EditBox::keyboardWillShow(IMEKeyboardNotificationInfo& info) { // AXLOGD("EditBox::keyboardWillShow"); - Rect rectTracked = getRect(this); + Rect rectTracked = getWorldBoundingBox(); // some adjustment for margin between the keyboard and the edit box. rectTracked.origin.y -= 4; // if the keyboard area doesn't intersect with the tracking node area, nothing needs to be done. - if (!rectTracked.intersectsRect(info.end)) + if (!rectTracked.intersectsRect(info.keyboardFrame)) { return; } // assume keyboard at the bottom of screen, calculate the vertical adjustment. - _adjustHeight = info.end.getMaxY() - rectTracked.getMinY(); - // AXLOGD("EditBox:needAdjustVerticalPosition({})", _adjustHeight); + _adjustHeight = info.keyboardFrame.getMaxY() - rectTracked.getMinY(); if (_editBoxImpl != nullptr) { diff --git a/axmol/ui/UIEditBox/UIEditBox.h b/axmol/ui/EditBox/EditBox.h similarity index 98% rename from axmol/ui/UIEditBox/UIEditBox.h rename to axmol/ui/EditBox/EditBox.h index 617a6aedd290..43028f0d6e33 100644 --- a/axmol/ui/UIEditBox/UIEditBox.h +++ b/axmol/ui/EditBox/EditBox.h @@ -27,10 +27,10 @@ #pragma once -#include "axmol/base/IMEDelegate.h" +#include "axmol/base/InputDelegate.h" #include "axmol/ui/GUIDefine.h" -#include "axmol/ui/UIWidget.h" -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/Widget.h" +#include "axmol/ui/Scale9Sprite.h" namespace ax { @@ -100,7 +100,7 @@ class AX_GUI_DLL EditBoxDelegate * You can use this widget to gather small amounts of text from the user. * */ -class AX_GUI_DLL EditBox : public Widget, public IMEDelegate +class AX_GUI_DLL EditBox : public Widget, public InputDelegate { public: /** @@ -636,7 +636,7 @@ class AX_GUI_DLL EditBox : public Widget, public IMEDelegate protected: void releaseUpEvent() override; - void initRenderer() override; + void initRenderNode() override; void onPressStateChangedToNormal() override; void onPressStateChangedToPressed() override; void onPressStateChangedToDisabled() override; @@ -653,7 +653,7 @@ class AX_GUI_DLL EditBox : public Widget, public IMEDelegate void pressedTextureScaleChangedWithSize(); void disabledTextureScaleChangedWithSize(); - void adaptRenderers() override; + void updateLayout() override; protected: void updatePosition(float dt); @@ -687,6 +687,8 @@ class AX_GUI_DLL EditBox : public Widget, public IMEDelegate EditBoxImpl* _editBoxImpl = nullptr; EditBoxDelegate* _delegate = nullptr; + CustomEventListener* _windowResizeListener{nullptr}; + float _adjustHeight = 0.f; #if AX_ENABLE_SCRIPT_BINDING int _scriptEditBoxHandler = 0; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-android.cpp b/axmol/ui/EditBox/EditBoxImpl-android.cpp similarity index 87% rename from axmol/ui/UIEditBox/UIEditBoxImpl-android.cpp rename to axmol/ui/EditBox/EditBoxImpl-android.cpp index d438daf8365b..d7cb44fe27c8 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-android.cpp +++ b/axmol/ui/EditBox/EditBoxImpl-android.cpp @@ -26,11 +26,11 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIEditBox/UIEditBoxImpl-android.h" +#include "axmol/ui/EditBox/EditBoxImpl-android.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) -# include "axmol/ui/UIEditBox/UIEditBox.h" +# include "axmol/ui/EditBox/EditBox.h" # include # include "axmol/platform/android/jni/JniHelper.h" # include "axmol/2d/Label.h" @@ -69,25 +69,11 @@ EditBoxImplAndroid::~EditBoxImplAndroid() JniHelper::callStaticVoidMethod(editBoxClassName, "removeEditBox", _editBoxIndex); } -void EditBoxImplAndroid::createNativeControl(const Rect& frame) +void EditBoxImplAndroid::createNativeControl() { - auto director = ax::Director::getInstance(); - auto renderView = director->getRenderView(); - auto windowSize = renderView->getWindowSize(); - - auto canvasSize = director->getCanvasSize(); - auto leftBottom = _editBox->convertToWorldSpace(Point::ZERO); - - auto contentSize = frame.size; - auto rightTop = _editBox->convertToWorldSpace(Point(contentSize.width, contentSize.height)); - - auto uiLeft = windowSize.width / 2 + (leftBottom.x - canvasSize.width / 2) * renderView->getScaleX(); - auto uiTop = windowSize.height / 2 - (rightTop.y - canvasSize.height / 2) * renderView->getScaleY(); - auto uiWidth = (rightTop.x - leftBottom.x) * renderView->getScaleX(); - auto uiHeight = (rightTop.y - leftBottom.y) * renderView->getScaleY(); - LOGD("scaleX = %f", renderView->getScaleX()); - _editBoxIndex = JniHelper::callStaticIntMethod(editBoxClassName, "createEditBox", (int)uiLeft, (int)uiTop, - (int)uiWidth, (int)uiHeight, (float)renderView->getScaleX()); + auto renderView = ax::Director::getInstance()->getRenderView(); + _editBoxIndex = + JniHelper::callStaticIntMethod(editBoxClassName, "createEditBox", 0, 0, 1, 1, (float)renderView->getScaleX()); s_allEditBoxes[_editBoxIndex] = this; } diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-android.h b/axmol/ui/EditBox/EditBoxImpl-android.h similarity index 96% rename from axmol/ui/UIEditBox/UIEditBoxImpl-android.h rename to axmol/ui/EditBox/EditBoxImpl-android.h index 620a431a7176..d99d8ae05407 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-android.h +++ b/axmol/ui/EditBox/EditBoxImpl-android.h @@ -31,7 +31,7 @@ #if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) -# include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" +# include "axmol/ui/EditBox/EditBoxImpl-common.h" namespace ax { @@ -55,7 +55,7 @@ class EditBoxImplAndroid : public EditBoxImplCommon virtual ~EditBoxImplAndroid(); bool isEditing() override; - void createNativeControl(const Rect& frame) override; + void createNativeControl() override; void setNativeFont(std::string_view fontName, int fontSize) override; void setNativeFontColor(const Color32& color) override; void setNativePlaceholderFont(std::string_view fontName, int fontSize) override; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-common.cpp b/axmol/ui/EditBox/EditBoxImpl-common.cpp similarity index 97% rename from axmol/ui/UIEditBox/UIEditBoxImpl-common.cpp rename to axmol/ui/EditBox/EditBoxImpl-common.cpp index 382ca7cacc69..f822d8d8b44b 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-common.cpp +++ b/axmol/ui/EditBox/EditBoxImpl-common.cpp @@ -25,14 +25,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" +#include "axmol/ui/EditBox/EditBoxImpl-common.h" #define kLabelZOrder 9999 -#include "axmol/ui/UIEditBox/UIEditBox.h" +#include "axmol/ui/EditBox/EditBox.h" #include "axmol/base/Director.h" #include "axmol/2d/Label.h" #include "axmol/ui/UIHelper.h" +#include "axmol/base/InputSystem.h" static const int AX_EDIT_BOX_PADDING = 5; @@ -75,9 +76,7 @@ bool EditBoxImplCommon::initWithSize(const Vec2& size) { do { - Rect rect = Rect(0, 0, size.width, size.height); - - this->createNativeControl(rect); + this->createNativeControl(); initInactiveLabels(size); setContentSize(size); @@ -330,7 +329,7 @@ void EditBoxImplCommon::draw(Renderer* /*renderer*/, const Mat4& /*transform*/, { if (flags) { - auto rect = ui::Helper::convertBoundingBoxToScreen(_editBox); + auto rect = ui::Helper::getNodeNativeWindowRect(_editBox); this->updateNativeFrame(rect); } } diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-common.h b/axmol/ui/EditBox/EditBoxImpl-common.h similarity index 97% rename from axmol/ui/UIEditBox/UIEditBoxImpl-common.h rename to axmol/ui/EditBox/EditBoxImpl-common.h index 26586a14ab52..2577f7e05322 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-common.h +++ b/axmol/ui/EditBox/EditBoxImpl-common.h @@ -30,8 +30,8 @@ #include "axmol/platform/PlatformConfig.h" #include "axmol/2d/Label.h" -#include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" -#include "axmol/ui/UIEditBox/UIEditBoxImpl.h" +#include "axmol/ui/EditBox/EditBoxImpl-common.h" +#include "axmol/ui/EditBox/EditBoxImpl.h" namespace ax { @@ -112,7 +112,7 @@ class AX_GUI_DLL EditBoxImplCommon : public EditBoxImpl EditBoxDelegate::EditBoxEndAction action = EditBoxDelegate::EditBoxEndAction::UNKNOWN); bool isEditing() override = 0; - virtual void createNativeControl(const Rect& frame) = 0; + virtual void createNativeControl() = 0; virtual void setNativeFont(std::string_view fontName, int fontSize) = 0; virtual void setNativeFontColor(const Color32& color) = 0; virtual void setNativePlaceholderFont(std::string_view fontName, int fontSize) = 0; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-ios.h b/axmol/ui/EditBox/EditBoxImpl-ios.h similarity index 94% rename from axmol/ui/UIEditBox/UIEditBoxImpl-ios.h rename to axmol/ui/EditBox/EditBoxImpl-ios.h index 5409f034eba0..0a7c77c578bf 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-ios.h +++ b/axmol/ui/EditBox/EditBoxImpl-ios.h @@ -29,7 +29,7 @@ #if (AX_TARGET_PLATFORM == AX_PLATFORM_IOS) -# include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" +# include "axmol/ui/EditBox/EditBoxImpl-common.h" @class UIEditBoxImplIOS_objc; @class UIFont; @@ -54,7 +54,7 @@ class EditBoxImplIOS : public EditBoxImplCommon virtual ~EditBoxImplIOS(); bool isEditing() override; - void createNativeControl(const Rect& frame) override; + void createNativeControl() override; void setNativeFont(std::string_view fontName, int fontSize) override; void setNativeFontColor(const Color32& color) override; void setNativePlaceholderFont(std::string_view fontName, int fontSize) override; @@ -77,7 +77,7 @@ class EditBoxImplIOS : public EditBoxImplCommon void doAnimationWhenKeyboardMove(float duration, float distance) override; private: - UIFont* constructFont(std::string_view fontName, int fontSize); + UIFont* createNativeFont(std::string_view fontName, int fontSize); UIEditBoxImplIOS_objc* _systemControl; }; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-ios.mm b/axmol/ui/EditBox/EditBoxImpl-ios.mm similarity index 87% rename from axmol/ui/UIEditBox/UIEditBoxImpl-ios.mm rename to axmol/ui/EditBox/EditBoxImpl-ios.mm index e48af8741a5a..9a2a567bdc8b 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-ios.mm +++ b/axmol/ui/EditBox/EditBoxImpl-ios.mm @@ -25,13 +25,13 @@ of this software and associated documentation files (the "Software"), to deal OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIEditBox/UIEditBoxImpl-ios.h" +#include "axmol/ui/EditBox/EditBoxImpl-ios.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_IOS) # define kLabelZOrder 9999 -# include "axmol/ui/UIEditBox/UIEditBox.h" +# include "axmol/ui/EditBox/EditBox.h" # include "axmol/base/Director.h" # include "axmol/2d/Label.h" # import "axmol/platform/ios/RenderHostView-ios.h" @@ -39,7 +39,7 @@ of this software and associated documentation files (the "Software"), to deal # import # import -# import "axmol/ui/UIEditBox/iOS/UIEditBoxIOS.h" +# import "axmol/ui/EditBox/iOS/EditBoxIOS.h" # define getEditBoxImplIOS() ((ax::ui::EditBoxImplIOS*)_editBox) @@ -62,20 +62,9 @@ of this software and associated documentation files (the "Software"), to deal _systemControl = nil; } -void EditBoxImplIOS::createNativeControl(const Rect& frame) +void EditBoxImplIOS::createNativeControl() { - auto renderView = ax::Director::getInstance()->getRenderView(); - - Rect rect(0, 0, frame.size.width * renderView->getScaleX(), frame.size.height * renderView->getScaleY()); - - float factor = ax::Director::getInstance()->getContentScaleFactor(); - - rect.size.width /= factor; - rect.size.height /= factor; - - _systemControl = [[UIEditBoxImplIOS_objc alloc] - initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height) - editBox:this]; + _systemControl = [[UIEditBoxImplIOS_objc alloc] initWithFrame:CGRectMake(0, 0, 1, 1) editBox:this]; } bool EditBoxImplIOS::isEditing() @@ -93,7 +82,7 @@ of this software and associated documentation files (the "Software"), to deal void EditBoxImplIOS::setNativeFont(std::string_view fontName, int fontSize) { - UIFont* textFont = constructFont(fontName, fontSize); + UIFont* textFont = createNativeFont(fontName, fontSize); if (textFont != nil) { [_systemControl setFont:textFont]; @@ -110,7 +99,7 @@ of this software and associated documentation files (the "Software"), to deal void EditBoxImplIOS::setNativePlaceholderFont(std::string_view fontName, int fontSize) { - UIFont* textFont = constructFont(fontName, fontSize); + UIFont* textFont = createNativeFont(fontName, fontSize); if (textFont != nil) { [_systemControl setPlaceholderFont:textFont]; @@ -185,7 +174,7 @@ of this software and associated documentation files (the "Software"), to deal auto renderView = ax::Director::getInstance()->getRenderView(); auto hostView = (__bridge RenderHostView*)renderView->getNativeDisplay(); - float factor = hostView.contentScaleFactor; + float factor = 1.0f; // hostView.contentScaleFactor; [_systemControl updateFrame:CGRectMake(rect.origin.x / factor, rect.origin.y / factor, rect.size.width / factor, rect.size.height / factor)]; @@ -207,7 +196,7 @@ of this software and associated documentation files (the "Software"), to deal [_systemControl closeKeyboard]; } -UIFont* EditBoxImplIOS::constructFont(std::string_view fontName, int fontSize) +UIFont* EditBoxImplIOS::createNativeFont(std::string_view fontName, int fontSize) { AXASSERT(!fontName.empty(), "fontName can't be nullptr"); auto hostView = static_cast(ax::Director::getInstance()->getRenderView()->getNativeDisplay()); @@ -226,7 +215,7 @@ of this software and associated documentation files (the "Software"), to deal } else { - fontSize = fontSize * scaleFactor / retinaFactor; + fontSize = fontSize * scaleFactor / renderView->getRenderScale(); } UIFont* textFont = [UIFont fontWithName:fntName size:fontSize]; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-linux.cpp b/axmol/ui/EditBox/EditBoxImpl-linux.cpp similarity index 99% rename from axmol/ui/UIEditBox/UIEditBoxImpl-linux.cpp rename to axmol/ui/EditBox/EditBoxImpl-linux.cpp index cd34138d197f..7bd598606e42 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-linux.cpp +++ b/axmol/ui/EditBox/EditBoxImpl-linux.cpp @@ -25,11 +25,11 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIEditBox/UIEditBoxImpl-linux.h" +#include "axmol/ui/EditBox/EditBoxImpl-linux.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) -# include "axmol/ui/UIEditBox/UIEditBox.h" +# include "axmol/ui/EditBox/EditBox.h" # include "axmol/2d/Label.h" # include "axmol/base/text_utils.h" diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-linux.h b/axmol/ui/EditBox/EditBoxImpl-linux.h similarity index 96% rename from axmol/ui/UIEditBox/UIEditBoxImpl-linux.h rename to axmol/ui/EditBox/EditBoxImpl-linux.h index 76b97991bb36..4f8ee35c038e 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-linux.h +++ b/axmol/ui/EditBox/EditBoxImpl-linux.h @@ -31,7 +31,7 @@ #if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) -# include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" +# include "axmol/ui/EditBox/EditBoxImpl-common.h" namespace ax { @@ -55,7 +55,7 @@ class EditBoxImplLinux : public EditBoxImplCommon virtual ~EditBoxImplLinux(); bool isEditing() override; - void createNativeControl(const Rect& frame) override {}; + void createNativeControl() override {}; void setNativeFont(std::string_view fontName, int fontSize) override {}; void setNativeFontColor(const Color32& color) override {}; void setNativePlaceholderFont(std::string_view fontName, int fontSize) override {}; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-mac.h b/axmol/ui/EditBox/EditBoxImpl-mac.h similarity index 93% rename from axmol/ui/UIEditBox/UIEditBoxImpl-mac.h rename to axmol/ui/EditBox/EditBoxImpl-mac.h index 94dd2b37d506..4f3039d6fa0c 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-mac.h +++ b/axmol/ui/EditBox/EditBoxImpl-mac.h @@ -31,7 +31,7 @@ #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) -# include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" +# include "axmol/ui/EditBox/EditBoxImpl-common.h" @class UIEditBoxImplMac; @class NSFont; @@ -56,7 +56,7 @@ class EditBoxImplMac : public EditBoxImplCommon virtual ~EditBoxImplMac(); bool isEditing() override; - void createNativeControl(const Rect& frame) override; + void createNativeControl() override; void setNativeFont(std::string_view fontName, int fontSize) override; void setNativeFontColor(const Color32& color) override; void setNativePlaceholderFont(std::string_view fontName, int fontSize) override; @@ -75,9 +75,8 @@ class EditBoxImplMac : public EditBoxImplCommon void setNativeMaxLength(int maxLength) override; private: - NSFont* constructFont(std::string_view fontName, int fontSize); + NSFont* createNativeFont(std::string_view fontName, int fontSize); - bool _inRetinaMode; UIEditBoxImplMac* _sysEdit; }; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-mac.mm b/axmol/ui/EditBox/EditBoxImpl-mac.mm similarity index 78% rename from axmol/ui/UIEditBox/UIEditBoxImpl-mac.mm rename to axmol/ui/EditBox/EditBoxImpl-mac.mm index 9155cc121809..d8afe1be0871 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-mac.mm +++ b/axmol/ui/EditBox/EditBoxImpl-mac.mm @@ -28,11 +28,11 @@ of this software and associated documentation files (the "Software"), to deal #include "axmol/platform/PlatformConfig.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) -# include "axmol/ui/UIEditBox/UIEditBoxImpl-mac.h" +# include "axmol/ui/EditBox/EditBoxImpl-mac.h" # include "axmol/base/Director.h" # include "axmol/base/text_utils.h" -# include "axmol/ui/UIEditBox/UIEditBox.h" -# include "axmol/ui/UIEditBox/Mac/UIEditBoxMac.h" +# include "axmol/ui/EditBox/EditBox.h" +# include "axmol/ui/EditBox/Mac/EditBoxMac.h" namespace ax { @@ -45,43 +45,28 @@ of this software and associated documentation files (the "Software"), to deal return new EditBoxImplMac(pEditBox); } -EditBoxImplMac::EditBoxImplMac(EditBox* pEditText) : EditBoxImplCommon(pEditText), _sysEdit(nullptr) -{ - //! TODO: Retina on Mac - //! _inRetinaMode = [[RenderHostView sharedERenderView] contentScaleFactor] == 2.0f ? true : false; - _inRetinaMode = false; -} +EditBoxImplMac::EditBoxImplMac(EditBox* pEditText) : EditBoxImplCommon(pEditText), _sysEdit(nullptr) {} EditBoxImplMac::~EditBoxImplMac() { [_sysEdit release]; } -void EditBoxImplMac::createNativeControl(const ax::Rect& frame) +void EditBoxImplMac::createNativeControl() { - auto renderView = ax::Director::getInstance()->getRenderView(); - Size size = frame.size; - NSRect rect = NSMakeRect(0, 0, size.width * renderView->getScaleX(), size.height * renderView->getScaleY()); - - float factor = ax::Director::getInstance()->getContentScaleFactor(); - - rect.size.width /= factor; - rect.size.height /= factor; - - _sysEdit = [[UIEditBoxImplMac alloc] initWithFrame:rect editBox:this]; + _sysEdit = [[UIEditBoxImplMac alloc] initWithFrame:CGRectMake(0, 0, 1, 1) editBox:this]; this->setNativeVisible(false); } -NSFont* EditBoxImplMac::constructFont(std::string_view fontName, int fontSize) +NSFont* EditBoxImplMac::createNativeFont(std::string_view fontName, int fontSize) { // [NSString stringWithUTF8String:fontName.data()]; - NSString* fntName = [[NSString alloc] initWithBytes:fontName.data() + NSString* fntName = [[NSString alloc] initWithBytes:fontName.data() length:fontName.length() encoding:NSUTF8StringEncoding]; - fntName = [[fntName lastPathComponent] stringByDeletingPathExtension]; - float retinaFactor = _inRetinaMode ? 2.0f : 1.0f; - auto renderView = ax::Director::getInstance()->getRenderView(); - float scaleFactor = renderView->getScaleX(); + fntName = [[fntName lastPathComponent] stringByDeletingPathExtension]; + auto renderView = ax::Director::getInstance()->getRenderView(); + float scaleFactor = renderView->getScaleX(); if (fontSize == -1) { @@ -90,7 +75,7 @@ of this software and associated documentation files (the "Software"), to deal } else { - fontSize = fontSize * scaleFactor / retinaFactor; + fontSize = fontSize * scaleFactor / renderView->getRenderScale(); } NSFont* textFont = [NSFont fontWithName:fntName size:fontSize]; @@ -104,13 +89,13 @@ of this software and associated documentation files (the "Software"), to deal void EditBoxImplMac::setNativeFont(std::string_view fontName, int fontSize) { - NSFont* textFont = constructFont(fontName, fontSize); + NSFont* textFont = createNativeFont(fontName, fontSize); [_sysEdit setFont:textFont]; } void EditBoxImplMac::setNativePlaceholderFont(std::string_view fontName, int fontSize) { - NSFont* textFont = constructFont(fontName, fontSize); + NSFont* textFont = createNativeFont(fontName, fontSize); if (!textFont) { @@ -191,8 +176,8 @@ of this software and associated documentation files (the "Software"), to deal void EditBoxImplMac::updateNativeFrame(const ax::Rect& rect) { - RenderView* renderView = Director::getInstance()->getRenderView(); - auto windowSize = renderView->getWindowSize(); + auto renderView = Director::getInstance()->getRenderView(); + auto windowSize = renderView->getWindowSize(); // Coordinate System on OSX has its origin at the lower left corner. // https://developer.apple.com/library/ios/documentation/General/Conceptual/Devpedia-CocoaApp/CoordinateSystem.html auto screenPosY = windowSize.height - rect.origin.y - rect.size.height; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-stub.cpp b/axmol/ui/EditBox/EditBoxImpl-stub.cpp similarity index 97% rename from axmol/ui/UIEditBox/UIEditBoxImpl-stub.cpp rename to axmol/ui/EditBox/EditBoxImpl-stub.cpp index efd9ecce41c1..7ad142657afd 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-stub.cpp +++ b/axmol/ui/EditBox/EditBoxImpl-stub.cpp @@ -23,7 +23,7 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIEditBox/UIEditBox.h" +#include "axmol/ui/EditBox/EditBox.h" #if (AX_TARGET_PLATFORM != AX_PLATFORM_ANDROID) && (AX_TARGET_PLATFORM != AX_PLATFORM_IOS) && \ (AX_TARGET_PLATFORM != AX_PLATFORM_WIN32) && (AX_TARGET_PLATFORM != AX_PLATFORM_MAC) diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-wasm.cpp b/axmol/ui/EditBox/EditBoxImpl-wasm.cpp similarity index 59% rename from axmol/ui/UIEditBox/UIEditBoxImpl-wasm.cpp rename to axmol/ui/EditBox/EditBoxImpl-wasm.cpp index 3e06ed8cf97f..96deb165926b 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-wasm.cpp +++ b/axmol/ui/EditBox/EditBoxImpl-wasm.cpp @@ -27,7 +27,7 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIEditBox/UIEditBoxImpl-wasm.h" +#include "axmol/ui/EditBox/EditBoxImpl-wasm.h" #if AX_TARGET_PLATFORM == AX_PLATFORM_WASM # include "axmol/ui/UIHelper.h" @@ -39,10 +39,9 @@ namespace ui { EditBoxImplWasm* _activeEditBox = nullptr; extern "C" { -EMSCRIPTEN_KEEPALIVE -void getInputOver(char* dataPtr, int dataLength) +EMSCRIPTEN_KEEPALIVE void axmol_editbox_endediting(const char* pszText, int length) { - std::string_view text{dataPtr, static_cast(dataLength)}; + std::string_view text{pszText, static_cast(length)}; AXLOGD("text {} ", text); if (_activeEditBox) { @@ -50,20 +49,16 @@ void getInputOver(char* dataPtr, int dataLength) _activeEditBox->editBoxEditingDidEnd(text, EditBoxDelegate::EditBoxEndAction::RETURN); _activeEditBox = nullptr; } - free(dataPtr); } -EMSCRIPTEN_KEEPALIVE - -void getInputChange(char* dataPtr, int dataLength) +EMSCRIPTEN_KEEPALIVE void axmol_editbox_textchange(const char* pszText, int length) { - std::string_view text{dataPtr, static_cast(dataLength)}; + std::string_view text{pszText, static_cast(length)}; AXLOGD("text {} ", text); if (_activeEditBox && _activeEditBox->isEditingMode()) { _activeEditBox->editBoxEditingChanged(text); } - free(dataPtr); } } @@ -94,19 +89,20 @@ bool EditBoxImplWasm::isEditing() return false; } -void EditBoxImplWasm::createNativeControl(const Rect& frame) +void EditBoxImplWasm::createNativeControl() { this->createEditCtrl(ax::ui::EditBox::InputMode::ANY); } void EditBoxImplWasm::setNativeFont(std::string_view /*fontName*/, int fontSize) { - EM_ASM( - { - var input = Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); - input.style.fontSize = $0 + "px"; - }, - fontSize); + // clang-format off + EM_ASM({ + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); + input.style.fontSize = $0 + "px"; + }, + fontSize); + // clang-format on } void EditBoxImplWasm::setNativeFontColor(const Color32& /*color*/) @@ -139,84 +135,76 @@ void EditBoxImplWasm::setNativeTextHorizontalAlignment(TextHAlignment alignment) void EditBoxImplWasm::setNativeText(std::string_view text) { - EM_ASM( - { - var input = Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); - input.value = UTF8ToString($0, $1); - }, - text.data(), static_cast(text.size())); -} - -void EditBoxImplWasm::setNativePlaceHolder(std::string_view /*text*/) -{ - // not implemented yet + // clang-format off + EM_ASM({ + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); + input.value = UTF8ToString($0, $1); + }, + text.data(), static_cast(text.size())); + // clang-format off } void EditBoxImplWasm::setNativeVisible(bool visible) { - EM_ASM( + // clang-format off + EM_ASM({ + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); + if ($0 == 0) + input.style.display = "none"; + else { - var input = Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); - - if ($0 == 0) - input.style.display = "none"; - else + var inputMode = $1; + var inputFlag = $2; + // set input type + switch (inputMode) { - var inputMode = $1; - var inputFlag = $2; - // set input type - switch (inputMode) + case 2: // NUMERIC + case 3: // PHONE_NUMBER + input.type = 'number'; + default: + if (inputFlag != 0) { - case 2: // NUMERIC - case 3: // PHONE_NUMBER - input.type = 'number'; - default: - if (inputFlag != 0) - { - input.type = 'text'; - } - else - { - input.type = 'password'; - } + input.type = 'text'; } + else + { + input.type = 'password'; + } + } - input.style.display = ""; - var canvas = document.getElementById('canvas'); - var inputParent = input.parentNode; - var canvasParent = canvas.parentNode; - if (inputParent != canvasParent) + input.style.display = ""; + var canvas = document.getElementById('canvas'); + var inputParent = input.parentNode; + var canvasParent = canvas.parentNode; + if (inputParent != canvasParent) + { + if (inputParent != null) { - if (inputParent != null) - { - inputParent.removeChild(input); - } - canvasParent.insertBefore(input, canvas); + inputParent.removeChild(input); } + canvasParent.insertBefore(input, canvas); } - }, - (int)visible, (int)_editBoxInputMode, (int)_editBoxInputFlag); + } + }, + (int)visible, (int)_editBoxInputMode, (int)_editBoxInputFlag); + // clang-format on } void EditBoxImplWasm::updateNativeFrame(const Rect& rect) { - EM_ASM( - { - var input = Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); - var canvas = Module["canvas"]; - // set input style - input.style.position = "absolute"; - input.style.left = canvas.offsetLeft + $0 + "px"; - input.style.top = canvas.offsetTop + $1 + "px"; - input.style.width = $2 + "px"; - input.style.height = $3 + "px"; - }, - rect.origin.x, rect.origin.y, rect.size.x, rect.size.y); -} - -std::string_view EditBoxImplWasm::getNativeDefaultFontName() -{ - return "Arial"sv; + // clang-format off + EM_ASM({ + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); + var canvas = Module["canvas"]; + // set input style + input.style.position = "absolute"; + input.style.left = canvas.offsetLeft + $0 + "px"; + input.style.top = canvas.offsetTop + $1 + "px"; + input.style.width = $2 + "px"; + input.style.height = $3 + "px"; + }, + rect.origin.x, rect.origin.y, rect.size.x, rect.size.y); + // clang-format on } void EditBoxImplWasm::nativeOpenKeyboard() @@ -227,35 +215,26 @@ void EditBoxImplWasm::nativeOpenKeyboard() auto text = this->getText(); - EM_ASM( - { - var input = Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); - // sync input value from native and focus - input.value = UTF8ToString($0, $1); - input.maxlength = $2 != -1 ? $2 : undefined; - input.focus(); - }, - text.data(), (int)text.size(), (int)_maxLength); + // clang-format off + EM_ASM({ + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); + // sync input value from native and focus + input.value = UTF8ToString($0, $1); + input.maxlength = $2 != -1 ? $2 : undefined; + input.focus(); + }, + text.data(), (int)text.size(), (int)_maxLength); + // clang-format on - auto rect = ui::Helper::convertBoundingBoxToScreen(_editBox); + auto rect = ui::Helper::getNodeNativeWindowRect(_editBox); this->updateNativeFrame(rect); } -void EditBoxImplWasm::nativeCloseKeyboard() -{ - // don't need to implement -} - -void EditBoxImplWasm::setNativeMaxLength(int /*maxLength*/) -{ - // since we use shared inputbox, we sync maxlength when open inputbox for current editbox -} - void EditBoxImplWasm::lazyInit() { // clang-format off EM_ASM({ - var input = Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); // set input type input.type = "text"; // set input style @@ -296,23 +275,20 @@ void EditBoxImplWasm::lazyInit() input.addEventListener( 'change', function() { // handle focus lost - var input = Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); - var value = input.value; - var lengthBytes = lengthBytesUTF8(value) + 1; - var stringOnWasmHeap = _malloc(lengthBytes); - stringToUTF8(value, stringOnWasmHeap, lengthBytes); - _getInputChange(stringOnWasmHeap, lengthBytes); + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); + + var result = Module.stringToUTF8WithLen(input.value); + _axmol_editbox_textchange(result.ptr, result.length); }); input.addEventListener( 'blur', function() { // handle focus lost - var input = Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); input.style.display = "none"; - var value = input.value; - var lengthBytes = lengthBytesUTF8(value) + 1; - var stringOnWasmHeap = _malloc(lengthBytes); - stringToUTF8(value, stringOnWasmHeap, lengthBytes); - _getInputOver(stringOnWasmHeap, lengthBytes); + + var result = Module.stringToUTF8WithLen(input.value); + _axmol_editbox_endediting(result.ptr, result.length); + _free(result.ptr) }); }); // clang-format on @@ -321,11 +297,38 @@ void EditBoxImplWasm::lazyInit() void EditBoxImplWasm::createEditCtrl(EditBox::InputMode inputMode) { - EM_ASM({ Module.axmolSharedInput = Module.axmolSharedInput || document.createElement("input"); }); + EM_ASM({ Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); }); this->setNativeFont(this->getNativeDefaultFontName(), this->_fontSize); this->setNativeText(this->_text); } +void EditBoxImplWasm::setNativePlaceHolder(std::string_view text) +{ + EM_ASM( + { + var input = Module.axmol_editbox_input = Module.axmol_editbox_input || document.createElement("input"); + // sync input value from native and focus + input.placeholder = UTF8ToString($0, $1); + input.focus(); + }, + !text.empty() ? text.data() : "", (int)text.size()); +} + +std::string_view EditBoxImplWasm::getNativeDefaultFontName() +{ + return "Arial"sv; +} + +void EditBoxImplWasm::nativeCloseKeyboard() +{ + // don't need to implement +} + +void EditBoxImplWasm::setNativeMaxLength(int /*maxLength*/) +{ + // since we use shared inputbox, we sync maxlength when open inputbox for current editbox +} + } // namespace ui } // namespace ax diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-wasm.h b/axmol/ui/EditBox/EditBoxImpl-wasm.h similarity index 96% rename from axmol/ui/UIEditBox/UIEditBoxImpl-wasm.h rename to axmol/ui/EditBox/EditBoxImpl-wasm.h index 4c5c94089ca0..78444e4b948a 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-wasm.h +++ b/axmol/ui/EditBox/EditBoxImpl-wasm.h @@ -28,7 +28,7 @@ THE SOFTWARE. #include "axmol/platform/PlatformConfig.h" #if AX_TARGET_PLATFORM == AX_PLATFORM_WASM -# include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" +# include "axmol/ui/EditBox/EditBoxImpl-common.h" namespace ax { @@ -45,7 +45,7 @@ class AX_API EditBoxImplWasm : public EditBoxImplCommon virtual ~EditBoxImplWasm(); bool isEditing() override; - void createNativeControl(const Rect& frame) override; + void createNativeControl() override; void setNativeFont(std::string_view fontName, int fontSize) override; void setNativeFontColor(const Color32& color) override; void setNativePlaceholderFont(std::string_view fontName, int fontSize) override; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-win32.cpp b/axmol/ui/EditBox/EditBoxImpl-win32.cpp similarity index 98% rename from axmol/ui/UIEditBox/UIEditBoxImpl-win32.cpp rename to axmol/ui/EditBox/EditBoxImpl-win32.cpp index 56d9dac04d49..7e41a3c81670 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-win32.cpp +++ b/axmol/ui/EditBox/EditBoxImpl-win32.cpp @@ -25,12 +25,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIEditBox/UIEditBoxImpl-win32.h" +#include "axmol/ui/EditBox/EditBoxImpl-win32.h" #include "axmol/platform/PlatformConfig.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) -# include "axmol/ui/UIEditBox/UIEditBox.h" +# include "axmol/ui/EditBox/EditBox.h" # include # include # include "axmol/2d/Label.h" @@ -134,7 +134,7 @@ void EditBoxImplWin::createEditCtrl(bool singleLine) } } -void EditBoxImplWin::createNativeControl(const Rect& frame) +void EditBoxImplWin::createNativeControl() { this->createEditCtrl(false); } @@ -305,7 +305,7 @@ void EditBoxImplWin::nativeOpenKeyboard() // s_previousFocusWnd = hwndEdit; this->editBoxEditingDidBegin(); - auto rect = ui::Helper::convertBoundingBoxToScreen(_editBox); + auto rect = ui::Helper::getNodeNativeWindowRect(_editBox); this->updateNativeFrame(rect); } diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-win32.h b/axmol/ui/EditBox/EditBoxImpl-win32.h similarity index 96% rename from axmol/ui/UIEditBox/UIEditBoxImpl-win32.h rename to axmol/ui/EditBox/EditBoxImpl-win32.h index 5b711ba68e15..423ddf936e12 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-win32.h +++ b/axmol/ui/EditBox/EditBoxImpl-win32.h @@ -30,7 +30,7 @@ THE SOFTWARE. #include "axmol/platform/PlatformConfig.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) -# include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" +# include "axmol/ui/EditBox/EditBoxImpl-common.h" namespace ax { @@ -47,7 +47,7 @@ class AX_GUI_DLL EditBoxImplWin : public EditBoxImplCommon virtual ~EditBoxImplWin(); bool isEditing() override; - void createNativeControl(const Rect& frame) override; + void createNativeControl() override; void setNativeFont(std::string_view fontName, int fontSize) override; void setNativeFontColor(const Color32& color) override; void setNativePlaceholderFont(std::string_view fontName, int fontSize) override; diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-winrt.cpp b/axmol/ui/EditBox/EditBoxImpl-winrt.cpp similarity index 86% rename from axmol/ui/UIEditBox/UIEditBoxImpl-winrt.cpp rename to axmol/ui/EditBox/EditBoxImpl-winrt.cpp index 26796a116175..386b3a190065 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-winrt.cpp +++ b/axmol/ui/EditBox/EditBoxImpl-winrt.cpp @@ -29,10 +29,10 @@ #include "axmol/platform/PlatformConfig.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_WINRT) -# include "axmol/ui/UIEditBox/UIEditBoxImpl-winrt.h" +# include "axmol/ui/EditBox/EditBoxImpl-winrt.h" # include "axmol/ui/UIHelper.h" # include "axmol/platform/winrt/WinRTUtils.h" -# include "axmol/platform/winrt/RenderViewImpl-winrt.h" +# include "axmol/platform/winrt/RenderView-winrt.h" # include "axmol/2d/FontFreeType.h" # include @@ -65,7 +65,9 @@ EditBoxImpl* __createSystemEditBox(EditBox* pEditBox) EditBoxWinRT::EditBoxWinRT( winrt::delegate const& beginHandler, winrt::delegate const& changeHandler, - winrt::delegate const& endHandler) + winrt::delegate const& endHandler) : _beginHandler(beginHandler) , _changeHandler(changeHandler) , _endHandler(endHandler) @@ -78,8 +80,9 @@ EditBoxWinRT::EditBoxWinRT( , _multiline(false) , _maxLength(0 /* unlimited */) { - m_dispatcher = ax::RenderViewImpl::sharedRenderView()->getDispatcher(); - m_panel = ax::RenderViewImpl::sharedRenderView()->getPanel(); + auto renderView = static_cast(Director::getInstance()->getRenderView()); + m_dispatcher = renderView->getDispatcher(); + m_panel = renderView->getPanel(); } void EditBoxWinRT::closeKeyboard() @@ -146,8 +149,10 @@ void EditBoxWinRT::onTextChanged(Windows::Foundation::IInspectable const& sender { text = _textBox.as().Text(); } - std::shared_ptr inputEvent(new UIEditBoxEvent(*this, text, _changeHandler)); - ax::RenderViewImpl::sharedRenderView()->QueueEvent(inputEvent); + // std::shared_ptr inputEvent(new UIEditBoxEvent(*this, text, _changeHandler)); + // ax::RenderView::sharedRenderView()->QueueEvent(inputEvent); + // TODO: + Director::getInstance()->postTask([this, text] { _changeHandler(*this, text); }); } void EditBoxWinRT::onKeyDown(Windows::Foundation::IInspectable const& sender, @@ -167,8 +172,12 @@ void EditBoxWinRT::onGotFocus(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args) { Concurrency::critical_section::scoped_lock lock(_critical_section); - std::shared_ptr inputEvent(new UIEditBoxEvent(*this, winrt::hstring{}, _beginHandler)); - ax::RenderViewImpl::sharedRenderView()->QueueEvent(inputEvent); + + // std::shared_ptr inputEvent(new UIEditBoxEvent(*this, winrt::hstring{}, _beginHandler)); + // ax::RenderView::sharedRenderView()->QueueEvent(inputEvent); + // TODO: + Director::getInstance()->postTask([this] { _beginHandler(*this, L""); }); + _isEditing = true; } @@ -206,9 +215,10 @@ void EditBoxWinRT::onLostFocus(Windows::Foundation::IInspectable const& sender, _textBox.as().TextChanged(_changeToken); } - std::shared_ptr inputEvent( - new UIEditBoxEndEvent(*this, text, static_cast(action), _endHandler)); - ax::RenderViewImpl::sharedRenderView()->QueueEvent(inputEvent); + // std::shared_ptr inputEvent( + // new UIEditBoxEndEvent(*this, text, static_cast(action), _endHandler)); + // ax::RenderView::sharedRenderView()->QueueEvent(inputEvent); + Director::getInstance()->postTask([this, text, action] { _endHandler(*this, text, action); }); _textBox.LostFocus(_unfocusToken); _textBox.GotFocus(_focusToken); @@ -260,8 +270,22 @@ void EditBoxWinRT::openKeyboard() { _textBox.as().Select(_initialText.size(), 0); } + }); +} - auto inputPane = Windows::UI::ViewManagement::InputPane::GetForCurrentView(); +void EditBoxWinRT::applyRect() +{ + if (!_isEditing || !_textBox) + return; + + m_dispatcher.get().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [this]() { + if (!_textBox) + return; + Canvas canvas = findXamlElement(m_panel.get(), CANVAS_XAML_NAME).as(); + canvas.SetLeft(_textBox, _position.X); + canvas.SetTop(_textBox, _position.Y - XAML_TOP_PADDING); + _textBox.Width(_size.Width); + _textBox.Height(_size.Height); }); } @@ -437,17 +461,17 @@ void EditBoxWinRT::setVisible(bool visible) UIEditBoxImplWinrt::UIEditBoxImplWinrt(EditBox* pEditText) : EditBoxImplCommon(pEditText) { - auto beginHandler = ([this](Windows::Foundation::IInspectable const& sender, winrt::hstring const& arg) { + auto beginHandler = ([this](Windows::Foundation::IInspectable const& sender, winrt::hstring const& hstr) { this->editBoxEditingDidBegin(); }); - auto changeHandler = ([this](Windows::Foundation::IInspectable const& sender, winrt::hstring const& arg) { - auto text = PlatformStringToString(arg); + auto changeHandler = ([this](Windows::Foundation::IInspectable const& sender, winrt::hstring const& hstr) { + auto text = PlatformStringToString(hstr); this->editBoxEditingChanged(text); }); - auto endHandler = ([this](Windows::Foundation::IInspectable const& sender, ax::EndEventArgs const& arg) { - auto text = PlatformStringToString(arg.GetText()); - auto action = arg.GetAction(); - this->editBoxEditingDidEnd(text, static_cast(action)); + auto endHandler = ([this](Windows::Foundation::IInspectable const& sender, winrt::hstring const& hstr, + EditBoxDelegate::EditBoxEndAction action) { + auto text = PlatformStringToString(hstr); + this->editBoxEditingDidEnd(text, action); this->onEndEditing(text); }); @@ -468,8 +492,8 @@ void UIEditBoxImplWinrt::setNativeFont(std::string_view fontName, int fontSize) auto font = ax::FontFreeType::create(fontName, fontSize, ax::GlyphCollection::DYNAMIC, ""sv); if (font != nullptr) { - std::string fontName = fmt::format("ms-appx:///Content/{}#{}", fontName, font->getFontFamily()); - _system_control->setFontFamily(PlatformStringFromString(fontName)); + std::string family = fmt::format("ms-appx:///Content/{}#{}", fontName, font->getFontFamily()); + _system_control->setFontFamily(PlatformStringFromString(family)); } } @@ -504,14 +528,23 @@ void UIEditBoxImplWinrt::setNativeVisible(bool visible) _system_control->setVisible(visible); } -void UIEditBoxImplWinrt::updateNativeFrame(const Rect& rect) {} +void UIEditBoxImplWinrt::updateNativeFrame(const Rect& rect) +{ + + if (_system_control) + { + _system_control->setPosition(rect.origin.x, rect.origin.y); + _system_control->setSize(rect.size.width, rect.size.height); + _system_control->applyRect(); + } +} void UIEditBoxImplWinrt::nativeOpenKeyboard() { // Update the text _system_control->setText(PlatformStringFromString(getText())); - auto rect = ui::Helper::convertBoundingBoxToScreen(_editBox); + auto rect = ui::Helper::getNodeNativeWindowRect(_editBox); _system_control->setPosition(rect.origin.x, rect.origin.y); _system_control->setSize(rect.size.width, rect.size.height); diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl-winrt.h b/axmol/ui/EditBox/EditBoxImpl-winrt.h similarity index 94% rename from axmol/ui/UIEditBox/UIEditBoxImpl-winrt.h rename to axmol/ui/EditBox/EditBoxImpl-winrt.h index 48ec5e1cf9b1..fe4fcf714ff7 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl-winrt.h +++ b/axmol/ui/EditBox/EditBoxImpl-winrt.h @@ -28,7 +28,7 @@ THE SOFTWARE. #include "axmol/platform/PlatformConfig.h" #if AX_TARGET_PLATFORM == AX_PLATFORM_WINRT -# include "axmol/ui/UIEditBox/UIEditBoxImpl-common.h" +# include "axmol/ui/EditBox/EditBoxImpl-common.h" # include # include @@ -49,7 +49,9 @@ class EditBoxWinRT : public winrt::implements const& beginHandler, winrt::delegate const& changeHandler, - winrt::delegate const& endHandler); + winrt::delegate const& endHandler); void closeKeyboard(); bool isEditing(); @@ -66,6 +68,8 @@ class EditBoxWinRT : public winrt::implements _beginHandler = nullptr; winrt::delegate _changeHandler = nullptr; - winrt::delegate _endHandler = nullptr; + winrt::delegate + _endHandler = nullptr; winrt::event_token _unfocusToken; winrt::event_token _changeToken; @@ -130,7 +135,7 @@ class AX_GUI_DLL UIEditBoxImplWinrt : public EditBoxImplCommon virtual ~UIEditBoxImplWinrt() {}; bool isEditing() override { return _system_control.get()->isEditing(); } - void createNativeControl(const Rect& frame) override {} + void createNativeControl() override {} void setNativeFont(std::string_view fontName, int fontSize) override; void setNativeFontColor(const Color32& color) override; void setNativePlaceholderFont(std::string_view fontName, int fontSize) override diff --git a/axmol/ui/UIEditBox/UIEditBoxImpl.h b/axmol/ui/EditBox/EditBoxImpl.h similarity index 99% rename from axmol/ui/UIEditBox/UIEditBoxImpl.h rename to axmol/ui/EditBox/EditBoxImpl.h index 8f7cec53c1b3..d4a0e083a179 100644 --- a/axmol/ui/UIEditBox/UIEditBoxImpl.h +++ b/axmol/ui/EditBox/EditBoxImpl.h @@ -27,7 +27,7 @@ #pragma once -#include "axmol/ui/UIEditBox/UIEditBox.h" +#include "axmol/ui/EditBox/EditBox.h" namespace ax { diff --git a/axmol/ui/UIEditBox/Mac/UIEditBoxMac.h b/axmol/ui/EditBox/Mac/EditBoxMac.h similarity index 91% rename from axmol/ui/UIEditBox/Mac/UIEditBoxMac.h rename to axmol/ui/EditBox/Mac/EditBoxMac.h index 391f2649bf08..070b481ebae3 100644 --- a/axmol/ui/UIEditBox/Mac/UIEditBoxMac.h +++ b/axmol/ui/EditBox/Mac/EditBoxMac.h @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -27,8 +28,8 @@ #import #import -#include "axmol/ui/UIEditBox/UIEditBoxImpl-mac.h" -#include "axmol/ui/UIEditBox/Mac/UITextInput.h" +#include "axmol/ui/EditBox/EditBoxImpl-mac.h" +#include "axmol/ui/EditBox/Mac/TextInput.h" #pragma mark - UIEditBox mac implementation @@ -36,11 +37,11 @@ @interface UIEditBoxImplMac : NSObject { BOOL _editState; - NSView* _textInput; + NSView* _textInput; void* _editBox; } -@property(nonatomic, retain) NSView* textInput; +@property(nonatomic, retain) NSView* textInput; @property(nonatomic, readonly) NSWindow* window; @property(nonatomic, readonly, getter=isEditState) BOOL editState; diff --git a/axmol/ui/UIEditBox/Mac/UIEditBoxMac.mm b/axmol/ui/EditBox/Mac/EditBoxMac.mm similarity index 93% rename from axmol/ui/UIEditBox/Mac/UIEditBoxMac.mm rename to axmol/ui/EditBox/Mac/EditBoxMac.mm index 8b8d3e8491c9..e7f70c70deb6 100644 --- a/axmol/ui/UIEditBox/Mac/UIEditBoxMac.mm +++ b/axmol/ui/EditBox/Mac/EditBoxMac.mm @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -24,11 +25,11 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/Mac/UIEditBoxMac.h" +#import "axmol/ui/EditBox/Mac/EditBoxMac.h" #include "axmol/base/Director.h" -#include "axmol/ui/UIEditBox/Mac/UISingleLineTextField.h" -#include "axmol/ui/UIEditBox/Mac/UIPasswordTextField.h" -#include "axmol/ui/UIEditBox/Mac/UIMultilineTextField.h" +#include "axmol/ui/EditBox/Mac/SingleLineTextField.h" +#include "axmol/ui/EditBox/Mac/PasswordTextField.h" +#include "axmol/ui/EditBox/Mac/MultilineTextField.h" #define getEditBoxImplMac() ((ax::ui::EditBoxImplMac*)_editBox) @@ -56,32 +57,32 @@ - (instancetype)initWithFrame:(NSRect)frameRect editBox:(void*)editBox - (void)createSingleLineTextField { - CCUISingleLineTextField* textField = [[[CCUISingleLineTextField alloc] initWithFrame:self.frameRect] autorelease]; + AxmolSingleLineTextField* textField = [[[AxmolSingleLineTextField alloc] initWithFrame:self.frameRect] autorelease]; self.textInput = textField; } - (void)createMultiLineTextField { - CCUIMultilineTextField* textView = [[[CCUIMultilineTextField alloc] initWithFrame:self.frameRect] autorelease]; + AxmolMultilineTextField* textView = [[[AxmolMultilineTextField alloc] initWithFrame:self.frameRect] autorelease]; [textView setVerticallyResizable:NO]; self.textInput = textView; } - (void)createPasswordTextField { - CCUIPasswordTextField* textField = [[[CCUIPasswordTextField alloc] initWithFrame:self.frameRect] autorelease]; + AxmolPasswordTextField* textField = [[[AxmolPasswordTextField alloc] initWithFrame:self.frameRect] autorelease]; self.textInput = textField; } -- (void)setTextInput:(NSView*)textInput +- (void)setTextInput:(NSView*)textInput { if (_textInput == textInput) return; - NSView* oldInput = _textInput; - _textInput = textInput; + NSView* oldInput = _textInput; + _textInput = textInput; if (_textInput != nil) { [_textInput retain]; // retain new input view diff --git a/axmol/ui/UIEditBox/Mac/UIMultilineTextField.h b/axmol/ui/EditBox/Mac/MultilineTextField.h similarity index 88% rename from axmol/ui/UIEditBox/Mac/UIMultilineTextField.h rename to axmol/ui/EditBox/Mac/MultilineTextField.h index 02ae18afe325..1e81856bf699 100644 --- a/axmol/ui/UIEditBox/Mac/UIMultilineTextField.h +++ b/axmol/ui/EditBox/Mac/MultilineTextField.h @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -26,9 +27,9 @@ #pragma once #import -#include "axmol/ui/UIEditBox/Mac/UITextInput.h" +#include "axmol/ui/EditBox/Mac/TextInput.h" -@interface CCUIMultilineTextField : NSTextView { +@interface AxmolMultilineTextField : NSTextView { NSString* _placeHolder; } diff --git a/axmol/ui/UIEditBox/Mac/UIMultilineTextField.m b/axmol/ui/EditBox/Mac/MultilineTextField.m similarity index 91% rename from axmol/ui/UIEditBox/Mac/UIMultilineTextField.m rename to axmol/ui/EditBox/Mac/MultilineTextField.m index 035a85ccbb8f..f326abcc9987 100644 --- a/axmol/ui/UIEditBox/Mac/UIMultilineTextField.m +++ b/axmol/ui/EditBox/Mac/MultilineTextField.m @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -23,13 +24,13 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/Mac/UIMultilineTextField.h" +#import "axmol/ui/EditBox/Mac/MultilineTextField.h" -@interface CCUIMultilineTextField() +@interface AxmolMultilineTextField() @property(nonatomic, copy)NSString* placeHolder; @end -@implementation CCUIMultilineTextField +@implementation AxmolMultilineTextField { } @@ -73,7 +74,7 @@ -(NSColor*)axui_placeholderColor } -#pragma mark - AXUITextInput +#pragma mark - AxmolTextInput - (NSString *)axui_text { return self.string; diff --git a/axmol/ui/UIEditBox/Mac/UIPasswordTextField.h b/axmol/ui/EditBox/Mac/PasswordTextField.h similarity index 88% rename from axmol/ui/UIEditBox/Mac/UIPasswordTextField.h rename to axmol/ui/EditBox/Mac/PasswordTextField.h index c24aa53c3257..1acc5c730098 100644 --- a/axmol/ui/UIEditBox/Mac/UIPasswordTextField.h +++ b/axmol/ui/EditBox/Mac/PasswordTextField.h @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -26,9 +27,9 @@ #pragma once #import -#include "axmol/ui/UIEditBox/Mac/UITextInput.h" +#include "axmol/ui/EditBox/Mac/TextInput.h" -@interface CCUIPasswordTextField : NSSecureTextField { +@interface AxmolPasswordTextField : NSSecureTextField { } @end diff --git a/axmol/ui/UIEditBox/Mac/UIPasswordTextField.m b/axmol/ui/EditBox/Mac/PasswordTextField.m similarity index 93% rename from axmol/ui/UIEditBox/Mac/UIPasswordTextField.m rename to axmol/ui/EditBox/Mac/PasswordTextField.m index c7887a8de0f3..01bcc648e037 100644 --- a/axmol/ui/UIEditBox/Mac/UIPasswordTextField.m +++ b/axmol/ui/EditBox/Mac/PasswordTextField.m @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -23,8 +24,8 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/Mac/UIPasswordTextField.h" -#include "axmol/ui/UIEditBox/Mac/UITextFieldFormatter.h" +#import "axmol/ui/EditBox/Mac/PasswordTextField.h" +#include "axmol/ui/EditBox/Mac/TextFieldFormatter.h" @interface RSVerticallyCenteredSecureTextFieldCell : NSSecureTextFieldCell { @@ -98,14 +99,14 @@ - (void)editWithFrame:(NSRect)aRect @end -@interface CCUIPasswordTextField() +@interface AxmolPasswordTextField() { } @end -@implementation CCUIPasswordTextField +@implementation AxmolPasswordTextField -(id) initWithFrame:(NSRect)frameRect { @@ -159,7 +160,7 @@ -(void)axui_setPlaceholderColor:(NSColor *)color //TODO; } -#pragma mark - AXUITextInput +#pragma mark - AxmolTextInput - (NSString *)axui_text { return self.stringValue; @@ -208,7 +209,7 @@ - (void)axui_setDelegate:(id)delegate - (void)axui_setMaxLength:(int)length { - id formater = [[[CCUITextFieldFormatter alloc]init] autorelease]; + id formater = [[[AxmolTextFieldFormatter alloc]init] autorelease]; [formater setMaximumLength:length]; [self setFormatter:formater]; } diff --git a/axmol/ui/UIEditBox/Mac/UISingleLineTextField.h b/axmol/ui/EditBox/Mac/SingleLineTextField.h similarity index 88% rename from axmol/ui/UIEditBox/Mac/UISingleLineTextField.h rename to axmol/ui/EditBox/Mac/SingleLineTextField.h index e1602fc27c83..e618b62ef9c7 100644 --- a/axmol/ui/UIEditBox/Mac/UISingleLineTextField.h +++ b/axmol/ui/EditBox/Mac/SingleLineTextField.h @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -26,9 +27,9 @@ #pragma once #import -#include "axmol/ui/UIEditBox/Mac/UITextInput.h" +#include "axmol/ui/EditBox/Mac/TextInput.h" -@interface CCUISingleLineTextField : NSTextField { +@interface AxmolSingleLineTextField : NSTextField { } @end diff --git a/axmol/ui/UIEditBox/Mac/UISingleLineTextField.m b/axmol/ui/EditBox/Mac/SingleLineTextField.m similarity index 94% rename from axmol/ui/UIEditBox/Mac/UISingleLineTextField.m rename to axmol/ui/EditBox/Mac/SingleLineTextField.m index abacfc1e3f7d..b1eae3d30de4 100644 --- a/axmol/ui/UIEditBox/Mac/UISingleLineTextField.m +++ b/axmol/ui/EditBox/Mac/SingleLineTextField.m @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -23,8 +24,8 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/Mac/UISingleLineTextField.h" -#include "axmol/ui/UIEditBox/Mac/UITextFieldFormatter.h" +#import "axmol/ui/EditBox/Mac/SingleLineTextField.h" +#include "axmol/ui/EditBox/Mac/TextFieldFormatter.h" @interface RSVerticallyCenteredTextFieldCell : NSTextFieldCell { @@ -99,7 +100,7 @@ - (void)editWithFrame:(NSRect)aRect @end -@implementation CCUISingleLineTextField +@implementation AxmolSingleLineTextField { } @@ -198,7 +199,7 @@ - (void)axui_setDelegate:(id)delegate - (void)axui_setMaxLength:(int)length { - id formater = [[[CCUITextFieldFormatter alloc]init] autorelease]; + id formater = [[[AxmolTextFieldFormatter alloc]init] autorelease]; [formater setMaximumLength:length]; [self setFormatter:formater]; } diff --git a/axmol/ui/UIEditBox/Mac/UITextFieldFormatter.h b/axmol/ui/EditBox/Mac/TextFieldFormatter.h similarity index 92% rename from axmol/ui/UIEditBox/Mac/UITextFieldFormatter.h rename to axmol/ui/EditBox/Mac/TextFieldFormatter.h index c120bd9ef97e..b9bccec7f65d 100644 --- a/axmol/ui/UIEditBox/Mac/UITextFieldFormatter.h +++ b/axmol/ui/EditBox/Mac/TextFieldFormatter.h @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -27,7 +28,7 @@ #import -@interface CCUITextFieldFormatter : NSFormatter { +@interface AxmolTextFieldFormatter : NSFormatter { int _maximumLength; } diff --git a/axmol/ui/UIEditBox/Mac/UITextFieldFormatter.m b/axmol/ui/EditBox/Mac/TextFieldFormatter.m similarity index 92% rename from axmol/ui/UIEditBox/Mac/UITextFieldFormatter.m rename to axmol/ui/EditBox/Mac/TextFieldFormatter.m index 350273544e36..5fff03d44090 100644 --- a/axmol/ui/UIEditBox/Mac/UITextFieldFormatter.m +++ b/axmol/ui/EditBox/Mac/TextFieldFormatter.m @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -23,9 +24,9 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/Mac/UITextFieldFormatter.h" +#import "axmol/ui/EditBox/Mac/TextFieldFormatter.h" -@implementation CCUITextFieldFormatter +@implementation AxmolTextFieldFormatter { } diff --git a/axmol/ui/UIEditBox/Mac/UITextInput.h b/axmol/ui/EditBox/Mac/TextInput.h similarity index 95% rename from axmol/ui/UIEditBox/Mac/UITextInput.h rename to axmol/ui/EditBox/Mac/TextInput.h index a5c1ae4190c8..0c2fa21cb844 100644 --- a/axmol/ui/UIEditBox/Mac/UITextInput.h +++ b/axmol/ui/EditBox/Mac/TextInput.h @@ -2,6 +2,7 @@ Copyright (c) 2010-2012 cocos2d-x.org Copyright (c) 2013-2016 zilongshanren Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -29,7 +30,7 @@ /** This protocol provides a common interface for consolidating text input method calls */ -@protocol AXUITextInput +@protocol AxmolTextInput @property(nonatomic, retain, setter=axui_setText:) NSString* axui_text; @property(nonatomic, retain, setter=axui_setTextColor:) NSColor* axui_textColor; diff --git a/axmol/ui/UIEditBox/iOS/UIEditBoxIOS.h b/axmol/ui/EditBox/iOS/EditBoxIOS.h similarity index 92% rename from axmol/ui/UIEditBox/iOS/UIEditBoxIOS.h rename to axmol/ui/EditBox/iOS/EditBoxIOS.h index 2160c139b008..994be0c4a644 100644 --- a/axmol/ui/UIEditBox/iOS/UIEditBoxIOS.h +++ b/axmol/ui/EditBox/iOS/EditBoxIOS.h @@ -4,6 +4,7 @@ Copyright (c) 2013-2015 zilongshanren Copyright (c) 2015 Mazyad Alabduljaleel Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -28,12 +29,12 @@ #pragma once #import -#import "axmol/ui/UIEditBox/iOS/UITextInput.h" -#include "axmol/ui/UIEditBox/UIEditBoxImpl-ios.h" +#import "axmol/ui/EditBox/iOS/TextInput.h" +#include "axmol/ui/EditBox/EditBoxImpl-ios.h" @interface UIEditBoxImplIOS_objc : NSObject -@property(nonatomic, retain) UIView* textInput; +@property(nonatomic, retain) UIView* textInput; @property(nonatomic, assign) void* editBox; @property(nonatomic, assign) NSString* text; @property(nonatomic, assign) CGRect frameRect; diff --git a/axmol/ui/UIEditBox/iOS/UIEditBoxIOS.mm b/axmol/ui/EditBox/iOS/EditBoxIOS.mm similarity index 94% rename from axmol/ui/UIEditBox/iOS/UIEditBoxIOS.mm rename to axmol/ui/EditBox/iOS/EditBoxIOS.mm index 49a9ccd5f66b..00f5c4ad2355 100644 --- a/axmol/ui/UIEditBox/iOS/UIEditBoxIOS.mm +++ b/axmol/ui/EditBox/iOS/EditBoxIOS.mm @@ -4,6 +4,7 @@ Copyright (c) 2013-2015 zilongshanren Copyright (c) 2015 Mazyad Alabduljaleel Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -26,9 +27,9 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/iOS/UIEditBoxIOS.h" -#import "axmol/ui/UIEditBox/iOS/UISingleLineTextField.h" -#import "axmol/ui/UIEditBox/iOS/UIMultilineTextField.h" +#import "axmol/ui/EditBox/iOS/EditBoxIOS.h" +#import "axmol/ui/EditBox/iOS/SingleLineTextField.h" +#import "axmol/ui/EditBox/iOS/MultilineTextField.h" #import "axmol/platform/ios/RenderHostView-ios.h" #include "axmol/base/Director.h" @@ -42,9 +43,6 @@ @implementation UIEditBoxImplIOS_objc + (void)initialize { [super initialize]; - - LoadUITextViewAXUITextInputCategory(); - LoadUITextFieldAXUITextInputCategory(); } #pragma mark - Init & Dealloc @@ -77,7 +75,7 @@ - (void)dealloc #pragma mark - Properties -- (void)setTextInput:(UIView*)textInput +- (void)setTextInput:(UIView*)textInput { if (_textInput == textInput) { @@ -112,9 +110,9 @@ - (void)setTextInput:(UIView*)textInput - (void)createSingleLineTextField { - CCUISingleLineTextField* textField = [[[CCUISingleLineTextField alloc] initWithFrame:self.frameRect] autorelease]; - textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; - textField.borderStyle = UITextBorderStyleNone; + AxmolSingleLineTextField* textField = [[[AxmolSingleLineTextField alloc] initWithFrame:self.frameRect] autorelease]; + textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; + textField.borderStyle = UITextBorderStyleNone; [textField addTarget:self action:@selector(textChanged:) forControlEvents:UIControlEventEditingChanged]; @@ -123,8 +121,8 @@ - (void)createSingleLineTextField - (void)createMultiLineTextField { - CCUIMultilineTextField* textView = [[[CCUIMultilineTextField alloc] initWithFrame:self.frameRect] autorelease]; - self.textInput = textView; + AxmolMultilineTextField* textView = [[[AxmolMultilineTextField alloc] initWithFrame:self.frameRect] autorelease]; + self.textInput = textView; } #pragma mark - Public methods @@ -339,6 +337,8 @@ - (void)openKeyboard auto view = ax::Director::getInstance()->getRenderView(); auto hostView = (__bridge RenderHostView*)view->getNativeDisplay(); + self.textInput.contentScaleFactor = [hostView contentScaleFactor]; + [hostView addSubview:self.textInput]; [self.textInput becomeFirstResponder]; } diff --git a/axmol/ui/UIEditBox/iOS/UIMultilineTextField.h b/axmol/ui/EditBox/iOS/MultilineTextField.h similarity index 87% rename from axmol/ui/UIEditBox/iOS/UIMultilineTextField.h rename to axmol/ui/EditBox/iOS/MultilineTextField.h index 4bf84115b17e..7683c7c613ad 100644 --- a/axmol/ui/UIEditBox/iOS/UIMultilineTextField.h +++ b/axmol/ui/EditBox/iOS/MultilineTextField.h @@ -3,6 +3,7 @@ Copyright (c) 2012 James Chen Copyright (c) 2015 Mazyad Alabduljaleel Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -27,11 +28,11 @@ #pragma once #import -#import "axmol/ui/UIEditBox/iOS/UITextView+UITextInput.h" +#import "axmol/ui/EditBox/iOS/TextView.h" -#pragma mark - UIMultilineTextField implementation +#pragma mark - AxmolMultilineTextField implementation -@interface CCUIMultilineTextField : UITextView +@interface AxmolMultilineTextField : UITextView @property(nonatomic, assign) NSString* placeholder; @property(nonatomic, retain) UILabel* placeHolderLabel; diff --git a/axmol/ui/UIEditBox/iOS/UIMultilineTextField.mm b/axmol/ui/EditBox/iOS/MultilineTextField.mm similarity index 96% rename from axmol/ui/UIEditBox/iOS/UIMultilineTextField.mm rename to axmol/ui/EditBox/iOS/MultilineTextField.mm index 79f1a82f0df9..6eeb9fd977e8 100644 --- a/axmol/ui/UIEditBox/iOS/UIMultilineTextField.mm +++ b/axmol/ui/EditBox/iOS/MultilineTextField.mm @@ -3,6 +3,7 @@ Copyright (c) 2012 James Chen Copyright (c) 2015 Mazyad Alabduljaleel Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -25,7 +26,7 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/iOS/UIMultilineTextField.h" +#import "axmol/ui/EditBox/iOS/MultilineTextField.h" #include "axmol/base/Director.h" @@ -34,7 +35,7 @@ of this software and associated documentation files (the "Software"), to deal */ CGFloat const UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25; -@implementation CCUIMultilineTextField +@implementation AxmolMultilineTextField #pragma mark - Init & Dealloc diff --git a/axmol/ui/UIEditBox/iOS/UISingleLineTextField.h b/axmol/ui/EditBox/iOS/SingleLineTextField.h similarity index 87% rename from axmol/ui/UIEditBox/iOS/UISingleLineTextField.h rename to axmol/ui/EditBox/iOS/SingleLineTextField.h index ef5443f1249c..346d0c4dc4a8 100644 --- a/axmol/ui/UIEditBox/iOS/UISingleLineTextField.h +++ b/axmol/ui/EditBox/iOS/SingleLineTextField.h @@ -3,6 +3,7 @@ Copyright (c) 2012 James Chen Copyright (c) 2015 Mazyad Alabduljaleel Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -27,11 +28,11 @@ #pragma once #import -#import "axmol/ui/UIEditBox/iOS/UITextField+UITextInput.h" +#import "axmol/ui/EditBox/iOS/TextField.h" -#pragma mark - UISingleLineTextField implementation +#pragma mark - AxmolSingleLineTextField implementation -@interface CCUISingleLineTextField : UITextField +@interface AxmolSingleLineTextField : UITextField @property(nonatomic, retain) UIColor* placeholderTextColor; @property(nonatomic, retain) UIFont* placeholderFont; diff --git a/axmol/ui/UIEditBox/iOS/UISingleLineTextField.mm b/axmol/ui/EditBox/iOS/SingleLineTextField.mm similarity index 92% rename from axmol/ui/UIEditBox/iOS/UISingleLineTextField.mm rename to axmol/ui/EditBox/iOS/SingleLineTextField.mm index 04f79d1888b9..a78cb57eaad8 100644 --- a/axmol/ui/UIEditBox/iOS/UISingleLineTextField.mm +++ b/axmol/ui/EditBox/iOS/SingleLineTextField.mm @@ -3,6 +3,7 @@ Copyright (c) 2012 James Chen Copyright (c) 2015 Mazyad Alabduljaleel Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -25,8 +26,8 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/iOS/UISingleLineTextField.h" -#import "axmol/ui/UIEditBox/iOS/UITextInput.h" +#import "axmol/ui/EditBox/iOS/SingleLineTextField.h" +#import "axmol/ui/EditBox/iOS/TextInput.h" #include "axmol/base/Director.h" @@ -34,7 +35,7 @@ of this software and associated documentation files (the "Software"), to deal * http://stackoverflow.com/questions/18244790/changing-uitextfield-placeholder-font */ -@implementation CCUISingleLineTextField +@implementation AxmolSingleLineTextField #pragma mark - Init & Dealloc diff --git a/axmol/ui/EditBox/iOS/TextField.h b/axmol/ui/EditBox/iOS/TextField.h new file mode 100644 index 000000000000..ad11e5f9cb57 --- /dev/null +++ b/axmol/ui/EditBox/iOS/TextField.h @@ -0,0 +1,32 @@ +/**************************************************************************** + Copyright (c) 2015 Mazyad Alabduljaleel + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + + https://axmol.dev/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +#pragma once + +#import +#import "axmol/ui/EditBox/iOS/TextInput.h" + +@interface UITextField (AxmolTextInput) +@end diff --git a/axmol/ui/UIEditBox/iOS/UITextField+UITextInput.mm b/axmol/ui/EditBox/iOS/TextField.mm similarity index 95% rename from axmol/ui/UIEditBox/iOS/UITextField+UITextInput.mm rename to axmol/ui/EditBox/iOS/TextField.mm index 17a69a37b4ef..029eda8a1874 100644 --- a/axmol/ui/UIEditBox/iOS/UITextField+UITextInput.mm +++ b/axmol/ui/EditBox/iOS/TextField.mm @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2015 Mazyad Alabduljaleel Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -23,9 +24,9 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/iOS/UITextField+UITextInput.h" +#import "axmol/ui/EditBox/iOS/TextField.h" -@implementation UITextField (AXUITextInput) +@implementation UITextField (AxmolTextInput) - (NSString*)axui_text { @@ -131,8 +132,3 @@ - (void)axui_setDelegate:(id)delegate } @end - -void LoadUITextFieldAXUITextInputCategory() -{ - // noop -} diff --git a/axmol/ui/UIEditBox/iOS/UITextInput.h b/axmol/ui/EditBox/iOS/TextInput.h similarity index 95% rename from axmol/ui/UIEditBox/iOS/UITextInput.h rename to axmol/ui/EditBox/iOS/TextInput.h index 255d6d1f64a0..78d098deeb37 100644 --- a/axmol/ui/UIEditBox/iOS/UITextInput.h +++ b/axmol/ui/EditBox/iOS/TextInput.h @@ -1,6 +1,7 @@ /**************************************************************************** Copyright (c) 2015 Mazyad Alabduljaleel Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). https://axmol.dev/ @@ -30,7 +31,7 @@ static const int AX_EDIT_BOX_PADDING = 5; /** This protocol provides a common interface for consolidating text input method calls */ -@protocol AXUITextInput +@protocol AxmolTextInput @property(nonatomic, retain, setter=axui_setText:) NSString* axui_text; @property(nonatomic, retain, setter=axui_setPlaceholder:) NSString* axui_placeholder; diff --git a/axmol/ui/EditBox/iOS/TextView.h b/axmol/ui/EditBox/iOS/TextView.h new file mode 100644 index 000000000000..c8010f3f1dd6 --- /dev/null +++ b/axmol/ui/EditBox/iOS/TextView.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2015 Mazyad Alabduljaleel + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + https://axmol.dev/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +#pragma once + +#import +#import "axmol/ui/EditBox/iOS/TextInput.h" + +@interface UITextView (AxmolTextInput) +@end diff --git a/axmol/ui/UIEditBox/iOS/UITextView+UITextInput.mm b/axmol/ui/EditBox/iOS/TextView.mm similarity index 96% rename from axmol/ui/UIEditBox/iOS/UITextView+UITextInput.mm rename to axmol/ui/EditBox/iOS/TextView.mm index 3bae4adf1d64..a83e2c5fed80 100644 --- a/axmol/ui/UIEditBox/iOS/UITextView+UITextInput.mm +++ b/axmol/ui/EditBox/iOS/TextView.mm @@ -23,9 +23,9 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#import "axmol/ui/UIEditBox/iOS/UITextView+UITextInput.h" +#import "axmol/ui/EditBox/iOS/TextView.h" -@implementation UITextView (AXUITextInput) +@implementation UITextView (AxmolTextInput) - (NSString*)axui_text { @@ -140,5 +140,3 @@ - (void)axui_setDelegate:(id)delegate } @end - -void LoadUITextViewAXUITextInputCategory() {} diff --git a/axmol/ui/UIHBox.cpp b/axmol/ui/HBox.cpp similarity index 95% rename from axmol/ui/UIHBox.cpp rename to axmol/ui/HBox.cpp index 2b0d79ec0097..4de05d2f6256 100644 --- a/axmol/ui/UIHBox.cpp +++ b/axmol/ui/HBox.cpp @@ -23,7 +23,7 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIHBox.h" +#include "axmol/ui/HBox.h" namespace ax { @@ -61,9 +61,9 @@ HBox* HBox::create(const Vec2& size) bool HBox::init() { - if (Layout::init()) + if (LayoutGroup::init()) { - setLayoutType(Layout::Type::HORIZONTAL); + setLayoutType(LayoutGroup::Type::HORIZONTAL); return true; } return false; diff --git a/axmol/ui/UIHBox.h b/axmol/ui/HBox.h similarity index 96% rename from axmol/ui/UIHBox.h rename to axmol/ui/HBox.h index bb89cf2af2d8..39a2c3a1ef67 100644 --- a/axmol/ui/UIHBox.h +++ b/axmol/ui/HBox.h @@ -26,7 +26,7 @@ #pragma once -#include "axmol/ui/UILayout.h" +#include "axmol/ui/LayoutGroup.h" #include "axmol/ui/GUIExport.h" namespace ax @@ -43,7 +43,7 @@ namespace ui * HBox is just a convenient wrapper class for horizontal layout type. * HBox lays out its children in a single horizontal row. */ -class AX_GUI_DLL HBox : public Layout +class AX_GUI_DLL HBox : public LayoutGroup { public: /** diff --git a/axmol/ui/UIImageView.cpp b/axmol/ui/ImageView.cpp similarity index 87% rename from axmol/ui/UIImageView.cpp rename to axmol/ui/ImageView.cpp index c501161a6009..1f658e82f42e 100644 --- a/axmol/ui/UIImageView.cpp +++ b/axmol/ui/ImageView.cpp @@ -24,8 +24,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIImageView.h" -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/ImageView.h" +#include "axmol/ui/Scale9Sprite.h" #include "axmol/ui/UIHelper.h" #include "axmol/2d/Sprite.h" @@ -41,7 +41,7 @@ IMPLEMENT_CLASS_GUI_INFO(ImageView) ImageView::ImageView() : _scale9Enabled(false) - , _prevIgnoreSize(true) + , _prevAutoSize(true) , _capInsets(Rect::ZERO) , _imageRenderer(nullptr) , _imageTexType(TextureResType::LOCAL) @@ -107,7 +107,7 @@ bool ImageView::init(std::string_view imageFileName, TextureResType texType) return bRet; } -void ImageView::initRenderer() +void ImageView::initRenderNode() { _imageRenderer = Scale9Sprite::create(); _imageRenderer->setRenderingType(Scale9Sprite::RenderingType::SIMPLE); @@ -135,7 +135,7 @@ void ImageView::loadTexture(std::string_view fileName, TextureResType texType) break; } // FIXME: https://github.com/cocos2d/cocos2d-x/issues/12249 - if (!_ignoreSize && _customSize.equals(Vec2::ZERO)) + if (!_autoSize && _customSize.equals(Vec2::ZERO)) { _customSize = _imageRenderer->getContentSize(); } @@ -154,7 +154,7 @@ void ImageView::setupTexture() this->updateChildrenDisplayedRGBA(); - updateContentSizeWithTextureSize(_imageTextureSize); + updateContentSize(); _imageRendererAdaptDirty = true; } @@ -197,13 +197,13 @@ void ImageView::setScale9Enabled(bool able) if (_scale9Enabled) { - bool ignoreBefore = _ignoreSize; - ignoreContentAdaptWithSize(false); - _prevIgnoreSize = ignoreBefore; + bool autoSizeBefore = _autoSize; + setAutoSize(false); + _prevAutoSize = autoSizeBefore; } else { - ignoreContentAdaptWithSize(_prevIgnoreSize); + setAutoSize(_prevAutoSize); } setCapInsets(_capInsets); _imageRendererAdaptDirty = true; @@ -214,15 +214,6 @@ bool ImageView::isScale9Enabled() const return _scale9Enabled; } -void ImageView::ignoreContentAdaptWithSize(bool ignore) -{ - if (!_scale9Enabled || (_scale9Enabled && !ignore)) - { - Widget::ignoreContentAdaptWithSize(ignore); - _prevIgnoreSize = ignore; - } -} - void ImageView::setCapInsets(const Rect& capInsets) { _capInsets = ui::Helper::restrictCapInsetRect(capInsets, _imageTextureSize); @@ -244,7 +235,7 @@ void ImageView::onSizeChanged() _imageRendererAdaptDirty = true; } -void ImageView::adaptRenderers() +void ImageView::updateLayout() { if (_imageRendererAdaptDirty) { @@ -253,19 +244,19 @@ void ImageView::adaptRenderers() } } -Vec2 ImageView::getVirtualRendererSize() const +Vec2 ImageView::resolvePreferredSize(const Vec2& /*sizeHint*/) const { return _imageTextureSize; } -Node* ImageView::getVirtualRenderer() +Node* ImageView::getRenderNode() { return _imageRenderer; } void ImageView::imageTextureScaleChangedWithSize() { - _imageRenderer->setPreferredSize(_contentSize); + _imageRenderer->setContentSize(_contentSize); _imageRenderer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); } @@ -285,7 +276,7 @@ void ImageView::copySpecialProperties(Widget* widget) ImageView* imageView = dynamic_cast(widget); if (imageView) { - _prevIgnoreSize = imageView->_prevIgnoreSize; + _prevAutoSize = imageView->_prevAutoSize; setScale9Enabled(imageView->_scale9Enabled); auto imageSprite = imageView->_imageRenderer->getSprite(); if (nullptr != imageSprite) @@ -314,6 +305,17 @@ const BlendFunc& ImageView::getBlendFunc() const return _imageRenderer->getBlendFunc(); } +void ImageView::setAutoSize(bool autoSize) +{ + // Note: autoSize=true means adapt to content, autoSize=false means fixed size + // For Scale9Sprite, we need special handling + if (!_scale9Enabled || (_scale9Enabled && !autoSize)) + { + Widget::setAutoSize(autoSize); + _prevAutoSize = autoSize; // Store the current value for backward compatibility + } +} + } // namespace ui } // namespace ax diff --git a/axmol/ui/UIImageView.h b/axmol/ui/ImageView.h similarity index 94% rename from axmol/ui/UIImageView.h rename to axmol/ui/ImageView.h index 208f1decec3d..1ea29b236d7b 100644 --- a/axmol/ui/UIImageView.h +++ b/axmol/ui/ImageView.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" /** @@ -136,10 +136,10 @@ class AX_GUI_DLL ImageView : public Widget, public ax::BlendProtocol const BlendFunc& getBlendFunc() const override; // override methods. - void ignoreContentAdaptWithSize(bool ignore) override; + void setAutoSize(bool autoSize) override; std::string getDescription() const override; - Vec2 getVirtualRendererSize() const override; - Node* getVirtualRenderer() override; + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; + Node* getRenderNode() override; ResourceData getRenderFile(); @@ -148,10 +148,10 @@ class AX_GUI_DLL ImageView : public Widget, public ax::BlendProtocol virtual bool init(std::string_view imageFileName, TextureResType texType = TextureResType::LOCAL); protected: - void initRenderer() override; + void initRenderNode() override; void onSizeChanged() override; - void adaptRenderers() override; + void updateLayout() override; void loadTexture(SpriteFrame* spriteframe); void setupTexture(); @@ -161,7 +161,7 @@ class AX_GUI_DLL ImageView : public Widget, public ax::BlendProtocol protected: bool _scale9Enabled; - bool _prevIgnoreSize; + bool _prevAutoSize; Rect _capInsets; Scale9Sprite* _imageRenderer; TextureResType _imageTexType; diff --git a/axmol/ui/InputField.cpp b/axmol/ui/InputField.cpp new file mode 100644 index 000000000000..29b4f888c0a9 --- /dev/null +++ b/axmol/ui/InputField.cpp @@ -0,0 +1,1875 @@ +/**************************************************************************** +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "axmol/ui/InputField.h" +#include "axmol/base/Director.h" +#include "axmol/base/text_utils.h" +#include "axmol/platform/Device.h" +#include "axmol/platform/RenderView.h" + +#include +#include +#include + +namespace ax::ui +{ + +#if defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP +# define axbeep(t) MessageBeep(t) +#else +# define axbeep(t) +#endif + +namespace +{ +static constexpr int INPUT_FIELD_RENDERER_Z = (-1); +static constexpr std::string_view DEFAULT_PASSWORD_CHAR = "\xe2\x80\xa2"sv; +static FontType labelTypeToFontType(Label::LabelType type) +{ + switch (type) + { + case Label::LabelType::STRING_TEXTURE: + return FontType::SYSTEM; + case Label::LabelType::TTF: + return FontType::TTF; + case Label::LabelType::BMFONT: + return FontType::BMFONT; + default: + return FontType::SYSTEM; + } +} + +static Node* findUIRoot(Node* current) +{ + ax::Node* top2DNode = nullptr; + + // Get the target mask bit for the 2D default camera (usually 1) + uint16_t default2DMask = static_cast(ax::CameraFlag::DEFAULT); + + // Climb up the scene graph + while (current != nullptr) + { + auto parent = current->getParent(); + + // Check if the parent is the Scene (meaning 'current' is a top-level node) + if (parent && dynamic_cast(parent) != nullptr) + { + // Verify if this top-level node is rendered by the 2D default camera + if (current->getCameraMask() & default2DMask) + { + top2DNode = current; + break; + } + } + current = parent; + } + + // Fallback to parent if no scene-level top node matched + return top2DNode ? top2DNode : current->getParent(); +} + +} // namespace + +////////////////////////////////////////////////////////////////////////// +// constructor and destructor +////////////////////////////////////////////////////////////////////////// +bool InputField::s_keyboardVisible = false; + +IMPLEMENT_CLASS_GUI_INFO(InputField) + +InputField::InputField() +{ + _passwordChar = DEFAULT_PASSWORD_CHAR; +} + +InputField::~InputField() +{ + if (_kbdListener != nullptr) + _eventDispatcher->removeEventListener(_kbdListener); + + // Release instance-specific measure label + AX_SAFE_RELEASE_NULL(_measureLabel); +} + +////////////////////////////////////////////////////////////////////////// +// static constructor +////////////////////////////////////////////////////////////////////////// + +InputField* InputField::create() +{ + InputField* ret = new InputField(); + if (ret && ret->initWithPlaceholder(""sv, "Arial"sv, 24, 2, Color32::BLACK)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} +InputField* InputField::create(std::string_view placeholder, + std::string_view fontName, + float fontSize, + float cursorWidth, + const Color32& cursorColor) +{ + InputField* ret = new InputField(); + if (ret && ret->initWithPlaceholder(placeholder, fontName, fontSize, cursorWidth, cursorColor)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +////////////////////////////////////////////////////////////////////////// +// text measurement +////////////////////////////////////////////////////////////////////////// +Vec2 InputField::measureText(std::string_view text) const +{ + // Lazy initialize instance-specific measure label + if (!_measureLabel) + { + _measureLabel = Label::create(""sv, _fontName, _fontSize); + _measureLabel->retain(); // Manual reference counting + } + + // Measure text (font is already synced in setFontName/setFontSize) + _measureLabel->setString(text); + return _measureLabel->getContentSize(); +} + +////////////////////////////////////////////////////////////////////////// +// initialize +////////////////////////////////////////////////////////////////////////// +bool InputField::initWithPlaceholder(std::string_view placeholder, + std::string_view fontName, + float fontSize, + float cursorWidth, + const Color32& cursorColor) +{ + + ui::Widget::init(); + + _fontName = fontName; + _fontSize = fontSize; + _placeholderText = placeholder; + + /// render label + // Create render label according to font type: BMFont (.fnt), TTF file, or system font + _renderLabel = Label::create(placeholder, fontName, fontSize); + AXASSERT(_renderLabel, "Failed to create render label for InputField"); + _fontType = labelTypeToFontType(_renderLabel->getLabelType()); + + if (!placeholder.empty()) + _renderLabel->setTextColor(_colorSpaceHolder); + this->addProtectedChild(_renderLabel, INPUT_FIELD_RENDERER_Z); + + _renderLabel->setAnchorPoint(Vec2::ANCHOR_MIDDLE); + + /// selection layer + _selectionLayer = DrawNode::create(); + this->addProtectedChild(_selectionLayer); + + /// Line Metrics + updateLineHeight(); + + _passwordCharWidth = measureText(_passwordChar).width; + + // Initialize content size based on placeholder text + updateContentSize(); + + /// cursor + _cursor = Sprite::createWithTexture(_director->getTextureCache()->getWhiteTexture()); + _cursor->setContentSize(Vec2{cursorWidth, _fontSize}); + this->addProtectedChild(_cursor); + hideCursor(); + + markDirty(DIRTY_CHAR_OFFSETS | DIRTY_LINE_METRICS); + + return true; +} + +void InputField::onEnter() +{ + Widget::onEnter(); + + setPointerEnabled(true); + + // Enable keyboard listener for cursor control and shortcuts + if (!_kbdListener) + { + _kbdListener = KeyboardEventListener::create(); + AX_SAFE_RETAIN(_kbdListener); + _kbdListener->onKeyPressed = [this](KeyboardEvent* event) { + auto code = event->getKeyCode(); + switch (code) + { + case KeyboardEvent::KeyCode::KEY_CTRL: + case KeyboardEvent::KeyCode::KEY_RIGHT_CTRL: + case KeyboardEvent::KeyCode::KEY_HYPER: + _ctrlKeyPressed = true; + return; + case KeyboardEvent::KeyCode::KEY_SHIFT: + case KeyboardEvent::KeyCode::KEY_RIGHT_SHIFT: + _shiftKeyPressed = true; + return; + default: + break; + } + + if (isCursorVisible()) + { + if (_ctrlKeyPressed) + { + switch (code) + { + case KeyboardEvent::KeyCode::KEY_A: + case KeyboardEvent::KeyCode::KEY_CAPITAL_A: + this->selectAll(); + return; + case KeyboardEvent::KeyCode::KEY_C: + case KeyboardEvent::KeyCode::KEY_CAPITAL_C: + this->copySelectionToClipboard(); + return; + case KeyboardEvent::KeyCode::KEY_X: + case KeyboardEvent::KeyCode::KEY_CAPITAL_X: + this->cutSelectionToClipboard(); + return; +#if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT // Windows UWP TextBox will dispatchInsertText event with clipboard text, + // so no need to handle paste action in keyboard event. + case KeyboardEvent::KeyCode::KEY_V: + case KeyboardEvent::KeyCode::KEY_CAPITAL_V: + this->pasteFromClipboard(); + return; +#endif + default: + break; + } + } + + switch (code) + { + case KeyboardEvent::KeyCode::KEY_LEFT_ARROW: + this->moveCursor(-1, _shiftKeyPressed); + break; + case KeyboardEvent::KeyCode::KEY_RIGHT_ARROW: + this->moveCursor(1, _shiftKeyPressed); + break; + case KeyboardEvent::KeyCode::KEY_DELETE: + case KeyboardEvent::KeyCode::KEY_KP_DELETE: + this->handleDeleteKeyEvent(); + break; + case KeyboardEvent::KeyCode::KEY_UP_ARROW: + this->moveCursorVertically(-1); + break; + case KeyboardEvent::KeyCode::KEY_DOWN_ARROW: + this->moveCursorVertically(1); + break; + default:; + } + } + }; + _kbdListener->onKeyReleased = [this](KeyboardEvent* event) { + switch (event->getKeyCode()) + { + case KeyboardEvent::KeyCode::KEY_CTRL: + case KeyboardEvent::KeyCode::KEY_RIGHT_CTRL: + case KeyboardEvent::KeyCode::KEY_HYPER: + _ctrlKeyPressed = false; + break; + case KeyboardEvent::KeyCode::KEY_SHIFT: + case KeyboardEvent::KeyCode::KEY_RIGHT_SHIFT: + _shiftKeyPressed = false; + break; + default: + break; + } + }; + + _eventDispatcher->addEventListenerWithSceneGraphPriority(_kbdListener, this); + } +} + +void InputField::onExit() +{ + detachWithIME(); + + if (_kbdListener) + { + _eventDispatcher->removeEventListener(_kbdListener); + AX_SAFE_RELEASE_NULL(_kbdListener); + } + setPointerEnabled(false); + Widget::onExit(); +} + +std::string_view InputField::getFontName() const +{ + return _fontName; +} + +void InputField::setFontName(std::string_view fontName) +{ + _renderLabel->setFontInfo(fontName, _fontSize); + + _fontName = fontName; + _fontType = labelTypeToFontType(_renderLabel->getLabelType()); + + // Sync measure label font + if (_measureLabel) + _measureLabel->setFontInfo(fontName, _fontSize); + + _passwordCharWidth = measureText(_passwordChar).width; + updateLineHeight(); + _cursor->setContentSize(Vec2{_cursor->getContentSize().width, _fontSize}); + markDirty(DIRTY_LINE_METRICS); +} + +void InputField::setFontSize(float size) +{ + if (_fontSize == size) + return; + + if (_fontType == FontType::BMFONT) + { + // BMFont size cannot be changed at runtime; ignore size change + return; + } + if (_fontType == FontType::SYSTEM) + { + _renderLabel->setSystemFontSize(size); + } + else if (_fontType == FontType::TTF) + { + TTFConfig config = _renderLabel->getTTFConfig(); + config.fontSize = size; + _renderLabel->setTTFConfig(config); + } + + _fontSize = size; + + // Sync measure label to the same font type and configuration as the render label. + if (_measureLabel) + { + if (_fontType == FontType::SYSTEM) + _measureLabel->setSystemFontSize(_fontSize); + else if (_fontType == FontType::TTF) + _measureLabel->setTTFConfig(_renderLabel->getTTFConfig()); + } + + _passwordCharWidth = measureText(_passwordChar).width; + updateLineHeight(); + markDirty(DIRTY_LINE_METRICS); +} + +float InputField::getFontSize() const +{ + return _fontSize; +} + +Label* InputField::getRenderLabel() +{ + return _renderLabel; +} + +////////////////////////////////////////////////////////////////////////// +// InputDelegate +////////////////////////////////////////////////////////////////////////// + +bool InputField::attachWithIME() +{ + if (_isAttachWithIME) + return true; + bool ret = InputDelegate::attachWithIME(); + if (ret) + { + // Initialize touch selection state + _selectingByTouch = false; + _selectionTouchMoved = false; + _isAttachWithIME = true; + + // Open keyboard + auto renderView = _director->getRenderView(); + if (renderView) + renderView->setIMEKeyboardState(true); + + showCursor(); + dispatchEvent(EventType::ATTACH_WITH_IME); + + markDirty(DIRTY_CURSOR); + } + return ret; +} + +bool InputField::detachWithIME() +{ + bool ret = InputDelegate::detachWithIME(); + if (ret) + { + _isAttachWithIME = false; + + // Hide cursor immediately + hideCursor(); + + // Close keyboard + auto renderView = _director->getRenderView(); + if (renderView) + renderView->setIMEKeyboardState(false); + + // Dispatch event before removing listeners + dispatchEvent(EventType::DETACH_WITH_IME); + } + return ret; +} + +bool InputField::hitTestWithIME(const Vec2& location) +{ + const Camera* camera = _hittedByCamera ? _hittedByCamera : Camera::getDefaultCamera(); + return hitTestSelf(location, camera, nullptr); +} + +void InputField::keyboardDidShow(IMEKeyboardNotificationInfo& /*info*/) +{ + s_keyboardVisible = true; +} + +void InputField::keyboardDidHide(IMEKeyboardNotificationInfo& /*info*/) +{ + _director->getRenderView()->hideContextMenu(); + + s_keyboardVisible = false; + bool ret = InputDelegate::detachWithIME(); + if (ret) + { + _isAttachWithIME = false; + + // Hide cursor immediately + hideCursor(); + + dispatchEvent(EventType::DETACH_WITH_IME); + } +} + +bool InputField::isCursorVisible() const +{ + return _cursor && _cursor->isVisible(); +} + +bool InputField::canAttachWithIME() const +{ + return true; +} + +bool InputField::canDetachWithIME() const +{ + return true; +} + +void InputField::setCharLimit(uint32_t limit) +{ + _charLimit = limit; + + // If the new limit is non-zero and shorter than current text, truncate + // the stored input so the control remains in a valid state. Use + // setString() to ensure rendering, prefix caches and cursor are updated + // consistently. + if (_charLimit > 0 && _charCount > _charLimit) + { + auto byteOffset = getByteOffset(static_cast(_charLimit)); + std::string_view textView(_inputText); + setString(textView.substr(0, byteOffset)); + } +} + +void InputField::insertText(std::string_view text) +{ + if (_readOnly || !this->_enabled) + return; + + // In multiline mode we allow newline characters; otherwise they signify commit and we strip them. + if (!_multilineEnabled) + { + auto pos = text.find_first_of("\r\n"); + if (pos != std::string::npos) + { + text = text.substr(0, pos); + detachWithIME(); + } + if (text.empty()) + return; + } + + if (hasSelection()) + deleteSelection(false); + + size_t insertionCharCount; + + // Character limit + if (_charLimit > 0) + { + int remaining = static_cast(_charLimit - _charCount); + if (remaining <= 0) + { + axbeep(0); + return; + } + // Truncate insertStr to not exceed limit + auto result = text_utils::countUTF8WithLimit(text, remaining); + if (!result) + { + axbeep(0); + return; + } + text = text.substr(0, result.byteCount); + insertionCharCount = result.charCount; + } + else + { + insertionCharCount = text_utils::countUTF8Chars(text); + } + + if (text.empty()) + return; + + markDirty(DIRTY_TEXT_CONTENT); + + auto prevCursorOffset = static_cast(_cursorCharOffset); + bool bInsertAtEnd = (prevCursorOffset == static_cast(_charCount)); + + // 1. Performance optimization: Modify _inputText in-place to avoid full string allocation or copying + _inputText.insert(_cursorByteOffset, text.data(), text.size()); + + // 2. Incremental optimization: Simply add the new character count to the existing total + auto newCharCount = _charCount + static_cast(insertionCharCount); + + updateLogicalLimits(newCharCount); + + // 3. Resolve cursor positioning logic that previously relied on updatePresentation parameters + if (bInsertAtEnd) // Equivalent to your original condition when inserting at the string tail + { + setCursorOffset(_charCount, false); + } + else + { + int newCursorOffset = prevCursorOffset + static_cast(insertionCharCount); + setCursorOffset(newCursorOffset, hasSelection()); + } + + dispatchEvent(EventType::INSERT_TEXT); +} + +void InputField::deleteBackward(unsigned int numChars) +{ + if (hasSelection()) + { + deleteSelection(true); + return; + } + + if (_readOnly || !this->_enabled || 0 == _charCount) + { + axbeep(0); + return; + } + + int len = static_cast(_inputText.length()); + if (0 == len || _cursorByteOffset == 0) + { + axbeep(0); + return; + } + + auto deleteChars = (std::min)(static_cast(numChars), _cursorCharOffset); + auto startCharOffset = _cursorCharOffset - deleteChars; + auto startByteOffset = getByteOffset(startCharOffset); + auto totalDeleteLen = _cursorByteOffset - startByteOffset; + + markDirty(DIRTY_TEXT_CONTENT); + + // If all text is deleted, fast-clear the buffer + if (len <= totalDeleteLen) + { + _inputText.clear(); + updateLogicalLimits(0); + setCursorOffset(0, false); + } + else + { + // Performance optimization: Erase characters in-place to avoid copying the whole string + _inputText.erase(startByteOffset, totalDeleteLen); + + // Incremental optimization: Safely subtract the deleted character count + uint32_t newCharCount = (_charCount > static_cast(deleteChars)) ? (_charCount - deleteChars) : 0; + + updateLogicalLimits(newCharCount); + + // Update the final cursor position to the deletion starting point + setCursorOffset(startCharOffset, false); + } + + dispatchEvent(EventType::DELETE_BACKWARD); +} + +void InputField::handleDeleteKeyEvent() +{ + if (hasSelection()) + { + deleteSelection(true); + return; + } + + if (_readOnly || !this->_enabled || 0 == _charCount) + { + axbeep(0); + return; + } + + int len = static_cast(_inputText.length()); + if (0 == len || _cursorCharOffset == static_cast(_charCount)) + { + axbeep(0); + return; + } + + auto nextByteOffset = getByteOffset(_cursorCharOffset + 1); + auto deleteLen = nextByteOffset - _cursorByteOffset; + + markDirty(DIRTY_TEXT_CONTENT); + + // If all text is deleted, fast-clear the buffer + if (len <= deleteLen) + { + _inputText.clear(); + updateLogicalLimits(0); + setCursorOffset(0, false); + } + else + { + + // Performance optimization: Erase the forward character in-place without heap allocations + _inputText.erase(_cursorByteOffset, deleteLen); + + // Incremental optimization: Simply decrement the total character count by 1 + auto newCharCount = (_charCount > 0) ? (_charCount - 1) : 0; + updateLogicalLimits(newCharCount); + + // Maintain the cursor position at the current cursor offset + setCursorOffset(_cursorCharOffset, false); + } + + dispatchEvent(EventType::DELETE_BACKWARD); +} + +void InputField::setTextColor(const Color32& color) +{ + _colorText = color; + if (!_inputText.empty()) + _renderLabel->setTextColor(_colorText); +} + +const Color32& InputField::getTextColor(void) const +{ + return _colorText; +} + +void InputField::setCursorColor(const Color32& color) +{ + _cursor->setColor(color); +} + +const Color32& InputField::getCursorColor(void) const +{ + return _cursor->getColor(); +} + +const Color32& InputField::getPlaceholderColor() const +{ + return _colorSpaceHolder; +} + +void InputField::setPlaceholderColor(const Color32& color) +{ + _colorSpaceHolder = color; + if (_inputText.empty()) + _renderLabel->setTextColor(color); +} + +////////////////////////////////////////////////////////////////////////// +// properties +////////////////////////////////////////////////////////////////////////// + +void InputField::setMultilineEnabled(bool enabled) +{ + if (_multilineEnabled != enabled) + { + _multilineEnabled = enabled; + markDirty(DIRTY_TEXT_GEOMETRY | DIRTY_LINE_METRICS); + } +} + +Node* InputField::getRenderNode() +{ + return _renderLabel; +} + +Vec2 InputField::resolvePreferredSize(const Vec2& /*sizeHint*/) const +{ + return _renderLabel->getContentSize(); +} + +// input text property +void InputField::setString(std::string_view text) +{ + _inputText = text; + + auto charCount = static_cast(text_utils::countUTF8Chars(_inputText)); + updateLogicalLimits(charCount); + + setCursorOffset(static_cast(_charCount), false); + + // Defer text updating and geometry remeasuring to the layout pass + markDirty(DIRTY_CHAR_OFFSETS | DIRTY_TEXT | DIRTY_LINE_METRICS | DIRTY_CURSOR); +} + +void InputField::updateLogicalLimits(uint32_t newCharCount) +{ + _charCount = newCharCount; + + // Safely clamp selection ranges within the new valid logical bounds + _selectionStart = (std::min)(_selectionStart, static_cast(_charCount)); + _selectionEnd = (std::min)(_selectionEnd, static_cast(_charCount)); + _selectionAnchor = (std::min)(_selectionAnchor, static_cast(_charCount)); +} + +void InputField::updatePresentation() +{ + clearDirty(DIRTY_TEXT); // Clear flag to support JIT queries safely + + if (_inputText.empty()) + { + _renderLabel->setString(_placeholderText); + _renderLabel->setTextColor(_colorSpaceHolder); + } + else + { + if (_passwordEnabled) + { + _renderLabel->setString(makePasswordString()); + } + else + { + _renderLabel->setString(_inputText); + } + _renderLabel->setTextColor(_colorText); + } +} + +void InputField::updateLineHeight() +{ + _lineHeight = measureText("M").y; +} + +std::string InputField::makePasswordString() +{ + std::string passwordStr; + + size_t length = _charCount; + // Pre-reserve memory to prevent frequent reallocation during appending + passwordStr.reserve(length * _passwordChar.size()); + + while (length > 0) + { + passwordStr += _passwordChar; + --length; + } + + return passwordStr; +} + +void InputField::updateContentSize() +{ + Widget::updateContentSize(); + + if (_autoSize && _multilineEnabled) + { + float height = _lineMetrics.size() * _lineHeight; + _contentSize.height = height; + } + if (_autoSize) + { + markDirty(DIRTY_TEXT_GEOMETRY | DIRTY_LINE_METRICS); + } +} + +void InputField::onSizeChanged() +{ + Widget::onSizeChanged(); + markDirty(DIRTY_TEXT_GEOMETRY | DIRTY_LINE_METRICS); +} + +std::string_view InputField::getString() const +{ + return _inputText; +} + +void InputField::setPasswordChar(std::string_view ch) +{ + if (ch != _passwordChar) + { + _passwordChar = !ch.empty() ? ch : DEFAULT_PASSWORD_CHAR; + _passwordCharWidth = measureText(_passwordChar).width; + + // Changing password glyph affects texture and typography metrics + markDirty(DIRTY_TEXT | DIRTY_LINE_METRICS | DIRTY_CURSOR); + } +} + +// place holder text property +void InputField::setPlaceholderText(std::string_view text) +{ + _placeholderText = text; + markDirty(DIRTY_TEXT); // Mark text presentation as dirty + + if (_inputText.empty()) + markDirty(DIRTY_LINE_METRICS); + + updateContentSize(); +} + +std::string_view InputField::getPlaceholderText() const +{ + return _placeholderText; +} + +// secureTextEntry +void InputField::setPasswordEnabled(bool value) +{ + if (_passwordEnabled != value) + { + _passwordEnabled = value; + + // Changing to password mode requires text reshaping and layout re-evaluation + markDirty(DIRTY_TYPOGRAPHY); + } +} + +bool InputField::isPasswordEnabled() const +{ + return _passwordEnabled; +} + +void InputField::addEventListener(const InputFieldCallback& callback) +{ + _eventCallback = callback; +} + +void InputField::dispatchEvent(EventType eventType) +{ + retain(); + + if (_eventCallback) + _eventCallback(this, eventType); + + if (_customEventCallback) + _customEventCallback(this, static_cast(eventType)); + + release(); +} + +void InputField::setEnabled(bool bEnabled) +{ + if (_enabled != bEnabled) + { + _enabled = bEnabled; + if (!bEnabled) + this->detachWithIME(); + } +} + +bool InputField::hasSelection() const +{ + return _selectionStart != _selectionEnd; +} + +void InputField::selectAll() +{ + setSelection(0, _charCount); + _selectionAnchor = 0; + setCursorOffset(_charCount, true); +} + +void InputField::clearSelection() +{ + setSelection(_cursorCharOffset, _cursorCharOffset); + _selectionAnchor = _cursorCharOffset; +} + +std::string InputField::getSelectedText() const +{ + if (!hasSelection()) + return {}; + + auto startByte = getByteOffset(_selectionStart); + auto endByte = getByteOffset(_selectionEnd); + + return _inputText.substr(startByte, endByte - startByte); +} + +bool InputField::copySelectionToClipboard() const +{ + auto selectedText = getSelectedText(); + if (selectedText.empty() || _passwordEnabled) + return false; + + Device::setClipboardText(selectedText); + return true; +} + +bool InputField::cutSelectionToClipboard() +{ + if (_readOnly || !this->_enabled || !copySelectionToClipboard()) + return false; + + return deleteSelection(true); +} + +bool InputField::pasteFromClipboard() +{ + if (_readOnly || !this->_enabled) + return false; + + Device::getClipboardText([inputField = ax::RefPtr(this)](std::string_view text) { + if (!text.empty() && inputField->isRunning()) + inputField->insertText(text); + }); + return true; +} + +void InputField::setSelectionColor(const Color& color) +{ + _selectionColor = color; + + markDirty(DIRTY_SELECTION); +} + +const Color& InputField::getSelectionColor() const +{ + return _selectionColor; +} + +void InputField::showCursor() +{ + if (_cursor) + { + _cursor->setVisible(true); + _cursor->setOpacity(255); // Ensure full opacity + + // Use opacity-based blinking for better performance + // Fade out to 0 over 0.5s, then fade in over 0.5s (total 1s cycle) + auto fadeOut = FadeOut::create(0.5f); + auto fadeIn = FadeIn::create(0.5f); + auto blinkSequence = Sequence::create(fadeOut, fadeIn, nullptr); + _cursor->runAction(RepeatForever::create(blinkSequence)); + } +} + +void InputField::hideCursor() +{ + if (_cursor) + { + _cursor->stopAllActions(); + _cursor->setOpacity(255); // Reset to full opacity + _cursor->setVisible(false); + } +} + +void InputField::updateCursorTransform() +{ + Vec2 pos = cursorPositionFromOffset(_cursorCharOffset); + if (_cursor) + { + // adjust cursor height to line height + Vec2 size = _cursor->getContentSize(); + if (_lineHeight > 0) + _cursor->setContentSize(Vec2(size.width, _fontSize)); + + _cursor->setPosition(pos); + } + updateSelectionLayer(); + + // Apply deferred preferred cursor X update + if (isDirty(DIRTY_PREFERRED_X)) + { + _preferredCursorX = pos.x; + clearDirty(DIRTY_PREFERRED_X); + } +} + +void InputField::moveCursor(int direction) +{ + moveCursor(direction, false); +} + +void InputField::moveCursor(int direction, bool keepSelection) +{ + auto cursorOffset = static_cast(_cursorCharOffset) + direction; + cursorOffset = std::clamp(cursorOffset, 0, static_cast(_charCount)); + setCursorOffset(cursorOffset, keepSelection); +} + +void InputField::moveCursorTo(const Vec2& point) +{ + moveCursorTo(point, false); +} + +void InputField::moveCursorTo(const Vec2& point, bool keepSelection) +{ + setCursorOffset(cursorOffsetFromPosition(point), keepSelection); +} + +void InputField::setCursorOffset(int cursorOffset, bool keepSelection, bool markPrefxDirty) +{ + cursorOffset = (std::min)(cursorOffset, static_cast(_charCount)); + + if (keepSelection) + { + setSelection(_selectionAnchor, cursorOffset); + } + else + { + _selectionAnchor = cursorOffset; + setSelection(cursorOffset, cursorOffset); + } + + _cursorCharOffset = cursorOffset; + _cursorByteOffset = getByteOffset(cursorOffset); + + if (markPrefxDirty) + markDirty(DIRTY_PREFERRED_X); + + markDirty(DIRTY_CURSOR); +} + +int InputField::getByteOffset(int cursorOffset) const +{ + if (isDirty(DIRTY_CHAR_OFFSETS)) + const_cast(this)->rebuildCharByteOffsets(); + + if (cursorOffset >= _charCount) + return static_cast(_inputText.length()); + + return _charByteOffsets[cursorOffset]; +} + +float InputField::getRenderLabelTextBottomY() const +{ + // Ensure layout metrics are up-to-date before calculating vertical offset + if (isDirty(DIRTY_LINE_METRICS)) + const_cast(this)->rebuildLineMetrics(); + + // If there is no render label, fallback to 0 + if (!_renderLabel) + return 0.0f; + + // Get label container position and anchor in InputField local coords + float labelPosY = _renderLabel->getPositionY(); + float anchorY = _renderLabel->getAnchorPoint().y; + float labelHeight = _renderLabel->getContentSize().height; + + // Compute the bottom Y of the label container + // labelBottomY = labelPosY - anchorY * labelHeight + float labelBottomY = labelPosY - anchorY * labelHeight; + float labelTopY = labelBottomY + labelHeight; + + // Compute the actual text height based on line metrics and line height + // If line metrics are not available, fallback to a single line height + float totalTextHeight = static_cast(_lineMetrics.size()) * _lineHeight; + if (totalTextHeight <= 0.0f) + { + // fallback to a measured single-line height + totalTextHeight = _lineHeight; + } + + // Determine where the text block sits inside the label container + // This must respect the vertical alignment used by the label rendering. + // We use InputField's _textVAlignment which should match the label's valign. + float textBottomYInLabel = labelBottomY; + + if (labelHeight > totalTextHeight) + { + if (_textVAlignment == TextVAlignment::TOP) + { + // Text is aligned to the top of the label container. + // Text top equals labelTopY, so bottom is labelTopY - totalTextHeight. + textBottomYInLabel = labelTopY - totalTextHeight; + } + else if (_textVAlignment == TextVAlignment::CENTER) + { + // Text is vertically centered inside the label container. + // Bottom = labelBottomY + (labelHeight - totalTextHeight) / 2 + textBottomYInLabel = labelBottomY + (labelHeight - totalTextHeight) * 0.5f; + } + else // TextVAlignment::BOTTOM + { + // Text is bottom-aligned; bottom equals labelBottomY + textBottomYInLabel = labelBottomY; + } + } + else + { + // If text fills or overflows the label container, clamp to label bottom. + textBottomYInLabel = labelBottomY; + } + + // If the Label exposes internal padding or baseline offsets, apply them here. + // Example (pseudo): textBottomYInLabel += _renderLabel->getInternalPaddingBottom(); + + return textBottomYInLabel; +} + +void InputField::setSelection(int selStart, int selEnd) +{ + selStart = (std::min)(selStart, static_cast(_charCount)); + selEnd = (std::min)(selEnd, static_cast(_charCount)); + if (selStart > selEnd) + std::swap(selStart, selEnd); + + if (selStart != _selectionStart || selEnd != _selectionEnd) + { + _selectionStart = selStart; + _selectionEnd = selEnd; + } + + markDirty(DIRTY_SELECTION); +} + +void InputField::updateSelectionLayer(void) +{ + if (!_selectionLayer) + return; + _selectionLayer->clear(); + + if (!hasSelection()) + return; + + int selStart = std::min(_selectionStart, _selectionEnd); + int selEnd = std::max(_selectionStart, _selectionEnd); + + if (selStart == selEnd) + return; + + float labelTextBottomY = getRenderLabelTextBottomY(); + int totalLines = static_cast(_lineMetrics.size()); + + for (size_t lineIdx = 0; lineIdx < _lineMetrics.size(); ++lineIdx) + { + const LineMetrics& line = _lineMetrics[lineIdx]; + + if (line.endCharIndex <= selStart || line.startCharIndex >= selEnd) + continue; + + int lineSelStart = std::max(selStart, line.startCharIndex) - line.startCharIndex; + int lineSelEnd = std::min(selEnd, line.endCharIndex) - line.startCharIndex; + + if (lineSelEnd <= lineSelStart) + continue; + + float x1 = line.charXOffsets[lineSelStart]; + float x2 = line.charXOffsets[lineSelEnd]; + + // apply horizontal alignment + float totalTextWidth = line.lineWidth; + float lineStartX = 0.0f; + + if (_textHAlignment == TextHAlignment::CENTER) + lineStartX = (_contentSize.width - totalTextWidth) * 0.5f; + else if (_textHAlignment == TextHAlignment::RIGHT) + lineStartX = _contentSize.width - totalTextWidth; + + float selX1 = lineStartX + x1; + float selX2 = lineStartX + x2; + + // Invert the line index for vertical coordinates: + // lineIdx = 0 should be rendered at the visual top. + int invertedLineIdx = totalLines - 1 - static_cast(lineIdx); + + // vertical coordinates using labelTextBottomY + float selY1 = labelTextBottomY + invertedLineIdx * _lineHeight; + float selY2 = selY1 + _lineHeight; + + _selectionLayer->drawSolidRect(Vec2(selX1, selY1), Vec2(selX2, selY2), _selectionColor); + } +} + +bool InputField::deleteSelection(bool notify) +{ + if (_readOnly || !this->_enabled || !hasSelection()) + return false; + + auto startByte = getByteOffset(_selectionStart); + auto endByte = getByteOffset(_selectionEnd); + auto newCursor = _selectionStart; + + // Calculate how many characters are being removed before mutating the string + auto deletedCharCount = static_cast(_selectionEnd - _selectionStart); + + markDirty(DIRTY_CHAR_OFFSETS | DIRTY_LINE_METRICS); + + // Performance optimization: Erase the selected slice in-place + _inputText.erase(startByte, endByte - startByte); + + // Incremental optimization: Deduct the exact selection block length + auto newCharCount = (_charCount > deletedCharCount) ? (_charCount - deletedCharCount) : 0; + + updateLogicalLimits(newCharCount); + + // Reposition the cursor to where the selection started + setCursorOffset(newCursor, false); + + markDirty(DIRTY_CHAR_OFFSETS | DIRTY_TEXT | DIRTY_LINE_METRICS); + + if (notify) + dispatchEvent(EventType::DELETE_BACKWARD); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +// Touch Area and Hit Test +////////////////////////////////////////////////////////////////////////// +void InputField::setTouchAreaSize(const Vec2& size) +{ + _touchAreaSize = size; +} + +Vec2 InputField::getTouchAreaSize() const +{ + return _touchAreaSize; +} + +void InputField::setTouchAreaEnabled(bool enable) +{ + _useTouchArea = enable; +} + +bool InputField::onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) +{ + if (!event || !camera) + return false; + + // Normal Widget path: + // If the pointer is inside the input field, let Widget handle and cache _hitted/_hittedByCamera. + if (Widget::onPointerHitTest(event, camera, outHitPoint)) + return true; + + // Important: + // If this field currently owns IME focus, it must still receive PointerDown outside + // itself so onPointerDown() can detachWithIME(). + // + // Return true only for PointerDown. onPointerDown() will call Widget::onPointerDown(), + // fail the hit test, detachWithIME(), and return false, so it will not capture or swallow. + if (event->getPhase() == InputPhase::PointerDown && event->isPrimaryPressed() && _isAttachWithIME) + { + _hitted = false; + _hittedByCamera = camera; + return true; + } + + return false; +} + +bool InputField::hitTestSelf(const Vec2& pt, const Camera* camera, Vec3* outHitPoint) const +{ + if (!camera) + return false; + + if (!_useTouchArea) + { + return Widget::hitTestSelf(pt, camera, outHitPoint); + } + + if (_touchAreaSize.width <= 0.0f || _touchAreaSize.height <= 0.0f) + return false; + + const auto size = getContentSize(); + const auto anch = getAnchorPoint(); + + Rect rect((size.width - _touchAreaSize.width) * anch.x, (size.height - _touchAreaSize.height) * anch.y, + _touchAreaSize.width, _touchAreaSize.height); + + return camera->isWorldPointInRect(pt, getWorldToNodeTransform(), rect, outHitPoint); +} + +////////////////////////////////////////////////////////////////////////// +// Text Alignment +////////////////////////////////////////////////////////////////////////// +void InputField::setTextHorizontalAlignment(TextHAlignment alignment) +{ + if (_textHAlignment != alignment) + { + _textHAlignment = alignment; + _renderLabel->setHorizontalAlignment(alignment); + markDirty(DIRTY_CURSOR | DIRTY_SELECTION); + } +} + +TextHAlignment InputField::getTextHorizontalAlignment() const +{ + return _textHAlignment; +} + +void InputField::setTextVerticalAlignment(TextVAlignment alignment) +{ + if (_textVAlignment != alignment) + { + _textVAlignment = alignment; + _renderLabel->setVerticalAlignment(alignment); + markDirty(DIRTY_CURSOR | DIRTY_SELECTION); + } +} + +TextVAlignment InputField::getTextVerticalAlignment() const +{ + return _textVAlignment; +} + +void InputField::updateLayout() +{ + if (_continuousTouchPending) + _continuousTouchElapsedTime += _director->getDeltaTime(); + + if (isDirty(DIRTY_CHAR_OFFSETS)) + rebuildCharByteOffsets(); + + if (isDirty(DIRTY_TEXT)) + updatePresentation(); + + if (isDirty(DIRTY_TEXT_GEOMETRY)) + { + clearDirty(DIRTY_TEXT_GEOMETRY); + + // Important: + // The render label must keep the same local coordinate space as InputField. + // Otherwise auto-size labels are centered by their own changing content width, + // while the cursor is positioned in InputField coordinates. + _renderLabel->setDimensions(_contentSize.width, _contentSize.height); + _renderLabel->setPosition(_contentSize.width * 0.5f, _contentSize.height * 0.5f); + } + + if (isDirty(DIRTY_LINE_METRICS)) + { + rebuildLineMetrics(); + markDirty(DIRTY_CURSOR | DIRTY_SELECTION); + } + + if (isDirty(DIRTY_CURSOR)) + updateCursorTransform(); + + if (isDirty(DIRTY_SELECTION)) + updateSelectionLayer(); + + _dirtyFlags = 0; +} + +////////////////////////////////////////////////////////////////////////// +// Touch Event Handlers (Override Widget's methods) +////////////////////////////////////////////////////////////////////////// + +bool InputField::onPointerDown(PointerEvent* event) +{ + if (!_enabled) + { + detachWithIME(); + return false; + } + + if (!Widget::onPointerDown(event)) + { + detachWithIME(); + return false; + } + + const bool focus = isCursorVisible(); + _selectingByTouch = focus; + _selectionTouchMoved = false; + _selectionAnchor = _cursorCharOffset; + + if (focus) + { + Vec2 localPoint = this->convertToNodeSpace(event->getLocation()); + _selectionAnchor = cursorOffsetFromPosition(localPoint); + + _continuousTouchPending = true; + _continuousTouchElapsedTime = 0.0f; + } + + return true; +} + +void InputField::onPointerMove(PointerEvent* event) +{ + Widget::onPointerMove(event); + + if (!_selectingByTouch || !_enabled) + return; + + Vec2 localPoint = this->convertToNodeSpace(event->getLocation()); + setCursorOffset(cursorOffsetFromPosition(localPoint), true); + _selectionTouchMoved = hasSelection(); +} + +void InputField::onPointerUp(PointerEvent* event) +{ + bool focus = _hitted && _enabled; + if (focus) + { + if (!s_keyboardVisible || !isCursorVisible()) + attachWithIME(); + + Vec2 worldPoint = event->getLocation(); + Vec2 localPoint = this->convertToNodeSpace(worldPoint); + moveCursorTo(localPoint, _selectionTouchMoved); + + if (_continuousTouchPending && _continuousTouchElapsedTime >= _continuousTouchDelayTime) + { + // Delay time reached, execute callback + if (_continuousTouchCallback) + { + _continuousTouchCallback(worldPoint); + } + else + { + // Default behavior: show system edit menu at touch location + _director->getRenderView()->showContextMenu(event->getScreenLocation(), _charCount > 0, hasSelection(), + _readOnly); + } + } + else + { + _director->getRenderView()->hideContextMenu(); + } + } + else + { + detachWithIME(); + } + + resetTouchState(); + + Widget::onPointerUp(event); +} + +void InputField::onPointerCancel(PointerEvent* event) +{ + _director->getRenderView()->hideContextMenu(); + + // Resets touch pending state + resetTouchState(); + + // Call parent class + Widget::onPointerCancel(event); +} + +void InputField::resetTouchState() +{ + _continuousTouchElapsedTime = 0; + _continuousTouchPending = false; + _selectingByTouch = false; +} + +Vec2 InputField::cursorPositionFromOffset(int cursorOffset) const +{ + if (isDirty(DIRTY_LINE_METRICS)) + const_cast(this)->rebuildLineMetrics(); + + cursorOffset = std::min(cursorOffset, static_cast(_charCount)); + if (_lineMetrics.empty()) + return Vec2::ZERO; + + // Find the line containing cursorOffset + auto it = std::upper_bound(_lineMetrics.begin(), _lineMetrics.end(), cursorOffset, + [](int pos, const LineMetrics& line) { return pos < line.startCharIndex; }); + int lineIdx = (it == _lineMetrics.begin()) ? 0 : int(it - _lineMetrics.begin()) - 1; + if (lineIdx >= (int)_lineMetrics.size()) + lineIdx = (int)_lineMetrics.size() - 1; + + const LineMetrics& line = _lineMetrics[lineIdx]; + int charInLine = cursorOffset - line.startCharIndex; + if (charInLine < 0) + charInLine = 0; + if (charInLine > (int)line.charXOffsets.size() - 1) + charInLine = (int)line.charXOffsets.size() - 1; + + float x = line.charXOffsets[charInLine]; // x offset from line start to cursor + // Apply horizontal alignment + float totalTextWidth = line.lineWidth; + float lineStartX = 0.0f; + if (_textHAlignment == TextHAlignment::CENTER) + lineStartX = (_contentSize.width - totalTextWidth) * 0.5f; + else if (_textHAlignment == TextHAlignment::RIGHT) + lineStartX = _contentSize.width - totalTextWidth; + + float finalX = lineStartX + x; + + // Use the label text bottom Y as the vertical base + auto yOffset = getRenderLabelTextBottomY(); + int invertedLineIdx = static_cast(_lineMetrics.size()) - 1 - lineIdx; + float finalY = yOffset + (invertedLineIdx + 0.5f) * _lineHeight; // center of line + return Vec2(finalX, finalY); +} + +int InputField::cursorOffsetFromPosition(const Vec2& position) const +{ + if (isDirty(DIRTY_LINE_METRICS)) + const_cast(this)->rebuildLineMetrics(); + if (_inputText.empty() || _lineMetrics.empty()) + return 0; + + float yOffset = getRenderLabelTextBottomY(); + + // Calculate the distance from the top of the text block to map Y coordinates correctly + // Since Axmol's Y-axis points upwards, lineIdx = 0 should be visually at the top. + float totalTextHeight = static_cast(_lineMetrics.size()) * _lineHeight; + float topY = yOffset + totalTextHeight; // Visual top of the text block + float distFromTop = topY - position.y; // Distance from top to the click position + + // Calculate line index (0 is the top visual line) + int lineIdx = std::clamp(static_cast(distFromTop / _lineHeight), 0, static_cast(_lineMetrics.size()) - 1); + + // Fallback if user clicked above the entire text block + if (distFromTop < 0.0f) + lineIdx = 0; + + const LineMetrics& line = _lineMetrics[lineIdx]; + + // Calculate relative X based on horizontal alignment + float lineStartX = 0.0f; + if (_textHAlignment == TextHAlignment::CENTER) + lineStartX = (_contentSize.width - line.lineWidth) * 0.5f; + else if (_textHAlignment == TextHAlignment::RIGHT) + lineStartX = _contentSize.width - line.lineWidth; + + float relX = position.x - lineStartX; + + // Handle boundary cases: before first char or after last char + if (relX <= 0.0f) + return line.startCharIndex; + if (relX >= line.lineWidth) + return line.endCharIndex; + + // Binary search using std::lower_bound to find first offset greater than relX + auto& offsets = line.charXOffsets; + auto it = std::lower_bound(offsets.begin(), offsets.end(), relX); + int idx = (int)(it - offsets.begin()); // idx is the first offset > relX, so the character interval is [idx-1, idx] + + if (idx == 0) + return line.startCharIndex; // clicked before first char + + float left = offsets[idx - 1]; + float right = offsets[idx]; + float mid = (left + right) * 0.5f; + + // Decide cursor position based on left/right half of the character + if (relX < mid) + return line.startCharIndex + idx - 1; // left half ==> before char + else + return line.startCharIndex + idx; // right half ==> after char +} + +void InputField::rebuildLineMetrics() +{ + // Ensure string mapping dependency is resolved before text layout + if (isDirty(DIRTY_CHAR_OFFSETS)) + rebuildCharByteOffsets(); + + // Ensure Label string is updated before measuring its geometry + if (isDirty(DIRTY_TEXT)) + updatePresentation(); + + clearDirty(DIRTY_LINE_METRICS); + _lineMetrics.clear(); + if (_inputText.empty()) + { + // At least one empty line placeholder + _lineMetrics.push_back({0, 0, 0.0f, {0.0f}}); + return; + } + + float availableWidth = _multilineEnabled ? _contentSize.width : std::numeric_limits::max(); + if (availableWidth <= 0.0f && _multilineEnabled) + availableWidth = std::numeric_limits::max(); // Treat as unlimited + + const int totalChars = static_cast(_charByteOffsets.size() - 1); + int currentCharIdx = 0; + + // Pre-allocate core container memory to prevent frequent reallocations + _lineMetrics.reserve(totalChars / 40 + 1); + + // Initialize the tracking metrics for the first line + LineMetrics currentLine; + currentLine.startCharIndex = 0; + currentLine.charXOffsets.push_back(0.0f); + + while (currentCharIdx < totalChars) + { + size_t charByteOffset = _charByteOffsets[currentCharIdx]; + + // 1. Handle explicit line breaks (\n) + if (_multilineEnabled && _inputText.at(charByteOffset) == '\n') + { + currentLine.endCharIndex = currentCharIdx; + currentLine.lineWidth = currentLine.charXOffsets.back(); + _lineMetrics.push_back(std::move(currentLine)); + + // Setup a new line starting right after the '\n' character + currentLine.startCharIndex = currentCharIdx + 1; + currentLine.charXOffsets.clear(); + currentLine.charXOffsets.push_back(0.0f); + + ++currentCharIdx; + continue; + } + + // 2. Obtain current character width + float charWidth = _passwordCharWidth; + if (!_passwordEnabled) + { + size_t nextByteOffset = _charByteOffsets[currentCharIdx + 1]; + std::string_view charView(_inputText.data() + charByteOffset, nextByteOffset - charByteOffset); + charWidth = measureText(charView).width; + } + + // 3. Auto-wrapping threshold evaluation + if (_multilineEnabled && currentCharIdx > currentLine.startCharIndex) + { + // If adding this character exceeds available width, commit current line before tracking it + if (currentLine.charXOffsets.back() + charWidth > availableWidth) + { + currentLine.endCharIndex = currentCharIdx; + currentLine.lineWidth = currentLine.charXOffsets.back(); + _lineMetrics.push_back(std::move(currentLine)); + + // Start a new line with the current character as its first element + currentLine.startCharIndex = currentCharIdx; + currentLine.charXOffsets.clear(); + currentLine.charXOffsets.push_back(0.0f); + + // Re-evaluate the current character in the next iteration as the line-start + continue; + } + } + + // 4. Record the glyph placement offsets and advance + currentLine.charXOffsets.push_back(currentLine.charXOffsets.back() + charWidth); + ++currentCharIdx; + } + + // Wrap up: Record the final remaining line segment after loop termination + currentLine.endCharIndex = totalChars; + currentLine.lineWidth = currentLine.charXOffsets.back(); + _lineMetrics.push_back(std::move(currentLine)); +} + +void InputField::rebuildCharByteOffsets() +{ + clearDirty(DIRTY_CHAR_OFFSETS); + _charByteOffsets.clear(); + _charByteOffsets.reserve(_inputText.length() + 1); + for (size_t i = 0; i < _inputText.length(); ++i) + { + if ((static_cast(_inputText[i]) & 0xC0) != 0x80) + { + _charByteOffsets.push_back(static_cast(i)); + } + } + _charByteOffsets.push_back(static_cast(_inputText.length())); +} + +void InputField::moveCursorVertically(int direction) +{ + if (isDirty(DIRTY_LINE_METRICS)) + rebuildLineMetrics(); + + if (_lineMetrics.empty()) + return; + + // 1. Locate current line index via standard binary search + auto currentLineIt = + std::lower_bound(_lineMetrics.begin(), _lineMetrics.end(), _cursorCharOffset, + [](const LineMetrics& lm, int cursorIdx) { return lm.endCharIndex < cursorIdx; }); + + int curLine = (currentLineIt == _lineMetrics.end()) + ? static_cast(_lineMetrics.size()) - 1 + : static_cast(std::distance(_lineMetrics.begin(), currentLineIt)); + + // 2. Compute and clamp the target line index + int targetLine = std::clamp(curLine + direction, 0, static_cast(_lineMetrics.size()) - 1); + if (targetLine == curLine) + return; + + const auto& targetMetrics = _lineMetrics[targetLine]; + const auto& offsets = targetMetrics.charXOffsets; + + // 3. Convert Content-Space preferred X to Line-Relative X based on alignment + float lineStartX = 0.0f; + if (_textHAlignment == TextHAlignment::CENTER) + lineStartX = (_contentSize.width - targetMetrics.lineWidth) * 0.5f; + else if (_textHAlignment == TextHAlignment::RIGHT) + lineStartX = _contentSize.width - targetMetrics.lineWidth; + + float lineRelX = std::clamp(_preferredCursorX - lineStartX, 0.0f, targetMetrics.lineWidth); + + // 4. Use standard upper_bound to find the closest matching character offset + auto offsetIt = std::upper_bound(offsets.begin(), offsets.end(), lineRelX); + int charInLine = static_cast(std::distance(offsets.begin(), offsetIt)) - 1; + + // Nearest-neighbor correction: snap to the closer edge if it sits between two characters + if (charInLine >= 0 && charInLine < static_cast(offsets.size()) - 1) + { + float leftDist = lineRelX - offsets[charInLine]; + float rightDist = offsets[charInLine + 1] - lineRelX; + if (rightDist < leftDist) + ++charInLine; + } + charInLine = std::clamp(charInLine, 0, static_cast(offsets.size()) - 1); + + // 5. Apply the final cursor position updates + int newCursorPos = targetMetrics.startCharIndex + charInLine; + + // Note: Do NOT overwrite _preferredCursorX here to preserve vertical movement memory + // when skipping through short lines back into long lines. + setCursorOffset(newCursorPos, false, false); +} + +void InputField::performEditAction(EditAction action) +{ + if (!canPerformEditAction(action)) + return; + + switch (action) + { + case EditAction::Copy: + copySelectionToClipboard(); + break; + case EditAction::Cut: + cutSelectionToClipboard(); + break; + case EditAction::Paste: + pasteFromClipboard(); + break; + case EditAction::SelectAll: + selectAll(); + break; + default: + break; + } +} + +bool InputField::canPerformEditAction(EditAction action) const +{ + if (_readOnly || !_enabled) + return false; + + switch (action) + { + case EditAction::Copy: + return hasSelection() && !_passwordEnabled; + case EditAction::Cut: + return hasSelection() && !_passwordEnabled; + case EditAction::SelectAll: + return _charCount > 0; + case EditAction::Paste: + return true; // Paste can be allowed even in password fields, as it doesn't reveal content + default: + return false; + } +} + +void InputField::keyboardWillShow(IMEKeyboardNotificationInfo& info) +{ + if (_readOnly || !_enabled || !_focused) + return; + + // Find the top-level 2D UI root container + auto uiRoot = findUIRoot(this); + if (!uiRoot) + return; + + // Record the true original Y position before any movement + if (std::isnan(_uiRootOriginY)) + { + _uiRootOriginY = uiRoot->getPositionY(); + } + + // Get the world bounding box of the current input field + Rect rectTracked = getWorldBoundingBox(); + rectTracked.origin.y -= uiRoot->getPositionY() - _uiRootOriginY; + rectTracked.origin.y -= 4; // 4-pixel safety margin + + // AXLOGI("##### keyboardWillShow: keyboardFrame: origin: ({}, {}) size: ({},{})", info.keyboardFrame.origin.x, + // info.keyboardFrame.origin.y, info.keyboardFrame.size.x, info.keyboardFrame.size.y); + + // If the keyboard doesn't cover the input field, do nothing + if (!rectTracked.intersectsRect(info.keyboardFrame)) + { + return; + } + + // Calculate the vertical pixel offset needed to slide the UI up + _adjustHeight = info.keyboardFrame.getMaxY() - rectTracked.getMinY(); + + if (_adjustHeight > 0.0f) + { + // Stop any ongoing actions to prevent animation conflicts + uiRoot->stopAllActions(); + + // Slide the entire UI root container upwards based on its original Y + auto moveUI = ax::MoveTo::create(info.duration, Vec2(uiRoot->getPositionX(), _uiRootOriginY + _adjustHeight)); + uiRoot->runAction(moveUI); + } +} + +void InputField::keyboardWillHide(IMEKeyboardNotificationInfo& info) +{ + // Find the top-level 2D UI root container and reset it + auto uiRoot = findUIRoot(this); + if (uiRoot && _adjustHeight > 0.0f) + { + uiRoot->stopAllActions(); + + // Use the recorded origin Y to reset, fallback to 0.0f only if NaN + float targetY = std::isnan(_uiRootOriginY) ? 0.0f : _uiRootOriginY; + + // Smoothly reset the UI container's Y position back to its original position + auto resetUI = ax::MoveTo::create(info.duration, Vec2(uiRoot->getPositionX(), targetY)); + uiRoot->runAction(resetUI); + } + + // Reset the adjustment height and the origin Y state + _adjustHeight = 0.0f; + _uiRootOriginY = std::numeric_limits::quiet_NaN(); +} + +} // namespace ax::ui diff --git a/axmol/ui/InputField.h b/axmol/ui/InputField.h new file mode 100644 index 000000000000..be853171bbf3 --- /dev/null +++ b/axmol/ui/InputField.h @@ -0,0 +1,656 @@ +/**************************************************************************** +Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). + +https://axmol.dev/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "axmol/ui/Widget.h" +#include "axmol/base/InputDelegate.h" +#include "axmol/2d/DrawNode.h" +#include "axmol/2d/Label.h" +#include "axmol/base/KeyboardEventListener.h" +#include + +namespace ax::ui +{ + +/** + * @brief Editable text input widget for UI. + * + * InputField provides a modern implementation of editable text input + * within the UI system. It supports text entry, cursor management, + * selection handling, and integration with the platform IME (Input Method Editor). + * + * This class is the base component for single-line input fields, and can be + * extended or configured to support multiline input scenarios. + */ +class AX_DLL InputField : public Widget, public InputDelegate +{ + /** + * @brief Dirty flag pipeline for fine-grained update control. + * Higher priority updates (lower bit) cascade down to lower priorities. + */ + enum DirtyFlag + { + DIRTY_NONE = 0, + DIRTY_CHAR_OFFSETS = 1 << 0, // Rebuilds UTF-8 byte offset cache + DIRTY_LINE_METRICS = 1 << 1, // Rebuilds text line wrapping/width metrics + DIRTY_TEXT_GEOMETRY = 1 << 2, // Updates render label dimensions and position + DIRTY_CURSOR = 1 << 3, // Updates cursor visual coordinate position + DIRTY_SELECTION = 1 << 4, // Redraws selection highlight geometry + DIRTY_PREFERRED_X = 1 << 5, + DIRTY_TEXT = 1 << 6, // Controls label text content, visibility, and style updates + // Composite Flags for Reusability + DIRTY_TEXT_CONTENT = DIRTY_CHAR_OFFSETS | DIRTY_TEXT | DIRTY_LINE_METRICS, + DIRTY_TYPOGRAPHY = DIRTY_TEXT | DIRTY_LINE_METRICS | DIRTY_CURSOR, + + DIRTY_ALL = DIRTY_CHAR_OFFSETS | DIRTY_LINE_METRICS | DIRTY_TEXT_GEOMETRY | DIRTY_CURSOR | DIRTY_SELECTION | + DIRTY_PREFERRED_X | DIRTY_TEXT + }; + + DECLARE_CLASS_GUI_INFO + +public: + static constexpr float DEFAULT_LONG_PRESS_DELAY = 0.5f; + + /** + * InputField event type. + */ + enum class EventType + { + ATTACH_WITH_IME, + DETACH_WITH_IME, + INSERT_TEXT, + DELETE_BACKWARD, + }; + using InputFieldCallback = std::function; + + /** + * @brief Construct a new InputField. + * + * The constructor performs minimal initialization; call initWithPlaceHolder + * or use the static create() helper to construct a fully initialized + * instance. + */ + InputField(); + + /** + * @brief Destroy the InputField instance. + */ + virtual ~InputField(); + + /** + * @brief Create a new InputField instance. + * @return A new InputField instance or nullptr on failure. + */ + static InputField* create(); + + /** + * @brief Create and initialize an InputField. + * @param placeholder Placeholder text shown when the field is empty. + * @param fontName Font path or system font name. + * @param fontSize Font size in points. + * @param cursorWidth Cursor visual width in pixels. + * @param color Cursor color. + * @return A new InputField instance or nullptr on failure. + */ + static InputField* create(std::string_view placeholder, + std::string_view fontName, + float fontSize, + float cursorWidth = 2, + const Color32& color = Color32::WHITE); + + /** + * @brief Initialize the InputField with placeholder and font settings. + * @return True if initialization succeeded. + */ + bool initWithPlaceholder(std::string_view placeholder, + std::string_view fontName, + float fontSize, + float cursorWidth = 2, + const Color32& color = Color32::WHITE); + + /** + * @brief Returns the internal `Label` used for rendering text. + */ + Label* getRenderLabel(); + + /** + * @brief Get the text character count (in UTF-8 characters/graphemes). + * @return Character count. + */ + inline int getCharCount() const { return static_cast(_charCount); }; + + /** + * @brief Set placeholder text color. + */ + virtual void setPlaceholderColor(const Color32& color); + + /** + * @brief Get placeholder text color. + */ + virtual const Color32& getPlaceholderColor() const; + + /** + * @brief Set the color used to render input text. + */ + virtual void setTextColor(const Color32& textColor); + + /** + * @brief Get the color used for input text. + */ + virtual const Color32& getTextColor(void) const; + + /** + * @brief Set cursor color. + */ + void setCursorColor(const Color32& color); + + /** + * @brief Get current cursor color. + */ + const Color32& getCursorColor(void) const; + + /** + * @brief Set the input text. This updates rendering and cursor state. + * @param text New UTF-8 text for the field. + */ + virtual void setString(std::string_view text); + + /** + * @brief Get the current input text (UTF-8 string view). + */ + virtual std::string_view getString() const; + + FontType getFontType() const { return _fontType; } + + /** + * @brief Set the continuous-touch delay. + * + * Timeout in seconds used to detect a continuous touch on mobile (iOS). + * Default is 0.6s. When a continuous-touch callback is set, that callback + * fires after this delay; otherwise the system edit menu is shown. + * + * @param delay Non-negative seconds before the continuous-touch action occurs. + */ + void setContinuousTouchDelayTime(float delay) { _continuousTouchDelayTime = delay; } + + /** + * @brief Get the continuous touch delay time. + */ + float getContinuousTouchDelayTime() const { return _continuousTouchDelayTime; } + + /** + * @brief Set a callback invoked after continuous touch delay elapses. + * + * When a continuous touch is detected on mobile platforms (currently iOS only), + * the engine will, by default, present the system copy/paste edit menu automatically + * after the touch ends. If a continuous touch callback is provided via this API, + * the automatic system menu presentation will be suppressed and the supplied + * callback will be invoked instead. + * + * The default continuous touch delay is **0.6 seconds**. Use + * `setContinuousTouchDelayTime(float seconds)` to change the delay. + * + * @param callback A function that will be called with the touch location in + * world coordinates (`Point& worldPoint`) when the continuous + * touch timeout elapses. Passing an empty/null callback will + * restore the default behavior (i.e., automatic system menu). + */ + void setContinuousTouchCallback(std::function callback) + { + _continuousTouchCallback = std::move(callback); + } + + /** + * @brief Set the single visible mask character used when password mode is enabled. + * @param ch UTF-8 string view for the visible mask character (uses first grapheme). + */ + void setPasswordChar(std::string_view ch); + + /** + * @brief Get the current password mask character. + */ + std::string_view getPasswordChar() const { return _passwordChar; } + + /** + * @brief Set placeholder text string. + */ + virtual void setPlaceholderText(std::string_view text); + + /** + * @brief Get placeholder text string. + */ + virtual std::string_view getPlaceholderText(void) const; + + /** + * @brief Enable or disable password (secure) entry mode. + */ + virtual void setPasswordEnabled(bool value); + + /** + * @brief Return whether password entry is enabled. + */ + virtual bool isPasswordEnabled() const; + + /** + * Add an event listener to InputField. + */ + void addEventListener(const InputFieldCallback& callback); + + /** + * @brief Check whether the field contains no visible characters. + */ + bool isEmpty(void) const { return _charCount == 0 || _inputText.empty(); } + + /** + * @brief Enable or disable the widget. + */ + void setEnabled(bool bEnabled) override; + + /** + * @brief Mark the field editable or read-only. + */ + void setReadOnly(bool bReadOnly) { _readOnly = bReadOnly; } + + /** + * @brief Query whether the field is read-only. + */ + bool isReadOnly(void) const { return _readOnly; } + + /** + * @brief Set maximum allowed character length (UTF-8 characters). + */ + void setMaxLength(int maxLength) { setCharLimit(maxLength); } + + /** + * @brief Query whether there is an active selection. + */ + bool hasSelection() const; + + /** + * @brief Select all text. + */ + void selectAll(); + + /** + * @brief Clear any text selection. + */ + void clearSelection(); + + /** + * @brief Return the selected substring (UTF-8). + */ + std::string getSelectedText() const; + + /** + * @brief Copy current selection to clipboard. Returns false if nothing copied. + */ + bool copySelectionToClipboard() const; + + /** + * @brief Cut current selection to clipboard. + */ + bool cutSelectionToClipboard(); + + /** + * @brief Paste text from clipboard at the cursor position. + */ + bool pasteFromClipboard(); + + /** + * @brief Set selection (highlight) color. + */ + void setSelectionColor(const Color& color); + + /** + * @brief Get current selection color. + */ + const Color& getSelectionColor() const; + + /// fonts + /** + * @brief Set font size used for rendering the text. + */ + void setFontSize(float size); + + /** + * @brief Get current font size. + */ + float getFontSize() const; + + /** + * @brief Set the font used for rendering (path or system name). + */ + void setFontName(std::string_view fontName); + + /** + * @brief Get the configured font name. + */ + std::string_view getFontName() const; + + /// text alignment + /** + * @brief Set horizontal alignment of rendered text. + */ + void setTextHorizontalAlignment(TextHAlignment alignment); + + /** + * @brief Get horizontal alignment. + */ + TextHAlignment getTextHorizontalAlignment() const; + + /** + * @brief Set vertical alignment of rendered text. + */ + void setTextVerticalAlignment(TextVAlignment alignment); + + /** + * @brief Get vertical alignment. + */ + TextVAlignment getTextVerticalAlignment() const; + + /// touch area + /** + * @brief Set the custom touch area size for hit testing. + */ + void setTouchAreaSize(const Vec2& size); + + /** + * @brief Get the touch area size. + */ + Vec2 getTouchAreaSize() const; + + /** + * @brief Enable or disable the custom touch area hit test. + */ + void setTouchAreaEnabled(bool enable); + + bool onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) override; + + /** + * @brief Test whether a point hits the field (respecting touch area if enabled). + */ + bool hitTestSelf(const Vec2& pt, const Camera* camera, Vec3* p) const override; + + /** + * @brief Set the maximum allowed character count for this input field. + * + * The limit is interpreted as a maximum number of UTF-8 characters (user + * level characters/grapheme clusters as counted by the control). Calling + * this affects subsequent input/insert operations which will be clipped to + * this limit. Use `0` to indicate no limit. + * + * @param limit Maximum characters allowed (0 = unlimited). + */ + void setCharLimit(uint32_t limit); + + /** + * @brief Get the configured character limit. + * @return The maximum number of characters allowed (0 means unlimited). + */ + uint32_t getCharLimit() const { return _charLimit; } + + /** + * @brief Enable or disable multiline mode. + * In multiline mode the control supports line breaks and the cursor/selection + * logic accounts for multiple text lines. The layout is recalculated + * automatically when the text, font, or content size changes. + */ + void setMultilineEnabled(bool enabled); + + /** + * @brief Return whether multiline mode is active. + */ + bool isMultilineEnabled() const { return _multilineEnabled; } + + /** + * @brief Get the internal renderer node. + * @return Pointer to the renderer node. + */ + Node* getRenderNode() override; + + /** + * @brief Get preferred size based on rendered text. + * @return Preferred size. + */ + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; + +protected: + void dispatchEvent(EventType eventType); + void updatePresentation(); + + void updateLineHeight(); + + ////////////////////////////////////////////////////////////////////////// + + void onEnter() override; + void onExit() override; + + bool canAttachWithIME() const override; + bool canDetachWithIME() const override; + + // InputDelegate interface + void insertText(std::string_view text) override; + void deleteBackward(unsigned int numChars) override; + + void handleDeleteKeyEvent(); + + /** + @brief Open keyboard and receive input text. + */ + bool attachWithIME() override; + + /** + @brief End text input and close keyboard. + */ + bool detachWithIME() override; + + void keyboardWillShow(IMEKeyboardNotificationInfo& info) override; + void keyboardWillHide(IMEKeyboardNotificationInfo& info) override; + + bool hitTestWithIME(const Vec2& location) override; + void keyboardDidShow(IMEKeyboardNotificationInfo& /*info*/) override; + void keyboardDidHide(IMEKeyboardNotificationInfo& /*info*/) override; + + void updateContentSize(void) override; + + void showCursor(void); + void hideCursor(void); + void updateCursorTransform(void); + + void moveCursor(int direction); + void moveCursor(int direction, bool keepSelection); + + void moveCursorTo(const Vec2& point); + void moveCursorTo(const Vec2& point, bool keepSelection); + void setCursorOffset(int cursorOffset, bool keepSelection, bool markPrefxDirty = true); + + void moveCursorVertically(int direction); + + /** + * @brief Calculate the coordinate for a given cursor index. + * @param cursorOffset UTF-8 character index (0 = before first char) + * @return X coordinate in local node space, accounting for text alignment + */ + Vec2 cursorPositionFromOffset(int cursorOffset) const; + + /** + * @brief Find the cursor index at a given coordinate. + * @param x X coordinate in local node space + * @return UTF-8 character index (inverse of cursorPositionFromIndex) + */ + int cursorOffsetFromPosition(const Vec2& position) const; + + int getByteOffset(int cursorOffset) const; + + float getRenderLabelTextBottomY() const; + + void setSelection(int start, int end); + void updateSelectionLayer(void); + bool deleteSelection(bool notify); + + bool isCursorVisible() const; + + void updateLayout() override; + void onSizeChanged() override; + + // Override Widget's touch event handlers + bool onPointerDown(PointerEvent* pointerEvent) override; + void onPointerMove(PointerEvent* pointerEvent) override; + void onPointerUp(PointerEvent* pointerEvent) override; + void onPointerCancel(PointerEvent* pointerEvent) override; + + void resetTouchState(); + + // Override InputDelegate's Editor action + void performEditAction(EditAction action) override; + + bool canPerformEditAction(EditAction action) const; + + /** + * @brief Check if a specific dirty flag is set. + * @param flag The dirty flag to check. + * @return True if the flag is set. + */ + inline bool isDirty(int flag) const { return (_dirtyFlags & flag) != 0; } + + /** + * @brief Clear specific dirty flag(s). + * @param flag The dirty flag(s) to clear. + */ + inline void clearDirty(int flag) { _dirtyFlags &= ~flag; } + + /** + * @brief Mark specific dirty flag(s) to trigger deferred layout/render updates. + */ + inline void markDirty(uint32_t flag) { _dirtyFlags |= flag; } + + /** + * @brief Measure text extent using instance-specific label (optimal performance). + * @param text The text to measure. + * @return The content size of the text. + */ + Vec2 measureText(std::string_view text) const; + + void rebuildLineMetrics(); + void rebuildCharByteOffsets(); + + std::string makePasswordString(); + + /** + * @brief Updates the logical text length and clamps selection indices within valid ranges. + * @note This manages logical index boundaries, not physical/geometric rendering sizes. + */ + void updateLogicalLimits(uint32_t newCharCount); + + // Per-line metrics used for cursor/selection mapping in both single and multi-line modes. + struct LineMetrics + { + int startCharIndex = 0; // inclusive UTF-8 character index of the first character in this line + int endCharIndex = 0; // exclusive UTF-8 character index (points past the last visible character) + float lineWidth = 0.0f; // pure text width of this line (before horizontal alignment) + std::vector + charXOffsets; // length = (endCharIndex - startCharIndex + 1) + // charXOffsets[i] = x-offset from line start to the *end* of the i-th character + }; + + std::vector _lineMetrics; // single-line mode: size() == 1 + float _lineHeight = 0.0f; // uniform line height computed from font metrics + float _preferredCursorX = 0.0f; // used when moving cursor vertically to keep desired column + + std::string _fontName; + float _fontSize{24.0f}; + + bool _readOnly{false}; + uint32_t _dirtyFlags{DIRTY_ALL}; + bool _passwordEnabled{false}; + bool _cursorVisible{false}; + bool _selectingByTouch{false}; + bool _selectionTouchMoved{false}; + bool _ctrlKeyPressed{false}; + bool _shiftKeyPressed{false}; + bool _useTouchArea{false}; + bool _multilineEnabled{false}; + + bool _isAttachWithIME{false}; + + // Continuous touch delay state (manual timing, no external timer dependency) + bool _continuousTouchPending{false}; + float _continuousTouchElapsedTime{0.0f}; + float _continuousTouchDelayTime{DEFAULT_LONG_PRESS_DELAY}; + + float _adjustHeight{0.0f}; + float _uiRootOriginY{std::numeric_limits::quiet_NaN()}; + + Label* _renderLabel{nullptr}; + + std::string _inputText; + std::string _placeholderText; + std::string _passwordChar; + std::vector _charByteOffsets; // Maps UTF-8 character index to byte offset in _inputText + + Color32 _colorSpaceHolder{Color32::GRAY}; + Color32 _colorText{Color32::WHITE}; + + Sprite* _cursor{nullptr}; + + DrawNode* _selectionLayer{nullptr}; + Color _selectionColor{0.26f, 0.52f, 1.0f, 0.35f}; + + FontType _fontType{FontType::SYSTEM}; + + uint32_t _charLimit{0}; + uint32_t _charCount{0}; + + int _selectionStart{0}; // Start character index of current selection (UTF-8) + int _selectionEnd{0}; // End character index of current selection (UTF-8) + int _selectionAnchor{0}; // Anchor character index used for extending selection (UTF-8) + + int _cursorCharOffset{0}; // Cursor position as character offset (UTF-8) + int _cursorByteOffset{0}; // Cursor position as byte offset in string + + float _passwordCharWidth{0.0f}; + + KeyboardEventListener* _kbdListener{nullptr}; + + /// Touch area + Vec2 _touchAreaSize; + + /// Text alignment + TextHAlignment _textHAlignment{TextHAlignment::LEFT}; + TextVAlignment _textVAlignment{TextVAlignment::TOP}; + + std::function _continuousTouchCallback; + InputFieldCallback _eventCallback; + + // Instance-specific measure label (lazy initialized, no sharing overhead) + mutable Label* _measureLabel{nullptr}; + + static bool s_keyboardVisible; +}; + +// end of input group +/// @} + +} // namespace ax::ui diff --git a/axmol/ui/UILayoutComponent.cpp b/axmol/ui/LayoutComponent.cpp similarity index 99% rename from axmol/ui/UILayoutComponent.cpp rename to axmol/ui/LayoutComponent.cpp index 9f5d567f12b6..67b62f78bd7c 100644 --- a/axmol/ui/UILayoutComponent.cpp +++ b/axmol/ui/LayoutComponent.cpp @@ -23,8 +23,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIPageView.h" -#include "axmol/ui/UILayoutComponent.h" +#include "axmol/ui/PageView.h" +#include "axmol/ui/LayoutComponent.h" #include "axmol/scene/Node.h" #include "axmol/ui/GUIDefine.h" #include "axmol/ui/UIHelper.h" diff --git a/axmol/ui/UILayoutComponent.h b/axmol/ui/LayoutComponent.h similarity index 100% rename from axmol/ui/UILayoutComponent.h rename to axmol/ui/LayoutComponent.h diff --git a/axmol/ui/UILayout.cpp b/axmol/ui/LayoutGroup.cpp similarity index 83% rename from axmol/ui/UILayout.cpp rename to axmol/ui/LayoutGroup.cpp index 386d945d3e2a..baa57dba232a 100644 --- a/axmol/ui/UILayout.cpp +++ b/axmol/ui/LayoutGroup.cpp @@ -24,17 +24,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UILayout.h" +#include "axmol/ui/LayoutGroup.h" #include "axmol/ui/UIHelper.h" -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/Scale9Sprite.h" #include "axmol/renderer/RenderState.h" #include "axmol/base/Director.h" #include "axmol/renderer/Renderer.h" -#include "axmol/ui/UILayoutManager.h" +#include "axmol/ui/LayoutManager.h" #include "axmol/2d/DrawNode.h" #include "axmol/2d/Layer.h" #include "axmol/2d/Sprite.h" -#include "axmol/base/EventFocus.h" +#include "axmol/base/FocusEvent.h" #include "axmol/base/StencilStateManager.h" #include @@ -47,9 +47,9 @@ namespace ui static const int BACKGROUNDIMAGE_Z = (-1); static const int BCAKGROUNDCOLORRENDERER_Z = (-2); -IMPLEMENT_CLASS_GUI_INFO(Layout) +IMPLEMENT_CLASS_GUI_INFO(LayoutGroup) -Layout::Layout() +LayoutGroup::LayoutGroup() : _backGroundScale9Enabled(false) , _backGroundImage(nullptr) , _backGroundImageFileName("") @@ -81,13 +81,13 @@ Layout::Layout() // no-op } -Layout::~Layout() +LayoutGroup::~LayoutGroup() { AX_SAFE_RELEASE(_clippingStencil); AX_SAFE_DELETE(_stencilStateManager); } -void Layout::onEnter() +void LayoutGroup::onEnter() { Widget::onEnter(); if (_clippingStencil) @@ -98,7 +98,7 @@ void Layout::onEnter() _clippingRectDirty = true; } -void Layout::onExit() +void LayoutGroup::onExit() { Widget::onExit(); if (_clippingStencil) @@ -107,7 +107,7 @@ void Layout::onExit() } } -void Layout::setGlobalZOrder(float globalZOrder) +void LayoutGroup::setGlobalZOrder(float globalZOrder) { // _protectedChildren's global z order is set in ProtectedNode::setGlobalZOrder() @@ -119,9 +119,9 @@ void Layout::setGlobalZOrder(float globalZOrder) child->setGlobalZOrder(globalZOrder); } -Layout* Layout::create() +LayoutGroup* LayoutGroup::create() { - Layout* layout = new Layout(); + LayoutGroup* layout = new LayoutGroup(); if (layout->init()) { layout->autorelease(); @@ -131,30 +131,30 @@ Layout* Layout::create() return nullptr; } -bool Layout::init() +bool LayoutGroup::init() { if (Widget::init()) { - ignoreContentAdaptWithSize(false); + setAutoSize(false); setContentSize(Vec2::ZERO); setAnchorPoint(Vec2::ZERO); - onPassFocusToChild = AX_CALLBACK_2(Layout::findNearestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findNearestChildWidgetIndex, this); return true; } return false; } -void Layout::addChild(Node* child) +void LayoutGroup::addChild(Node* child) { - Layout::addChild(child, child->getLocalZOrder(), child->getTag()); + LayoutGroup::addChild(child, child->getLocalZOrder(), child->getTag()); } -void Layout::addChild(Node* child, int localZOrder) +void LayoutGroup::addChild(Node* child, int localZOrder) { - Layout::addChild(child, localZOrder, child->getTag()); + LayoutGroup::addChild(child, localZOrder, child->getTag()); } -void Layout::addChild(Node* child, int zOrder, int tag) +void LayoutGroup::addChild(Node* child, int zOrder, int tag) { if (dynamic_cast(child)) { @@ -165,7 +165,7 @@ void Layout::addChild(Node* child, int zOrder, int tag) _doLayoutDirty = true; } -void Layout::addChild(Node* child, int zOrder, std::string_view name) +void LayoutGroup::addChild(Node* child, int zOrder, std::string_view name) { if (dynamic_cast(child)) { @@ -176,30 +176,30 @@ void Layout::addChild(Node* child, int zOrder, std::string_view name) _doLayoutDirty = true; } -void Layout::removeChild(Node* child, bool cleanup) +void LayoutGroup::removeChild(Node* child, bool cleanup) { Widget::removeChild(child, cleanup); _doLayoutDirty = true; } -void Layout::removeAllChildren() +void LayoutGroup::removeAllChildren() { Widget::removeAllChildren(); _doLayoutDirty = true; } -void Layout::removeAllChildrenWithCleanup(bool cleanup) +void LayoutGroup::removeAllChildrenWithCleanup(bool cleanup) { Widget::removeAllChildrenWithCleanup(cleanup); _doLayoutDirty = true; } -bool Layout::isClippingEnabled() const +bool LayoutGroup::isClippingEnabled() const { return _clippingEnabled; } -void Layout::visit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) +void LayoutGroup::visit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) { if (!_visible) { @@ -209,7 +209,7 @@ void Layout::visit(Renderer* renderer, const Mat4& parentTransform, uint32_t par if (FLAGS_TRANSFORM_DIRTY & parentFlags || _transformUpdated || _contentSizeDirty) _clippingRectDirty = true; - adaptRenderers(); + updateLayout(); doLayout(); if (_clippingEnabled) @@ -233,7 +233,7 @@ void Layout::visit(Renderer* renderer, const Mat4& parentTransform, uint32_t par } } -void Layout::stencilClippingVisit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) +void LayoutGroup::stencilClippingVisit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) { if (!_visible) return; @@ -317,7 +317,7 @@ void Layout::stencilClippingVisit(Renderer* renderer, const Mat4& parentTransfor _director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } -void Layout::onBeforeVisitScissor() +void LayoutGroup::onBeforeVisitScissor() { auto renderView = _director->getRenderView(); // apply scissor test @@ -338,7 +338,7 @@ void Layout::onBeforeVisitScissor() } } -void Layout::onAfterVisitScissor() +void LayoutGroup::onAfterVisitScissor() { if (_scissorOldState) { @@ -358,7 +358,7 @@ void Layout::onAfterVisitScissor() } } -void Layout::scissorClippingVisit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) +void LayoutGroup::scissorClippingVisit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) { if (parentFlags & FLAGS_DIRTY_MASK) { @@ -375,21 +375,21 @@ void Layout::scissorClippingVisit(Renderer* renderer, const Mat4& parentTransfor auto beforeVisitCmdScissor = renderer->nextCallbackCommand(); beforeVisitCmdScissor->init(_globalZOrder); - beforeVisitCmdScissor->func = AX_CALLBACK_0(Layout::onBeforeVisitScissor, this); + beforeVisitCmdScissor->func = AX_CALLBACK_0(LayoutGroup::onBeforeVisitScissor, this); renderer->addCommand(beforeVisitCmdScissor); ProtectedNode::visit(renderer, parentTransform, parentFlags); auto afterVisitCmdScissor = renderer->nextCallbackCommand(); afterVisitCmdScissor->init(_globalZOrder); - afterVisitCmdScissor->func = AX_CALLBACK_0(Layout::onAfterVisitScissor, this); + afterVisitCmdScissor->func = AX_CALLBACK_0(LayoutGroup::onAfterVisitScissor, this); renderer->addCommand(afterVisitCmdScissor); renderer->popGroup(); _director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } -void Layout::setClippingEnabled(bool able) +void LayoutGroup::setClippingEnabled(bool able) { if (able == _clippingEnabled) { @@ -425,7 +425,7 @@ void Layout::setClippingEnabled(bool able) } } -void Layout::setClippingType(ClippingType type) +void LayoutGroup::setClippingType(ClippingType type) { if (type == _clippingType) { @@ -437,12 +437,12 @@ void Layout::setClippingType(ClippingType type) setClippingEnabled(clippingEnabled); } -Layout::ClippingType Layout::getClippingType() const +LayoutGroup::ClippingType LayoutGroup::getClippingType() const { return _clippingType; } -void Layout::setStencilClippingSize(const Vec2& /*size*/) +void LayoutGroup::setStencilClippingSize(const Vec2& /*size*/) { if (_clippingEnabled && _clippingType == ClippingType::STENCIL) { @@ -451,7 +451,7 @@ void Layout::setStencilClippingSize(const Vec2& /*size*/) } } -const Rect& Layout::getClippingRect() +const Rect& LayoutGroup::getClippingRect() { if (_clippingRectDirty) { @@ -463,11 +463,11 @@ const Rect& Layout::getClippingRect() const auto scissorWidth = std::fabs(worldPos2.x - worldPos1.x); const auto scissorHeight = std::fabs(worldPos2.y - worldPos1.y); - Layout* parent = this; + LayoutGroup* parent = this; while (parent) { - parent = dynamic_cast(parent->getParent()); + parent = dynamic_cast(parent->getParent()); if (parent) { if (parent->isClippingEnabled()) @@ -505,7 +505,7 @@ const Rect& Layout::getClippingRect() return _clippingRect; } -void Layout::onSizeChanged() +void LayoutGroup::onSizeChanged() { Widget::onSizeChanged(); setStencilClippingSize(_contentSize); @@ -516,11 +516,11 @@ void Layout::onSizeChanged() _backGroundImage->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); if (_backGroundScale9Enabled) { - _backGroundImage->setPreferredSize(_contentSize); + _backGroundImage->setContentSize(_contentSize); } else { - _backGroundImage->setPreferredSize(_backGroundImageTextureSize); + _backGroundImage->setContentSize(_backGroundImageTextureSize); } } if (_colorRender) @@ -533,7 +533,7 @@ void Layout::onSizeChanged() } } -void Layout::setBackGroundImageScale9Enabled(bool able) +void LayoutGroup::setBackGroundImageScale9Enabled(bool able) { if (_backGroundScale9Enabled == able) { @@ -548,23 +548,23 @@ void Layout::setBackGroundImageScale9Enabled(bool able) if (_backGroundScale9Enabled) { _backGroundImage->setRenderingType(Scale9Sprite::RenderingType::SLICE); - _backGroundImage->setPreferredSize(_contentSize); + _backGroundImage->setContentSize(_contentSize); } else { _backGroundImage->setRenderingType(Scale9Sprite::RenderingType::SIMPLE); - _backGroundImage->setPreferredSize(_backGroundImageTextureSize); + _backGroundImage->setContentSize(_backGroundImageTextureSize); } setBackGroundImageCapInsets(_backGroundImageCapInsets); } -bool Layout::isBackGroundImageScale9Enabled() const +bool LayoutGroup::isBackGroundImageScale9Enabled() const { return _backGroundScale9Enabled; } -void Layout::setBackGroundImage(std::string_view fileName, TextureResType texType) +void LayoutGroup::setBackGroundImage(std::string_view fileName, TextureResType texType) { if (fileName.empty()) { @@ -601,16 +601,16 @@ void Layout::setBackGroundImage(std::string_view fileName, TextureResType texTyp _backGroundImage->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); if (_backGroundScale9Enabled) { - _backGroundImage->setPreferredSize(_contentSize); + _backGroundImage->setContentSize(_contentSize); } else { - _backGroundImage->setPreferredSize(_backGroundImageTextureSize); + _backGroundImage->setContentSize(_backGroundImageTextureSize); } updateBackGroundImageRGBA(); } -void Layout::setBackGroundImageCapInsets(const Rect& capInsets) +void LayoutGroup::setBackGroundImageCapInsets(const Rect& capInsets) { _backGroundImageCapInsets = capInsets; if (_backGroundScale9Enabled && _backGroundImage) @@ -619,12 +619,12 @@ void Layout::setBackGroundImageCapInsets(const Rect& capInsets) } } -const Rect& Layout::getBackGroundImageCapInsets() const +const Rect& LayoutGroup::getBackGroundImageCapInsets() const { return _backGroundImageCapInsets; } -void Layout::supplyTheLayoutParameterLackToChild(Widget* child) +void LayoutGroup::supplyTheLayoutParameterLackToChild(Widget* child) { if (!child) { @@ -660,7 +660,7 @@ void Layout::supplyTheLayoutParameterLackToChild(Widget* child) } } -void Layout::addBackGroundImage() +void LayoutGroup::addBackGroundImage() { _backGroundImage = Scale9Sprite::create(); _backGroundImage->setRenderingType(Scale9Sprite::RenderingType::SIMPLE); @@ -670,7 +670,7 @@ void Layout::addBackGroundImage() _backGroundImage->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); } -void Layout::removeBackGroundImage() +void LayoutGroup::removeBackGroundImage() { if (!_backGroundImage) { @@ -682,7 +682,7 @@ void Layout::removeBackGroundImage() _backGroundImageTextureSize = Vec2::ZERO; } -void Layout::setBackGroundColorType(BackGroundColorType type) +void LayoutGroup::setBackGroundColorType(BackGroundColorType type) { if (_colorType == type) { @@ -744,12 +744,12 @@ void Layout::setBackGroundColorType(BackGroundColorType type) } } -Layout::BackGroundColorType Layout::getBackGroundColorType() const +LayoutGroup::BackGroundColorType LayoutGroup::getBackGroundColorType() const { return _colorType; } -void Layout::setBackGroundColor(const Color32& color) +void LayoutGroup::setBackGroundColor(const Color32& color) { _cColor = color; if (_colorRender) @@ -758,12 +758,12 @@ void Layout::setBackGroundColor(const Color32& color) } } -const Color32& Layout::getBackGroundColor() const +const Color32& LayoutGroup::getBackGroundColor() const { return _cColor; } -void Layout::setBackGroundColor(const Color32& startColor, const Color32& endColor) +void LayoutGroup::setBackGroundColor(const Color32& startColor, const Color32& endColor) { _gStartColor = startColor; if (_gradientRender) @@ -777,17 +777,17 @@ void Layout::setBackGroundColor(const Color32& startColor, const Color32& endCol } } -const Color32& Layout::getBackGroundStartColor() const +const Color32& LayoutGroup::getBackGroundStartColor() const { return _gStartColor; } -const Color32& Layout::getBackGroundEndColor() const +const Color32& LayoutGroup::getBackGroundEndColor() const { return _gEndColor; } -void Layout::setBackGroundColorOpacity(uint8_t opacity) +void LayoutGroup::setBackGroundColorOpacity(uint8_t opacity) { _cColor.a = opacity; switch (_colorType) @@ -805,12 +805,12 @@ void Layout::setBackGroundColorOpacity(uint8_t opacity) } } -uint8_t Layout::getBackGroundColorOpacity() const +uint8_t LayoutGroup::getBackGroundColorOpacity() const { return _cColor.a; } -void Layout::setBackGroundColorVector(const Vec2& vector) +void LayoutGroup::setBackGroundColorVector(const Vec2& vector) { _alongVector = vector; if (_gradientRender) @@ -819,34 +819,34 @@ void Layout::setBackGroundColorVector(const Vec2& vector) } } -const Vec2& Layout::getBackGroundColorVector() const +const Vec2& LayoutGroup::getBackGroundColorVector() const { return _alongVector; } -void Layout::setBackGroundImageColor(const Color32& color) +void LayoutGroup::setBackGroundImageColor(const Color32& color) { _backGroundImageColor = color; updateBackGroundImageColor(); } -void Layout::setBackGroundImageOpacity(uint8_t opacity) +void LayoutGroup::setBackGroundImageOpacity(uint8_t opacity) { _backGroundImageColor.a = opacity; updateBackGroundImageOpacity(); } -const Color32& Layout::getBackGroundImageColor() const +const Color32& LayoutGroup::getBackGroundImageColor() const { return _backGroundImageColor; } -uint8_t Layout::getBackGroundImageOpacity() const +uint8_t LayoutGroup::getBackGroundImageOpacity() const { return _backGroundImageColor.a; } -void Layout::updateBackGroundImageColor() +void LayoutGroup::updateBackGroundImageColor() { if (_backGroundImage) { @@ -854,7 +854,7 @@ void Layout::updateBackGroundImageColor() } } -void Layout::updateBackGroundImageOpacity() +void LayoutGroup::updateBackGroundImageOpacity() { if (_backGroundImage) { @@ -862,7 +862,7 @@ void Layout::updateBackGroundImageOpacity() } } -void Layout::updateBackGroundImageRGBA() +void LayoutGroup::updateBackGroundImageRGBA() { if (_backGroundImage) { @@ -870,12 +870,12 @@ void Layout::updateBackGroundImageRGBA() } } -const Vec2& Layout::getBackGroundImageTextureSize() const +const Vec2& LayoutGroup::getBackGroundImageTextureSize() const { return _backGroundImageTextureSize; } -void Layout::setLayoutType(Type type) +void LayoutGroup::setLayoutType(Type type) { _layoutType = type; @@ -890,33 +890,33 @@ void Layout::setLayoutType(Type type) _doLayoutDirty = true; } -Layout::Type Layout::getLayoutType() const +LayoutGroup::Type LayoutGroup::getLayoutType() const { return _layoutType; } -void Layout::forceDoLayout() +void LayoutGroup::forceDoLayout() { this->requestDoLayout(); this->doLayout(); } -void Layout::requestDoLayout() +void LayoutGroup::requestDoLayout() { _doLayoutDirty = true; } -Vec2 Layout::getLayoutContentSize() const +Vec2 LayoutGroup::getLayoutContentSize() const { return this->getContentSize(); } -const Vector& Layout::getLayoutElements() const +const Vector& LayoutGroup::getLayoutElements() const { return this->getChildren(); } -LayoutManager* Layout::createLayoutManager() +LayoutManager* LayoutGroup::createLayoutManager() { LayoutManager* exe = nullptr; switch (_layoutType) @@ -942,7 +942,7 @@ LayoutManager* Layout::createLayoutManager() return exe; } -void Layout::doLayout() +void LayoutGroup::doLayout() { if (!_doLayoutDirty) @@ -962,24 +962,24 @@ void Layout::doLayout() _doLayoutDirty = false; } -std::string Layout::getDescription() const +std::string LayoutGroup::getDescription() const { - return "Layout"; + return "LayoutGroup"; } -Widget* Layout::createCloneInstance() +Widget* LayoutGroup::createCloneInstance() { - return Layout::create(); + return LayoutGroup::create(); } -void Layout::copyClonedWidgetChildren(Widget* model) +void LayoutGroup::copyClonedWidgetChildren(Widget* model) { Widget::copyClonedWidgetChildren(model); } -void Layout::copySpecialProperties(Widget* widget) +void LayoutGroup::copySpecialProperties(Widget* widget) { - Layout* layout = dynamic_cast(widget); + LayoutGroup* layout = dynamic_cast(widget); if (layout) { setBackGroundImageScale9Enabled(layout->_backGroundScale9Enabled); @@ -998,34 +998,34 @@ void Layout::copySpecialProperties(Widget* widget) } } -void Layout::setLoopFocus(bool loop) +void LayoutGroup::setLoopFocus(bool loop) { _loopFocus = loop; } -bool Layout::isLoopFocus() const +bool LayoutGroup::isLoopFocus() const { return _loopFocus; } -void Layout::setPassFocusToChild(bool pass) +void LayoutGroup::setPassFocusToChild(bool pass) { _passFocusToChild = pass; } -bool Layout::isPassFocusToChild() const +bool LayoutGroup::isPassFocusToChild() const { return _passFocusToChild; } -Vec2 Layout::getLayoutAccumulatedSize() const +Vec2 LayoutGroup::getLayoutAccumulatedSize() const { const auto& children = this->getChildren(); Vec2 layoutSize = Vec2::ZERO; int widgetCount = 0; for (const auto& widget : children) { - Layout* layout = dynamic_cast(widget); + LayoutGroup* layout = dynamic_cast(widget); if (nullptr != layout) { layoutSize = layoutSize + layout->getLayoutAccumulatedSize(); @@ -1055,16 +1055,16 @@ Vec2 Layout::getLayoutAccumulatedSize() const return layoutSize; } -Vec2 Layout::getWorldCenterPoint(Widget* widget) const +Vec2 LayoutGroup::getWorldCenterPoint(Widget* widget) const { - Layout* layout = dynamic_cast(widget); + LayoutGroup* layout = dynamic_cast(widget); // FIXEDME: we don't need to calculate the content size of layout anymore Vec2 widgetSize = layout ? layout->getLayoutAccumulatedSize() : widget->getContentSize(); // AXLOGD("content size : width = {}, height = {}", widgetSize.width, widgetSize.height); return widget->convertToWorldSpace(Vec2(widgetSize.width / 2, widgetSize.height / 2)); } -float Layout::calculateNearestDistance(Widget* baseWidget) +float LayoutGroup::calculateNearestDistance(Widget* baseWidget) { float distance = FLT_MAX; @@ -1072,7 +1072,7 @@ float Layout::calculateNearestDistance(Widget* baseWidget) for (Node* node : _children) { - Layout* layout = dynamic_cast(node); + LayoutGroup* layout = dynamic_cast(node); int length; if (layout) { @@ -1100,7 +1100,7 @@ float Layout::calculateNearestDistance(Widget* baseWidget) return distance; } -float Layout::calculateFarthestDistance(ax::ui::Widget* baseWidget) +float LayoutGroup::calculateFarthestDistance(ax::ui::Widget* baseWidget) { float distance = -FLT_MAX; @@ -1108,7 +1108,7 @@ float Layout::calculateFarthestDistance(ax::ui::Widget* baseWidget) for (Node* node : _children) { - Layout* layout = dynamic_cast(node); + LayoutGroup* layout = dynamic_cast(node); int length; if (layout) { @@ -1136,7 +1136,7 @@ float Layout::calculateFarthestDistance(ax::ui::Widget* baseWidget) return distance; } -int Layout::findFirstFocusEnabledWidgetIndex() +int LayoutGroup::findFirstFocusEnabledWidgetIndex() { ssize_t index = 0; ssize_t count = this->getChildren().size(); @@ -1153,7 +1153,7 @@ int Layout::findFirstFocusEnabledWidgetIndex() return 0; } -int Layout::findNearestChildWidgetIndex(FocusDirection direction, Widget* baseWidget) +int LayoutGroup::findNearestChildWidgetIndex(FocusDirection direction, Widget* baseWidget) { if (baseWidget == nullptr || baseWidget == this) { @@ -1175,7 +1175,7 @@ int Layout::findNearestChildWidgetIndex(FocusDirection direction, Widget* baseWi { Vec2 wPosition = this->getWorldCenterPoint(w); float length; - Layout* layout = dynamic_cast(w); + LayoutGroup* layout = dynamic_cast(w); if (layout) { length = layout->calculateNearestDistance(baseWidget); @@ -1200,7 +1200,7 @@ int Layout::findNearestChildWidgetIndex(FocusDirection direction, Widget* baseWi return 0; } -int Layout::findFarthestChildWidgetIndex(FocusDirection direction, ax::ui::Widget* baseWidget) +int LayoutGroup::findFarthestChildWidgetIndex(FocusDirection direction, ax::ui::Widget* baseWidget) { if (baseWidget == nullptr || baseWidget == this) { @@ -1222,7 +1222,7 @@ int Layout::findFarthestChildWidgetIndex(FocusDirection direction, ax::ui::Widge { Vec2 wPosition = this->getWorldCenterPoint(w); float length; - Layout* layout = dynamic_cast(w); + LayoutGroup* layout = dynamic_cast(w); if (layout) { length = layout->calculateFarthestDistance(baseWidget); @@ -1247,7 +1247,7 @@ int Layout::findFarthestChildWidgetIndex(FocusDirection direction, ax::ui::Widge return 0; } -Widget* Layout::findFocusEnabledChildWidgetByIndex(ssize_t index) +Widget* LayoutGroup::findFocusEnabledChildWidgetByIndex(ssize_t index) { Widget* widget = this->getChildWidgetByIndex(index); @@ -1264,12 +1264,12 @@ Widget* Layout::findFocusEnabledChildWidgetByIndex(ssize_t index) return nullptr; } -Widget* Layout::findFirstNonLayoutWidget() +Widget* LayoutGroup::findFirstNonLayoutWidget() { Widget* widget = nullptr; for (Node* node : _children) { - Layout* layout = dynamic_cast(node); + LayoutGroup* layout = dynamic_cast(node); if (layout) { widget = layout->findFirstNonLayoutWidget(); @@ -1292,7 +1292,7 @@ Widget* Layout::findFirstNonLayoutWidget() return widget; } -void Layout::findProperSearchingFunctor(FocusDirection dir, Widget* baseWidget) +void LayoutGroup::findProperSearchingFunctor(FocusDirection dir, Widget* baseWidget) { if (baseWidget == nullptr) { @@ -1307,44 +1307,44 @@ void Layout::findProperSearchingFunctor(FocusDirection dir, Widget* baseWidget) { if (previousWidgetPosition.x > widgetPosition.x) { - onPassFocusToChild = AX_CALLBACK_2(Layout::findNearestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findNearestChildWidgetIndex, this); } else { - onPassFocusToChild = AX_CALLBACK_2(Layout::findFarthestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findFarthestChildWidgetIndex, this); } } else if (dir == FocusDirection::RIGHT) { if (previousWidgetPosition.x > widgetPosition.x) { - onPassFocusToChild = AX_CALLBACK_2(Layout::findFarthestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findFarthestChildWidgetIndex, this); } else { - onPassFocusToChild = AX_CALLBACK_2(Layout::findNearestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findNearestChildWidgetIndex, this); } } else if (dir == FocusDirection::DOWN) { if (previousWidgetPosition.y > widgetPosition.y) { - onPassFocusToChild = AX_CALLBACK_2(Layout::findNearestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findNearestChildWidgetIndex, this); } else { - onPassFocusToChild = AX_CALLBACK_2(Layout::findFarthestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findFarthestChildWidgetIndex, this); } } else if (dir == FocusDirection::UP) { if (previousWidgetPosition.y < widgetPosition.y) { - onPassFocusToChild = AX_CALLBACK_2(Layout::findNearestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findNearestChildWidgetIndex, this); } else { - onPassFocusToChild = AX_CALLBACK_2(Layout::findFarthestChildWidgetIndex, this); + onPassFocusToChild = AX_CALLBACK_2(LayoutGroup::findFarthestChildWidgetIndex, this); } } else @@ -1353,7 +1353,7 @@ void Layout::findProperSearchingFunctor(FocusDirection dir, Widget* baseWidget) } } -Widget* Layout::passFocusToChild(FocusDirection dir, ax::ui::Widget* current) +Widget* LayoutGroup::passFocusToChild(FocusDirection dir, ax::ui::Widget* current) { if (checkFocusEnabledChild()) { @@ -1363,8 +1363,8 @@ Widget* Layout::passFocusToChild(FocusDirection dir, ax::ui::Widget* current) int index = onPassFocusToChild(dir, previousWidget); - Widget* widget = this->getChildWidgetByIndex(index); - Layout* layout = dynamic_cast(widget); + Widget* widget = this->getChildWidgetByIndex(index); + LayoutGroup* layout = dynamic_cast(widget); if (layout) { layout->_isFocusPassing = true; @@ -1382,7 +1382,7 @@ Widget* Layout::passFocusToChild(FocusDirection dir, ax::ui::Widget* current) } } -bool Layout::checkFocusEnabledChild() const +bool LayoutGroup::checkFocusEnabledChild() const { bool ret = false; for (Node* node : _children) @@ -1397,7 +1397,7 @@ bool Layout::checkFocusEnabledChild() const return ret; } -Widget* Layout::getChildWidgetByIndex(ssize_t index) const +Widget* LayoutGroup::getChildWidgetByIndex(ssize_t index) const { ssize_t size = _children.size(); int count = 0; @@ -1434,7 +1434,7 @@ Widget* Layout::getChildWidgetByIndex(ssize_t index) const return widget; } -Widget* Layout::getPreviousFocusedWidget(FocusDirection direction, Widget* current) +Widget* LayoutGroup::getPreviousFocusedWidget(FocusDirection direction, Widget* current) { Widget* nextWidget = nullptr; ssize_t previousWidgetPos = _children.getIndex(current); @@ -1444,7 +1444,7 @@ Widget* Layout::getPreviousFocusedWidget(FocusDirection direction, Widget* curre nextWidget = this->getChildWidgetByIndex(previousWidgetPos); if (nextWidget->isFocusEnabled()) { - Layout* layout = dynamic_cast(nextWidget); + LayoutGroup* layout = dynamic_cast(nextWidget); if (layout) { layout->_isFocusPassing = true; @@ -1469,7 +1469,7 @@ Widget* Layout::getPreviousFocusedWidget(FocusDirection direction, Widget* curre nextWidget = this->getChildWidgetByIndex(previousWidgetPos); if (nextWidget->isFocusEnabled()) { - Layout* layout = dynamic_cast(nextWidget); + LayoutGroup* layout = dynamic_cast(nextWidget); if (layout) { layout->_isFocusPassing = true; @@ -1488,7 +1488,7 @@ Widget* Layout::getPreviousFocusedWidget(FocusDirection direction, Widget* curre } else { - if (dynamic_cast(current)) + if (dynamic_cast(current)) { return current; } @@ -1506,7 +1506,7 @@ Widget* Layout::getPreviousFocusedWidget(FocusDirection direction, Widget* curre { return Widget::findNextFocusedWidget(direction, this); } - if (dynamic_cast(current)) + if (dynamic_cast(current)) { return current; } @@ -1523,7 +1523,7 @@ Widget* Layout::getPreviousFocusedWidget(FocusDirection direction, Widget* curre } } -Widget* Layout::getNextFocusedWidget(FocusDirection direction, Widget* current) +Widget* LayoutGroup::getNextFocusedWidget(FocusDirection direction, Widget* current) { Widget* nextWidget = nullptr; ssize_t previousWidgetPos = _children.getIndex(current); @@ -1536,7 +1536,7 @@ Widget* Layout::getNextFocusedWidget(FocusDirection direction, Widget* current) { if (nextWidget->isFocusEnabled()) { - Layout* layout = dynamic_cast(nextWidget); + LayoutGroup* layout = dynamic_cast(nextWidget); if (layout) { layout->_isFocusPassing = true; @@ -1568,7 +1568,7 @@ Widget* Layout::getNextFocusedWidget(FocusDirection direction, Widget* current) nextWidget = this->getChildWidgetByIndex(previousWidgetPos); if (nextWidget->isFocusEnabled()) { - Layout* layout = dynamic_cast(nextWidget); + LayoutGroup* layout = dynamic_cast(nextWidget); if (layout) { layout->_isFocusPassing = true; @@ -1587,7 +1587,7 @@ Widget* Layout::getNextFocusedWidget(FocusDirection direction, Widget* current) } else { - if (dynamic_cast(current)) + if (dynamic_cast(current)) { return current; } @@ -1605,7 +1605,7 @@ Widget* Layout::getNextFocusedWidget(FocusDirection direction, Widget* current) { return Widget::findNextFocusedWidget(direction, this); } - if (dynamic_cast(current)) + if (dynamic_cast(current)) { return current; } @@ -1622,9 +1622,9 @@ Widget* Layout::getNextFocusedWidget(FocusDirection direction, Widget* current) } } -bool Layout::isLastWidgetInContainer(Widget* widget, FocusDirection direction) const +bool LayoutGroup::isLastWidgetInContainer(Widget* widget, FocusDirection direction) const { - Layout* parent = dynamic_cast(widget->getParent()); + LayoutGroup* parent = dynamic_cast(widget->getParent()); if (parent == nullptr) { return true; @@ -1710,9 +1710,9 @@ bool Layout::isLastWidgetInContainer(Widget* widget, FocusDirection direction) c return false; } -bool Layout::isWidgetAncestorSupportLoopFocus(Widget* widget, FocusDirection direction) const +bool LayoutGroup::isWidgetAncestorSupportLoopFocus(Widget* widget, FocusDirection direction) const { - Layout* parent = dynamic_cast(widget->getParent()); + LayoutGroup* parent = dynamic_cast(widget->getParent()); if (parent == nullptr) { return false; @@ -1754,17 +1754,17 @@ bool Layout::isWidgetAncestorSupportLoopFocus(Widget* widget, FocusDirection dir } } -Widget* Layout::findNextFocusedWidget(FocusDirection direction, Widget* current) +Widget* LayoutGroup::findNextFocusedWidget(FocusDirection direction, Widget* current) { if (_isFocusPassing || this->isFocused()) { - Layout* parent = dynamic_cast(this->getParent()); - _isFocusPassing = false; + LayoutGroup* parent = dynamic_cast(this->getParent()); + _isFocusPassing = false; if (_passFocusToChild) { Widget* w = this->passFocusToChild(direction, current); - if (dynamic_cast(w)) + if (dynamic_cast(w)) { if (parent) { @@ -1782,7 +1782,7 @@ Widget* Layout::findNextFocusedWidget(FocusDirection direction, Widget* current) parent->_isFocusPassing = true; return parent->findNextFocusedWidget(direction, this); } - else if (current->isFocused() || dynamic_cast(current)) + else if (current->isFocused() || dynamic_cast(current)) { if (_layoutType == Type::HORIZONTAL || _layoutType == Type::CENTER_HORIZONTAL) { @@ -1864,7 +1864,7 @@ Widget* Layout::findNextFocusedWidget(FocusDirection direction, Widget* current) } else { - AXASSERT(0, "Un Supported Layout type, please use VBox and HBox instead!!!"); + AXASSERT(0, "Un Supported LayoutGroup type, please use VBox and HBox instead!!!"); return current; } } @@ -1874,7 +1874,7 @@ Widget* Layout::findNextFocusedWidget(FocusDirection direction, Widget* current) } } -void Layout::setCameraMask(unsigned short mask, bool applyChildren) +void LayoutGroup::setCameraMask(unsigned short mask, bool applyChildren) { Widget::setCameraMask(mask, applyChildren); if (_clippingStencil) @@ -1883,7 +1883,7 @@ void Layout::setCameraMask(unsigned short mask, bool applyChildren) } } -ResourceData Layout::getRenderFile() +ResourceData LayoutGroup::getRenderFile() { ResourceData rData; rData.type = (int)_bgImageTexType; diff --git a/axmol/ui/UILayout.h b/axmol/ui/LayoutGroup.h similarity index 97% rename from axmol/ui/UILayout.h rename to axmol/ui/LayoutGroup.h index ee30e46fba8f..ee8836b2e907 100644 --- a/axmol/ui/UILayout.h +++ b/axmol/ui/LayoutGroup.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" #include "axmol/renderer/CustomCommand.h" #include "axmol/renderer/GroupCommand.h" @@ -52,7 +52,7 @@ class LayoutManager; class Scale9Sprite; /** - *@brief Layout interface for creating LayoutManger and do actual layout. + *@brief LayoutGroup interface for creating LayoutManger and do actual layout. */ class AX_GUI_DLL LayoutProtocol { @@ -108,14 +108,14 @@ class AX_GUI_DLL LayoutProtocol * - Relative layout: child elements are arranged relative to certain rules. * */ -class AX_GUI_DLL Layout : public Widget, public LayoutProtocol +class AX_GUI_DLL LayoutGroup : public Widget, public LayoutProtocol { DECLARE_CLASS_GUI_INFO public: /** - * Layout type, default is ABSOLUTE. + * LayoutGroup type, default is ABSOLUTE. */ enum class Type { @@ -150,18 +150,18 @@ class AX_GUI_DLL Layout : public Widget, public LayoutProtocol * Default constructor * @lua new */ - Layout(); + LayoutGroup(); /** * Default destructor * @lua NA */ - virtual ~Layout(); + virtual ~LayoutGroup(); /** * Create a empty layout. */ - static Layout* create(); + static LayoutGroup* create(); /** * Sets a background image for layout. @@ -287,7 +287,7 @@ class AX_GUI_DLL Layout : public Widget, public LayoutProtocol /** * Get color of layout's background image. - *@return Layout's background image color. + *@return LayoutGroup's background image color. */ const Color32& getBackGroundImageColor() const; @@ -346,7 +346,7 @@ class AX_GUI_DLL Layout : public Widget, public LayoutProtocol /** * Change the layout type. - *@param type Layout type. + *@param type LayoutGroup type. */ virtual void setLayoutType(Type type); @@ -628,7 +628,7 @@ class AX_GUI_DLL Layout : public Widget, public LayoutProtocol bool _scissorOldState; Rect _clippingOldRect; Rect _clippingRect; - Layout* _clippingParent; + LayoutGroup* _clippingParent; bool _clippingRectDirty; // clipping @@ -651,6 +651,9 @@ class AX_GUI_DLL Layout : public Widget, public LayoutProtocol bool _isFocusPassing; }; +// deprecated alias +using Layout = LayoutGroup; + } // namespace ui } // namespace ax // end of ui group diff --git a/axmol/ui/UILayoutManager.cpp b/axmol/ui/LayoutManager.cpp similarity index 99% rename from axmol/ui/UILayoutManager.cpp rename to axmol/ui/LayoutManager.cpp index 4e0789307bba..94c01024b55c 100644 --- a/axmol/ui/UILayoutManager.cpp +++ b/axmol/ui/LayoutManager.cpp @@ -24,8 +24,8 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UILayoutManager.h" -#include "axmol/ui/UILayout.h" +#include "axmol/ui/LayoutManager.h" +#include "axmol/ui/LayoutGroup.h" namespace ax { diff --git a/axmol/ui/UILayoutManager.h b/axmol/ui/LayoutManager.h similarity index 96% rename from axmol/ui/UILayoutManager.h rename to axmol/ui/LayoutManager.h index e26c28ffa8b1..5f1521a03304 100644 --- a/axmol/ui/UILayoutManager.h +++ b/axmol/ui/LayoutManager.h @@ -59,7 +59,7 @@ class AX_GUI_DLL LayoutManager : public Object */ virtual void doLayout(LayoutProtocol* layout) = 0; - friend class Layout; + friend class LayoutGroup; }; /** @@ -75,7 +75,7 @@ class AX_GUI_DLL LinearVerticalLayoutManager : public LayoutManager static LinearVerticalLayoutManager* create(); void doLayout(LayoutProtocol* layout) override; - friend class Layout; + friend class LayoutGroup; }; /** @@ -91,7 +91,7 @@ class AX_GUI_DLL LinearHorizontalLayoutManager : public LayoutManager static LinearHorizontalLayoutManager* create(); void doLayout(LayoutProtocol* layout) override; - friend class Layout; + friend class LayoutGroup; }; /** @@ -107,7 +107,7 @@ class AX_GUI_DLL LinearCenterVerticalLayoutManager : public LayoutManager static LinearCenterVerticalLayoutManager* create(); void doLayout(LayoutProtocol* layout) override; - friend class Layout; + friend class LayoutGroup; }; /** @@ -123,7 +123,7 @@ class AX_GUI_DLL LinearCenterHorizontalLayoutManager : public LayoutManager static LinearCenterHorizontalLayoutManager* create(); void doLayout(LayoutProtocol* layout) override; - friend class Layout; + friend class LayoutGroup; }; /** @@ -158,7 +158,7 @@ class AX_GUI_DLL RelativeLayoutManager : public LayoutManager RelativeLayoutParameter* _relativeWidgetLP; - friend class Layout; + friend class LayoutGroup; }; } // namespace ui diff --git a/axmol/ui/UILayoutParameter.cpp b/axmol/ui/LayoutParameter.cpp similarity index 98% rename from axmol/ui/UILayoutParameter.cpp rename to axmol/ui/LayoutParameter.cpp index cea8b55155ec..66aa464d49a0 100644 --- a/axmol/ui/UILayoutParameter.cpp +++ b/axmol/ui/LayoutParameter.cpp @@ -24,8 +24,8 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UILayoutParameter.h" -#include "axmol/ui/UILayout.h" +#include "axmol/ui/LayoutParameter.h" +#include "axmol/ui/LayoutGroup.h" namespace ax { diff --git a/axmol/ui/UILayoutParameter.h b/axmol/ui/LayoutParameter.h similarity index 100% rename from axmol/ui/UILayoutParameter.h rename to axmol/ui/LayoutParameter.h diff --git a/axmol/ui/UIListView.cpp b/axmol/ui/ListView.cpp similarity index 95% rename from axmol/ui/UIListView.cpp rename to axmol/ui/ListView.cpp index 8d10ab75a6d5..d4968fb9fca0 100644 --- a/axmol/ui/UIListView.cpp +++ b/axmol/ui/ListView.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIListView.h" +#include "axmol/ui/ListView.h" #include "axmol/ui/UIHelper.h" namespace ax @@ -47,9 +47,8 @@ ListView::ListView() , _bottomPadding(0.0f) , _curSelectedIndex(-1) , _innerContainerDoLayoutDirty(true) - , _eventCallback(nullptr) { - this->setTouchEnabled(true); + this->setPointerEnabled(true); } ListView::~ListView() @@ -92,9 +91,9 @@ void ListView::setItemModel(Widget* model) AX_SAFE_RETAIN(_model); } -void ListView::handleReleaseLogic(Touch* touch) +void ListView::handleReleaseLogic(PointerEvent* event) { - ScrollView::handleReleaseLogic(touch); + ScrollView::handleReleaseLogic(event); if (!_autoScrolling) { @@ -566,52 +565,39 @@ void ListView::doLayout() _innerContainerDoLayoutDirty = false; } -void ListView::addEventListener(const ccListViewCallback& callback) +void ListView::addEventListener(const ListViewCallback& callback) { _eventCallback = callback; } -void ListView::selectedItemEvent(TouchEventType event) +void ListView::selectedItemEvent(InputPhase phase) { - this->retain(); - switch (event) - { - case TouchEventType::BEGAN: + auto eventType = EventType::ON_SELECTED_ITEM_END; + if (phase == InputPhase::PointerDown) { - if (_eventCallback) - { - _eventCallback(this, EventType::ON_SELECTED_ITEM_START); - } - if (_ccEventCallback) - { - _ccEventCallback(this, static_cast(EventType::ON_SELECTED_ITEM_START)); - } + eventType = EventType::ON_SELECTED_ITEM_START; } - break; - default: + + this->retain(); + if (_eventCallback) { - if (_eventCallback) - { - _eventCallback(this, EventType::ON_SELECTED_ITEM_END); - } - if (_ccEventCallback) - { - _ccEventCallback(this, static_cast(EventType::ON_SELECTED_ITEM_END)); - } + _eventCallback(this, eventType); } - break; + if (_customEventCallback) + { + _customEventCallback(this, static_cast(eventType)); } this->release(); } -void ListView::interceptTouchEvent(TouchEventType event, Widget* sender, Touch* touch) +void ListView::interceptPointerEvent(Widget* sender, PointerEvent* event) { - ScrollView::interceptTouchEvent(event, sender, touch); - if (!_touchEnabled) + ScrollView::interceptPointerEvent(sender, event); + if (!_pointerEnabled) { return; } - if (event != TouchEventType::MOVED) + if (event->getPhase() != InputPhase::PointerMove) { Widget* parent = sender; while (parent) @@ -625,7 +611,7 @@ void ListView::interceptTouchEvent(TouchEventType event, Widget* sender, Touch* } if (sender->isHighlighted()) { - selectedItemEvent(event); + selectedItemEvent(event->getPhase()); } } } @@ -861,7 +847,7 @@ void ListView::setCurSelectedIndex(int itemIndex) return; } _curSelectedIndex = itemIndex; - this->selectedItemEvent(ax::ui::Widget::TouchEventType::ENDED); + this->selectedItemEvent(InputPhase::PointerUp); } void ListView::onSizeChanged() diff --git a/axmol/ui/UIListView.h b/axmol/ui/ListView.h similarity index 95% rename from axmol/ui/UIListView.h rename to axmol/ui/ListView.h index 9ff2a72ef640..8e639372e78d 100644 --- a/axmol/ui/UIListView.h +++ b/axmol/ui/ListView.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIScrollView.h" +#include "axmol/ui/ScrollView.h" #include "axmol/ui/GUIExport.h" /** @@ -64,15 +64,6 @@ class AX_GUI_DLL ListView : public ScrollView CENTER_VERTICAL }; - /** - * ListView element item click event. - */ - enum class EventType - { - ON_SELECTED_ITEM_START, - ON_SELECTED_ITEM_END - }; - /** * ListView supports magnetic scroll. * With CENTER type, ListView tries to align its items in center of current view. @@ -91,9 +82,14 @@ class AX_GUI_DLL ListView : public ScrollView }; /** - * ListView item click callback. + * ListView element item click event. */ - typedef std::function ccListViewCallback; + enum class EventType + { + ON_SELECTED_ITEM_START, + ON_SELECTED_ITEM_END + }; + using ListViewCallback = std::function; /** * Default constructor @@ -400,10 +396,9 @@ class AX_GUI_DLL ListView : public ScrollView void setCurSelectedIndex(int itemIndex); /** - * Add an event click callback to ListView, then one item of Listview is clicked, the callback will be called. - *@param callback A callback function with type of `ccListViewCallback`. + * Add an item selection callback to ListView. */ - void addEventListener(const ccListViewCallback& callback); + void addEventListener(const ListViewCallback& callback); using ScrollView::addEventListener; /** @@ -419,7 +414,7 @@ class AX_GUI_DLL ListView : public ScrollView bool init() override; protected: - void handleReleaseLogic(Touch* touch) override; + void handleReleaseLogic(PointerEvent* event) override; virtual void onItemListChanged(); @@ -432,8 +427,8 @@ class AX_GUI_DLL ListView : public ScrollView Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; void copyClonedWidgetChildren(Widget* model) override; - void selectedItemEvent(TouchEventType event); - void interceptTouchEvent(Widget::TouchEventType event, Widget* sender, Touch* touch) override; + void selectedItemEvent(InputPhase hase); + void interceptPointerEvent(Widget* sender, PointerEvent* event) override; Vec2 getHowMuchOutOfBoundary(const Vec2& addition = Vec2::ZERO) override; @@ -461,7 +456,7 @@ class AX_GUI_DLL ListView : public ScrollView ssize_t _curSelectedIndex; bool _innerContainerDoLayoutDirty; - ccListViewCallback _eventCallback; + ListViewCallback _eventCallback; }; } // namespace ui diff --git a/axmol/ui/UILoadingBar.cpp b/axmol/ui/LoadingBar.cpp similarity index 90% rename from axmol/ui/UILoadingBar.cpp rename to axmol/ui/LoadingBar.cpp index e01450dff39e..e77e4e393719 100644 --- a/axmol/ui/UILoadingBar.cpp +++ b/axmol/ui/LoadingBar.cpp @@ -24,9 +24,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UILoadingBar.h" +#include "axmol/ui/LoadingBar.h" #include "axmol/ui/UIHelper.h" -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/Scale9Sprite.h" #include "axmol/2d/Sprite.h" namespace ax @@ -53,7 +53,7 @@ LoadingBar::LoadingBar() , _barRendererTextureSize(Vec2::ZERO) , _originalRect(Rect::ZERO) , _scale9Enabled(false) - , _prevIgnoreSize(true) + , _prevAutoSize(true) , _capInsets(Rect::ZERO) , _barRendererAdaptDirty(true) , _textureFile("") @@ -92,7 +92,7 @@ LoadingBar* LoadingBar::create(std::string_view textureName, TextureResType texT return nullptr; } -void LoadingBar::initRenderer() +void LoadingBar::initRenderNode() { _barRenderer = Scale9Sprite::create(); _barRenderer->setScale9Enabled(false); @@ -148,7 +148,7 @@ void LoadingBar::loadTexture(std::string_view texture, TextureResType texType) } // FIXME: https://github.com/cocos2d/cocos2d-x/issues/12249 - if (!_ignoreSize && _customSize.equals(Vec2::ZERO)) + if (!_autoSize && _customSize.equals(Vec2::ZERO)) { _customSize = _barRenderer->getContentSize(); } @@ -183,7 +183,7 @@ void LoadingBar::setupTexture() barRendererScaleChangedWithSize(); - updateContentSizeWithTextureSize(_barRendererTextureSize); + updateContentSize(); this->updateProgressBar(); @@ -220,13 +220,13 @@ void LoadingBar::setScale9Enabled(bool enabled) if (_scale9Enabled) { - bool ignoreBefore = _ignoreSize; - ignoreContentAdaptWithSize(false); - _prevIgnoreSize = ignoreBefore; + bool autoSizeBefore = _autoSize; + setAutoSize(false); + _prevAutoSize = autoSizeBefore; } else { - ignoreContentAdaptWithSize(_prevIgnoreSize); + setAutoSize(_prevAutoSize); } setCapInsets(_capInsets); @@ -308,7 +308,7 @@ void LoadingBar::onSizeChanged() _barRendererAdaptDirty = true; } -void LoadingBar::adaptRenderers() +void LoadingBar::updateLayout() { if (_barRendererAdaptDirty) { @@ -317,34 +317,36 @@ void LoadingBar::adaptRenderers() } } -void LoadingBar::ignoreContentAdaptWithSize(bool ignore) +void LoadingBar::setAutoSize(bool autoSize) { - if (!_scale9Enabled || (_scale9Enabled && !ignore)) + // Note: autoSize=true means adapt to content, autoSize=false means fixed size + // For Scale9Sprite, we need special handling + if (!_scale9Enabled || (_scale9Enabled && !autoSize)) { - Widget::ignoreContentAdaptWithSize(ignore); - _prevIgnoreSize = ignore; + Widget::setAutoSize(autoSize); + _prevAutoSize = autoSize; // Store the current value for backward compatibility } } -Vec2 LoadingBar::getVirtualRendererSize() const +Vec2 LoadingBar::resolvePreferredSize(const Vec2& /*sizeHint*/) const { return _barRendererTextureSize; } -Node* LoadingBar::getVirtualRenderer() +Node* LoadingBar::getRenderNode() { return _barRenderer; } void LoadingBar::barRendererScaleChangedWithSize() { - if (_unifySize) + if (!_autoSize) { //_barRenderer->setPreferredSize(_contentSize); _totalLength = _contentSize.width; this->setPercent(_percent); } - else if (_ignoreSize) + else if (_autoSize) { if (!_scale9Enabled) { @@ -391,7 +393,7 @@ void LoadingBar::barRendererScaleChangedWithSize() void LoadingBar::setScale9Scale() { float width = (float)(_percent) / 100.0f * _totalLength; - _barRenderer->setPreferredSize(Vec2(width, _contentSize.height)); + _barRenderer->setContentSize(Vec2(width, _contentSize.height)); } std::string LoadingBar::getDescription() const @@ -409,7 +411,7 @@ void LoadingBar::copySpecialProperties(Widget* widget) LoadingBar* loadingBar = dynamic_cast(widget); if (loadingBar) { - _prevIgnoreSize = loadingBar->_prevIgnoreSize; + _prevAutoSize = loadingBar->_prevAutoSize; setScale9Enabled(loadingBar->_scale9Enabled); // clone the inner sprite: https://github.com/cocos2d/cocos2d-x/issues/16930 diff --git a/axmol/ui/UILoadingBar.h b/axmol/ui/LoadingBar.h similarity index 95% rename from axmol/ui/UILoadingBar.h rename to axmol/ui/LoadingBar.h index cfb427bb10fb..968afbd0dd50 100644 --- a/axmol/ui/UILoadingBar.h +++ b/axmol/ui/LoadingBar.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" namespace ax @@ -164,15 +164,15 @@ class AX_GUI_DLL LoadingBar : public Widget const Rect& getCapInsets() const; // override methods. - void ignoreContentAdaptWithSize(bool ignore) override; - Vec2 getVirtualRendererSize() const override; - Node* getVirtualRenderer() override; + void setAutoSize(bool autoSize) override; + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; + Node* getRenderNode() override; std::string getDescription() const override; ResourceData getRenderFile(); protected: - void initRenderer() override; + void initRenderNode() override; void onSizeChanged() override; void setScale9Scale(); @@ -183,7 +183,7 @@ class AX_GUI_DLL LoadingBar : public Widget void handleSpriteFlipX(); void loadTexture(SpriteFrame* spriteframe); - void adaptRenderers() override; + void updateLayout() override; Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; @@ -197,7 +197,7 @@ class AX_GUI_DLL LoadingBar : public Widget Vec2 _barRendererTextureSize; Rect _originalRect; bool _scale9Enabled; - bool _prevIgnoreSize; + bool _prevAutoSize; Rect _capInsets; bool _barRendererAdaptDirty; std::string _textureFile; diff --git a/axmol/ui/UIPageView.cpp b/axmol/ui/PageView.cpp similarity index 92% rename from axmol/ui/UIPageView.cpp rename to axmol/ui/PageView.cpp index 5810fae12210..9bb693c907b0 100644 --- a/axmol/ui/UIPageView.cpp +++ b/axmol/ui/PageView.cpp @@ -24,8 +24,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIPageView.h" -#include "axmol/ui/UIPageViewIndicator.h" +#include "axmol/ui/PageView.h" +#include "axmol/ui/PageViewIndicator.h" namespace ax { @@ -40,7 +40,6 @@ PageView::PageView() , _indicatorPositionAsAnchorPoint(Vec2(0.5f, 0.1f)) , _currentPageIndex(-1) , _childFocusCancelOffset(5.0f) - , _eventCallback(nullptr) , _autoScrollStopEpsilon(0.001f) , _previousPageIndex(-1) , _isTouchBegin(false) @@ -216,9 +215,9 @@ void PageView::refreshIndicatorPosition() } } -void PageView::handlePressLogic(Touch* touch) +void PageView::handlePressLogic(PointerEvent* event) { - ListView::handlePressLogic(touch); + ListView::handlePressLogic(event); if (!_isTouchBegin) { _currentPageIndex = getIndex(getCenterItemInCurrentView()); @@ -227,10 +226,10 @@ void PageView::handlePressLogic(Touch* touch) } } -void PageView::handleReleaseLogic(Touch* touch) +void PageView::handleReleaseLogic(PointerEvent* event) { // Use `ScrollView` method in order to avoid `startMagneticScroll()` by `ListView`. - ScrollView::handleReleaseLogic(touch); + ScrollView::handleReleaseLogic(event); if (_items.empty()) { @@ -279,31 +278,24 @@ float PageView::getAutoScrollStopEpsilon() const return _autoScrollStopEpsilon; } -void PageView::pageTurningEvent() +void PageView::addEventListener(const PageViewCallback& callback) { - this->retain(); - if (_eventCallback) - { - _eventCallback(this, EventType::TURNING); - } - if (_ccEventCallback) - { - _ccEventCallback(this, static_cast(EventType::TURNING)); - } - _isTouchBegin = false; - this->release(); -} + _eventCallback = callback; -void PageView::addEventListener(const ccPageViewCallback& callback) -{ - _eventCallback = callback; - ccScrollViewCallback scrollViewCallback = [this](Object* /*ref*/, ScrollView::EventType type) -> void { + ScrollViewCallback scrollViewCallback = [this](Object* sender, ScrollView::EventType type) -> void { if (type == ScrollView::EventType::AUTOSCROLL_ENDED && _previousPageIndex != _currentPageIndex) { - pageTurningEvent(); + if (_eventCallback) + { + _eventCallback(sender, EventType::TURNING); + } + if (_customEventCallback) + { + _customEventCallback(sender, static_cast(EventType::TURNING)); + } } }; - this->addEventListener(scrollViewCallback); + ScrollView::addEventListener(scrollViewCallback); } std::string PageView::getDescription() const @@ -322,13 +314,12 @@ void PageView::copySpecialProperties(Widget* widget) if (pageView) { ListView::copySpecialProperties(widget); - _eventCallback = pageView->_eventCallback; - _ccEventCallback = pageView->_ccEventCallback; _currentPageIndex = pageView->_currentPageIndex; _previousPageIndex = pageView->_previousPageIndex; _childFocusCancelOffset = pageView->_childFocusCancelOffset; _autoScrollStopEpsilon = pageView->_autoScrollStopEpsilon; _indicatorPositionAsAnchorPoint = pageView->_indicatorPositionAsAnchorPoint; + _eventCallback = pageView->_eventCallback; _isTouchBegin = pageView->_isTouchBegin; } } diff --git a/axmol/ui/UIPageView.h b/axmol/ui/PageView.h similarity index 95% rename from axmol/ui/UIPageView.h rename to axmol/ui/PageView.h index cc86e4f8903d..db2897500dc1 100644 --- a/axmol/ui/UIPageView.h +++ b/axmol/ui/PageView.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIListView.h" +#include "axmol/ui/ListView.h" #include "axmol/ui/GUIExport.h" /** @@ -69,11 +69,7 @@ class AX_GUI_DLL PageView : public ListView UP, DOWN }; - - /** - * PageView page turn event callback. - */ - typedef std::function ccPageViewCallback; + using PageViewCallback = std::function; /** * Default constructor @@ -183,8 +179,8 @@ class AX_GUI_DLL PageView : public ListView * * @param callback A page turning callback. */ - void addEventListener(const ccPageViewCallback& callback); - using ScrollView::addEventListener; + void addEventListener(const PageViewCallback& callback); + using ListView::addEventListener; // override methods std::string getDescription() const override; @@ -331,15 +327,14 @@ class AX_GUI_DLL PageView : public ListView void doLayout() override; protected: - void pageTurningEvent(); float getAutoScrollStopEpsilon() const override; void remedyLayoutParameter(Widget* item) override; void moveInnerContainer(const Vec2& deltaMove, bool canStartBounceBack) override; void onItemListChanged() override; void onSizeChanged() override; - void handleReleaseLogic(Touch* touch) override; - void handlePressLogic(Touch* touch) override; + void handleReleaseLogic(PointerEvent* event) override; + void handlePressLogic(PointerEvent* event) override; Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; @@ -354,7 +349,7 @@ class AX_GUI_DLL PageView : public ListView float _childFocusCancelOffset; - ccPageViewCallback _eventCallback; + PageViewCallback _eventCallback; float _autoScrollStopEpsilon; ssize_t _previousPageIndex; bool _isTouchBegin; diff --git a/axmol/ui/UIPageViewIndicator.cpp b/axmol/ui/PageViewIndicator.cpp similarity index 99% rename from axmol/ui/UIPageViewIndicator.cpp rename to axmol/ui/PageViewIndicator.cpp index 71bfc5bd9657..c90eae8725c3 100644 --- a/axmol/ui/UIPageViewIndicator.cpp +++ b/axmol/ui/PageViewIndicator.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIPageViewIndicator.h" +#include "axmol/ui/PageViewIndicator.h" #include "axmol/2d/Sprite.h" #include "axmol/base/Utils.h" diff --git a/axmol/ui/UIPageViewIndicator.h b/axmol/ui/PageViewIndicator.h similarity index 99% rename from axmol/ui/UIPageViewIndicator.h rename to axmol/ui/PageViewIndicator.h index cad1b3604701..2ecd1610cd96 100644 --- a/axmol/ui/UIPageViewIndicator.h +++ b/axmol/ui/PageViewIndicator.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIPageView.h" +#include "axmol/ui/PageView.h" #include "axmol/2d/Sprite.h" namespace ax diff --git a/axmol/ui/UIRadioButton.cpp b/axmol/ui/RadioButton.cpp similarity index 81% rename from axmol/ui/UIRadioButton.cpp rename to axmol/ui/RadioButton.cpp index 10c4b6355457..869c437cd6c4 100644 --- a/axmol/ui/UIRadioButton.cpp +++ b/axmol/ui/RadioButton.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIRadioButton.h" +#include "axmol/ui/RadioButton.h" namespace ax { @@ -34,12 +34,11 @@ namespace ui IMPLEMENT_CLASS_GUI_INFO(RadioButton) -RadioButton::RadioButton() : _radioButtonEventCallback(nullptr), _group(nullptr) {} +RadioButton::RadioButton() : _group(nullptr) {} RadioButton::~RadioButton() { - _radioButtonEventCallback = nullptr; - _group = nullptr; + _group = nullptr; } RadioButton* RadioButton::create() @@ -85,27 +84,28 @@ RadioButton* RadioButton::create(std::string_view backGround, std::string_view c void RadioButton::dispatchSelectChangedEvent(bool selected) { - EventType eventType = (selected ? EventType::SELECTED : EventType::UNSELECTED); + auto eventType = selected ? EventType::SELECTED : EventType::UNSELECTED; this->retain(); - if (_radioButtonEventCallback) + if (_eventCallback) { - _radioButtonEventCallback(this, eventType); + _eventCallback(this, eventType); } - if (_ccEventCallback) + if (_customEventCallback) { - _ccEventCallback(this, static_cast(eventType)); + _customEventCallback(this, static_cast(eventType)); } if (selected && _group != nullptr) { + RefPtr guard(this); _group->onChangedRadioButtonSelect(this); } this->release(); } -void RadioButton::addEventListener(const ccRadioButtonCallback& callback) +void RadioButton::addEventListener(const RadioButtonCallback& callback) { - _radioButtonEventCallback = callback; + _eventCallback = callback; } void RadioButton::releaseUpEvent() @@ -135,20 +135,16 @@ void RadioButton::copySpecialProperties(Widget* widget) if (radioButton) { AbstractCheckButton::copySpecialProperties(widget); - _radioButtonEventCallback = radioButton->_radioButtonEventCallback; - _ccEventCallback = radioButton->_ccEventCallback; - _group = radioButton->_group; + _eventCallback = radioButton->_eventCallback; + _group = radioButton->_group; } } -RadioButtonGroup::RadioButtonGroup() - : _radioButtonGroupEventCallback(nullptr), _selectedRadioButton(nullptr), _allowedNoSelection(false) -{} +RadioButtonGroup::RadioButtonGroup() : _selectedRadioButton(nullptr), _allowedNoSelection(false) {} RadioButtonGroup::~RadioButtonGroup() { - _radioButtonGroupEventCallback = nullptr; - _selectedRadioButton = nullptr; + _selectedRadioButton = nullptr; _radioButtons.clear(); } @@ -164,9 +160,9 @@ RadioButtonGroup* RadioButtonGroup::create() return nullptr; } -void RadioButtonGroup::addEventListener(const ccRadioButtonGroupCallback& callback) +void RadioButtonGroup::addEventListener(const RadioButtonGroupCallback& callback) { - _radioButtonGroupEventCallback = callback; + _eventCallback = callback; } void RadioButtonGroup::addRadioButton(RadioButton* radioButton) @@ -320,10 +316,9 @@ void RadioButtonGroup::copySpecialProperties(Widget* widget) RadioButtonGroup* radioButtonGroup = dynamic_cast(widget); if (radioButtonGroup) { - _radioButtonGroupEventCallback = radioButtonGroup->_radioButtonGroupEventCallback; - _ccEventCallback = radioButtonGroup->_ccEventCallback; - _selectedRadioButton = radioButtonGroup->_selectedRadioButton; - _allowedNoSelection = radioButtonGroup->_allowedNoSelection; + _selectedRadioButton = radioButtonGroup->_selectedRadioButton; + _allowedNoSelection = radioButtonGroup->_allowedNoSelection; + _eventCallback = radioButtonGroup->_eventCallback; _radioButtons.clear(); for (const auto& radioButton : radioButtonGroup->_radioButtons) @@ -342,14 +337,13 @@ void RadioButtonGroup::onChangedRadioButtonSelect(RadioButton* radioButton) } this->retain(); - if (_radioButtonGroupEventCallback) + if (_eventCallback) { - int index = (int)_radioButtons.getIndex(radioButton); - _radioButtonGroupEventCallback(_selectedRadioButton, index, EventType::SELECT_CHANGED); + _eventCallback(radioButton, getSelectedButtonIndex(), EventType::SELECT_CHANGED); } - if (_ccEventCallback) + if (_customEventCallback) { - _ccEventCallback(this, static_cast(EventType::SELECT_CHANGED)); + _customEventCallback(this, static_cast(EventType::SELECT_CHANGED)); } this->release(); } diff --git a/axmol/ui/UIRadioButton.h b/axmol/ui/RadioButton.h similarity index 87% rename from axmol/ui/UIRadioButton.h rename to axmol/ui/RadioButton.h index 39fd4b630cc1..989480201107 100644 --- a/axmol/ui/UIRadioButton.h +++ b/axmol/ui/RadioButton.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIAbstractCheckButton.h" +#include "axmol/ui/AbstractCheckButton.h" #include "axmol/ui/GUIExport.h" /** @@ -54,19 +54,14 @@ class AX_GUI_DLL RadioButton : public AbstractCheckButton public: /** - * Radio button event types. + * RadioButton event type. */ enum class EventType { SELECTED, UNSELECTED }; - - /** - * A callback which will be called after certain RadioButton event issue. - * @see `RadioButton::EventType` - */ - typedef std::function ccRadioButtonCallback; + using RadioButtonCallback = std::function; /** * Default constructor. @@ -120,9 +115,8 @@ class AX_GUI_DLL RadioButton : public AbstractCheckButton /** * Add a callback function which would be called when radio button is selected or unselected. - *@param callback A std::function with type @see `ccRadioButtonCallback` */ - void addEventListener(const ccRadioButtonCallback& callback); + void addEventListener(const RadioButtonCallback& callback); std::string getDescription() const override; @@ -134,7 +128,7 @@ class AX_GUI_DLL RadioButton : public AbstractCheckButton Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; - ccRadioButtonCallback _radioButtonEventCallback; + RadioButtonCallback _eventCallback; RadioButtonGroup* _group; }; @@ -154,12 +148,7 @@ class AX_GUI_DLL RadioButtonGroup : public Widget { SELECT_CHANGED, }; - - /** - * A callback which will be called after RadioButtonGroup event issue. - * @see `RadioButtonGroup::EventType` - */ - typedef std::function ccRadioButtonGroupCallback; + using RadioButtonGroupCallback = std::function; /** * Default constructor. @@ -181,10 +170,9 @@ class AX_GUI_DLL RadioButtonGroup : public Widget static RadioButtonGroup* create(); /** - * Add a callback function which would be called when radio button is selected or unselected. - *@param callback A std::function with type @see `ccRadioButtonGroupCallback` + * Add a callback function which would be called when selected radio button changes. */ - void addEventListener(const ccRadioButtonGroupCallback& callback); + void addEventListener(const RadioButtonGroupCallback& callback); /** * Get the index of selected radio button. @@ -282,8 +270,8 @@ class AX_GUI_DLL RadioButtonGroup : public Widget void deselect(); Vector _radioButtons; - ccRadioButtonGroupCallback _radioButtonGroupEventCallback; RadioButton* _selectedRadioButton; + RadioButtonGroupCallback _eventCallback; bool _allowedNoSelection; }; diff --git a/axmol/ui/UIRelativeBox.cpp b/axmol/ui/RelativeBox.cpp similarity index 98% rename from axmol/ui/UIRelativeBox.cpp rename to axmol/ui/RelativeBox.cpp index 59bbaf02f334..825e6464c8ce 100644 --- a/axmol/ui/UIRelativeBox.cpp +++ b/axmol/ui/RelativeBox.cpp @@ -24,7 +24,7 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIRelativeBox.h" +#include "axmol/ui/RelativeBox.h" namespace ax { diff --git a/axmol/ui/UIRelativeBox.h b/axmol/ui/RelativeBox.h similarity index 98% rename from axmol/ui/UIRelativeBox.h rename to axmol/ui/RelativeBox.h index 1e80242b89dc..7d4daa307322 100644 --- a/axmol/ui/UIRelativeBox.h +++ b/axmol/ui/RelativeBox.h @@ -26,7 +26,7 @@ #pragma once -#include "axmol/ui/UILayout.h" +#include "axmol/ui/LayoutGroup.h" #include "axmol/ui/GUIExport.h" namespace ax diff --git a/axmol/ui/UIRichText.cpp b/axmol/ui/RichText.cpp similarity index 98% rename from axmol/ui/UIRichText.cpp rename to axmol/ui/RichText.cpp index 7e6a817bf026..5b6698d8a52c 100644 --- a/axmol/ui/UIRichText.cpp +++ b/axmol/ui/RichText.cpp @@ -24,7 +24,7 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIRichText.h" +#include "axmol/ui/RichText.h" #include #include @@ -34,7 +34,7 @@ #include "axmol/platform/FileUtils.h" #include "axmol/platform/Application.h" -#include "axmol/base/EventListenerTouch.h" +#include "axmol/base/PointerEventListener.h" #include "axmol/base/EventDispatcher.h" #include "axmol/base/Director.h" #include "axmol/2d/Label.h" @@ -76,10 +76,9 @@ class UrlTouchListenerComponent : public Component setName(UrlTouchListenerComponent::COMPONENT_NAME); - _touchListener = ax::EventListenerTouchOneByOne::create(); - _touchListener->onTouchBegan = AX_CALLBACK_2(UrlTouchListenerComponent::onTouchBegan, this); - _touchListener->onTouchEnded = AX_CALLBACK_2(UrlTouchListenerComponent::onTouchEnded, this); - _touchListener->setSwallowTouches(true); + _touchListener = ax::PointerEventListener::create(); + _touchListener->onPointerDown = AX_CALLBACK_1(UrlTouchListenerComponent::onPointerDown, this); + _touchListener->onPointerUp = AX_CALLBACK_1(UrlTouchListenerComponent::onPointerUp, this); Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(_touchListener, _parent); return true; @@ -91,11 +90,11 @@ class UrlTouchListenerComponent : public Component _touchListener = nullptr; } - bool onTouchBegan(Touch* touch, Event* /*event*/) + bool onPointerDown(PointerEvent* pointerEvent) { // FIXME: Node::getBoundBox() doesn't return it in local coordinates... so create one manually. const auto localRect = Rect(Vec2::ZERO, _parent->getContentSize()); - if (localRect.containsPoint(_parent->convertTouchToNodeSpace(touch))) + if (localRect.containsPoint(_parent->convertPointerToNodeSpace(pointerEvent))) { return true; } @@ -103,7 +102,7 @@ class UrlTouchListenerComponent : public Component return false; } - void onTouchEnded(Touch* /*touch*/, Event* /*event*/) + void onPointerUp(PointerEvent*) { if (_handleOpenUrl) { @@ -117,7 +116,7 @@ class UrlTouchListenerComponent : public Component Node* _parent; // weak ref. std::string _url; RichText::OpenUrlHandler _handleOpenUrl; - RefPtr _touchListener; // strong ref. + RefPtr _touchListener; // strong ref. }; const std::string UrlTouchListenerComponent::COMPONENT_NAME("ax_ui_UIRichText_UrlTouchListenerComponent"); @@ -1335,7 +1334,7 @@ bool RichText::setString(std::string_view text) return true; } -void RichText::initRenderer() {} +void RichText::initRenderNode() {} void RichText::insertElement(RichElement* element, int index) { @@ -1757,7 +1756,7 @@ void RichText::formatText(bool force) this->removeAllProtectedChildren(); _elementRenders.clear(); _lineHeights.clear(); - if (_ignoreSize) + if (_autoSize) { addNewLine(); for (ssize_t i = 0, size = _richElements.size(); i < size; ++i) @@ -2280,7 +2279,7 @@ void RichText::formatRenderers() float verticalSpace = _defaults[KEY_VERTICAL_SPACE].asFloat(); float fontSize = _defaults[KEY_FONT_SIZE].asFloat(); - if (_ignoreSize) + if (_autoSize) { const auto verticalAlignment = static_cast(_defaults.at(KEY_VERTICAL_ALIGNMENT).asInt()); @@ -2389,16 +2388,7 @@ void RichText::formatRenderers() _elementRenders.clear(); _lineHeights.clear(); - if (_ignoreSize) - { - Vec2 s = getVirtualRendererSize(); - this->setContentSize(s); - } - else - { - this->setContentSize(_customSize); - } - updateContentSizeWithTextureSize(_contentSize); + updateContentSize(); } namespace @@ -2465,7 +2455,7 @@ float RichText::stripTrailingWhitespace(const Vector& row) return 0.0f; } -void RichText::adaptRenderers() +void RichText::updateLayout() { this->formatText(); } @@ -2484,12 +2474,12 @@ void RichText::setVerticalSpace(float space) _defaults[KEY_VERTICAL_SPACE] = space; } -void RichText::ignoreContentAdaptWithSize(bool ignore) +void RichText::setAutoSize(bool autoSize) { - if (_ignoreSize != ignore) + if (_autoSize != autoSize) { _formatTextDirty = true; - Widget::ignoreContentAdaptWithSize(ignore); + Widget::setAutoSize(autoSize); } } diff --git a/axmol/ui/UIRichText.h b/axmol/ui/RichText.h similarity index 99% rename from axmol/ui/UIRichText.h rename to axmol/ui/RichText.h index 45e061ae48ce..9f673f39cf63 100644 --- a/axmol/ui/UIRichText.h +++ b/axmol/ui/RichText.h @@ -25,7 +25,7 @@ ****************************************************************************/ #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" #include "axmol/base/Value.h" @@ -503,7 +503,7 @@ class AX_GUI_DLL RichText : public Widget void formatText(bool force = false); // override functions. - void ignoreContentAdaptWithSize(bool ignore) override; + void setAutoSize(bool autoSize) override; std::string getDescription() const override; void setWrapMode(WrapMode wrapMode); /*!< sets the wrapping mode: WRAP_PER_CHAR or WRAP_PER_WORD */ @@ -591,9 +591,9 @@ class AX_GUI_DLL RichText : public Widget bool setString(std::string_view text); protected: - void adaptRenderers() override; + void updateLayout() override; - void initRenderer() override; + void initRenderNode() override; void pushToContainer(Node* renderer); void handleTextRenderer(std::string_view text, std::string_view fontName, diff --git a/axmol/ui/UIScale9Sprite.cpp b/axmol/ui/Scale9Sprite.cpp similarity index 98% rename from axmol/ui/UIScale9Sprite.cpp rename to axmol/ui/Scale9Sprite.cpp index ef30ead52bf2..51873d99ac2c 100644 --- a/axmol/ui/UIScale9Sprite.cpp +++ b/axmol/ui/Scale9Sprite.cpp @@ -24,7 +24,7 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/Scale9Sprite.h" #include "axmol/2d/Sprite.h" #include "axmol/base/Vector.h" #include "axmol/base/Director.h" @@ -313,11 +313,6 @@ void Scale9Sprite::setSpriteFrame(SpriteFrame* spriteFrame, const Rect& capInset setCapInsets(capInsets); } -void Scale9Sprite::setPreferredSize(const Vec2& preferredSize) -{ - setContentSize(preferredSize); -} - void Scale9Sprite::setInsetLeft(float insetLeft) { _insetLeft = insetLeft; @@ -357,11 +352,6 @@ ax::Vec2 Scale9Sprite::getOriginalSize() const return _originalContentSize; } -ax::Vec2 Scale9Sprite::getPreferredSize() const -{ - return getContentSize(); -} - float Scale9Sprite::getInsetLeft() const { return _insetLeft; diff --git a/axmol/ui/UIScale9Sprite.h b/axmol/ui/Scale9Sprite.h similarity index 98% rename from axmol/ui/UIScale9Sprite.h rename to axmol/ui/Scale9Sprite.h index 0e21cf8449c8..5aeb7c7f5a1e 100644 --- a/axmol/ui/UIScale9Sprite.h +++ b/axmol/ui/Scale9Sprite.h @@ -401,6 +401,9 @@ class AX_GUI_DLL Scale9Sprite : public Sprite */ State getState() const; + AX_DEPRECATED(3.0) inline void setPreferredSize(const Size& size) { this->setContentSize(size); } + AX_DEPRECATED(3.0) inline const Size& getPreferredSize() const { return this->getContentSize(); } + /** * @brief Query the sprite's original size. * @@ -408,20 +411,6 @@ class AX_GUI_DLL Scale9Sprite : public Sprite */ Vec2 getOriginalSize() const; - /** - * @brief Change the preferred size of Scale9Sprite. - * - * @param size A delimitation zone. - */ - void setPreferredSize(const Vec2& size); - - /** - * @brief Query the Scale9Sprite's preferred size. - * - * @return Scale9Sprite's preferred size. - */ - Vec2 getPreferredSize() const; - /** * @brief Change the left sprite's cap inset. * diff --git a/axmol/ui/UIScrollView.cpp b/axmol/ui/ScrollView.cpp similarity index 90% rename from axmol/ui/UIScrollView.cpp rename to axmol/ui/ScrollView.cpp index dba3090d2ff2..5bfad1ecd320 100644 --- a/axmol/ui/UIScrollView.cpp +++ b/axmol/ui/ScrollView.cpp @@ -24,11 +24,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIScrollView.h" +#include "axmol/ui/ScrollView.h" #include "axmol/base/Director.h" #include "axmol/base/Utils.h" #include "axmol/platform/Device.h" -#include "axmol/ui/UIScrollViewBar.h" +#include "axmol/ui/ScrollViewBar.h" #include "axmol/2d/TweenFunction.h" #include "axmol/scene/Camera.h" namespace ax @@ -82,20 +82,16 @@ ScrollView::ScrollView() , _scrollBarEnabled(true) , _verticalScrollBar(nullptr) , _horizontalScrollBar(nullptr) - , _scrollViewEventListener(nullptr) - , _eventCallback(nullptr) , _scrollTime(DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM) { - setTouchEnabled(true); - setMouseEnabled(true); - _propagateTouchEvents = false; + setPointerEnabled(true); + _propagatePointerEvents = false; } ScrollView::~ScrollView() { - _verticalScrollBar = nullptr; - _horizontalScrollBar = nullptr; - _scrollViewEventListener = nullptr; + _verticalScrollBar = nullptr; + _horizontalScrollBar = nullptr; } ScrollView* ScrollView::create() @@ -112,22 +108,22 @@ ScrollView* ScrollView::create() void ScrollView::onEnter() { - Layout::onEnter(); + LayoutGroup::onEnter(); scheduleUpdate(); } void ScrollView::onExit() { - Layout::onExit(); + LayoutGroup::onExit(); stopOverallScroll(); } bool ScrollView::init() { - if (Layout::init()) + if (LayoutGroup::init()) { setClippingEnabled(true); - _innerContainer->setTouchEnabled(false); + _innerContainer->setPointerEnabled(false); if (_scrollBarEnabled) { initScrollBar(); @@ -138,10 +134,10 @@ bool ScrollView::init() return false; } -void ScrollView::initRenderer() +void ScrollView::initRenderNode() { - Layout::initRenderer(); - _innerContainer = Layout::create(); + LayoutGroup::initRenderNode(); + _innerContainer = LayoutGroup::create(); _innerContainer->setColor(Color32::WHITE); _innerContainer->setCascadeColorEnabled(true); addProtectedChild(_innerContainer, 1, 1); @@ -149,7 +145,7 @@ void ScrollView::initRenderer() void ScrollView::onSizeChanged() { - Layout::onSizeChanged(); + LayoutGroup::onSizeChanged(); _topBoundary = _contentSize.height; _rightBoundary = _contentSize.width; Vec2 innerSize = _innerContainer->getContentSize(); @@ -261,16 +257,7 @@ void ScrollView::setInnerContainerPosition(const Vec2& position) } } - this->retain(); - if (_eventCallback) - { - _eventCallback(this, EventType::CONTAINER_MOVED); - } - if (_ccEventCallback) - { - _ccEventCallback(this, static_cast(EventType::CONTAINER_MOVED)); - } - this->release(); + dispatchEvent(EventType::CONTAINER_MOVED); } const Vec2& ScrollView::getInnerContainerPosition() const @@ -524,11 +511,11 @@ void ScrollView::stopScroll() { if (_verticalScrollBar != nullptr) { - _verticalScrollBar->onTouchEnded(); + _verticalScrollBar->onPointerUp(); } if (_horizontalScrollBar != nullptr) { - _horizontalScrollBar->onTouchEnded(); + _horizontalScrollBar->onPointerUp(); } _scrolling = false; @@ -546,11 +533,11 @@ void ScrollView::stopAutoScroll() { if (_verticalScrollBar != nullptr) { - _verticalScrollBar->onTouchEnded(); + _verticalScrollBar->onPointerUp(); } if (_horizontalScrollBar != nullptr) { - _horizontalScrollBar->onTouchEnded(); + _horizontalScrollBar->onPointerUp(); } _autoScrolling = false; @@ -937,10 +924,10 @@ void ScrollView::jumpToPercentBothDirection(const Vec2& percent) jumpToDestination(Vec2(-(percent.x * w / 100.0f), minY + percent.y * h / 100.0f)); } -bool ScrollView::calculateCurrAndPrevTouchPoints(Touch* touch, Vec3* currPt, Vec3* prevPt) +bool ScrollView::calculateCurrAndPrevPoints(PointerEvent* event, Vec3* currPt, Vec3* prevPt) { - if (nullptr == _hittedByCamera || false == hitTest(touch->getLocation(), _hittedByCamera, currPt) || - false == hitTest(touch->getPreviousLocation(), _hittedByCamera, prevPt)) + if (nullptr == _hittedByCamera || false == hitTestSelf(event->getLocation(), _hittedByCamera, currPt) || + false == hitTestSelf(event->getPreviousLocation(), _hittedByCamera, prevPt)) { return false; } @@ -961,7 +948,7 @@ void ScrollView::gatherTouchMove(const Vec2& delta) _touchMovePreviousTimestamp = timestamp; } -void ScrollView::handlePressLogic(Touch* /*touch*/) +void ScrollView::handlePressLogic(PointerEvent* /*event*/) { _bePressed = true; _autoScrolling = false; @@ -975,21 +962,21 @@ void ScrollView::handlePressLogic(Touch* /*touch*/) if (_verticalScrollBar != nullptr) { - _verticalScrollBar->onTouchBegan(); + _verticalScrollBar->onPointerDown(); } if (_horizontalScrollBar != nullptr) { - _horizontalScrollBar->onTouchBegan(); + _horizontalScrollBar->onPointerDown(); } } -void ScrollView::handleMoveLogic(Touch* touch) +void ScrollView::handleMoveLogic(PointerEvent* event) { if (!_bePressed) return; Vec3 currPt, prevPt; - if (!calculateCurrAndPrevTouchPoints(touch, &currPt, &prevPt)) + if (!calculateCurrAndPrevPoints(event, &currPt, &prevPt)) { return; } @@ -1001,7 +988,7 @@ void ScrollView::handleMoveLogic(Touch* touch) gatherTouchMove(delta); } -void ScrollView::handleReleaseLogic(Touch* touch) +void ScrollView::handleReleaseLogic(PointerEvent* event) { if (!_bePressed) return; @@ -1009,7 +996,7 @@ void ScrollView::handleReleaseLogic(Touch* touch) // Gather the last touch information when released { Vec3 currPt, prevPt; - if (calculateCurrAndPrevTouchPoints(touch, &currPt, &prevPt)) + if (calculateCurrAndPrevPoints(event, &currPt, &prevPt)) { Vec3 delta3 = currPt - prevPt; Vec2 delta(delta3.x, delta3.y); @@ -1031,11 +1018,11 @@ void ScrollView::handleReleaseLogic(Touch* touch) if (_verticalScrollBar != nullptr) { - _verticalScrollBar->onTouchEnded(); + _verticalScrollBar->onPointerUp(); } if (_horizontalScrollBar != nullptr) { - _horizontalScrollBar->onTouchEnded(); + _horizontalScrollBar->onPointerUp(); } if (_scrolling) @@ -1044,75 +1031,80 @@ void ScrollView::handleReleaseLogic(Touch* touch) } } -bool ScrollView::onTouchBegan(Touch* touch, Event* unusedEvent) +bool ScrollView::onPointerDown(PointerEvent* event) { - bool pass = Layout::onTouchBegan(touch, unusedEvent); + bool pass = LayoutGroup::onPointerDown(event); if (!_isInterceptTouch) { if (_hitted) { - handlePressLogic(touch); + handlePressLogic(event); } } return pass; } -void ScrollView::onTouchMoved(Touch* touch, Event* unusedEvent) +void ScrollView::onPointerMove(PointerEvent* event) { - Layout::onTouchMoved(touch, unusedEvent); + LayoutGroup::onPointerMove(event); if (!_isInterceptTouch) { - handleMoveLogic(touch); + handleMoveLogic(event); } } -void ScrollView::onTouchEnded(Touch* touch, Event* unusedEvent) +void ScrollView::onPointerUp(PointerEvent* event) { - Layout::onTouchEnded(touch, unusedEvent); + LayoutGroup::onPointerUp(event); if (!_isInterceptTouch) { - handleReleaseLogic(touch); + handleReleaseLogic(event); } _isInterceptTouch = false; } -void ScrollView::onTouchCancelled(Touch* touch, Event* unusedEvent) +void ScrollView::onPointerCancel(PointerEvent* event) { - Layout::onTouchCancelled(touch, unusedEvent); + LayoutGroup::onPointerCancel(event); if (!_isInterceptTouch) { - handleReleaseLogic(touch); + handleReleaseLogic(event); } _isInterceptTouch = false; } -bool ScrollView::onMouseScroll(Event* event) +bool ScrollView::onPointerScroll(PointerEvent* event) { - bool pass = Widget::onMouseScroll(event); + if (!event || !isVisible() || !isEnabled() || !isAncestorsEnabled() || !isAncestorsVisible(this)) + return false; - if (pass) - { - auto mouseEvent = static_cast(event); - float mouseFactor = 20.f; - Vec2 move; + if (_direction == Direction::NONE) + return false; - if (_direction == Direction::HORIZONTAL) - { - move = Vec2(mouseEvent->getScrollY() * mouseFactor, 0.f); - } - else - { - move = Vec2(0.f, mouseEvent->getScrollY() * mouseFactor); - } + // Widget::onPointerScroll() returns false by default. ScrollView handles + // wheel/trackpad scrolling itself once EventDispatcher has hit-tested it. + constexpr float mouseFactor = 20.f; + Vec2 move; - bool origBounce = _bounceEnabled; - _bounceEnabled = false; - scrollChildren(move); - _bounceEnabled = origBounce; - processScrollingEndedEvent(); + if (_direction == Direction::HORIZONTAL) + { + move = Vec2(event->getScrollDelta().y * mouseFactor, 0.f); + } + else + { + move = Vec2(0.f, event->getScrollDelta().y * mouseFactor); } - return pass; + if (move == Vec2::ZERO) + return false; + + bool origBounce = _bounceEnabled; + _bounceEnabled = false; + scrollChildren(move); + _bounceEnabled = origBounce; + processScrollingEndedEvent(); + + return true; } void ScrollView::update(float dt) @@ -1123,28 +1115,28 @@ void ScrollView::update(float dt) } } -void ScrollView::interceptTouchEvent(Widget::TouchEventType event, Widget* sender, Touch* touch) +void ScrollView::interceptPointerEvent(Widget* sender, PointerEvent* event) { - if (!_touchEnabled) + if (!_pointerEnabled) { - Layout::interceptTouchEvent(event, sender, touch); + LayoutGroup::interceptPointerEvent(sender, event); return; } if (_direction == Direction::NONE) return; - Vec2 touchPoint = touch->getLocation(); - switch (event) + Vec2 touchPoint = event->getLocation(); + switch (event->getPhase()) { - case TouchEventType::BEGAN: + case InputPhase::PointerDown: { - _isInterceptTouch = true; - _touchBeganPosition = touch->getLocation(); - handlePressLogic(touch); + _isInterceptTouch = true; + _pointerDownPosition = event->getLocation(); + handlePressLogic(event); } break; - case TouchEventType::MOVED: + case InputPhase::PointerMove: { - _touchMovePosition = touch->getLocation(); + _pointerMovePosition = event->getLocation(); // calculates move offset in points float offsetInInch = 0; switch (_direction) @@ -1166,20 +1158,17 @@ void ScrollView::interceptTouchEvent(Widget::TouchEventType event, Widget* sende if (offsetInInch > _childFocusCancelOffsetInInch) { sender->setHighlighted(false); - handleMoveLogic(touch); + handleMoveLogic(event); } } break; - case TouchEventType::CANCELED: - case TouchEventType::ENDED: + case InputPhase::PointerCancel: + case InputPhase::PointerUp: { - _touchEndPosition = touch->getLocation(); - handleReleaseLogic(touch); - if (sender->isSwallowTouches()) - { - _isInterceptTouch = false; - } + _pointerUpPosition = event->getLocation(); + handleReleaseLogic(event); + _isInterceptTouch = false; } break; } @@ -1187,7 +1176,7 @@ void ScrollView::interceptTouchEvent(Widget::TouchEventType event, Widget* sende void ScrollView::processScrollEvent(MoveDirection dir, bool bounce) { - EventType eventType; + EventType eventType = EventType::SCROLLING; switch (dir) { case MoveDirection::TOP: @@ -1237,14 +1226,14 @@ void ScrollView::dispatchEvent(EventType eventType) { _eventCallback(this, eventType); } - if (_ccEventCallback) + if (_customEventCallback) { - _ccEventCallback(this, static_cast(eventType)); + _customEventCallback(this, static_cast(eventType)); } this->release(); } -void ScrollView::addEventListener(const ccScrollViewCallback& callback) +void ScrollView::addEventListener(const ScrollViewCallback& callback) { _eventCallback = callback; } @@ -1492,7 +1481,7 @@ float ScrollView::getTouchTotalTimeThreshold() const return _touchTotalTimeThreshold; } -Layout* ScrollView::getInnerContainer() const +LayoutGroup* ScrollView::getInnerContainer() const { return _innerContainer; } @@ -1502,7 +1491,7 @@ void ScrollView::setLayoutType(Type type) _innerContainer->setLayoutType(type); } -Layout::Type ScrollView::getLayoutType() const +LayoutGroup::Type ScrollView::getLayoutType() const { return _innerContainer->getLayoutType(); } @@ -1528,7 +1517,7 @@ Widget* ScrollView::createCloneInstance() void ScrollView::copyClonedWidgetChildren(Widget* model) { - Layout::copyClonedWidgetChildren(model); + LayoutGroup::copyClonedWidgetChildren(model); } void ScrollView::copySpecialProperties(Widget* widget) @@ -1536,7 +1525,7 @@ void ScrollView::copySpecialProperties(Widget* widget) ScrollView* scrollView = dynamic_cast(widget); if (scrollView) { - Layout::copySpecialProperties(widget); + LayoutGroup::copySpecialProperties(widget); setDirection(scrollView->_direction); setInnerContainerPosition(scrollView->getInnerContainerPosition()); setInnerContainerSize(scrollView->getInnerContainerSize()); @@ -1559,11 +1548,9 @@ void ScrollView::copySpecialProperties(Widget* widget) _autoScrollCurrentlyOutOfBoundary = scrollView->_autoScrollCurrentlyOutOfBoundary; _autoScrollBraking = scrollView->_autoScrollBraking; _autoScrollBrakingStartPosition = scrollView->_autoScrollBrakingStartPosition; + _eventCallback = scrollView->_eventCallback; setInertiaScrollEnabled(scrollView->_inertiaScrollEnabled); setBounceEnabled(scrollView->_bounceEnabled); - _scrollViewEventListener = scrollView->_scrollViewEventListener; - _eventCallback = scrollView->_eventCallback; - _ccEventCallback = scrollView->_ccEventCallback; setScrollBarEnabled(scrollView->isScrollBarEnabled()); if (isScrollBarEnabled()) @@ -1614,7 +1601,7 @@ void ScrollView::removeScrollBar() Widget* ScrollView::findNextFocusedWidget(ax::ui::Widget::FocusDirection direction, ax::ui::Widget* current) { - if (this->getLayoutType() == Layout::Type::VERTICAL || this->getLayoutType() == Layout::Type::HORIZONTAL) + if (this->getLayoutType() == LayoutGroup::Type::VERTICAL || this->getLayoutType() == LayoutGroup::Type::HORIZONTAL) { return _innerContainer->findNextFocusedWidget(direction, current); } diff --git a/axmol/ui/UIScrollView.h b/axmol/ui/ScrollView.h similarity index 92% rename from axmol/ui/UIScrollView.h rename to axmol/ui/ScrollView.h index 7f87f6c6afa8..8085b5c83ca0 100644 --- a/axmol/ui/UIScrollView.h +++ b/axmol/ui/ScrollView.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UILayout.h" +#include "axmol/ui/LayoutGroup.h" #include "axmol/ui/GUIExport.h" #include @@ -37,7 +37,7 @@ namespace ax * @{ */ -class EventFocusListener; +class FocusEventListener; namespace ui { @@ -45,10 +45,10 @@ namespace ui class ScrollViewBar; /** - * Layout container for a view hierarchy that can be scrolled by the user, allowing it to be larger than the physical - * display. It holds a inner `Layout` container for storing child items horizontally or vertically. + * LayoutGroup container for a view hierarchy that can be scrolled by the user, allowing it to be larger than the + * physical display. It holds a inner `LayoutGroup` container for storing child items horizontally or vertically. */ -class AX_GUI_DLL ScrollView : public Layout +class AX_GUI_DLL ScrollView : public LayoutGroup { DECLARE_CLASS_GUI_INFO @@ -66,7 +66,7 @@ class AX_GUI_DLL ScrollView : public Layout }; /** - * Scrollview scroll event type. + * ScrollView scroll event type. */ enum class EventType { @@ -84,11 +84,7 @@ class AX_GUI_DLL ScrollView : public Layout SCROLLING_ENDED, AUTOSCROLL_ENDED }; - - /** - * A callback which would be called when a ScrollView is scrolling. - */ - typedef std::function ccScrollViewCallback; + using ScrollViewCallback = std::function; /** * Default constructor @@ -132,7 +128,7 @@ class AX_GUI_DLL ScrollView : public Layout * * @return Inner container pointer. */ - Layout* getInnerContainer() const; + LayoutGroup* getInnerContainer() const; /** * Immediately stops inner container scroll (auto scrolling is not affected). @@ -333,10 +329,9 @@ class AX_GUI_DLL ScrollView : public Layout const Vec2& getInnerContainerPosition() const; /** - * Add callback function which will be called when scrollview event triggered. - * @param callback A callback function with type of `ccScrollViewCallback`. + * Add callback function which will be called when ScrollView event is triggered. */ - virtual void addEventListener(const ccScrollViewCallback& callback); + virtual void addEventListener(const ScrollViewCallback& callback); // override functions void addChild(Node* child) override; @@ -352,11 +347,11 @@ class AX_GUI_DLL ScrollView : public Layout Node* getChildByTag(int tag) const override; Node* getChildByName(std::string_view name) const override; // touch event callback - bool onTouchBegan(Touch* touch, Event* unusedEvent) override; - void onTouchMoved(Touch* touch, Event* unusedEvent) override; - void onTouchEnded(Touch* touch, Event* unusedEvent) override; - void onTouchCancelled(Touch* touch, Event* unusedEvent) override; - bool onMouseScroll(Event* event) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; + void onPointerUp(PointerEvent* event) override; + void onPointerCancel(PointerEvent* event) override; + bool onPointerScroll(PointerEvent* event) override; void update(float dt) override; /** @@ -614,7 +609,7 @@ class AX_GUI_DLL ScrollView : public Layout RIGHT, }; - void initRenderer() override; + void initRenderNode() override; void onSizeChanged() override; void doLayout() override; @@ -634,7 +629,7 @@ class AX_GUI_DLL ScrollView : public Layout virtual void moveInnerContainer(const Vec2& deltaMove, bool canStartBounceBack); - bool calculateCurrAndPrevTouchPoints(Touch* touch, Vec3* currPt, Vec3* prevPt); + bool calculateCurrAndPrevPoints(PointerEvent* touch, Vec3* currPt, Vec3* prevPt); void gatherTouchMove(const Vec2& delta); Vec2 calculateTouchMoveVelocity() const; @@ -652,11 +647,11 @@ class AX_GUI_DLL ScrollView : public Layout virtual void scrollChildren(const Vec2& deltaMove); - virtual void handlePressLogic(Touch* touch); - virtual void handleMoveLogic(Touch* touch); - virtual void handleReleaseLogic(Touch* touch); + virtual void handlePressLogic(PointerEvent* event); + virtual void handleMoveLogic(PointerEvent* event); + virtual void handleReleaseLogic(PointerEvent* event); - void interceptTouchEvent(Widget::TouchEventType event, Widget* sender, Touch* touch) override; + void interceptPointerEvent(Widget* sender, PointerEvent* event) override; void processScrollEvent(MoveDirection dir, bool bounce); void processScrollingEvent(); @@ -670,7 +665,7 @@ class AX_GUI_DLL ScrollView : public Layout protected: virtual float getAutoScrollStopEpsilon() const; bool fltEqualZero(const Vec2& point) const; - Layout* _innerContainer; + LayoutGroup* _innerContainer; Direction _direction; @@ -712,8 +707,7 @@ class AX_GUI_DLL ScrollView : public Layout ScrollViewBar* _verticalScrollBar; ScrollViewBar* _horizontalScrollBar; - Object* _scrollViewEventListener; - ccScrollViewCallback _eventCallback; + ScrollViewCallback _eventCallback; float _scrollTime; }; diff --git a/axmol/ui/UIScrollViewBar.cpp b/axmol/ui/ScrollViewBar.cpp similarity index 98% rename from axmol/ui/UIScrollViewBar.cpp rename to axmol/ui/ScrollViewBar.cpp index 1bfea4cd43e3..787911135b3c 100644 --- a/axmol/ui/UIScrollViewBar.cpp +++ b/axmol/ui/ScrollViewBar.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIScrollViewBar.h" +#include "axmol/ui/ScrollViewBar.h" #include "axmol/platform/Image.h" #include "axmol/2d/Sprite.h" #include "axmol/base/Utils.h" @@ -209,7 +209,7 @@ void ScrollViewBar::processAutoHide(float deltaTime) } } -void ScrollViewBar::onTouchBegan() +void ScrollViewBar::onPointerDown() { if (!_autoHideEnabled) { @@ -218,7 +218,7 @@ void ScrollViewBar::onTouchBegan() _touching = true; } -void ScrollViewBar::onTouchEnded() +void ScrollViewBar::onPointerUp() { if (!_autoHideEnabled) { @@ -242,7 +242,7 @@ void ScrollViewBar::onScrolled(const Vec2& outOfBoundary) ProtectedNode::setOpacity(_opacity); } - Layout* innerContainer = _parent->getInnerContainer(); + LayoutGroup* innerContainer = _parent->getInnerContainer(); float innerContainerMeasure = 0; float scrollViewMeasure = 0; diff --git a/axmol/ui/UIScrollViewBar.h b/axmol/ui/ScrollViewBar.h similarity index 98% rename from axmol/ui/UIScrollViewBar.h rename to axmol/ui/ScrollViewBar.h index 50bba9fc3c6a..02b696048ea8 100644 --- a/axmol/ui/UIScrollViewBar.h +++ b/axmol/ui/ScrollViewBar.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIScrollView.h" +#include "axmol/ui/ScrollView.h" namespace ax { @@ -139,12 +139,12 @@ class AX_GUI_DLL ScrollViewBar : public ProtectedNode /** * @brief This is called by parent ScrollView when a touch is began. Don't call this directly. */ - void onTouchBegan(); + void onPointerDown(); /** * @brief This is called by parent ScrollView when a touch is ended. Don't call this directly. */ - void onTouchEnded(); + void onPointerUp(); bool init() override; diff --git a/axmol/ui/UISlider.cpp b/axmol/ui/Slider.cpp similarity index 87% rename from axmol/ui/UISlider.cpp rename to axmol/ui/Slider.cpp index 566c6f8c632d..aa98ad6350ff 100644 --- a/axmol/ui/UISlider.cpp +++ b/axmol/ui/Slider.cpp @@ -24,8 +24,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UISlider.h" -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/Slider.h" +#include "axmol/ui/Scale9Sprite.h" #include "axmol/ui/UIHelper.h" #include "axmol/2d/Sprite.h" #include "axmol/scene/Camera.h" @@ -62,7 +62,7 @@ Slider::Slider() , _percent(0) , _maxPercent(100) , _scale9Enabled(false) - , _prevIgnoreSize(true) + , _prevAutoSize(true) , _zoomScale(0.1f) , _sliderBallNormalTextureScaleX(1.0) , _sliderBallNormalTextureScaleY(1.0) @@ -70,8 +70,6 @@ Slider::Slider() , _isSliderBallDisabledTexturedLoaded(false) , _capInsetsBarRenderer(Rect::ZERO) , _capInsetsProgressBarRenderer(Rect::ZERO) - , _sliderEventListener(nullptr) - , _eventCallback(nullptr) , _barTexType(TextureResType::LOCAL) , _progressBarTexType(TextureResType::LOCAL) , _ballNTexType(TextureResType::LOCAL) @@ -85,13 +83,10 @@ Slider::Slider() , _slidBallPressedTextureFile("") , _slidBallDisabledTextureFile("") { - setTouchEnabled(true); + setPointerEnabled(true); } -Slider::~Slider() -{ - _sliderEventListener = nullptr; -} +Slider::~Slider() {} Slider* Slider::create() { @@ -128,7 +123,7 @@ bool Slider::init() return false; } -void Slider::initRenderer() +void Slider::initRenderNode() { _barRenderer = Scale9Sprite::create(); _progressBarRenderer = Scale9Sprite::create(); @@ -179,7 +174,7 @@ void Slider::loadBarTexture(std::string_view fileName, TextureResType texType) } } // FIXME: https://github.com/cocos2d/cocos2d-x/issues/12249 - if (!_ignoreSize && _customSize.equals(Vec2::ZERO)) + if (!_autoSize && _customSize.equals(Vec2::ZERO)) { _customSize = _barRenderer->getContentSize(); } @@ -193,12 +188,14 @@ void Slider::loadBarTexture(SpriteFrame* spriteframe) void Slider::setupBarTexture() { - this->updateChildrenDisplayedRGBA(); + _barRendererAdaptDirty = true; _progressBarRendererDirty = true; - updateContentSizeWithTextureSize(_barRenderer->getContentSize()); - _barTextureSize = _barRenderer->getContentSize(); - _originalBarRect = _barRenderer->getTextureRect(); + _barTextureSize = _barRenderer->getContentSize(); + _originalBarRect = _barRenderer->getTextureRect(); + + updateChildrenDisplayedRGBA(); + updateContentSize(); } void Slider::loadProgressBarTexture(std::string_view fileName, TextureResType texType) @@ -254,13 +251,13 @@ void Slider::setScale9Enabled(bool able) if (_scale9Enabled) { - bool ignoreBefore = _ignoreSize; - ignoreContentAdaptWithSize(false); - _prevIgnoreSize = ignoreBefore; + bool autoSizeBefore = _autoSize; + setAutoSize(false); + _prevAutoSize = autoSizeBefore; } else { - ignoreContentAdaptWithSize(_prevIgnoreSize); + setAutoSize(_prevAutoSize); } setCapInsetsBarRenderer(_capInsetsBarRenderer); setCapInsetProgressBarRenderer(_capInsetsProgressBarRenderer); @@ -273,12 +270,14 @@ bool Slider::isScale9Enabled() const return _scale9Enabled; } -void Slider::ignoreContentAdaptWithSize(bool ignore) +void Slider::setAutoSize(bool autoSize) { - if (!_scale9Enabled || (_scale9Enabled && !ignore)) + // Note: autoSize=true means adapt to content, autoSize=false means fixed size + // For Scale9Sprite, we need special handling + if (!_scale9Enabled || (_scale9Enabled && !autoSize)) { - Widget::ignoreContentAdaptWithSize(ignore); - _prevIgnoreSize = ignore; + Widget::setAutoSize(autoSize); + _prevAutoSize = autoSize; // Store the current value for backward compatibility } } @@ -459,7 +458,7 @@ void Slider::setPercent(int percent) { _percent = percent; updateVisualSlider(); - percentChangedEvent(EventType::ON_PERCENTAGE_CHANGED); + dispatchEvent(EventType::ON_PERCENTAGE_CHANGED); } } @@ -478,7 +477,7 @@ void Slider::updateVisualSlider() _slidBallRenderer->setPosition(dis, _contentSize.height / 2.0f); if (_scale9Enabled) { - _progressBarRenderer->setPreferredSize(Vec2(dis, _contentSize.height)); + _progressBarRenderer->setContentSize(Vec2(dis, _contentSize.height)); } else { @@ -488,7 +487,7 @@ void Slider::updateVisualSlider() } } -bool Slider::hitTest(const ax::Vec2& pt, const Camera* camera, Vec3* /*p*/) const +bool Slider::hitTestSelf(const ax::Vec2& pt, const Camera* camera, Vec3* /*p*/) const { Rect rect; rect.size = _slidBallNormalRenderer->getContentSize(); @@ -497,70 +496,71 @@ bool Slider::hitTest(const ax::Vec2& pt, const Camera* camera, Vec3* /*p*/) cons Rect sliderBarRect; sliderBarRect.size = this->_barRenderer->getContentSize(); auto barW2l = this->_barRenderer->getWorldToNodeTransform(); - return isScreenPointInRect(pt, camera, w2l, rect, nullptr) || - isScreenPointInRect(pt, camera, barW2l, sliderBarRect, nullptr); + return camera->isWorldPointInRect(pt, w2l, rect) || camera->isWorldPointInRect(pt, barW2l, sliderBarRect); } -bool Slider::onTouchBegan(Touch* touch, Event* unusedEvent) +bool Slider::onPointerDown(PointerEvent* event) { - bool pass = Widget::onTouchBegan(touch, unusedEvent); + bool pass = Widget::onPointerDown(event); if (_hitted) { - setPercent(getPercentWithBallPos(_touchBeganPosition)); - percentChangedEvent(EventType::ON_SLIDEBALL_DOWN); + setPercent(getPercentWithBallPos(_pointerDownPosition)); + dispatchEvent(EventType::ON_SLIDEBALL_DOWN); } return pass; } -void Slider::onTouchMoved(Touch* touch, Event* /*unusedEvent*/) +void Slider::onPointerMove(PointerEvent* event) { - _touchMovePosition = touch->getLocation(); - setPercent(getPercentWithBallPos(_touchMovePosition)); + if (!_hitted) + return; + _pointerMovePosition = event->getLocation(); + setPercent(getPercentWithBallPos(_pointerMovePosition)); } -void Slider::onTouchEnded(Touch* touch, Event* unusedEvent) +void Slider::onPointerUp(PointerEvent* event) { - Widget::onTouchEnded(touch, unusedEvent); - percentChangedEvent(EventType::ON_SLIDEBALL_UP); + Widget::onPointerUp(event); + dispatchEvent(EventType::ON_SLIDEBALL_UP); } -void Slider::onTouchCancelled(Touch* touch, Event* unusedEvent) +void Slider::onPointerCancel(PointerEvent* event) { - Widget::onTouchCancelled(touch, unusedEvent); - percentChangedEvent(EventType::ON_SLIDEBALL_CANCEL); + Widget::onPointerCancel(event); + dispatchEvent(EventType::ON_SLIDEBALL_CANCEL); } float Slider::getPercentWithBallPos(const Vec2& pt) const { Vec3 p; - Widget::hitTest(pt, _hittedByCamera, &p); + Widget::hitTestSelf(pt, _hittedByCamera, &p); return ((p.x / _barLength) * static_cast(_maxPercent)); } -void Slider::addEventListener(const ccSliderCallback& callback) +int Slider::getPercent() const +{ + return _percent; +} + +void Slider::addEventListener(const SliderCallback& callback) { _eventCallback = callback; } -void Slider::percentChangedEvent(EventType event) +void Slider::dispatchEvent(EventType event) { this->retain(); if (_eventCallback) { _eventCallback(this, event); } - if (_ccEventCallback) + if (_customEventCallback) { - _ccEventCallback(this, static_cast(EventType::ON_PERCENTAGE_CHANGED)); + _customEventCallback(this, static_cast(event)); } this->release(); } -int Slider::getPercent() const -{ - return _percent; -} - void Slider::onSizeChanged() { Widget::onSizeChanged(); @@ -568,7 +568,7 @@ void Slider::onSizeChanged() _progressBarRendererDirty = true; } -void Slider::adaptRenderers() +void Slider::updateLayout() { if (_barRendererAdaptDirty) { @@ -582,24 +582,24 @@ void Slider::adaptRenderers() } } -Vec2 Slider::getVirtualRendererSize() const +Vec2 Slider::resolvePreferredSize(const Vec2& /*sizeHint*/) const { return _barRenderer->getContentSize(); } -Node* Slider::getVirtualRenderer() +Node* Slider::getRenderNode() { return _barRenderer; } void Slider::barRendererScaleChangedWithSize() { - if (_unifySize) + if (!_autoSize) { _barLength = _contentSize.width; - _barRenderer->setPreferredSize(_contentSize); + _barRenderer->setContentSize(_contentSize); } - else if (_ignoreSize) + else if (!_autoSize) // Previously isIgnoreContentAdaptWithSize() { _barRenderer->setScale(1.0f); @@ -610,7 +610,7 @@ void Slider::barRendererScaleChangedWithSize() _barLength = _contentSize.width; if (_scale9Enabled) { - _barRenderer->setPreferredSize(_contentSize); + _barRenderer->setContentSize(_contentSize); _barRenderer->setScale(1.0f); } else @@ -635,11 +635,11 @@ void Slider::barRendererScaleChangedWithSize() void Slider::progressBarRendererScaleChangedWithSize() { - if (_unifySize) + if (!_autoSize) { - _progressBarRenderer->setPreferredSize(_contentSize); + _progressBarRenderer->setContentSize(_contentSize); } - else if (_ignoreSize) + else if (!_autoSize) // Previously isIgnoreContentAdaptWithSize() { if (!_scale9Enabled) { @@ -654,7 +654,7 @@ void Slider::progressBarRendererScaleChangedWithSize() { if (_scale9Enabled) { - _progressBarRenderer->setPreferredSize(_contentSize); + _progressBarRenderer->setContentSize(_contentSize); _progressBarRenderer->setScale(1.0); } else @@ -745,7 +745,7 @@ void Slider::copySpecialProperties(Widget* widget) Slider* slider = dynamic_cast(widget); if (slider) { - _prevIgnoreSize = slider->_prevIgnoreSize; + _prevAutoSize = slider->_prevAutoSize; setScale9Enabled(slider->_scale9Enabled); // clone the inner sprite: https://github.com/cocos2d/cocos2d-x/issues/16928 @@ -761,9 +761,7 @@ void Slider::copySpecialProperties(Widget* widget) setMaxPercent(slider->getMaxPercent()); _isSliderBallPressedTextureLoaded = slider->_isSliderBallPressedTextureLoaded; _isSliderBallDisabledTexturedLoaded = slider->_isSliderBallDisabledTexturedLoaded; - _sliderEventListener = slider->_sliderEventListener; _eventCallback = slider->_eventCallback; - _ccEventCallback = slider->_ccEventCallback; } } diff --git a/axmol/ui/UISlider.h b/axmol/ui/Slider.h similarity index 88% rename from axmol/ui/UISlider.h rename to axmol/ui/Slider.h index dde7431a5a25..da28dace37d0 100644 --- a/axmol/ui/UISlider.h +++ b/axmol/ui/Slider.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" namespace ax @@ -66,12 +66,12 @@ class AX_GUI_DLL Slider : public Widget enum class EventType { ON_PERCENTAGE_CHANGED, - //@since v3.7 ON_SLIDEBALL_DOWN, ON_SLIDEBALL_UP, ON_SLIDEBALL_CANCEL }; - typedef std::function ccSliderCallback; + using SliderCallback = std::function; + /** * Default constructor. * @lua new @@ -229,28 +229,26 @@ class AX_GUI_DLL Slider : public Widget int getMaxPercent() const; /** - * Add call back function called when slider's percent has changed to slider. - * - * @param callback An given call back function called when slider's percent has changed to slider. + * Add a callback function which would be called when Slider event occurs. */ - void addEventListener(const ccSliderCallback& callback); + void addEventListener(const SliderCallback& callback); - bool onTouchBegan(Touch* touch, Event* unusedEvent) override; - void onTouchMoved(Touch* touch, Event* unusedEvent) override; - void onTouchEnded(Touch* touch, Event* unusedEvent) override; - void onTouchCancelled(Touch* touch, Event* unusedEvent) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; + void onPointerUp(PointerEvent* event) override; + void onPointerCancel(PointerEvent* event) override; - // override "getVirtualRendererSize" method of widget. - Vec2 getVirtualRendererSize() const override; + // override "resolvePreferredSize" method of widget. + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; - // override "getVirtualRenderer" method of widget. - Node* getVirtualRenderer() override; + // override "getRenderNode" method of widget. + Node* getRenderNode() override; - // override "ignoreContentAdaptWithSize" method of widget. - void ignoreContentAdaptWithSize(bool ignore) override; + // override "setAutoSize" method of widget. + void setAutoSize(bool autoSize) override; - // override the widget's hitTest function to perform its own - bool hitTest(const Vec2& pt, const Camera* camera, Vec3* p) const override; + // override the widget's hitTestSelf function to perform its own + bool hitTestSelf(const Vec2& pt, const Camera* camera, Vec3* p) const override; /** * Returns the "class name" of widget. */ @@ -281,9 +279,9 @@ class AX_GUI_DLL Slider : public Widget bool init() override; protected: - void initRenderer() override; + void initRenderNode() override; float getPercentWithBallPos(const Vec2& pt) const; - void percentChangedEvent(EventType event); + void dispatchEvent(EventType event); void onPressStateChangedToNormal() override; void onPressStateChangedToPressed() override; void onPressStateChangedToDisabled() override; @@ -301,7 +299,7 @@ class AX_GUI_DLL Slider : public Widget void progressBarRendererScaleChangedWithSize(); Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; - void adaptRenderers() override; + void updateLayout() override; protected: Scale9Sprite* _barRenderer; @@ -321,7 +319,7 @@ class AX_GUI_DLL Slider : public Widget int _maxPercent; bool _scale9Enabled; - bool _prevIgnoreSize; + bool _prevAutoSize; float _zoomScale; float _sliderBallNormalTextureScaleX; @@ -333,10 +331,6 @@ class AX_GUI_DLL Slider : public Widget Rect _capInsetsBarRenderer; Rect _capInsetsProgressBarRenderer; - Object* _sliderEventListener; - - ccSliderCallback _eventCallback; - TextureResType _barTexType; TextureResType _progressBarTexType; TextureResType _ballNTexType; @@ -345,6 +339,8 @@ class AX_GUI_DLL Slider : public Widget bool _barRendererAdaptDirty; bool _progressBarRendererDirty; + SliderCallback _eventCallback; + std::string _textureFile; std::string _progressBarTextureFile; std::string _slidBallNormalTextureFile; diff --git a/axmol/ui/UITabControl.cpp b/axmol/ui/TabView.cpp similarity index 87% rename from axmol/ui/UITabControl.cpp rename to axmol/ui/TabView.cpp index 7db8438a91b4..458821cd0fa9 100644 --- a/axmol/ui/UITabControl.cpp +++ b/axmol/ui/TabView.cpp @@ -27,8 +27,8 @@ #include "axmol/platform/FileUtils.h" #include "axmol/2d/Sprite.h" #include "axmol/2d/Label.h" -#include "axmol/ui/UILayout.h" -#include "axmol/ui/UITabControl.h" +#include "axmol/ui/LayoutGroup.h" +#include "axmol/ui/TabView.h" namespace ax { @@ -36,7 +36,7 @@ namespace ax namespace ui { -TabControl::TabControl() +TabView::TabView() : _selectedItem(nullptr) , _headerHeight(20) , _headerWidth(50) @@ -50,7 +50,7 @@ TabControl::TabControl() setContentSize(Vec2(200, 200)); } -TabControl::~TabControl() +TabView::~TabView() { for (auto&& item : _tabItems) { @@ -60,7 +60,7 @@ TabControl::~TabControl() _tabItems.clear(); } -void TabControl::insertTab(int index, TabHeader* header, Layout* container) +void TabView::insertTab(int index, TabHeader* header, LayoutGroup* container) { int cellSize = (int)_tabItems.size(); if (index > cellSize) @@ -73,14 +73,15 @@ void TabControl::insertTab(int index, TabHeader* header, Layout* container) addProtectedChild(header, -2, -1); _tabItems.insert(_tabItems.begin() + index, new TabItem(header, container)); - header->_tabView = this; - header->_tabSelectedEvent = - AX_CALLBACK_2(TabControl::dispatchSelectedTabChanged, this); // binding tab selected event + header->_tabView = this; + header->_tabSelectedEvent = [this](int tabIndex, TabHeader::EventType eventType) { + dispatchSelectedTabChanged(tabIndex, eventType); + }; initAfterInsert(index); } -void TabControl::initAfterInsert(int index) +void TabView::initAfterInsert(int index) { auto cellSize = _tabItems.size(); auto tabItem = _tabItems.at(index); @@ -98,9 +99,9 @@ void TabControl::initAfterInsert(int index) headerCell->setContentSize(Vec2(_headerWidth, _headerHeight)); headerCell->setAnchorPoint(getHeaderAnchorWithDock()); - if (headerCell->isIgnoreContentAdaptWithSize() == _ignoreHeaderTextureSize) + if (headerCell->_autoSize == !_ignoreHeaderTextureSize) { - headerCell->ignoreContentAdaptWithSize(!_ignoreHeaderTextureSize); + headerCell->setAutoSize(_ignoreHeaderTextureSize); if (_ignoreHeaderTextureSize) headerCell->setContentSize(Vec2(_headerWidth, _headerHeight)); headerCell->backGroundDisabledTextureScaleChangedWithSize(); @@ -120,7 +121,7 @@ void TabControl::initAfterInsert(int index) } } -void TabControl::removeTab(int index) +void TabView::removeTab(int index) { int cellSize = (int)_tabItems.size(); if (cellSize == 0 || index >= cellSize) @@ -149,12 +150,12 @@ void TabControl::removeTab(int index) initTabHeadersPos(index); } -size_t TabControl::getTabCount() const +size_t TabView::getTabCount() const { return _tabItems.size(); } -void TabControl::setHeaderWidth(float headerWidth) +void TabView::setHeaderWidth(float headerWidth) { _headerWidth = headerWidth; if (_headerDockPlace == Dock::TOP || _headerDockPlace == Dock::BOTTOM) @@ -163,7 +164,7 @@ void TabControl::setHeaderWidth(float headerWidth) initContainers(); } -void TabControl::setHeaderHeight(float headerHeight) +void TabView::setHeaderHeight(float headerHeight) { _headerHeight = headerHeight; if (_headerDockPlace == Dock::LEFT || _headerDockPlace == Dock::RIGHT) @@ -172,7 +173,7 @@ void TabControl::setHeaderHeight(float headerHeight) initContainers(); } -void TabControl::setHeaderDockPlace(TabControl::Dock dockPlace) +void TabView::setHeaderDockPlace(TabView::Dock dockPlace) { if (_headerDockPlace != dockPlace) { @@ -188,7 +189,7 @@ void TabControl::setHeaderDockPlace(TabControl::Dock dockPlace) } } -ax::Vec2 TabControl::getHeaderAnchorWithDock() const +ax::Vec2 TabView::getHeaderAnchorWithDock() const { Vec2 anpoint(.5f, .0f); switch (_headerDockPlace) @@ -213,13 +214,13 @@ ax::Vec2 TabControl::getHeaderAnchorWithDock() const return anpoint; } -void TabControl::onSizeChanged() +void TabView::onSizeChanged() { initTabHeadersPos(0); initContainers(); } -void TabControl::initTabHeadersPos(int startIndex) +void TabView::initTabHeadersPos(int startIndex) { int cellSize = (int)_tabItems.size(); if (startIndex >= cellSize) @@ -258,7 +259,7 @@ void TabControl::initTabHeadersPos(int startIndex) } } -void TabControl::initContainers() +void TabView::initContainers() { switch (_headerDockPlace) { @@ -284,13 +285,13 @@ void TabControl::initContainers() for (auto&& tabItem : _tabItems) { - Layout* container = tabItem->container; + LayoutGroup* container = tabItem->container; container->setPosition(_containerPosition); container->setContentSize(_containerSize); } } -TabHeader* TabControl::getTabHeader(int index) const +TabHeader* TabView::getTabHeader(int index) const { if (index >= (int)getTabCount()) return nullptr; @@ -298,14 +299,14 @@ TabHeader* TabControl::getTabHeader(int index) const return _tabItems.at(index)->header; } -Layout* TabControl::getTabContainer(int index) const +LayoutGroup* TabView::getTabContainer(int index) const { if (index >= (int)getTabCount()) return nullptr; return _tabItems.at(index)->container; } -void TabControl::dispatchSelectedTabChanged(int tabIndex, TabHeader::EventType eventType) +void TabView::dispatchSelectedTabChanged(int tabIndex, TabHeader::EventType eventType) { if (eventType == TabHeader::EventType::SELECTED) { @@ -340,14 +341,18 @@ void TabControl::dispatchSelectedTabChanged(int tabIndex, TabHeader::EventType e int currentIndex = getSelectedTabIndex(); _tabChangedCallback(currentIndex, EventType::SELECT_CHANGED); } + if (_customEventCallback != nullptr) + { + _customEventCallback(this, static_cast(EventType::SELECT_CHANGED)); + } } -void TabControl::setTabChangedEventListener(const ccTabControlCallback& callback) +void TabView::setTabChangedEventListener(const TabViewCallback& callback) { _tabChangedCallback = callback; } -int TabControl::indexOfTabHeader(const TabHeader* tabCell) const +int TabView::indexOfTabHeader(const TabHeader* tabCell) const { int n = (int)_tabItems.size(); for (auto i = 0; i < n; i++) @@ -360,9 +365,9 @@ int TabControl::indexOfTabHeader(const TabHeader* tabCell) const return -1; } -TabControl* TabControl::create() +TabView* TabView::create() { - TabControl* tabview = new TabControl(); + TabView* tabview = new TabView(); if (tabview->init()) { tabview->autorelease(); @@ -372,12 +377,12 @@ TabControl* TabControl::create() return nullptr; } -void TabControl::setSelectTab(int index) +void TabView::setSelectTab(int index) { dispatchSelectedTabChanged(index, TabHeader::EventType::SELECTED); } -void TabControl::setSelectTab(TabHeader* tabHeader) +void TabView::setSelectTab(TabHeader* tabHeader) { if (_selectedItem != nullptr && tabHeader == _selectedItem->header) return; @@ -385,7 +390,7 @@ void TabControl::setSelectTab(TabHeader* tabHeader) setSelectTab(indexOfTabHeader(tabHeader)); } -void TabControl::setHeaderSelectedZoom(float zoom) +void TabView::setHeaderSelectedZoom(float zoom) { if (_currentHeaderZoom != zoom) { @@ -398,7 +403,7 @@ void TabControl::setHeaderSelectedZoom(float zoom) } } -void TabControl::activeTabItem(TabItem* item) +void TabView::activeTabItem(TabItem* item) { if (item != nullptr) { @@ -410,7 +415,7 @@ void TabControl::activeTabItem(TabItem* item) } } -void TabControl::deactiveTabItem(TabItem* item) +void TabView::deactiveTabItem(TabItem* item) { if (item != nullptr) { @@ -422,9 +427,9 @@ void TabControl::deactiveTabItem(TabItem* item) } } -void TabControl::copySpecialProperties(Widget* model) +void TabView::copySpecialProperties(Widget* model) { - auto srcTab = dynamic_cast(model); + auto srcTab = dynamic_cast(model); if (srcTab != nullptr) { Widget::copySpecialProperties(srcTab); @@ -436,7 +441,7 @@ void TabControl::copySpecialProperties(Widget* model) } } -void TabControl::ignoreHeadersTextureSize(bool ignore) +void TabView::ignoreHeadersTextureSize(bool ignore) { if (_ignoreHeaderTextureSize == ignore) return; @@ -444,7 +449,7 @@ void TabControl::ignoreHeadersTextureSize(bool ignore) _ignoreHeaderTextureSize = ignore; for (auto&& item : _tabItems) { - item->header->ignoreContentAdaptWithSize(!ignore); + item->header->setAutoSize(ignore); if (ignore) item->header->setContentSize(Vec2(_headerWidth, _headerHeight)); item->header->backGroundDisabledTextureScaleChangedWithSize(); @@ -455,24 +460,18 @@ void TabControl::ignoreHeadersTextureSize(bool ignore) } } -int TabControl::getSelectedTabIndex() const +int TabView::getSelectedTabIndex() const { return _selectedItem == nullptr ? -1 : indexOfTabHeader(_selectedItem->header); } -TabHeader::TabHeader() - : _tabLabelRender(nullptr) - , _tabLabelFontSize(12) - , _tabView(nullptr) - , _tabSelectedEvent(nullptr) - , _fontType(FontType::SYSTEM) +TabHeader::TabHeader() : _tabLabelRender(nullptr), _tabLabelFontSize(12), _tabView(nullptr), _fontType(FontType::SYSTEM) {} TabHeader::~TabHeader() { - _tabLabelRender = nullptr; - _tabView = nullptr; - _tabSelectedEvent = nullptr; + _tabLabelRender = nullptr; + _tabView = nullptr; } TabHeader* TabHeader::create() @@ -528,7 +527,7 @@ TabHeader* TabHeader::create(std::string_view titleStr, return nullptr; } -void TabHeader::initRenderer() +void TabHeader::initRenderNode() { _backGroundBoxRenderer = Sprite::create(); _backGroundSelectedBoxRenderer = Sprite::create(); @@ -688,8 +687,7 @@ void TabHeader::dispatchSelectChangedEvent(bool select) if (_tabView == nullptr) return; - EventType eventType = (select ? EventType::SELECTED : EventType::UNSELECTED); - + EventType eventType = select ? EventType::SELECTED : EventType::UNSELECTED; if (_tabSelectedEvent != nullptr) { int index = _tabView->indexOfTabHeader(this); @@ -697,9 +695,9 @@ void TabHeader::dispatchSelectChangedEvent(bool select) _tabSelectedEvent(index, eventType); } - if (_ccEventCallback != nullptr) + if (_customEventCallback != nullptr) { - _ccEventCallback(this, static_cast(eventType)); + _customEventCallback(this, static_cast(eventType)); } } diff --git a/axmol/ui/UITabControl.h b/axmol/ui/TabView.h similarity index 85% rename from axmol/ui/UITabControl.h rename to axmol/ui/TabView.h index 810f3c6404cc..2f5f8304101d 100644 --- a/axmol/ui/UITabControl.h +++ b/axmol/ui/TabView.h @@ -26,8 +26,8 @@ #pragma once -#include "axmol/ui/UIAbstractCheckButton.h" -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/AbstractCheckButton.h" +#include "axmol/ui/Widget.h" /** * @addtogroup ui @@ -41,14 +41,14 @@ class Label; namespace ui { -class Layout; -class TabControl; +class LayoutGroup; +class TabView; /** - * the header button in TabControl + * the header button in TabView */ class AX_GUI_DLL TabHeader : public AbstractCheckButton { - friend class TabControl; + friend class TabView; public: enum class EventType @@ -150,8 +150,8 @@ class AX_GUI_DLL TabHeader : public AbstractCheckButton std::string_view getTitleFontName() const; /** - * get the index this header in the TabControl - * @return -1 means not in any TabControl + * get the index this header in the TabView + * @return -1 means not in any TabView */ int getIndexInTabControl() const; @@ -159,10 +159,10 @@ class AX_GUI_DLL TabHeader : public AbstractCheckButton TabHeader(); ~TabHeader(); - void initRenderer() override; + void initRenderNode() override; void onSizeChanged() override; - void updateContentSize(); + void updateContentSize() override; void releaseUpEvent() override; void dispatchSelectChangedEvent(bool select) override; @@ -172,24 +172,18 @@ class AX_GUI_DLL TabHeader : public AbstractCheckButton private: Label* _tabLabelRender; float _tabLabelFontSize; - TabControl* _tabView; + TabView* _tabView; - typedef std::function ccTabCallback; - ccTabCallback _tabSelectedEvent; + using TabCallback = std::function; + TabCallback _tabSelectedEvent; - enum class FontType - { - SYSTEM, - TTF, - BMFONT - }; FontType _fontType; }; /** - * TabControl, use header button switch container + * TabView, use header button switch container */ -class AX_GUI_DLL TabControl : public Widget +class AX_GUI_DLL TabView : public Widget { public: enum class Dock @@ -204,16 +198,15 @@ class AX_GUI_DLL TabControl : public Widget { SELECT_CHANGED, }; + using TabViewCallback = std::function; - typedef std::function ccTabControlCallback; - - static TabControl* create(); + static TabView* create(); /// @{ /// @name behaviours /** - * remove the tab from this TabControl + * remove the tab from this TabView * @param index The index of tab */ void removeTab(int index); @@ -240,18 +233,18 @@ class AX_GUI_DLL TabControl : public Widget * get Container * @param index The index of tab */ - Layout* getTabContainer(int index) const; + LayoutGroup* getTabContainer(int index) const; /** * insert tab, and init the position of header and container * @param index The index tab should be - * @param header The header Button, will be a protected child in TabControl - * @param container The container, will be a protected child in TabControl + * @param header The header Button, will be a protected child in TabView + * @param container The container, will be a protected child in TabView */ - void insertTab(int index, TabHeader* header, Layout* container); + void insertTab(int index, TabHeader* header, LayoutGroup* container); /** - * get the count of tabs in this TabControl + * get the count of tabs in this TabView * @return the count of tabs */ size_t getTabCount() const; @@ -269,10 +262,10 @@ class AX_GUI_DLL TabControl : public Widget int indexOfTabHeader(const TabHeader* tabCell) const; /** - * Add a callback function which would be called when selected tab changed - *@param callback A std::function with type @see `ccTabControlCallback` + * Add a callback function which would be called when selected tab changes. */ - void setTabChangedEventListener(const ccTabControlCallback& callback); + void setTabChangedEventListener(const TabViewCallback& callback); + /// @} /// @{ @@ -328,23 +321,22 @@ class AX_GUI_DLL TabControl : public Widget float getHeaderSelectedZoom() const { return _currentHeaderZoom; } /** - * the header dock place of header in TabControl + * the header dock place of header in TabView * @param dockPlace The strip place */ - void setHeaderDockPlace(TabControl::Dock dockPlace); - TabControl::Dock getHeaderDockPlace() const { return _headerDockPlace; } + void setHeaderDockPlace(TabView::Dock dockPlace); + TabView::Dock getHeaderDockPlace() const { return _headerDockPlace; } /// @} protected: - TabControl(); - ~TabControl(); + TabView(); + ~TabView(); void onSizeChanged() override; void initTabHeadersPos(int startIndex); void initContainers(); void copySpecialProperties(Widget* model) override; - ccTabControlCallback _tabChangedCallback; // dispatch selected changed void dispatchSelectedTabChanged(int tabIndex, TabHeader::EventType eventType); @@ -352,9 +344,9 @@ class AX_GUI_DLL TabControl : public Widget typedef struct CellContainer { TabHeader* header; - Layout* container; + LayoutGroup* container; - CellContainer(TabHeader* headerCell, Layout* layout) + CellContainer(TabHeader* headerCell, LayoutGroup* layout) { header = headerCell; container = layout; @@ -376,10 +368,14 @@ class AX_GUI_DLL TabControl : public Widget Vec2 _containerSize; float _currentHeaderZoom; bool _ignoreHeaderTextureSize; + TabViewCallback _tabChangedCallback; // for index the cells and containers std::vector _tabItems; }; + +using TabControl = TabView; + } // namespace ui // end group /// @} diff --git a/axmol/ui/UIText.cpp b/axmol/ui/Text.cpp similarity index 92% rename from axmol/ui/UIText.cpp rename to axmol/ui/Text.cpp index fb352a30fbf6..def9b1002c8b 100644 --- a/axmol/ui/UIText.cpp +++ b/axmol/ui/Text.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIText.h" +#include "axmol/ui/Text.h" #include "axmol/2d/Label.h" #include "axmol/platform/FileUtils.h" @@ -102,7 +102,7 @@ bool Text::init(std::string_view textContent, std::string_view fontName, float f return ret; } -void Text::initRenderer() +void Text::initRenderNode() { _labelRenderer = Label::create(); addProtectedChild(_labelRenderer, LABEL_RENDERER_Z, -1); @@ -115,7 +115,7 @@ void Text::setString(std::string_view text) return; } _labelRenderer->setString(text); - updateContentSizeWithTextureSize(_labelRenderer->getContentSize()); + updateContentSize(); _labelRendererAdaptDirty = true; } @@ -124,9 +124,9 @@ std::string_view Text::getString() const return _labelRenderer->getString(); } -ssize_t Text::getStringLength() const +ssize_t Text::getCharCount() const { - return _labelRenderer->getStringLength(); + return _labelRenderer->getCharCount(); } void Text::setFontSize(float size) @@ -142,7 +142,7 @@ void Text::setFontSize(float size) _labelRenderer->setTTFConfig(config); } _fontSize = size; - updateContentSizeWithTextureSize(_labelRenderer->getContentSize()); + updateContentSize(); _labelRendererAdaptDirty = true; } @@ -171,7 +171,7 @@ void Text::setFontName(std::string_view name) _type = Type::SYSTEM; } _fontName = name; - updateContentSizeWithTextureSize(_labelRenderer->getContentSize()); + updateContentSize(); _labelRendererAdaptDirty = true; } @@ -188,11 +188,11 @@ Text::Type Text::getType() const void Text::setTextAreaSize(const Vec2& size) { _labelRenderer->setDimensions(size.width, size.height); - if (!_ignoreSize) + if (!_autoSize) { _customSize = size; } - updateContentSizeWithTextureSize(_labelRenderer->getContentSize()); + updateContentSize(); _labelRendererAdaptDirty = true; } @@ -269,7 +269,7 @@ void Text::onSizeChanged() _labelRendererAdaptDirty = true; } -void Text::adaptRenderers() +void Text::updateLayout() { if (_labelRendererAdaptDirty) { @@ -278,7 +278,7 @@ void Text::adaptRenderers() } } -Vec2 Text::getVirtualRendererSize() const +Vec2 Text::resolvePreferredSize(const Vec2& /*sizeHint*/) const { return _labelRenderer->getContentSize(); } @@ -286,7 +286,7 @@ Vec2 Text::getVirtualRendererSize() const Vec2 Text::getAutoRenderSize() { Vec2 virtualSize = _labelRenderer->getContentSize(); - if (!_ignoreSize) + if (!_autoSize) { _labelRenderer->setDimensions(0, 0); virtualSize = _labelRenderer->getContentSize(); @@ -296,14 +296,14 @@ Vec2 Text::getAutoRenderSize() return virtualSize; } -Node* Text::getVirtualRenderer() +Node* Text::getRenderNode() { return _labelRenderer; } void Text::labelScaleChangedWithSize() { - if (_ignoreSize) + if (_autoSize) { _labelRenderer->setScale(1.0f); _normalScaleValueX = _normalScaleValueY = 1.0f; @@ -340,7 +340,7 @@ void Text::enableShadow(const Color32& shadowColor, const Vec2& offset, int blur void Text::enableOutline(const Color32& outlineColor, int outlineSize) { _labelRenderer->enableOutline(outlineColor, outlineSize); - updateContentSizeWithTextureSize(_labelRenderer->getContentSize()); + updateContentSize(); _labelRendererAdaptDirty = true; } @@ -353,7 +353,7 @@ void Text::enableGlow(const Color32& glowColor) void Text::disableEffect() { _labelRenderer->disableEffect(); - updateContentSizeWithTextureSize(_labelRenderer->getContentSize()); + updateContentSize(); _labelRendererAdaptDirty = true; } @@ -363,7 +363,7 @@ void Text::disableEffect(LabelEffect effect) // only outline effect will affect the content size of label if (LabelEffect::OUTLINE == effect) { - updateContentSizeWithTextureSize(_labelRenderer->getContentSize()); + updateContentSize(); _labelRendererAdaptDirty = true; } } diff --git a/axmol/ui/UIText.h b/axmol/ui/Text.h similarity index 95% rename from axmol/ui/UIText.h rename to axmol/ui/Text.h index a8d034a96a67..cf8ab585c09c 100644 --- a/axmol/ui/UIText.h +++ b/axmol/ui/Text.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" #include "axmol/base/Types.h" @@ -112,14 +112,14 @@ class AX_GUI_DLL Text : public Widget, public ax::BlendProtocol std::string_view getString() const; /** - * Gets the string length of the label. - * Note: This length will be larger than the raw string length, + * Gets the UTF-8 character count of the label. + * Note: This count will be larger than the raw string length, * if you want to get the raw string length, * you should call this->getString().size() instead. * - * @return String length. + * @return UTF-8 character count. */ - ssize_t getStringLength() const; + ssize_t getCharCount() const; /** * Sets the font size of label. @@ -177,11 +177,11 @@ class AX_GUI_DLL Text : public Widget, public ax::BlendProtocol */ bool isTouchScaleChangeEnabled() const; - // override "getVirtualRendererSize" method of widget. - Vec2 getVirtualRendererSize() const override; + // override "resolvePreferredSize" method of widget. + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; - // override "getVirtualRenderer" method of widget. - Node* getVirtualRenderer() override; + // override "getRenderNode" method of widget. + Node* getRenderNode() override; /** Gets the render size in auto mode. * @@ -342,7 +342,7 @@ class AX_GUI_DLL Text : public Widget, public ax::BlendProtocol virtual bool init(std::string_view textContent, std::string_view fontName, float fontSize); protected: - void initRenderer() override; + void initRenderNode() override; void onPressStateChangedToNormal() override; void onPressStateChangedToPressed() override; void onPressStateChangedToDisabled() override; @@ -351,7 +351,7 @@ class AX_GUI_DLL Text : public Widget, public ax::BlendProtocol void labelScaleChangedWithSize(); Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; - void adaptRenderers() override; + void updateLayout() override; protected: bool _touchScaleChangeEnabled; diff --git a/axmol/ui/UITextAtlas.cpp b/axmol/ui/TextAtlas.cpp similarity index 92% rename from axmol/ui/UITextAtlas.cpp rename to axmol/ui/TextAtlas.cpp index 54a1890c7630..4d8f5adf3f62 100644 --- a/axmol/ui/UITextAtlas.cpp +++ b/axmol/ui/TextAtlas.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UITextAtlas.h" +#include "axmol/ui/TextAtlas.h" #include "axmol/2d/Label.h" namespace ax @@ -61,7 +61,7 @@ TextAtlas* TextAtlas::create() return nullptr; } -void TextAtlas::initRenderer() +void TextAtlas::initRenderNode() { _labelAtlasRenderer = Label::create(); _labelAtlasRenderer->setAnchorPoint(Point::ANCHOR_MIDDLE); @@ -100,7 +100,7 @@ void TextAtlas::setProperty(std::string_view stringValue, _labelAtlasRenderer->setCharMap(_charMapFileName, _itemWidth, _itemHeight, (int)(_startCharMap[0])); _labelAtlasRenderer->setString(stringValue); - updateContentSizeWithTextureSize(_labelAtlasRenderer->getContentSize()); + updateContentSize(); _labelAtlasRendererAdaptDirty = true; // AXLOGD("cs w {}, h {}", _contentSize.width, _contentSize.height); } @@ -113,7 +113,7 @@ void TextAtlas::setString(std::string_view value) } _stringValue = value; _labelAtlasRenderer->setString(value); - updateContentSizeWithTextureSize(_labelAtlasRenderer->getContentSize()); + updateContentSize(); _labelAtlasRendererAdaptDirty = true; // AXLOGD("cssss w {}, h {}", _contentSize.width, _contentSize.height); } @@ -123,9 +123,9 @@ std::string_view TextAtlas::getString() const return _labelAtlasRenderer->getString(); } -ssize_t TextAtlas::getStringLength() const +ssize_t TextAtlas::getCharCount() const { - return _labelAtlasRenderer->getStringLength(); + return _labelAtlasRenderer->getCharCount(); } void TextAtlas::onSizeChanged() @@ -134,7 +134,7 @@ void TextAtlas::onSizeChanged() _labelAtlasRendererAdaptDirty = true; } -void TextAtlas::adaptRenderers() +void TextAtlas::updateLayout() { if (_labelAtlasRendererAdaptDirty) { @@ -143,19 +143,19 @@ void TextAtlas::adaptRenderers() } } -Vec2 TextAtlas::getVirtualRendererSize() const +Vec2 TextAtlas::resolvePreferredSize(const Vec2& /*sizeHint*/) const { return _labelAtlasRenderer->getContentSize(); } -Node* TextAtlas::getVirtualRenderer() +Node* TextAtlas::getRenderNode() { return _labelAtlasRenderer; } void TextAtlas::labelAtlasScaleChangedWithSize() { - if (_ignoreSize) + if (_autoSize) { _labelAtlasRenderer->setScale(1.0f); } diff --git a/axmol/ui/UITextAtlas.h b/axmol/ui/TextAtlas.h similarity index 89% rename from axmol/ui/UITextAtlas.h rename to axmol/ui/TextAtlas.h index 09daed6270a6..88a435c70737 100644 --- a/axmol/ui/UITextAtlas.h +++ b/axmol/ui/TextAtlas.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" namespace ax @@ -118,19 +118,19 @@ class AX_GUI_DLL TextAtlas : public Widget std::string_view getString() const; /** - * Gets the string length of the label. - * Note: This length will be larger than the raw string length, + * Gets the UTF-8 character count of the label. + * Note: This count will be larger than the raw string length, * if you want to get the raw string length, you should call this->getString().size() instead * - * @return string length. + * @return UTF-8 character count. */ - ssize_t getStringLength() const; + ssize_t getCharCount() const; - // override "getVirtualRendererSize" method of widget. - Vec2 getVirtualRendererSize() const override; + // override "resolvePreferredSize" method of widget. + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; - // override "getVirtualRenderer" method of widget. - Node* getVirtualRenderer() override; + // override "getRenderNode" method of widget. + Node* getRenderNode() override; /** * Returns the "class name" of widget. @@ -139,12 +139,12 @@ class AX_GUI_DLL TextAtlas : public Widget /** */ - void adaptRenderers() override; + void updateLayout() override; ResourceData getRenderFile(); protected: - void initRenderer() override; + void initRenderNode() override; void onSizeChanged() override; void labelAtlasScaleChangedWithSize(); diff --git a/axmol/ui/UITextBMFont.cpp b/axmol/ui/TextBMFont.cpp similarity index 90% rename from axmol/ui/UITextBMFont.cpp rename to axmol/ui/TextBMFont.cpp index e14b7e80e12f..f7b81541dbd6 100644 --- a/axmol/ui/UITextBMFont.cpp +++ b/axmol/ui/TextBMFont.cpp @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UITextBMFont.h" +#include "axmol/ui/TextBMFont.h" #include "axmol/2d/Label.h" namespace ax @@ -69,7 +69,7 @@ TextBMFont* TextBMFont::create(std::string_view text, std::string_view filename) return nullptr; } -void TextBMFont::initRenderer() +void TextBMFont::initRenderNode() { _labelBMFontRenderer = ax::Label::create(); addProtectedChild(_labelBMFontRenderer, LABELBMFONT_RENDERER_Z, -1); @@ -84,7 +84,7 @@ void TextBMFont::setFntFile(std::string_view fileName) _fntFileName = fileName; _labelBMFontRenderer->setBMFontFilePath(fileName); - updateContentSizeWithTextureSize(_labelBMFontRenderer->getContentSize()); + updateContentSize(); _labelBMFontRendererAdaptDirty = true; } @@ -96,7 +96,7 @@ void TextBMFont::setString(std::string_view value) } _stringValue = value; _labelBMFontRenderer->setString(value); - updateContentSizeWithTextureSize(_labelBMFontRenderer->getContentSize()); + updateContentSize(); _labelBMFontRendererAdaptDirty = true; } @@ -105,9 +105,9 @@ std::string_view TextBMFont::getString() const return _stringValue; } -ssize_t TextBMFont::getStringLength() const +ssize_t TextBMFont::getCharCount() const { - return _labelBMFontRenderer->getStringLength(); + return _labelBMFontRenderer->getCharCount(); } void TextBMFont::onSizeChanged() @@ -116,7 +116,7 @@ void TextBMFont::onSizeChanged() _labelBMFontRendererAdaptDirty = true; } -void TextBMFont::adaptRenderers() +void TextBMFont::updateLayout() { if (_labelBMFontRendererAdaptDirty) { @@ -125,19 +125,19 @@ void TextBMFont::adaptRenderers() } } -Vec2 TextBMFont::getVirtualRendererSize() const +Vec2 TextBMFont::resolvePreferredSize(const Vec2& /*sizeHint*/) const { return _labelBMFontRenderer->getContentSize(); } -Node* TextBMFont::getVirtualRenderer() +Node* TextBMFont::getRenderNode() { return _labelBMFontRenderer; } void TextBMFont::labelBMFontScaleChangedWithSize() { - if (_ignoreSize) + if (_autoSize) { _labelBMFontRenderer->setScale(1.0f); } @@ -188,7 +188,7 @@ ResourceData TextBMFont::getRenderFile() void TextBMFont::resetRender() { this->removeProtectedChild(_labelBMFontRenderer); - this->initRenderer(); + this->initRenderNode(); } } // namespace ui diff --git a/axmol/ui/UITextBMFont.h b/axmol/ui/TextBMFont.h similarity index 88% rename from axmol/ui/UITextBMFont.h rename to axmol/ui/TextBMFont.h index aa607cd76210..52a69eb9839d 100644 --- a/axmol/ui/UITextBMFont.h +++ b/axmol/ui/TextBMFont.h @@ -26,7 +26,7 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" /** @@ -80,16 +80,16 @@ class AX_GUI_DLL TextBMFont : public Widget std::string_view getString() const; /** - * Gets the string length of the label. - * Note: This length will be larger than the raw string length, + * Gets the UTF-8 character count of the label. + * Note: This count will be larger than the raw string length, * if you want to get the raw string length, you should call this->getString().size() instead * - * @return string length. + * @return UTF-8 character count. */ - ssize_t getStringLength() const; + ssize_t getCharCount() const; - Vec2 getVirtualRendererSize() const override; - Node* getVirtualRenderer() override; + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; + Node* getRenderNode() override; /** * Returns the "class name" of widget. */ @@ -103,13 +103,13 @@ class AX_GUI_DLL TextBMFont : public Widget void resetRender(); protected: - void initRenderer() override; + void initRenderNode() override; void onSizeChanged() override; void labelBMFontScaleChangedWithSize(); Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; - void adaptRenderers() override; + void updateLayout() override; protected: Label* _labelBMFontRenderer; diff --git a/axmol/ui/UIEditBox/iOS/UITextField+UITextInput.h b/axmol/ui/UIEditBox/iOS/UITextField+UITextInput.h deleted file mode 100644 index 4d7afd4ad85f..000000000000 --- a/axmol/ui/UIEditBox/iOS/UITextField+UITextInput.h +++ /dev/null @@ -1,36 +0,0 @@ -/**************************************************************************** - Copyright (c) 2015 Mazyad Alabduljaleel - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - https://axmol.dev/ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - ****************************************************************************/ -#pragma once - -#import -#import "axmol/ui/UIEditBox/iOS/UITextInput.h" - -@interface UITextField (AXUITextInput) -@end - -/** Trick to load category objects without using -ObjC flag - * http://stackoverflow.com/questions/2567498/objective-c-categories-in-static-library - */ -extern void LoadUITextFieldAXUITextInputCategory(); diff --git a/axmol/ui/UIEditBox/iOS/UITextView+UITextInput.h b/axmol/ui/UIEditBox/iOS/UITextView+UITextInput.h deleted file mode 100644 index 69c761cb8fde..000000000000 --- a/axmol/ui/UIEditBox/iOS/UITextView+UITextInput.h +++ /dev/null @@ -1,36 +0,0 @@ -/**************************************************************************** - Copyright (c) 2015 Mazyad Alabduljaleel - Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. - - https://axmol.dev/ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - ****************************************************************************/ -#pragma once - -#import -#import "axmol/ui/UIEditBox/iOS/UITextInput.h" - -@interface UITextView (AXUITextInput) -@end - -/** Trick to load category objects without using -ObjC flag - * http://stackoverflow.com/questions/2567498/objective-c-categories-in-static-library - */ -extern void LoadUITextViewAXUITextInputCategory(); diff --git a/axmol/ui/UIHelper.cpp b/axmol/ui/UIHelper.cpp index 0169a63352b4..4835e21c3abf 100644 --- a/axmol/ui/UIHelper.cpp +++ b/axmol/ui/UIHelper.cpp @@ -26,10 +26,12 @@ THE SOFTWARE. ****************************************************************************/ #include "axmol/ui/UIHelper.h" -#include "axmol/ui/UIWidget.h" -#include "axmol/ui/UILayoutComponent.h" +#include "axmol/ui/Widget.h" +#include "axmol/ui/LayoutComponent.h" #include "axmol/base/Director.h" #include "axmol/base/text_utils.h" +#include "axmol/base/InputSystem.h" +#include "axmol/scene/Camera.h" namespace ax { @@ -187,44 +189,9 @@ Rect Helper::restrictCapInsetRect(const ax::Rect& capInsets, const Vec2& texture return Rect(x, y, width, height); } -Rect Helper::convertBoundingBoxToScreen(Node* node) +Rect Helper::getNodeNativeWindowRect(Node* node) { - auto director = Director::getInstance(); - auto renderView = director->getRenderView(); - - float scaleX = renderView->getScaleX(); - float scaleY = renderView->getScaleY(); - - const auto windowPlatform = renderView->getWindowPlatform(); - const auto renderScaleMode = Application::getContextAttrs().renderScaleMode; -#if defined(AX_PLATFORM_PC) - Size winSize = renderView->getNativeWindowSize(); - if (renderScaleMode == RenderScaleMode::Physical && - (windowPlatform != WindowPlatform::Win32 && windowPlatform != WindowPlatform::X11)) - { - auto renderScale = renderView->getRenderScale(); - if (renderScale > 0.f) - { - scaleX /= renderScale; - scaleY /= renderScale; - } - } -#else - Size winSize = renderView->getWindowSize(); -#endif - - auto canvasSize = director->getCanvasSize(); - auto leftBottom = node->convertToWorldSpace(Point::ZERO); - - auto contentSize = node->getContentSize(); - auto rightTop = node->convertToWorldSpace(Point(contentSize.width, contentSize.height)); - - auto uiLeft = winSize.width / 2 + (leftBottom.x - canvasSize.width / 2) * scaleX; - auto uiTop = winSize.height / 2 - (rightTop.y - canvasSize.height / 2) * scaleY; - auto uiWidth = (rightTop.x - leftBottom.x) * scaleX; - auto uiHeight = (rightTop.y - leftBottom.y) * scaleY; - - return Rect(uiLeft, uiTop, uiWidth, uiHeight); + return InputSystem::getInstance()->getNodeNativeWindowRect(node); } #pragma region Layout helper @@ -236,8 +203,8 @@ void Helper::setDesignSizeFixedEdge(const Vec2& designSize) Helper::s_designSize = designSize; // Set the design resolution - RenderView* pERenderView = Director::getInstance()->getRenderView(); - const Vec2& windowSize = pERenderView->getWindowSize(); + auto pERenderView = Director::getInstance()->getRenderView(); + const Vec2& windowSize = pERenderView->getWindowSize(); // Vec2 lsSize = lsaSize; @@ -261,8 +228,8 @@ void Helper::setDesignSizeNoBorder(const Vec2& designSize) Helper::s_designSize = designSize; // Set the design resolution - RenderView* pERenderView = Director::getInstance()->getRenderView(); - const Vec2& windowSize = pERenderView->getWindowSize(); + auto pERenderView = Director::getInstance()->getRenderView(); + const Vec2& windowSize = pERenderView->getWindowSize(); // Vec2 lsSize = lsaSize; @@ -1065,105 +1032,100 @@ void Helper::makeVerticalSpacingEqual(std::span nodes, float theSpa // ----------------- Helper::VisibleRect -------------------------- -ax::Rect Helper::VisibleRect::s_ScreenVisibleRect; +ax::Rect Helper::VisibleRect::s_rect; /// x-studio: when design resolution changed, should call this func. void Helper::VisibleRect::refresh(void) { - auto director = Director::getInstance(); - s_ScreenVisibleRect.origin = Director::getInstance()->getVisibleOrigin(); - s_ScreenVisibleRect.size = Director::getInstance()->getVisibleSize(); + auto director = Director::getInstance(); + s_rect.origin = Director::getInstance()->getVisibleOrigin(); + s_rect.size = Director::getInstance()->getVisibleSize(); } void Helper::VisibleRect::lazyInit() { - if (s_ScreenVisibleRect.size.width == 0.0f && s_ScreenVisibleRect.size.height == 0.0f) + if (s_rect.size.width == 0.0f && s_rect.size.height == 0.0f) { auto director = Director::getInstance(); auto renderView = director->getRenderView(); if (renderView->getResolutionPolicy() == ResolutionPolicy::NO_BORDER) { - s_ScreenVisibleRect.origin = director->getVisibleOrigin(); - s_ScreenVisibleRect.size = director->getVisibleSize(); + s_rect.origin = director->getVisibleOrigin(); + s_rect.size = director->getVisibleSize(); } else { - s_ScreenVisibleRect.origin = Helper::getVisibleOrigin(); - s_ScreenVisibleRect.size = Helper::getVisibleSize(); + s_rect.origin = Helper::getVisibleOrigin(); + s_rect.size = Helper::getVisibleSize(); } } } -ax::Rect Helper::VisibleRect::getScreenVisibleRect() +ax::Rect Helper::VisibleRect::getRect() { lazyInit(); - return ax::Rect(s_ScreenVisibleRect.origin.x, s_ScreenVisibleRect.origin.y, s_ScreenVisibleRect.size.width, - s_ScreenVisibleRect.size.height); + return s_rect; } Vec2 Helper::VisibleRect::size() { lazyInit(); - return s_ScreenVisibleRect.size; + return s_rect.size; } Point Helper::VisibleRect::left() { lazyInit(); - return ax::Point(s_ScreenVisibleRect.origin.x, s_ScreenVisibleRect.origin.y + s_ScreenVisibleRect.size.height / 2); + return ax::Point(s_rect.origin.x, s_rect.origin.y + s_rect.size.height / 2); } Point Helper::VisibleRect::right() { lazyInit(); - return ax::Point(s_ScreenVisibleRect.origin.x + s_ScreenVisibleRect.size.width, - s_ScreenVisibleRect.origin.y + s_ScreenVisibleRect.size.height / 2); + return ax::Point(s_rect.origin.x + s_rect.size.width, s_rect.origin.y + s_rect.size.height / 2); } Point Helper::VisibleRect::top() { lazyInit(); - return ax::Point(s_ScreenVisibleRect.origin.x + s_ScreenVisibleRect.size.width / 2, - s_ScreenVisibleRect.origin.y + s_ScreenVisibleRect.size.height); + return ax::Point(s_rect.origin.x + s_rect.size.width / 2, s_rect.origin.y + s_rect.size.height); } Point Helper::VisibleRect::bottom() { lazyInit(); - return ax::Point(s_ScreenVisibleRect.origin.x + s_ScreenVisibleRect.size.width / 2, s_ScreenVisibleRect.origin.y); + return ax::Point(s_rect.origin.x + s_rect.size.width / 2, s_rect.origin.y); } Point Helper::VisibleRect::center() { lazyInit(); - return ax::Point(s_ScreenVisibleRect.origin.x + s_ScreenVisibleRect.size.width / 2, - s_ScreenVisibleRect.origin.y + s_ScreenVisibleRect.size.height / 2); + return ax::Point(s_rect.origin.x + s_rect.size.width / 2, s_rect.origin.y + s_rect.size.height / 2); } Point Helper::VisibleRect::leftTop() { lazyInit(); - return ax::Point(s_ScreenVisibleRect.origin.x, s_ScreenVisibleRect.origin.y + s_ScreenVisibleRect.size.height); + return ax::Point(s_rect.origin.x, s_rect.origin.y + s_rect.size.height); } Point Helper::VisibleRect::rightTop() { lazyInit(); - return ax::Point(s_ScreenVisibleRect.origin.x + s_ScreenVisibleRect.size.width, - s_ScreenVisibleRect.origin.y + s_ScreenVisibleRect.size.height); + return ax::Point(s_rect.origin.x + s_rect.size.width, s_rect.origin.y + s_rect.size.height); } Point Helper::VisibleRect::leftBottom() { lazyInit(); - return s_ScreenVisibleRect.origin; + return s_rect.origin; } Point Helper::VisibleRect::rightBottom() { lazyInit(); - return ax::Point(s_ScreenVisibleRect.origin.x + s_ScreenVisibleRect.size.width, s_ScreenVisibleRect.origin.y); + return ax::Point(s_rect.origin.x + s_rect.size.width, s_rect.origin.y); } /// visual screen @@ -1185,14 +1147,14 @@ float Helper::VisibleRect::getNodeRight(Node* pNode) ax::Point ptLocal(Helper::getNodeLeft(pNode) + pNode->getContentSize().width /* * pNode->getScaleX()*/, 0); auto ptWorld = pNode->getParent()->convertToWorldSpace(ptLocal); - auto visibleRect = Helper::VisibleRect::getScreenVisibleRect(); + auto visibleRect = Helper::VisibleRect::getRect(); return visibleRect.size.width - ptWorld.x; } float Helper::VisibleRect::getNodeTop(Node* pNode) { ax::Point ptLocal(0, Helper::getNodeBottom(pNode) + pNode->getContentSize().height /* * pNode->getScaleY()*/); auto ptWorld = pNode->getParent()->convertToWorldSpace(ptLocal); - auto visibleRect = Helper::VisibleRect::getScreenVisibleRect(); + auto visibleRect = Helper::VisibleRect::getRect(); return visibleRect.size.height - ptWorld.y; } @@ -1435,7 +1397,7 @@ void Helper::VisibleRect::setNodeNormalizedTop(Node* pNode, const float ratioTop void Helper::VisibleRect::setNodeNormalizedPositionX(ax::Node* pNode, float ratio) { AX_ASSERT(pNode); - ax::Rect visibleRect = Helper::Helper::VisibleRect::getScreenVisibleRect(); + ax::Rect visibleRect = Helper::Helper::VisibleRect::getRect(); ax::Point ptWorld(visibleRect.size.width * ratio + visibleRect.origin.x, 0); auto ptLocal = pNode->getParent()->convertToNodeSpace(ptWorld); pNode->setPositionX(ptLocal.x); @@ -1444,7 +1406,7 @@ void Helper::VisibleRect::setNodeNormalizedPositionX(ax::Node* pNode, float rati void Helper::VisibleRect::setNodeNormalizedPositionY(ax::Node* pNode, float ratio) { AX_ASSERT(pNode); - ax::Rect visibleRect = Helper::Helper::VisibleRect::getScreenVisibleRect(); + ax::Rect visibleRect = Helper::Helper::VisibleRect::getRect(); ax::Point ptWorld(0, visibleRect.size.height * ratio + visibleRect.origin.y); auto ptLocal = pNode->getParent()->convertToNodeSpace(ptWorld); @@ -1455,7 +1417,7 @@ void Helper::VisibleRect::setNodeNormalizedPosition(Node* pNode, const ax::Point AX_ASSERT(pNode); pNode->setIgnoreAnchorPointForPosition(false); pNode->setAnchorPoint(ax::Vec2(.5f, .5f)); - ax::Rect visibleRect = Helper::Helper::VisibleRect::getScreenVisibleRect(); + ax::Rect visibleRect = Helper::Helper::VisibleRect::getRect(); ax::Point ptWorld(visibleRect.size.width * ratio.x + visibleRect.origin.x, visibleRect.size.height * ratio.y + visibleRect.origin.y); auto ptLocal = pNode->getParent()->convertToNodeSpace(ptWorld); diff --git a/axmol/ui/UIHelper.h b/axmol/ui/UIHelper.h index d5ef9e09118e..55c8e367b8ef 100644 --- a/axmol/ui/UIHelper.h +++ b/axmol/ui/UIHelper.h @@ -116,13 +116,14 @@ class AX_GUI_DLL Helper static Rect restrictCapInsetRect(const Rect& capInsets, const Vec2& textureSize); /** - *@brief Convert a node's boundingBox rect into screen coordinates. + * Returns the node bounding rectangle in OS native window coordinates. * - * @param node Any node pointer. + * Coordinates origin is top-left. Units are OS native pixels/points (NOT engine logical screen units). + * The returned rect is suitable for native windowing APIs, platform UI integration, and system-level hit testing. * - * @return A Rect in screen coordinates. + * Note: This function returns screen rect after converting engine screen units by dividing by _inputScale. */ - static Rect convertBoundingBoxToScreen(Node* node); + static Rect getNodeNativeWindowRect(Node* node); #ifndef _AX_GEN_SCRIPT_BINDINGS # pragma region x-studio spec @@ -888,7 +889,7 @@ class AX_GUI_DLL Helper public: static void refresh(void); - static ax::Rect getScreenVisibleRect(); + static ax::Rect getRect(); static Vec2 size(); static ax::Point left(); static ax::Point right(); @@ -940,7 +941,7 @@ class AX_GUI_DLL Helper private: static void lazyInit(); - static ax::Rect s_ScreenVisibleRect; + static ax::Rect s_rect; }; # pragma endregion #endif diff --git a/axmol/ui/UITextField.cpp b/axmol/ui/UITextField.cpp deleted file mode 100644 index b79ac3039cac..000000000000 --- a/axmol/ui/UITextField.cpp +++ /dev/null @@ -1,845 +0,0 @@ -/**************************************************************************** -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#include "axmol/ui/UITextField.h" -#include "axmol/platform/FileUtils.h" -#include "axmol/ui/UIHelper.h" -#include "axmol/base/text_utils.h" -#include "axmol/scene/Camera.h" - -namespace ax -{ - -namespace ui -{ - -UICCTextField* UICCTextField::create() -{ - UICCTextField* ret = new UICCTextField(); - ret->autorelease(); - return ret; -} - -UICCTextField::UICCTextField() - : _maxLengthEnabled(false) - , _maxLength(0) - , _attachWithIME(false) - , _detachWithIME(false) - , _insertText(false) - , _deleteBackward(false) -{} - -UICCTextField::~UICCTextField() {} - -UICCTextField* UICCTextField::create(std::string_view placeholder, std::string_view fontName, float fontSize) -{ - UICCTextField* pRet = new UICCTextField(); - - if (pRet->initWithPlaceHolder("", fontName, fontSize)) - { - pRet->autorelease(); - if (!placeholder.empty()) - { - pRet->setPlaceHolder(placeholder); - } - return pRet; - } - AX_SAFE_DELETE(pRet); - - return nullptr; -} - -void UICCTextField::onEnter() -{ - TextFieldTTF::onEnter(); - TextFieldTTF::setDelegate(this); -} - -bool UICCTextField::onTextFieldAttachWithIME(TextFieldTTF* /*pSender*/) -{ - setAttachWithIME(true); - return false; -} - -bool UICCTextField::onTextFieldInsertText(TextFieldTTF* /*pSender*/, const char* text, size_t nLen) -{ - if (nLen == 1 && strcmp(text, "\n") == 0) - { - return false; - } - setInsertText(true); - if (_maxLengthEnabled) - { - if (static_cast(TextFieldTTF::getCharCount()) >= _maxLength) - { - return true; - } - } - - return false; -} - -bool UICCTextField::onTextFieldDeleteBackward(TextFieldTTF* /*pSender*/, const char* /*delText*/, size_t /*nLen*/) -{ - setDeleteBackward(true); - return false; -} - -bool UICCTextField::onTextFieldDetachWithIME(TextFieldTTF* /*pSender*/) -{ - setDetachWithIME(true); - return false; -} - -void UICCTextField::insertText(const char* text, size_t len) -{ - std::string input_text = text; - - if (strcmp(text, "\n") != 0) - { - if (_maxLengthEnabled) - { - int32_t text_count = text_utils::countUTF8Chars(getString()); - if (text_count >= _maxLength) - { - // password - if (this->isSecureTextEntry()) - { - setPasswordText(getString()); - } - return; - } - - int32_t input_count = text_utils::countUTF8Chars(text); - int32_t total = text_count + input_count; - - if (total > _maxLength) - { - int32_t length = _maxLength - text_count; - - input_text = Helper::getSubStringOfUTF8String(input_text, 0, length); - len = input_text.length(); - } - } - } - TextFieldTTF::insertText(input_text.c_str(), len); -} - -void UICCTextField::openIME() -{ - TextFieldTTF::attachWithIME(); -} - -void UICCTextField::closeIME() -{ - TextFieldTTF::detachWithIME(); -} - -void UICCTextField::setMaxLengthEnabled(bool enable) -{ - _maxLengthEnabled = enable; -} - -bool UICCTextField::isMaxLengthEnabled() const -{ - return _maxLengthEnabled; -} - -void UICCTextField::setMaxLength(int length) -{ - _maxLength = length; -} - -int UICCTextField::getMaxLength() const -{ - return _maxLength; -} - -std::size_t UICCTextField::getCharCount() const -{ - return TextFieldTTF::getCharCount(); -} - -void UICCTextField::setPasswordEnabled(bool enable) -{ - this->setSecureTextEntry(enable); -} - -bool UICCTextField::isPasswordEnabled() const -{ - return this->isSecureTextEntry(); -} - -void UICCTextField::setPasswordStyleText(std::string_view styleText) -{ - this->setPasswordTextStyle(styleText); -} - -void UICCTextField::setPasswordText(std::string_view text) -{ - std::string tempStr = ""; - int32_t text_count = text_utils::countUTF8Chars(text); - int32_t max = text_count; - - if (_maxLengthEnabled) - { - if (text_count > _maxLength) - { - max = _maxLength; - } - } - - for (int i = 0; i < max; ++i) - { - tempStr.append(_passwordStyleText); - } - - Label::setString(tempStr); -} - -void UICCTextField::setAttachWithIME(bool attach) -{ - _attachWithIME = attach; -} - -bool UICCTextField::getAttachWithIME() const -{ - return _attachWithIME; -} - -void UICCTextField::setDetachWithIME(bool detach) -{ - _detachWithIME = detach; -} - -bool UICCTextField::getDetachWithIME() const -{ - return _detachWithIME; -} - -void UICCTextField::setInsertText(bool insert) -{ - _insertText = insert; -} - -bool UICCTextField::getInsertText() const -{ - return _insertText; -} - -void UICCTextField::setDeleteBackward(bool deleteBackward) -{ - _deleteBackward = deleteBackward; -} - -bool UICCTextField::getDeleteBackward() const -{ - return _deleteBackward; -} - -static const int TEXTFIELD_RENDERER_Z = (-1); - -IMPLEMENT_CLASS_GUI_INFO(TextField) - -TextField::TextField() - : _textFieldRenderer(nullptr) - , _touchWidth(0.0f) - , _touchHeight(0.0f) - , _useTouchArea(false) - , _textFieldEventListener(nullptr) - , _eventCallback(nullptr) - , _textFieldRendererAdaptDirty(true) - , _fontName("Thonburi") - , _fontSize(10) - , _fontType(FontType::SYSTEM) -{} - -TextField::~TextField() -{ - _textFieldEventListener = nullptr; -} - -TextField* TextField::create() -{ - TextField* widget = new TextField(); - if (widget->init()) - { - widget->autorelease(); - return widget; - } - AX_SAFE_DELETE(widget); - return nullptr; -} - -TextField* TextField::create(std::string_view placeholder, std::string_view fontName, int fontSize) -{ - TextField* widget = new TextField(); - if (widget->init()) - { - widget->setFontName(fontName); - widget->setFontSize(fontSize); - widget->setPlaceHolder(placeholder); - widget->autorelease(); - return widget; - } - AX_SAFE_DELETE(widget); - return nullptr; -} - -bool TextField::init() -{ - if (Widget::init()) - { - setTouchEnabled(true); - return true; - } - return false; -} - -void TextField::onEnter() -{ - Widget::onEnter(); - scheduleUpdate(); -} - -void TextField::onExit() -{ - if (_textFieldRenderer) - _textFieldRenderer->detachWithIME(); - Widget::onExit(); -} - -void TextField::initRenderer() -{ - _textFieldRenderer = UICCTextField::create(); - addProtectedChild(_textFieldRenderer, TEXTFIELD_RENDERER_Z, -1); -} - -void TextField::setTouchSize(const Vec2& size) -{ - _touchWidth = size.width; - _touchHeight = size.height; -} - -void TextField::setTouchAreaEnabled(bool enable) -{ - _useTouchArea = enable; -} - -bool TextField::hitTest(const Vec2& pt, const Camera* camera, Vec3* /*p*/) const -{ - if (false == _useTouchArea) - { - return Widget::hitTest(pt, camera, nullptr); - } - - auto size = getContentSize(); - auto anch = getAnchorPoint(); - Rect rect((size.width - _touchWidth) * anch.x, (size.height - _touchHeight) * anch.y, _touchWidth, _touchHeight); - return isScreenPointInRect(pt, camera, getWorldToNodeTransform(), rect, nullptr); -} - -Vec2 TextField::getTouchSize() const -{ - return Vec2(_touchWidth, _touchHeight); -} - -void TextField::setString(std::string_view text) -{ - std::string strText(text); - - if (isMaxLengthEnabled()) - { - int max = _textFieldRenderer->getMaxLength(); - int32_t text_count = text_utils::countUTF8Chars(text); - if (text_count > max) - { - strText = Helper::getSubStringOfUTF8String(strText, 0, max); - } - } - - if (isPasswordEnabled()) - { - _textFieldRenderer->setPasswordText(strText); - _textFieldRenderer->setString(""); - _textFieldRenderer->insertText(strText.c_str(), strText.size()); - } - else - { - _textFieldRenderer->setString(strText); - } - _textFieldRendererAdaptDirty = true; - updateContentSizeWithTextureSize(_textFieldRenderer->getContentSize()); -} - -void TextField::setPlaceHolder(std::string_view value) -{ - _textFieldRenderer->setPlaceHolder(value); - _textFieldRendererAdaptDirty = true; - updateContentSizeWithTextureSize(_textFieldRenderer->getContentSize()); -} - -std::string_view TextField::getPlaceHolder() const -{ - return _textFieldRenderer->getPlaceHolder(); -} - -const Color32& TextField::getPlaceHolderColor() const -{ - return _textFieldRenderer->getColorSpaceHolder(); -} - -void TextField::setPlaceHolderColor(const ax::Color32& color) -{ - _textFieldRenderer->setColorSpaceHolder(color); -} - -const Color32& TextField::getTextColor() const -{ - return _textFieldRenderer->getTextColor(); -} - -void TextField::setTextColor(const ax::Color32& textColor) -{ - _textFieldRenderer->setTextColor(textColor); -} - -void TextField::setFontSize(int size) -{ - if (_fontType == FontType::SYSTEM) - { - _textFieldRenderer->setSystemFontSize(size); - } - else if (_fontType == FontType::BMFONT) - { - _textFieldRenderer->setBMFontSize(size); - } - else - { - TTFConfig config = _textFieldRenderer->getTTFConfig(); - config.fontSize = size; - _textFieldRenderer->setTTFConfig(config); - } - _fontSize = size; - _textFieldRendererAdaptDirty = true; - updateContentSizeWithTextureSize(_textFieldRenderer->getContentSize()); -} - -int TextField::getFontSize() const -{ - return _fontSize; -} - -void TextField::setFontName(std::string_view name) -{ - if (FileUtils::getInstance()->isFileExist(name)) - { - std::string lcName{name}; - std::transform(lcName.begin(), lcName.end(), lcName.begin(), ::tolower); - if (lcName.substr(lcName.length() - 4) == ".fnt") - { - _textFieldRenderer->setBMFontFilePath(name); - _fontType = FontType::BMFONT; - } - else - { - TTFConfig config = _textFieldRenderer->getTTFConfig(); - config.fontFilePath = name; - config.fontSize = _fontSize; - _textFieldRenderer->setTTFConfig(config); - _fontType = FontType::TTF; - } - } - else - { - _textFieldRenderer->setSystemFontName(name); - if (_fontType == FontType::TTF) - { - _textFieldRenderer->requestSystemFontRefresh(); - } - _fontType = FontType::SYSTEM; - } - _fontName = name; - _textFieldRendererAdaptDirty = true; - updateContentSizeWithTextureSize(_textFieldRenderer->getContentSize()); -} - -std::string_view TextField::getFontName() const -{ - return _fontName; -} - -void TextField::didNotSelectSelf() -{ - _textFieldRenderer->detachWithIME(); -} - -std::string_view TextField::getString() const -{ - return _textFieldRenderer->getString(); -} - -int TextField::getStringLength() const -{ - return _textFieldRenderer->getStringLength(); -} - -bool TextField::onTouchBegan(Touch* touch, Event* unusedEvent) -{ - bool pass = Widget::onTouchBegan(touch, unusedEvent); - if (_hitted) - { - if (isFocusEnabled()) - { - requestFocus(); - } - - _textFieldRenderer->attachWithIME(); - } - else - { - this->didNotSelectSelf(); - } - return pass; -} - -void TextField::setMaxLengthEnabled(bool enable) -{ - _textFieldRenderer->setMaxLengthEnabled(enable); -} - -bool TextField::isMaxLengthEnabled() const -{ - return _textFieldRenderer->isMaxLengthEnabled(); -} - -void TextField::setMaxLength(int length) -{ - _textFieldRenderer->setMaxLength(length); - - setString(getString()); -} - -int TextField::getMaxLength() const -{ - return _textFieldRenderer->getMaxLength(); -} - -void TextField::setPasswordEnabled(bool enable) -{ - _textFieldRenderer->setPasswordEnabled(enable); -} - -bool TextField::isPasswordEnabled() const -{ - return _textFieldRenderer->isPasswordEnabled(); -} - -void TextField::setPasswordStyleText(std::string_view styleText) -{ - _textFieldRenderer->setPasswordStyleText(styleText); - - setString(getString()); -} - -std::string_view TextField::getPasswordStyleText() const -{ - return _textFieldRenderer->getPasswordTextStyle(); -} - -void TextField::update(float /*dt*/) -{ - if (getDetachWithIME()) - { - detachWithIMEEvent(); - setDetachWithIME(false); - } - - if (getAttachWithIME()) - { - attachWithIMEEvent(); - setAttachWithIME(false); - } - - if (getDeleteBackward()) - { - _textFieldRendererAdaptDirty = true; - updateContentSizeWithTextureSize(_textFieldRenderer->getContentSize()); - - deleteBackwardEvent(); - setDeleteBackward(false); - } - - if (getInsertText()) - { - // we update the content size first such that when user call getContentSize() in event callback won't be wrong - _textFieldRendererAdaptDirty = true; - updateContentSizeWithTextureSize(_textFieldRenderer->getContentSize()); - - insertTextEvent(); - setInsertText(false); - } -} - -bool TextField::getAttachWithIME() const -{ - return _textFieldRenderer->getAttachWithIME(); -} - -void TextField::setAttachWithIME(bool attach) -{ - _textFieldRenderer->setAttachWithIME(attach); -} - -bool TextField::getDetachWithIME() const -{ - return _textFieldRenderer->getDetachWithIME(); -} - -void TextField::setDetachWithIME(bool detach) -{ - _textFieldRenderer->setDetachWithIME(detach); -} - -bool TextField::getInsertText() const -{ - return _textFieldRenderer->getInsertText(); -} - -void TextField::setInsertText(bool insertText) -{ - _textFieldRenderer->setInsertText(insertText); -} - -bool TextField::getDeleteBackward() const -{ - return _textFieldRenderer->getDeleteBackward(); -} - -void TextField::setDeleteBackward(bool deleteBackward) -{ - _textFieldRenderer->setDeleteBackward(deleteBackward); -} - -void TextField::attachWithIMEEvent() -{ - this->retain(); - if (_eventCallback) - { - _eventCallback(this, EventType::ATTACH_WITH_IME); - } - if (_ccEventCallback) - { - _ccEventCallback(this, static_cast(EventType::ATTACH_WITH_IME)); - } - this->release(); -} - -void TextField::detachWithIMEEvent() -{ - this->retain(); - if (_eventCallback) - { - _eventCallback(this, EventType::DETACH_WITH_IME); - } - if (_ccEventCallback) - { - _ccEventCallback(this, static_cast(EventType::DETACH_WITH_IME)); - } - this->release(); -} - -void TextField::insertTextEvent() -{ - this->retain(); - if (_eventCallback) - { - _eventCallback(this, EventType::INSERT_TEXT); - } - if (_ccEventCallback) - { - _ccEventCallback(this, static_cast(EventType::INSERT_TEXT)); - } - this->release(); -} - -void TextField::deleteBackwardEvent() -{ - this->retain(); - if (_eventCallback) - { - _eventCallback(this, EventType::DELETE_BACKWARD); - } - if (_ccEventCallback) - { - _ccEventCallback(this, static_cast(EventType::DELETE_BACKWARD)); - } - this->release(); -} - -void TextField::addEventListener(const ccTextFieldCallback& callback) -{ - _eventCallback = callback; -} - -void TextField::onSizeChanged() -{ - Widget::onSizeChanged(); - _textFieldRendererAdaptDirty = true; -} - -void TextField::adaptRenderers() -{ - if (_textFieldRendererAdaptDirty) - { - textfieldRendererScaleChangedWithSize(); - _textFieldRendererAdaptDirty = false; - } -} - -void TextField::textfieldRendererScaleChangedWithSize() -{ - if (!_ignoreSize) - { - _textFieldRenderer->setDimensions(_contentSize.width, _contentSize.height); - } - _textFieldRenderer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); -} - -Vec2 TextField::getAutoRenderSize() -{ - Vec2 virtualSize = _textFieldRenderer->getContentSize(); - if (!_ignoreSize) - { - _textFieldRenderer->setDimensions(0, 0); - virtualSize = _textFieldRenderer->getContentSize(); - _textFieldRenderer->setDimensions(_contentSize.width, _contentSize.height); - } - - return virtualSize; -} - -Vec2 TextField::getVirtualRendererSize() const -{ - return _textFieldRenderer->getContentSize(); -} - -Node* TextField::getVirtualRenderer() -{ - return _textFieldRenderer; -} - -std::string TextField::getDescription() const -{ - return "TextField"; -} - -void TextField::attachWithIME() -{ - _textFieldRenderer->attachWithIME(); -} - -void TextField::detachWithIME() -{ - _textFieldRenderer->detachWithIME(); -} - -Widget* TextField::createCloneInstance() -{ - return TextField::create(); -} - -void TextField::copySpecialProperties(Widget* widget) -{ - TextField* textField = dynamic_cast(widget); - if (textField) - { - setString(textField->_textFieldRenderer->getString()); - setPlaceHolder(textField->getString()); - setFontSize(textField->_fontSize); - setFontName(textField->_fontName); - setMaxLengthEnabled(textField->isMaxLengthEnabled()); - setMaxLength(textField->getMaxLength()); - setPasswordEnabled(textField->isPasswordEnabled()); - setPasswordStyleText(textField->getPasswordStyleText()); - setAttachWithIME(textField->getAttachWithIME()); - setDetachWithIME(textField->getDetachWithIME()); - setInsertText(textField->getInsertText()); - setDeleteBackward(textField->getDeleteBackward()); - _eventCallback = textField->_eventCallback; - _ccEventCallback = textField->_ccEventCallback; - _textFieldEventListener = textField->_textFieldEventListener; - } -} - -void TextField::setTextAreaSize(const Vec2& size) -{ - this->setContentSize(size); -} - -void TextField::setTextHorizontalAlignment(TextHAlignment alignment) -{ - _textFieldRenderer->setHorizontalAlignment(alignment); -} - -TextHAlignment TextField::getTextHorizontalAlignment() const -{ - return _textFieldRenderer->getHorizontalAlignment(); -} - -void TextField::setTextVerticalAlignment(TextVAlignment alignment) -{ - _textFieldRenderer->setVerticalAlignment(alignment); -} - -TextVAlignment TextField::getTextVerticalAlignment() const -{ - return _textFieldRenderer->getVerticalAlignment(); -} - -void TextField::setCursorEnabled(bool enabled) -{ - _textFieldRenderer->setCursorEnabled(enabled); -} - -void TextField::setCursorChar(char cursor) -{ - _textFieldRenderer->setCursorChar(cursor); -} - -void TextField::setCursorPosition(std::size_t cursorPosition) -{ - _textFieldRenderer->setCursorPosition(cursorPosition); -} - -void TextField::setCursorFromPoint(const Vec2& point, const Camera* camera) -{ - _textFieldRenderer->setCursorFromPoint(point, camera); -} - -} // namespace ui - -} // namespace ax diff --git a/axmol/ui/UITextField.h b/axmol/ui/UITextField.h deleted file mode 100644 index 949439f5f98b..000000000000 --- a/axmol/ui/UITextField.h +++ /dev/null @@ -1,642 +0,0 @@ -/**************************************************************************** -Copyright (c) 2013-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -#include "axmol/ui/UIWidget.h" -#include "axmol/2d/TextFieldTTF.h" -#include "axmol/ui/GUIExport.h" - -namespace ax -{ -/** - * @addtogroup ui - * @{ - */ - -namespace ui -{ - -/** - * @brief A helper class which inherit from @see `TextFieldTTF` and implements the @see `TextFieldDelegate` protocol. - * It is mainly be used internally by @see `UITextField` class. - * !!!DEPRECATED since axmol-2.1.3 - * @lua NA - */ -class AX_GUI_DLL UICCTextField : public TextFieldTTF, public TextFieldDelegate -{ -public: - /** - * @brief Create an empty UICCTextField. - * - * @return A UICCTextField instance. - */ - static UICCTextField* create(); - - /** - * Default constructor - */ - UICCTextField(); - - /** - * Default destructor - */ - ~UICCTextField(); - - void onEnter() override; - - /** - * Create a UICCTextField instance with a placeholder, a fontName and a fontSize. - *@param placeholder Placeholder in string. - *@param fontName Font name in string. - *@param fontSize Font size in float. - *@return A UICCTextField instance. - */ - static UICCTextField* create(std::string_view placeholder, std::string_view fontName, float fontSize); - - // override functions - bool onTextFieldAttachWithIME(TextFieldTTF* pSender) override; - bool onTextFieldDetachWithIME(TextFieldTTF* pSender) override; - bool onTextFieldInsertText(TextFieldTTF* pSender, const char* text, size_t nLen) override; - bool onTextFieldDeleteBackward(TextFieldTTF* pSender, const char* delText, size_t nLen) override; - void insertText(const char* text, size_t len) override; - - /** - * Open up the IME. - */ - void openIME(); - - /** - * Close the IME. - */ - void closeIME(); - - /** - * Toggle enable max length limitation. - *@param enable True to enable max length, false otherwise. - */ - void setMaxLengthEnabled(bool enable); - - /** - * Query max length enable state. - *@return Whether max length is enabled or not. - */ - bool isMaxLengthEnabled() const; - - /** - * Set maximize length. - *@param length The maximize length in integer. - */ - void setMaxLength(int length); - - /** - * Get maximize length. - *@return Maximize length. - */ - int getMaxLength() const; - - /** - * Return the total inputed characters. - *@return Total inputed character count. - */ - std::size_t getCharCount() const; - - /** - * @brief Toggle password input mode. - * - * @param enable True if enable password input, false otherwise. - */ - void setPasswordEnabled(bool enable); - - /** - * @brief Query whether password input mode is enabled or not. - * - * @return True if password input is enabled, false otherwise. - */ - bool isPasswordEnabled() const; - - /** - * @brief Change password style text. - * - * @param styleText The styleText for password mask, the default value is "*". - */ - void setPasswordStyleText(std::string_view styleText); - - /** - * @brief Set the password text content. - * - * @param text The content of password. - */ - void setPasswordText(std::string_view text); - - /** - * @brief Toggle attach with IME. - * - * @param attach True if attach with IME, false otherwise. - */ - void setAttachWithIME(bool attach); - - /** - * @brief Query whether the IME is attached or not. - * - * @return True if IME is attached, false otherwise. - */ - bool getAttachWithIME() const; - - /** - * @brief Toggle detach with IME. - * - * @param detach True if detach with IME, false otherwise. - */ - void setDetachWithIME(bool detach); - - /** - * @brief Query whether IME is detached or not. - * - * @return True if IME is detached, false otherwise. - */ - bool getDetachWithIME() const; - - /** - * @brief Toggle enable text insert. - * - * @param insert True if enable insert text, false otherwise. - */ - void setInsertText(bool insert); - - /** - * @brief Query whether insert text is enabled or not. - * - * @return True if insert text is enabled, false otherwise. - */ - bool getInsertText() const; - - /** - * @brief Toggle enable delete backward. - * - * @param deleteBackward True if enable delete backward, false otherwise. - */ - void setDeleteBackward(bool deleteBackward); - - /** - * @brief Query whether delete backward is enabled or not. - * - * @return True if delete backward is enabled, false otherwise. - */ - bool getDeleteBackward() const; - -protected: - bool _maxLengthEnabled; - int _maxLength; - bool _attachWithIME; - bool _detachWithIME; - bool _insertText; - bool _deleteBackward; -}; - -/** - * @brief A widget which allows users to input text. - * The rendering of the input text are based on @see `TextFieldTTF'. - * If you want to use system control behavior, please use @see `EditBox` instead. - * @lua NA - */ -class AX_GUI_DLL TextField : public Widget -{ - - DECLARE_CLASS_GUI_INFO - -public: - /** - * TextField event type. - */ - enum class EventType - { - ATTACH_WITH_IME, - DETACH_WITH_IME, - INSERT_TEXT, - DELETE_BACKWARD, - }; - /** - * A callback which would be called when a TextField event happens. - */ - typedef std::function ccTextFieldCallback; - - /** - * @brief Default constructor. - * - */ - TextField(); - - /** - * @brief Default destructor. - */ - virtual ~TextField(); - - /** - * @brief Create an empty TextField. - * - * @return A TextField instance. - */ - static TextField* create(); - - /** - * @brief Create a TextField with a placeholder, a font name and a font size. - * - * @param placeholder The placeholder string. - * @param fontName The font name. - * @param fontSize The font size. - * @return A TextField instance. - */ - static TextField* create(std::string_view placeholder, std::string_view fontName, int fontSize); - - /** - * @brief Set the touch size - * The touch size is used for @see `hitTest`. - * @param size A delimitation zone. - */ - void setTouchSize(const Vec2& size); - - /** - * @brief Get current touch size of TextField. - * - * @return The TextField's touch size. - */ - Vec2 getTouchSize() const; - - /** - * @brief Toggle enable touch area. - * - * @param enable True if enable touch area, false otherwise. - */ - void setTouchAreaEnabled(bool enable); - - bool hitTest(const Vec2& pt, const Camera* camera, Vec3* p) const override; - - /** - * @brief Set placeholder of TextField. - * - * @param value The string value of placeholder. - */ - void setPlaceHolder(std::string_view value); - - /** - * @brief Get the placeholder of TextField. - * - * @return A placeholder string. - */ - std::string_view getPlaceHolder() const; - - /** - * @brief Query the placeholder string color. - * - * @return The color of placeholder. - */ - const Color32& getPlaceHolderColor() const; - - /** - * @brief Change the placeholder color. - * - * @param color A color value in `Color32`. - */ - void setPlaceHolderColor(const Color32& color); - - /** - * @brief Query the text string color. - * - * @return The color of the text. - */ - const Color32& getTextColor() const; - - /** - * @brief Change the text color. - * - * @param textColor The color value in `Color32`. - */ - void setTextColor(const Color32& textColor); - - /** - * @brief Change font size of TextField. - * - * @param size The integer font size. - */ - void setFontSize(int size); - - /** - * @brief Query the font size. - * - * @return The integer font size. - */ - int getFontSize() const; - - /** - * @brief Change the font name of TextField. - * - * @param name The font name string. - */ - void setFontName(std::string_view name); - - /** - * @brief Query the TextField's font name. - * - * @return The font name string. - */ - std::string_view getFontName() const; - - /** - * @brief Detach the IME. - */ - virtual void didNotSelectSelf(); - - /** - *Change content of TextField. - *@param text A string content. - */ - void setString(std::string_view text); - - /** - *Query the content of TextField. - *@return The string value of TextField. - */ - std::string_view getString() const; - - bool onTouchBegan(Touch* touch, Event* unusedEvent) override; - - /** - * @brief Toggle maximize length enable - * - * @param enable True if enable maximize length, false otherwise. - */ - void setMaxLengthEnabled(bool enable); - - /** - * @brief Query whether max length is enabled or not. - * - * @return True if maximize length is enabled, false otherwise. - */ - bool isMaxLengthEnabled() const; - - /** - * @brief Change maximize input length limitation. - * - * @param length A character count in integer. - */ - void setMaxLength(int length); - - /** - * @brief Query maximize input length of TextField. - * - * @return The integer value of maximize input length. - */ - int getMaxLength() const; - - /** - * @brief Query the input string length. - * - * @return A integer length value. - */ - int getStringLength() const; - - /** - * @brief Toggle enable password input mode. - * - * @param enable True if enable password input mode, false otherwise. - */ - void setPasswordEnabled(bool enable); - - /** - * @brief Query whether password is enabled or not. - * - * @return True if password is enabled, false otherwise. - */ - bool isPasswordEnabled() const; - - /** - * @brief Change password style text. - * - * @param styleText The styleText for password mask, the default value is "*". - */ - void setPasswordStyleText(std::string_view styleText); - /** - * @brief Query the password style text. - * - * @return A password style text. - */ - std::string_view getPasswordStyleText() const; - - void update(float dt) override; - - /** - * @brief Query whether the IME is attached or not. - * - * @return True if IME is attached, false otherwise. - */ - bool getAttachWithIME() const; - - /** - * @brief Toggle attach with IME. - * - * @param attach True if attach with IME, false otherwise. - */ - void setAttachWithIME(bool attach); - - /** - * @brief Query whether IME is detached or not. - * - * @return True if IME is detached, false otherwise. - */ - bool getDetachWithIME() const; - - /** - * @brief Toggle detach with IME. - * - * @param detach True if detach with IME, false otherwise. - */ - void setDetachWithIME(bool detach); - - /** - * @brief Whether it is ready to get the inserted text or not. - * - * @return True if the insert text is ready, false otherwise. - */ - bool getInsertText() const; - - /** - * @brief Toggle enable insert text mode - * - * @param insertText True if enable insert text, false otherwise. - */ - void setInsertText(bool insertText); - - /** - * @brief Whether it is ready to delete backward in TextField. - * - * @return True is the delete backward is enabled, false otherwise. - */ - bool getDeleteBackward() const; - - /** - * @brief Toggle enable delete backward mode. - * - * @param deleteBackward True is delete backward is enabled, false otherwise. - */ - void setDeleteBackward(bool deleteBackward); - - /** - * Add a event listener to TextField, when some predefined event happens, the callback will be called. - *@param callback A callback function with type of `ccTextFieldCallback`. - */ - void addEventListener(const ccTextFieldCallback& callback); - - /** - * Returns the "class name" of widget. - */ - std::string getDescription() const override; - - /** - * @brief Get the renderer size in auto mode. - * - * @return A delimitation zone. - */ - virtual Vec2 getAutoRenderSize(); - // override functions. - Vec2 getVirtualRendererSize() const override; - Node* getVirtualRenderer() override; - void onEnter() override; - void onExit() override; - - /** - * @brief Attach the IME for inputing. - * - */ - void attachWithIME(); - - /** - * @brief Detach the IME from inputing. - * - */ - void detachWithIME(); - - /** - * @brief Change the text area size. - * - * @param size A delimitation zone. - */ - void setTextAreaSize(const Vec2& size); - - /** - * @brief Change horizontal text alignment. - * - * @param alignment A alignment arguments in @see `TextHAlignment`. - */ - void setTextHorizontalAlignment(TextHAlignment alignment); - - /** - * @brief Inquire the horizontal alignment - * - * @return The horizontal alignment - */ - TextHAlignment getTextHorizontalAlignment() const; - - /** - * @brief Change the vertical text alignment. - * - * @param alignment A alignment arguments in @see `TextVAlignment`. - */ - void setTextVerticalAlignment(TextVAlignment alignment); - - /** - * @brief Inquire the horizontal alignment - * - * @return The horizontal alignment - */ - TextVAlignment getTextVerticalAlignment() const; - - /** - * Set enable cursor use. - */ - void setCursorEnabled(bool enabled); - - /** - * Set char showing cursor. - */ - void setCursorChar(char cursor); - - /** - * Set cursor position, if enabled - */ - void setCursorPosition(std::size_t cursorPosition); - - /** - * Set cursor position to hit letter, if enabled - */ - void setCursorFromPoint(const Vec2& point, const Camera* camera); - - bool init() override; - -protected: - void initRenderer() override; - void attachWithIMEEvent(); - void detachWithIMEEvent(); - void insertTextEvent(); - void deleteBackwardEvent(); - void onSizeChanged() override; - - void textfieldRendererScaleChangedWithSize(); - - Widget* createCloneInstance() override; - void copySpecialProperties(Widget* model) override; - void adaptRenderers() override; - -protected: - UICCTextField* _textFieldRenderer; - - float _touchWidth; - float _touchHeight; - bool _useTouchArea; - - Object* _textFieldEventListener; - ccTextFieldCallback _eventCallback; - - bool _textFieldRendererAdaptDirty; - -protected: - enum class FontType - { - SYSTEM, - TTF, - BMFONT - }; - - std::string _fontName; - int _fontSize; - FontType _fontType; -}; - -} // namespace ui - -// end of ui group -/// @} -} // namespace ax diff --git a/axmol/ui/UITextFieldEx.cpp b/axmol/ui/UITextFieldEx.cpp deleted file mode 100644 index 8901666731ab..000000000000 --- a/axmol/ui/UITextFieldEx.cpp +++ /dev/null @@ -1,1036 +0,0 @@ -/**************************************************************************** -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#include "axmol/ui/UITextFieldEx.h" -#include "axmol/base/Director.h" - -namespace ax -{ - -#if defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP -# define axbeep(t) MessageBeep(t) -#else -# define axbeep(t) -#endif - -static Label* _createLabel(std::string_view text, - std::string_view font, - float fontSize, - const Vec2& dimensions = Vec2::ZERO, - TextHAlignment hAlignment = TextHAlignment::LEFT, - TextVAlignment vAlignment = TextVAlignment::TOP) -{ - if (FileUtils::getInstance()->isFileExist(font)) - { - return Label::createWithTTF(text, font, fontSize, dimensions, hAlignment, vAlignment); - } - else - { - return Label::createWithSystemFont(text, font, fontSize, dimensions, hAlignment, vAlignment); - } -} - -static bool _checkVisibility(Node* theNode) -{ - // AX_ASSERT(theNode != NULL); - bool visible = false; - for (Node* ptr = theNode; (ptr != nullptr && (visible = ptr->isVisible())); ptr = ptr->getParent()) - ; - return visible; -} - -static bool _containsTouchPoint(ax::Node* target, ax::Touch* touch) -{ - assert(target != nullptr); - - ax::Point pt = target->convertTouchToNodeSpace(touch); - - const Vec2& size = target->getContentSize(); - - ax::Rect rc(0, 0, size.width, size.height); - - bool contains = (rc.containsPoint(pt)); - - // AXLOGD("check {:#x} coordinate:({}, {}), contains:{}", target, pt.x, pt.y, contains); - return contains; -} - -static bool engine_inj_containsPoint(ax::Node* target, const ax::Vec2& worldPoint) -{ - ax::Point pt = target->convertToNodeSpace(worldPoint); - - const Vec2& size = target->getContentSize(); - - ax::Rect rc(0, 0, size.width, size.height); - - bool contains = (rc.containsPoint(pt)); - - // AXLOGD("check {:#x} coordinate:({}, {}), contains:{}", target, pt.x, pt.y, contains); - return contains; -} - -static uint32_t engine_inj_c4b2dw(const Color32& value) -{ - auto rvalue = (uint32_t)value.a << 24 | (uint32_t)value.b << 16 | (uint32_t)value.g << 8 | (uint32_t)value.r; - return rvalue; -} - -static Sprite* engine_inj_create_lump(const Color32& color, int height, int width) -{ - unsigned int* pixels((unsigned int*)malloc(height * width * sizeof(unsigned int))); - - // Fill Pixels - uint32_t* ptr = pixels; - const Color32 fillColor = Color32::WHITE; - for (int i = 0; i < height * width; ++i) - { - ptr[i] = engine_inj_c4b2dw(fillColor); // 0xffffffff; - } - - // create cursor by pixels - Texture2D* texture = new Texture2D(); - - texture->initWithData(pixels, height * width * sizeof(unsigned int), rhi::PixelFormat::RGBA8, width, height); - - auto cursor = Sprite::createWithTexture(texture); - - cursor->setColor(color); - - texture->release(); - - free(pixels); - - return cursor; -} - -namespace ui -{ - -/// calculate the UTF-8 string's char count. -static int _truncateUTF8String(const char* text, int limit, int& nb) -{ - int n = 0; - char ch = 0; - nb = 0; - while ((ch = *text) != 0x0) - { - AX_BREAK_IF(!ch || n > limit); - - if (0x80 != (0xC0 & ch)) - { - ++n; - } - ++nb; - ++text; - } - return n; -} - -static void internalSetLableFont(Label* l, std::string_view fontName, float fontSize) -{ - if (FileUtils::getInstance()->isFileExist(fontName)) - { - TTFConfig config = l->getTTFConfig(); - config.fontFilePath = fontName; - config.fontSize = fontSize; - l->setTTFConfig(config); - } - else - { - l->setSystemFontName(fontName); - l->requestSystemFontRefresh(); - l->setSystemFontSize(fontSize); - } -} - -static float internalCalcStringWidth(std::string_view s, std::string_view fontName, float fontSize) -{ - auto label = _createLabel(std::string{s}, fontName, fontSize); - return label->getContentSize().width; -} - -static std::string internalUTF8MoveLeft(std::string_view utf8Text, int length /* default utf8Text.length() */) -{ - if (!utf8Text.empty() && length > 0) - { - - // get the delete byte number - int deleteLen = 1; // default, erase 1 byte - - while (length >= deleteLen && 0x80 == (0xC0 & utf8Text.at(length - deleteLen))) - { - ++deleteLen; - } - - return std::string{utf8Text.data(), static_cast(length - deleteLen)}; - } - else - { - return std::string{utf8Text}; - } -} - -static std::string internalUTF8MoveRight(std::string_view utf8Text, int length /* default utf8Text.length() */) -{ - if (!utf8Text.empty() && length >= 0) - { - - // get the delete byte number - size_t addLen = 1; // default, erase 1 byte - - while ((length + addLen) < utf8Text.size() && 0x80 == (0xC0 & utf8Text.at(length + addLen))) - { - ++addLen; - } - - return std::string{utf8Text.data(), static_cast(length + addLen)}; - } - else - { - return std::string{utf8Text}; - } -} - -////////////////////////////////////////////////////////////////////////// -// constructor and destructor -////////////////////////////////////////////////////////////////////////// -bool TextFieldEx::s_keyboardVisible = false; -TextFieldEx::TextFieldEx() - : _editable(true) - , _renderLabel(nullptr) - , _charCount(0) - , _inputText("") - , _placeHolder("") - , _colorText(Color32::WHITE) - , _colorSpaceHolder(Color32::GRAY) - , _secureTextEntry(false) - , _cursor(nullptr) - , _touchListener(nullptr) - , _kbdListener(nullptr) - , onTextModify(nullptr) - , onOpenIME(nullptr) - , onCloseIME(nullptr) - , _charLimit(std::numeric_limits::max()) - , _systemFontUsed(false) - , _fontSize(24) - , _insertPosUtf8(0) - , _insertPos(0) - , _cursorPos(0) - , _touchCursorControlEnabled(true) - , _cursorVisible(false) - , _continuousTouchDelayTimerID(nullptr) - , _continuousTouchDelayTime(0.6) -{} - -TextFieldEx::~TextFieldEx() -{ - if (_kbdListener != nullptr) - _eventDispatcher->removeEventListener(_kbdListener); - if (_touchListener != nullptr) - _eventDispatcher->removeEventListener(_touchListener); -} - -////////////////////////////////////////////////////////////////////////// -// static constructor -////////////////////////////////////////////////////////////////////////// -TextFieldEx* TextFieldEx::create(std::string_view placeholder, - std::string_view fontName, - float fontSize, - float cursorWidth, - const Color32& cursorColor) -{ - TextFieldEx* ret = new TextFieldEx(); - if (ret && ret->initWithPlaceHolder("", fontName, fontSize, cursorWidth, cursorColor)) - { - ret->autorelease(); - if (placeholder.size() > 0) - { - ret->setPlaceholderText(placeholder); - } - return ret; - } - AX_SAFE_DELETE(ret); - return nullptr; -} - -////////////////////////////////////////////////////////////////////////// -// initialize -////////////////////////////////////////////////////////////////////////// -bool TextFieldEx::initWithPlaceHolder(std::string_view placeholder, - std::string_view fontName, - float fontSize, - float cursorWidth, - const Color32& cursorColor) -{ - _placeHolder = placeholder; - - _renderLabel = - _createLabel(placeholder, fontName, fontSize, Vec2::ZERO, TextHAlignment::CENTER, TextVAlignment::CENTER); - _renderLabel->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT); - this->addChild(_renderLabel); - - _director->getScheduler()->runOnAxmolThread( - [this] { _renderLabel->setPosition(Point(0, this->getContentSize().height / 2)); }); - - __initCursor(fontSize, cursorWidth, cursorColor); - - _fontName = fontName; - _fontSize = fontSize; - _systemFontUsed = !FileUtils::getInstance()->isFileExist(fontName); - - return true; -} - -std::string_view TextFieldEx::getTextFontName() const -{ - return _fontName; -} - -void TextFieldEx::setTextFontName(std::string_view fontName) -{ - if (FileUtils::getInstance()->isFileExist(fontName)) - { - TTFConfig config = _renderLabel->getTTFConfig(); - config.fontFilePath = fontName; - config.fontSize = _fontSize; - _renderLabel->setTTFConfig(config); - _systemFontUsed = false; - _fontType = 1; - } - else - { - _renderLabel->setSystemFontName(fontName); - if (!_systemFontUsed) - { - _renderLabel->requestSystemFontRefresh(); - } - _renderLabel->setSystemFontSize(_fontSize); - _systemFontUsed = true; - _fontType = 0; - } - _fontName = fontName; - - using namespace std::string_view_literals; - _asteriskWidth = internalCalcStringWidth("*"sv, _fontName, _fontSize); -} - -void TextFieldEx::setTextFontSize(float size) -{ - if (_systemFontUsed) - { - _renderLabel->setSystemFontSize(size); - } - else - { - TTFConfig config = _renderLabel->getTTFConfig(); - config.fontSize = size; - _renderLabel->setTTFConfig(config); - } - - _fontSize = size; - - using namespace std::string_view_literals; - _asteriskWidth = internalCalcStringWidth("*"sv, _fontName, _fontSize); -} - -float TextFieldEx::getTextFontSize() const -{ - return _fontSize; -} - -void TextFieldEx::enableIME(Node* control) -{ - if (_touchListener != nullptr) - { - return; - } - _touchListener = EventListenerTouchOneByOne::create(); - - if (control == nullptr) - control = this; - - _touchListener->onTouchBegan = [control, this](Touch* touch, Event*) { - bool focus = (_checkVisibility(this) && _editable && this->_enabled && _containsTouchPoint(control, touch)); - - if (this->_continuousTouchDelayTimerID != nullptr) - { - stimer::kill(this->_continuousTouchDelayTimerID); - this->_continuousTouchDelayTimerID = nullptr; - } - - if (focus && _cursorVisible) - { - auto worldPoint = touch->getLocation(); - if (this->_continuousTouchCallback) - { - this->_continuousTouchDelayTimerID = stimer::delay( - this->_continuousTouchDelayTime, [=, this]() { this->_continuousTouchCallback(worldPoint); }); - } - } - return true; - }; - _touchListener->onTouchEnded = [control, this](Touch* touch, Event* e) { - if (this->_continuousTouchDelayTimerID != nullptr) - { - stimer::kill(this->_continuousTouchDelayTimerID); - this->_continuousTouchDelayTimerID = nullptr; - } - - bool focus = (_checkVisibility(this) && _editable && this->_enabled && _containsTouchPoint(control, touch)); - - if (focus) - { - if (!s_keyboardVisible || !_cursorVisible) - openIME(); - if (_touchCursorControlEnabled) - { - auto renderLabelPoint = _renderLabel->convertToNodeSpace(touch->getLocation()); - __moveCursorTo(renderLabelPoint.x); - } - } - else - { - closeIME(); - } - }; - - _eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this); - - /// enable use keyboard <- -> to move cursor. - _kbdListener = EventListenerKeyboard::create(); - _kbdListener->onKeyPressed = [this](EventKeyboard::KeyCode code, Event*) { - if (_cursorVisible) - { - switch (code) - { - case EventKeyboard::KeyCode::KEY_LEFT_ARROW: - this->__moveCursor(-1); - break; - case EventKeyboard::KeyCode::KEY_RIGHT_ARROW: - this->__moveCursor(1); - break; - case EventKeyboard::KeyCode::KEY_DELETE: - case EventKeyboard::KeyCode::KEY_KP_DELETE: - this->handleDeleteKeyEvent(); - break; - default:; - } - } - }; - - _eventDispatcher->addEventListenerWithSceneGraphPriority(_kbdListener, this); -} - -void TextFieldEx::disableIME(void) -{ - _eventDispatcher->removeEventListener(_kbdListener); - _eventDispatcher->removeEventListener(_touchListener); - - _kbdListener = nullptr; - _touchListener = nullptr; - closeIME(); -} - -Label* TextFieldEx::getRenderLabel() -{ - return _renderLabel; -} - -////////////////////////////////////////////////////////////////////////// -// IMEDelegate -////////////////////////////////////////////////////////////////////////// - -bool TextFieldEx::attachWithIME() -{ - bool ret = IMEDelegate::attachWithIME(); - if (ret) - { - // open keyboard - RenderView* renderView = _director->getRenderView(); - if (renderView) - renderView->setIMEKeyboardState(true); - } - return ret; -} - -bool TextFieldEx::detachWithIME() -{ - bool ret = IMEDelegate::detachWithIME(); - if (ret) - { - // close keyboard - RenderView* renderView = _director->getRenderView(); - if (renderView) - renderView->setIMEKeyboardState(false); - } - return ret; -} - -void TextFieldEx::keyboardDidShow(IMEKeyboardNotificationInfo& /*info*/) -{ - s_keyboardVisible = true; -} - -void TextFieldEx::keyboardDidHide(IMEKeyboardNotificationInfo& /*info*/) -{ - s_keyboardVisible = false; -} - -void TextFieldEx::openIME(void) -{ - AXLOGD("TextFieldEx:: openIME"); - this->attachWithIME(); - __updateCursorPosition(); - __showCursor(); - - if (this->onOpenIME) - this->onOpenIME(); -} - -void TextFieldEx::closeIME(void) -{ - AXLOGD("TextFieldEx:: closeIME"); - __hideCursor(); - this->detachWithIME(); - - if (this->onCloseIME) - this->onCloseIME(); -} - -bool TextFieldEx::canAttachWithIME() -{ - return true; //(_delegate) ? (! _delegate->onTextFieldAttachWithIME(this)) : true; -} - -bool TextFieldEx::canDetachWithIME() -{ - return true; //(_delegate) ? (! _delegate->onTextFieldDetachWithIME(this)) : true; -} - -void TextFieldEx::insertText(const char* text, size_t len) -{ - if (!_editable || !this->_enabled) - { - return; - } - - if (_charLimit > 0 && _charCount >= _charLimit) - { // regard zero as unlimited - axbeep(0); - return; - } - - int nb; - auto n = _truncateUTF8String(text, static_cast(_charLimit - _charCount), nb); - - std::string insert(text, nb); - - // insert \n means input end - auto pos = insert.find('\n'); - if (insert.npos != pos) - { - len = pos; - insert.erase(pos); - } - - if (len > 0) - { - // if (_delegate && _delegate->onTextFieldInsertText(this, insert.c_str(), len)) - //{ - // // delegate doesn't want to insert text - // return; - // } - - _charCount += n; // _calcCharCount(insert.c_str()); - std::string sText(_inputText); - sText.insert(_insertPos, insert); // original is: sText.append(insert); - - // bool needUpdatePos - this->setString(sText); - while (n-- > 0) - __moveCursor(1); - - // this->contentDirty = true; - // __updateCursorPosition(); - - if (this->onTextModify) - this->onTextModify(); - } - - if (insert.npos == pos) - { - return; - } - - // '\n' inserted, let delegate process first - /*if (_delegate && _delegate->onTextFieldInsertText(this, "\n", 1)) - { - return; - }*/ - - // if delegate hasn't processed, detach from IME by default - this->closeIME(); -} - -void TextFieldEx::deleteBackward(size_t numChars) -{ - if (!_editable || !this->_enabled || 0 == _charCount) - { - axbeep(0); - return; - } - - size_t len = _inputText.length(); - if (0 == len || _insertPos == 0) - { - axbeep(0); - // there is no string - // __updateCursorPosition(); - return; - } - - // Length of characters to delete is based on input editor, but the actual - // length of the displayed text may be less - numChars = std::min(numChars, len); - - size_t totalDeleteLen = 0; - for (auto i = 0; i < numChars; ++i) - { - // get the delete byte number - size_t deleteLen = 1; // default, erase 1 byte - - // Calculate the actual number of bytes to delete for a specific character - while (0x80 == (0xC0 & _inputText.at(_insertPos - totalDeleteLen - deleteLen))) - { - ++deleteLen; - } - totalDeleteLen += deleteLen; - } - - // if (_delegate && _delegate->onTextFieldDeleteBackward(this, _inputText.c_str() + len - deleteLen, - // static_cast(deleteLen))) - //{ - // // delegate doesn't want to delete backwards - // return; - // } - - // if all text deleted, show placeholder string - if (len <= totalDeleteLen) - { - __moveCursor(-1); - - _inputText.clear(); - _charCount = 0; - _renderLabel->setTextColor(_colorSpaceHolder); - _renderLabel->setString(_placeHolder); - - // __updateCursorPosition(); - - // this->contentDirty = true; - - if (this->onTextModify) - this->onTextModify(); - return; - } - - // set new input text - std::string text = _inputText; // (inputText.c_str(), len - deleteLen); - text.erase(_insertPos - totalDeleteLen, totalDeleteLen); - - __moveCursor(-1); - - this->setString(text); - - //__updateCursorPosition(); - // __moveCursor(-1); - - if (this->onTextModify) - this->onTextModify(); -} - -void TextFieldEx::handleDeleteKeyEvent() -{ - if (!_editable || !this->_enabled || 0 == _charCount) - { - axbeep(0); - return; - } - - size_t len = _inputText.length(); - if (0 == len || _insertPosUtf8 == _charCount) - { - axbeep(0); - // there is no string - // __updateCursorPosition(); - return; - } - - // get the delete byte number - size_t deleteLen = 1; // default, erase 1 byte - - while ((_inputText.length() > _insertPos + deleteLen) && 0x80 == (0xC0 & _inputText.at(_insertPos + deleteLen))) - { - ++deleteLen; - } - - // if (_delegate && _delegate->onTextFieldDeleteBackward(this, _inputText.c_str() + len - deleteLen, - // static_cast(deleteLen))) - //{ - // // delegate doesn't wan't to delete backwards - // return; - // } - - // if all text deleted, show placeholder string - if (len <= deleteLen) - { - _inputText.clear(); - _charCount = 0; - _renderLabel->setTextColor(_colorSpaceHolder); - _renderLabel->setString(_placeHolder); - - __updateCursorPosition(); - - // this->contentDirty = true; - - if (this->onTextModify) - this->onTextModify(); - return; - } - - // set new input text - std::string text = _inputText; // (inputText.c_str(), len - deleteLen); - text.erase(_insertPos, deleteLen); - - // __moveCursor(-1); - - this->setString(text); - - if (this->onTextModify) - this->onTextModify(); -} - -std::string_view TextFieldEx::getContentText() -{ - return _inputText; -} - -void TextFieldEx::setTextColor(const Color32& color) -{ - _colorText = color; - if (!_inputText.empty()) - _renderLabel->setTextColor(_colorText); -} - -const Color32& TextFieldEx::getTextColor(void) const -{ - return _colorText; -} - -void TextFieldEx::setCursorColor(const Color32& color) -{ - _cursor->setColor(color); -} - -const Color32& TextFieldEx::getCursorColor(void) const -{ - return _cursor->getColor(); -} - -const Color32& TextFieldEx::getPlaceholderColor() const -{ - return _colorSpaceHolder; -} - -void TextFieldEx::setPlaceholderColor(const Color32& color) -{ - _colorSpaceHolder = color; - if (_inputText.empty()) - _renderLabel->setTextColor(color); -} - -////////////////////////////////////////////////////////////////////////// -// properties -////////////////////////////////////////////////////////////////////////// - -// input text property -void TextFieldEx::setString(std::string_view text) -{ - static char bulletString[] = {(char)0xe2, (char)0x80, (char)0xa2, (char)0x00}; - - _inputText = text; - - std::string secureText; - - std::string* displayText = &_inputText; - - if (!_inputText.empty()) - { - if (_secureTextEntry) - { - size_t length = _inputText.length(); - displayText = &secureText; - - while (length > 0) - { - displayText->append(bulletString); - --length; - } - } - } - - // if there is no input text, display placeholder instead - if (_inputText.empty()) - { - _renderLabel->setTextColor(_colorSpaceHolder); - _renderLabel->setString(_placeHolder); - } - else - { - _renderLabel->setTextColor(_colorText); - _renderLabel->setString(*displayText); - } - - bool bInsertAtEnd = (_insertPosUtf8 == _charCount); - - _charCount = text_utils::countUTF8Chars(_inputText); - - if (bInsertAtEnd) - { - _insertPosUtf8 = static_cast(_charCount); - _insertPos = static_cast(_inputText.length()); - _cursorPos = static_cast(displayText->length()); - } -} - -void TextFieldEx::updateContentSize(void) -{ - this->setContentSize(_renderLabel->getContentSize()); -} - -std::string_view TextFieldEx::getString() const -{ - return _inputText; -} - -// place holder text property -void TextFieldEx::setPlaceholderText(std::string_view text) -{ - _placeHolder = text; - if (_inputText.empty()) - { - _renderLabel->setTextColor(_colorSpaceHolder); - _renderLabel->setString(_placeHolder); - } -} - -std::string_view TextFieldEx::getPlaceholderText() const -{ - return _placeHolder; -} - -// secureTextEntry -void TextFieldEx::setPasswordEnabled(bool value) -{ - if (_secureTextEntry != value) - { - _secureTextEntry = value; - this->setString(this->getString()); - __updateCursorPosition(); - } -} - -bool TextFieldEx::isPasswordEnabled() const -{ - return _secureTextEntry; -} - -void TextFieldEx::setEnabled(bool bEnabled) -{ - if (this->_enabled != bEnabled) - { - if (!bEnabled) - { - this->closeIME(); - } - this->_enabled = bEnabled; - } -} - -int TextFieldEx::getFontType() const -{ - return _fontType; -} - -void TextFieldEx::__initCursor(int height, int width, const Color32& color) -{ - _cursor = engine_inj_create_lump(Color32(color), height, width); - - this->addChild(_cursor); - - _cursor->setPosition(Point(0, this->getContentSize().height / 2)); - // nodes_layout::setNodeLB(_cursor, ax::Point::ZERO); - - __hideCursor(); - - __updateCursorPosition(); -} - -void TextFieldEx::__showCursor(void) -{ - if (_cursor) - { - _cursorVisible = true; - _cursor->setVisible(true); - _cursor->runAction(RepeatForever::create(Blink::create(1, 1))); - } -} - -void TextFieldEx::__hideCursor(void) -{ - if (_cursor) - { - _cursor->setVisible(false); - _cursorVisible = false; - _cursor->stopAllActions(); - } -} - -void TextFieldEx::__updateCursorPosition(void) -{ - if (_cursor && _insertPosUtf8 == _charCount) - { - if (0 == this->getCharCount()) - { - _cursor->setPosition(Point(0, this->getContentSize().height / 2)); - } - else - { - _cursor->setPosition(Point(_renderLabel->getContentSize().width, this->getContentSize().height / 2)); - } - } -} - -void TextFieldEx::__moveCursor(int direction) -{ - auto newOffset = _insertPosUtf8 + direction; - - if (newOffset > 0 && newOffset <= _charCount) - { - - std::string_view displayText; - if (!_secureTextEntry) - displayText = this->getString(); - else if (!_inputText.empty()) - displayText = _renderLabel->getString(); - - if (direction < 0) - { - _insertPos = static_cast(internalUTF8MoveLeft(_inputText, _insertPos).size()); - - auto s = internalUTF8MoveLeft(displayText, _cursorPos); - - auto width = internalCalcStringWidth(s, _fontName, _fontSize); - _cursor->setPosition(Point(width, this->getContentSize().height / 2)); - _cursorPos = static_cast(s.length()); - } - else - { - _insertPos = static_cast(internalUTF8MoveRight(_inputText, _insertPos).size()); - - auto s = internalUTF8MoveRight(displayText, _cursorPos); - auto width = internalCalcStringWidth(s, _fontName, _fontSize); - _cursor->setPosition(Point(width, this->getContentSize().height / 2)); - _cursorPos = static_cast(s.length()); - } - - _insertPosUtf8 = newOffset; - } - else if (newOffset == 0) - { - _cursor->setPosition(Point(0, this->getContentSize().height / 2)); - _insertPosUtf8 = newOffset; - _insertPos = 0; - _cursorPos = 0; - } - else - { - // MessageBeep(0); - } -} - -void TextFieldEx::__moveCursorTo(float x) -{ // test - // normalized x - float normalizedX = 0; - - std::string_view displayText; - if (!_secureTextEntry) - { - displayText = _inputText; - } - else - { - if (!_inputText.empty()) - { - displayText = _renderLabel->getString(); - } - } - - int length = static_cast(displayText.length()); - int n = static_cast(_charCount); // UTF8 char counter - - int insertWhere = 0; - int insertWhereUtf8 = 0; - while (length > 0) - { - auto checkX = internalCalcStringWidth(displayText, _fontName, _fontSize); - if (x >= checkX) - { - insertWhere = length; - insertWhereUtf8 = n; - normalizedX = checkX; - break; - } - - // clamp backward - size_t backwardLen = 1; // default, erase 1 byte - while (0x80 == (0xC0 & displayText.at(displayText.length() - backwardLen))) - { - ++backwardLen; - } - - --n; - displayText.remove_suffix(backwardLen); - - length -= backwardLen; - } - - _insertPos = !_secureTextEntry ? insertWhere : insertWhereUtf8; - _cursorPos = insertWhere; - _insertPosUtf8 = insertWhereUtf8; - _cursor->setPosition(Point(normalizedX, this->getContentSize().height / 2)); -} -}; // namespace ui - -} // namespace ax diff --git a/axmol/ui/UITextFieldEx.h b/axmol/ui/UITextFieldEx.h deleted file mode 100644 index 05ef2eba3b2d..000000000000 --- a/axmol/ui/UITextFieldEx.h +++ /dev/null @@ -1,214 +0,0 @@ -/**************************************************************************** -Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md). - -https://axmol.dev/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ - -#pragma once - -#include "axmol/ui/UIWidget.h" -#include "axmol/base/IMEDelegate.h" -#include "axmol/base/SimpleTimer.h" -#include "axmol/2d/Label.h" -#include "axmol/base/EventListenerKeyboard.h" - -namespace ax -{ - -namespace ui -{ - -/** -@brief The ui::TextFieldEx, better design, better cursor support than ui::TextField -will replace ui::TextField, currently, ui::TextField, 2d/TextFieldTTF were maked as deprecated -*/ -class AX_DLL TextFieldEx : public Widget, public IMEDelegate -{ -public: - /** - */ - TextFieldEx(); - /** - * @lua NA - */ - virtual ~TextFieldEx(); - - static TextFieldEx* create(std::string_view placeholder, - std::string_view fontName, - float fontSize, - float cursorWidth = 2, - const Color32& color = Color32::WHITE); - - bool initWithPlaceHolder(std::string_view placeholder, - std::string_view fontName, - float fontSize, - float cursorWidth = 2, - const Color32& color = Color32::WHITE); - - void enableIME(Node* control); - void disableIME(void); - - Label* getRenderLabel(); - - inline int getCharCount() const { return static_cast(_charCount); }; - - virtual void setPlaceholderColor(const Color32& color); - virtual const Color32& getPlaceholderColor() const; - - virtual void setTextColor(const Color32& textColor); - virtual const Color32& getTextColor(void) const; - - void setCursorColor(const Color32& color); - const Color32& getCursorColor(void) const; - - // input text property - virtual void setString(std::string_view text); - virtual std::string_view getString() const; - - // Continuous touch event trigger support. - void setContinuousTouchDelayTime(float delay) { _continuousTouchDelayTime = delay; } - float getContinuousTouchDelayTime() const { return _continuousTouchDelayTime; } - void setContinuousTouchCallback(std::function callback) - { - _continuousTouchCallback = std::move(callback); - } - - // place holder text property - // place holder text displayed when there is no text in the text field. - virtual void setPlaceholderText(std::string_view text); - virtual std::string_view getPlaceholderText(void) const; - - virtual void setPasswordEnabled(bool value); - virtual bool isPasswordEnabled() const; - - bool empty(void) const { return _charCount == 0 || _inputText.empty(); } - - void setEnabled(bool bEnabled) override; - - void setEditable(bool bEditable) { _editable = bEditable; } - bool isEditable(void) const { return _editable; } - - void setMaxLength(int maxLength) { setCharLimit(maxLength); } - - int getFontType() const; - - /// fonts - void setTextFontSize(float size); - float getTextFontSize() const; - void setTextFontName(std::string_view fontName); - std::string_view getTextFontName() const; - - AX_SYNTHESIZE(size_t, _charLimit, CharLimit); - - bool isSystemFont(void) const { return _systemFontUsed; } - -public: - std::function onTextModify; - std::function onOpenIME; - std::function onCloseIME; - // IMEDelegate interface - ////////////////////////////////////////////////////////////////////////// - void openIME(void); - void closeIME(void); - - void insertText(const char* text, size_t len) override; - -protected: - ////////////////////////////////////////////////////////////////////////// - - bool canAttachWithIME() override; - bool canDetachWithIME() override; - - void deleteBackward(size_t numChars) override; - std::string_view getContentText() override; - - void handleDeleteKeyEvent(); - - /** - @brief Open keyboard and receive input text. - */ - bool attachWithIME() override; - - /** - @brief End text input and close keyboard. - */ - bool detachWithIME() override; - - void keyboardDidShow(IMEKeyboardNotificationInfo& /*info*/) override; - void keyboardDidHide(IMEKeyboardNotificationInfo& /*info*/) override; - - void updateContentSize(void); - - void __initCursor(int height, int width = 6, const Color32& color = Color32::WHITE); - void __showCursor(void); - void __hideCursor(void); - void __updateCursorPosition(void); - - void __moveCursor(int direction); - - void __moveCursorTo(float x); - -protected: - bool _systemFontUsed; - std::string _fontName; - float _fontSize; - - bool _editable; - - Label* _renderLabel; - - size_t _charCount; - std::string _inputText; - - std::string _placeHolder; - Color32 _colorSpaceHolder; - Color32 _colorText; - - bool _secureTextEntry; - - Sprite* _cursor; - bool _cursorVisible; - - int _insertPosUtf8; - int _insertPos; // The actual input content insertPos, step: bytes - int _cursorPos; // The cursor normalzed pos, - - EventListenerTouchOneByOne* _touchListener; - EventListenerKeyboard* _kbdListener; - - bool _touchCursorControlEnabled; - float _asteriskWidth; - - int _fontType; - - ax::stimer::TIMER_ID _continuousTouchDelayTimerID; - float _continuousTouchDelayTime; - std::function _continuousTouchCallback; - - static bool s_keyboardVisible; -}; - -// end of input group -/// @} - -}; // namespace ui - -} // namespace ax diff --git a/axmol/ui/UIVBox.cpp b/axmol/ui/VBox.cpp similarity index 95% rename from axmol/ui/UIVBox.cpp rename to axmol/ui/VBox.cpp index b744b8c73a8b..3f11e54e8314 100644 --- a/axmol/ui/UIVBox.cpp +++ b/axmol/ui/VBox.cpp @@ -24,7 +24,7 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIVBox.h" +#include "axmol/ui/VBox.h" namespace ax { @@ -62,9 +62,9 @@ VBox* VBox::create(const Vec2& size) bool VBox::init() { - if (Layout::init()) + if (LayoutGroup::init()) { - setLayoutType(Layout::Type::VERTICAL); + setLayoutType(LayoutGroup::Type::VERTICAL); return true; } return false; diff --git a/axmol/ui/UIVBox.h b/axmol/ui/VBox.h similarity index 96% rename from axmol/ui/UIVBox.h rename to axmol/ui/VBox.h index b03f551a773f..3922bbbce725 100644 --- a/axmol/ui/UIVBox.h +++ b/axmol/ui/VBox.h @@ -26,7 +26,7 @@ #pragma once -#include "axmol/ui/UILayout.h" +#include "axmol/ui/LayoutGroup.h" #include "axmol/ui/GUIExport.h" namespace ax @@ -43,7 +43,7 @@ namespace ui * VBox is just a convenient wrapper class for vertical layout type. * VBox lays out its children in a single vertical column. */ -class AX_GUI_DLL VBox : public Layout +class AX_GUI_DLL VBox : public LayoutGroup { public: /** diff --git a/axmol/ui/UIMediaPlayer.cpp b/axmol/ui/VideoPlayer.cpp similarity index 73% rename from axmol/ui/UIMediaPlayer.cpp rename to axmol/ui/VideoPlayer.cpp index aae5eefa9309..f99b47698f61 100644 --- a/axmol/ui/UIMediaPlayer.cpp +++ b/axmol/ui/VideoPlayer.cpp @@ -24,20 +24,20 @@ THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIMediaPlayer.h" +#include "axmol/ui/VideoPlayer.h" // Now, common implementation based on redesigned MediaEngine is enable for windows and macOS -#if defined(AX_ENABLE_MEDIA) +#if defined(AX_ENABLE_VIDEO) # include # include # include # include "axmol/base/Director.h" -# include "axmol/base/EventListenerKeyboard.h" +# include "axmol/base/KeyboardEventListener.h" # include "axmol/platform/FileUtils.h" # include "axmol/ui/UIHelper.h" # include "axmol/media/MediaEngine.h" -# include "axmol/ui/UIButton.h" -# include "axmol/ui/UILayout.h" +# include "axmol/ui/Button.h" +# include "axmol/ui/LayoutGroup.h" # include "axmol/tlx/byte_buffer.hpp" //----------------------------------------------------------------------------------------------------------- @@ -282,7 +282,7 @@ struct PrivateVideoContext _renderFrameFunc(const_cast(frame)); } - void rescaleTo(MediaPlayer* videoView) + void rescaleTo(VideoPlayer* videoView) { auto& videoSize = _vrender->getContentSize(); if (videoSize.x > 0 && videoSize.y > 0) @@ -294,29 +294,29 @@ struct PrivateVideoContext std::swap(viewSize.x, viewSize.y); _vrender->setRotation(_vpixelDesc._rotation); - if (!videoView->isKeepAspectRatioEnabled()) + if (!videoView->isKeepAspectRatio()) { const auto scale = Vec2(viewSize.x / videoSize.x, viewSize.y / videoSize.y); _vrender->setScale(scale.x, scale.y); - auto* mediaController = videoView->getMediaController(); - if (mediaController) + auto* videoController = videoView->getVideoController(); + if (videoController) { - if (mediaController->getOrientation() == MediaController::Orientation::RotatedLeft) + if (videoController->getOrientation() == VideoController::Orientation::RotatedLeft) { - mediaController->setContentSize(videoSize * + videoController->setContentSize(videoSize * Vec2(viewSize.y / videoSize.x, viewSize.x / videoSize.y)); - mediaController->setRotation(-90); + videoController->setRotation(-90); } - else if (mediaController->getOrientation() == MediaController::Orientation::RotatedRight) + else if (videoController->getOrientation() == VideoController::Orientation::RotatedRight) { - mediaController->setContentSize(videoSize * + videoController->setContentSize(videoSize * Vec2(viewSize.y / videoSize.x, viewSize.x / videoSize.y)); - mediaController->setRotation(90); + videoController->setRotation(90); } else { - mediaController->setContentSize(videoSize * scale); + videoController->setContentSize(videoSize * scale); } } } @@ -326,22 +326,22 @@ struct PrivateVideoContext _vrender->setScale(aspectRatio); - auto* mediaController = videoView->getMediaController(); - if (mediaController) + auto* videoController = videoView->getVideoController(); + if (videoController) { - if (mediaController->getOrientation() == MediaController::Orientation::RotatedLeft) + if (videoController->getOrientation() == VideoController::Orientation::RotatedLeft) { - mediaController->setContentSize(Vec2(videoSize.y * aspectRatio, videoSize.x * aspectRatio)); - mediaController->setRotation(-90); + videoController->setContentSize(Vec2(videoSize.y * aspectRatio, videoSize.x * aspectRatio)); + videoController->setRotation(-90); } - else if (mediaController->getOrientation() == MediaController::Orientation::RotatedRight) + else if (videoController->getOrientation() == VideoController::Orientation::RotatedRight) { - mediaController->setContentSize(Vec2(videoSize.y * aspectRatio, videoSize.x * aspectRatio)); - mediaController->setRotation(90); + videoController->setContentSize(Vec2(videoSize.y * aspectRatio, videoSize.x * aspectRatio)); + videoController->setRotation(90); } else { - mediaController->setContentSize(videoSize * aspectRatio); + videoController->setContentSize(videoSize * aspectRatio); } } } @@ -389,7 +389,7 @@ constexpr auto TIMELINE_BAR_HEIGHT = 12.f; RefPtr g_mediaControlsTexture = nullptr; -enum class MediaControlButtonId +enum class VideoControlButtonId { Play, Stop, @@ -399,9 +399,9 @@ enum class MediaControlButtonId TimelineSliderButton }; -std::map g_mediaControlTextureRegions; +std::map g_mediaControlTextureRegions; -void createMediaControlTexture() +void createVideoControlTexture() { if (g_mediaControlsTexture) return; @@ -486,13 +486,13 @@ void createMediaControlTexture() drawNode->drawSolidCircle(middle, panelW / 2, 0, 180, Color::WHITE); }; - std::map> items = { - {MediaControlButtonId::Play, DrawPlay}, - {MediaControlButtonId::Stop, DrawStop}, - {MediaControlButtonId::Pause, DrawPause}, - {MediaControlButtonId::EnterFullscreen, DrawEnterFullscreen}, - {MediaControlButtonId::ExitFullscreen, DrawExitFullScreen}, - {MediaControlButtonId::TimelineSliderButton, DrawSliderControlButton}}; + std::map> items = { + {VideoControlButtonId::Play, DrawPlay}, + {VideoControlButtonId::Stop, DrawStop}, + {VideoControlButtonId::Pause, DrawPause}, + {VideoControlButtonId::EnterFullscreen, DrawEnterFullscreen}, + {VideoControlButtonId::ExitFullscreen, DrawExitFullScreen}, + {VideoControlButtonId::TimelineSliderButton, DrawSliderControlButton}}; auto numItems = static_cast(items.size()); auto totalWidth = utils::nextPOT(numItems * panelW + (numItems - 1) * gap + (border * 2)); @@ -541,14 +541,14 @@ void createMediaControlTexture() static const float ZOOM_ACTION_TIME_STEP = 0.05f; -void MediaController::setOrientation(Orientation orientation) +void VideoController::setOrientation(Orientation orientation) { _orientation = orientation; } -MediaPlayerControl* MediaPlayerControl::create(SpriteFrame* frame) +VideoPlayerControl* VideoPlayerControl::create(SpriteFrame* frame) { - auto* widget = new MediaPlayerControl(); + auto* widget = new VideoPlayerControl(); if (widget->init(frame)) { widget->autorelease(); @@ -558,12 +558,12 @@ MediaPlayerControl* MediaPlayerControl::create(SpriteFrame* frame) return nullptr; } -MediaPlayerControl::~MediaPlayerControl() +VideoPlayerControl::~VideoPlayerControl() { AX_SAFE_RELEASE(_overlay); } -bool MediaPlayerControl::init(SpriteFrame* frame) +bool VideoPlayerControl::init(SpriteFrame* frame) { if (!Button::init("")) { @@ -574,34 +574,22 @@ bool MediaPlayerControl::init(SpriteFrame* frame) { _overlay = Sprite::createWithSpriteFrame(frame); AX_SAFE_RETAIN(_overlay); - auto spriteSize = _overlay->getContentSize(); - setContentSize(spriteSize); _overlay->setAnchorPoint(Vec2::ANCHOR_MIDDLE); _overlay->setPosition(_contentSize.width * 0.5f, _contentSize.height * 0.5f); addProtectedChild(_overlay, -2, -1); - if (!_ignoreSize && _customSize.equals(Vec2::ZERO)) + if (!_autoSize && _customSize.equals(Vec2::ZERO)) { _customSize = _overlay->getContentSize(); } this->updateChildrenDisplayedRGBA(); - if (_unifySize) - { - if (!_scale9Enabled) - { - updateContentSizeWithTextureSize(spriteSize); - } - } - else - { - updateContentSizeWithTextureSize(spriteSize); - } + updateContentSize(); } return true; } -void MediaPlayerControl::onSizeChanged() +void VideoPlayerControl::onSizeChanged() { Button::onSizeChanged(); if (_overlay) @@ -610,14 +598,14 @@ void MediaPlayerControl::onSizeChanged() } } -Vec2 MediaPlayerControl::getVirtualRendererSize() const +Vec2 VideoPlayerControl::resolvePreferredSize(const Vec2& /*sizeHint*/) const { - if (_unifySize) + if (!_autoSize) { return this->getNormalSize(); } - if (nullptr != _overlay) + if (_overlay) { Vec2 overlaySize = _overlay->getContentSize(); if (!_normalTextureLoaded) @@ -628,7 +616,7 @@ Vec2 MediaPlayerControl::getVirtualRendererSize() const return _normalTextureSize; } -Vec2 MediaPlayerControl::getNormalSize() const +Vec2 VideoPlayerControl::getNormalSize() const { if (_overlay) { @@ -638,14 +626,14 @@ Vec2 MediaPlayerControl::getNormalSize() const return Button::getNormalSize(); } -void MediaPlayerControl::onPressStateChangedToNormal() +void VideoPlayerControl::onPressStateChangedToNormal() { Button::onPressStateChangedToNormal(); if (nullptr != _overlay) { _overlay->stopAllActions(); - if (_unifySize) + if (!_autoSize) { Action* zoomTitleAction = ScaleTo::create(ZOOM_ACTION_TIME_STEP, 1.0f, 1.0f); _overlay->runAction(zoomTitleAction); @@ -658,7 +646,7 @@ void MediaPlayerControl::onPressStateChangedToNormal() } } -void MediaPlayerControl::onPressStateChangedToPressed() +void VideoPlayerControl::onPressStateChangedToPressed() { Button::onPressStateChangedToPressed(); if (nullptr != _overlay) @@ -669,7 +657,7 @@ void MediaPlayerControl::onPressStateChangedToPressed() } } -void MediaPlayerControl::onPressStateChangedToDisabled() +void VideoPlayerControl::onPressStateChangedToDisabled() { Button::onPressStateChangedToDisabled(); if (nullptr != _overlay) @@ -678,13 +666,13 @@ void MediaPlayerControl::onPressStateChangedToDisabled() } } -BasicMediaController::BasicMediaController(MediaPlayer* player) - : MediaController(player), _timelineBarHeight(TIMELINE_BAR_HEIGHT) +DefaultVideoController::DefaultVideoController(VideoPlayer* player) + : VideoController(player), _timelineBarHeight(TIMELINE_BAR_HEIGHT) {} -BasicMediaController* BasicMediaController::create(MediaPlayer* mediaPlayer) +DefaultVideoController* DefaultVideoController::create(VideoPlayer* videoPlayer) { - auto* widget = new BasicMediaController(mediaPlayer); + auto* widget = new DefaultVideoController(videoPlayer); if (widget->init()) { widget->autorelease(); @@ -694,26 +682,26 @@ BasicMediaController* BasicMediaController::create(MediaPlayer* mediaPlayer) return nullptr; } -bool BasicMediaController::init() +bool DefaultVideoController::init() { if (!Widget::init()) { return false; } - setTouchEnabled(true); + setPointerEnabled(true); setCascadeOpacityEnabled(false); updateControllerState(); - if (_mediaPlayer) + if (_videoPlayer) { - setContentSize(_mediaPlayer->getContentSize()); + setContentSize(_videoPlayer->getContentSize()); } return true; } -void BasicMediaController::initRenderer() +void DefaultVideoController::initRenderNode() { - Widget::initRenderer(); + Widget::initRenderNode(); // scheduleOnce is used to create the controls on the next update // loop. This is a work-around for a RenderTexture issue @@ -721,12 +709,12 @@ void BasicMediaController::initRenderer() // on Apple platforms/Metal renderer backend scheduleOnce([this](float) { createControls(); - updateControlsForContentSize(_mediaPlayer->getContentSize()); + updateControlsForContentSize(_videoPlayer->getContentSize()); updateControllerState(); }, 0.f, "__create_video_controls"sv); } -void BasicMediaController::onPressStateChangedToPressed() +void DefaultVideoController::onPressStateChangedToPressed() { _lastTouch = std::chrono::steady_clock::now(); @@ -739,7 +727,7 @@ void BasicMediaController::onPressStateChangedToPressed() _mediaOverlay->runAction(Sequence::create(FadeTo::create(0.5f, 150), nullptr)); _controlPanel->runAction(Sequence::create(FadeIn::create(0.5f), CallFunc::create([this] { - if (_controlPanel->isScheduled("__media_controller_fader"sv)) + if (_controlPanel->isScheduled("__video_controller_fader"sv)) return; _controlPanel->schedule([this](float) { @@ -747,76 +735,76 @@ void BasicMediaController::onPressStateChangedToPressed() auto deltaTime = std::chrono::duration_cast(now - _lastTouch); if (deltaTime > std::chrono::milliseconds{2500}) { - _controlPanel->unschedule("__media_controller_fader"sv); + _controlPanel->unschedule("__video_controller_fader"sv); _controlPanel->runAction(Sequence::create(FadeOut::create(0.5f), nullptr)); _mediaOverlay->runAction(Sequence::create(FadeOut::create(0.5f), nullptr)); } - }, 1.f, "__media_controller_fader"sv); + }, 1.f, "__video_controller_fader"sv); }), nullptr)); } -void BasicMediaController::setContentSize(const Vec2& contentSize) +void DefaultVideoController::setContentSize(const Vec2& contentSize) { Widget::setContentSize(contentSize); updateControlsForContentSize(contentSize); updateControllerState(); } -void BasicMediaController::update(float delta) +void DefaultVideoController::update(float delta) { Widget::update(delta); updateControls(); } -void BasicMediaController::onEnter() +void DefaultVideoController::onEnter() { Widget::onEnter(); scheduleUpdate(); } -void BasicMediaController::setGlobalZOrder(float globalZOrder) +void DefaultVideoController::setGlobalZOrder(float globalZOrder) { Widget::setGlobalZOrder(globalZOrder); updateControlsGlobalZ(globalZOrder); } -void BasicMediaController::updateControllerState() +void DefaultVideoController::updateControllerState() { - if (!_mediaPlayer || !_controlsReady) + if (!_videoPlayer || !_controlsReady) return; - auto state = _mediaPlayer->getState(); - if (state == MediaPlayer::MediaState::LOADING || state == MediaPlayer::MediaState::CLOSED || - state == MediaPlayer::MediaState::ERROR) + auto state = _videoPlayer->getState(); + if (state == VideoPlayer::State::LOADING || state == VideoPlayer::State::CLOSED || + state == VideoPlayer::State::ERROR) { _playButton->setVisible(false); _pauseButton->setVisible(false); _stopButton->setVisible(false); _timelineTotal->setVisible(false); - _fullScreenExitButton->setVisible(false); - _fullScreenEnterButton->setVisible(false); + _fullscreenExitButton->setVisible(false); + _fullscreenEnterButton->setVisible(false); } else { _timelineTotal->setVisible(true); - _fullScreenExitButton->setVisible(_mediaPlayer->isFullScreenEnabled()); - _fullScreenEnterButton->setVisible(!_mediaPlayer->isFullScreenEnabled()); + _fullscreenExitButton->setVisible(_videoPlayer->isFullscreen()); + _fullscreenEnterButton->setVisible(!_videoPlayer->isFullscreen()); switch (state) { - case MediaPlayer::MediaState::PLAYING: + case VideoPlayer::State::PLAYING: _playButton->setVisible(false); _pauseButton->setVisible(true); _stopButton->setVisible(true); break; - case MediaPlayer::MediaState::PAUSED: + case VideoPlayer::State::PAUSED: _playButton->setVisible(true); _pauseButton->setVisible(false); _stopButton->setVisible(true); break; - case MediaPlayer::MediaState::STOPPED: - case MediaPlayer::MediaState::FINISHED: + case VideoPlayer::State::STOPPED: + case VideoPlayer::State::FINISHED: _playButton->setVisible(true); _pauseButton->setVisible(false); _stopButton->setVisible(false); @@ -826,7 +814,7 @@ void BasicMediaController::updateControllerState() } } -void BasicMediaController::setTimelineBarHeight(float height) +void DefaultVideoController::setTimelineBarHeight(float height) { _timelineBarHeight = height; if (_timelineBarHeight < TIMELINE_BAR_HEIGHT) @@ -835,9 +823,9 @@ void BasicMediaController::setTimelineBarHeight(float height) updateControlsForContentSize(getContentSize()); } -void BasicMediaController::createControls() +void DefaultVideoController::createControls() { - createMediaControlTexture(); + createVideoControlTexture(); // Check if controls are already created if (_controlsReady) @@ -872,85 +860,80 @@ void BasicMediaController::createControls() _primaryButtonPanel->setScale(1 / scale); _controlPanel->addProtectedChild(_primaryButtonPanel); - _playButton = MediaPlayerControl::create(SpriteFrame::createWithTexture( - g_mediaControlsTexture, g_mediaControlTextureRegions[MediaControlButtonId::Play])); + _playButton = VideoPlayerControl::create(SpriteFrame::createWithTexture( + g_mediaControlsTexture, g_mediaControlTextureRegions[VideoControlButtonId::Play])); _playButton->addClickEventListener([this](Object* ref) { if (_controlPanel->getOpacity() <= 50) return; _playRate = 1.f; - _mediaPlayer->setPlayRate(_playRate); - _mediaPlayer->play(); + _videoPlayer->setPlayRate(_playRate); + _videoPlayer->play(); updateControllerState(); }); - _playButton->setSwallowTouches(false); _playButton->setPositionNormalized(Vec2(0.25f, 0.5f)); _playButton->setCascadeOpacityEnabled(true); _playButton->setVisible(false); _primaryButtonPanel->addProtectedChild(_playButton, 1, -1); - _stopButton = MediaPlayerControl::create(SpriteFrame::createWithTexture( - g_mediaControlsTexture, g_mediaControlTextureRegions[MediaControlButtonId::Stop])); + _stopButton = VideoPlayerControl::create(SpriteFrame::createWithTexture( + g_mediaControlsTexture, g_mediaControlTextureRegions[VideoControlButtonId::Stop])); _stopButton->addClickEventListener([this](Object* ref) { if (_controlPanel->getOpacity() <= 50) return; _playRate = 1.f; - _mediaPlayer->setPlayRate(_playRate); - _mediaPlayer->stop(); + _videoPlayer->setPlayRate(_playRate); + _videoPlayer->stop(); updateControllerState(); }); - _stopButton->setSwallowTouches(false); _stopButton->setPositionNormalized(Vec2(0.75f, 0.5f)); _stopButton->setCascadeOpacityEnabled(true); _stopButton->setVisible(false); _primaryButtonPanel->addProtectedChild(_stopButton, 1, -1); - _pauseButton = MediaPlayerControl::create(SpriteFrame::createWithTexture( - g_mediaControlsTexture, g_mediaControlTextureRegions[MediaControlButtonId::Pause])); + _pauseButton = VideoPlayerControl::create(SpriteFrame::createWithTexture( + g_mediaControlsTexture, g_mediaControlTextureRegions[VideoControlButtonId::Pause])); _pauseButton->addClickEventListener([this](Object* ref) { if (_controlPanel->getOpacity() <= 50) return; _playRate = 1.f; - _mediaPlayer->setPlayRate(_playRate); - _mediaPlayer->pause(); + _videoPlayer->setPlayRate(_playRate); + _videoPlayer->pause(); updateControllerState(); }); - _pauseButton->setSwallowTouches(false); _pauseButton->setPositionNormalized(Vec2(0.25f, 0.5f)); _pauseButton->setCascadeOpacityEnabled(true); _pauseButton->setVisible(false); _primaryButtonPanel->addProtectedChild(_pauseButton, 1, -1); - _fullScreenEnterButton = MediaPlayerControl::create(SpriteFrame::createWithTexture( - g_mediaControlsTexture, g_mediaControlTextureRegions[MediaControlButtonId::EnterFullscreen])); - _fullScreenEnterButton->addClickEventListener([this](Object* ref) { + _fullscreenEnterButton = VideoPlayerControl::create(SpriteFrame::createWithTexture( + g_mediaControlsTexture, g_mediaControlTextureRegions[VideoControlButtonId::EnterFullscreen])); + _fullscreenEnterButton->addClickEventListener([this](Object* ref) { if (_controlPanel->getOpacity() <= 50) return; - _mediaPlayer->setFullScreenEnabled(true); + _videoPlayer->setFullscreen(true); updateControllerState(); }); - _fullScreenEnterButton->setSwallowTouches(false); - _fullScreenEnterButton->setAnchorPoint(Vec2::ANCHOR_TOP_LEFT); - _fullScreenEnterButton->setPositionNormalized(Vec2(0.03f, 0.97f)); - _fullScreenEnterButton->setCascadeOpacityEnabled(true); - _fullScreenEnterButton->setVisible(false); - _fullScreenEnterButton->setScale(1 / scale); - _controlPanel->addProtectedChild(_fullScreenEnterButton, 1, -1); - - _fullScreenExitButton = MediaPlayerControl::create(SpriteFrame::createWithTexture( - g_mediaControlsTexture, g_mediaControlTextureRegions[MediaControlButtonId::ExitFullscreen])); - _fullScreenExitButton->addClickEventListener([this](Object* ref) { + _fullscreenEnterButton->setAnchorPoint(Vec2::ANCHOR_TOP_LEFT); + _fullscreenEnterButton->setPositionNormalized(Vec2(0.03f, 0.97f)); + _fullscreenEnterButton->setCascadeOpacityEnabled(true); + _fullscreenEnterButton->setVisible(false); + _fullscreenEnterButton->setScale(1 / scale); + _controlPanel->addProtectedChild(_fullscreenEnterButton, 1, -1); + + _fullscreenExitButton = VideoPlayerControl::create(SpriteFrame::createWithTexture( + g_mediaControlsTexture, g_mediaControlTextureRegions[VideoControlButtonId::ExitFullscreen])); + _fullscreenExitButton->addClickEventListener([this](Object* ref) { if (_controlPanel->getOpacity() <= 50) return; - _mediaPlayer->setFullScreenEnabled(false); + _videoPlayer->setFullscreen(false); updateControllerState(); }); - _fullScreenExitButton->setSwallowTouches(false); - _fullScreenExitButton->setAnchorPoint(Vec2::ANCHOR_TOP_LEFT); - _fullScreenExitButton->setPositionNormalized(Vec2(0.03f, 0.97f)); - _fullScreenExitButton->setCascadeOpacityEnabled(true); - _fullScreenExitButton->setVisible(false); - _fullScreenExitButton->setScale(1 / scale); - _controlPanel->addProtectedChild(_fullScreenExitButton, 1, -1); + _fullscreenExitButton->setAnchorPoint(Vec2::ANCHOR_TOP_LEFT); + _fullscreenExitButton->setPositionNormalized(Vec2(0.03f, 0.97f)); + _fullscreenExitButton->setCascadeOpacityEnabled(true); + _fullscreenExitButton->setVisible(false); + _fullscreenExitButton->setScale(1 / scale); + _controlPanel->addProtectedChild(_fullscreenExitButton, 1, 1033); _timelineTotal = utils::createSpriteFromBase64Cached(BODY_IMAGE_1_PIXEL_HEIGHT, BODY_IMAGE_1_PIXEL_HEIGHT_KEY); _timelineTotal->setAnchorPoint(Vec2::ANCHOR_MIDDLE_BOTTOM); @@ -971,7 +954,7 @@ void BasicMediaController::createControls() _timelineTotal->addChild(_timelinePlayed, 5); _timelineSelector = Sprite::createWithTexture( - g_mediaControlsTexture, g_mediaControlTextureRegions[MediaControlButtonId::TimelineSliderButton]); + g_mediaControlsTexture, g_mediaControlTextureRegions[VideoControlButtonId::TimelineSliderButton]); _timelineSelector->setAnchorPoint(Vec2::ANCHOR_MIDDLE); _timelineSelector->setPositionNormalized(Vec2(1.f, 0.5f)); _timelineSelector->setCascadeOpacityEnabled(true); @@ -980,49 +963,49 @@ void BasicMediaController::createControls() _timelineSelector->setVisible(false); _timelinePlayed->addChild(_timelineSelector, 10); - _timelineTouchListener = EventListenerTouchOneByOne::create(); - _timelineTouchListener->setSwallowTouches(true); - _timelineTouchListener->onTouchBegan = [this](ax::Touch* touch, ax::Event* event) -> bool { + _timelineTouchListener = PointerEventListener::create(); + _timelineTouchListener->onPointerDown = [this](PointerEvent* event) -> bool { auto target = event->getCurrentTarget(); - const auto locationInNode = target->convertToNodeSpace(touch->getLocation()); + const auto locationInNode = target->convertToNodeSpace(event->getLocation()); const auto& size = target->getContentSize(); const auto rect = ax::Rect(0, 0, size.width, size.height); if (rect.containsPoint(locationInNode)) { auto percent = locationInNode.x / rect.size.x; - auto duration = _mediaPlayer->getDuration(); + auto duration = _videoPlayer->getDuration(); auto newTime = percent * duration; - _mediaPlayer->seekTo(newTime); + _videoPlayer->seekTo(newTime); _timelineSelector->setVisible(true); return true; } return false; }; - _timelineTouchListener->onTouchMoved = [this](Touch* touch, Event* event) { + _timelineTouchListener->onPointerMove = [this](PointerEvent* event) { auto target = event->getCurrentTarget(); - const auto locationInNode = target->convertToNodeSpace(touch->getLocation()); + const auto locationInNode = target->convertToNodeSpace(event->getLocation()); const auto& size = target->getContentSize(); const auto rect = ax::Rect(0, 0, size.width, size.height); if (rect.containsPoint(locationInNode)) { auto percent = locationInNode.x / rect.size.x; - auto duration = _mediaPlayer->getDuration(); + auto duration = _videoPlayer->getDuration(); auto newTime = percent * duration; - _mediaPlayer->seekTo(newTime); + _videoPlayer->seekTo(newTime); } + return true; }; - _timelineTouchListener->onTouchEnded = [this](Touch* touch, Event* event) { _timelineSelector->setVisible(false); }; + _timelineTouchListener->onPointerUp = [this](PointerEvent* /*event*/) { _timelineSelector->setVisible(false); }; getEventDispatcher()->addEventListenerWithSceneGraphPriority(_timelineTouchListener, _timelineTotal); _controlsReady = true; } -void BasicMediaController::updateControlsGlobalZ(float globalZOrder) +void DefaultVideoController::updateControlsGlobalZ(float globalZOrder) { - if (!_mediaPlayer || !_controlsReady) + if (!_videoPlayer || !_controlsReady) return; _controlPanel->setGlobalZOrder(globalZOrder); @@ -1031,18 +1014,18 @@ void BasicMediaController::updateControlsGlobalZ(float globalZOrder) _timelineSelector->setGlobalZOrder(globalZOrder); } -void BasicMediaController::updateControls() +void DefaultVideoController::updateControls() { - if (_mediaPlayer && _controlsReady) + if (_videoPlayer && _controlsReady) { - const auto currentTime = _mediaPlayer->getCurrentTime(); - const auto duration = _mediaPlayer->getDuration(); + const auto currentTime = _videoPlayer->getCurrentTime(); + const auto duration = _videoPlayer->getDuration(); auto& totalSize = _timelineTotal->getContentSize(); _timelinePlayed->setContentSize(Size(totalSize.width * (currentTime / duration), totalSize.height)); } } -void BasicMediaController::updateControlsForContentSize(const Vec2& contentSize) +void DefaultVideoController::updateControlsForContentSize(const Vec2& contentSize) { if (!_controlsReady) return; @@ -1054,16 +1037,16 @@ void BasicMediaController::updateControlsForContentSize(const Vec2& contentSize) _primaryButtonPanel->setScale(1 / scale); _timelineTotal->setContentSize(Size(contentSize.width - 40, _timelineBarHeight / scale)); _timelineSelector->setContentSize(Size(_timelineBarHeight, _timelineBarHeight) * 1.5f / scale); - _fullScreenEnterButton->setScale(1 / scale); - _fullScreenExitButton->setScale(1 / scale); + _fullscreenEnterButton->setScale(1 / scale); + _fullscreenExitButton->setScale(1 / scale); - _fullScreenEnterButton->setPositionNormalized(Vec2()); - _fullScreenEnterButton->setPositionNormalized(Vec2(0.03f, 0.97f)); - _fullScreenExitButton->setPositionNormalized(Vec2()); - _fullScreenExitButton->setPositionNormalized(Vec2(0.03f, 0.97f)); + _fullscreenEnterButton->setPositionNormalized(Vec2()); + _fullscreenEnterButton->setPositionNormalized(Vec2(0.03f, 0.97f)); + _fullscreenExitButton->setPositionNormalized(Vec2()); + _fullscreenExitButton->setPositionNormalized(Vec2(0.03f, 0.97f)); } -MediaPlayer::MediaPlayer() +VideoPlayer::VideoPlayer() { auto pvd = new PrivateVideoContext{}; _videoContext = pvd; @@ -1087,31 +1070,31 @@ MediaPlayer::MediaPlayer() { case MEMediaEventType::Playing: if (!isPlaying()) - onPlayEvent((int)EventType::PLAYING); + onPlayEvent(EventType::PLAYING); break; case MEMediaEventType::Paused: - onPlayEvent((int)EventType::PAUSED); + onPlayEvent(EventType::PAUSED); break; case MEMediaEventType::Stopped: - onPlayEvent(pvd->_engine->isPlaybackEnded() ? (int)EventType::COMPLETED : (int)EventType::STOPPED); + onPlayEvent(pvd->_engine->isPlaybackEnded() ? EventType::COMPLETED : EventType::STOPPED); break; /* Raised by a media source when a presentation ends. This event signals that all streams in the presentation are complete. The Media Session forwards this event to the application. */ // case MEEndOfPresentation: - // onPlayEvent((int)EventType::COMPLETED); + // onPlayEvent(EventType::COMPLETED); // break; /* Raised by the Media Session when it has finished playing the last presentation in the playback queue. * We send complete event at this case */ // case MEMediaEventType::Stopped: - // onPlayEvent((int)EventType::COMPLETED); + // onPlayEvent(EventType::COMPLETED); // break; case MEMediaEventType::Error: - onPlayEvent((int)EventType::ERROR); + onPlayEvent(EventType::ERROR); break; } }, [this, pvd](const ax::MEVideoFrame& frame) { @@ -1121,17 +1104,17 @@ MediaPlayer::MediaPlayer() } else { - AXLOGE("Create MediaPlayer backend failed"); + AXLOGE("Create VideoPlayer backend failed"); } } -MediaPlayer::~MediaPlayer() +VideoPlayer::~VideoPlayer() { auto pvd = reinterpret_cast(_videoContext); removeAllProtectedChildren(); - AX_SAFE_RELEASE_NULL(_mediaController); + AX_SAFE_RELEASE_NULL(_videoController); if (pvd->_engine) { @@ -1151,7 +1134,7 @@ MediaPlayer::~MediaPlayer() delete pvd; } -bool MediaPlayer::init() +bool VideoPlayer::init() { if (!Widget::init()) { @@ -1160,13 +1143,13 @@ bool MediaPlayer::init() if (_userInputEnabled) { - setMediaController(BasicMediaController::create(this)); + setVideoController(DefaultVideoController::create(this)); } return true; } -void MediaPlayer::setFileName(std::string_view fileName) +void VideoPlayer::setFileName(std::string_view fileName) { auto fullPath = FileUtils::getInstance()->fullPathForFilename(fileName); if (ax::utils::filePathToUrl(std::forward(fullPath)) != _videoURL) @@ -1174,20 +1157,20 @@ void MediaPlayer::setFileName(std::string_view fileName) reinterpret_cast(_videoContext)->closePlayer(); _videoURL = std::move(fullPath); } - _videoSource = MediaPlayer::Source::FILENAME; + _videoSource = VideoPlayer::Source::FILENAME; } -void MediaPlayer::setURL(std::string_view videoUrl) +void VideoPlayer::setURL(std::string_view videoUrl) { if (_videoURL != videoUrl) { reinterpret_cast(_videoContext)->closePlayer(); _videoURL = videoUrl; } - _videoSource = MediaPlayer::Source::URL; + _videoSource = VideoPlayer::Source::URL; } -void MediaPlayer::setLooping(bool looping) +void VideoPlayer::setLooping(bool looping) { _isLooping = looping; @@ -1196,31 +1179,31 @@ void MediaPlayer::setLooping(bool looping) pvd->_engine->setLoop(looping); } -void MediaPlayer::setUserInputEnabled(bool enableInput) +void VideoPlayer::setUserInputEnabled(bool enableInput) { _userInputEnabled = enableInput; - if (_mediaController) + if (_videoController) { - _mediaController->setEnabled(_userInputEnabled); + _videoController->setEnabled(_userInputEnabled); } else if (_userInputEnabled) { - setMediaController(BasicMediaController::create(this)); + setVideoController(DefaultVideoController::create(this)); } } -void MediaPlayer::setStyle(StyleType style) +void VideoPlayer::setStyle(StyleType style) { _styleType = style; } -Node* MediaPlayer::getVirtualRenderer() +Node* VideoPlayer::getRenderNode() { auto pvd = reinterpret_cast(_videoContext); return pvd->_vrender; } -void MediaPlayer::draw(Renderer* renderer, const Mat4& transform, uint32_t flags) +void VideoPlayer::draw(Renderer* renderer, const Mat4& transform, uint32_t flags) { ax::ui::Widget::draw(renderer, transform, flags); @@ -1245,21 +1228,19 @@ void MediaPlayer::draw(Renderer* renderer, const Mat4& transform, uint32_t flags # endif } -void MediaPlayer::setContentSize(const Size& contentSize) +void VideoPlayer::setContentSize(const Size& contentSize) { Widget::setContentSize(contentSize); auto videoContext = reinterpret_cast(_videoContext); videoContext->_originalViewSize = contentSize; - if (_mediaController) - { - _mediaController->setContentSize(contentSize); - } + if (_videoController) + _videoController->setContentSize(contentSize); } -MediaPlayer::MediaState MediaPlayer::getState() const +VideoPlayer::State VideoPlayer::getState() const { if (_videoURL.empty()) - return MediaState::CLOSED; + return State::CLOSED; auto engine = reinterpret_cast(_videoContext)->_engine; if (engine) @@ -1267,71 +1248,75 @@ MediaPlayer::MediaState MediaPlayer::getState() const switch (engine->getState()) { case MEMediaState::Closed: - return MediaState::CLOSED; + return State::CLOSED; case MEMediaState::Preparing: - return MediaState::LOADING; + return State::LOADING; case MEMediaState::Playing: - return MediaState::PLAYING; + return State::PLAYING; case MEMediaState::Paused: - return MediaState::PAUSED; + return State::PAUSED; case MEMediaState::Stopped: - return MediaState::STOPPED; + return State::STOPPED; case MEMediaState::Error: - return MediaState::ERROR; + return State::ERROR; } } - return MediaState::CLOSED; + return State::CLOSED; } -void MediaPlayer::setMediaController(MediaController* controller) +void VideoPlayer::setVideoController(VideoController* controller) { - if (_mediaController) + if (_videoController) { - removeProtectedChild(_mediaController, true); - AX_SAFE_RELEASE(_mediaController); + removeProtectedChild(_videoController, true); + AX_SAFE_RELEASE(_videoController); } - _mediaController = controller; - if (_mediaController) + _videoController = controller; + if (_videoController) { - AX_SAFE_RETAIN(_mediaController); - _mediaController->setPositionNormalized(Vec2(0.5f, 0.5f)); - _mediaController->setAnchorPoint(Vec2::ANCHOR_MIDDLE); - _mediaController->setEnabled(_userInputEnabled); - addProtectedChild(_mediaController, 1); + AX_SAFE_RETAIN(_videoController); + _videoController->setPositionNormalized(Vec2(0.5f, 0.5f)); + _videoController->setAnchorPoint(Vec2::ANCHOR_MIDDLE); + _videoController->setEnabled(_userInputEnabled); + addProtectedChild(_videoController, 1); } } -void MediaPlayer::setFullScreenEnabled(bool enabled) +void VideoPlayer::setFullscreen(bool enabled) { - if (_fullScreenEnabled != enabled) + if (_fullscreen != enabled) { - _fullScreenEnabled = enabled; + _fullscreen = enabled; - auto pvd = reinterpret_cast(_videoContext); - const auto contentSize = - enabled ? _director->getRenderView()->getDesignResolutionSize() : pvd->_originalViewSize; + auto pvd = reinterpret_cast(_videoContext); + const auto contentSize = enabled ? _director->getCanvasSize() : pvd->_originalViewSize; + + // Don't invoke this->setContentSize to avoid overwriting original view size. Widget::setContentSize(contentSize); - sendEvent((int)EventType::FULLSCREEN_SWITCH); + if (_videoController) + _videoController->setContentSize(contentSize); + + postEvent(EventType::FULLSCREEN_SWITCH); } } -bool MediaPlayer::isFullScreenEnabled() const +bool VideoPlayer::isFullscreen() const { - return _fullScreenEnabled; + return _fullscreen; } -void MediaPlayer::setKeepAspectRatioEnabled(bool enable) +void VideoPlayer::setKeepAspectRatio(bool enable) { - if (_keepAspectRatioEnabled != enable) + if (_keepAspectRatio != enable) { - _keepAspectRatioEnabled = enable; + _keepAspectRatio = enable; reinterpret_cast(_videoContext)->_scaleDirty = true; } } -void MediaPlayer::setPlayRate(float fRate) +void VideoPlayer::setPlayRate(float fRate) { if (!_videoURL.empty()) { @@ -1341,7 +1326,7 @@ void MediaPlayer::setPlayRate(float fRate) } } -void MediaPlayer::play() +void VideoPlayer::play() { if (!_videoURL.empty()) { @@ -1357,24 +1342,24 @@ void MediaPlayer::play() default: engine->play(); } - updateMediaController(); + updateVideoController(); } } } -void MediaPlayer::pause() +void VideoPlayer::pause() { Widget::pause(); pausePlayback(); } -void MediaPlayer::resume() +void VideoPlayer::resume() { resumePlayback(); Widget::resume(); } -void MediaPlayer::pausePlayback() +void VideoPlayer::pausePlayback() { if (!_videoURL.empty()) { @@ -1382,12 +1367,12 @@ void MediaPlayer::pausePlayback() if (engine) { engine->pause(); - updateMediaController(); + updateVideoController(); } } } -void MediaPlayer::resumePlayback() +void VideoPlayer::resumePlayback() { if (!_videoURL.empty()) { @@ -1401,12 +1386,12 @@ void MediaPlayer::resumePlayback() engine->play(); } - updateMediaController(); + updateVideoController(); } } } -void MediaPlayer::stop() +void VideoPlayer::stop() { if (!_videoURL.empty()) { @@ -1414,12 +1399,12 @@ void MediaPlayer::stop() if (engine) { engine->stop(); - updateMediaController(); + updateVideoController(); } } } -void MediaPlayer::seekTo(float sec) +void VideoPlayer::seekTo(float sec) { if (!_videoURL.empty()) { @@ -1427,12 +1412,12 @@ void MediaPlayer::seekTo(float sec) if (engine) { engine->setCurrentTime(sec); - updateMediaController(); + updateVideoController(); } } } -float MediaPlayer::getCurrentTime() +float VideoPlayer::getCurrentTime() { if (!_videoURL.empty()) { @@ -1446,7 +1431,7 @@ float MediaPlayer::getCurrentTime() return 0.f; } -float MediaPlayer::getDuration() +float VideoPlayer::getDuration() { if (!_videoURL.empty()) { @@ -1460,88 +1445,92 @@ float MediaPlayer::getDuration() return 0.f; } -bool MediaPlayer::isPlaying() const +bool VideoPlayer::isPlaying() const { return _isPlaying; } -bool MediaPlayer::isLooping() const +bool VideoPlayer::isLooping() const { return _isLooping; } -bool MediaPlayer::isUserInputEnabled() const +bool VideoPlayer::isUserInputEnabled() const { return _userInputEnabled; } -void MediaPlayer::setVisible(bool visible) +void VideoPlayer::setVisible(bool visible) { ax::ui::Widget::setVisible(visible); } -void MediaPlayer::onEnter() +void VideoPlayer::onEnter() { Widget::onEnter(); } -void MediaPlayer::onExit() +void VideoPlayer::onExit() { _eventCallback = nullptr; Widget::onExit(); } -void MediaPlayer::addEventListener(const MediaPlayer::VideoPlayerCallback& callback) +void VideoPlayer::addEventListener(const VideoPlayerCallback& callback) { _eventCallback = callback; } -void MediaPlayer::onPlayEvent(int event) +void VideoPlayer::onPlayEvent(EventType eventType) { - _isPlaying = (event == (int)MediaPlayer::EventType::PLAYING); + _isPlaying = (eventType == EventType::PLAYING); - sendEvent(event); + postEvent(eventType); } -void MediaPlayer::sendEvent(int event) +void VideoPlayer::postEvent(EventType eventType) { if (_eventCallback) { - _director->getScheduler()->runOnAxmolThread(std::bind(_eventCallback, this, (MediaPlayer::EventType)event)); + RefPtr guard(this); + _director->postTask([guard, eventType]() { + if (guard->_eventCallback) + guard->_eventCallback(guard, eventType); + }); } } -ax::ui::Widget* MediaPlayer::createCloneInstance() +ax::ui::Widget* VideoPlayer::createCloneInstance() { - return MediaPlayer::create(); + return VideoPlayer::create(); } -void MediaPlayer::copySpecialProperties(Widget* widget) +void VideoPlayer::copySpecialProperties(Widget* widget) { - MediaPlayer* mplayer = dynamic_cast(widget); - if (mplayer) + VideoPlayer* player = dynamic_cast(widget); + if (player) { - _isPlaying = mplayer->_isPlaying; - _isLooping = mplayer->_isLooping; - _userInputEnabled = mplayer->_userInputEnabled; - _styleType = mplayer->_styleType; - _fullScreenEnabled = mplayer->_fullScreenEnabled; - _fullScreenDirty = mplayer->_fullScreenDirty; - _videoURL = mplayer->_videoURL; - _keepAspectRatioEnabled = mplayer->_keepAspectRatioEnabled; - _videoSource = mplayer->_videoSource; - _eventCallback = mplayer->_eventCallback; + _isPlaying = player->_isPlaying; + _isLooping = player->_isLooping; + _userInputEnabled = player->_userInputEnabled; + _styleType = player->_styleType; + _fullscreen = player->_fullscreen; + _fullscreenDirty = player->_fullscreenDirty; + _videoURL = player->_videoURL; + _keepAspectRatio = player->_keepAspectRatio; + _videoSource = player->_videoSource; + _eventCallback = player->_eventCallback; } } -void MediaPlayer::updateMediaController() +void VideoPlayer::updateVideoController() { - if (!_userInputEnabled || !_mediaController) + if (!_userInputEnabled || !_videoController) { return; } - _mediaController->updateControllerState(); + _videoController->updateControllerState(); } #endif diff --git a/axmol/ui/UIMediaPlayer.h b/axmol/ui/VideoPlayer.h similarity index 70% rename from axmol/ui/UIMediaPlayer.h rename to axmol/ui/VideoPlayer.h index 8955b1993a58..f08d3ab61410 100644 --- a/axmol/ui/UIMediaPlayer.h +++ b/axmol/ui/VideoPlayer.h @@ -25,11 +25,11 @@ ****************************************************************************/ #pragma once -#if defined(AX_ENABLE_MEDIA) +#if defined(AX_ENABLE_VIDEO) -# include "axmol/ui/UIButton.h" -# include "axmol/ui/UIWidget.h" -# include "axmol/ui/UILayout.h" +# include "axmol/ui/Button.h" +# include "axmol/ui/Widget.h" +# include "axmol/ui/LayoutGroup.h" # include "axmol/2d/Sprite.h" # include @@ -49,9 +49,9 @@ namespace ax namespace ui { -class MediaPlayer; +class VideoPlayer; -class AX_GUI_DLL MediaController : public ax::ui::Widget +class AX_GUI_DLL VideoController : public ax::ui::Widget { public: enum class Orientation @@ -61,8 +61,7 @@ class AX_GUI_DLL MediaController : public ax::ui::Widget RotatedRight, }; - explicit MediaController(MediaPlayer* player) : _mediaPlayer(player) {} - ~MediaController() override = 0; + explicit VideoController(VideoPlayer* player) : _videoPlayer(player) {} virtual void updateControllerState() = 0; virtual void setTimelineBarHeight(float height) = 0; @@ -71,23 +70,22 @@ class AX_GUI_DLL MediaController : public ax::ui::Widget Orientation getOrientation() const { return _orientation; } protected: - MediaPlayer* _mediaPlayer = nullptr; + VideoPlayer* _videoPlayer = nullptr; Orientation _orientation = Orientation::Default; }; -inline MediaController::~MediaController() = default; // Required since the destructor is pure virtual -class MediaPlayerControl : public ax::ui::Button +class VideoPlayerControl : public ax::ui::Button { public: - static MediaPlayerControl* create(SpriteFrame* frame); + static VideoPlayerControl* create(SpriteFrame* frame); - MediaPlayerControl() = default; - ~MediaPlayerControl() override; + VideoPlayerControl() = default; + ~VideoPlayerControl() override; virtual bool init(SpriteFrame* frame); void onSizeChanged() override; - Vec2 getVirtualRendererSize() const override; + Vec2 resolvePreferredSize(const Vec2& /*sizeHint*/) const override; Vec2 getNormalSize() const override; void onPressStateChangedToNormal() override; @@ -98,15 +96,15 @@ class MediaPlayerControl : public ax::ui::Button Sprite* _overlay = nullptr; }; -class AX_GUI_DLL BasicMediaController : public MediaController +class AX_GUI_DLL DefaultVideoController : public VideoController { public: - explicit BasicMediaController(MediaPlayer* player); + explicit DefaultVideoController(VideoPlayer* player); - static BasicMediaController* create(MediaPlayer* mediaPlayer); + static DefaultVideoController* create(VideoPlayer* mediaPlayer); bool init() override; - void initRenderer() override; + void initRenderNode() override; void onPressStateChangedToPressed() override; void setContentSize(const Vec2& contentSize) override; @@ -125,37 +123,37 @@ class AX_GUI_DLL BasicMediaController : public MediaController protected: Widget* _controlPanel = nullptr; - MediaPlayerControl* _fullScreenEnterButton = nullptr; - MediaPlayerControl* _fullScreenExitButton = nullptr; - MediaPlayerControl* _playButton = nullptr; - MediaPlayerControl* _stopButton = nullptr; - MediaPlayerControl* _pauseButton = nullptr; + VideoPlayerControl* _fullscreenEnterButton = nullptr; + VideoPlayerControl* _fullscreenExitButton = nullptr; + VideoPlayerControl* _playButton = nullptr; + VideoPlayerControl* _stopButton = nullptr; + VideoPlayerControl* _pauseButton = nullptr; Sprite* _timelineSelector = nullptr; Sprite* _timelineTotal = nullptr; Sprite* _timelinePlayed = nullptr; - Layout* _mediaOverlay = nullptr; + LayoutGroup* _mediaOverlay = nullptr; Widget* _primaryButtonPanel = nullptr; - EventListenerTouchOneByOne* _timelineTouchListener = nullptr; - float _playRate = 1.f; + PointerEventListener* _timelineTouchListener = nullptr; + float _playRate = 1.f; std::chrono::steady_clock::time_point _lastTouch; bool _controlsReady = false; float _timelineBarHeight; }; /** - * @class MediaPlayer - * @brief Play a media file. + * @class VideoPlayer + * @brief Play a video file. * - * @note MediaPlayer play a media file base on system widget. - * It's mean MediaPlayer play a media file above all graphical elements of axmol. + * @note VideoPlayer play a video file base on system widget. + * It's mean VideoPlayer play a video file above all graphical elements of axmol. */ -class AX_GUI_DLL MediaPlayer : public ax::ui::Widget +class AX_GUI_DLL VideoPlayer : public ax::ui::Widget { public: /** - * Videoplayer play event type. + * VideoPlayer play event type. */ enum class EventType { @@ -166,8 +164,9 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget ERROR, FULLSCREEN_SWITCH }; + using VideoPlayerCallback = std::function; - enum class MediaState + enum class State { CLOSED = 0, LOADING, @@ -177,6 +176,7 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget FINISHED, ERROR }; + using MediaState = State; /** * Styles of how the the video player is presented @@ -190,19 +190,14 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget }; /** - * A callback which will be called after specific MediaPlayer event happens. + *Static create method for instancing a VideoPlayer. */ - typedef std::function VideoPlayerCallback; - - /** - *Static create method for instancing a MediaPlayer. - */ - CREATE_FUNC(MediaPlayer); + CREATE_FUNC(VideoPlayer); bool init() override; /** - * Sets a file path as a video source for MediaPlayer. + * Sets a file path as a video source for VideoPlayer. */ virtual void setFileName(std::string_view videoPath); @@ -214,7 +209,7 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget virtual std::string_view getFileName() const { return _videoURL; } /** - * Sets a URL as a video source for MediaPlayer. + * Sets a URL as a video source for VideoPlayer. */ virtual void setURL(std::string_view _videoURL); @@ -293,7 +288,7 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget virtual void seekTo(float sec); /** - * Gets the current media position. + * Gets the current video position. * * @return float The current position in seconds */ @@ -307,21 +302,21 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget virtual float getDuration(); /** - * Checks whether the MediaPlayer is playing. + * Checks whether the VideoPlayer is playing. * * @return True if currently playing, false otherwise. */ virtual bool isPlaying() const; /** - * Checks whether the MediaPlayer is set with looping mode. + * Checks whether the VideoPlayer is set with looping mode. * * @return true if the videoplayer is set to loop, false otherwise. */ virtual bool isLooping() const; /** - * Checks whether the MediaPlayer is set to listen user input to resume and pause the video + * Checks whether the VideoPlayer is set to listen user input to resume and pause the video * * @return true if the videoplayer user input is set, false otherwise. */ @@ -333,40 +328,38 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget * @param enable Specify true to keep aspect ratio or false to scale the video until * both dimensions fit the visible bounds of the view exactly. */ - virtual void setKeepAspectRatioEnabled(bool enable); + virtual void setKeepAspectRatio(bool enable); /** * Indicates whether the video player keep aspect ratio when displaying the video. */ - virtual bool isKeepAspectRatioEnabled() const { return _keepAspectRatioEnabled; } + virtual bool isKeepAspectRatio() const { return _keepAspectRatio; } /** * Causes the video player to enter or exit full-screen mode. * * @param fullscreen Specify true to enter full-screen mode or false to exit full-screen mode. */ - virtual void setFullScreenEnabled(bool fullscreen); + virtual void setFullscreen(bool fullscreen); /** * Indicates whether the video player is in full-screen mode. * * @return True if the video player is in full-screen mode, false otherwise. */ - virtual bool isFullScreenEnabled() const; + virtual bool isFullscreen() const; /** - * Register a callback to be invoked when the video state is updated. + * @brief A function which will be called when video is playing. * - * @param callback The callback that will be run. + * @param event @see VideoPlayer::EventType. */ - virtual void addEventListener(const MediaPlayer::VideoPlayerCallback& callback); + virtual void onPlayEvent(EventType eventType); /** - * @brief A function which will be called when video is playing. - * - * @param event @see MediaPlayer::EventType. + * Register a callback to be invoked when the video state is updated. */ - virtual void onPlayEvent(int event); + virtual void addEventListener(const VideoPlayerCallback& callback); void setVisible(bool visible) override; void draw(Renderer* renderer, const Mat4& transform, uint32_t flags) override; @@ -376,25 +369,25 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget void setContentSize(const Size& contentSize) override; /** - * @brief Get current state of the media + * @brief Get current state of the video * - * @return MediaState + * @return State */ - MediaState getState() const; + State getState() const; - Node* getVirtualRenderer() override; + Node* getRenderNode() override; - void setMediaController(MediaController* controller); - MediaController* getMediaController() const { return _mediaController; } + void setVideoController(VideoController* controller); + VideoController* getVideoController() const { return _videoController; } - MediaPlayer(); - ~MediaPlayer() override; + VideoPlayer(); + ~VideoPlayer() override; protected: ax::ui::Widget* createCloneInstance() override; void copySpecialProperties(Widget* model) override; - virtual void updateMediaController(); - void sendEvent(int event); + virtual void updateVideoController(); + void postEvent(EventType eventType); # if AX_VIDEOPLAYER_DEBUG_DRAW DrawNode* _debugDrawNode; @@ -406,25 +399,24 @@ class AX_GUI_DLL MediaPlayer : public ax::ui::Widget URL }; - bool _isPlaying = false; - bool _isLooping = false; - bool _fullScreenDirty = false; - bool _fullScreenEnabled = false; - bool _keepAspectRatioEnabled = false; - bool _userInputEnabled = false; + bool _isPlaying = false; + bool _isLooping = false; + bool _fullscreenDirty = false; + bool _fullscreen = false; + bool _keepAspectRatio = false; + bool _userInputEnabled = false; StyleType _styleType = StyleType::DEFAULT; std::string _videoURL; Source _videoSource; - VideoPlayerCallback _eventCallback = nullptr; - void* _videoContext = nullptr; - MediaController* _mediaController = nullptr; + VideoController* _videoController = nullptr; + VideoPlayerCallback _eventCallback; }; -using VideoPlayer = MediaPlayer; +using MediaPlayer = VideoPlayer; } // namespace ui } // namespace ax diff --git a/axmol/ui/UIWebView/UIWebView-inl.h b/axmol/ui/WebView/WebView-inl.h similarity index 92% rename from axmol/ui/UIWebView/UIWebView-inl.h rename to axmol/ui/WebView/WebView-inl.h index 82460144fd10..600297c51093 100644 --- a/axmol/ui/UIWebView/UIWebView-inl.h +++ b/axmol/ui/WebView/WebView-inl.h @@ -31,7 +31,7 @@ (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || \ AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) -# include "axmol/ui/UIWebView/UIWebView.h" +# include "axmol/ui/WebView/WebView.h" # include "axmol/platform/RenderView.h" # include "axmol/base/Director.h" # include "axmol/platform/FileUtils.h" @@ -200,12 +200,12 @@ void WebView::copySpecialProperties(Widget* model) } } -void WebView::setOnDidFailLoading(const ccWebViewCallback& callback) +void WebView::setOnDidFailLoading(const WebViewCallback& callback) { _onDidFailLoading = callback; } -void WebView::setOnDidFinishLoading(const ccWebViewCallback& callback) +void WebView::setOnDidFinishLoading(const WebViewCallback& callback) { _onDidFinishLoading = callback; } @@ -215,7 +215,7 @@ void WebView::setOnShouldStartLoading(const std::function WebView::getOnShouldS return _onShouldStartLoading; } -WebView::ccWebViewCallback WebView::getOnDidFailLoading() const +WebView::WebViewCallback WebView::getOnDidFailLoading() const { return _onDidFailLoading; } -WebView::ccWebViewCallback WebView::getOnDidFinishLoading() const +WebView::WebViewCallback WebView::getOnDidFinishLoading() const { return _onDidFinishLoading; } -WebView::ccWebViewCallback WebView::getOnJSCallback() const +WebView::WebViewCallback WebView::getOnJSCallback() const { return _onJSCallback; } diff --git a/axmol/ui/UIWebView/UIWebView.cpp b/axmol/ui/WebView/WebView.cpp similarity index 83% rename from axmol/ui/UIWebView/UIWebView.cpp rename to axmol/ui/WebView/WebView.cpp index 34526cec42bd..8862f0c10505 100644 --- a/axmol/ui/UIWebView/UIWebView.cpp +++ b/axmol/ui/WebView/WebView.cpp @@ -27,17 +27,17 @@ #if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) -# include "axmol/ui/UIWebView/UIWebViewImpl-android.h" -# include "axmol/ui/UIWebView/UIWebView-inl.h" +# include "axmol/ui/WebView/WebViewImpl-android.h" +# include "axmol/ui/WebView/WebView-inl.h" #elif (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) -# include "axmol/ui/UIWebView/UIWebViewImpl-win32.h" -# include "axmol/ui/UIWebView/UIWebView-inl.h" +# include "axmol/ui/WebView/WebViewImpl-win32.h" +# include "axmol/ui/WebView/WebView-inl.h" #elif (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) && defined(AX_HAVE_WEBKIT2GTK) -# include "axmol/ui/UIWebView/UIWebViewImpl-linux.h" -# include "axmol/ui/UIWebView/UIWebView-inl.h" +# include "axmol/ui/WebView/WebViewImpl-linux.h" +# include "axmol/ui/WebView/WebView-inl.h" #endif diff --git a/axmol/ui/UIWebView/UIWebView.h b/axmol/ui/WebView/WebView.h similarity index 91% rename from axmol/ui/UIWebView/UIWebView.h rename to axmol/ui/WebView/WebView.h index 0e4bba9b61b2..af4809f10b7a 100644 --- a/axmol/ui/UIWebView/UIWebView.h +++ b/axmol/ui/WebView/WebView.h @@ -25,7 +25,7 @@ ****************************************************************************/ #pragma once -#include "axmol/ui/UIWidget.h" +#include "axmol/ui/Widget.h" #include "axmol/ui/GUIExport.h" #include "axmol/base/Data.h" @@ -159,26 +159,26 @@ class AX_GUI_DLL WebView : public ax::ui::Widget /** * A callback which will be called when a WebView event happens. */ - typedef std::function ccWebViewCallback; + using WebViewCallback = std::function; /** * Call after a web view finishes loading. * * @param callback The web view that has finished loading. */ - void setOnDidFinishLoading(const ccWebViewCallback& callback); + void setOnDidFinishLoading(const WebViewCallback& callback); /** * Call if a web view failed to load content. * * @param callback The web view that has failed loading. */ - void setOnDidFailLoading(const ccWebViewCallback& callback); + void setOnDidFailLoading(const WebViewCallback& callback); /** * This callback called when load URL that start with javascript interface scheme. */ - void setOnJSCallback(const ccWebViewCallback& callback); + void setOnJSCallback(const WebViewCallback& callback); /** * Get the callback when WebView is about to start. @@ -188,17 +188,17 @@ class AX_GUI_DLL WebView : public ax::ui::Widget /** * Get the callback when WebView has finished loading. */ - ccWebViewCallback getOnDidFinishLoading() const; + WebViewCallback getOnDidFinishLoading() const; /** * Get the callback when WebView has failed loading. */ - ccWebViewCallback getOnDidFailLoading() const; + WebViewCallback getOnDidFailLoading() const; /** *Get the Javascript callback. */ - ccWebViewCallback getOnJSCallback() const; + WebViewCallback getOnJSCallback() const; /** * Set whether the webview bounces at end of scroll of WebView. @@ -239,9 +239,9 @@ class AX_GUI_DLL WebView : public ax::ui::Widget void copySpecialProperties(Widget* model) override; std::function _onShouldStartLoading = nullptr; - ccWebViewCallback _onDidFinishLoading = nullptr; - ccWebViewCallback _onDidFailLoading = nullptr; - ccWebViewCallback _onJSCallback = nullptr; + WebViewCallback _onDidFinishLoading = nullptr; + WebViewCallback _onDidFailLoading = nullptr; + WebViewCallback _onJSCallback = nullptr; private: WebViewImpl* _impl = nullptr; diff --git a/axmol/ui/UIWebView/UIWebView.mm b/axmol/ui/WebView/WebView.mm similarity index 93% rename from axmol/ui/UIWebView/UIWebView.mm rename to axmol/ui/WebView/WebView.mm index 1b3248aac84f..665927b583f1 100644 --- a/axmol/ui/UIWebView/UIWebView.mm +++ b/axmol/ui/WebView/WebView.mm @@ -24,5 +24,5 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIWebView/UIWebViewImpl-ios.h" -#include "axmol/ui/UIWebView/UIWebView-inl.h" +#include "axmol/ui/WebView/WebViewImpl-ios.h" +#include "axmol/ui/WebView/WebView-inl.h" diff --git a/axmol/ui/UIWebView/UIWebViewCommon.h b/axmol/ui/WebView/WebViewCommon.h similarity index 100% rename from axmol/ui/UIWebView/UIWebViewCommon.h rename to axmol/ui/WebView/WebViewCommon.h diff --git a/axmol/ui/UIWebView/UIWebViewImpl-android.cpp b/axmol/ui/WebView/WebViewImpl-android.cpp similarity index 98% rename from axmol/ui/UIWebView/UIWebViewImpl-android.cpp rename to axmol/ui/WebView/WebViewImpl-android.cpp index c278c0f3a5fe..ea0985df606b 100644 --- a/axmol/ui/UIWebView/UIWebViewImpl-android.cpp +++ b/axmol/ui/WebView/WebViewImpl-android.cpp @@ -23,14 +23,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIWebView/UIWebViewImpl-android.h" +#include "axmol/ui/WebView/WebViewImpl-android.h" #include #include #include #include "axmol/platform/android/jni/JniHelper.h" -#include "axmol/ui/UIWebView/UIWebView.h" +#include "axmol/ui/WebView/WebView.h" #include "axmol/platform/RenderView.h" #include "axmol/base/Director.h" #include "axmol/platform/FileUtils.h" @@ -326,7 +326,7 @@ void WebViewImpl::draw(ax::Renderer* renderer, ax::Mat4 const& transform, uint32 { if (flags & ax::Node::FLAGS_TRANSFORM_DIRTY) { - auto uiRect = ax::ui::Helper::convertBoundingBoxToScreen(_webView); + auto uiRect = ax::ui::Helper::getNodeNativeWindowRect(_webView); JniHelper::callStaticVoidMethod(className, "setWebViewRect", _viewTag, (int)uiRect.origin.x, (int)uiRect.origin.y, (int)uiRect.size.width, (int)uiRect.size.height); } diff --git a/axmol/ui/UIWebView/UIWebViewImpl-android.h b/axmol/ui/WebView/WebViewImpl-android.h similarity index 100% rename from axmol/ui/UIWebView/UIWebViewImpl-android.h rename to axmol/ui/WebView/WebViewImpl-android.h diff --git a/axmol/ui/UIWebView/UIWebViewImpl-ios.h b/axmol/ui/WebView/WebViewImpl-ios.h similarity index 100% rename from axmol/ui/UIWebView/UIWebViewImpl-ios.h rename to axmol/ui/WebView/WebViewImpl-ios.h diff --git a/axmol/ui/UIWebView/UIWebViewImpl-ios.mm b/axmol/ui/WebView/WebViewImpl-ios.mm similarity index 62% rename from axmol/ui/UIWebView/UIWebViewImpl-ios.mm rename to axmol/ui/WebView/WebViewImpl-ios.mm index cee60bc6ad9c..9d3c77b4b5ee 100644 --- a/axmol/ui/UIWebView/UIWebViewImpl-ios.mm +++ b/axmol/ui/WebView/WebViewImpl-ios.mm @@ -23,18 +23,26 @@ of this software and associated documentation files (the "Software"), to deal OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#import -#import -#import +#import -#include "axmol/ui/UIWebView/UIWebViewImpl-ios.h" -#include "axmol/ui/UIWebView/UIWebView.h" +#include "axmol/ui/WebView/WebViewImpl-ios.h" +#include "axmol/ui/WebView/WebView.h" +#include "axmol/ui/UIHelper.h" #include "axmol/renderer/Renderer.h" +#include "axmol/base/Logging.h" #include "axmol/base/Director.h" #include "axmol/platform/RenderView.h" #include "axmol/platform/ios/RenderHostView-ios.h" #include "axmol/platform/FileUtils.h" +@class UIWebViewWrapper; + +static NSString* const AXWebViewConsoleMessageHandlerName = @"axmolWebViewConsole"; + +@interface AXWebViewScriptMessageHandler : NSObject +@property(nonatomic, assign) UIWebViewWrapper* webViewWrapper; +@end + @interface UIWebViewWrapper : NSObject @property(nonatomic) std::function shouldStartLoading; @property(nonatomic) std::function didFinishLoading; @@ -75,6 +83,8 @@ - (void)stopLoading; - (void)reload; +- (void)handleScriptMessage:(WKScriptMessage*)message; + - (void)evaluateJS:(std::string_view)js; - (void)goBack; @@ -86,10 +96,23 @@ - (void)setScalesPageToFit:(const bool)scalesPageToFit; @interface UIWebViewWrapper () @property(nonatomic) WKWebView* wkWebView; +@property(nonatomic) BOOL suppressNextDidFinishLoading; +@property(nonatomic, retain) AXWebViewScriptMessageHandler* scriptMessageHandler; @property(nonatomic, copy) NSString* jsScheme; @end +@implementation AXWebViewScriptMessageHandler + +- (void)userContentController:(WKUserContentController*)userContentController + didReceiveScriptMessage:(WKScriptMessage*)message +{ + AX_UNUSED_PARAM(userContentController); + [self.webViewWrapper handleScriptMessage:message]; +} + +@end + @implementation UIWebViewWrapper { } @@ -103,10 +126,12 @@ - (instancetype)init self = [super init]; if (self) { - self.wkWebView = nil; - self.shouldStartLoading = nullptr; - self.didFinishLoading = nullptr; - self.didFailLoading = nullptr; + self.wkWebView = nil; + self.shouldStartLoading = nullptr; + self.didFinishLoading = nullptr; + self.didFailLoading = nullptr; + self.suppressNextDidFinishLoading = NO; + self.scriptMessageHandler = nil; } return self; } @@ -115,10 +140,14 @@ - (void)dealloc { self.wkWebView.UIDelegate = nil; self.wkWebView.navigationDelegate = nil; + [self.wkWebView.configuration.userContentController + removeScriptMessageHandlerForName:AXWebViewConsoleMessageHandlerName]; [self.wkWebView removeFromSuperview]; [self.wkWebView release]; - self.wkWebView = nil; - self.jsScheme = nil; + self.wkWebView = nil; + self.scriptMessageHandler.webViewWrapper = nil; + self.scriptMessageHandler = nil; + self.jsScheme = nil; [super dealloc]; } @@ -126,7 +155,61 @@ - (void)setupWebView { if (!self.wkWebView) { - self.wkWebView = [[WKWebView alloc] init]; + static NSString* const consoleBridgeScript = + @"(function(){" + @"if(window.__axmolWebViewConsoleBridgeInstalled){return;}" + @"window.__axmolWebViewConsoleBridgeInstalled=true;" + @"function stringify(value){" + @"try{" + @"if(value instanceof Error){return value.stack||value.message||String(value);}" + @"if(typeof value==='object'){return JSON.stringify(value);}" + @"return String(value);" + @"}catch(e){return String(value);}" + @"}" + @"function post(level,args,source,line,column,stack){" + @"try{" + @"window.webkit.messageHandlers.axmolWebViewConsole.postMessage({" + @"level:level," + @"message:Array.prototype.map.call(args,stringify).join(' ')," + @"source:source||''," + @"line:line||0," + @"column:column||0," + @"stack:stack||''" + @"});" + @"}catch(e){}" + @"}" + @"['log','info','warn','error'].forEach(function(level){" + @"var original=console[level];" + @"console[level]=function(){" + @"post(level,arguments);" + @"if(original){original.apply(console,arguments);}" + @"};" + @"});" + @"window.addEventListener('error',function(event){" + @"var error=event.error;" + @"post('exception',[event.message||'JavaScript " + @"error'],event.filename,event.lineno,event.colno,error&&error.stack);" + @"});" + @"window.addEventListener('unhandledrejection',function(event){" + @"var reason=event.reason;" + @"post('unhandledrejection',[reason&&(reason.stack||reason.message)||reason],'',0,0,reason&&reason.stack);" + @"});" + @"})();"; + + WKWebViewConfiguration* configuration = [[[WKWebViewConfiguration alloc] init] autorelease]; + WKUserContentController* userContentController = [[[WKUserContentController alloc] init] autorelease]; + WKUserScript* userScript = [[[WKUserScript alloc] initWithSource:consoleBridgeScript + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly:NO] autorelease]; + + [userContentController addUserScript:userScript]; + self.scriptMessageHandler = [[[AXWebViewScriptMessageHandler alloc] init] autorelease]; + self.scriptMessageHandler.webViewWrapper = self; + [userContentController addScriptMessageHandler:self.scriptMessageHandler + name:AXWebViewConsoleMessageHandlerName]; + configuration.userContentController = userContentController; + + self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]; self.wkWebView.UIDelegate = self; self.wkWebView.navigationDelegate = self; } @@ -259,7 +342,30 @@ - (void)stopLoading - (void)reload { - [self.wkWebView reload]; + if (!self.wkWebView) + { + [self setupWebView]; + } + + NSURL* url = [self.wkWebView.URL retain]; + if (!url) + { + [self.wkWebView reload]; + return; + } + + [self.wkWebView stopLoading]; + self.suppressNextDidFinishLoading = YES; + [self.wkWebView loadHTMLString:@"" baseURL:nil]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, static_cast(0.05 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + NSURLRequest* request = [NSURLRequest requestWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:60]; + [self.wkWebView loadRequest:request]; + [url release]; + }); } - (BOOL)canGoForward @@ -299,6 +405,63 @@ - (void)setScalesPageToFit:(const bool)scalesPageToFit // it will be too complex. } +- (void)handleScriptMessage:(WKScriptMessage*)message +{ + if (![message.name isEqualToString:AXWebViewConsoleMessageHandlerName] || + ![message.body isKindOfClass:[NSDictionary class]]) + { + return; + } + + NSDictionary* body = (NSDictionary*)message.body; + + auto stringValue = [](id value) -> std::string { + if ([value isKindOfClass:[NSString class]]) + { + return [(NSString*)value UTF8String]; + } + if ([value respondsToSelector:@selector(stringValue)]) + { + return [[value stringValue] UTF8String]; + } + return {}; + }; + + auto intValue = [](id value) -> int { + if ([value respondsToSelector:@selector(intValue)]) + { + return [value intValue]; + } + return 0; + }; + + const auto level = stringValue(body[@"level"]); + const auto text = stringValue(body[@"message"]); + const auto source = stringValue(body[@"source"]); + const auto stack = stringValue(body[@"stack"]); + const auto line = intValue(body[@"line"]); + const auto column = intValue(body[@"column"]); + auto messageText = text; + if (!stack.empty()) + { + messageText += "\n"; + messageText += stack; + } + + if (level == "error" || level == "exception" || level == "unhandledrejection") + { + AXLOGE("WKWebView JavaScript {}: {} ({}:{}:{})", level, messageText, source, line, column); + } + else if (level == "warn") + { + AXLOGW("WKWebView JavaScript {}: {} ({}:{}:{})", level, messageText, source, line, column); + } + else + { + AXLOGD("WKWebView JavaScript {}: {} ({}:{}:{})", level, messageText, source, line, column); + } +} + #pragma mark - WKNavigationDelegate - (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction @@ -326,18 +489,33 @@ - (void)webView:(WKWebView*)webView - (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation { + if (self.suppressNextDidFinishLoading) + { + self.suppressNextDidFinishLoading = NO; + return; + } + if (self.didFinishLoading) { NSString* url = [webView.URL absoluteString]; - self.didFinishLoading([url UTF8String]); + if (url) + { + self.didFinishLoading([url UTF8String]); + } } } - (void)webView:(WKWebView*)webView didFailProvisionalNavigation:(WKNavigation*)navigation withError:(NSError*)error { + AX_UNUSED_PARAM(webView); + AX_UNUSED_PARAM(navigation); + + NSString* errorInfo = error.userInfo[NSURLErrorFailingURLStringErrorKey]; + AXLOGE("WKWebView provisional navigation failed: {} ({})", errorInfo ? [errorInfo UTF8String] : "", + error.localizedDescription ? [error.localizedDescription UTF8String] : ""); + if (self.didFailLoading) { - NSString* errorInfo = error.userInfo[NSURLErrorFailingURLStringErrorKey]; if (errorInfo) { self.didFailLoading([errorInfo UTF8String]); @@ -345,6 +523,22 @@ - (void)webView:(WKWebView*)webView didFailProvisionalNavigation:(WKNavigation*) } } +- (void)webView:(WKWebView*)webView didFailNavigation:(WKNavigation*)navigation withError:(NSError*)error +{ + AX_UNUSED_PARAM(webView); + AX_UNUSED_PARAM(navigation); + + AXLOGE("WKWebView navigation failed: {}", + error.localizedDescription ? [error.localizedDescription UTF8String] : ""); +} + +- (void)webViewWebContentProcessDidTerminate:(WKWebView*)webView +{ + AX_UNUSED_PARAM(webView); + + AXLOGE("WKWebView web content process terminated"); +} + #pragma WKUIDelegate // Implement js alert function. @@ -498,25 +692,11 @@ - (void)webView:(WKWebView*)webView { if (flags & ax::Node::FLAGS_TRANSFORM_DIRTY) { - - auto director = ax::Director::getInstance(); - auto renderView = director->getRenderView(); - auto windowSize = renderView->getWindowSize(); - - auto scaleFactor = [static_cast(renderView->getNativeDisplay()) contentScaleFactor]; - - auto canvasSize = director->getCanvasSize(); - - auto leftBottom = this->_webView->convertToWorldSpace(ax::Vec2::ZERO); - auto rightTop = this->_webView->convertToWorldSpace( - ax::Vec2(this->_webView->getContentSize().width, this->_webView->getContentSize().height)); - - auto x = (windowSize.width / 2 + (leftBottom.x - canvasSize.width / 2) * renderView->getScaleX()) / scaleFactor; - auto y = (windowSize.height / 2 - (rightTop.y - canvasSize.height / 2) * renderView->getScaleY()) / scaleFactor; - auto width = (rightTop.x - leftBottom.x) * renderView->getScaleX() / scaleFactor; - auto height = (rightTop.y - leftBottom.y) * renderView->getScaleY() / scaleFactor; - - [_uiWebViewWrapper setFrameWithX:x y:y width:width height:height]; + const auto uiRect = ax::ui::Helper::getNodeNativeWindowRect(_webView); + [_uiWebViewWrapper setFrameWithX:uiRect.origin.x + y:uiRect.origin.y + width:uiRect.size.width + height:uiRect.size.height]; } } diff --git a/axmol/ui/UIWebView/UIWebViewImpl-linux.cpp b/axmol/ui/WebView/WebViewImpl-linux.cpp similarity index 95% rename from axmol/ui/UIWebView/UIWebViewImpl-linux.cpp rename to axmol/ui/WebView/WebViewImpl-linux.cpp index 3fb578340742..7d75d2a072a7 100644 --- a/axmol/ui/UIWebView/UIWebViewImpl-linux.cpp +++ b/axmol/ui/WebView/WebViewImpl-linux.cpp @@ -26,7 +26,7 @@ Note: only support x11, wayland not implement yet ****************************************************************************/ -#include "axmol/ui/UIWebView/UIWebViewImpl-linux.h" +#include "axmol/ui/WebView/WebViewImpl-linux.h" #if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) && defined(AX_HAVE_WEBKIT2GTK) @@ -36,12 +36,12 @@ # include # include -# include "axmol/ui/UIWebView/UIWebView.h" +# include "axmol/ui/WebView/WebView.h" # include "axmol/base/Director.h" # include "axmol/platform/FileUtils.h" # include "axmol/platform/RenderView.h" # include "axmol/ui/UIHelper.h" -# include "axmol/ui/UIWebView/UIWebViewCommon.h" +# include "axmol/ui/WebView/WebViewCommon.h" # include # include @@ -255,18 +255,20 @@ static void init_gtk_platform_with_display(Display* x11Display) // this will instruct gdk to use XWayland layer on Wayland platforms if (webkit_dmabuf::is_wayland_display()) { - gdk_set_allowed_backends("x11"); + // Using gdk_set_allowed_backends("x11") is insufficient here because it only affects + // the main process. We use setenv to globally force the X11 backend so that WebKit + // child processes (e.g., WebProcess) inherit it, preventing Wayland-related crashes in gtk_init. + setenv("GDK_BACKEND", "x11", 1); } gtk_init(nullptr, nullptr); // instructing gdk display manager to use specified x11 display as default const char* display_string = DisplayString(x11Display); - GdkDisplay* x11_gdk_display = - display_string != nullptr ? gdk_display_open(display_string) : gdk_display_get_default(); + GdkDisplay* gdk_display = display_string != nullptr ? gdk_display_open(display_string) : gdk_display_get_default(); auto default_display_manager = gdk_display_manager_get(); - gdk_display_manager_set_default_display(default_display_manager, x11_gdk_display); + gdk_display_manager_set_default_display(default_display_manager, gdk_display); has_initiated = true; } @@ -278,8 +280,9 @@ class GTKWebKit public: GTKWebKit() : isXwayland(webkit_dmabuf::is_wayland_display()) { - m_X11Display = static_cast(ax::Director::getInstance()->getRenderView()->getNativeDisplay()); - m_ParentX11Window = reinterpret_cast(ax::Director::getInstance()->getRenderView()->getNativeWindow()); + auto renderView = ax::Director::getInstance()->getRenderView(); + m_X11Display = static_cast(renderView->getNativeDisplay()); + m_ParentX11Window = reinterpret_cast(renderView->getNativeWindow()); init_gtk_platform_with_display(m_X11Display); @@ -705,19 +708,19 @@ WebViewImpl::WebViewImpl(WebView* webView) : _createSucceeded(false), _gtkWebKit } return true; }, [this](std::string_view url) { - WebView::ccWebViewCallback didFinishLoading = _webView->getOnDidFinishLoading(); + WebView::WebViewCallback didFinishLoading = _webView->getOnDidFinishLoading(); if (didFinishLoading != nullptr) { didFinishLoading(_webView, url); } }, [this](std::string_view url) { - WebView::ccWebViewCallback didFailLoading = _webView->getOnDidFailLoading(); + WebView::WebViewCallback didFailLoading = _webView->getOnDidFailLoading(); if (didFailLoading != nullptr) { didFailLoading(_webView, url); } }, [this](std::string_view url) { - WebView::ccWebViewCallback onJsCallback = _webView->getOnJSCallback(); + WebView::WebViewCallback onJsCallback = _webView->getOnJSCallback(); if (onJsCallback != nullptr) { onJsCallback(_webView, url); @@ -867,7 +870,7 @@ void WebViewImpl::draw(Renderer* renderer, Mat4 const& transform, uint32_t flags } if (flags & Node::FLAGS_TRANSFORM_DIRTY) { - const auto uiRect = ax::ui::Helper::convertBoundingBoxToScreen(_webView); + const auto uiRect = ax::ui::Helper::getNodeNativeWindowRect(_webView); _gtkWebKit->setWebViewRect(static_cast(uiRect.origin.x), static_cast(uiRect.origin.y), static_cast(uiRect.size.width), static_cast(uiRect.size.height)); } diff --git a/axmol/ui/UIWebView/UIWebViewImpl-linux.h b/axmol/ui/WebView/WebViewImpl-linux.h similarity index 100% rename from axmol/ui/UIWebView/UIWebViewImpl-linux.h rename to axmol/ui/WebView/WebViewImpl-linux.h diff --git a/axmol/ui/UIWebView/UIWebViewImpl-win32.cpp b/axmol/ui/WebView/WebViewImpl-win32.cpp similarity index 98% rename from axmol/ui/UIWebView/UIWebViewImpl-win32.cpp rename to axmol/ui/WebView/WebViewImpl-win32.cpp index 14706336541b..7633689c9cdd 100644 --- a/axmol/ui/UIWebView/UIWebViewImpl-win32.cpp +++ b/axmol/ui/WebView/WebViewImpl-win32.cpp @@ -27,16 +27,18 @@ #if defined(_WIN32) && defined(AX_ENABLE_MSEDGE_WEBVIEW2) -# include "axmol/ui/UIWebView/UIWebViewImpl-win32.h" -# include "axmol/ui/UIWebView/UIWebView.h" -# include "axmol/ui/UIWebView/UIWebViewCommon.h" +# include "axmol/ui/WebView/WebViewImpl-win32.h" +# include "axmol/ui/WebView/WebView.h" +# include "axmol/ui/WebView/WebViewCommon.h" # include "axmol/base/Director.h" # include "axmol/platform/FileUtils.h" # include "axmol/platform/RenderView.h" # include "axmol/ui/UIHelper.h" # include "axmol/base/Utils.h" -# define WIN32_LEAN_AND_MEAN +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif # include # include # include @@ -463,19 +465,19 @@ WebViewImpl::WebViewImpl(WebView* webView) : _createSucceeded(false), _systemWeb } return true; }, [this](std::string_view url) { - WebView::ccWebViewCallback didFinishLoading = _webView->getOnDidFinishLoading(); + WebView::WebViewCallback didFinishLoading = _webView->getOnDidFinishLoading(); if (didFinishLoading != nullptr) { didFinishLoading(_webView, url); } }, [this](std::string_view url) { - WebView::ccWebViewCallback didFailLoading = _webView->getOnDidFailLoading(); + WebView::WebViewCallback didFailLoading = _webView->getOnDidFailLoading(); if (didFailLoading != nullptr) { didFailLoading(_webView, url); } }, [this](std::string_view url) { - WebView::ccWebViewCallback onJsCallback = _webView->getOnJSCallback(); + WebView::WebViewCallback onJsCallback = _webView->getOnJSCallback(); if (onJsCallback != nullptr) { onJsCallback(_webView, url); @@ -622,7 +624,7 @@ void WebViewImpl::draw(Renderer* renderer, Mat4 const& transform, uint32_t flags { if (_createSucceeded && (flags & Node::FLAGS_TRANSFORM_DIRTY)) { - const auto uiRect = ax::ui::Helper::convertBoundingBoxToScreen(_webView); + const auto uiRect = ax::ui::Helper::getNodeNativeWindowRect(_webView); _systemWebControl->setWebViewRect(static_cast(uiRect.origin.x), static_cast(uiRect.origin.y), static_cast(uiRect.size.width), static_cast(uiRect.size.height)); } diff --git a/axmol/ui/UIWebView/UIWebViewImpl-win32.h b/axmol/ui/WebView/WebViewImpl-win32.h similarity index 100% rename from axmol/ui/UIWebView/UIWebViewImpl-win32.h rename to axmol/ui/WebView/WebViewImpl-win32.h diff --git a/axmol/ui/UIWidget.cpp b/axmol/ui/Widget.cpp similarity index 70% rename from axmol/ui/UIWidget.cpp rename to axmol/ui/Widget.cpp index 1af6d391787d..a50c18d910bb 100644 --- a/axmol/ui/UIWidget.cpp +++ b/axmol/ui/Widget.cpp @@ -24,19 +24,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "axmol/ui/UIWidget.h" -#include "axmol/ui/UILayout.h" +#include "axmol/ui/Widget.h" +#include "axmol/ui/LayoutGroup.h" #include "axmol/ui/UIHelper.h" -#include "axmol/base/EventListenerTouch.h" -#include "axmol/base/EventListenerKeyboard.h" +#include "axmol/base/PointerEventListener.h" +#include "axmol/base/KeyboardEventListener.h" #include "axmol/base/Director.h" -#include "axmol/base/EventFocus.h" +#include "axmol/base/FocusEvent.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/ui/UILayoutComponent.h" +#include "axmol/ui/LayoutComponent.h" #include "axmol/renderer/Shaders.h" #include "axmol/scene/Camera.h" #include "axmol/2d/Sprite.h" -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/Scale9Sprite.h" namespace ax { @@ -61,7 +61,7 @@ class Widget::FocusNavigationController protected: void setFirstFocusedWidget(Widget* widget); - void onKeypadKeyPressed(EventKeyboard::KeyCode, Event*); + void onKeypadKeyPressed(KeyboardEvent* event); void addKeyboardEventListener(); void removeKeyboardEventListener(); @@ -69,7 +69,7 @@ class Widget::FocusNavigationController friend class Widget; private: - EventListenerKeyboard* _keyboardListener; + KeyboardEventListener* _keyboardListener; Widget* _firstFocusedWidget; bool _enableFocusNavigation; const int _keyboardEventPriority; @@ -80,26 +80,27 @@ Widget::FocusNavigationController::~FocusNavigationController() this->removeKeyboardEventListener(); } -void Widget::FocusNavigationController::onKeypadKeyPressed(EventKeyboard::KeyCode keyCode, Event* /*event*/) +void Widget::FocusNavigationController::onKeypadKeyPressed(KeyboardEvent* event) { if (_enableFocusNavigation && _firstFocusedWidget) { - if (keyCode == EventKeyboard::KeyCode::KEY_DPAD_DOWN) + auto keyCode = event->getKeyCode(); + if (keyCode == KeyboardEvent::KeyCode::KEY_DPAD_DOWN) { _firstFocusedWidget = _firstFocusedWidget->findNextFocusedWidget(Widget::FocusDirection::DOWN, _firstFocusedWidget); } - if (keyCode == EventKeyboard::KeyCode::KEY_DPAD_UP) + if (keyCode == KeyboardEvent::KeyCode::KEY_DPAD_UP) { _firstFocusedWidget = _firstFocusedWidget->findNextFocusedWidget(Widget::FocusDirection::UP, _firstFocusedWidget); } - if (keyCode == EventKeyboard::KeyCode::KEY_DPAD_LEFT) + if (keyCode == KeyboardEvent::KeyCode::KEY_DPAD_LEFT) { _firstFocusedWidget = _firstFocusedWidget->findNextFocusedWidget(Widget::FocusDirection::LEFT, _firstFocusedWidget); } - if (keyCode == EventKeyboard::KeyCode::KEY_DPAD_RIGHT) + if (keyCode == KeyboardEvent::KeyCode::KEY_DPAD_RIGHT) { _firstFocusedWidget = _firstFocusedWidget->findNextFocusedWidget(Widget::FocusDirection::RIGHT, _firstFocusedWidget); @@ -129,8 +130,8 @@ void Widget::FocusNavigationController::addKeyboardEventListener() { if (nullptr == _keyboardListener) { - _keyboardListener = EventListenerKeyboard::create(); - _keyboardListener->onKeyReleased = AX_CALLBACK_2(Widget::FocusNavigationController::onKeypadKeyPressed, this); + _keyboardListener = KeyboardEventListener::create(); + _keyboardListener->onKeyReleased = AX_CALLBACK_1(Widget::FocusNavigationController::onKeypadKeyPressed, this); EventDispatcher* dispatcher = Director::getInstance()->getEventDispatcher(); dispatcher->addEventListenerWithFixedPriority(_keyboardListener, _keyboardEventPriority); } @@ -151,14 +152,14 @@ Widget::FocusNavigationController* Widget::_focusNavigationController = nullptr; Widget::Widget() : _usingLayoutComponent(false) - , _unifySize(false) + , _autoSize(true) , _enabled(true) , _bright(true) - , _touchEnabled(false) + , _pointerEnabled(false) , _highlight(false) + , _hovered(false) , _affectByClipping(false) - , _ignoreSize(false) - , _propagateTouchEvents(true) + , _propagatePointerEvents(true) , _brightStyle(BrightStyle::NONE) , _sizeType(SizeType::ABSOLUTE) , _positionType(PositionType::ABSOLUTE) @@ -166,19 +167,15 @@ Widget::Widget() , _customSize(Vec2::ZERO) , _hitted(false) , _hittedByCamera(nullptr) - , _touchListener(nullptr) + , _hoveredByCamera(nullptr) + , _pointerEventListener(nullptr) , _flippedX(false) , _flippedY(false) , _layoutParameterType(LayoutParameter::Type::NONE) , _focused(false) , _focusEnabled(true) - , _touchEventListener(nullptr) - , _ccEventCallback(nullptr) , _callbackType("") , _callbackName("") - , _mouseEnabled(false) - , _mouseListener(nullptr) - , _mouseHitted(false) {} Widget::~Widget() @@ -188,9 +185,9 @@ Widget::~Widget() void Widget::cleanupWidget() { - // clean up _touchListener - _eventDispatcher->removeEventListener(_touchListener); - AX_SAFE_RELEASE_NULL(_touchListener); + // clean up _pointerEventListener + _eventDispatcher->removeEventListener(_pointerEventListener); + AX_SAFE_RELEASE_NULL(_pointerEventListener); // cleanup focused widget and focus navigation controller if (_focusedWidget == this) @@ -217,13 +214,13 @@ bool Widget::init() { if (ProtectedNode::init()) { - initRenderer(); + initRenderNode(); setBright(true); onFocusChanged = AX_CALLBACK_2(Widget::onFocusChange, this); onNextFocusedWidget = nullptr; this->setAnchorPoint(Vec2(0.5f, 0.5f)); - ignoreContentAdaptWithSize(true); + setAutoSize(true); return true; } @@ -247,7 +244,7 @@ void Widget::visit(Renderer* renderer, const Mat4& parentTransform, uint32_t par { if (_visible) { - adaptRenderers(); + updateLayout(); ProtectedNode::visit(renderer, parentTransform, parentFlags); } } @@ -263,7 +260,7 @@ void Widget::setEnabled(bool enabled) setBright(enabled); } -void Widget::initRenderer() {} +void Widget::initRenderNode() {} LayoutComponent* Widget::getOrCreateLayoutComponent() { @@ -285,17 +282,11 @@ void Widget::setContentSize(const Vec2& contentSize) { return; } - ProtectedNode::setContentSize(contentSize); _customSize = contentSize; - if (_unifySize) - { - // unify size logic - } - else if (_ignoreSize) - { - ProtectedNode::setContentSize(getVirtualRendererSize()); - } + + ProtectedNode::setContentSize(_autoSize ? resolvePreferredSize(_customSize) : _customSize); + if (!_usingLayoutComponent && _running) { Widget* widgetParent = getWidgetParent(); @@ -349,14 +340,11 @@ void Widget::setSizePercent(const Vec2& percent) cSize = Vec2(_parent->getContentSize().width * percent.x, _parent->getContentSize().height * percent.y); } } - if (_ignoreSize) - { - this->setContentSize(getVirtualRendererSize()); - } + if (_autoSize) + setContentSize(resolvePreferredSize(cSize)); else - { - this->setContentSize(cSize); - } + setContentSize(cSize); + _customSize = cSize; } } @@ -374,9 +362,9 @@ void Widget::updateSizeAndPosition(const Vec2& parentSize) { case SizeType::ABSOLUTE: { - if (_ignoreSize) + if (_autoSize) { - this->setContentSize(getVirtualRendererSize()); + this->setContentSize(resolvePreferredSize(_customSize)); } else { @@ -398,9 +386,9 @@ void Widget::updateSizeAndPosition(const Vec2& parentSize) case SizeType::PERCENT: { Vec2 cSize = Vec2(parentSize.width * _sizePercent.x, parentSize.height * _sizePercent.y); - if (_ignoreSize) + if (_autoSize) { - this->setContentSize(getVirtualRendererSize()); + this->setContentSize(resolvePreferredSize(cSize)); } else { @@ -493,34 +481,6 @@ Widget::SizeType Widget::getSizeType() const return _sizeType; } -void Widget::ignoreContentAdaptWithSize(bool ignore) -{ - if (_unifySize) - { - this->setContentSize(_customSize); - return; - } - if (_ignoreSize == ignore) - { - return; - } - _ignoreSize = ignore; - if (_ignoreSize) - { - Vec2 s = getVirtualRendererSize(); - this->setContentSize(s); - } - else - { - this->setContentSize(_customSize); - } -} - -bool Widget::isIgnoreContentAdaptWithSize() const -{ - return _ignoreSize; -} - const Vec2& Widget::getCustomSize() const { return _customSize; @@ -537,7 +497,7 @@ const Vec2& Widget::getSizePercent() return _sizePercent; } -Node* Widget::getVirtualRenderer() +Node* Widget::getRenderNode() { return this; } @@ -557,86 +517,55 @@ void Widget::onSizeChanged() } } -Vec2 Widget::getVirtualRendererSize() const +Vec2 Widget::resolvePreferredSize(const Vec2& sizeHint) const { - return _contentSize; + return sizeHint; } -void Widget::updateContentSizeWithTextureSize(const Vec2& size) +void Widget::updateContentSize() { - if (_unifySize) - { - this->setContentSize(size); - return; - } - if (_ignoreSize) - { - this->setContentSize(size); - } - else - { - this->setContentSize(_customSize); - } -} + auto preferredSize = resolvePreferredSize(_customSize); -void Widget::setTouchEnabled(bool enable) -{ - if (enable == _touchEnabled) - { - return; - } - _touchEnabled = enable; - if (_touchEnabled) + if (_autoSize) { - _touchListener = EventListenerTouchOneByOne::create(); - AX_SAFE_RETAIN(_touchListener); - _touchListener->setSwallowTouches(true); - _touchListener->onTouchBegan = AX_CALLBACK_2(Widget::onTouchBegan, this); - _touchListener->onTouchMoved = AX_CALLBACK_2(Widget::onTouchMoved, this); - _touchListener->onTouchEnded = AX_CALLBACK_2(Widget::onTouchEnded, this); - _touchListener->onTouchCancelled = AX_CALLBACK_2(Widget::onTouchCancelled, this); - _eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this); + // Auto size mode: update to the preferred size based on content + ProtectedNode::setContentSize(preferredSize); } else { - _eventDispatcher->removeEventListener(_touchListener); - AX_SAFE_RELEASE_NULL(_touchListener); + // Fixed size mode: keep custom size unchanged + ProtectedNode::setContentSize(_customSize); } } -bool Widget::isTouchEnabled() const -{ - return _touchEnabled; -} - -void Widget::setMouseEnabled(bool enable) +void Widget::setPointerEnabled(bool enable) { - if (enable == _mouseEnabled) + if (enable == _pointerEnabled) { return; } - _mouseEnabled = enable; - if (_mouseEnabled) + _pointerEnabled = enable; + if (_pointerEnabled) { - _mouseListener = EventListenerMouse::create(); - AX_SAFE_RETAIN(_mouseListener); - _mouseListener->setSwallowMouse(true); - _mouseListener->onMouseUp = AX_CALLBACK_1(Widget::onMouseUp, this); - _mouseListener->onMouseDown = AX_CALLBACK_1(Widget::onMouseDown, this); - _mouseListener->onMouseMove = AX_CALLBACK_1(Widget::onMouseMove, this); - _mouseListener->onMouseScroll = AX_CALLBACK_1(Widget::onMouseScroll, this); - _eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this); + _pointerEventListener = PointerEventListener::create(); + AX_SAFE_RETAIN(_pointerEventListener); + _pointerEventListener->onPointerDown = AX_CALLBACK_1(Widget::onPointerDown, this); + _pointerEventListener->onPointerMove = AX_CALLBACK_1(Widget::dispatchPointerMove, this); + _pointerEventListener->onPointerUp = AX_CALLBACK_1(Widget::onPointerUp, this); + _pointerEventListener->onPointerCancel = AX_CALLBACK_1(Widget::onPointerCancel, this); + _pointerEventListener->onPointerScroll = AX_CALLBACK_1(Widget::onPointerScroll, this); + _eventDispatcher->addEventListenerWithSceneGraphPriority(_pointerEventListener, this); } else { - _eventDispatcher->removeEventListener(_mouseListener); - AX_SAFE_RELEASE_NULL(_mouseListener); + _eventDispatcher->removeEventListener(_pointerEventListener); + AX_SAFE_RELEASE_NULL(_pointerEventListener); } } -bool Widget::isMouseEnabled() const +bool Widget::isPointerEnabled() const { - return _mouseEnabled; + return _pointerEnabled; } bool Widget::isHighlighted() const @@ -768,105 +697,132 @@ bool Widget::isAncestorsEnabled() return parentWidget->isAncestorsEnabled(); } -void Widget::setPropagateTouchEvents(bool isPropagate) +void Widget::setPropagatePointerEvents(bool isPropagate) { - _propagateTouchEvents = isPropagate; + _propagatePointerEvents = isPropagate; } -bool Widget::isPropagateTouchEvents() const +bool Widget::isPropagatePointerEvents() const { - return _propagateTouchEvents; + return _propagatePointerEvents; } -void Widget::setSwallowTouches(bool swallow) +bool Widget::onPointerDown(PointerEvent* event) { - if (_touchListener) - { - _touchListener->setSwallowTouches(swallow); - } -} + if (!event || !event->isPrimaryPressed()) + return false; -bool Widget::isSwallowTouches() const -{ - if (_touchListener) - { - return _touchListener->isSwallowTouches(); - } - return false; -} + auto camera = event->getCamera(); + if (!camera) + return false; -bool Widget::onTouchBegan(Touch* touch, Event* /*unusedEvent*/) -{ - _hitted = false; - if (isVisible() && isEnabled() && isAncestorsEnabled() && isAncestorsVisible(this)) + RefPtr guard(this); + + // Fallback for old/manual paths where onPointerHitTest() was not called. + if (!_hitted || _hittedByCamera != camera) { - _touchBeganPosition = touch->getLocation(); - auto camera = Camera::getVisitingCamera(); - if (hitTest(_touchBeganPosition, camera, nullptr)) + _hitted = false; + _hittedByCamera = nullptr; + _pointerDownPosition = event->getLocation(); + + if (isPointerInside(event, camera, nullptr)) { - if (isClippingParentContainsPoint(_touchBeganPosition)) - { - _hittedByCamera = camera; - _hitted = true; - } + _hittedByCamera = camera; + _hitted = true; } } + if (!_hitted) - { return false; - } + setHighlighted(true); - /* - * Propagate touch events to its parents - */ - if (_propagateTouchEvents) + if (_propagatePointerEvents) { - this->propagateTouchEvent(TouchEventType::BEGAN, this, touch); + this->propagatePointerEvent(this, event); } pushDownEvent(); return true; } -void Widget::propagateTouchEvent(ax::ui::Widget::TouchEventType event, ax::ui::Widget* sender, ax::Touch* touch) +void Widget::propagatePointerEvent(ax::ui::Widget* sender, PointerEvent* event) { Widget* widgetParent = getWidgetParent(); if (widgetParent) { widgetParent->_hittedByCamera = _hittedByCamera; - widgetParent->interceptTouchEvent(event, sender, touch); + widgetParent->interceptPointerEvent(sender, event); widgetParent->_hittedByCamera = nullptr; } } -void Widget::onTouchMoved(Touch* touch, Event* /*unusedEvent*/) +void Widget::onPointerMove(PointerEvent* event) { - _touchMovePosition = touch->getLocation(); + RefPtr guard(this); + + _pointerMovePosition = event->getLocation(); - setHighlighted(hitTest(_touchMovePosition, _hittedByCamera, nullptr)); + setHighlighted(isPointerInside(event, _hittedByCamera, nullptr)); - /* - * Propagate touch events to its parents - */ - if (_propagateTouchEvents) + if (_propagatePointerEvents) { - this->propagateTouchEvent(TouchEventType::MOVED, this, touch); + this->propagatePointerEvent(this, event); } moveEvent(); } -void Widget::onTouchEnded(Touch* touch, Event* /*unusedEvent*/) +void Widget::dispatchPointerMove(PointerEvent* event) { - _touchEndPosition = touch->getLocation(); + RefPtr guard(this); - /* - * Propagate touch events to its parents - */ - if (_propagateTouchEvents) + if (event->isPrimaryCaptured()) { - this->propagateTouchEvent(TouchEventType::ENDED, this, touch); + if (_hitted) + onPointerMove(event); + return; + } + + auto camera = event->getCamera(); + if (!camera) + return; + + const bool hit = isPointerInside(event, camera, nullptr); + + if (hit) + { + if (!_hovered) + { + _hovered = true; + _hoveredByCamera = camera; + hoverEnterEvent(); + } + else + { + _hoveredByCamera = camera; + } + + hoverMoveEvent(); + event->stopPropagation(); + } + else if (_hovered) + { + _hovered = false; + _hoveredByCamera = nullptr; + hoverExitEvent(); + } +} + +void Widget::onPointerUp(PointerEvent* event) +{ + RefPtr guard(this); + + _pointerUpPosition = event->getLocation(); + + if (_propagatePointerEvents) + { + this->propagatePointerEvent(this, event); } bool highlight = _highlight; @@ -880,166 +836,244 @@ void Widget::onTouchEnded(Touch* touch, Event* /*unusedEvent*/) { cancelUpEvent(); } + + _hitted = false; + + const Camera* hoverCamera = _hoveredByCamera ? _hoveredByCamera : _hittedByCamera; + if (_hovered && !isPointerInside(event, hoverCamera, nullptr)) + { + _hovered = false; + _hoveredByCamera = nullptr; + hoverExitEvent(); + } + + _hittedByCamera = nullptr; } -void Widget::onTouchCancelled(Touch* touch, Event* /*unusedEvent*/) +void Widget::onPointerCancel(PointerEvent* event) { - /* - * Propagate touch events to its parents - */ - if (_propagateTouchEvents) + RefPtr guard(this); + + if (_propagatePointerEvents) { - this->propagateTouchEvent(TouchEventType::CANCELED, this, touch); + this->propagatePointerEvent(this, event); } setHighlighted(false); cancelUpEvent(); + + _hitted = false; + _hittedByCamera = nullptr; + + if (_hovered) + { + _hovered = false; + _hoveredByCamera = nullptr; + hoverExitEvent(); + } +} + +bool Widget::onPointerScroll(PointerEvent* pointerEvent) +{ + return false; } void Widget::pushDownEvent() { - this->retain(); - if (_touchEventCallback) + RefPtr guard(this); + + if (_pointerEventHandler) { - _touchEventCallback(this, TouchEventType::BEGAN); + _pointerEventHandler(this, PointerPhase::Down); } - - this->release(); } void Widget::moveEvent() { - this->retain(); - if (_touchEventCallback) + RefPtr guard(this); + + if (_pointerEventHandler) { - _touchEventCallback(this, TouchEventType::MOVED); + _pointerEventHandler(this, PointerPhase::Move); } +} + +void Widget::hoverEnterEvent() +{ + RefPtr guard(this); - this->release(); + if (_hoverEventHandler) + { + _hoverEventHandler(this, HoverEventType::ENTER); + } +} + +void Widget::hoverMoveEvent() +{ + RefPtr guard(this); + + if (_hoverEventHandler) + { + _hoverEventHandler(this, HoverEventType::MOVE); + } +} + +void Widget::hoverExitEvent() +{ + RefPtr guard(this); + + if (_hoverEventHandler) + { + _hoverEventHandler(this, HoverEventType::EXIT); + } } void Widget::releaseUpEvent() { - this->retain(); + RefPtr guard(this); if (isFocusEnabled()) { requestFocus(); } - if (_touchEventCallback) + if (_pointerEventHandler) { - _touchEventCallback(this, TouchEventType::ENDED); + _pointerEventHandler(this, PointerPhase::Up); } - if (_clickEventListener) + if (_clickEventHandler) { - _clickEventListener(this); + _clickEventHandler(this); } - this->release(); } void Widget::cancelUpEvent() { - this->retain(); - if (_touchEventCallback) + RefPtr guard(this); + + if (_pointerEventHandler) { - _touchEventCallback(this, TouchEventType::CANCELED); + _pointerEventHandler(this, PointerPhase::Cancel); } - - this->release(); } -void Widget::setSwallowMouse(bool swallow) +void Widget::addTouchEventListener(const TouchEventHandler& handler) { - if (_mouseListener) + if (!handler) { - _mouseListener->setSwallowMouse(swallow); + _pointerEventHandler = nullptr; + return; } + + _pointerEventHandler = [handler](Object* sender, PointerPhase phase) { + TouchEventType type = TouchEventType::CANCELED; + switch (phase) + { + case PointerPhase::Down: + type = TouchEventType::BEGAN; + break; + case PointerPhase::Move: + type = TouchEventType::MOVED; + break; + case PointerPhase::Up: + type = TouchEventType::ENDED; + break; + case PointerPhase::Cancel: + type = TouchEventType::CANCELED; + break; + } + handler(sender, type); + }; } -bool Widget::isSwallowMouse() const +void Widget::addCCSEventListener(const WidgetEventCallback& callback) { - if (_mouseListener) - { - return _mouseListener->isSwallowMouse(); - } - return false; + _customEventCallback = callback; } -bool Widget::onMouseEvent(Event* event) +bool Widget::onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) { - _mouseHitted = false; - if (isVisible() && isEnabled() && isAncestorsEnabled() && isAncestorsVisible(this)) + if (!event || !camera) + return false; + + const auto phase = event->getPhase(); + + // PointerDown: this is the real press hit-test. + if (phase == InputPhase::PointerDown) { - auto scrollPosition = static_cast(event)->getLocation(); - auto camera = Camera::getVisitingCamera(); - if (hitTest(scrollPosition, camera, nullptr)) - { - if (isClippingParentContainsPoint(scrollPosition)) - { - _mouseHitted = true; - } - } + _hitted = false; + _hittedByCamera = nullptr; + + if (!event->isPrimaryPressed()) + return false; + + if (!isPointerInside(event, camera, outHitPoint)) + return false; + + // Cache the down hit so onPointerDown() does not repeat hitTestSelf(). + _pointerDownPosition = event->getLocation(); + _hittedByCamera = camera; + _hitted = true; + + return true; } - if (!_mouseHitted) + + // Uncaptured move is used for hover. + // Need to keep dispatching to the previous hovered widget so it can emit hover exit. + if (phase == InputPhase::PointerMove && !event->isPrimaryCaptured()) { - return false; + const bool hit = isPointerInside(event, camera, outHitPoint); + return hit || (_hovered && _hoveredByCamera == camera); } - return true; -} + // Scroll should only go to widgets under the pointer. + if (phase == InputPhase::PointerScroll) + { + return isPointerInside(event, camera, outHitPoint); + } -bool Widget::onMouseUp(Event* event) -{ - return onMouseEvent(event); + return isPointerInside(event, camera, outHitPoint); } -bool Widget::onMouseDown(Event* event) +bool Widget::isPointerInside(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) { - return onMouseEvent(event); -} + if (!event || !camera) + return false; -bool Widget::onMouseMove(Event* event) -{ - return onMouseEvent(event); -} + if (!isVisible() || !isEnabled() || !isAncestorsEnabled() || !isAncestorsVisible(this)) + return false; -bool Widget::onMouseScroll(Event* event) -{ - return onMouseEvent(event); -} + const Vec2 pt = event->getLocation(); -void Widget::addTouchEventListener(const ccWidgetTouchCallback& callback) -{ - this->_touchEventCallback = callback; -} + if (!hitTestSelf(pt, camera, outHitPoint)) + return false; -void Widget::addClickEventListener(const ccWidgetClickCallback& callback) -{ - this->_clickEventListener = callback; -} + if (!isClippingParentContainsPoint(pt, camera)) + return false; -void Widget::addCCSEventListener(const ccWidgetEventCallback& callback) -{ - this->_ccEventCallback = callback; + return true; } -bool Widget::hitTest(const Vec2& pt, const Camera* camera, Vec3* p) const +bool Widget::hitTestSelf(const Vec2& pt, const Camera* camera, Vec3* p) const { + if (!camera) + return false; + Rect rect; rect.size = getContentSize(); - return isScreenPointInRect(pt, camera, getWorldToNodeTransform(), rect, p); + return camera->isWorldPointInRect(pt, getWorldToNodeTransform(), rect, p); } -bool Widget::isClippingParentContainsPoint(const Vec2& pt) +bool Widget::isClippingParentContainsPoint(const Vec2& pt, const Camera* camera) { _affectByClipping = false; Node* parent = getParent(); Widget* clippingParent = nullptr; + while (parent) { - Layout* layoutParent = dynamic_cast(parent); + LayoutGroup* layoutParent = dynamic_cast(parent); if (layoutParent) { if (layoutParent->isClippingEnabled()) @@ -1049,39 +1083,31 @@ bool Widget::isClippingParentContainsPoint(const Vec2& pt) break; } } + parent = parent->getParent(); } if (!_affectByClipping) - { return true; - } - if (clippingParent) - { - bool bRet = false; - auto camera = Camera::getVisitingCamera(); - // Camera isn't null means in touch begin process, otherwise use _hittedByCamera instead. - if (clippingParent->hitTest(pt, (camera ? camera : _hittedByCamera), nullptr)) - { - bRet = true; - } - if (bRet) - { - return clippingParent->isClippingParentContainsPoint(pt); - } + if (!clippingParent || !camera) return false; + + if (clippingParent->hitTestSelf(pt, camera, nullptr)) + { + return clippingParent->isClippingParentContainsPoint(pt, camera); } - return true; + + return false; } -void Widget::interceptTouchEvent(ax::ui::Widget::TouchEventType event, ax::ui::Widget* sender, Touch* touch) +void Widget::interceptPointerEvent(ax::ui::Widget* sender, PointerEvent* event) { Widget* widgetParent = getWidgetParent(); if (widgetParent) { widgetParent->_hittedByCamera = _hittedByCamera; - widgetParent->interceptTouchEvent(event, sender, touch); + widgetParent->interceptPointerEvent(sender, event); widgetParent->_hittedByCamera = nullptr; } } @@ -1230,17 +1256,17 @@ float Widget::getTopBoundary() const const Vec2& Widget::getTouchBeganPosition() const { - return _touchBeganPosition; + return _pointerDownPosition; } const Vec2& Widget::getTouchMovePosition() const { - return _touchMovePosition; + return _pointerMovePosition; } const Vec2& Widget::getTouchEndPosition() const { - return _touchEndPosition; + return _pointerUpPosition; } void Widget::setLayoutParameter(LayoutParameter* parameter) @@ -1297,13 +1323,12 @@ void Widget::copyProperties(Widget* widget) setEnabled(widget->isEnabled()); setVisible(widget->isVisible()); setBright(widget->isBright()); - setTouchEnabled(widget->isTouchEnabled()); - setMouseEnabled(widget->isMouseEnabled()); + setPointerEnabled(widget->isPointerEnabled()); setLocalZOrder(widget->getLocalZOrder()); setTag(widget->getTag()); setName(widget->getName()); setActionTag(widget->getActionTag()); - _ignoreSize = widget->_ignoreSize; + _autoSize = widget->_autoSize; this->setContentSize(widget->_contentSize); _customSize = widget->_customSize; _sizeType = widget->getSizeType(); @@ -1323,12 +1348,12 @@ void Widget::copyProperties(Widget* widget) setOpacity(widget->getOpacity()); setCascadeColorEnabled(widget->isCascadeColorEnabled()); setCascadeOpacityEnabled(widget->isCascadeOpacityEnabled()); - _touchEventCallback = widget->_touchEventCallback; - _touchEventListener = widget->_touchEventListener; - _clickEventListener = widget->_clickEventListener; - _focused = widget->_focused; - _focusEnabled = widget->_focusEnabled; - _propagateTouchEvents = widget->_propagateTouchEvents; + _pointerEventHandler = widget->_pointerEventHandler; + _clickEventHandler = widget->_clickEventHandler; + _customEventCallback = widget->_customEventCallback; + _focused = widget->_focused; + _focusEnabled = widget->_focusEnabled; + _propagatePointerEvents = widget->_propagatePointerEvents; copySpecialProperties(widget); @@ -1460,15 +1485,15 @@ Widget* Widget::findNextFocusedWidget(FocusDirection direction, Widget* current) { if (nullptr == onNextFocusedWidget || nullptr == onNextFocusedWidget(direction)) { - if (this->isFocused() || dynamic_cast(current)) + if (this->isFocused() || dynamic_cast(current)) { Node* parent = this->getParent(); - Layout* layout = dynamic_cast(parent); + LayoutGroup* layout = dynamic_cast(parent); if (nullptr == layout) { // the outer layout's default behaviour is : loop focus - if (dynamic_cast(current)) + if (dynamic_cast(current)) { return current->findNextFocusedWidget(direction, current); } @@ -1514,7 +1539,7 @@ void Widget::dispatchFocusEvent(ax::ui::Widget* widgetLoseFocus, ax::ui::Widget* widgetLoseFocus->onFocusChanged(widgetLoseFocus, widgetGetFocus); } - EventFocus event(widgetLoseFocus, widgetGetFocus); + FocusEvent event(widgetLoseFocus, widgetGetFocus); auto dispatcher = _director->getEventDispatcher(); dispatcher->dispatchEvent(&event); } @@ -1573,14 +1598,31 @@ void Widget::enableDpadNavigation(bool enable) } } -bool Widget::isUnifySizeEnabled() const +void Widget::setAutoSize(bool enable) { - return _unifySize; -} + if (_autoSize == enable) + return; -void Widget::setUnifySizeEnabled(bool enable) -{ - _unifySize = enable; + _autoSize = enable; + + if (!_autoSize) + { + // Switching to fixed size mode: use the explicit custom size when one + // was provided while auto-size was active. If there is no custom size + // yet, preserve the current auto-sized content size. + if (_customSize.equals(Vec2::ZERO)) + _customSize = getContentSize(); + + ProtectedNode::setContentSize(_customSize); + onSizeChanged(); + } + else + { + // Switching to auto size mode: update to content size immediately + ProtectedNode::setContentSize(this->resolvePreferredSize(_customSize)); + + onSizeChanged(); + } } void Widget::setLayoutComponentEnabled(bool enable) diff --git a/axmol/ui/UIWidget.h b/axmol/ui/Widget.h similarity index 75% rename from axmol/ui/UIWidget.h rename to axmol/ui/Widget.h index 8347ed5fc7c4..862734ec1ce7 100644 --- a/axmol/ui/UIWidget.h +++ b/axmol/ui/Widget.h @@ -27,7 +27,7 @@ THE SOFTWARE. #pragma once #include "axmol/2d/ProtectedNode.h" -#include "axmol/ui/UILayoutParameter.h" +#include "axmol/ui/LayoutParameter.h" #include "axmol/ui/GUIDefine.h" #include "axmol/ui/GUIExport.h" #include "axmol/base/Map.h" @@ -39,14 +39,20 @@ THE SOFTWARE. namespace ax { -class EventListenerTouchOneByOne; -class EventListenerMouse; +class PointerEventListener; class Camera; namespace ui { class LayoutComponent; +enum class FontType +{ + SYSTEM, + TTF, + BMFONT +}; + /** *@brief Base class for all ui widgets. * This class inherent from `ProtectedNode` and `LayoutParameterProtocol`. @@ -85,14 +91,29 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol }; /** - * Touch event type. + * Pointer event phase. */ + enum class PointerPhase + { + Down, + Move, + Up, + Cancel + }; + + enum class HoverEventType + { + ENTER, + MOVE, + EXIT + }; + enum class TouchEventType { BEGAN, MOVED, ENDED, - CANCELED + CANCELED, }; /** @@ -117,18 +138,21 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol }; /** - * Widget touch event callback. + * Widget pointer event callback. */ - typedef std::function ccWidgetTouchCallback; + using PointerEventHandler = std::function; + using WidgetTouchCallback = std::function; + using TouchEventHandler = WidgetTouchCallback; + using WidgetHoverCallback = std::function; + using HoverEventHandler = WidgetHoverCallback; /** * Widget click event callback. */ - typedef std::function ccWidgetClickCallback; - /** - * Widget custom event callback. - * It is mainly used together with Cocos Studio. - */ - typedef std::function ccWidgetEventCallback; + using WidgetClickCallback = std::function; + using ClickEventHandler = WidgetClickCallback; + using WidgetEventCallback = std::function; + using CustomEventCallback = WidgetEventCallback; + /** * Default constructor * @lua new @@ -188,7 +212,7 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol * * @param enabled True if the widget is touch enabled, false if the widget is touch disabled. */ - virtual void setTouchEnabled(bool enabled); + virtual void setPointerEnabled(bool enabled); /** * To set the bright style of widget. @@ -200,28 +224,16 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol */ void setBrightStyle(BrightStyle style); - /** - * Sets whether the widget is mouse enabled. - * - * The default value is false, a widget is default to mouse disabled. - * - * @param enabled True if the widget is mouse enabled, false if the widget is mouse disabled. - */ - virtual void setMouseEnabled(bool enabled); - - /** - * Determines if the widget is touch enabled - * - * @return true if the widget is touch enabled, false if the widget is touch disabled. - */ - bool isTouchEnabled() const; + /// @brief Get the bright style of widget + /// @return + BrightStyle getBrightStyle() const { return _brightStyle; } /** - * Determines if the widget is mouse enabled + * Determines if the widget is pointer enabled * - * @return true if the widget is mouse enabled, false if the widget is mouse disabled. + * @return true if the widget is pointer enabled, false if the widget is pointer disabled. */ - bool isMouseEnabled() const; + bool isPointerEnabled() const; /** * Determines if the widget is highlighted @@ -268,22 +280,32 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol void visit(ax::Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags) override; /** - * Set a callback to touch vent listener. - *@param callback The callback in `ccWidgetEventCallback.` + * Set a callback to pointer event listener. + *@param callback The callback in `PointerEventHandler.` */ - void addTouchEventListener(const ccWidgetTouchCallback& callback); + void addPointerEventListener(const PointerEventHandler& handler) { _pointerEventHandler = handler; } + void addTouchEventListener(const TouchEventHandler& handler); + + /** + * Set a callback for pointer hover events. + */ + void addHoverEventListener(const HoverEventHandler& handler) { _hoverEventHandler = handler; } /** * Set a click event handler to the widget. - * @param callback The callback in `ccWidgetClickCallback`. + * @param callback The handler in `ClickEventHandler`. */ - void addClickEventListener(const ccWidgetClickCallback& callback); + void addClickEventListener(const ClickEventHandler& handler) { _clickEventHandler = handler; } + /** - * Set a event handler to the widget in order to use cocostudio editor and framework - * @param callback The callback in `ccWidgetEventCallback`. + * Set a custom event callback for Cocos Studio compatibility. + * @param callback The callback in `WidgetEventCallback`. * @lua NA */ - virtual void addCCSEventListener(const ccWidgetEventCallback& callback); + virtual void addCCSEventListener(const WidgetEventCallback& callback); + + void setTouchEnabled(bool enable) { setPointerEnabled(enable); } + bool isTouchEnabled() const { return isPointerEnabled(); } /** * Changes the position (x,y) of the widget in OpenGL coordinates @@ -388,7 +410,7 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol * @param pt A point in `Vec2`. * @return true if the point is in parent's area, false otherwise. */ - bool isClippingParentContainsPoint(const Vec2& pt); + bool isClippingParentContainsPoint(const Vec2& pt, const Camera* camera); /** * Gets the touch began point of widget when widget is selected. @@ -457,70 +479,6 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol */ const Vec2& getSizePercent(); - /** - * Checks a point is in widget's content space. - * This function is used for determining touch area of widget. - * - * @param pt The point in `Vec2`. - * @param camera The camera look at widget, used to convert GL screen point to near/far plane. - * @param p Point to a Vec3 for store the intersect point, if don't need them set to nullptr. - * @return true if the point is in widget's content space, false otherwise. - */ - virtual bool hitTest(const Vec2& pt, const Camera* camera, Vec3* p) const; - - /** - * A callback which will be called when touch began event is issued. - *@param touch The touch info. - *@param unusedEvent The touch event info. - *@return True if user want to handle touches, false otherwise. - */ - virtual bool onTouchBegan(Touch* touch, Event* unusedEvent); - - /** - * A callback which will be called when touch moved event is issued. - *@param touch The touch info. - *@param unusedEvent The touch event info. - */ - virtual void onTouchMoved(Touch* touch, Event* unusedEvent); - - /** - * A callback which will be called when touch ended event is issued. - *@param touch The touch info. - *@param unusedEvent The touch event info. - */ - virtual void onTouchEnded(Touch* touch, Event* unusedEvent); - - /** - * A callback which will be called when touch cancelled event is issued. - *@param touch The touch info. - *@param unusedEvent The touch event info. - */ - virtual void onTouchCancelled(Touch* touch, Event* unusedEvent); - - /** - * A callback which will be called when a mouse up event is issued. - *@param event The mouse event info. - */ - virtual bool onMouseUp(Event* event); - - /** - * A callback which will be called when a mouse down event is issued. - *@param event The mouse event info. - */ - virtual bool onMouseDown(Event* event); - - /** - * A callback which will be called when a mouse move event is issued. - *@param event The mouse event info. - */ - virtual bool onMouseMove(Event* event); - - /** - * A callback which will be called when a mouse scroll event is issued. - *@param event The mouse event info. - */ - virtual bool onMouseScroll(Event* event); - /** * Sets a LayoutParameter to widget. * @@ -538,35 +496,20 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol LayoutParameter* getLayoutParameter() const override; /** - * Toggle whether ignore user defined content size for widget. - * Set true will ignore user defined content size which means - * the widget size is always equal to the return value of `getVirtualRendererSize`. - * - * @param ignore set member variable _ignoreSize to ignore - */ - virtual void ignoreContentAdaptWithSize(bool ignore); - - /** - * Query whether the widget ignores user defined content size or not + * Gets the internal render node of widget. * - * @return True means ignore user defined content size, false otherwise. - */ - bool isIgnoreContentAdaptWithSize() const; - - /** - * Gets the inner Renderer node of widget. - * - * For example, a button's Virtual Renderer is it's texture renderer. + * For example, a button's render node is its texture sprite. * * @return Node pointer. */ - virtual Node* getVirtualRenderer(); + virtual Node* getRenderNode(); /** - * Get the virtual renderer's size - *@return Widget virtual renderer size. + * Gets the preferred size of the internal render node. + * This is equivalent to getRenderNode()->getContentSize(). + * @return The render node's content size. */ - virtual Vec2 getVirtualRendererSize() const; + Vec2 getPreferredSize() const { return resolvePreferredSize(_contentSize); } /** * Returns the string representation of widget class name @@ -617,7 +560,7 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol * @param isPropagate True to allow propagation, false otherwise. * @since v3.3 */ - void setPropagateTouchEvents(bool isPropagate); + void setPropagatePointerEvents(bool isPropagate); /** * Return whether the widget is propagate touch events to its parents or not @@ -625,35 +568,7 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol * @since v3.3 */ - bool isPropagateTouchEvents() const; - - /** - * Toggle widget swallow touch option. - * @brief Specify widget to swallow touches or not - * @param swallow True to swallow touch, false otherwise. - * @since v3.3 - */ - void setSwallowTouches(bool swallow); - - /** - * Return whether the widget is swallowing touch or not - * @return Whether touch is swallowed. - * @since v3.3 - */ - bool isSwallowTouches() const; - - /** - * Toggle widget swallow mouse option. - * @brief Specify widget to swallow mouse or not - * @param swallow True to swallow mouse, false otherwise. - */ - void setSwallowMouse(bool swallow); - - /** - * Return whether the widget is swallowing mouse or not - * @return Whether mouse is swallowed. - */ - bool isSwallowMouse() const; + bool isPropagatePointerEvents() const; /** * Query whether widget is focused or not. @@ -717,16 +632,17 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol std::function onNextFocusedWidget; /** - *Toggle use unify size. - *@param enable True to use unify size, false otherwise. + * Toggle auto size mode. + * When enabled, the widget will automatically adapt its size to its content (virtual renderer). + * @param enable True to enable auto size, false to use custom size. */ - void setUnifySizeEnabled(bool enable); + virtual void setAutoSize(bool enable); /** - * Query whether unify size enable state. - *@return true represent the widget use Unify size, false represent the widget couldn't use Unify size + * Query whether auto size mode is enabled. + * @return true if auto size is enabled, false otherwise. */ - bool isUnifySizeEnabled() const; + bool isAutoSize() const { return _autoSize; } /** * Set callback name. @@ -776,12 +692,12 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol * @param parent * @param point */ - virtual void interceptTouchEvent(TouchEventType event, Widget* sender, Touch* touch); + virtual void interceptPointerEvent(Widget* sender, PointerEvent* event); /** *@brief Propagate touch events to its parents */ - void propagateTouchEvent(TouchEventType event, Widget* sender, Touch* touch); + void propagatePointerEvent(Widget* sender, PointerEvent* event); /** * This method is called when a focus change event happens @@ -791,18 +707,74 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol void onFocusChange(Widget* widgetLostFocus, Widget* widgetGetFocus); /** - * Dispatch a EventFocus through a EventDispatcher + * Dispatch a FocusEvent through a EventDispatcher *@param widgetLoseFocus The widget which lose its focus *@param widgetGetFocus he widget which get its focus */ void dispatchFocusEvent(Widget* widgetLoseFocus, Widget* widgetGetFocus); protected: + bool onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) override; + + /** + * Checks a point is in widget's content space. + * This function is used for determining touch area of widget. + * + * @param pt The point in `Vec2`. + * @param camera The camera look at widget, used to convert GL screen point to near/far plane. + * @param p Point to a Vec3 for store the intersect point, if don't need them set to nullptr. + * @return true if the point is in widget's content space, false otherwise. + */ + virtual bool hitTestSelf(const Vec2& pt, const Camera* camera, Vec3* p) const; + + /** + * A callback which will be called when pointer down event is issued. + *@param touch The pointer event. + *@return True if user want to handle touches, false otherwise. + */ + virtual bool onPointerDown(PointerEvent* pointerEvent); + + /** + * A callback which will be called when pointer moved event is issued. + *@param touch The pointer event. + */ + virtual void onPointerMove(PointerEvent* pointerEvent); + + /** + * A callback which will be called when pointer ended event is issued. + *@param touch The pointer event. + */ + virtual void onPointerUp(PointerEvent* pointerEvent); + + /** + * A callback which will be called when pointer cancelled event is issued. + *@param touch The pointer event. + */ + virtual void onPointerCancel(PointerEvent* pointerEvent); + + /** + * A callback which will be called when a pointer scroll event is issued. + *@param event The mouse event info. + */ + virtual bool onPointerScroll(PointerEvent* pointerEvent); + + bool isPointerInside(PointerEvent* event, const Camera* camera, Vec3* outHitPoint); + + /** + * @brief [Core Layout Override] Measures the widget's desired size based on a layout hint. + * @param sizeHint The proposed layout size constraint. + * @return The final size resolved by your custom widget or content. + * @note Overriding Guide: When creating a custom widget (e.g., custom button/label/EditBox), + * override this method instead of resolvePreferredSize(). It is a pure `const` function + * that eliminates layout deadlocks and platform view rendering jitter. + */ + virtual Vec2 resolvePreferredSize(const Vec2& sizeHint) const; + // call back function called when size changed. virtual void onSizeChanged(); // initializes renderer of widget. - virtual void initRenderer(); + virtual void initRenderNode(); // call back function called widget's state changed to normal. virtual void onPressStateChangedToNormal(); @@ -811,15 +783,16 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol // call back function called widget's state changed to dark. virtual void onPressStateChangedToDisabled(); - virtual bool onMouseEvent(Event* event); - void pushDownEvent(); void moveEvent(); + void hoverEnterEvent(); + void hoverMoveEvent(); + void hoverExitEvent(); virtual void releaseUpEvent(); virtual void cancelUpEvent(); - virtual void adaptRenderers() {}; + virtual void updateLayout() {}; void updateChildrenDisplayedRGBA(); void copyProperties(Widget* model); @@ -828,7 +801,7 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol virtual void copyClonedWidgetChildren(Widget* model); Widget* getWidgetParent(); - void updateContentSizeWithTextureSize(const Vec2& size); + virtual void updateContentSize(); bool isAncestorsEnabled(); Widget* getAncestorWidget(Node* node); @@ -836,18 +809,23 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol void cleanupWidget(); LayoutComponent* getOrCreateLayoutComponent(); + void dispatchPointerMove(PointerEvent* pointerEvent); protected: bool _usingLayoutComponent; - bool _unifySize; + bool _autoSize; bool _enabled; bool _bright; - bool _touchEnabled; - bool _mouseEnabled; + bool _pointerEnabled; bool _highlight; + bool _hovered; bool _affectByClipping; - bool _ignoreSize; - bool _propagateTouchEvents; + bool _propagatePointerEvents; + bool _hitted; + bool _flippedX; + bool _flippedY; + bool _focused; + bool _focusEnabled; BrightStyle _brightStyle; SizeType _sizeType; @@ -861,40 +839,25 @@ class AX_GUI_DLL Widget : public ProtectedNode, public LayoutParameterProtocol Vec2 _sizePercent; Vec2 _positionPercent; - bool _hitted; - // weak reference of the camera which made the widget passed the hit test when response touch begin event - // it's useful in the next touch move/end events const Camera* _hittedByCamera; - EventListenerTouchOneByOne* _touchListener; - Vec2 _touchBeganPosition; - Vec2 _touchMovePosition; - Vec2 _touchEndPosition; - - bool _mouseHitted; - EventListenerMouse* _mouseListener; + const Camera* _hoveredByCamera; + PointerEventListener* _pointerEventListener; + Vec2 _pointerDownPosition; + Vec2 _pointerMovePosition; + Vec2 _pointerUpPosition; - bool _flippedX; - bool _flippedY; - - // use map to enable switch back and forth for user layout parameters Map _layoutParameterDictionary; LayoutParameter::Type _layoutParameterType; - bool _focused; - bool _focusEnabled; - /** - * store the only one focused widget - */ - static Widget* _focusedWidget; // both layout & widget will be stored in this variable - - Object* _touchEventListener; - ccWidgetTouchCallback _touchEventCallback; - ccWidgetClickCallback _clickEventListener; - ccWidgetEventCallback _ccEventCallback; + PointerEventHandler _pointerEventHandler; + HoverEventHandler _hoverEventHandler; + ClickEventHandler _clickEventHandler; + WidgetEventCallback _customEventCallback; std::string _callbackType; std::string _callbackName; + static Widget* _focusedWidget; // both layout & widget will be stored in this variable private: class FocusNavigationController; static FocusNavigationController* _focusNavigationController; diff --git a/axmol/ui/axmol-ui.h b/axmol/ui/axmol-ui.h index 037731188547..543cba7ad3c3 100644 --- a/axmol/ui/axmol-ui.h +++ b/axmol/ui/axmol-ui.h @@ -26,38 +26,37 @@ THE SOFTWARE. #pragma once -#include "axmol/ui/UIWidget.h" -#include "axmol/ui/UILayout.h" -#include "axmol/ui/UIButton.h" -#include "axmol/ui/UICheckBox.h" -#include "axmol/ui/UIRadioButton.h" -#include "axmol/ui/UIImageView.h" -#include "axmol/ui/UIText.h" -#include "axmol/ui/UITextAtlas.h" -#include "axmol/ui/UILoadingBar.h" -#include "axmol/ui/UIScrollView.h" -#include "axmol/ui/UIListView.h" -#include "axmol/ui/UISlider.h" -#include "axmol/ui/UITextField.h" -#include "axmol/ui/UITextFieldEx.h" -#include "axmol/ui/UITextBMFont.h" -#include "axmol/ui/UIPageView.h" +#include "axmol/ui/Widget.h" +#include "axmol/ui/LayoutGroup.h" +#include "axmol/ui/Button.h" +#include "axmol/ui/CheckBox.h" +#include "axmol/ui/RadioButton.h" +#include "axmol/ui/ImageView.h" +#include "axmol/ui/Text.h" +#include "axmol/ui/TextAtlas.h" +#include "axmol/ui/LoadingBar.h" +#include "axmol/ui/ScrollView.h" +#include "axmol/ui/ListView.h" +#include "axmol/ui/Slider.h" +#include "axmol/ui/InputField.h" +#include "axmol/ui/TextBMFont.h" +#include "axmol/ui/PageView.h" #include "axmol/ui/UIHelper.h" -#include "axmol/ui/UIRichText.h" -#include "axmol/ui/UIHBox.h" -#include "axmol/ui/UIVBox.h" -#include "axmol/ui/UIRelativeBox.h" -#if defined(AX_ENABLE_MEDIA) -# include "axmol/ui/UIMediaPlayer.h" +#include "axmol/ui/RichText.h" +#include "axmol/ui/HBox.h" +#include "axmol/ui/VBox.h" +#include "axmol/ui/RelativeBox.h" +#if defined(AX_ENABLE_VIDEO) +# include "axmol/ui/VideoPlayer.h" #endif #if !defined(_WIN32) || defined(AX_ENABLE_MSEDGE_WEBVIEW2) -# include "axmol/ui/UIWebView/UIWebView.h" +# include "axmol/ui/WebView/WebView.h" #endif #include "axmol/ui/GUIExport.h" -#include "axmol/ui/UIScale9Sprite.h" -#include "axmol/ui/UIEditBox/UIEditBox.h" -#include "axmol/ui/UILayoutComponent.h" -#include "axmol/ui/UITabControl.h" +#include "axmol/ui/Scale9Sprite.h" +#include "axmol/ui/EditBox/EditBox.h" +#include "axmol/ui/LayoutComponent.h" +#include "axmol/ui/TabView.h" /** * @addtogroup ui diff --git a/axmol/vr/VRBase.h b/axmol/vr/VRBase.h index 8e0086c08d6a..1ea36b2c66a3 100644 --- a/axmol/vr/VRBase.h +++ b/axmol/vr/VRBase.h @@ -36,7 +36,7 @@ namespace ax class Scene; class Renderer; -class RenderView; +class RenderViewCore; inline namespace experimental { @@ -55,9 +55,9 @@ class AX_DLL IVRRenderer { public: virtual ~IVRRenderer() {} - virtual void init(RenderView* rv) = 0; + virtual void init(RenderViewCore* rv) = 0; virtual void cleanup() = 0; - virtual void onRenderViewResized(RenderView* rv) = 0; + virtual void onRenderViewResized(RenderViewCore* rv) = 0; virtual void setScissorRect(float x, float y, float width, float height) = 0; virtual const ScissorRect& getScissorRect() const = 0; virtual void render(Scene* scene, Renderer* renderer) = 0; diff --git a/axmol/vr/VRGenericRenderer.cpp b/axmol/vr/VRGenericRenderer.cpp index 9ac7ed7b31d4..f1dfb3dbacd2 100644 --- a/axmol/vr/VRGenericRenderer.cpp +++ b/axmol/vr/VRGenericRenderer.cpp @@ -89,7 +89,7 @@ void VRGenericRenderer::cleanup() AX_SAFE_DELETE(_rightDistortionMesh); } -void VRGenericRenderer::onRenderViewResized(RenderView* rv) +void VRGenericRenderer::onRenderViewResized(RenderViewCore* rv) { cleanup(); init(rv); @@ -115,7 +115,7 @@ const ScissorRect& VRGenericRenderer::getScissorRect() const return _sourceScissorRect; } -void VRGenericRenderer::init(RenderView* rv) +void VRGenericRenderer::init(RenderViewCore* rv) { // Ensure VR render view uses the same resolution policy as the normal render view by basing it on the viewport size const auto screenSize = rv->getViewportRect().size; @@ -151,7 +151,7 @@ void VRGenericRenderer::init(RenderView* rv) _rightEyeCmd.setIndexDrawInfo(0, _rightDistortionMesh->_indices); } -void VRGenericRenderer::fillEyeViewports(RenderView* rv, const Vec2& screenSize) +void VRGenericRenderer::fillEyeViewports(RenderViewCore* rv, const Vec2& screenSize) { const auto& rtSize = _renderTexture->getContentSize(); if (screenSize.x <= 0 || screenSize.y <= 0 || rtSize.width <= 0 || rtSize.height <= 0) diff --git a/axmol/vr/VRGenericRenderer.h b/axmol/vr/VRGenericRenderer.h index 12442519b21e..79e3e4af8cab 100644 --- a/axmol/vr/VRGenericRenderer.h +++ b/axmol/vr/VRGenericRenderer.h @@ -89,9 +89,9 @@ class AX_DLL VRGenericRenderer : public IVRRenderer void setScissorRect(float x, float y, float w, float h) override; const ScissorRect& getScissorRect() const override; - void onRenderViewResized(RenderView* rv) override; + void onRenderViewResized(RenderViewCore* rv) override; - void init(RenderView* rv) override; + void init(RenderViewCore* rv) override; void cleanup() override; void render(Scene* scene, Renderer* renderer) override; @@ -99,7 +99,7 @@ class AX_DLL VRGenericRenderer : public IVRRenderer protected: void setupProgram(); - void fillEyeViewports(RenderView* rv, const Vec2& screenSize); + void fillEyeViewports(RenderViewCore* rv, const Vec2& screenSize); DistortionMesh* createDistortionMesh(VREye::EyeType eyeType, const Size& screenSize); diff --git a/cmake/Modules/AXBuildHelpers.cmake b/cmake/Modules/AXBuildHelpers.cmake index ccae5faa1b59..daf298f17ae2 100644 --- a/cmake/Modules/AXBuildHelpers.cmake +++ b/cmake/Modules/AXBuildHelpers.cmake @@ -8,13 +8,10 @@ endif() if (WASM) set(AX_WASM_SHELL_FILE "${_AX_ROOT}/axmol/platform/wasm/shell_minimal.html" CACHE STRING "The path of wasm shell file") - set(_AX_WASM_EXPORTS "_main,_axmol_webglcontextlost,_axmol_webglcontextrestored,_axmol_hdoc_visibilitychange,_axmol_onwebclickcallback") + set(_AX_WASM_EXPORTS "_main") # option: AX_WASM_ENABLE_DEVTOOLS option(AX_WASM_ENABLE_DEVTOOLS "Enable wasm devtools" ON) - if(AX_WASM_ENABLE_DEVTOOLS) - string(APPEND _AX_WASM_EXPORTS ",_axmol_dev_pause,_axmol_dev_resume,_axmol_dev_step") - endif() set(AX_WASM_EXPORTS "${_AX_WASM_EXPORTS}" CACHE STRING "" FORCE) # option: AX_WASM_ASSETS_PRELOAD_FILE @@ -664,7 +661,7 @@ macro(ax_setup_app_props app_name) set(CMAKE_EXECUTABLE_SUFFIX ".html") target_link_options(${app_name} PRIVATE "-sEXPORTED_FUNCTIONS=[${AX_WASM_EXPORTS}]" - "-sEXPORTED_RUNTIME_METHODS=[ccall,cwrap,HEAPU8,requestFullscreen]" + "-sEXPORTED_RUNTIME_METHODS=[ccall,cwrap,HEAPU8,requestFullscreen,lengthBytesUTF8,stringToUTF8]" ) set(EMSCRIPTEN_LINK_FLAGS "-lidbfs.js -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s STACK_SIZE=4mb --shell-file ${AX_WASM_SHELL_FILE} --use-preload-cache") @@ -746,17 +743,8 @@ macro(ax_setup_winrt_sources) ${_AX_ROOT}/axmol/platform/winrt/xaml/SwapChainPage.idl ${_AX_ROOT}/axmol/platform/winrt/xaml/SwapChainPage.h ${_AX_ROOT}/axmol/platform/winrt/xaml/SwapChainPage.cpp - ${_AX_ROOT}/axmol/platform/winrt/xaml/AxmolRenderer.h - ${_AX_ROOT}/axmol/platform/winrt/xaml/AxmolRenderer.cpp ) - if(AX_ENABLE_GL) - list(APPEND PLATFORM_SOURCES - ${_AX_ROOT}/axmol/platform/winrt/xaml/EGLSurfaceProvider.h - ${_AX_ROOT}/axmol/platform/winrt/xaml/EGLSurfaceProvider.cpp - ) - endif() - file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/proj.winrt/App.xaml" APP_XAML_FULL_PATH) set_property( SOURCE proj.winrt/App.h proj.winrt/App.cpp proj.winrt/App.idl diff --git a/cmake/Modules/AXConfigDefine.cmake b/cmake/Modules/AXConfigDefine.cmake index 6fd5bbf2076b..de2e07f0e711 100644 --- a/cmake/Modules/AXConfigDefine.cmake +++ b/cmake/Modules/AXConfigDefine.cmake @@ -26,7 +26,7 @@ if(WINRT) # The minmal deploy target version: Windows 10, version 1809 (Build 10.0.17763) for building msix package # refer to: https://learn.microsoft.com/en-us/windows/msix/supported-platforms?source=recommendations set(CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION "10.0.17763" CACHE STRING "") - set(AX_CPPWINRT_VERSION "2.0.250303.1" CACHE STRING "") + set(AX_CPPWINRT_VERSION "3.0.260520.1" CACHE STRING "") # For axmol deprecated policy, we need disable /sdl checks explicitly to avoid compiler traits invoking deprecated functions as error set(CMAKE_C_FLAGS "/sdl- ${CMAKE_C_FLAGS}") diff --git a/cmake/Modules/AXLinkHelpers.cmake b/cmake/Modules/AXLinkHelpers.cmake index c0604d7a34bb..07423e7d8d60 100644 --- a/cmake/Modules/AXLinkHelpers.cmake +++ b/cmake/Modules/AXLinkHelpers.cmake @@ -73,7 +73,7 @@ function(ax_link_cxx_prebuilt APP_NAME AX_ROOT_DIR AX_PREBUILT_DIR) ax_config_pred(${APP_NAME} AX_ENABLE_3D) ax_config_pred(${APP_NAME} AX_ENABLE_PHYSICS_3D) ax_config_pred(${APP_NAME} AX_ENABLE_NAVMESH) - ax_config_pred(${APP_NAME} AX_ENABLE_MEDIA) + ax_config_pred(${APP_NAME} AX_ENABLE_VIDEO) ax_config_pred(${APP_NAME} AX_ENABLE_AUDIO) ax_config_pred(${APP_NAME} AX_ENABLE_CONSOLE) diff --git a/extensions/DragonBones/src/DragonBones/CCArmatureDisplay.cpp b/extensions/DragonBones/src/DragonBones/CCArmatureDisplay.cpp index 56a054f66968..11f32ad4e87a 100644 --- a/extensions/DragonBones/src/DragonBones/CCArmatureDisplay.cpp +++ b/extensions/DragonBones/src/DragonBones/CCArmatureDisplay.cpp @@ -59,7 +59,7 @@ void CCArmatureDisplay::dbUpdate() void CCArmatureDisplay::addDBEventListener(std::string_view type, const std::function& callback) { - auto lambda = [callback](ax::EventCustom* event) -> void { + auto lambda = [callback](ax::CustomEvent* event) -> void { callback(static_cast(event->getUserData())); }; _dispatcher->addCustomEventListener(type, lambda); @@ -150,7 +150,7 @@ bool DBCCSprite::_checkVisibility(const ax::Mat4& transform, const ax::Size& siz ax::Vec3 v3p(hSizeX, hSizeY, 0); transform.transformPoint(&v3p); - ax::Vec2 v2p = ax::Camera::getVisitingCamera()->projectGL(v3p); + ax::Vec2 v2p = ax::Camera::getVisitingCamera()->projectWorldToCanvas(v3p); // convert content size to world coordinates float wshw = std::max(fabsf(hSizeX * transform.m[0] + hSizeY * transform.m[4]), diff --git a/extensions/GUI/src/GUI/ControlExtension/Control.cpp b/extensions/GUI/src/GUI/ControlExtension/Control.cpp index d8e08824d6d8..c7b54ab9d56f 100644 --- a/extensions/GUI/src/GUI/ControlExtension/Control.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/Control.cpp @@ -32,10 +32,9 @@ #include "Control.h" #include "axmol/base/Director.h" #include "axmol/2d/Menu.h" -#include "axmol/base/Touch.h" #include "Invocation.h" #include "axmol/base/EventDispatcher.h" -#include "axmol/base/EventListenerTouch.h" +#include "axmol/base/PointerEventListener.h" NS_AX_EXT_BEGIN @@ -74,12 +73,11 @@ bool Control::init() setHighlighted(false); auto dispatcher = Director::getInstance()->getEventDispatcher(); - auto touchListener = EventListenerTouchOneByOne::create(); - touchListener->setSwallowTouches(true); - touchListener->onTouchBegan = AX_CALLBACK_2(Control::onTouchBegan, this); - touchListener->onTouchMoved = AX_CALLBACK_2(Control::onTouchMoved, this); - touchListener->onTouchEnded = AX_CALLBACK_2(Control::onTouchEnded, this); - touchListener->onTouchCancelled = AX_CALLBACK_2(Control::onTouchCancelled, this); + auto touchListener = PointerEventListener::create(); + touchListener->onPointerDown = AX_CALLBACK_1(Control::onPointerDown, this); + touchListener->onPointerMove = AX_CALLBACK_1(Control::onPointerMove, this); + touchListener->onPointerUp = AX_CALLBACK_1(Control::onPointerUp, this); + touchListener->onPointerCancel = AX_CALLBACK_1(Control::onPointerCancel, this); dispatcher->addEventListenerWithSceneGraphPriority(touchListener, this); @@ -237,7 +235,7 @@ bool Control::isOpacityModifyRGB() const return _isOpacityModifyRGB; } -Vec2 Control::getTouchLocation(Touch* touch) +Vec2 Control::getTouchLocation(ax::PointerEvent* touch) { Vec2 touchLocation = touch->getLocation(); // Get the touch position touchLocation = this->convertToNodeSpace(touchLocation); // Convert to the node space of this class @@ -245,20 +243,26 @@ Vec2 Control::getTouchLocation(Touch* touch) return touchLocation; } -bool Control::onTouchBegan(Touch* /*touch*/, Event* /*event*/) +bool Control::onPointerDown(ax::PointerEvent* event) { - return false; + return event->isPrimaryPressed(); } -void Control::onTouchMoved(Touch* /*touch*/, Event* /*event*/) {} +void Control::onPointerMove(ax::PointerEvent* /*event*/) {} -void Control::onTouchEnded(Touch* /*touch*/, Event* /*event*/) {} +void Control::onPointerUp(ax::PointerEvent* /*event*/) +{ + _isPressed = false; +} -void Control::onTouchCancelled(Touch* /*touch*/, Event* /*event*/) {} +void Control::onPointerCancel(ax::PointerEvent* /*event*/) +{ + _isPressed = false; +} -bool Control::isTouchInside(Touch* touch) +bool Control::isTouchInside(ax::PointerEvent* event) { - Vec2 touchLocation = touch->getLocation(); // Get the touch position + Vec2 touchLocation = event->getLocation(); // Get the touch position touchLocation = this->getParent()->convertToNodeSpace(touchLocation); Rect bBox = getBoundingBox(); return bBox.containsPoint(touchLocation); diff --git a/extensions/GUI/src/GUI/ControlExtension/Control.h b/extensions/GUI/src/GUI/ControlExtension/Control.h index 4fb52c187978..fdda1afbbcaf 100644 --- a/extensions/GUI/src/GUI/ControlExtension/Control.h +++ b/extensions/GUI/src/GUI/ControlExtension/Control.h @@ -32,6 +32,7 @@ #include "ControlUtils.h" #include "axmol/2d/Layer.h" +#include "axmol/base/PointerEvent.h" #include "extensions/ExtensionExport.h" NS_AX_EXT_BEGIN @@ -159,12 +160,12 @@ class AX_EXT_API Control : public Layer * control space coordinates. * @param touch A Touch object that represents a touch. */ - virtual Vec2 getTouchLocation(Touch* touch); + virtual Vec2 getTouchLocation(ax::PointerEvent* touch); - virtual bool onTouchBegan(Touch* touch, Event* event); - virtual void onTouchMoved(Touch* touch, Event* event); - virtual void onTouchEnded(Touch* touch, Event* event); - virtual void onTouchCancelled(Touch* touch, Event* event); + virtual bool onPointerDown(PointerEvent* event); + virtual void onPointerMove(PointerEvent* event); + virtual void onPointerUp(PointerEvent* event); + virtual void onPointerCancel(PointerEvent* event); /** * Returns a boolean value that indicates whether a touch is inside the bounds @@ -174,7 +175,7 @@ class AX_EXT_API Control : public Layer * * @return Whether a touch is inside the receiver's rect. */ - virtual bool isTouchInside(Touch* touch); + virtual bool isTouchInside(PointerEvent* touch); // Overrides bool isOpacityModifyRGB() const override; @@ -250,6 +251,8 @@ class AX_EXT_API Control : public Layer bool _selected; bool _highlighted; + bool _isPressed{false}; + /** True if all of the controls parents are visible */ bool _hasVisibleParents; diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlButton.cpp b/extensions/GUI/src/GUI/ControlExtension/ControlButton.cpp index 87a5c35edda8..075364403c69 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlButton.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/ControlButton.cpp @@ -42,8 +42,7 @@ enum }; ControlButton::ControlButton() - : _isPushed(false) - , _parentInited(false) + : _parentInited(false) , _doesAdjustBackgroundImage(false) , _currentTitleColor(Color32::WHITE) , _titleLabel(nullptr) @@ -80,7 +79,7 @@ bool ControlButton::initWithLabelAndBackgroundSprite(Node* node, _parentInited = true; - _isPushed = false; + _isPressed = false; // Adjust the background image by adjustBackGroundSize setPreferredSize(Size::ZERO); @@ -238,7 +237,7 @@ void ControlButton::setPreferredSize(const Size& size) for (auto iter = _backgroundSpriteDispatchTable.begin(); iter != _backgroundSpriteDispatchTable.end(); ++iter) { - iter->second->setPreferredSize(size); + iter->second->setContentSize(size); } } @@ -464,10 +463,10 @@ void ControlButton::setBackgroundSpriteForState(ui::Scale9Sprite* sprite, State if (oldPreferredSize.equals(_preferredSize)) { // Force update of preferred size - sprite->setPreferredSize(Size(oldPreferredSize.width + 1, oldPreferredSize.height + 1)); + sprite->setContentSize(Size(oldPreferredSize.width + 1, oldPreferredSize.height + 1)); } - sprite->setPreferredSize(this->_preferredSize); + sprite->setContentSize(this->_preferredSize); } // If the current state if equal to the given state we update the layout @@ -552,7 +551,7 @@ void ControlButton::needsLayout() // TODO: should this also have margins if one of the preferred sizes is relaxed? if (_backgroundSprite != nullptr) { - Size preferredSize = _backgroundSprite->getPreferredSize(); + Size preferredSize = _backgroundSprite->getContentSize(); if (preferredSize.width <= 0) { preferredSize.width = titleLabelSize.width; @@ -595,9 +594,13 @@ void ControlButton::needsLayout() } } -bool ControlButton::onTouchBegan(Touch* pTouch, Event* /*pEvent*/) +bool ControlButton::onPointerDown(PointerEvent* event) { - if (!isTouchInside(pTouch) || !isEnabled() || !isVisible() || !hasVisibleParents()) + bool ret = Control::onPointerDown(event); + if (!ret) + return false; + + if (!isTouchInside(event) || !isEnabled() || !isVisible() || !hasVisibleParents()) { return false; } @@ -610,13 +613,13 @@ bool ControlButton::onTouchBegan(Touch* pTouch, Event* /*pEvent*/) } } - _isPushed = true; + _isPressed = true; this->setHighlighted(true); sendActionsForControlEvents(Control::EventType::TOUCH_DOWN); return true; } -void ControlButton::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) +void ControlButton::onPointerMove(PointerEvent* event) { if (!isEnabled() || !isPushed() || isSelected()) { @@ -627,7 +630,7 @@ void ControlButton::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) return; } - bool isTouchMoveInside = isTouchInside(pTouch); + bool isTouchMoveInside = isTouchInside(event); if (isTouchMoveInside && !isHighlighted()) { setHighlighted(true); @@ -647,13 +650,15 @@ void ControlButton::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) { sendActionsForControlEvents(Control::EventType::DRAG_OUTSIDE); } + + AX_UNUSED_PARAM(isTouchMoveInside); } -void ControlButton::onTouchEnded(Touch* pTouch, Event* /*pEvent*/) +void ControlButton::onPointerUp(PointerEvent* event) { - _isPushed = false; + _isPressed = false; setHighlighted(false); - if (isTouchInside(pTouch)) + if (isTouchInside(event)) { sendActionsForControlEvents(Control::EventType::TOUCH_UP_INSIDE); } @@ -723,9 +728,9 @@ void ControlButton::updateDisplayedColor(const Color32& parentColor) } } -void ControlButton::onTouchCancelled(Touch* /*pTouch*/, Event* /*pEvent*/) +void ControlButton::onPointerCancel(PointerEvent* /*event*/) { - _isPushed = false; + _isPressed = false; setHighlighted(false); sendActionsForControlEvents(Control::EventType::TOUCH_CANCEL); } diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlButton.h b/extensions/GUI/src/GUI/ControlExtension/ControlButton.h index 559750238498..b357c73641c1 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlButton.h +++ b/extensions/GUI/src/GUI/ControlExtension/ControlButton.h @@ -34,7 +34,7 @@ #include "Control.h" #include "Invocation.h" #include "axmol/base/Map.h" -#include "axmol/ui/UIScale9Sprite.h" +#include "axmol/ui/Scale9Sprite.h" NS_AX_EXT_BEGIN @@ -66,7 +66,7 @@ class AX_EXT_API ControlButton : public Control void setSelected(bool enabled) override; void setHighlighted(bool enabled) override; - bool isPushed() const { return _isPushed; } + bool isPushed() const { return _isPressed; } /** * Returns the title used for a state. @@ -178,10 +178,10 @@ class AX_EXT_API ControlButton : public Control void setAdjustBackgroundImage(bool adjustBackgroundImage); // Overrides - bool onTouchBegan(Touch* touch, Event* event) override; - void onTouchMoved(Touch* touch, Event* event) override; - void onTouchEnded(Touch* touch, Event* event) override; - void onTouchCancelled(Touch* touch, Event* event) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; + void onPointerUp(PointerEvent* event) override; + void onPointerCancel(PointerEvent* event) override; void setOpacity(uint8_t var) override; void updateDisplayedOpacity(uint8_t parentOpacity) override; @@ -207,7 +207,6 @@ class AX_EXT_API ControlButton : public Control virtual bool initWithTitleAndFontNameAndFontSize(std::string_view title, std::string_view fontName, float fontSize); protected: - bool _isPushed; bool _parentInited; bool _doesAdjustBackgroundImage; diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlColourPicker.cpp b/extensions/GUI/src/GUI/ControlExtension/ControlColourPicker.cpp index 7421df0f2f04..b0f442bb7d08 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlColourPicker.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/ControlColourPicker.cpp @@ -191,7 +191,7 @@ void ControlColourPicker::colourSliderValueChanged(Object* sender, Control::Even } // ignore all touches, handled by children -bool ControlColourPicker::onTouchBegan(Touch* /*touch*/, Event* /*pEvent*/) +bool ControlColourPicker::onPointerDown(PointerEvent* /*event*/) { return false; } diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlColourPicker.h b/extensions/GUI/src/GUI/ControlExtension/ControlColourPicker.h index 25dda5ce14a3..9e4f57e63e64 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlColourPicker.h +++ b/extensions/GUI/src/GUI/ControlExtension/ControlColourPicker.h @@ -73,7 +73,7 @@ class AX_EXT_API ControlColourPicker : public Control protected: void updateControlPicker(); void updateHueAndControlPicker(); - bool onTouchBegan(Touch* touch, Event* pEvent) override; + bool onPointerDown(PointerEvent* event) override; HSV _hsv; AX_SYNTHESIZE_RETAIN(ControlSaturationBrightnessPicker*, _colourPicker, colourPicker) diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlHuePicker.cpp b/extensions/GUI/src/GUI/ControlExtension/ControlHuePicker.cpp index 534933f169a7..67877a8d667e 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlHuePicker.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/ControlHuePicker.cpp @@ -158,24 +158,32 @@ bool ControlHuePicker::checkSliderPosition(Vec2 location) return false; } -bool ControlHuePicker::onTouchBegan(Touch* touch, Event* /*event*/) +bool ControlHuePicker::onPointerDown(PointerEvent* event) { + bool ret = Control::onPointerDown(event); + if (!ret) + return false; + if (!isEnabled() || !isVisible()) { return false; } // Get the touch location - Vec2 touchLocation = getTouchLocation(touch); + Vec2 touchLocation = getTouchLocation(event); // Check the touch position on the slider - return checkSliderPosition(touchLocation); + _isPressed = checkSliderPosition(touchLocation); + return _isPressed; } -void ControlHuePicker::onTouchMoved(Touch* touch, Event* /*event*/) +void ControlHuePicker::onPointerMove(PointerEvent* event) { + if (!_isPressed) + return; + // Get the touch location - Vec2 touchLocation = getTouchLocation(touch); + Vec2 touchLocation = getTouchLocation(event); // small modification: this allows changing of the colour, even if the touch leaves the bounding area // updateSliderPosition(touchLocation); diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlHuePicker.h b/extensions/GUI/src/GUI/ControlExtension/ControlHuePicker.h index 4cfe22282818..7a4e5a2fe077 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlHuePicker.h +++ b/extensions/GUI/src/GUI/ControlExtension/ControlHuePicker.h @@ -62,8 +62,8 @@ class AX_EXT_API ControlHuePicker : public Control void setEnabled(bool enabled) override; // overrides - bool onTouchBegan(Touch* touch, Event* pEvent) override; - void onTouchMoved(Touch* pTouch, Event* pEvent) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; protected: void updateSliderPosition(Vec2 location); diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlPotentiometer.cpp b/extensions/GUI/src/GUI/ControlExtension/ControlPotentiometer.cpp index 780c7e1ba3a1..47c9125bcdc6 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlPotentiometer.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/ControlPotentiometer.cpp @@ -162,7 +162,7 @@ float ControlPotentiometer::getMaximumValue() return _maximumValue; } -bool ControlPotentiometer::isTouchInside(Touch* touch) +bool ControlPotentiometer::isTouchInside(PointerEvent* touch) { Vec2 touchLocation = this->getTouchLocation(touch); @@ -171,30 +171,41 @@ bool ControlPotentiometer::isTouchInside(Touch* touch) return distance < MIN(getContentSize().width / 2, getContentSize().height / 2); } -bool ControlPotentiometer::onTouchBegan(Touch* pTouch, Event* /*pEvent*/) +bool ControlPotentiometer::onPointerDown(PointerEvent* event) { - if (!this->isTouchInside(pTouch) || !this->isEnabled() || !isVisible()) + bool ret = Control::onPointerDown(event); + if (!ret) + return false; + + if (!this->isTouchInside(event) || !this->isEnabled() || !isVisible()) { return false; } - _previousLocation = this->getTouchLocation(pTouch); + _previousLocation = this->getTouchLocation(event); this->potentiometerBegan(_previousLocation); + _isPressed = true; + return true; } -void ControlPotentiometer::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) +void ControlPotentiometer::onPointerMove(PointerEvent* event) { - Vec2 location = this->getTouchLocation(pTouch); + if (!_isPressed) + return; + + Vec2 location = this->getTouchLocation(event); this->potentiometerMoved(location); } -void ControlPotentiometer::onTouchEnded(Touch* /*pTouch*/, Event* /*pEvent*/) +void ControlPotentiometer::onPointerUp(PointerEvent*) { this->potentiometerEnded(Vec2::ZERO); + + _isPressed = false; } float ControlPotentiometer::distanceBetweenPointAndPoint(Vec2 point1, Vec2 point2) diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlPotentiometer.h b/extensions/GUI/src/GUI/ControlExtension/ControlPotentiometer.h index cbd5aa7b81c4..ced6aa34381a 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlPotentiometer.h +++ b/extensions/GUI/src/GUI/ControlExtension/ControlPotentiometer.h @@ -78,11 +78,11 @@ class AX_EXT_API ControlPotentiometer : public Control float getMaximumValue(); // Overrides - bool isTouchInside(Touch* touch) override; + bool isTouchInside(PointerEvent* event) override; void setEnabled(bool enabled) override; - bool onTouchBegan(Touch* pTouch, Event* pEvent) override; - void onTouchMoved(Touch* pTouch, Event* pEvent) override; - void onTouchEnded(Touch* pTouch, Event* pEvent) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; + void onPointerUp(PointerEvent* event) override; /** Factorize the event dispatch into these methods. */ void potentiometerBegan(Vec2 location); diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlSaturationBrightnessPicker.cpp b/extensions/GUI/src/GUI/ControlExtension/ControlSaturationBrightnessPicker.cpp index abf2e0f29fc1..ef4d72af2df1 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlSaturationBrightnessPicker.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/ControlSaturationBrightnessPicker.cpp @@ -185,24 +185,31 @@ bool ControlSaturationBrightnessPicker::checkSliderPosition(Vec2 location) return false; } -bool ControlSaturationBrightnessPicker::onTouchBegan(Touch* touch, Event* /*event*/) +bool ControlSaturationBrightnessPicker::onPointerDown(PointerEvent* event) { + bool ret = Control::onPointerDown(event); + if (!ret) + return false; + if (!isEnabled() || !isVisible()) { return false; } // Get the touch location - Vec2 touchLocation = getTouchLocation(touch); + Vec2 touchLocation = getTouchLocation(event); // Check the touch position on the slider - return checkSliderPosition(touchLocation); + _isPressed = checkSliderPosition(touchLocation); + return _isPressed; } -void ControlSaturationBrightnessPicker::onTouchMoved(Touch* touch, Event* /*event*/) +void ControlSaturationBrightnessPicker::onPointerMove(PointerEvent* event) { + if (!_isPressed) + return; // Get the touch location - Vec2 touchLocation = getTouchLocation(touch); + Vec2 touchLocation = getTouchLocation(event); // small modification: this allows changing of the colour, even if the touch leaves the bounding area // updateSliderPosition(touchLocation); diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlSaturationBrightnessPicker.h b/extensions/GUI/src/GUI/ControlExtension/ControlSaturationBrightnessPicker.h index 522736e1a377..243841422d20 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlSaturationBrightnessPicker.h +++ b/extensions/GUI/src/GUI/ControlExtension/ControlSaturationBrightnessPicker.h @@ -90,8 +90,8 @@ class AX_EXT_API ControlSaturationBrightnessPicker : public Control void updateSliderPosition(Vec2 location); bool checkSliderPosition(Vec2 location); - bool onTouchBegan(Touch* touch, Event* pEvent) override; - void onTouchMoved(Touch* pTouch, Event* pEvent) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; }; // end of GUI group diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlSlider.cpp b/extensions/GUI/src/GUI/ControlExtension/ControlSlider.cpp index 81c5f1c3bbcb..41ef297c0ccf 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlSlider.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/ControlSlider.cpp @@ -30,7 +30,6 @@ */ #include "ControlSlider.h" -#include "axmol/base/Touch.h" #include "axmol/base/Director.h" NS_AX_EXT_BEGIN @@ -221,7 +220,7 @@ void ControlSlider::setMaximumValue(float maximumValue) setValue(_value); } -bool ControlSlider::isTouchInside(Touch* touch) +bool ControlSlider::isTouchInside(PointerEvent* touch) { Vec2 touchLocation = touch->getLocation(); touchLocation = this->getParent()->convertToNodeSpace(touchLocation); @@ -233,7 +232,7 @@ bool ControlSlider::isTouchInside(Touch* touch) return rect.containsPoint(touchLocation); } -Vec2 ControlSlider::locationFromTouch(Touch* touch) +Vec2 ControlSlider::locationFromTouch(PointerEvent* touch) { Vec2 touchLocation = touch->getLocation(); // Get the touch position touchLocation = this->convertToNodeSpace(touchLocation); // Convert to the node space of this class @@ -250,27 +249,40 @@ Vec2 ControlSlider::locationFromTouch(Touch* touch) return touchLocation; } -bool ControlSlider::onTouchBegan(Touch* touch, Event* /*pEvent*/) +bool ControlSlider::onPointerDown(PointerEvent* event) { - if (!isTouchInside(touch) || !isEnabled() || !isVisible()) + bool ret = Control::onPointerDown(event); + if (!ret) + return false; + + if (!isTouchInside(event) || !isEnabled() || !isVisible()) { return false; } - Vec2 location = locationFromTouch(touch); + Vec2 location = locationFromTouch(event); sliderBegan(location); + + _isPressed = true; return true; } -void ControlSlider::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) +void ControlSlider::onPointerMove(PointerEvent* event) { - Vec2 location = locationFromTouch(pTouch); + if (!_isPressed) + { + return; + } + + Vec2 location = locationFromTouch(event); sliderMoved(location); } -void ControlSlider::onTouchEnded(Touch* /*pTouch*/, Event* /*pEvent*/) +void ControlSlider::onPointerUp(PointerEvent* /*pTouch*/) { sliderEnded(Vec2::ZERO); + + _isPressed = false; } void ControlSlider::needsLayout() diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlSlider.h b/extensions/GUI/src/GUI/ControlExtension/ControlSlider.h index 8ba3bc255a35..6f8329da3165 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlSlider.h +++ b/extensions/GUI/src/GUI/ControlExtension/ControlSlider.h @@ -116,8 +116,8 @@ class AX_EXT_API ControlSlider : public Control virtual void setMaximumValue(float val); void setEnabled(bool enabled) override; - bool isTouchInside(Touch* touch) override; - Vec2 locationFromTouch(Touch* touch); + bool isTouchInside(PointerEvent* event) override; + Vec2 locationFromTouch(PointerEvent* event); virtual void setValue(float val); virtual void setMinimumValue(float val); @@ -126,9 +126,9 @@ class AX_EXT_API ControlSlider : public Control void sliderMoved(Vec2 location); void sliderEnded(Vec2 location); - bool onTouchBegan(Touch* touch, Event* pEvent) override; - void onTouchMoved(Touch* pTouch, Event* pEvent) override; - void onTouchEnded(Touch* pTouch, Event* pEvent) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; + void onPointerUp(PointerEvent* event) override; /** Returns the value for the given location. */ float valueForLocation(Vec2 location); diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlStepper.cpp b/extensions/GUI/src/GUI/ControlExtension/ControlStepper.cpp index c18145a0e8f3..6e79f63f2122 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlStepper.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/ControlStepper.cpp @@ -278,13 +278,19 @@ void ControlStepper::updateLayoutUsingTouchLocation(Vec2 location) } } -bool ControlStepper::onTouchBegan(Touch* pTouch, Event* /*pEvent*/) +bool ControlStepper::onPointerDown(PointerEvent* pTouch) { + bool ret = Control::onPointerDown(pTouch); + if (!ret) + return false; + if (!isTouchInside(pTouch) || !isEnabled() || !isVisible()) { return false; } + _isPressed = true; + Vec2 location = this->getTouchLocation(pTouch); this->updateLayoutUsingTouchLocation(location); @@ -298,8 +304,11 @@ bool ControlStepper::onTouchBegan(Touch* pTouch, Event* /*pEvent*/) return true; } -void ControlStepper::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) +void ControlStepper::onPointerMove(PointerEvent* pTouch) { + if (!_isPressed) + return; + if (this->isTouchInside(pTouch)) { Vec2 location = this->getTouchLocation(pTouch); @@ -314,6 +323,7 @@ void ControlStepper::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) this->startAutorepeat(); } } + return; } else { @@ -328,10 +338,12 @@ void ControlStepper::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) { this->stopAutorepeat(); } + + return; } } -void ControlStepper::onTouchEnded(Touch* pTouch, Event* /*pEvent*/) +void ControlStepper::onPointerUp(PointerEvent* pTouch) { _minusSprite->setColor(Color32::WHITE); _plusSprite->setColor(Color32::WHITE); @@ -348,6 +360,8 @@ void ControlStepper::onTouchEnded(Touch* pTouch, Event* /*pEvent*/) this->setValue(_value + ((location.x < _minusSprite->getContentSize().width) ? (0.0 - _stepValue) : _stepValue)); } + + _isPressed = false; } NS_AX_EXT_END diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlStepper.h b/extensions/GUI/src/GUI/ControlExtension/ControlStepper.h index 27589da6fcab..436a8626a1d1 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlStepper.h +++ b/extensions/GUI/src/GUI/ControlExtension/ControlStepper.h @@ -76,9 +76,9 @@ class AX_EXT_API ControlStepper : public Control virtual bool isContinuous() const; // Overrides - bool onTouchBegan(Touch* pTouch, Event* pEvent) override; - void onTouchMoved(Touch* pTouch, Event* pEvent) override; - void onTouchEnded(Touch* pTouch, Event* pEvent) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; + void onPointerUp(PointerEvent* event) override; void update(float dt) override; /** Update the layout of the stepper with the given touch location. */ diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlSwitch.cpp b/extensions/GUI/src/GUI/ControlExtension/ControlSwitch.cpp index deb06de34d21..a086b3031f3f 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlSwitch.cpp +++ b/extensions/GUI/src/GUI/ControlExtension/ControlSwitch.cpp @@ -376,7 +376,7 @@ void ControlSwitch::setEnabled(bool enabled) } } -Vec2 ControlSwitch::locationFromTouch(Touch* pTouch) +Vec2 ControlSwitch::locationFromTouch(PointerEvent* pTouch) { Vec2 touchLocation = pTouch->getLocation(); // Get the touch position touchLocation = this->convertToNodeSpace(touchLocation); // Convert to the node space of this class @@ -384,8 +384,12 @@ Vec2 ControlSwitch::locationFromTouch(Touch* pTouch) return touchLocation; } -bool ControlSwitch::onTouchBegan(Touch* pTouch, Event* /*pEvent*/) +bool ControlSwitch::onPointerDown(PointerEvent* pTouch) { + bool ret = Control::onPointerDown(pTouch); + if (!ret) + return false; + if (!isTouchInside(pTouch) || !isEnabled() || !isVisible()) { return false; @@ -400,12 +404,17 @@ bool ControlSwitch::onTouchBegan(Touch* pTouch, Event* /*pEvent*/) _switchSprite->getThumbSprite()->setColor(Color32::GRAY); _switchSprite->needsLayout(); + _isPressed = true; + return true; } -void ControlSwitch::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) +void ControlSwitch::onPointerMove(PointerEvent* event) { - Vec2 location = this->locationFromTouch(pTouch); + if (!_isPressed) + return; + + Vec2 location = this->locationFromTouch(event); location = Vec2(location.x - _initialTouchXPosition, 0.0f); _moved = true; @@ -413,7 +422,7 @@ void ControlSwitch::onTouchMoved(Touch* pTouch, Event* /*pEvent*/) _switchSprite->setSliderXPosition(location.x); } -void ControlSwitch::onTouchEnded(Touch* pTouch, Event* /*pEvent*/) +void ControlSwitch::onPointerUp(PointerEvent* pTouch) { Vec2 location = this->locationFromTouch(pTouch); @@ -427,9 +436,11 @@ void ControlSwitch::onTouchEnded(Touch* pTouch, Event* /*pEvent*/) { setOn(!_on, true); } + + _isPressed = false; } -void ControlSwitch::onTouchCancelled(Touch* pTouch, Event* /*pEvent*/) +void ControlSwitch::onPointerCancel(PointerEvent* pTouch) { Vec2 location = this->locationFromTouch(pTouch); diff --git a/extensions/GUI/src/GUI/ControlExtension/ControlSwitch.h b/extensions/GUI/src/GUI/ControlExtension/ControlSwitch.h index 9ce6eac324c7..863b53d878a8 100644 --- a/extensions/GUI/src/GUI/ControlExtension/ControlSwitch.h +++ b/extensions/GUI/src/GUI/ControlExtension/ControlSwitch.h @@ -96,13 +96,13 @@ class AX_EXT_API ControlSwitch : public Control bool hasMoved() const { return _moved; } void setEnabled(bool enabled) override; - Vec2 locationFromTouch(Touch* touch); + Vec2 locationFromTouch(PointerEvent* event); // Overrides - bool onTouchBegan(Touch* pTouch, Event* pEvent) override; - void onTouchMoved(Touch* pTouch, Event* pEvent) override; - void onTouchEnded(Touch* pTouch, Event* pEvent) override; - void onTouchCancelled(Touch* pTouch, Event* pEvent) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; + void onPointerUp(PointerEvent* event) override; + void onPointerCancel(PointerEvent* event) override; protected: /** Sprite which represents the view. */ diff --git a/extensions/GUI/src/GUI/ScrollView/ScrollView.cpp b/extensions/GUI/src/GUI/ScrollView/ScrollView.cpp index ee3a451564b6..713626f70fcf 100644 --- a/extensions/GUI/src/GUI/ScrollView/ScrollView.cpp +++ b/extensions/GUI/src/GUI/ScrollView/ScrollView.cpp @@ -118,7 +118,7 @@ bool ScrollView::initWithViewSize(Size size, Node* container /* = nullptr*/) setTouchEnabled(true); - _touches.reserve(EventTouch::MAX_TOUCHES); + _touches.reserve(4); _delegate = nullptr; _bounceable = true; @@ -188,12 +188,12 @@ void ScrollView::setTouchEnabled(bool enabled) if (enabled) { - _touchListener = EventListenerTouchOneByOne::create(); - _touchListener->setSwallowTouches(true); - _touchListener->onTouchBegan = AX_CALLBACK_2(ScrollView::onTouchBegan, this); - _touchListener->onTouchMoved = AX_CALLBACK_2(ScrollView::onTouchMoved, this); - _touchListener->onTouchEnded = AX_CALLBACK_2(ScrollView::onTouchEnded, this); - _touchListener->onTouchCancelled = AX_CALLBACK_2(ScrollView::onTouchCancelled, this); + _touchListener = PointerEventListener::create(); + _touchListener->onPointerDown = AX_CALLBACK_1(ScrollView::onPointerDown, this); + _touchListener->onPointerMove = AX_CALLBACK_1(ScrollView::onPointerMove, this); + _touchListener->onPointerUp = AX_CALLBACK_1(ScrollView::onPointerUp, this); + _touchListener->onPointerCancel = AX_CALLBACK_1(ScrollView::onPointerCancel, this); + _touchListener->onPointerScroll = AX_CALLBACK_1(ScrollView::onPointerScroll, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this); } @@ -205,14 +205,6 @@ void ScrollView::setTouchEnabled(bool enabled) } } -void ScrollView::setSwallowTouches(bool needSwallow) -{ - if (_touchListener != nullptr) - { - _touchListener->setSwallowTouches(needSwallow); - } -} - void ScrollView::setContentOffset(Vec2 offset, bool animated /* = false*/) { if (animated) @@ -702,8 +694,49 @@ void ScrollView::visit(Renderer* renderer, const Mat4& parentTransform, uint32_t director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } -bool ScrollView::onTouchBegan(Touch* touch, Event* /*event*/) +bool ScrollView::onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) { + if (!event || !camera) + return false; + + auto phase = event->getPhase(); + if (phase == InputPhase::PointerDown) + { + if (!event->isPrimaryPressed()) + return false; + } + + else if (phase != InputPhase::PointerScroll) + { + // Captured Move/Up/Cancel should not come through hit-test. + return false; + } + + if (!this->isVisible() || !this->hasVisibleParents()) + return false; + + // ScrollView uses _viewSize/getViewRect as its input area, not container contentSize. + Rect frame = getViewRect(); + + // Keep the same acceptance rules as onPointerDown. + // Dispatcher-level hit-test should reject touches outside visible bounds. + if (_touches.size() > 2 || _touchMoved || !frame.containsPoint(event->getLocation())) + return false; + + if (outHitPoint) + { + Vec2 local = this->convertToNodeSpace(event->getLocation()); + outHitPoint->set(local.x, local.y, 0.0f); + } + + return true; +} + +bool ScrollView::onPointerDown(PointerEvent* touch) +{ + if (!touch->isPrimaryPressed()) + return false; + if (!this->isVisible() || !this->hasVisibleParents()) { return false; @@ -724,7 +757,7 @@ bool ScrollView::onTouchBegan(Touch* touch, Event* /*event*/) if (_touches.size() == 1) { // scrolling - _touchPoint = this->convertTouchToNodeSpace(touch); + _touchPoint = this->convertPointerToNodeSpace(touch); _touchMoved = false; _dragging = true; // dragging started _scrollDistance.setZero(); @@ -733,17 +766,17 @@ bool ScrollView::onTouchBegan(Touch* touch, Event* /*event*/) else if (_touches.size() == 2) { _touchPoint = - (this->convertTouchToNodeSpace(_touches[0]).getMidpoint(this->convertTouchToNodeSpace(_touches[1]))); + (this->convertPointerToNodeSpace(_touches[0]).getMidpoint(this->convertPointerToNodeSpace(_touches[1]))); - _touchLength = _container->convertTouchToNodeSpace(_touches[0]) - .getDistance(_container->convertTouchToNodeSpace(_touches[1])); + _touchLength = _container->convertPointerToNodeSpace(_touches[0]) + .getDistance(_container->convertPointerToNodeSpace(_touches[1])); _dragging = false; } return true; } -void ScrollView::onTouchMoved(Touch* touch, Event* /*event*/) +void ScrollView::onPointerMove(PointerEvent* touch) { if (!this->isVisible()) { @@ -760,7 +793,7 @@ void ScrollView::onTouchMoved(Touch* touch, Event* /*event*/) frame = getViewRect(); - newPoint = this->convertTouchToNodeSpace(_touches[0]); + newPoint = this->convertPointerToNodeSpace(_touches[0]); moveDistance = newPoint - _touchPoint; float dis = 0.0f; @@ -836,14 +869,15 @@ void ScrollView::onTouchMoved(Touch* touch, Event* /*event*/) } else if (_touches.size() == 2 && !_dragging) { - const float len = _container->convertTouchToNodeSpace(_touches[0]) - .getDistance(_container->convertTouchToNodeSpace(_touches[1])); + const float len = _container->convertPointerToNodeSpace(_touches[0]) + .getDistance(_container->convertPointerToNodeSpace(_touches[1])); this->setZoomScale(this->getZoomScale() * len / _touchLength); } + return; } } -void ScrollView::onTouchEnded(Touch* touch, Event* /*event*/) +void ScrollView::onPointerUp(PointerEvent* touch) { if (!this->isVisible()) { @@ -868,7 +902,7 @@ void ScrollView::onTouchEnded(Touch* touch, Event* /*event*/) } } -void ScrollView::onTouchCancelled(Touch* touch, Event* /*event*/) +void ScrollView::onPointerCancel(PointerEvent* touch) { if (!this->isVisible()) { @@ -889,6 +923,62 @@ void ScrollView::onTouchCancelled(Touch* touch, Event* /*event*/) } } +bool ScrollView::onPointerScroll(PointerEvent* event) +{ + if (!event || !_container || !this->isVisible() || !this->hasVisibleParents()) + return false; + + if (_direction == Direction::NONE) + return false; + + constexpr float mouseFactor = 20.0f; + Vec2 move; + + const auto minOffset = this->minContainerOffset(); + const auto maxOffset = this->maxContainerOffset(); + const bool canScrollX = minOffset.x < maxOffset.x; + const bool canScrollY = minOffset.y < maxOffset.y; + const auto scrollDelta = event->getScrollDelta(); + + switch (_direction) + { + case Direction::HORIZONTAL: + if (!canScrollX) + return true; + move.x = (scrollDelta.x != 0.0f ? scrollDelta.x : scrollDelta.y) * mouseFactor; + break; + + case Direction::VERTICAL: + if (!canScrollY) + return true; + move.y = scrollDelta.y * mouseFactor; + break; + + case Direction::BOTH: + if (!canScrollX && !canScrollY) + return true; + move.x = canScrollX ? scrollDelta.x * mouseFactor : 0.0f; + move.y = canScrollY ? scrollDelta.y * mouseFactor : 0.0f; + break; + + default: + return false; + } + + if (move == Vec2::ZERO) + return false; + + this->unschedule(AX_SCHEDULE_SELECTOR(ScrollView::deaccelerateScrolling)); + _scrollDistance.setZero(); + + const bool bounceable = _bounceable; + _bounceable = false; + this->setContentOffset(_container->getPosition() + move); + _bounceable = bounceable; + + return true; +} + Rect ScrollView::getViewRect() { Vec2 screenPos = this->convertToWorldSpace(Vec2::ZERO); diff --git a/extensions/GUI/src/GUI/ScrollView/ScrollView.h b/extensions/GUI/src/GUI/ScrollView/ScrollView.h index e640c71647ee..e7cb1458615d 100644 --- a/extensions/GUI/src/GUI/ScrollView/ScrollView.h +++ b/extensions/GUI/src/GUI/ScrollView/ScrollView.h @@ -27,7 +27,7 @@ #pragma once #include "axmol/2d/Layer.h" -#include "axmol/base/EventListenerTouch.h" +#include "axmol/base/PointerEventListener.h" #include "axmol/2d/ActionTween.h" #include "extensions/ExtensionMacros.h" #include "extensions/ExtensionExport.h" @@ -185,7 +185,6 @@ class AX_EXT_API ScrollView : public Layer, public ActionTweenDelegate void setTouchEnabled(bool enabled); bool isTouchEnabled() const; - void setSwallowTouches(bool needSwallow); bool isDragging() const { return _dragging; } bool isTouchMoved() const { return _touchMoved; } bool isBounceable() const { return _bounceable; } @@ -228,10 +227,13 @@ class AX_EXT_API ScrollView : public Layer, public ActionTweenDelegate bool isClippingToBounds() { return _clippingToBounds; } void setClippingToBounds(bool bClippingToBounds) { _clippingToBounds = bClippingToBounds; } - virtual bool onTouchBegan(Touch* touch, Event* event); - virtual void onTouchMoved(Touch* touch, Event* event); - virtual void onTouchEnded(Touch* touch, Event* event); - virtual void onTouchCancelled(Touch* touch, Event* event); + bool onPointerHitTest(PointerEvent* event, const Camera* camera, Vec3* outHitPoint) override; + + virtual bool onPointerDown(PointerEvent*); + virtual void onPointerMove(PointerEvent*); + virtual void onPointerUp(PointerEvent*); + virtual void onPointerCancel(PointerEvent*); + virtual bool onPointerScroll(PointerEvent*); // Overrides void setContentSize(const Size& size) override; @@ -349,7 +351,7 @@ class AX_EXT_API ScrollView : public Layer, public ActionTweenDelegate /** * Touch objects to detect multitouch */ - std::vector _touches; + std::vector _touches; /** * size to clip. Node boundingBox uses contentSize directly. * It's semantically different what it actually means to common scroll views. @@ -367,7 +369,7 @@ class AX_EXT_API ScrollView : public Layer, public ActionTweenDelegate bool _scissorRestored; /** Touch listener */ - EventListenerTouchOneByOne* _touchListener; + PointerEventListener* _touchListener; // CustomCommand _beforeDrawCommand; // CustomCommand _afterDrawCommand; diff --git a/extensions/GUI/src/GUI/ScrollView/TableView.cpp b/extensions/GUI/src/GUI/ScrollView/TableView.cpp index a1ef0f6b7f94..0a8a7413e793 100644 --- a/extensions/GUI/src/GUI/ScrollView/TableView.cpp +++ b/extensions/GUI/src/GUI/ScrollView/TableView.cpp @@ -589,7 +589,7 @@ void TableView::scrollViewDidScroll(ScrollView* /*view*/) } } -void TableView::onTouchEnded(Touch* pTouch, Event* pEvent) +void TableView::onPointerUp(PointerEvent* pTouch) { if (!this->isVisible()) { @@ -598,22 +598,25 @@ void TableView::onTouchEnded(Touch* pTouch, Event* pEvent) if (_touchedCell) { - Rect bb = this->getBoundingBox(); - bb.origin = _parent->convertToWorldSpace(bb.origin); + Rect frame = this->getViewRect(); - if (bb.containsPoint(pTouch->getLocation()) && _tableViewDelegate != nullptr) + if (frame.containsPoint(pTouch->getLocation()) && _tableViewDelegate != nullptr) { _tableViewDelegate->tableCellUnhighlight(this, _touchedCell); _tableViewDelegate->tableCellTouched(this, _touchedCell); } + else if (_tableViewDelegate != nullptr) + { + _tableViewDelegate->tableCellUnhighlight(this, _touchedCell); + } _touchedCell = nullptr; } - ScrollView::onTouchEnded(pTouch, pEvent); + ScrollView::onPointerUp(pTouch); } -bool TableView::onTouchBegan(Touch* pTouch, Event* pEvent) +bool TableView::onPointerDown(PointerEvent* pTouch) { for (Node* c = this; c != nullptr; c = c->getParent()) { @@ -623,16 +626,15 @@ bool TableView::onTouchBegan(Touch* pTouch, Event* pEvent) } } - bool touchResult = ScrollView::onTouchBegan(pTouch, pEvent); + bool touchResult = ScrollView::onPointerDown(pTouch); + if (!touchResult) + return false; if (_touches.size() == 1) { - ssize_t index; - Vec2 point; + Vec2 point = this->getContainer()->convertPointerToNodeSpace(pTouch); - point = this->getContainer()->convertTouchToNodeSpace(pTouch); - - index = this->_indexFromOffset(point); + ssize_t index = this->_indexFromOffset(point); if (index == AX_INVALID_INDEX) { _touchedCell = nullptr; @@ -657,12 +659,12 @@ bool TableView::onTouchBegan(Touch* pTouch, Event* pEvent) _touchedCell = nullptr; } - return touchResult; + return true; } -void TableView::onTouchMoved(Touch* pTouch, Event* pEvent) +void TableView::onPointerMove(PointerEvent* pTouch) { - ScrollView::onTouchMoved(pTouch, pEvent); + ScrollView::onPointerMove(pTouch); if (_touchedCell && isTouchMoved()) { @@ -672,12 +674,13 @@ void TableView::onTouchMoved(Touch* pTouch, Event* pEvent) } _touchedCell = nullptr; + return; } } -void TableView::onTouchCancelled(Touch* pTouch, Event* pEvent) +void TableView::onPointerCancel(PointerEvent* pTouch) { - ScrollView::onTouchCancelled(pTouch, pEvent); + ScrollView::onPointerCancel(pTouch); if (_touchedCell) { diff --git a/extensions/GUI/src/GUI/ScrollView/TableView.h b/extensions/GUI/src/GUI/ScrollView/TableView.h index 91200761bb0f..34ee6ae81775 100644 --- a/extensions/GUI/src/GUI/ScrollView/TableView.h +++ b/extensions/GUI/src/GUI/ScrollView/TableView.h @@ -256,10 +256,10 @@ class AX_EXT_API TableView : public ScrollView, public ScrollViewDelegate // Overrides void scrollViewDidScroll(ScrollView* view) override; void scrollViewDidZoom(ScrollView* view) override {} - bool onTouchBegan(Touch* pTouch, Event* pEvent) override; - void onTouchMoved(Touch* pTouch, Event* pEvent) override; - void onTouchEnded(Touch* pTouch, Event* pEvent) override; - void onTouchCancelled(Touch* pTouch, Event* pEvent) override; + bool onPointerDown(PointerEvent* event) override; + void onPointerMove(PointerEvent* event) override; + void onPointerUp(PointerEvent* event) override; + void onPointerCancel(PointerEvent* event) override; protected: ssize_t __indexFromOffset(Vec2 offset); diff --git a/extensions/ImGui/CMakeLists.txt b/extensions/ImGui/CMakeLists.txt index 2edfafe9e896..0daa1366007b 100644 --- a/extensions/ImGui/CMakeLists.txt +++ b/extensions/ImGui/CMakeLists.txt @@ -37,7 +37,7 @@ list(APPEND SOURCE list(APPEND SOURCE src/ImGui/backends/imgui_impl_axmol.cpp) list(APPEND HEADER src/ImGui/backends/imgui_impl_axmol.h) -if(WINRT OR ANDROID OR IOS) +if(WINRT OR ANDROID OR IOS OR WASM) list(APPEND SOURCE src/ImGui/backends/imgui_impl_axmol_sw.cpp) list(APPEND HEADER src/ImGui/backends/imgui_impl_axmol_sw.h) else() diff --git a/extensions/ImGui/src/ImGui/ImGuiPresenter.cpp b/extensions/ImGui/src/ImGui/ImGuiPresenter.cpp index 7486c2576bbb..762987a91c71 100644 --- a/extensions/ImGui/src/ImGui/ImGuiPresenter.cpp +++ b/extensions/ImGui/src/ImGui/ImGuiPresenter.cpp @@ -24,7 +24,7 @@ THE SOFTWARE. #include "ImGuiPresenter.h" #include -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW # include "backends/imgui_impl_glfw.h" #else # include "backends/imgui_impl_axmol_sw.h" @@ -151,7 +151,7 @@ ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) } } // namespace -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW class ImGuiEventTracker { public: @@ -167,20 +167,18 @@ class ImGuiSceneEventTracker : public ImGuiEventTracker _trackLayer = utils::newInstance(&Node::initLayer); // note: when at the first click to focus the window, this will not take effect - auto listener = EventListenerTouchOneByOne::create(); - listener->setSwallowTouches(true); - listener->onTouchBegan = [this](Touch* touch, Event*) -> bool { return ImGui::GetIO().WantCaptureMouse; }; + auto listener = PointerEventListener::create(); + listener->onPointerHitTest = [](PointerEvent*, const Camera*, Vec3*) { + return ImGui::GetIO().WantCaptureMouse; + }; + listener->onPointerDown = [](PointerEvent*) -> bool { return ImGui::GetIO().WantCaptureMouse; }; + listener->onPointerMove = [](PointerEvent* event) { + if (ImGui::GetIO().WantCaptureMouse) + event->stopPropagation(); + }; + listener->onPointerScroll = [](PointerEvent*) -> bool { return true; }; _trackLayer->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, _trackLayer); - // capture mouse events - auto captureMouse = [=](EventMouse* event) -> bool { return ImGui::GetIO().WantCaptureMouse; }; - auto mouseListener = EventListenerMouse::create(); - mouseListener->setSwallowMouse(true); - mouseListener->onMouseDown = captureMouse; - mouseListener->onMouseUp = captureMouse; - mouseListener->onMouseMove = captureMouse; - mouseListener->onMouseScroll = captureMouse; - _trackLayer->getEventDispatcher()->addEventListenerWithSceneGraphPriority(mouseListener, _trackLayer); scene->addChild(_trackLayer, INT_MAX); // add an empty sprite to avoid render problem // const auto sp = Sprite::create(); @@ -226,35 +224,31 @@ class ImGuiGlobalEventTracker : public ImGuiEventTracker auto eventDispatcher = Director::getInstance()->getEventDispatcher(); - _touchListener = utils::newInstance(); - _touchListener->setSwallowTouches(true); - _touchListener->onTouchBegan = [this](Touch* touch, Event*) -> bool { return ImGui::GetIO().WantCaptureMouse; }; - eventDispatcher->addEventListenerWithFixedPriority(_touchListener, highestPriority); - - // capture mouse events - auto captureMouse = [=](EventMouse* event) -> bool { return ImGui::GetIO().WantCaptureMouse; }; - _mouseListener = utils::newInstance(); - _mouseListener->setSwallowMouse(true); - _mouseListener->onMouseDown = captureMouse; - _mouseListener->onMouseUp = captureMouse; - _mouseListener->onMouseMove = captureMouse; - _mouseListener->onMouseScroll = captureMouse; - eventDispatcher->addEventListenerWithFixedPriority(_mouseListener, highestPriority); + _pointerListener = utils::newInstance(); + _pointerListener->onPointerHitTest = [](PointerEvent*, const Camera*, Vec3*) { + return ImGui::GetIO().WantCaptureMouse; + }; + _pointerListener->onPointerDown = [](PointerEvent*) -> bool { return ImGui::GetIO().WantCaptureMouse; }; + _pointerListener->onPointerMove = [](PointerEvent* event) { + if (ImGui::GetIO().WantCaptureMouse) + event->stopPropagation(); + }; + _pointerListener->onPointerScroll = [](PointerEvent*) -> bool { return ImGui::GetIO().WantCaptureMouse; }; + eventDispatcher->addEventListenerWithFixedPriority(_pointerListener, highestPriority); + return true; } ~ImGuiGlobalEventTracker() override { auto eventDispatcher = Director::getInstance()->getEventDispatcher(); - eventDispatcher->removeEventListener(_mouseListener); - eventDispatcher->removeEventListener(_touchListener); + // eventDispatcher->removeEventListener(_mouseListener); + eventDispatcher->removeEventListener(_pointerListener); - _mouseListener->release(); - _touchListener->release(); + AX_SAFE_RELEASE_NULL(_pointerListener); } - EventListenerTouchOneByOne* _touchListener = nullptr; - EventListenerMouse* _mouseListener = nullptr; + PointerEventListener* _pointerListener = nullptr; }; #endif @@ -291,7 +285,7 @@ void ImGuiPresenter::init() // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW if (rhi::DriverContext::isOpenGL()) io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows #endif @@ -317,8 +311,8 @@ void ImGuiPresenter::init() style.Colors[ImGuiCol_WindowBg].w = 1.0f; } -#if defined(AX_PLATFORM_GLFW) - auto window = static_cast(Director::getInstance()->getRenderView())->getWindow(); +#if AX_IMGUI_USE_GLFW + auto window = static_cast(Director::getInstance()->getRenderView())->getWindow(); ImGui_ImplGlfw_InitForAxmol(window, true); #else ImGui_ImplAxmolSW_Init(Director::getInstance()->getRenderView(), true); @@ -331,10 +325,10 @@ void ImGuiPresenter::init() auto eventDispatcher = Director::getInstance()->getEventDispatcher(); _event1 = - eventDispatcher->addCustomEventListener(Director::EVENT_BEFORE_DRAW, [this](EventCustom*) { beginFrame(); }); + eventDispatcher->addCustomEventListener(Director::EVENT_BEFORE_DRAW, [this](CustomEvent*) { beginFrame(); }); _event2 = - eventDispatcher->addCustomEventListener(Director::EVENT_AFTER_VISIT, [this](EventCustom*) { endFrame(); }); - _event3 = eventDispatcher->addCustomEventListener(Director::EVENT_BEFORE_GFX_DROP, [](EventCustom*) { + eventDispatcher->addCustomEventListener(Director::EVENT_AFTER_VISIT, [this](CustomEvent*) { endFrame(); }); + _event3 = eventDispatcher->addCustomEventListener(Director::EVENT_BEFORE_GFX_DROP, [](CustomEvent*) { if (_instance) { _instance->cleanup(); @@ -352,7 +346,7 @@ void ImGuiPresenter::cleanup() ImGui_ImplAxmol_SetRebuildFontsFunc(nullptr, nullptr); ImGui_ImplAxmol_Shutdown(); -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW ImGui_ImplGlfw_Shutdown(); #else ImGui_ImplAxmolSW_Shutdown(); @@ -362,7 +356,7 @@ void ImGuiPresenter::cleanup() if (!_renderLoops.empty()) { -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW for (auto item : _renderLoops) { delete item.second.tracker; @@ -479,7 +473,7 @@ void ImGuiPresenter::beginFrame() { // create frame ImGui_ImplAxmol_NewFrame(); -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW ImGui_ImplGlfw_NewFrame(); #else ImGui_ImplAxmolSW_NewFrame(); @@ -529,7 +523,7 @@ void ImGuiPresenter::update() auto& imLoop = iter->second; if (imLoop.removing) { -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW auto tracker = imLoop.tracker; iter = _renderLoops.erase(iter); delete tracker; @@ -547,7 +541,7 @@ void ImGuiPresenter::update() bool ImGuiPresenter::addRenderLoop(std::string_view id, std::function func, Scene* target) { -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW auto tracker = target ? static_cast(utils::newInstance( &ImGuiSceneEventTracker::initWithScene, target)) : static_cast(utils::newInstance()); @@ -557,7 +551,7 @@ bool ImGuiPresenter::addRenderLoop(std::string_view id, std::function fu auto iter = _renderLoops.find(fourccId); if (iter == _renderLoops.end()) { -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW _renderLoops.emplace(fourccId, ImGuiLoop{tracker, std::move(func)}); #else _renderLoops.emplace(fourccId, ImGuiLoop{std::move(func)}); @@ -568,7 +562,7 @@ bool ImGuiPresenter::addRenderLoop(std::string_view id, std::function fu // allow reuse imLoop, update func, tracker, removing status auto& imLoop = iter->second; imLoop.func = std::move(func); -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW AX_SAFE_DELETE(imLoop.tracker); imLoop.tracker = tracker; #endif diff --git a/extensions/ImGui/src/ImGui/ImGuiPresenter.h b/extensions/ImGui/src/ImGui/ImGuiPresenter.h index 30c3b7258499..c4a30d939f36 100644 --- a/extensions/ImGui/src/ImGui/ImGuiPresenter.h +++ b/extensions/ImGui/src/ImGui/ImGuiPresenter.h @@ -36,7 +36,13 @@ THE SOFTWARE. NS_AX_EXT_BEGIN -#if defined(AX_PLATFORM_GLFW) +#if defined(AX_PLATFORM_GLFW) && AX_TARGET_PLATFORM != AX_PLATFORM_WASM +# define AX_IMGUI_USE_GLFW 1 +#else +# define AX_IMGUI_USE_GLFW 0 +#endif + +#if AX_IMGUI_USE_GLFW class ImGuiEventTracker; #endif @@ -180,7 +186,7 @@ class ImGuiPresenter private: struct ImGuiLoop { -#if defined(AX_PLATFORM_GLFW) +#if AX_IMGUI_USE_GLFW ImGuiEventTracker* tracker{nullptr}; #endif std::function func; diff --git a/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol.cpp b/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol.cpp index d9356f05f305..5fe779846c35 100644 --- a/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol.cpp +++ b/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol.cpp @@ -3,7 +3,7 @@ #include "axmol/base/Director.h" #include "axmol/base/Data.h" #if defined(AX_PLATFORM_GLFW) -# include "axmol/platform/RenderViewImpl.h" +# include "axmol/platform/RenderView.h" #endif #include "axmol/rhi/Program.h" #include "axmol/rhi/ProgramState.h" @@ -358,6 +358,8 @@ IMGUI_IMPL_API void ImGui_ImplAxmol_RenderDrawData(ImDrawData* draw_data) ImGui_ImplAxmol_SaveRenderState(renderer); ImGui_ImplAxmol_SetupRenderState(renderer, draw_data, fb_width, fb_height); + + auto drawCallback_ResetState = ImGui::GetPlatformIO().DrawCallback_ResetRenderState; // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports @@ -389,7 +391,7 @@ IMGUI_IMPL_API void ImGui_ImplAxmol_RenderDrawData(ImDrawData* draw_data) // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user // to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == drawCallback_ResetState) ImGui_ImplAxmol_SetupRenderState(renderer, draw_data, fb_width, fb_height); else { diff --git a/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol_sw.cpp b/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol_sw.cpp index 41a8afa222e3..03476cc2ac39 100644 --- a/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol_sw.cpp +++ b/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol_sw.cpp @@ -1,12 +1,16 @@ #include "imgui_impl_axmol_sw.h" #include "axmol/base/Director.h" -#include "axmol/base/EventListenerTouch.h" -#include "axmol/base/IMEDelegate.h" +#include "axmol/base/PointerEventListener.h" +#include "axmol/base/KeyboardEventListener.h" #include "axmol/rhi/axmol-rhi.h" +#include "axmol/base/InputDelegate.h" +#include "axmol/base/EventDispatcher.h" +#include "axmol/platform/RenderViewCore.h" + #include +#include +#include #include -#include "axmol/base/IMEDelegate.h" -#include "axmol/base/EventDispatcher.h" using namespace ax; using namespace ax::rhi; @@ -21,26 +25,212 @@ using namespace ax::rhi; # endif #endif +struct ImGui_ImplAxmolSW_KeyCodeHash +{ + size_t operator()(KeyboardEvent::KeyCode keyCode) const + { + return static_cast(keyCode); + } +}; + +static ImGuiKey ImGui_ImplAxmolSW_KeyToImGuiKey(KeyboardEvent::KeyCode keyCode) +{ + static const std::unordered_map keyMap = { + {KeyboardEvent::KeyCode::KEY_TAB, ImGuiKey_Tab}, + {KeyboardEvent::KeyCode::KEY_LEFT_ARROW, ImGuiKey_LeftArrow}, + {KeyboardEvent::KeyCode::KEY_KP_LEFT, ImGuiKey_LeftArrow}, + {KeyboardEvent::KeyCode::KEY_RIGHT_ARROW, ImGuiKey_RightArrow}, + {KeyboardEvent::KeyCode::KEY_KP_RIGHT, ImGuiKey_RightArrow}, + {KeyboardEvent::KeyCode::KEY_UP_ARROW, ImGuiKey_UpArrow}, + {KeyboardEvent::KeyCode::KEY_KP_UP, ImGuiKey_UpArrow}, + {KeyboardEvent::KeyCode::KEY_DOWN_ARROW, ImGuiKey_DownArrow}, + {KeyboardEvent::KeyCode::KEY_KP_DOWN, ImGuiKey_DownArrow}, + {KeyboardEvent::KeyCode::KEY_PG_UP, ImGuiKey_PageUp}, + {KeyboardEvent::KeyCode::KEY_KP_PG_UP, ImGuiKey_PageUp}, + {KeyboardEvent::KeyCode::KEY_PG_DOWN, ImGuiKey_PageDown}, + {KeyboardEvent::KeyCode::KEY_KP_PG_DOWN, ImGuiKey_PageDown}, + {KeyboardEvent::KeyCode::KEY_HOME, ImGuiKey_Home}, + {KeyboardEvent::KeyCode::KEY_KP_HOME, ImGuiKey_Home}, + {KeyboardEvent::KeyCode::KEY_END, ImGuiKey_End}, + {KeyboardEvent::KeyCode::KEY_KP_END, ImGuiKey_End}, + {KeyboardEvent::KeyCode::KEY_INSERT, ImGuiKey_Insert}, + {KeyboardEvent::KeyCode::KEY_KP_INSERT, ImGuiKey_Insert}, + {KeyboardEvent::KeyCode::KEY_DELETE, ImGuiKey_Delete}, + {KeyboardEvent::KeyCode::KEY_KP_DELETE, ImGuiKey_Delete}, + {KeyboardEvent::KeyCode::KEY_BACKSPACE, ImGuiKey_Backspace}, + {KeyboardEvent::KeyCode::KEY_SPACE, ImGuiKey_Space}, + {KeyboardEvent::KeyCode::KEY_ENTER, ImGuiKey_Enter}, + {KeyboardEvent::KeyCode::KEY_RETURN, ImGuiKey_Enter}, + {KeyboardEvent::KeyCode::KEY_KP_ENTER, ImGuiKey_KeypadEnter}, + {KeyboardEvent::KeyCode::KEY_ESCAPE, ImGuiKey_Escape}, + {KeyboardEvent::KeyCode::KEY_APOSTROPHE, ImGuiKey_Apostrophe}, + {KeyboardEvent::KeyCode::KEY_COMMA, ImGuiKey_Comma}, + {KeyboardEvent::KeyCode::KEY_MINUS, ImGuiKey_Minus}, + {KeyboardEvent::KeyCode::KEY_PERIOD, ImGuiKey_Period}, + {KeyboardEvent::KeyCode::KEY_SLASH, ImGuiKey_Slash}, + {KeyboardEvent::KeyCode::KEY_SEMICOLON, ImGuiKey_Semicolon}, + {KeyboardEvent::KeyCode::KEY_EQUAL, ImGuiKey_Equal}, + {KeyboardEvent::KeyCode::KEY_LEFT_BRACKET, ImGuiKey_LeftBracket}, + {KeyboardEvent::KeyCode::KEY_BACK_SLASH, ImGuiKey_Backslash}, + {KeyboardEvent::KeyCode::KEY_RIGHT_BRACKET, ImGuiKey_RightBracket}, + {KeyboardEvent::KeyCode::KEY_GRAVE, ImGuiKey_GraveAccent}, + {KeyboardEvent::KeyCode::KEY_TILDE, ImGuiKey_GraveAccent}, + {KeyboardEvent::KeyCode::KEY_CAPS_LOCK, ImGuiKey_CapsLock}, + {KeyboardEvent::KeyCode::KEY_SCROLL_LOCK, ImGuiKey_ScrollLock}, + {KeyboardEvent::KeyCode::KEY_NUM_LOCK, ImGuiKey_NumLock}, + {KeyboardEvent::KeyCode::KEY_PRINT, ImGuiKey_PrintScreen}, + {KeyboardEvent::KeyCode::KEY_PAUSE, ImGuiKey_Pause}, + {KeyboardEvent::KeyCode::KEY_0, ImGuiKey_0}, + {KeyboardEvent::KeyCode::KEY_1, ImGuiKey_1}, + {KeyboardEvent::KeyCode::KEY_2, ImGuiKey_2}, + {KeyboardEvent::KeyCode::KEY_3, ImGuiKey_3}, + {KeyboardEvent::KeyCode::KEY_4, ImGuiKey_4}, + {KeyboardEvent::KeyCode::KEY_5, ImGuiKey_5}, + {KeyboardEvent::KeyCode::KEY_6, ImGuiKey_6}, + {KeyboardEvent::KeyCode::KEY_7, ImGuiKey_7}, + {KeyboardEvent::KeyCode::KEY_8, ImGuiKey_8}, + {KeyboardEvent::KeyCode::KEY_9, ImGuiKey_9}, + {KeyboardEvent::KeyCode::KEY_A, ImGuiKey_A}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_A, ImGuiKey_A}, + {KeyboardEvent::KeyCode::KEY_B, ImGuiKey_B}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_B, ImGuiKey_B}, + {KeyboardEvent::KeyCode::KEY_C, ImGuiKey_C}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_C, ImGuiKey_C}, + {KeyboardEvent::KeyCode::KEY_D, ImGuiKey_D}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_D, ImGuiKey_D}, + {KeyboardEvent::KeyCode::KEY_E, ImGuiKey_E}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_E, ImGuiKey_E}, + {KeyboardEvent::KeyCode::KEY_F, ImGuiKey_F}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_F, ImGuiKey_F}, + {KeyboardEvent::KeyCode::KEY_G, ImGuiKey_G}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_G, ImGuiKey_G}, + {KeyboardEvent::KeyCode::KEY_H, ImGuiKey_H}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_H, ImGuiKey_H}, + {KeyboardEvent::KeyCode::KEY_I, ImGuiKey_I}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_I, ImGuiKey_I}, + {KeyboardEvent::KeyCode::KEY_J, ImGuiKey_J}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_J, ImGuiKey_J}, + {KeyboardEvent::KeyCode::KEY_K, ImGuiKey_K}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_K, ImGuiKey_K}, + {KeyboardEvent::KeyCode::KEY_L, ImGuiKey_L}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_L, ImGuiKey_L}, + {KeyboardEvent::KeyCode::KEY_M, ImGuiKey_M}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_M, ImGuiKey_M}, + {KeyboardEvent::KeyCode::KEY_N, ImGuiKey_N}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_N, ImGuiKey_N}, + {KeyboardEvent::KeyCode::KEY_O, ImGuiKey_O}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_O, ImGuiKey_O}, + {KeyboardEvent::KeyCode::KEY_P, ImGuiKey_P}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_P, ImGuiKey_P}, + {KeyboardEvent::KeyCode::KEY_Q, ImGuiKey_Q}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_Q, ImGuiKey_Q}, + {KeyboardEvent::KeyCode::KEY_R, ImGuiKey_R}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_R, ImGuiKey_R}, + {KeyboardEvent::KeyCode::KEY_S, ImGuiKey_S}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_S, ImGuiKey_S}, + {KeyboardEvent::KeyCode::KEY_T, ImGuiKey_T}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_T, ImGuiKey_T}, + {KeyboardEvent::KeyCode::KEY_U, ImGuiKey_U}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_U, ImGuiKey_U}, + {KeyboardEvent::KeyCode::KEY_V, ImGuiKey_V}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_V, ImGuiKey_V}, + {KeyboardEvent::KeyCode::KEY_W, ImGuiKey_W}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_W, ImGuiKey_W}, + {KeyboardEvent::KeyCode::KEY_X, ImGuiKey_X}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_X, ImGuiKey_X}, + {KeyboardEvent::KeyCode::KEY_Y, ImGuiKey_Y}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_Y, ImGuiKey_Y}, + {KeyboardEvent::KeyCode::KEY_Z, ImGuiKey_Z}, + {KeyboardEvent::KeyCode::KEY_CAPITAL_Z, ImGuiKey_Z}, + {KeyboardEvent::KeyCode::KEY_KP_PLUS, ImGuiKey_KeypadAdd}, + {KeyboardEvent::KeyCode::KEY_KP_MINUS, ImGuiKey_KeypadSubtract}, + {KeyboardEvent::KeyCode::KEY_KP_MULTIPLY, ImGuiKey_KeypadMultiply}, + {KeyboardEvent::KeyCode::KEY_KP_DIVIDE, ImGuiKey_KeypadDivide}, + {KeyboardEvent::KeyCode::KEY_LEFT_SHIFT, ImGuiKey_LeftShift}, + {KeyboardEvent::KeyCode::KEY_RIGHT_SHIFT, ImGuiKey_RightShift}, + {KeyboardEvent::KeyCode::KEY_LEFT_CTRL, ImGuiKey_LeftCtrl}, + {KeyboardEvent::KeyCode::KEY_RIGHT_CTRL, ImGuiKey_RightCtrl}, + {KeyboardEvent::KeyCode::KEY_LEFT_ALT, ImGuiKey_LeftAlt}, + {KeyboardEvent::KeyCode::KEY_RIGHT_ALT, ImGuiKey_RightAlt}, + {KeyboardEvent::KeyCode::KEY_HYPER, ImGuiKey_LeftSuper}, + {KeyboardEvent::KeyCode::KEY_MENU, ImGuiKey_Menu}, + {KeyboardEvent::KeyCode::KEY_F1, ImGuiKey_F1}, + {KeyboardEvent::KeyCode::KEY_F2, ImGuiKey_F2}, + {KeyboardEvent::KeyCode::KEY_F3, ImGuiKey_F3}, + {KeyboardEvent::KeyCode::KEY_F4, ImGuiKey_F4}, + {KeyboardEvent::KeyCode::KEY_F5, ImGuiKey_F5}, + {KeyboardEvent::KeyCode::KEY_F6, ImGuiKey_F6}, + {KeyboardEvent::KeyCode::KEY_F7, ImGuiKey_F7}, + {KeyboardEvent::KeyCode::KEY_F8, ImGuiKey_F8}, + {KeyboardEvent::KeyCode::KEY_F9, ImGuiKey_F9}, + {KeyboardEvent::KeyCode::KEY_F10, ImGuiKey_F10}, + {KeyboardEvent::KeyCode::KEY_F11, ImGuiKey_F11}, + {KeyboardEvent::KeyCode::KEY_F12, ImGuiKey_F12}, + }; + + auto it = keyMap.find(keyCode); + return it != keyMap.end() ? it->second : ImGuiKey_None; +} + +static void ImGui_ImplAxmolSW_AddKeyEvent(KeyboardEvent::KeyCode keyCode, bool down) +{ + ImGuiIO& io = ImGui::GetIO(); + switch (keyCode) + { + case KeyboardEvent::KeyCode::KEY_LEFT_CTRL: + case KeyboardEvent::KeyCode::KEY_RIGHT_CTRL: + io.AddKeyEvent(ImGuiMod_Ctrl, down); + break; + case KeyboardEvent::KeyCode::KEY_LEFT_SHIFT: + case KeyboardEvent::KeyCode::KEY_RIGHT_SHIFT: + io.AddKeyEvent(ImGuiMod_Shift, down); + break; + case KeyboardEvent::KeyCode::KEY_LEFT_ALT: + case KeyboardEvent::KeyCode::KEY_RIGHT_ALT: + io.AddKeyEvent(ImGuiMod_Alt, down); + break; + case KeyboardEvent::KeyCode::KEY_HYPER: + io.AddKeyEvent(ImGuiMod_Super, down); + break; + default: + break; + } + + auto imguiKey = ImGui_ImplAxmolSW_KeyToImGuiKey(keyCode); + if (imguiKey != ImGuiKey_None) + io.AddKeyEvent(imguiKey, down); +} + +static void ImGui_ImplAxmolSW_AddPressedKeyEvent(KeyboardEvent::KeyCode keyCode) +{ + ImGui_ImplAxmolSW_AddKeyEvent(keyCode, true); + ImGui_ImplAxmolSW_AddKeyEvent(keyCode, false); +} + // Text handling -class KeyboardInputDelegate : public IMEDelegate +class KeyboardInputDelegate : public InputDelegate { protected: - bool canAttachWithIME() override { return true; } + bool canAttachWithIME() const override { return true; } - bool canDetachWithIME() override { return true; } + bool canDetachWithIME() const override { return true; } - void controlKey(EventKeyboard::KeyCode keyCode) override + void controlKey(KeyboardEvent::KeyCode keyCode) override { - // Not handled at the moment + ImGui_ImplAxmolSW_AddPressedKeyEvent(keyCode); } - void insertText(const char* text, size_t len) override + void deleteBackward(unsigned int numChars) override + { + for (unsigned int i = 0; i < numChars; ++i) + ImGui_ImplAxmolSW_AddPressedKeyEvent(KeyboardEvent::KeyCode::KEY_BACKSPACE); + } + + void insertText(std::string_view text) override { ImGuiIO& io = ImGui::GetIO(); - for (int i = 0; i < len && text[i] != 0; ++i) - { - io.AddInputCharacter(text[i]); - } + std::string input{text}; + io.AddInputCharactersUTF8(input.c_str()); } }; @@ -48,7 +238,7 @@ class KeyboardInputDelegate : public IMEDelegate struct ImGui_ImplAxmolSW_Data { - RenderView* Window{nullptr}; + RenderViewCore* Window{nullptr}; double Time{0}; bool InstalledCallbacks{false}; @@ -56,7 +246,10 @@ struct ImGui_ImplAxmolSW_Data // axmol spec data ImVec2 LastValidMousePos; - EventListener* TouchListener = nullptr; + EventListener* PointerListener = nullptr; + EventListener* KeyboardListener = nullptr; + + intptr_t CapturedPointerId = -1; KeyboardInputDelegate KeyboardInputDelegate; }; @@ -67,17 +260,6 @@ static ImGui_ImplAxmolSW_Data* ImGui_ImplAxmolSW_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplAxmolSW_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; } -static ax::Vec2 convertToScreen(const Vec2& pos) -{ - auto* bd = ImGui_ImplAxmolSW_GetBackendData(); - ImGuiIO& io = ImGui::GetIO(); - auto origin = bd->Window->getViewportRect().origin; - auto uiX = (pos.x * bd->Window->getScaleX() + origin.x) / io.DisplayFramebufferScale.x; - auto uiY = (pos.y * bd->Window->getScaleY() + origin.y) / io.DisplayFramebufferScale.y; - - return Vec2(uiX, uiY); -} - static bool ImGui_ImplAxmol_HitTest(const ImVec2& p) { ImGuiContext* ctx = ImGui::GetCurrentContext(); @@ -101,10 +283,8 @@ static bool ImGui_ImplAxmol_HitTest(const ImVec2& p) return false; } -static int s_CapturedTouchId = -1; - // Functions -bool ImGui_ImplAxmolSW_Init(RenderView* window, bool install_callbacks) +bool ImGui_ImplAxmolSW_Init(RenderViewCore* window, bool install_callbacks) { ImGuiIO& io = ImGui::GetIO(); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); @@ -127,28 +307,27 @@ bool ImGui_ImplAxmolSW_Init(RenderView* window, bool install_callbacks) io.AddFocusEvent(true); - auto* touchListener = EventListenerTouchOneByOne::create(); - touchListener->setSwallowTouches(true); - touchListener->retain(); - bd->TouchListener = touchListener; + auto listener = PointerEventListener::create(); + listener->retain(); + bd->PointerListener = listener; - touchListener->onTouchBegan = [](Touch* touch, Event* event) -> bool { + listener->onPointerDown = [bd](PointerEvent* event) -> bool { ImGuiIO& io = ImGui::GetIO(); ImGui_ImplAxmolSW_Data* bd = ImGui_ImplAxmolSW_GetBackendData(); - auto location = convertToScreen(touch->getLocationInView()); - auto touchPos = ImVec2(location.x, location.y); + auto location = event->getScreenLocation(); + auto pointerPos = ImVec2(location.x, location.y); // We perform our own hit test here because on Android there is no real mouse hover event. // ImGui's WantCaptureMouse is updated only after queued input events are processed - // (i.e. in NewFrame), so in onTouchBegan it would still reflect the previous frame. + // (i.e. in NewFrame), so in onPointerDown it would still reflect the previous frame. // Without an immediate hit test, we can't know right now if this touch is on a UI element. // By checking the position against ImGui windows ourselves, we can decide instantly // whether to capture the touch and stop it from reaching the game scene. - if (s_CapturedTouchId == -1 && ImGui_ImplAxmol_HitTest(touchPos)) + if (bd->CapturedPointerId == -1 && ImGui_ImplAxmol_HitTest(pointerPos)) { - s_CapturedTouchId = touch->getID(); + bd->CapturedPointerId = event->getPointerId(); io.AddMousePosEvent(location.x, location.y); - bd->LastValidMousePos = touchPos; + bd->LastValidMousePos = pointerPos; io.AddMouseButtonEvent(0, true); event->stopPropagation(); return true; @@ -156,22 +335,26 @@ bool ImGui_ImplAxmolSW_Init(RenderView* window, bool install_callbacks) return false; }; - touchListener->onTouchMoved = [](Touch* touch, Event* /*event*/) { - if (touch->getID() != s_CapturedTouchId) - return; + auto updateMousePosition = [](PointerEvent* event) { ImGuiIO& io = ImGui::GetIO(); ImGui_ImplAxmolSW_Data* bd = ImGui_ImplAxmolSW_GetBackendData(); - auto location = convertToScreen(touch->getLocationInView()); + auto location = event->getScreenLocation(); io.AddMousePosEvent(location.x, location.y); bd->LastValidMousePos = ImVec2(location.x, location.y); }; - touchListener->onTouchEnded = [](Touch* touch, Event* event) { - if (touch->getID() != s_CapturedTouchId) + listener->onPointerMove = [bd, updateMousePosition](PointerEvent* event) { + if (bd->CapturedPointerId != -1 && event->getPointerId() != bd->CapturedPointerId) + return; + updateMousePosition(event); + }; + + listener->onPointerUp = [bd](PointerEvent* event) { + if (event->getPointerId() != bd->CapturedPointerId) return; ImGuiIO& io = ImGui::GetIO(); ImGui_ImplAxmolSW_Data* bd = ImGui_ImplAxmolSW_GetBackendData(); - auto location = convertToScreen(touch->getLocationInView()); + auto location = event->getScreenLocation(); io.AddMousePosEvent(location.x, location.y); bd->LastValidMousePos = ImVec2(location.x, location.y); io.AddMouseButtonEvent(0, false); @@ -187,25 +370,70 @@ bool ImGui_ImplAxmolSW_Init(RenderView* window, bool install_callbacks) bd->Window->setIMEKeyboardState(false); } - s_CapturedTouchId = -1; + bd->CapturedPointerId = -1; }; - touchListener->onTouchCancelled = [](Touch* touch, Event* /*event*/) { - if (touch->getID() != s_CapturedTouchId) + listener->onPointerCancel = [bd](PointerEvent* event) { + if (event->getPointerId() != bd->CapturedPointerId) return; ImGuiIO& io = ImGui::GetIO(); ImGui_ImplAxmolSW_Data* bd = ImGui_ImplAxmolSW_GetBackendData(); - auto location = convertToScreen(touch->getLocationInView()); + auto location = event->getScreenLocation(); io.AddMousePosEvent(location.x, location.y); bd->LastValidMousePos = ImVec2(location.x, location.y); io.AddMouseButtonEvent(0, false); - s_CapturedTouchId = -1; + bd->CapturedPointerId = -1; + }; + + listener->onPointerScroll = [bd](PointerEvent* event) -> bool { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplAxmolSW_Data* bd = ImGui_ImplAxmolSW_GetBackendData(); + + auto location = event->getScreenLocation(); + auto pointerPos = ImVec2(location.x, location.y); + + io.AddMousePosEvent(location.x, location.y); + bd->LastValidMousePos = pointerPos; + + auto scrollDelta = event->getScrollDelta(); + +#if defined(__EMSCRIPTEN__) + scrollDelta *= 0.1f; +#endif + io.AddMouseWheelEvent(scrollDelta.x, -scrollDelta.y); + + if (ImGui_ImplAxmol_HitTest(pointerPos)) + return true; + + return false; }; constexpr int highestPriority = (std::numeric_limits::min)(); - Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(touchListener, highestPriority); + Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(listener, highestPriority); + + auto keyboardListener = KeyboardEventListener::create(); + keyboardListener->retain(); + bd->KeyboardListener = keyboardListener; + + auto handleKeyEvent = [](KeyboardEvent* event, bool down) { + ImGui_ImplAxmolSW_AddKeyEvent(event->getKeyCode(), down); + + auto& io = ImGui::GetIO(); + if (event && (io.WantCaptureKeyboard || io.WantTextInput)) + event->stopPropagation(); + }; + keyboardListener->onKeyPressed = [handleKeyEvent](KeyboardEvent* event) { + handleKeyEvent(event, true); + }; + keyboardListener->onKeyReleased = [handleKeyEvent](KeyboardEvent* event) { + handleKeyEvent(event, false); + }; + keyboardListener->onKeyRepeat = [handleKeyEvent](KeyboardEvent* event) { + handleKeyEvent(event, true); + }; + Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(keyboardListener, highestPriority); return true; } @@ -220,8 +448,11 @@ void ImGui_ImplAxmolSW_Shutdown() io.BackendPlatformUserData = nullptr; io.BackendRendererUserData = nullptr; - Director::getInstance()->getEventDispatcher()->removeEventListener(bd->TouchListener); - AX_SAFE_RELEASE_NULL(bd->TouchListener); + Director::getInstance()->getEventDispatcher()->removeEventListener(bd->PointerListener); + AX_SAFE_RELEASE_NULL(bd->PointerListener); + + Director::getInstance()->getEventDispatcher()->removeEventListener(bd->KeyboardListener); + AX_SAFE_RELEASE_NULL(bd->KeyboardListener); IM_DELETE(bd); } @@ -238,9 +469,19 @@ void ImGui_ImplAxmolSW_NewFrame() auto winSize = renderView->getWindowSize(); auto renderScale = renderView->getRenderScale(); - io.DisplaySize = ImVec2((float)winSize.width, (float)winSize.height); - if (winSize.width > 0 && winSize.height > 0) - io.DisplayFramebufferScale = ImVec2(renderScale, renderScale); + // 1. Convert to absolute physical frame buffer pixels + float physicalWidth = winSize.width * renderScale; + float physicalHeight = winSize.height * renderScale; + + // 2. Map ImGui strictly to the Physical Pixel Space to restore "Desktop-like" crisp size + // and perfectly align with the scaled physical touch coordinates from InputSystem. + io.DisplaySize = ImVec2(physicalWidth, physicalHeight); + + if (physicalWidth > 0 && physicalHeight > 0) + { + // 3. Since DisplaySize is already physical, the scale relative to itself is strictly 1.0! + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + } // Setup time step auto now = std::chrono::high_resolution_clock::now(); diff --git a/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol_sw.h b/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol_sw.h index 5ecd69c8e945..a36c037310d2 100644 --- a/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol_sw.h +++ b/extensions/ImGui/src/ImGui/backends/imgui_impl_axmol_sw.h @@ -1,9 +1,13 @@ #pragma once #include "imgui.h" -#include "axmol/platform/RenderView.h" + +namespace ax +{ +class RenderViewCore; +} /// ImGui Axmol SingleWindow platform spec APIs -IMGUI_IMPL_API bool ImGui_ImplAxmolSW_Init(ax::RenderView* window, bool install_callbacks); +IMGUI_IMPL_API bool ImGui_ImplAxmolSW_Init(ax::RenderViewCore* window, bool install_callbacks); IMGUI_IMPL_API void ImGui_ImplAxmolSW_Shutdown(); IMGUI_IMPL_API void ImGui_ImplAxmolSW_NewFrame(); diff --git a/extensions/ImGui/src/ImGui/backends/imgui_impl_glfw.cpp b/extensions/ImGui/src/ImGui/backends/imgui_impl_glfw.cpp index 3603105f9f16..dc38955137b7 100644 --- a/extensions/ImGui/src/ImGui/backends/imgui_impl_glfw.cpp +++ b/extensions/ImGui/src/ImGui/backends/imgui_impl_glfw.cpp @@ -32,6 +32,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-04-21: Added a Win32-specific implementation of ImGui_ImplGlfw_GetContentScaleXXXX functions for legacy GLFW 3.2. // 2026-03-25: Mouse cursor is properly restored if changed by user app/code while using glfwSetInputMode(..., GLFW_CURSOR_DISABLED) or ImGuiConfigFlags_NoMouseCursorChange. Amend change from 2025-12-10. // 2026-02-10: Try to set IMGUI_IMPL_GLFW_DISABLE_X11 / IMGUI_IMPL_GLFW_DISABLE_WAYLAND automatically if corresponding headers are not accessible. (#9225) // 2026-01-25: [Docking] Improve workarounds for cases where GLFW is unable to provide any reliable monitor info. Preserve existing monitor list when none of the new one is valid. (#9195, #7902, #5683) @@ -315,6 +316,31 @@ static void ImGui_ImplGlfw_UpdateMonitors(); static void ImGui_ImplGlfw_InitMultiViewportSupport(); static void ImGui_ImplGlfw_ShutdownMultiViewportSupport(); +#if defined(__APPLE__) && defined(GLFW_IME) +static void ImGui_ImplGlfw_SetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) +{ + GLFWwindow* window = viewport ? (GLFWwindow*)viewport->PlatformHandle : nullptr; + if (window == nullptr) + if (ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData()) + window = bd->Window; + if (window == nullptr) + return; + + const bool wants_text_input = data->WantTextInput || data->WantVisible; + glfwSetInputMode(window, GLFW_IME, wants_text_input ? GLFW_TRUE : GLFW_FALSE); + + if (wants_text_input) + { + const float viewport_x = viewport ? viewport->Pos.x : 0.0f; + const float viewport_y = viewport ? viewport->Pos.y : 0.0f; + const int x = (int)(data->InputPos.x - viewport_x); + const int y = (int)(data->InputPos.y - viewport_y); + const int h = data->InputLineHeight > 1.0f ? (int)data->InputLineHeight : 1; + glfwSetPreeditCursorRectangle(window, x, y, 1, h); + } +} +#endif + // Functions static bool ImGui_ImplGlfw_IsWayland() { @@ -1131,6 +1157,16 @@ static void ImGui_ImplGlfw_UpdateMonitors() } } +// For GFLW 3.2 + Windows: include a simplified non-monitor aware version of ImGui_ImplWin32_GetDpiScaleForMonitor(). +// This is merely a band-aid to make using GLFW 3.2 a little bit nicer, but prefer to use GLFW 3.3+ or the full correct functions from the Win32 backend. +#if !GLFW_HAS_PER_MONITOR_DPI && defined(_WIN32) && !defined(NOGDI) +static float ImGui_ImplWin32_GetLegacyDpiScale() { const HDC dc = ::GetDC(nullptr); UINT xdpi = ::GetDeviceCaps(dc, LOGPIXELSX); ::ReleaseDC(nullptr, dc); return (float)xdpi / 96.0f; } +static void glfwGetWindowContentScale(GLFWwindow*, float* x_scale, float* y_scale) { *x_scale = *y_scale = ImGui_ImplWin32_GetLegacyDpiScale(); } +static void glfwGetMonitorContentScale(GLFWmonitor*, float* x_scale, float* y_scale) { *x_scale = *y_scale = ImGui_ImplWin32_GetLegacyDpiScale(); } +#undef GLFW_HAS_PER_MONITOR_DPI +#define GLFW_HAS_PER_MONITOR_DPI 1 +#endif + // - On Windows the process needs to be marked DPI-aware!! SDL2 doesn't do it by default. You can call ::SetProcessDPIAware() or call ImGui_ImplWin32_EnableDpiAwareness() from Win32 backend. // - Apple platforms use FramebufferScale so we always return 1.0f. // - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle. @@ -1200,7 +1236,7 @@ void ImGui_ImplGlfw_NewFrame() // (Accept glfwGetTime() not returning a monotonically increasing value. Seems to happens on disconnecting peripherals and probably on VMs and Emscripten, see #6491, #6189, #6114, #3644) double current_time = glfwGetTime(); if (current_time <= bd->Time) - current_time = bd->Time + 0.00001f; + current_time = bd->Time + 0.00001; io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f); bd->Time = current_time; @@ -1734,21 +1770,35 @@ static LRESULT CALLBACK ImGui_ImplGlfw_WndProc(HWND hWnd, UINT msg, WPARAM wPara // axmol spec IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForAxmol(GLFWwindow* window, bool install_callbacks) { + bool initialized = false; auto driverType = ax::rhi::DriverContext::currentDriverType(); switch (driverType) { case ax::rhi::DriverType::OpenGL: - return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL); + initialized = ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL); + break; case ax::rhi::DriverType::Metal: - return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Metal); + initialized = ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Metal); + break; case ax::rhi::DriverType::D3D12: case ax::rhi::DriverType::D3D11: - return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_D3D); + initialized = ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_D3D); + break; case ax::rhi::DriverType::Vulkan: - return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan); + initialized = ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan); + break; + default: + break; } - return false; + if (!initialized) + return false; + +#if defined(__APPLE__) && defined(GLFW_IME) + ImGui::GetPlatformIO().Platform_SetImeDataFn = ImGui_ImplGlfw_SetImeData; +#endif + + return true; } #endif // #ifndef IMGUI_DISABLE diff --git a/extensions/ImGui/src/ImGui/imgui.cpp b/extensions/ImGui/src/ImGui/imgui.cpp index 306ca5709ac6..f536cb010a94 100644 --- a/extensions/ImGui/src/ImGui/imgui.cpp +++ b/extensions/ImGui/src/ImGui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.7 +// dear imgui, v1.92.8 // (main code and documentation) // Help: @@ -402,7 +402,31 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos) - likewise io.MousePos and GetMousePos() will use OS coordinates. If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos. - + - 2026/05/07 (1.92.8) - DrawList: swapped the last two arguments of AddRect(), AddPolyline(), PathStroke(). + - Before: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); + - After: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); + - Before: void ImDrawList::AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); + - After: void ImDrawList::AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); + - Before: void ImDrawList::PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f); + - After: void ImDrawList::PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); + Added inline redirection functions when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is off. + Marked the old functions are =delete when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is on, to allow for better type-checking. + Effectively the typical call site is changing from: + - Before: window->DrawList->AddRect(p_min, p_max, color, rounding, ImDrawFlags_None, border_size); + - After: window->DrawList->AddRect(p_min, p_max, color, rounding, border_size); + Notes: + - Users of C++ and other languages with type-checking will be notified at compile-time of any mistakes. + - Users of high-level bindings or languages with no type-checking will be notified at runtime via an assert for invalid flags value. + If you are a binding maintainer consider doing something to facilitate transition or error detection. + - This is perhaps the worst breaking change in our history :( but it makes ImDrawList function signatures consistent. + As we are aiming to add flags and features to variety of ImDrawList functions, that consistency becomes more important. + The new order is also more convenient as `flags` are less frequently used than `thickness` in real code. + - As a general policy in Dear ImGui, all our flags default to 0 so ImDrawFlags_None was likely written 0 in some call sites. + - Consider adding `#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS` in your imconfig.h, even temporarily, to clean up legacy code. + - 2026/04/23 (1.92.8) - DrawList: obsoleted `ImDrawCallback_ResetRenderState` in favor of using `ImGui::GetPlatformIO().DrawCallback_ResetRenderState`, which is part of our new standard draw callbacks. (#9378) + - 2026/04/22 (1.92.8) - Backends: Vulkan: redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. + - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler. + - When creating your own descriptor pool (instead of letting backend creates its own): need at least IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE + IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_SAMPLER. - 2026/03/19 (1.92.7) - MultiSelect: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto. - 2026/02/26 (1.92.7) - Separator: fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator. The fix could affect code e.g. computing height from multiple widgets in order to allocate vertical space for a footer or multi-line status bar. (#2657, #9263) @@ -1524,7 +1548,7 @@ ImGuiStyle::ImGuiStyle() TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. TabBorderSize = 0.0f; // Thickness of border around tabs. TabMinWidthBase = 1.0f; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. - TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. + TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. FLT_MAX: never shrink, will behave like ImGuiTabBarFlags_FittingPolicyScroll. TabCloseButtonMinWidthSelected = -1.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. TabCloseButtonMinWidthUnselected = 0.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected. TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. @@ -1573,6 +1597,7 @@ ImGuiStyle::ImGuiStyle() // Scale all spacing/padding/thickness values. Do not scale fonts. +// Consider not calling this if your initial scale factor if <1.0. // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. void ImGuiStyle::ScaleAllSizes(float scale_factor) { @@ -3454,9 +3479,6 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) if (clipper->ItemsHeight <= 0.0f) { IM_ASSERT(data->StepNo == 1); - if (table) - IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y); - bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision((float)clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); if (affected_by_floating_point_precision) { @@ -3470,7 +3492,14 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) } if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode. return false; - IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); + if (clipper->ItemsHeight <= 0.0f) + { + IM_ASSERT_USER_ERROR(clipper->ItemsHeight > 0.0f, "ImGuiListClipper: Failed to calculate item height! First item hasn't been submitted by user code, or has not moved the cursor vertically!"); + return false; + } + if (table) + IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y); + calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards. } @@ -3516,12 +3545,14 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) // FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos. // RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that. // As a workaround we currently half ItemSpacing worth on each side. - min_y -= g.Style.ItemSpacing.y; - max_y += g.Style.ItemSpacing.y; + float pad_y = g.Style.ItemSpacing.y; + min_y -= pad_y; + max_y += pad_y; // Box-select on 2D area requires different clipping. + // (best adding pad_y here than in BeginBoxSelect() as we are closer to current state) if (bs->UnclipMode) - data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0)); + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y - pad_y, bs->UnclipRect.Max.y + pad_y, 0, 0)); } // Add main visible range @@ -3720,6 +3751,7 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] = { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesSize)}, // ImGuiStyleVar_TreeLinesSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesRounding)}, // ImGuiStyleVar_TreeLinesRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DragDropTargetRounding)}, // ImGuiStyleVar_DragDropTargetRounding { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorSize)}, // ImGuiStyleVar_SeparatorSize @@ -3821,6 +3853,7 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; case ImGuiCol_CheckMark: return "CheckMark"; + case ImGuiCol_CheckboxSelectedBg: return "CheckboxSelectedBg"; case ImGuiCol_SliderGrab: return "SliderGrab"; case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; case ImGuiCol_Button: return "Button"; @@ -3903,7 +3936,7 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool else { if (!text_end) - text_end = text + ImStrlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT (not reached by our internal calls) text_display_end = text_end; } @@ -3921,7 +3954,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end ImGuiWindow* window = g.CurrentWindow; if (!text_end) - text_end = text + ImStrlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT (not reached by our internal calls) if (text != text_end) { @@ -3990,8 +4023,8 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con text_end_full = FindRenderedTextEnd(text); const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f); - //draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 6), IM_COL32(0, 0, 255, 255)); - //draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y - 2), ImVec2(ellipsis_max_x, pos_max.y + 3), IM_COL32(0, 255, 0, 255)); + //draw_list->AddLineV(pos_max.x, pos_min.y - 4, pos_max.y + 6, IM_COL32(0, 0, 255, 255)); + //draw_list->AddLineV(ellipsis_max_x, pos_min.y - 2, pos_max.y + 3, IM_COL32(0, 255, 0, 255)); // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels. if (text_size.x > pos_max.x - pos_min.x) @@ -4036,8 +4069,8 @@ void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders const float border_size = g.Style.FrameBorderSize; if (borders && border_size > 0.0f) { - window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); - window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, border_size); } } @@ -4048,8 +4081,8 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) const float border_size = g.Style.FrameBorderSize; if (border_size > 0.0f) { - window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); - window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, border_size); } } @@ -4084,7 +4117,7 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl const float thickness = 2.0f; if (flags & ImGuiNavRenderCursorFlags_Compact) { - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, thickness); } else { @@ -4093,7 +4126,7 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl bool fully_visible = window->ClipRect.Contains(display_rect); if (!fully_visible) window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, thickness); if (!fully_visible) window->DrawList->PopClipRect(); } @@ -4127,7 +4160,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI); float a_max = a_min + IM_PI * 1.65f; draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max); - draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale); + draw_list->PathStroke(col_fill, 3.0f * scale); } draw_list->PopTexture(); } @@ -4229,7 +4262,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); if (shared_font_atlas == NULL) IO.Fonts->OwnerContext = this; - WithinEndChildID = 0; + WithinEndChildID = WithinEndPopupID = 0; TestEngine = NULL; InputEventsNextMouseSource = ImGuiMouseSource_Mouse; @@ -4795,7 +4828,8 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.ActiveIdIsJustActivated = (g.ActiveId != id); if (g.ActiveIdIsJustActivated) { - IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() old:0x%08X (window \"%s\") -> new:0x%08X (window \"%s\")\n", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : "", id, window ? window->Name : ""); + IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() 0x%08X in \"%s\"%*s(previously 0x%08X in \"%s\")\n", id, window ? window->Name : "", + ImMax(0, 20 - (int)(window ? strlen(window->Name) : 0)), "", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : ""); g.ActiveIdTimer = 0.0f; g.ActiveIdHasBeenPressedBefore = false; g.ActiveIdHasBeenEditedBefore = false; @@ -4851,8 +4885,12 @@ void ImGui::MarkItemEdited(ImGuiID id) // This marking is to be able to provide info for IsItemDeactivatedAfterEdit(). // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data. ImGuiContext& g = *GImGui; + + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_EditedInternal; if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited) return; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; + if (g.ActiveId == id || g.ActiveId == 0) { // FIXME: Can't we fully rely on LastItemData yet? @@ -4866,9 +4904,6 @@ void ImGui::MarkItemEdited(ImGuiID id) // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) // FIXME: This assert is getting a bit meaningless over time. It helped detect some unusual use cases but eventually it is becoming an unnecessary restriction. IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); - - //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); - g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; } bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) @@ -5042,7 +5077,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag { g.HoveredIdPreviousFrameItemCount++; if (g.DebugDrawIdConflictsId == id) - window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); + window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, 2.0f); } #endif @@ -5920,7 +5955,7 @@ void ImGui::NewFrame() g.CurrentWindowStack.resize(0); g.BeginPopupStack.resize(0); g.ItemFlagsStack.resize(0); - g.ItemFlagsStack.push_back(ImGuiItemFlags_AutoClosePopups); // Default flags + g.ItemFlagsStack.push_back(ImGuiItemFlags_Default_); // Default flags g.CurrentItemFlags = g.ItemFlagsStack.back(); g.GroupStack.resize(0); @@ -6090,14 +6125,6 @@ void ImGui::PopClipRect() window->ClipRect = window->DrawList->_ClipRectStack.back(); } -static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window) -{ - for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--) - if (IsWindowActiveAndVisible(window->DC.ChildWindows[n])) - return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]); - return window; -} - static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) @@ -6199,7 +6226,7 @@ static void ImGui::RenderDimmedBackgrounds() if (window->DrawList->CmdBuffer.Size == 0) window->DrawList->AddDrawCmd(); window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size); - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f); // FIXME-DPI + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 3.0f); // FIXME-DPI window->DrawList->PopClipRect(); } @@ -6717,7 +6744,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking; window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag if (child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize)) - window_flags |= ImGuiWindowFlags_AlwaysAutoResize; + window_flags |= ImGuiWindowFlags_AlwaysAutoResize; // FIXME: Would be sane to not make single-axis flag set this. (#9355) if ((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0) window_flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; @@ -6866,6 +6893,14 @@ void ImGui::EndChild() g.LogLinePosY = -FLT_MAX; // To enforce a carriage return } +ImGuiWindow* ImGui::FindFrontMostVisibleChildWindow(ImGuiWindow* window) +{ + for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--) + if (IsWindowActiveAndVisible(window->DC.ChildWindows[n])) + return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]); + return window; +} + static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) { window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); @@ -7050,8 +7085,8 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont const float decoration_h_without_scrollbars = window->DecoOuterSizeY1 + window->DecoOuterSizeY2 - window->ScrollbarSizes.y; ImVec2 size_pad = window->WindowPadding * 2.0f; ImVec2 size_desired; - size_desired[ImGuiAxis_X] = (axis_mask & 1) ? size_contents.x + size_pad.x + decoration_w_without_scrollbars : window->Size.x; - size_desired[ImGuiAxis_Y] = (axis_mask & 2) ? size_contents.y + size_pad.y + decoration_h_without_scrollbars : window->Size.y; + size_desired.x = (axis_mask & 1) ? size_contents.x + size_pad.x + decoration_w_without_scrollbars : window->Size.x; + size_desired.y = (axis_mask & 2) ? size_contents.y + size_pad.y + decoration_h_without_scrollbars : window->Size.y; // Determine maximum window size // Child windows are laid within their parent (unless they are also popups/menus) and thus have no restriction @@ -7078,8 +7113,10 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont // When the window cannot fit all contents (either because of constraints, either because screen is too small), // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding. ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit); - bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); - bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_h_without_scrollbars < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); + float size_contents_for_scrollbar_x = (axis_mask & 1) ? size_contents.x : window->ContentSize.x; // See #9352. In theory this should use same logic as `window->ScrollbarY = ...` codepath in Begin(). Needs some plumbling. + float size_contents_for_scrollbar_y = (axis_mask & 2) ? size_contents.y : window->ContentSize.y; + bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x < size_contents_for_scrollbar_x + size_pad.x + decoration_w_without_scrollbars && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); + bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y < size_contents_for_scrollbar_y + size_pad.y + decoration_h_without_scrollbars && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); if (will_have_scrollbar_x) size_auto_fit.y += style.ScrollbarSize; if (will_have_scrollbar_y) @@ -7433,7 +7470,7 @@ static void RenderWindowOuterSingleBorder(ImGuiWindow* window, int border_n, ImU const ImRect border_r = GetResizeBorderRect(window, border_n, rounding, 0.0f); window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle); window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f); - window->DrawList->PathStroke(border_col, ImDrawFlags_None, border_size); + window->DrawList->PathStroke(border_col, border_size); } static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) @@ -7442,7 +7479,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) const float border_size = window->WindowBorderSize; const ImU32 border_col = GetColorU32(ImGuiCol_Border); if (border_size > 0.0f && (window->Flags & ImGuiWindowFlags_NoBackground) == 0) - window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, 0, window->WindowBorderSize); + window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, window->WindowBorderSize); else if (border_size > 0.0f) { if (window->ChildFlags & ImGuiChildFlags_ResizeX) // Similar code as 'resize_border_mask' computation in UpdateWindowManualResize() but we specifically only always draw explicit child resize border. @@ -7459,7 +7496,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { float y = window->Pos.y + window->TitleBarHeight - 1; - window->DrawList->AddLine(ImVec2(window->Pos.x + border_size * 0.5f, y), ImVec2(window->Pos.x + window->Size.x - border_size * 0.5f, y), border_col, g.Style.FrameBorderSize); + window->DrawList->AddLineH(window->Pos.x + border_size * 0.5f, window->Pos.x + window->Size.x - border_size * 0.5f, y, border_col, g.Style.FrameBorderSize); } } @@ -7569,7 +7606,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) - window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5f, menu_bar_rect.Max.x - window_border_size * 0.5f, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock @@ -8357,12 +8394,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2)); ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + scrollbar_sizes_from_last_frame; ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f; - float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x; - float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; + float size_for_scrollbars_x = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x; + float size_for_scrollbars_y = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; bool scrollbar_x_prev = window->ScrollbarX; //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons? - window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); - window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_for_scrollbars_y) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_for_scrollbars_x - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); // Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488) // (Feedback loops of this sort can manifest in various situations, but combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent cause. @@ -8377,7 +8414,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ScrollbarXStabilizeEnabled = scrollbar_x_stabilize; if (window->ScrollbarX && !window->ScrollbarY) - window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarY = (needed_size_from_last_frame.y > size_for_scrollbars_y - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); // Amend the partially filled window->DecorationXXX values. @@ -8546,9 +8583,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.LayoutType = ImGuiLayoutType_Vertical; window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; - // Default item width. Make it proportional to window size if window manually resizes - const bool is_resizable_window = (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)); - if (is_resizable_window) + // Default item width. Make it proportional to window size if window can be manually resized. + // (we cannot use AutoFitFramesX/AutoFitFramesY which is a temporary state) + bool is_resizable_width; + if (flags & ImGuiWindowFlags_ChildWindow) + is_resizable_width = (window->Size.x > 0.0f) && !(window->ChildFlags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AlwaysAutoResize)); + else + is_resizable_width = (window->Size.x > 0.0f) && !(flags & ImGuiWindowFlags_AlwaysAutoResize); + if (is_resizable_width) window->DC.ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); else window->DC.ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); @@ -8770,6 +8812,8 @@ void ImGui::End() ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back(); // Error checking: verify that user doesn't directly call End() on a child window. + if (window->Flags & ImGuiWindowFlags_Popup) + IM_ASSERT_USER_ERROR(g.WithinEndPopupID == window->ID, "Must call EndPopup() and not End()!"); if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->DockIsActive) IM_ASSERT_USER_ERROR(g.WithinEndChildID == window->ID, "Must call EndChild() and not End()!"); @@ -9367,6 +9411,17 @@ void ImGui::PopFocusScope() g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back().ID : 0; } +bool ImGui::IsInNavFocusRoute(ImGuiID focus_scope_id) +{ + ImGuiContext& g = *GImGui; + if (g.NavFocusScopeId == focus_scope_id) + return true; + for (const ImGuiFocusScopeData& focus_scope : g.NavFocusRoute) + if (focus_scope.ID == focus_scope_id) + return true; + return false; +} + void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) { ImGuiContext& g = *GImGui; @@ -9601,7 +9656,7 @@ ImFont* ImGui::GetDefaultFont() return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0]; } -// EXPERIMENTAL: DO NOT USE YET. +// EXPERIMENTAL. Use ImTextureDataQueueUpload() to queue updates. void ImGui::RegisterUserTexture(ImTextureData* tex) { ImGuiContext& g = *GImGui; @@ -9723,7 +9778,7 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) } g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; - g.FontBakedScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; + g.FontBakedScale = (g.FontBaked != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; g.DrawListSharedData.FontScale = g.FontBakedScale; } @@ -10936,15 +10991,21 @@ void ImGui::UpdateMouseWheel() LockWheelingWindow(NULL, 0.0f); } - ImVec2 wheel; - wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheelH : 0.0f; - wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheel : 0.0f; - - //IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y); ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow; if (!mouse_window || mouse_window->Collapsed) return; + ImGuiID owner_id = mouse_window->ID; + ImVec2 wheel; + wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, owner_id) ? g.IO.MouseWheelH : 0.0f; + wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, owner_id) ? g.IO.MouseWheel : 0.0f; + //IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y); + if (g.WheelingWindow != NULL) + { + SetKeyOwner(ImGuiKey_MouseWheelX, owner_id); + SetKeyOwner(ImGuiKey_MouseWheelY, owner_id); + } + // Zoom / Scale window // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned. if (wheel.y != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling) @@ -11253,6 +11314,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) // - SetKeyOwner(..., None) : clears owner // - SetKeyOwner(..., Any, !Lock) : illegal (assert) // - SetKeyOwner(..., Any or None, Lock) : set lock +// Ownership is automatically released on the frame after a release, see code in UpdateKeyboardInputs(). void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; @@ -11279,30 +11341,34 @@ void ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, I if (key_chord & ~ImGuiMod_Mask_) { SetKeyOwner((ImGuiKey)(key_chord & ~ImGuiMod_Mask_), owner_id, flags); } } -// This is more or less equivalent to: +// This is more or less equivalent to a fancier version of: // if (IsItemHovered() || IsItemActive()) // SetKeyOwner(key, GetItemID()); // Extensive uses of that (e.g. many calls for a single item) may want to manually perform the tests once and then call SetKeyOwner() multiple times. // More advanced usage scenarios may want to call SetKeyOwner() manually based on different condition. // Worth noting is that only one item can be hovered and only one item can be active, therefore this usage pattern doesn't need to bother with routing and priority. -void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) +bool ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; ImGuiID id = g.LastItemData.ID; if (id == 0 || (g.HoveredId != id && g.ActiveId != id)) - return; + return false; if ((flags & ImGuiInputFlags_CondMask_) == 0) flags |= ImGuiInputFlags_CondDefault_; if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) || (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive))) { IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) == 0); // Passing flags not supported by this function! + if (!TestKeyOwner(key, id)) + return false; SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_); + return true; } + return false; } -void ImGui::SetItemKeyOwner(ImGuiKey key) +bool ImGui::SetItemKeyOwner(ImGuiKey key) { - SetItemKeyOwner(key, ImGuiInputFlags_None); + return SetItemKeyOwner(key, ImGuiInputFlags_None); } // This is the only public API until we expose owner_id versions of the API as replacements. @@ -11443,7 +11509,8 @@ bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, si // to extend contents size of our parent container (e.g. window contents size, which is used for auto-resizing // windows, table column contents size used for auto-resizing columns, group size). // This was causing issues and ambiguities and we needed to retire that. -// From 1.89, extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED. +// 2022/08/05 (1.89): extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED. However we gated the new logic behind a '#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS' block. +// 2025/06/25 (1.92): removed the legacy path and turned into an assert. It was a mistake that there was a #ifndef before: our obsolescence schedule gets pushed back a bit more :( // // Previously this would make the window content size ~200x200: // Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK ANYMORE @@ -13102,6 +13169,17 @@ bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags ext return false; } + // As we bypass BeginChild(), set ImGuiChildFlags_AlwaysAutoResize as it is checked independently from ImGuiWindowFlags_AlwaysAutoResize for now (see #9355) + // Ideally we should remove setting ImGuiWindowFlags_AlwaysAutoResize in BeginChild(). + if ((extra_window_flags & ImGuiWindowFlags_ChildWindow) && (extra_window_flags & ImGuiWindowFlags_AlwaysAutoResize)) + { + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) + g.NextWindowData.ChildFlags |= ImGuiChildFlags_AlwaysAutoResize; + else + g.NextWindowData.ChildFlags = ImGuiChildFlags_AlwaysAutoResize; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; + } + char name[128]; IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu); ImFormatString(name, IM_COUNTOF(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth @@ -13174,10 +13252,13 @@ void ImGui::EndPopup() NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY); // Child-popups don't need to be laid out + const ImGuiID backup_within_end_popup_id = g.WithinEndPopupID; const ImGuiID backup_within_end_child_id = g.WithinEndChildID; + g.WithinEndPopupID = window->ID; if (window->Flags & ImGuiWindowFlags_ChildWindow) g.WithinEndChildID = window->ID; End(); + g.WithinEndPopupID = backup_within_end_popup_id; g.WithinEndChildID = backup_within_end_child_id; } @@ -14025,7 +14106,7 @@ static void ImGui::NavProcessItem() const ImGuiID id = g.LastItemData.ID; const ImGuiItemFlags item_flags = g.LastItemData.ItemFlags; - // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221, #8816) + // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221, #8816, #7994) ImRect nav_bb = g.LastItemData.NavRect; if (window->DC.NavIsScrollPushableX == false) { @@ -15807,7 +15888,7 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop IM_ASSERT(viewport != NULL); ImRect bb = g.DragDropTargetRect; bb.Expand(-3.5f); - RenderDragDropTargetRectEx(GetForegroundDrawList(viewport), bb); + RenderDragDropTargetRectEx(GetForegroundDrawList(viewport), bb, g.Style.DragDropTargetRounding); } else if (draw_target_rect) { @@ -15838,16 +15919,16 @@ void ImGui::RenderDragDropTargetRectForItem(const ImRect& bb) bool push_clip_rect = !window->ClipRect.Contains(bb_display); if (push_clip_rect) window->DrawList->PushClipRectFullScreen(); - RenderDragDropTargetRectEx(window->DrawList, bb_display); + RenderDragDropTargetRectEx(window->DrawList, bb_display, g.Style.DragDropTargetRounding); if (push_clip_rect) window->DrawList->PopClipRect(); } -void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb) +void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding) { ImGuiContext& g = *GImGui; - draw_list->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTargetBg), g.Style.DragDropTargetRounding, 0); - draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTarget), g.Style.DragDropTargetRounding, 0, g.Style.DragDropTargetBorderSize); + draw_list->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTargetBg), rounding, 0); + draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTarget), rounding, g.Style.DragDropTargetBorderSize); } const ImGuiPayload* ImGui::GetDragDropPayload() @@ -16576,6 +16657,7 @@ void ImGuiPlatformIO::ClearRendererHandlers() Renderer_CreateWindow = Renderer_DestroyWindow = NULL; Renderer_SetWindowSize = NULL; Renderer_RenderWindow = Renderer_SwapBuffers = NULL; + DrawCallback_ResetRenderState = DrawCallback_SetSamplerLinear = DrawCallback_SetSamplerNearest = NULL; } ImGuiViewport* ImGui::GetMainViewport() @@ -17366,6 +17448,8 @@ void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_win window->Viewport->Flags = viewport_flags; + window->Viewport->PlatformIconData = window->WindowClass.PlatformIconData; + // Update parent viewport ID // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport()) if (window->WindowClass.ParentViewportId != (ImGuiID)-1) @@ -18883,10 +18967,13 @@ static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node) node->WantHiddenTabBarToggle = false; // Apply toggles at a single point of the frame (here!) + const ImGuiDockNodeFlags prev_local_flags = node->LocalFlags; if (node->Windows.Size > 1) node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); else if (node->WantHiddenTabBarToggle) node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar); + if ((node->LocalFlags ^ prev_local_flags) & ImGuiDockNodeFlags_SavedFlagsMask_) + MarkIniSettingsDirty(); // Bit flaky to only do this here. Perhaps compare node flags every frame? #9380 node->WantHiddenTabBarToggle = false; DockNodeUpdateVisibleFlag(node); @@ -21987,7 +22074,7 @@ void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding); ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y); ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y); - draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, 2.0f); + draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, 2.0f); draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding); ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y); draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_data->Label); @@ -22410,7 +22497,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); if (IsItemHovered()) - GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f); Indent(); char buf[128]; for (int rect_n = 0; rect_n < TRT_Count; rect_n++) @@ -22425,7 +22512,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImFormatString(buf, IM_COUNTOF(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]); Selectable(buf); if (IsItemHovered()) - GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f); } } else @@ -22434,7 +22521,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImFormatString(buf, IM_COUNTOF(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]); Selectable(buf); if (IsItemHovered()) - GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f); } } Unindent(); @@ -22922,7 +23009,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n); ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255); float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f; - draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness); + draw_list->AddRect(r.Min, r.Max, col, 0.0f, thickness); } } else @@ -23199,7 +23286,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con { ImDrawListFlags backup_flags = fg_draw_list->Flags; fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. - fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); + fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed); fg_draw_list->Flags = backup_flags; } } @@ -23227,7 +23314,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co for (int n = 0; n < 3; n++, idx_n++) vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos)); if (show_mesh) - out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); // In yellow: mesh triangles + out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed); // In yellow: mesh triangles } // Draw bounding boxes if (show_aabb) @@ -23508,8 +23595,8 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) { ImDrawList* draw_list = GetForegroundDrawList(tab_bar->Window); draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); - draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); - draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + draw_list->AddLineV(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y, tab_bar->BarRect.Max.y, IM_COL32(0, 255, 0, 255)); + draw_list->AddLineV(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y, tab_bar->BarRect.Max.y, IM_COL32(0, 255, 0, 255)); } if (open) { @@ -23786,7 +23873,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("ActiveId", ImGuiDebugLogFlags_EventActiveId); ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper); ShowDebugLogFlag("Docking", ImGuiDebugLogFlags_EventDocking); - ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); + ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_FocusEvent); ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); @@ -23871,8 +23958,8 @@ void ImGui::DebugDrawCursorPos(ImU32 col) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 pos = window->DC.CursorPos; - window->DrawList->AddLine(ImVec2(pos.x, pos.y - 3.0f), ImVec2(pos.x, pos.y + 4.0f), col, 1.0f); - window->DrawList->AddLine(ImVec2(pos.x - 3.0f, pos.y), ImVec2(pos.x + 4.0f, pos.y), col, 1.0f); + window->DrawList->AddLineV(pos.x, pos.y - 3.0f, pos.y + 4.0f, col, 1.0f); + window->DrawList->AddLineH(pos.x - 3.0f, pos.x + 4.0f, pos.y, col, 1.0f); } // Draw a 10px wide rectangle around CurposPos.x using Line Y1/Y2 in current window's DrawList @@ -23883,9 +23970,9 @@ void ImGui::DebugDrawLineExtents(ImU32 col) float curr_x = window->DC.CursorPos.x; float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y); float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y); - window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y1), ImVec2(curr_x + 5.0f, line_y1), col, 1.0f); - window->DrawList->AddLine(ImVec2(curr_x - 0.5f, line_y1), ImVec2(curr_x - 0.5f, line_y2), col, 1.0f); - window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y2), ImVec2(curr_x + 5.0f, line_y2), col, 1.0f); + window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y1, col, 1.0f); + window->DrawList->AddLineV(curr_x - 0.5f, line_y1, line_y2, col, 1.0f); + window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y2, col, 1.0f); } // Draw last item rect in ForegroundDrawList (so it is always visible) @@ -24255,7 +24342,7 @@ void ImGui::ShowFontSelector(const char* label) "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" "- Read FAQ and docs/FONTS.md for more details.\n" - "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); + "- Legacy backend: if you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); } #endif // #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) diff --git a/extensions/ImGui/src/ImGui/imgui.h b/extensions/ImGui/src/ImGui/imgui.h index 7bf34c842d3f..86cb07b4d8d8 100644 --- a/extensions/ImGui/src/ImGui/imgui.h +++ b/extensions/ImGui/src/ImGui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.7 +// dear imgui, v1.92.8 // (headers) // Help: @@ -29,8 +29,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.92.7" -#define IMGUI_VERSION_NUM 19270 +#define IMGUI_VERSION "1.92.8" +#define IMGUI_VERSION_NUM 19280 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 #define IMGUI_HAS_VIEWPORT // In 'docking' WIP branch. @@ -1128,10 +1128,11 @@ namespace ImGui // Inputs Utilities: Key/Input Ownership [BETA] // - One common use case would be to allow your items to disable standard inputs behaviors such // as Tab or Alt key handling, Mouse Wheel scrolling, etc. - // e.g. Button(...); SetItemKeyOwner(ImGuiKey_MouseWheelY); to make hovering/activating a button disable wheel for scrolling. + // e.g. `Button(...); if (SetItemKeyOwner(ImGuiKey_MouseWheelY)) { ... }` to make hovering/activating a button disable wheel for scrolling. // - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them. + // - The return value of SetItemKeyOwner() says if ownership has been requested for the item, which is a shortcut to calling yet non-public TestKeyOwner() function. // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version. - IMGUI_API void SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Return true when ownership has been set. Roughly equivalent to 'if (TestKeyOwner(key, GetItemID()) && (IsItemHovered() || IsItemActive())) { SetKeyOwner(key, GetItemID());'. // Inputs Utilities: Mouse // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. @@ -1448,7 +1449,7 @@ enum ImGuiTabBarFlags_ ImGuiTabBarFlags_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab // Fitting/Resize policy - ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling buttons. + ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling. Setting TabMinWidthShrink to FLT_MAX makes this behave like ImGuiTabBarFlags_FittingPolicyScroll. ImGuiTabBarFlags_FittingPolicyShrink = 1 << 8, // Shrink down tabs when they don't fit ImGuiTabBarFlags_FittingPolicyScroll = 1 << 9, // Enable scrolling buttons when tabs don't fit ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyMixed | ImGuiTabBarFlags_FittingPolicyShrink | ImGuiTabBarFlags_FittingPolicyScroll, @@ -1813,7 +1814,7 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_RendererHasViewports = 1 << 10, // Backend Renderer supports multiple viewports. ImGuiBackendFlags_PlatformHasViewports = 1 << 11, // Backend Platform supports multiple viewports. ImGuiBackendFlags_HasMouseHoveredViewport=1 << 12, // Backend Platform supports calling io.AddMouseViewportEvent() with the viewport under the mouse. IF POSSIBLE, ignore viewports with the ImGuiViewportFlags_NoInputs flag (Win32 backend, GLFW 3.30+ backend can do this, SDL backend cannot). If this cannot be done, Dear ImGui needs to use a flawed heuristic to find the viewport under. - ImGuiBackendFlags_HasParentViewport = 1 << 13, // Backend Platform supports honoring viewport->ParentViewport/ParentViewportId value, by applying the corresponding parent/child relation at the Platform level. + ImGuiBackendFlags_HasParentViewport = 1 << 13, // Backend Platform supports honoring viewport->ParentViewport/ParentViewportId value, by applying the corresponding parent/child relationship at the Platform level. Child windows always appear in front of their parent window. }; // Enumeration for PushStyleColor() / PopStyleColor() @@ -1838,6 +1839,7 @@ enum ImGuiCol_ ImGuiCol_ScrollbarGrabHovered, ImGuiCol_ScrollbarGrabActive, ImGuiCol_CheckMark, // Checkbox tick and RadioButton circle + ImGuiCol_CheckboxSelectedBg, // Checkbox background when Selected, otherwise use FrameBg ImGuiCol_SliderGrab, ImGuiCol_SliderGrabActive, ImGuiCol_Button, @@ -1937,6 +1939,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign ImGuiStyleVar_TreeLinesSize, // float TreeLinesSize ImGuiStyleVar_TreeLinesRounding, // float TreeLinesRounding + ImGuiStyleVar_DragDropTargetRounding, // float DragDropTargetRounding ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign ImGuiStyleVar_SeparatorSize, // float SeparatorSize @@ -2405,7 +2408,7 @@ struct ImGuiStyle float TabBorderSize; // Thickness of border around tabs. float TabMinWidthBase; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. float TabMinWidthShrink; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. - float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. + float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never shrink, will behave like ImGuiTabBarFlags_FittingPolicyScroll. float TabCloseButtonMinWidthUnselected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected. float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. float TabBarOverlineSize; // Thickness of tab-bar overline, which highlights the selected tab-bar. @@ -2414,14 +2417,14 @@ struct ImGuiStyle ImGuiTreeNodeFlags TreeLinesFlags; // Default way to draw lines connecting TreeNode hierarchy. ImGuiTreeNodeFlags_DrawLinesNone or ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes. float TreeLinesSize; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. float TreeLinesRounding; // Radius of lines connecting child nodes to the vertical line. - float DragDropTargetRounding; // Radius of the drag and drop target frame. + float DragDropTargetRounding; // Radius of the drag and drop target frame. When <0.0f: use FrameRounding. float DragDropTargetBorderSize; // Thickness of the drag and drop target border. float DragDropTargetPadding; // Size to expand the drag and drop target from actual target item size. float ColorMarkerSize; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers. ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. - float SeparatorSize; // Thickness of border in Separator() + float SeparatorSize; // Thickness of border in Separator(). Must be >= 1.0f. float SeparatorTextBorderSize; // Thickness of border in SeparatorText() ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). ImVec2 SeparatorTextPadding; // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. @@ -2453,7 +2456,7 @@ struct ImGuiStyle // Functions IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. + IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. See comments in definition. Consider not calling this if your initial scale factor if <1.0. // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -2521,10 +2524,11 @@ struct ImGuiIO bool ConfigDockingTransparentPayload;// = false // [BETA] Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge. // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set) + // (sorry for the amount of "NoXXXX" flags, which may be harder to reason about! may rework someday) bool ConfigViewportsNoAutoMerge; // = false; // Set to make all floating imgui windows always create their own viewport. Otherwise, they are merged into the main host viewports when overlapping it. May also set ImGuiViewportFlags_NoAutoMerge on individual viewport. bool ConfigViewportsNoTaskBarIcon; // = false // Disable default OS task bar icon flag for secondary viewports. When a viewport doesn't want a task bar icon, ImGuiViewportFlags_NoTaskBarIcon will be set on it. bool ConfigViewportsNoDecoration; // = true // Disable default OS window decoration flag for secondary viewports. When a viewport doesn't want window decorations, ImGuiViewportFlags_NoDecoration will be set on it. Enabling decoration can create subsequent issues at OS levels (e.g. minimum window size). - bool ConfigViewportsNoDefaultParent; // = true // When false: set secondary viewports' ParentViewportId to main viewport ID by default. Expects the platform backend to setup a parent/child relationship between the OS windows based on this value. Some backend may ignore this. Set to true if you want viewports to automatically be parent of main viewport, otherwise all viewports will be top-level OS windows. + bool ConfigViewportsNoDefaultParent; // = true // Disable setting OS window parent to main viewport by default. The platform backend is expected to honor `viewport->ParentViewportID` to setup a parent/child relationship between the OS windows (supported if ImGuiBackendFlags_HasParentViewport is set). When parented: child windows always appear in front of their parent. Set to false if you want viewports to automatically be parent of main viewport, otherwise all viewports will be top-level OS windows. Parent/child relationship may be set on a per-window basis using ImGuiWindowClass. bool ConfigViewportsPlatformFocusSetsImGuiFocus;//= true // When a platform window is focused (e.g. using Alt+Tab, clicking Platform Title Bar), apply corresponding focus on imgui windows (may clear focus/active id from imgui windows location in other platform windows). In principle this is better enabled but we provide an opt-out, because some Linux window managers tend to eagerly focus windows (e.g. on mouse hover, or even a simple window pos/size change). // DPI/Scaling options @@ -2752,7 +2756,7 @@ struct ImGuiInputTextCallbackData ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only void* UserData; // What user passed to InputText() // Read-only - ImGuiID ID; // Widget ID // Read-only + ImGuiID ID; // Widget ID // Read-only // Arguments for the different callback events // - During Resize callback, Buf will be same as your input buffer. @@ -2766,9 +2770,9 @@ struct ImGuiInputTextCallbackData char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length() int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land: == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 - int CursorPos; // // Read-write // [Completion,History,Always] - int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection - int SelectionEnd; // // Read-write // [Completion,History,Always] + int CursorPos; // // Read-write // [Completion,History,Always,CharFilter] + int SelectionStart; // // Read-write // [Completion,History,Always,CharFilter] == to SelectionEnd when no selection + int SelectionEnd; // // Read-write // [Completion,History,Always,CharFilter] // Helper functions for text manipulation. // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. @@ -2796,7 +2800,7 @@ struct ImGuiSizeCallbackData // before we stabilize Docking features. Please be mindful if using this. // Provide hints: // - To the platform backend via altered viewport flags (enable/disable OS decoration, OS task bar icons, etc.) -// - To the platform backend for OS level parent/child relationships of viewport. +// - To the platform backend for OS level parent/child relationships of viewport (otherwise: default is configured via io.ConfigViewportsNoDefaultParent) // - To the docking system for various options and filtering. struct ImGuiWindowClass { @@ -2809,6 +2813,7 @@ struct ImGuiWindowClass ImGuiDockNodeFlags DockNodeFlagsOverrideSet; // [EXPERIMENTAL] Dock node flags to set when a window of this class is hosted by a dock node (it doesn't have to be selected!) bool DockingAlwaysTabBar; // Set to true to enforce single floating windows of this class always having their own docking node (equivalent of setting the global io.ConfigDockingAlwaysTabBar) bool DockingAllowUnclassed; // Set to true to allow windows of this class to be docked/merged with an unclassed window. // FIXME-DOCK: Move to DockNodeFlags override? + void* PlatformIconData; // [EXPERIMENTAL] Pass opaque data for Platform backend to handle. ImGuiWindowClass() { memset((void*)this, 0, sizeof(*this)); ParentViewportId = (ImGuiID)-1; DockingAllowUnclassed = true; } }; @@ -3298,12 +3303,6 @@ typedef unsigned short ImDrawIdx; // Default: 16-bit (for maximum compatibilit typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); #endif -// Special Draw callback value to request renderer backend to reset the graphics/render state. -// The renderer backend needs to handle this special value, otherwise it will crash trying to call a function at this address. -// This is useful, for example, if you submitted callbacks which you know have altered the render state and you want it to be restored. -// Render state is not reset by default because they are many perfectly useful way of altering render state (e.g. changing shader/blending settings before an Image call). -#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) - // Typically, 1 command = 1 GPU draw call (unless command is a callback) // - VtxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled, // this fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices. @@ -3377,16 +3376,15 @@ struct ImDrawListSplitter }; // Flags for ImDrawList functions -// (Legacy: bit 0 must always correspond to ImDrawFlags_Closed to be backward compatible with old API using a bool. Bits 1..3 must be unused) enum ImDrawFlags_ { ImDrawFlags_None = 0, - ImDrawFlags_Closed = 1 << 0, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason) ImDrawFlags_RoundCornersTopLeft = 1 << 4, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-left corner only (when rounding > 0.0f, we default to all corners). Was 0x01. ImDrawFlags_RoundCornersTopRight = 1 << 5, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-right corner only (when rounding > 0.0f, we default to all corners). Was 0x02. ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-left corner only (when rounding > 0.0f, we default to all corners). Was 0x04. ImDrawFlags_RoundCornersBottomRight = 1 << 7, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-right corner only (when rounding > 0.0f, we default to all corners). Wax 0x08. ImDrawFlags_RoundCornersNone = 1 << 8, // AddRect(), AddRectFilled(), PathRect(): disable rounding on all corners (when rounding > 0.0f). This is NOT zero, NOT an implicit flag! + ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason) ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, @@ -3394,6 +3392,7 @@ enum ImDrawFlags_ ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified. ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, + ImDrawFlags_InvalidMask_ = (ImDrawFlags)0x8000000F, }; // Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. @@ -3459,7 +3458,9 @@ struct ImDrawList // In future versions we will use textures to provide cheaper and higher-quality circles. // Use AddNgon() and AddNgonFilled() functions if you need to guarantee a specific number of sides. IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); // a: upper-left, b: lower-right (== upper-left + size) + IMGUI_API void AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f); @@ -3480,7 +3481,7 @@ struct ImDrawList // General polygon // - Only simple polygons are supported by filling functions (no self-intersections, no holes). // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. - IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); @@ -3500,7 +3501,7 @@ struct ImDrawList inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } - inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; } + inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse @@ -3510,14 +3511,15 @@ struct ImDrawList // Advanced: Draw Callbacks // - May be used to alter render state (change sampler, blending, current shader). May be used to emit custom rendering commands (difficult to do correctly, but possible). - // - Use special ImDrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default. + // - Use special GetPlatformIO().DrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default. + // - See other standard callbacks in GetPlatformIO(), which may or not be supported by your backend. // - Your rendering loop must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. All standard backends are honoring this. // - For some backends, the callback may access selected render-states exposed by the backend in a ImGui_ImplXXXX_RenderState structure pointed to by platform_io.Renderer_RenderState. // - IMPORTANT: please be mindful of the different level of indirection between using size==0 (copying argument) and using size>0 (copying pointed data into a buffer). // - If userdata_size == 0: we copy/store the 'userdata' argument as-is. It will be available unmodified in ImDrawCmd::UserCallbackData during render. // - If userdata_size > 0, we copy/store 'userdata_size' bytes pointed to by 'userdata'. We store them in a buffer stored inside the drawlist. ImDrawCmd::UserCallbackData will point inside that buffer so you have to retrieve data from there. Your callback may need to use ImDrawCmd::UserCallbackDataSize if you expect dynamically-sized data. // - Support for userdata_size > 0 was added in v1.91.4, October 2024. So earlier code always only allowed to copy/store a simple void*. - IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size = 0); + IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata = NULL, size_t userdata_size = 0); // Advanced: Miscellaneous IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible @@ -3547,8 +3549,15 @@ struct ImDrawList // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) { AddRect(p_min, p_max, col, rounding, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED. + inline void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) { AddPolyline(points, num_points, col, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED. + inline void PathStroke(ImU32 col, ImDrawFlags flags, float thickness) { PathStroke(col, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED. inline void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.0 inline void PopTextureID() { PopTexture(); } // RENAMED in 1.92.0 +#else + IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding /*= 0.0f*/, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete; + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) = delete; + inline void PathStroke(ImU32 col, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete; #endif //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) @@ -3660,6 +3669,7 @@ struct ImTextureData bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. // Functions + // - If GetPixels() functions asserts while being called by your render loop, it could be caused by calling ImFontAtlas::Clear() instead of ClearFonts()? ImTextureData() { memset((void*)this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; } ~ImTextureData() { DestroyPixels(); } IMGUI_API void Create(ImTextureFormat format, int w, int h); @@ -3815,13 +3825,13 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. IMGUI_API void RemoveFont(ImFont* font); - IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures). + IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame! + IMGUI_API void ClearFonts(); // Clear input+output font data/glyphs. You can call this mid-frame if you load new fonts afterwards! IMGUI_API void CompactCache(); // Compact cached glyphs and texture. IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. // As we are transitioning toward a new font system, we expect to obsolete those soon: IMGUI_API void ClearInputData(); // [OBSOLETE] Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. - IMGUI_API void ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -3993,6 +4003,7 @@ enum ImFontFlags_ ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. + ImFontFlags_ImplicitRefSize = 1 << 4, // [Internal] Reference size was not set explicitly. }; // Font runtime data and rendering @@ -4121,6 +4132,7 @@ struct ImGuiViewport // The library never uses those fields, they are merely storage to facilitate backend implementation. void* RendererUserData; // void* to hold custom data structure for the renderer (e.g. swap chain, framebuffers etc.). generally set by your Renderer_CreateWindow function. void* PlatformUserData; // void* to hold custom data structure for the OS / platform (e.g. windowing info, render context). generally set by your Platform_CreateWindow function. + void* PlatformIconData; // void* to hold custom data structure for the OS / platform to specify an icon. Currently unused for exposed to allow experiments. void* PlatformHandle; // void* to hold higher-level, platform window handle (e.g. HWND for Win32 backend, Uint32 WindowID for SDL, GLFWWindow* for GLFW), for FindViewportByPlatformHandle(). void* PlatformHandleRaw; // void* to hold lower-level, platform-native window handle (always HWND on Win32 platform, unused for other platforms). bool PlatformWindowCreated; // Platform window has been created (Platform_CreateWindow() has been called). This is false during the first frame where a viewport is being created. @@ -4228,6 +4240,12 @@ struct ImGuiPlatformIO // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. void* Renderer_RenderState; + // Standard draw callbacks provided by renderer backend. + ImDrawCallback DrawCallback_ResetRenderState; // Request to reset the graphics/render state. + ImDrawCallback DrawCallback_SetSamplerLinear; // Request backend to set texture sampling to Linear. + ImDrawCallback DrawCallback_SetSamplerNearest; // Request backend to set texture sampling to Nearest/Point. + //ImDrawCallback DrawCallback_SetSamplerCustom; // Request backend to set texture sampling using Backend Specific data. + //------------------------------------------------------------------ // Input - Interface with Platform & Renderer backends for Multi-Viewport support //------------------------------------------------------------------ @@ -4421,6 +4439,8 @@ namespace ImGui //static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 } +#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) // OBSOLETED in 1.92.8: Use ImGui::GetPlatformIO().DrawCallback_ResetRenderState + //-- OBSOLETED in 1.92.0: ImFontAtlasCustomRect becomes ImTextureRect // - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y // - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h diff --git a/extensions/ImGui/src/ImGui/imgui_demo.cpp b/extensions/ImGui/src/ImGui/imgui_demo.cpp index 9f4bc8d3cfa4..3941966a1c33 100644 --- a/extensions/ImGui/src/ImGui/imgui_demo.cpp +++ b/extensions/ImGui/src/ImGui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.7 +// dear imgui, v1.92.8 // (demo code) // Help: @@ -73,6 +73,7 @@ Index of this file: // [SECTION] Demo Window / ShowDemoWindow() // [SECTION] DemoWindowMenuBar() // [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) +// [SECTION] Helpers: ExampleImageViewer // [SECTION] DemoWindowWidgetsBasic() // [SECTION] DemoWindowWidgetsBullets() // [SECTION] DemoWindowWidgetsCollapsingHeaders() @@ -108,6 +109,7 @@ Index of this file: // [SECTION] User Guide / ShowUserGuide() // [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() // [SECTION] Example App: Debug Console / ShowExampleAppConsole() +// [SECTION] Example App: Image Viewer / ShowExampleAppImageViewer() // [SECTION] Example App: Debug Log / ShowExampleAppLog() // [SECTION] Example App: Simple Layout / ShowExampleAppLayout() // [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() @@ -240,6 +242,7 @@ static void ShowExampleAppConsole(bool* p_open); static void ShowExampleAppCustomRendering(bool* p_open); static void ShowExampleAppDockSpace(bool* p_open); static void ShowExampleAppDocuments(bool* p_open); +static void ShowExampleAppImageViewer(bool* p_open); static void ShowExampleAppLog(bool* p_open); static void ShowExampleAppLayout(bool* p_open); static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data); @@ -321,6 +324,7 @@ struct ImGuiDemoWindowData bool ShowAppCustomRendering = false; bool ShowAppDocuments = false; bool ShowAppDockSpace = false; + bool ShowAppImageViewer = false; bool ShowAppLog = false; bool ShowAppLayout = false; bool ShowAppPropertyEditor = false; @@ -367,6 +371,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (demo_data.ShowAppAssetsBrowser) { ShowExampleAppAssetsBrowser(&demo_data.ShowAppAssetsBrowser); } if (demo_data.ShowAppConsole) { ShowExampleAppConsole(&demo_data.ShowAppConsole); } if (demo_data.ShowAppCustomRendering) { ShowExampleAppCustomRendering(&demo_data.ShowAppCustomRendering); } + if (demo_data.ShowAppImageViewer) { ShowExampleAppImageViewer(&demo_data.ShowAppImageViewer); } if (demo_data.ShowAppLog) { ShowExampleAppLog(&demo_data.ShowAppLog); } if (demo_data.ShowAppLayout) { ShowExampleAppLayout(&demo_data.ShowAppLayout); } if (demo_data.ShowAppPropertyEditor) { ShowExampleAppPropertyEditor(&demo_data.ShowAppPropertyEditor, &demo_data); } @@ -748,6 +753,7 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) ImGui::MenuItem("Custom rendering", NULL, &demo_data->ShowAppCustomRendering); ImGui::MenuItem("Documents", NULL, &demo_data->ShowAppDocuments); ImGui::MenuItem("Dockspace", NULL, &demo_data->ShowAppDockSpace); + ImGui::MenuItem("Image Viewer", NULL, &demo_data->ShowAppImageViewer); ImGui::MenuItem("Log", NULL, &demo_data->ShowAppLog); ImGui::MenuItem("Property editor", NULL, &demo_data->ShowAppPropertyEditor); ImGui::MenuItem("Simple layout", NULL, &demo_data->ShowAppLayout); @@ -779,7 +785,7 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) ImGui::Checkbox("Highlight ID Conflicts", &io.ConfigDebugHighlightIdConflicts); ImGui::EndDisabled(); ImGui::Checkbox("Assert on error recovery", &io.ConfigErrorRecoveryEnableAssert); - ImGui::TextDisabled("(see Demo->Configuration for details & more)"); + ImGui::TextDisabled("(see Demo->Configuration for more)"); ImGui::EndMenu(); } ImGui::MenuItem("Debug Log", NULL, &demo_data->ShowDebugLog, has_debug_tools); @@ -896,6 +902,87 @@ static ExampleTreeNode* ExampleTree_CreateDemoTree() return node_L0; } +//----------------------------------------------------------------------------- +// [SECTION] Helpers: ExampleImageViewer +//----------------------------------------------------------------------------- + +struct ExampleImageViewerData +{ + ImU32 ImageBgColor = IM_COL32(100, 100, 100, 255); + ImU32 GridColor = IM_COL32(255, 255, 255, 100); + bool GridEnabled = true; + bool ViewReset = true; + ImVec2 ViewOffset; // in image space + float Zoom = 10.0f; + float ZoomMin = 1.0f; + float ZoomMax = 10000.0f; +}; + +static void ExampleImageViewer_DrawOptions(ExampleImageViewerData* data) +{ + ImGui::SetNextItemShortcut(ImGuiKey_G, ImGuiInputFlags_Tooltip); // | ImGuiInputFlags_RouteGlobal + ImGui::Checkbox("Grid", &data->GridEnabled); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f); + float zoom_100 = data->Zoom * 100.0f; + if (ImGui::DragFloat("##Zoom", &zoom_100, 5.0f, data->ZoomMin * 100.0f, data->ZoomMax * 100.0f, "%.0f%%", ImGuiSliderFlags_AlwaysClamp)) + data->Zoom = zoom_100 / 100.0f; +} + +static void ExampleImageViewer_DrawCanvas(ExampleImageViewerData* data, ImVec2 canvas_size, ImTextureRef image_tex_ref, int image_w, int image_h) +{ + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + IM_ASSERT(canvas_size.x >= 0.0f && canvas_size.y >= 0.0f); + + // Layout canvas + ImGui::InvisibleButton("##Canvas", canvas_size); + ImVec2 canvas_min = ImGui::GetItemRectMin(); + ImVec2 canvas_max = ImGui::GetItemRectMax(); + + if (data->ViewReset) + data->ViewOffset = ImVec2((canvas_size.x * 0.5f / data->Zoom) - 0.5f, (canvas_size.y * 0.5f / data->Zoom) - 0.5f); // Add half a pixel padding + data->ViewReset = false; + + // Handle inputs + if (ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY)) + if (io.MouseWheel != 0.0f) + data->Zoom = IM_CLAMP(data->Zoom * (1.0f + io.MouseWheel * 0.10f), data->ZoomMin, data->ZoomMax); + float zoom = data->Zoom; // (float)(int)ViewZoom; + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0)) + { + data->ViewOffset.x -= io.MouseDelta.x / zoom; + data->ViewOffset.y -= io.MouseDelta.y / zoom; + } + + // Display image + ImVec2 image_min, image_max; + image_min.x = (float)(int)((canvas_min.x - (data->ViewOffset.x * zoom)) + (canvas_size.x * 0.5f)); + image_min.y = (float)(int)((canvas_min.y - (data->ViewOffset.y * zoom)) + (canvas_size.y * 0.5f)); + image_max.x = (float)(int)(image_min.x + image_w * zoom); + image_max.y = (float)(int)(image_min.y + image_h * zoom); + draw_list->AddRect(ImVec2(canvas_min.x - 1.0f, canvas_min.y - 1.0f), ImVec2(canvas_max.x + 1.0f, canvas_max.y + 1.0f), IM_COL32(255, 255, 255, 255)); + draw_list->PushClipRect(canvas_min, canvas_max, true); + draw_list->AddRectFilled(image_min, image_max, data->ImageBgColor); + if (platform_io.DrawCallback_SetSamplerNearest != NULL) + draw_list->AddCallback(platform_io.DrawCallback_SetSamplerNearest); + draw_list->AddImage(image_tex_ref, image_min, image_max); + if (platform_io.DrawCallback_SetSamplerLinear != NULL) + draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear); + + // Display grid lines for visible pixels + if (data->GridEnabled && zoom > 6.0f) + { + const float step = (float)zoom; + for (int px = (int)((canvas_min.x - image_min.x) / step); px <= (int)((canvas_max.x - image_min.x) / step); px++) + draw_list->AddLineV(image_min.x + px * step, canvas_min.y, canvas_max.y, data->GridColor, 1.0f); + for (int py = (int)((canvas_min.y - image_min.y) / step); py <= (int)((canvas_max.y - image_min.y) / step); py++) + draw_list->AddLineH(canvas_min.x, canvas_max.x, image_min.y + py * step, data->GridColor, 1.0f); + } + draw_list->PopClipRect(); +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsBasic() //----------------------------------------------------------------------------- @@ -1873,40 +1960,29 @@ static void DemoWindowWidgetsImages() // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // Grab the current texture identifier used by the font atlas. - ImTextureRef my_tex_id = io.Fonts->TexRef; + ImFontAtlas* atlas = io.Fonts; + ImTextureRef my_tex_id = atlas->TexRef; + float my_tex_w = (float)atlas->TexData->Width; // Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it. + float my_tex_h = (float)atlas->TexData->Height; + ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); + + // Basic drawing + ImGui::SeparatorText("Image()/ImageWithBg() function"); + ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left + ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right + ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize)); + ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PopStyleVar(); - // Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it. - float my_tex_w = (float)io.Fonts->TexData->Width; - float my_tex_h = (float)io.Fonts->TexData->Height; - - { - ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left - ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right - ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize)); - ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - if (ImGui::BeginItemTooltip()) - { - float region_sz = 32.0f; - float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; - float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; - float zoom = 4.0f; - if (region_x < 0.0f) { region_x = 0.0f; } - else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; } - if (region_y < 0.0f) { region_y = 0.0f; } - else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; } - ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y); - ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz); - ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); - ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); - ImGui::ImageWithBg(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(); - } + // Fancy widget + ImGui::SeparatorText("Interactive Image Viewer"); + static ExampleImageViewerData image_viewer; + ImVec2 canvas_size(ImGui::GetContentRegionAvail().x, my_tex_h * 2.0f); + ExampleImageViewer_DrawOptions(&image_viewer); + ExampleImageViewer_DrawCanvas(&image_viewer, canvas_size, my_tex_id, (int)my_tex_w, (int)my_tex_h); IMGUI_DEMO_MARKER("Widgets/Images/Textured buttons"); + ImGui::SeparatorText("Textured Buttons"); ImGui::TextWrapped("And now some textured buttons.."); static int pressed_count = 0; for (int i = 0; i < 8; i++) @@ -8941,7 +9017,7 @@ static void ShowExampleMenuFile() IMGUI_DEMO_MARKER("Examples/Menu/Options"); static bool enabled = true; ImGui::MenuItem("Enabled", "", &enabled); - ImGui::BeginChild("child", ImVec2(0, 60), ImGuiChildFlags_Borders); + ImGui::BeginChild("child", ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5.0f), ImGuiChildFlags_Borders); for (int i = 0; i < 10; i++) ImGui::Text("Scrolling Text %d", i); ImGui::EndChild(); @@ -9353,6 +9429,28 @@ static void ShowExampleAppConsole(bool* p_open) console.Draw("Example: Console", p_open); } +//----------------------------------------------------------------------------- +// [SECTION] Example App: Image Viewer / ShowExampleAppImageViewer() +//----------------------------------------------------------------------------- + +static void ShowExampleAppImageViewer(bool* p_open) +{ + ImFontAtlas* atlas = ImGui::GetIO().Fonts; + ImTextureRef tex_ref = atlas->TexRef; // We don't have access to other textures in this demo! + int tex_w = atlas->TexData->Width; + int tex_h = atlas->TexData->Height; + if (ImGui::Begin("Example: Image Viewer", p_open)) + { + static ExampleImageViewerData image_viewer; + ExampleImageViewer_DrawOptions(&image_viewer); + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); + ImVec2 canvas_min_size = ImGui::IsWindowAppearing() ? ImVec2(3.0f * tex_w, 4.0f * tex_h) : ImVec2(1.0f, 1.0f); + canvas_size = ImVec2(IM_MAX(canvas_size.x, canvas_min_size.x), IM_MAX(canvas_size.y, canvas_min_size.y)); + ExampleImageViewer_DrawCanvas(&image_viewer, canvas_size, tex_ref, tex_w, tex_h); + } + ImGui::End(); +} + //----------------------------------------------------------------------------- // [SECTION] Example App: Debug Log / ShowExampleAppLog() //----------------------------------------------------------------------------- @@ -10248,20 +10346,20 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th); x += sz + spacing; // Square - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th); x += sz + spacing; // Square with all rounded corners - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th); x += sz + spacing; // Square + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th); x += sz + spacing; // Square with all rounded corners + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, corners_tl_br); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, ImDrawFlags_Closed, th); x += sz + spacing; // Concave Shape + PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, ImDrawFlags_Closed); x += sz + spacing; // Concave Shape //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) + draw_list->AddLineH(x, x + sz, y, col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) + draw_list->AddLineV(x, y, y + sz, col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line // Path draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); - draw_list->PathStroke(col, ImDrawFlags_None, th); + draw_list->PathStroke(col, th); x += sz + spacing; // Quadratic Bezier Curve (3 control points) @@ -10395,9 +10493,9 @@ static void ShowExampleAppCustomRendering(bool* p_open) { const float GRID_STEP = 64.0f; for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP) - draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40)); + draw_list->AddLineV(canvas_p0.x + x, canvas_p0.y, canvas_p1.y, IM_COL32(200, 200, 200, 40)); for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP) - draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40)); + draw_list->AddLineH(canvas_p0.x, canvas_p1.x, canvas_p0.y + y, IM_COL32(200, 200, 200, 40)); } for (int n = 0; n < points.Size; n += 2) draw_list->AddLine(ImVec2(origin.x + points[n].x, origin.y + points[n].y), ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y), IM_COL32(255, 255, 0, 255), 2.0f); @@ -11067,10 +11165,11 @@ struct ExampleAssetsBrowser bool AllowBoxSelect = true; // Will set ImGuiMultiSelectFlags_BoxSelect2d bool AllowBoxSelectInsideSelection = false; // Will set ImGuiMultiSelectFlags_SelectOnClickAlways bool AllowDragUnselected = false; // Will set ImGuiMultiSelectFlags_SelectOnClickRelease - float IconSize = 32.0f; + float IconSize = 0; int IconSpacing = 10; - int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer. + int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer. bool StretchSpacing = true; + bool UseScrollX = false; // Debug: submit twice the number of items per line (overflow horizontally to exercise ScrollX + box-select) // State ImVector Items; // Our items @@ -11121,12 +11220,15 @@ struct ExampleAssetsBrowser // Layout: calculate number of icon per line and number of lines LayoutItemSize = ImVec2(floorf(IconSize), floorf(IconSize)); LayoutColumnCount = IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1); - LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount; // Layout: when stretching: allocate remaining space to more spacing. Round before division, so item_spacing may be non-integer. if (StretchSpacing && LayoutColumnCount > 1) LayoutItemSpacing = floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / LayoutColumnCount; + if (UseScrollX) + LayoutColumnCount *= 2; + LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount; + LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, LayoutItemSize.y + LayoutItemSpacing); LayoutSelectableSpacing = IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f); LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f); @@ -11134,6 +11236,9 @@ struct ExampleAssetsBrowser void Draw(const char* title, bool* p_open) { + if (IconSize <= 0.0f) + IconSize = ImGui::CalcTextSize("99999").x; + ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver); if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar)) { @@ -11183,6 +11288,7 @@ struct ExampleAssetsBrowser ImGui::SliderInt("Icon Spacing", &IconSpacing, 0, 32); ImGui::SliderInt("Icon Hit Spacing", &IconHitSpacing, 0, 32); ImGui::Checkbox("Stretch Spacing", &StretchSpacing); + ImGui::Checkbox("Use ScrollX", &UseScrollX); ImGui::PopItemWidth(); ImGui::EndMenu(); } @@ -11212,7 +11318,7 @@ struct ExampleAssetsBrowser ImGuiIO& io = ImGui::GetIO(); ImGui::SetNextWindowContentSize(ImVec2(0.0f, LayoutOuterPadding + LayoutLineCount * (LayoutItemSize.y + LayoutItemSpacing))); - if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove)) + if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_HorizontalScrollbar)) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -11356,6 +11462,8 @@ struct ExampleAssetsBrowser } } clipper.End(); + if (Items.Size == 0) + ImGui::Dummy(ImVec2(0, 0)); ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing // Context menu diff --git a/extensions/ImGui/src/ImGui/imgui_draw.cpp b/extensions/ImGui/src/ImGui/imgui_draw.cpp index 91af902246f4..b5a93b10b8cd 100644 --- a/extensions/ImGui/src/ImGui/imgui_draw.cpp +++ b/extensions/ImGui/src/ImGui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.7 +// dear imgui, v1.92.8 // (drawing and font code) /* @@ -208,6 +208,7 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_CheckboxSelectedBg] = ImLerp(colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBgHovered], 0.65f); colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); @@ -277,6 +278,7 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_CheckboxSelectedBg] = ImLerp(colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBgActive], 0.65f); colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); @@ -347,6 +349,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_CheckboxSelectedBg] = ImVec4(0.95f, 0.97f, 1.00f, 1.00f); colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); @@ -534,9 +537,14 @@ void ImDrawList::_PopUnusedDrawCmd() void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size) { + IM_ASSERT(callback != NULL); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (callback == ImDrawCallback_ResetRenderState && _Data->Context != NULL && _Data->Context->PlatformIO.DrawCallback_ResetRenderState != NULL) + callback = _Data->Context->PlatformIO.DrawCallback_ResetRenderState; // == ImGui::GetPlatformIO().DrawCallback_ResetRenderState +#endif + IM_ASSERT_PARANOID(CmdBuffer.Size > 0); ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; - IM_ASSERT(callback != NULL); IM_ASSERT(curr_cmd->UserCallback == NULL); if (curr_cmd->ElemCount != 0) { @@ -668,7 +676,7 @@ void ImDrawList::PushClipRect(const ImVec2& cr_min, const ImVec2& cr_max, bool i if (intersect_with_current_clip_rect) { ImVec4 current = _CmdHeader.ClipRect; - if (cr.x < current.x) cr.x = current.x; + if (cr.x < current.x) cr.x = current.x; // = ClipWith(). Note that passing inverted range wouldn't be fixed here. if (cr.y < current.y) cr.y = current.y; if (cr.z > current.z) cr.z = current.z; if (cr.w > current.w) cr.w = current.w; @@ -812,7 +820,7 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c // TODO: Thickness anti-aliased lines cap are missing their AA fringe. // We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. -void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags, float thickness) +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; @@ -821,6 +829,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const ImVec2 opaque_uv = _Data->TexUvWhitePixel; const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw const bool thick_line = (thickness > _FringeScale); + IM_ASSERT((flags & ImDrawFlags_InvalidMask_) == 0 && "Incorrect parameter. Did you swapped 'thickness' and 'flags'?"); if (Flags & ImDrawListFlags_AntiAliasedLines) { @@ -1438,35 +1447,13 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, } } -static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) -{ - /* - IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4)); -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - // Obsoleted in 1.82 (from February 2021). This code was stripped/simplified and mostly commented in 1.90 (from September 2023) - // - Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) - if (flags == ~0) { return ImDrawFlags_RoundCornersAll; } - // - Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations). Read details in older version of this code. - if (flags >= 0x01 && flags <= 0x0F) { return (flags << 4); } - // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' -#endif - */ - // If this assert triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. - // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc. anyway. - // See details in 1.82 Changelog as well as 2021/03/12 and 2023/09/08 entries in "API BREAKING CHANGES" section. - IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); - - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; - - return flags; -} - void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags) { if (rounding >= 0.5f) { - flags = FixRectCornerFlags(flags); + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); } @@ -1496,20 +1483,48 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th return; PathLineTo(p1 + ImVec2(0.5f, 0.5f)); PathLineTo(p2 + ImVec2(0.5f, 0.5f)); - PathStroke(col, 0, thickness); + PathStroke(col, thickness); +} + +void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(ImVec2(min_x + 0.5f, y + 0.5f)); // Same as AddLine() above. + PathLineTo(ImVec2(max_x + 0.5f, y + 0.5f)); + PathStroke(col, thickness); +} + +void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(ImVec2(x + 0.5f, min_y + 0.5f)); // Same as AddLine() above. + PathLineTo(ImVec2(x + 0.5f, max_y + 0.5f)); + PathStroke(col, thickness); } // p_min = upper-left, p_max = lower-right // Note we don't render 1 pixels sized rectangles properly. -void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) -{ +void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) +{ + // If this assert triggers on legacy code: + // - 1.92.8 (2025/04): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + // - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundCornersXXX values. + // If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! + // - Hard coded support for ~0 == ImDrawFlags_RoundCornersAll. + // - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. + // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'. + // See "API BREAKING CHANGES" section for 1.82 and 1.90. + IM_ASSERT((flags & ImDrawFlags_InvalidMask_) == 0 && "Incorrect parameter. Did you swapped 'thickness' and 'flags'?"); // Or misuse of legacy hard-coded ImDrawCornerFlags values + if ((col & IM_COL32_A_MASK) == 0) return; if (Flags & ImDrawListFlags_AntiAliasedLines) PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); else PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags) @@ -1553,7 +1568,7 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c PathLineTo(p2); PathLineTo(p3); PathLineTo(p4); - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) @@ -1576,7 +1591,7 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) @@ -1611,7 +1626,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); } - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) @@ -1647,7 +1662,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } // Guaranteed to honor 'num_segments' @@ -1674,7 +1689,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); - PathStroke(col, true, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) @@ -1699,7 +1714,7 @@ void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2 PathLineTo(p1); PathBezierCubicCurveTo(p2, p3, p4, num_segments); - PathStroke(col, 0, thickness); + PathStroke(col, thickness); } // Quadratic Bezier takes 3 controls points @@ -1710,7 +1725,7 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im PathLineTo(p1); PathBezierQuadraticCurveTo(p2, p3, num_segments); - PathStroke(col, 0, thickness); + PathStroke(col, thickness); } void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) @@ -1782,7 +1797,10 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons if ((col & IM_COL32_A_MASK) == 0) return; - flags = FixRectCornerFlags(flags); + IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); // If this assert triggers on legacy code: see comments in ImDrawList::PathRect(). + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); @@ -2507,10 +2525,11 @@ void ImTextureData::DestroyPixels() // - Default texture data encoded in ASCII // - ImFontAtlas() // - ImFontAtlas::Clear() -// - ImFontAtlas::CompactCache() +// - ImFontAtlas::ClearFonts() // - ImFontAtlas::ClearInputData() // - ImFontAtlas::ClearTexData() -// - ImFontAtlas::ClearFonts() +// - ImFontAtlas::CompactCache() +// - ImFontAtlas::SetFontLoader() //----------------------------------------------------------------------------- // - ImFontAtlasUpdateNewFrame() // - ImFontAtlasTextureBlockConvert() @@ -2671,7 +2690,9 @@ ImFontAtlas::~ImFontAtlas() TexData = NULL; } -// If you call this mid-frame, you would need to add new font and bind them! +// You probably should not call this directly. It is not well specified. +// If you want to replace all your fonts mid-frame, most likely you should instead call ClearFonts() then load the new fonts. +// Calling this mid-frame will discard the CPU-side copy of the texture data which is generally unreliable as you may have textures queued for creation or updates. void ImFontAtlas::Clear() { bool backup_renderer_has_textures = RendererHasTextures; @@ -2681,20 +2702,27 @@ void ImFontAtlas::Clear() RendererHasTextures = backup_renderer_has_textures; } -void ImFontAtlas::CompactCache() -{ - ImFontAtlasTextureCompact(this); -} - -void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader) +void ImFontAtlas::ClearFonts() { - ImFontAtlasBuildSetupFontLoader(this, font_loader); + // FIXME-NEWATLAS: Illegal to remove currently bound font. + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + for (ImFont* font : Fonts) + ImFontAtlasBuildNotifySetFont(this, font, NULL); + ImFontAtlasBuildDestroy(this); + ClearInputData(); + Fonts.clear_delete(); + TexIsBuilt = false; + for (ImDrawListSharedData* shared_data : DrawListSharedDatas) + if (shared_data->FontAtlas == this) + { + shared_data->Font = NULL; + shared_data->FontScale = shared_data->FontSize = 0.0f; + } } void ImFontAtlas::ClearInputData() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - for (ImFont* font : Fonts) ImFontAtlasFontDestroyOutput(this, font); for (ImFontConfig& font_cfg : Sources) @@ -2718,22 +2746,14 @@ void ImFontAtlas::ClearTexData() //Locked = true; // Hoped to be able to lock this down but some reload patterns may not be happy with it. } -void ImFontAtlas::ClearFonts() +void ImFontAtlas::CompactCache() { - // FIXME-NEWATLAS: Illegal to remove currently bound font. - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - for (ImFont* font : Fonts) - ImFontAtlasBuildNotifySetFont(this, font, NULL); - ImFontAtlasBuildDestroy(this); - ClearInputData(); - Fonts.clear_delete(); - TexIsBuilt = false; - for (ImDrawListSharedData* shared_data : DrawListSharedDatas) - if (shared_data->FontAtlas == this) - { - shared_data->Font = NULL; - shared_data->FontScale = shared_data->FontSize = 0.0f; - } + ImFontAtlasTextureCompact(this); +} + +void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader) +{ + ImFontAtlasBuildSetupFontLoader(this, font_loader); } static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas) @@ -2967,12 +2987,17 @@ void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, I memcpy(dst_tex->GetPixelsAt(dst_x, dst_y + y), src_tex->GetPixelsAt(src_x, src_y + y), w * dst_tex->BytesPerPixel); } -// Queue texture block update for renderer backend void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h) +{ + ImTextureDataQueueUpload(tex, x, y, w, h); + atlas->TexIsBuilt = false; +} + +// Queue texture block update for renderer backend +void ImTextureDataQueueUpload(ImTextureData* tex, int x, int y, int w, int h) { IM_ASSERT(tex->Status != ImTextureStatus_WantDestroy && tex->Status != ImTextureStatus_Destroyed); IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); - IM_UNUSED(atlas); ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); @@ -2985,7 +3010,6 @@ void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y); tex->UsedRect.w = (unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w, req.x + req.w) - tex->UsedRect.x); tex->UsedRect.h = (unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h, req.y + req.h) - tex->UsedRect.y); - atlas->TexIsBuilt = false; // No need to queue if status is == ImTextureStatus_WantCreate if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) @@ -3056,7 +3080,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) } else { - IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. + IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font!"); // When using MergeMode make sure that a font has already been added before. font = font_cfg_in->DstFont ? font_cfg_in->DstFont : Fonts.back(); ImFontAtlasFontDiscardBakes(this, font, 0); // Need to discard bakes if the font was already used, because baked->FontLoaderDatas[] will change size. (#9162) } @@ -3084,6 +3108,11 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet. } + // | Target w/ Implicit RefSize | Target w/ Explicit RefSize | + // Adding w/ Implicit RefSize: | OK (same scale) | OK (same scale) | + // Adding w/ Explicit RefSize: | KO | OK (custom scale) | + if (font_cfg_in->MergeMode && font_cfg_in->SizePixels > 0) + IM_ASSERT((font->Flags & ImFontFlags_ImplicitRefSize) == 0 && "Cannot use MergeMode with an explicit reference size when the destination font used an implicit reference size!"); IM_ASSERT(font_cfg->FontLoaderData == NULL); if (!ImFontAtlasFontSourceInit(this, font_cfg)) @@ -3151,7 +3180,10 @@ ImFont* ImFontAtlas::AddFontDefaultBitmap(const ImFontConfig* font_cfg_template) if (!font_cfg_template) font_cfg.PixelSnapH = true; // Prevents sub-integer scaling factors at lower-level layers. if (font_cfg.SizePixels <= 0.0f) + { font_cfg.SizePixels = 13.0f; // This only serves (1) as a reference for GlyphOffset.y setting and (2) as a default for pre-1.92 backend. + font_cfg.Flags |= ImFontFlags_ImplicitRefSize; + } if (font_cfg.Name[0] == '\0') ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "ProggyClean.ttf"); font_cfg.EllipsisChar = (ImWchar)0x0085; @@ -3176,7 +3208,10 @@ ImFont* ImFontAtlas::AddFontDefaultVector(const ImFontConfig* font_cfg_template) if (!font_cfg_template) font_cfg.PixelSnapH = true; // Precisely match ProggyClean, but prevents sub-integer scaling factors at lower-level layers. if (font_cfg.SizePixels <= 0.0f) + { font_cfg.SizePixels = 13.0f; + font_cfg.Flags |= ImFontFlags_ImplicitRefSize; + } if (font_cfg.Name[0] == '\0') ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "ProggyForever.ttf"); font_cfg.ExtraSizeScale *= 1.015f; // Match ProggyClean @@ -6021,7 +6056,7 @@ void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float draw_list->PathLineTo(ImVec2(bx - third, by - third)); draw_list->PathLineTo(ImVec2(bx, by)); draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f)); - draw_list->PathStroke(col, 0, thickness); + draw_list->PathStroke(col, thickness); } // Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. diff --git a/extensions/ImGui/src/ImGui/imgui_internal.h b/extensions/ImGui/src/ImGui/imgui_internal.h index 9f532abc37f6..aa35dce5133b 100644 --- a/extensions/ImGui/src/ImGui/imgui_internal.h +++ b/extensions/ImGui/src/ImGui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.7 +// dear imgui, v1.92.8 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -252,7 +252,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer // Debug Logging for ShowDebugLogWindow(). This is designed for relatively rare events so please don't spam. #define IMGUI_DEBUG_LOG_ERROR(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventError) IMGUI_DEBUG_LOG(__VA_ARGS__); else g.DebugLogSkippedErrors++; } while (0) #define IMGUI_DEBUG_LOG_ACTIVEID(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventActiveId) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) -#define IMGUI_DEBUG_LOG_FOCUS(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFocus) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_FOCUS(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_FocusEvent) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_POPUP(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_NAV(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventNav) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) @@ -263,6 +263,9 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_DOCKING(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventDocking) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_VIEWPORT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventViewport) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +// Debug options (also see ones on top of imgui.cpp) +//#define IMGUI_DEBUG_BOXSELECT + // Static Asserts #define IM_STATIC_ASSERT(_COND) static_assert(_COND, "") @@ -517,7 +520,8 @@ inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } template T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } template T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } template T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } -template T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } +template T ImLerp(double a, double b, float t) { return (T)(a + (b - a) * (double)t); } +template T ImLerp(T a, T b, float t) { return (T)((float)a + (float)(b - a) * t); } template void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } template T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } template T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } @@ -614,6 +618,8 @@ struct IMGUI_API ImRect bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; } void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; } void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; } + void AddX(float x) { if (Min.x > x) Min.x = x; if (Max.x < x) Max.x = x; } + void AddY(float y) { if (Min.y > y) Min.y = y; if (Max.y < y) Max.y = y; } void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; } void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; } void Translate(const ImVec2& d) { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; } @@ -1010,6 +1016,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_HasClipRect = 1 << 9, // g.LastItemData.ClipRect is valid. ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). //ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb. + ImGuiItemStatusFlags_EditedInternal = 1 << 11, // Similar to ImGuiItemStatusFlags_Edited but bypassing ImGuiItemFlags_NoMarkEdited. // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -1215,6 +1222,7 @@ struct IMGUI_API ImGuiMenuColumns }; // Internal temporary state for deactivating InputText() instances. +// Store as part of ImGuiDeactivatedItemData? struct IMGUI_API ImGuiInputTextDeactivatedState { ImGuiID ID; // widget id owning the text state (which just got deactivated) @@ -1468,6 +1476,7 @@ struct ImGuiPtrOrIndex }; // Data used by IsItemDeactivated()/IsItemDeactivatedAfterEdit() functions +// Also see ImGuiInputTextDeactivatedState which is an extension for this for InputText() struct ImGuiDeactivatedItemData { ImGuiID ID; @@ -1912,6 +1921,7 @@ struct ImGuiBoxSelectState // Temporary/Transient data bool UnclipMode; // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select. ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. + ImRect UnclipRects[2]; // Per-axis versions. ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) ImRect BoxSelectRectCurr; @@ -1934,7 +1944,8 @@ struct IMGUI_API ImGuiMultiSelectTempData ImGuiMultiSelectFlags Flags; ImVec2 ScopeRectMin; ImVec2 BackupCursorMaxPos; - ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + //ImGuiSelectionUserData CurrSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + //ImGuiSelectionUserData PrevSubmittedItem; // Copy of previous submitted item data, used to merge output ranges. ImGuiID BoxSelectId; ImGuiKeyChord KeyMods; ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all. @@ -2022,7 +2033,7 @@ enum ImGuiDockNodeState ImGuiDockNodeState_HostWindowVisible, }; -// sizeof() 156~192 +// sizeof() 176~216 struct IMGUI_API ImGuiDockNode { ImGuiID ID; @@ -2039,8 +2050,8 @@ struct IMGUI_API ImGuiDockNode ImVec2 Size; // Current size ImVec2 SizeRef; // [Split node only] Last explicitly written-to size (overridden when using a splitter affecting the node), used to calculate Size. ImGuiAxis SplitAxis; // [Split node only] Split axis (X or Y) - ImGuiWindowClass WindowClass; // [Root node only] ImU32 LastBgColor; + ImGuiWindowClass WindowClass; // [Root node only] ImGuiWindow* HostWindow; ImGuiWindow* VisibleWindow; // Generally point to window which is ID is == SelectedTabID, but when CTRL+Tabbing this can be a different window. @@ -2271,7 +2282,7 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_None = 0, ImGuiDebugLogFlags_EventError = 1 << 0, // Error submitted by IM_ASSERT_USER_ERROR() ImGuiDebugLogFlags_EventActiveId = 1 << 1, - ImGuiDebugLogFlags_EventFocus = 1 << 2, + ImGuiDebugLogFlags_FocusEvent = 1 << 2, ImGuiDebugLogFlags_EventPopup = 1 << 3, ImGuiDebugLogFlags_EventNav = 1 << 4, ImGuiDebugLogFlags_EventClipper = 1 << 5, @@ -2282,7 +2293,7 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_EventDocking = 1 << 10, ImGuiDebugLogFlags_EventViewport = 1 << 11, - ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_FocusEvent | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY ImGuiDebugLogFlags_OutputToDebugger = 1 << 21, // Also send output to Debugger Console [Windows only] ImGuiDebugLogFlags_OutputToTestEngine = 1 << 22, // Also send output to Dear ImGui Test Engine @@ -2410,6 +2421,7 @@ struct ImGuiContext float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; ImGuiID WithinEndChildID; // Set within EndChild() + ImGuiID WithinEndPopupID; // Set within EndPopup() void* TestEngine; // Test engine user data // Inputs @@ -3439,7 +3451,7 @@ namespace ImGui IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); // Fonts, drawing - IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL: DO NOT USE YET. + IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL. IMGUI_API void UnregisterUserTexture(ImTextureData* tex); IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas); IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); @@ -3557,6 +3569,7 @@ namespace ImGui // Childs IMGUI_API bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags); + IMGUI_API ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window); // Popups, Modals IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags); @@ -3662,7 +3675,7 @@ namespace ImGui IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key); IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); IMGUI_API void SetKeyOwnersForKeyChord(ImGuiKeyChord key, ImGuiID owner_id, ImGuiInputFlags flags = 0); - IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags); IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id' inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiContext* ctx, ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &ctx->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; } @@ -3765,6 +3778,7 @@ namespace ImGui // We don't use the ID Stack for this as it is common to want them separate. IMGUI_API void PushFocusScope(ImGuiID id); IMGUI_API void PopFocusScope(); + IMGUI_API bool IsInNavFocusRoute(ImGuiID focus_scope_id); inline ImGuiID GetCurrentFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentFocusScopeId; } // Focus scope we are outputting into, set by PushFocusScope() // Drag and Drop @@ -3774,7 +3788,7 @@ namespace ImGui IMGUI_API void ClearDragDrop(); IMGUI_API bool IsDragDropPayloadBeingAccepted(); IMGUI_API void RenderDragDropTargetRectForItem(const ImRect& bb); - IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb); + IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding); // Typing-Select API // (provide Windows Explorer style "select items by typing partial name" + "cycle through items by typing same letter" feature) @@ -3831,6 +3845,7 @@ namespace ImGui IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table); + IMGUI_API void TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect); IMGUI_API void TableDrawBorders(ImGuiTable* table); IMGUI_API void TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags_for_section_to_display); IMGUI_API bool TableBeginContextMenuPopup(ImGuiTable* table); @@ -4261,6 +4276,7 @@ IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); +IMGUI_API void ImTextureDataQueueUpload(ImTextureData* tex, int x, int y, int w, int h); IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format); IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status); IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format); diff --git a/extensions/ImGui/src/ImGui/imgui_tables.cpp b/extensions/ImGui/src/ImGui/imgui_tables.cpp index 9b523bf3eb5f..135410a0dd2b 100644 --- a/extensions/ImGui/src/ImGui/imgui_tables.cpp +++ b/extensions/ImGui/src/ImGui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.7 +// dear imgui, v1.92.8 // (tables and columns code) /* @@ -240,6 +240,7 @@ Index of this file: #pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' #pragma GCC diagnostic ignored "-Wstrict-overflow" #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may change value #pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result #endif @@ -1315,14 +1316,32 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->InnerWindow->DecoInnerSizeY1 = table_instance->LastFrozenHeight; table_instance->LastFrozenHeight = 0.0f; - // Initial state ImGuiWindow* inner_window = table->InnerWindow; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (bs->Window == inner_window && bs->UnclipMode) + TableApplyExternalUnclipRect(table, bs->UnclipRect); + + // Initial state if (table->Flags & ImGuiTableFlags_NoClip) table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); else inner_window->DrawList->PushClipRect(inner_window->InnerClipRect.Min, inner_window->InnerClipRect.Max, false); // FIXME: use table->InnerClipRect? } +// When starting a BeginMultiSelect() after table has been layout we update IsRequestOutput fields. +void ImGui::TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect) +{ + if (rect.IsInverted()) + return; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsRequestOutput) + if (rect.Overlaps(ImRect(column->MinX, table->WorkRect.Min.y, column->MaxX, FLT_MAX))) + column->IsRequestOutput = true; + } +} + // Process hit-testing on resizing borders. Actual size change will be applied in EndTable() // - Set table->HoveredColumnBorder with a short delay/timer to reduce visual feedback noise. void ImGui::TableUpdateBorders(ImGuiTable* table) @@ -1436,12 +1455,12 @@ void ImGui::EndTable() if (table->Flags & ImGuiTableFlags_ScrollX) { const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; - float max_pos_x = table->InnerWindow->DC.CursorMaxPos.x; + float max_pos_x = inner_window->DC.CursorMaxPos.x; if (table->RightMostEnabledColumn != -1) max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border); if (table->ResizedColumn != -1) max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2); - table->InnerWindow->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth; + inner_window->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth; } // Pop clipping rect @@ -1550,7 +1569,7 @@ void ImGui::EndTable() } else { - table->InnerWindow->DC.TreeDepth--; + inner_window->DC.TreeDepth--; ItemSize(table->OuterRect.GetSize()); ItemAdd(table->OuterRect, 0); } @@ -1565,13 +1584,12 @@ void ImGui::EndTable() } else if (temp_data->UserOuterSize.x <= 0.0f) { - // Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block - // - Checking for ImGuiTableFlags_ScrollX/ScrollY flag makes us a frame ahead when disabling those flags. - // - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback. - const float inner_content_max_x = table->OuterRect.Min.x + table->ColumnsAutoFitWidth; // Slightly misleading name but used for code symmetry with inner_content_max_y - const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.x : 0.0f); - outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, inner_content_max_x + decoration_size - temp_data->UserOuterSize.x); - outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, inner_content_max_x + decoration_size)); + // Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block, #9352 + // - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback? See broken test in 'table_reported_size_outer' + const float outer_content_max_x = table->OuterRect.Min.x + table->ColumnsAutoFitWidth; + const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((inner_window != outer_window) ? inner_window->ScrollbarSizes.x : 0.0f); + outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, outer_content_max_x + decoration_size - temp_data->UserOuterSize.x); + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, outer_content_max_x + decoration_size)); } else { @@ -1579,9 +1597,12 @@ void ImGui::EndTable() } if (temp_data->UserOuterSize.y <= 0.0f) { - const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.y : 0.0f; - outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y); - outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y + decoration_size)); + // (same comment as above) + const float outer_content_size_y = (inner_window == outer_window) ? (inner_content_max_y - table->InnerRect.Min.y) : (inner_content_max_y - inner_window->DC.CursorStartPos.y); + const float outer_content_max_y = table->OuterRect.Min.y + outer_content_size_y; + const float decoration_size = (inner_window != outer_window ? inner_window->ScrollbarSizes.y : 0.0f); + outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, outer_content_max_y + decoration_size - temp_data->UserOuterSize.y); + outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, outer_content_max_y + decoration_size)); } else { @@ -1641,7 +1662,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo ImGuiTable* table = g.CurrentTable; IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); IM_ASSERT_USER_ERROR_RET(table->DeclColumnsCount < table->ColumnsCount, "TableSetupColumn(): called too many times!"); - IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!"); + IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!"); // Table layout is locked when submitting a row or when calling BeginMultiSelect() with box-select. IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; @@ -2053,11 +2074,11 @@ void ImGui::TableEndRow(ImGuiTable* table) // Draw top border if (top_border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), top_border_col, border_size); + window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y1, top_border_col, border_size); // Draw bottom border at the row unfreezing mark (always strong) if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); + window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y2, table->BorderColorStrong, border_size); } // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) @@ -2834,7 +2855,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) else if ((table->Flags & (ImGuiTableFlags_NoBordersInBodyUntilResize | ImGuiTableFlags_NoBordersInBody)) == 0) draw_y2 = draw_y2_body; if (draw_y2 > draw_y1) - inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), TableGetColumnBorderCol(table, order_n, column_n), border_size); + inner_drawlist->AddLineV(column->MaxX, draw_y1, draw_y2, TableGetColumnBorderCol(table, order_n, column_n), border_size); } } @@ -2851,17 +2872,17 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const ImU32 outer_col = table->BorderColorStrong; if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) { - inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size); + inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterV) { - inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size); - inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size); + inner_drawlist->AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); + inner_drawlist->AddLineV(outer_border.Max.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterH) { - inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size); - inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size); + inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Min.y, outer_col, border_size); + inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Max.y, outer_col, border_size); } } if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) @@ -2869,7 +2890,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw bottom-most row border between it is above outer border. const float border_y = table->RowPosY2; if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) - inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size); + inner_drawlist->AddLineH(table->BorderX1, table->BorderX2, border_y, table->BorderColorLight, border_size); } inner_drawlist->PopClipRect(); @@ -3174,7 +3195,7 @@ void ImGui::TableHeader(const char* label) if (label == NULL) label = ""; const char* label_end = FindRenderedTextEnd(label); - ImVec2 label_size = CalcTextSize(label, label_end, true); + ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 label_pos = window->DC.CursorPos; // If we already got a row height, there's use that. @@ -3293,6 +3314,8 @@ void ImGui::TableHeader(const char* label) // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden if (IsPopupOpenRequestForItem(ImGuiPopupFlags_None, id)) TableOpenContextMenu(column_n); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); } // Unlike TableHeadersRow() it is not expected that you can reimplement or customize this with custom widgets. @@ -4595,7 +4618,7 @@ void ImGui::EndColumns() // Draw column const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); const float xi = IM_TRUNC(x); - window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col); + window->DrawList->AddLineV(xi, y1 + 1.0f, y2, col); } // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. diff --git a/extensions/ImGui/src/ImGui/imgui_widgets.cpp b/extensions/ImGui/src/ImGui/imgui_widgets.cpp index b404d391be55..c255b5d6a62e 100644 --- a/extensions/ImGui/src/ImGui/imgui_widgets.cpp +++ b/extensions/ImGui/src/ImGui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.7 +// dear imgui, v1.92.8 // (widgets code) /* @@ -92,6 +92,7 @@ Index of this file: #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may change value #pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result #endif @@ -401,7 +402,8 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) const char* value_text_begin, *value_text_end; ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args); const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 pos = window->DC.CursorPos; const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); @@ -413,7 +415,7 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) // Render RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label, label_end, false); } void ImGui::BulletText(const char* fmt, ...) @@ -788,7 +790,8 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 pos = window->DC.CursorPos; if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) @@ -810,7 +813,7 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags if (g.LogEnabled) LogSetNextTextDecoration("[", "]"); - RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, label_end, &label_size, style.ButtonTextAlign, &bb); // Automatically close popups //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) @@ -981,7 +984,7 @@ ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) IM_ASSERT(scrollbar_size >= 0.0f); const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f); - const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f; + const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : (window->Flags & ImGuiWindowFlags_NoTitleBar) ? border_size : 0; if (axis == ImGuiAxis_X) return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); else @@ -1155,7 +1158,7 @@ void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const Im else window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); if (g.Style.ImageBorderSize > 0.0f) - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), rounding, ImDrawFlags_None, g.Style.ImageBorderSize); + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), rounding, g.Style.ImageBorderSize); } void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) @@ -1250,7 +1253,8 @@ bool ImGui::Checkbox(const char* label, bool* v) ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; @@ -1291,8 +1295,9 @@ bool ImGui::Checkbox(const char* label, bool* v) if (is_visible) { RenderNavCursor(total_bb, id); - RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : (mixed_value || checked) ? ImGuiCol_CheckboxSelectedBg : ImGuiCol_FrameBg); ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); + RenderFrame(check_bb.Min, check_bb.Max, bg_col, true, style.FrameRounding); if (mixed_value) { // Undocumented tristate/mixed/indeterminate checkbox (#2644) @@ -1310,7 +1315,7 @@ bool ImGui::Checkbox(const char* label, bool* v) if (g.LogEnabled) LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); if (is_visible && label_size.x > 0.0f) - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return pressed; @@ -1372,7 +1377,8 @@ bool ImGui::RadioButton(const char* label, bool active) ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; @@ -1411,7 +1417,7 @@ bool ImGui::RadioButton(const char* label, bool active) if (g.LogEnabled) LogRenderedText(&label_pos, active ? "(x)" : "( )"); if (label_size.x > 0.0f) - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; @@ -1527,7 +1533,7 @@ bool ImGui::TextLink(const char* label) const char* label_end = FindRenderedTextEnd(label); ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); - ImVec2 size = CalcTextSize(label, label_end, true); + ImVec2 size = CalcTextSize(label, label_end, false); ImRect bb(pos, pos + size); ItemSize(size, 0.0f); if (!ItemAdd(bb, id)) @@ -1558,10 +1564,10 @@ bool ImGui::TextLink(const char* label) } float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f); - window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf), 1.0f * (float)(int)g.Style._MainScale); // FIXME-TEXT: Underline mode // FIXME-DPI + window->DrawList->AddLineH(bb.Min.x, bb.Max.x, line_y, GetColorU32(line_colf), 1.0f * (float)(int)g.Style._MainScale); // FIXME-TEXT: Underline mode // FIXME-DPI PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); - RenderText(bb.Min, label, label_end); + RenderText(bb.Min, label, label_end, false); PopStyleColor(); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); @@ -1731,7 +1737,7 @@ void ImGui::Separator() if (window->DC.CurrentColumns) flags |= ImGuiSeparatorFlags_SpanAllColumns; - SeparatorEx(flags, g.Style.SeparatorSize); + SeparatorEx(flags, ImMax(g.Style.SeparatorSize, 1.0f)); } void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w) @@ -1768,9 +1774,9 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float sep1_x2 = label_pos.x - style.ItemSpacing.x; const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x; if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f) - window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness); + window->DrawList->AddLineH(sep1_x1, sep1_x2, seps_y, separator_col, separator_thickness); if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f) - window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); + window->DrawList->AddLineH(sep2_x1, sep2_x2, seps_y, separator_col, separator_thickness); if (g.LogEnabled) LogSetNextTextDecoration("---", NULL); RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size); @@ -1780,7 +1786,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end if (g.LogEnabled) LogText("---"); if (separator_thickness > 0.0f) - window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); + window->DrawList->AddLineH(sep1_x1, sep2_x2, seps_y, separator_col, separator_thickness); } } @@ -1951,8 +1957,9 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f; + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, false).x : 0.0f; const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth()); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -2003,7 +2010,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL); } if (label_size.x > 0) - RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label, label_end, false); if (!popup_open) return false; @@ -2376,12 +2383,17 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void // Sanitize format // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf - // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %. char format_sanitized[32]; if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) + { format = type_info->ScanFmt; + } else + { format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_COUNTOF(format_sanitized)); + if (format[0] == '\0') + format = type_info->ScanFmt; // Format doesn't want us to show the number currently, but we still need to parse the resulting input + } // Small types need a 32-bit buffer to receive the result from scanf() int v32 = 0; @@ -2716,7 +2728,8 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, const float w = CalcItemWidth(); const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0; - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -2792,7 +2805,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; @@ -3317,7 +3330,8 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat const float w = CalcItemWidth(); const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0; - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -3389,7 +3403,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; @@ -3494,7 +3508,8 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -3539,8 +3554,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return value_changed; } @@ -3563,7 +3579,8 @@ bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, // - ImParseFormatSanitizeForPrinting() [Internal] // - ImParseFormatSanitizeForScanning() [Internal] // - ImParseFormatPrecision() [Internal] -// - TempInputTextScalar() [Internal] +// - TempInputText() [Internal] +// - TempInputScalar() [Internal] // - InputScalar() // - InputScalarN() // - InputFloat() @@ -3792,7 +3809,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; - IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()! + //IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()! if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; @@ -3829,7 +3846,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data } // Apply - bool value_changed = ret ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false; + bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved. + bool value_changed = input_edited ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false; // Step buttons if (has_step_buttons) @@ -3843,13 +3861,13 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data if (ButtonEx("-", ImVec2(button_size, button_size))) { DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); - value_changed = true; + value_changed = ret = true; } SameLine(0, style.ItemInnerSpacing.x); if (ButtonEx("+", ImVec2(button_size, button_size))) { DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); - value_changed = true; + value_changed = ret = true; } PopItemFlag(); if (flags & ImGuiInputTextFlags_ReadOnly) @@ -3871,6 +3889,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data if (value_changed) MarkItemEdited(g.LastItemData.ID); + if (flags & ImGuiInputTextFlags_EnterReturnsTrue) + return ret; return value_changed; } @@ -4520,6 +4540,9 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* sta callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; callback_data.EventChar = (ImWchar)c; callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.CursorPos = state->Stb->cursor; + callback_data.SelectionStart = state->Stb->select_start; + callback_data.SelectionEnd = state->Stb->select_end; callback_data.UserData = user_data; if (callback(&callback_data) != 0) return false; @@ -4568,6 +4591,7 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) ImGuiInputTextState* state = &g.InputTextState; if (id == 0 || state->ID != id) return; + //IMGUI_DEBUG_LOG_ACTIVEID("InputTextDeactivateHook() id = 0x%08X\n", id); g.InputTextDeactivatedState.ID = state->ID; if (state->Flags & ImGuiInputTextFlags_ReadOnly) { @@ -4702,7 +4726,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar) BeginGroup(); const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y); @@ -4716,7 +4741,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { ImVec2 backup_pos = window->DC.CursorPos; ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) + bool no_clip = (g.InputTextDeactivatedState.ID == id) || (g.ActiveId == id) || (id == g.NavActivateId); // Mimic some of ItemAdd() logic + add InputTextDeactivatedState.ID check. + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable) && !no_clip) { EndGroup(); return false; @@ -4742,7 +4768,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.NavActivateId = backup_activate_id; PopStyleVar(3); PopStyleColor(); - if (!child_visible) + if (!child_visible && !no_clip) { EndChild(); EndGroup(); @@ -4806,7 +4832,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf); - const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state. + const bool init_changed_specs_multiline = (state != NULL && (state->Stb->single_line != !is_multiline)); // state != NULL means its our state. + const bool init_changed_specs_readonly = (state != NULL && ((state->Flags ^ flags) & ImGuiInputTextFlags_ReadOnly)); // state != NULL means its our state. const bool init_make_active = (input_requested_by_user || input_requested_by_nav || input_requested_by_reactivate || user_scroll_finish); if (init_reload_from_user_buf) { @@ -4820,7 +4847,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->Stb->select_start = state->ReloadSelectionStart; state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; // will be clamped to bounds below } - else if ((init_make_active && g.ActiveId != id) || init_changed_specs) + else if ((init_make_active && g.ActiveId != id) || init_changed_specs_multiline || init_changed_specs_readonly) { // Access state even if we don't own it yet. state = &g.InputTextState; @@ -4841,8 +4868,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Preserve cursor position and undo/redo stack if we come back to same widget // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate? - bool recycle_state = (state->ID == id && !init_changed_specs); - if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0))) + bool recycle_state = (state->ID == id && !init_changed_specs_multiline); + if (recycle_state && !init_changed_specs_readonly && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0))) recycle_state = false; // Start edition @@ -4880,7 +4907,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } const bool is_osx = io.ConfigMacOSXBehaviors; - if (g.ActiveId != id && init_make_active) + if (init_make_active && g.ActiveId != id) { IM_ASSERT(state && state->ID == id); SetActiveID(id, window); @@ -4951,13 +4978,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_password && !is_displaying_hint) PushPasswordFont(); - // Word-wrapping: attempt to keep cursor in view while resizing frame/parent - // FIXME-WORDWRAP: It would be better to preserve same relative offset. - if (is_wordwrap && state != NULL && state->ID == id && state->WrapWidth != wrap_width) + if (state != NULL && state->ID == id) { - state->CursorCenterY = true; - state->WrapWidth = wrap_width; - render_cursor = true; + state->Flags = flags; + + // Word-wrapping: attempt to keep cursor in view while resizing frame/parent (FIXME-WORDWRAP: would be better to preserve same relative offset) + if (is_wordwrap && state->WrapWidth != wrap_width) + { + state->CursorCenterY = true; + state->WrapWidth = wrap_width; + render_cursor = true; + } } // Process mouse inputs and character inputs @@ -4966,7 +4997,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(state != NULL); state->EditedThisFrame = false; state->BufCapacity = buf_size; - state->Flags = flags; state->WrapWidth = wrap_width; // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. @@ -5618,7 +5648,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f * (float)(int)style._MainScale); // FIXME-DPI: Cursor thickness (#7031) + draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), 1.0f * (float)(int)style._MainScale); // FIXME-DPI: Cursor thickness (#7031) // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) // This is required for some backends (SDL3) to start emitting character/text inputs. @@ -5666,7 +5696,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (label_size.x > 0) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); if (value_changed) MarkItemEdited(id); @@ -6332,7 +6362,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; const int vert_start_idx = draw_list->VtxBuffer.Size; draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); - draw_list->PathStroke(col_white, 0, wheel_thickness); + draw_list->PathStroke(col_white, wheel_thickness); const int vert_end_idx = draw_list->VtxBuffer.Size; // Paint colors over existing vertices @@ -6476,7 +6506,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (g.Style.FrameBorderSize > 0.0f) RenderFrameBorder(bb.Min, bb.Max, rounding); else - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding, 0, 1.0f * (float)(int)g.Style._MainScale); // Color buttons are often in need of some sort of border // FIXME-DPI + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding, 1.0f * (float)(int)g.Style._MainScale); // Color buttons are often in need of some sort of border // FIXME-DPI } // Drag and Drop Source @@ -7149,11 +7179,11 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3); if (x1 < x2) window->DrawList->PathLineTo(ImVec2(x2, y)); - window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), ImDrawFlags_None, g.Style.TreeLinesSize); + window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } else { - window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + window->DrawList->AddLineH(x1, x2, y, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } } @@ -7179,7 +7209,7 @@ void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) float x = ImTrunc(data->DrawLinesX1); if (data->DrawLinesTableColumn != -1) TablePushColumnChannel(data->DrawLinesTableColumn); - window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + window->DrawList->AddLineV(x, y1, y2, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); if (data->DrawLinesTableColumn != -1) TablePopColumnChannel(); } @@ -7338,7 +7368,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. ImGuiID id = window->GetID(label); - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); ImVec2 pos = window->DC.CursorPos; pos.y += window->DC.CurrLineTextBaseOffset; @@ -7493,7 +7524,11 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns. if (is_visible) - RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb); + RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, label_end, &label_size, style.SelectableTextAlign, &bb); + +#ifdef IMGUI_DEBUG_BOXSELECT + if (g.BoxSelectState.UnclipMode) { GetForegroundDrawList()->AddText(pos, IM_COL32(255,255,0,200), label, label_end); } +#endif // Automatically close popups if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups)) @@ -7810,7 +7845,7 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI return false; // Current frame absolute prev/current rectangles are used to toggle selection. - // They are derived from positions relative to scrolling space. + // They are derived from positions relative to scrolling space, so "previous" rectangle is reprojected for current frame coordinates. ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel); ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already ImVec2 curr_end_pos_abs = g.IO.MousePos; @@ -7820,20 +7855,69 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs); bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); + //IMGUI_DEBUG_LOG("StartPosRel (%.2f,%.2f) EndPosRel (%.2f,%.2f) -> (%.2f,%.2f)\n", bs->StartPosRel.x, bs->StartPosRel.y, bs->EndPosRel.x, bs->EndPosRel.y, WindowPosAbsToRel(window, g.IO.MousePos).x, WindowPosAbsToRel(window, g.IO.MousePos).y); - // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) - // Storing an extra rect used by widgets supporting box-select. - if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) - if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) + // Box-select 2D mode detects change of the rectangle. + // Storing unclip rects which will be tested by widgets supporting box-select. Always update rectangles when active (even if we don't use them). + // To facilitate understanding this: enable IMGUI_DEBUG_BOXSELECT and visualize all geometry. + if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + // For both sides, compute the area differing between Prev and Curr rectangles. + bs->UnclipRects[0] = bs->UnclipRects[1] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + for (int side = 0; side < 2; side++) { - bs->UnclipMode = true; - bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis. - bs->UnclipRect.Add(bs->BoxSelectRectCurr); + ImVec2 d_min = (side == 0) ? ImMin(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMin(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max); + ImVec2 d_max = (side == 0) ? ImMax(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMax(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max); + if (d_min.x != d_max.x) + { + bs->UnclipRects[0].AddX(d_min.x); + bs->UnclipRects[0].AddX(d_max.x); + } + if (d_min.y != d_max.y) + { + bs->UnclipRects[1].AddY(d_min.y); + bs->UnclipRects[1].AddY(d_max.y); + } } - //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + ImRect box_select_intersection = bs->BoxSelectRectPrev; + box_select_intersection.Add(bs->BoxSelectRectCurr); + if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) + if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) + { + bs->UnclipRects[0].AddY(box_select_intersection.Min.y); + bs->UnclipRects[0].AddY(box_select_intersection.Max.y); + } + if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + if (bs->BoxSelectRectPrev.Min.y != bs->BoxSelectRectCurr.Min.y || bs->BoxSelectRectPrev.Max.y != bs->BoxSelectRectCurr.Max.y) + { + bs->UnclipRects[1].AddX(box_select_intersection.Min.x); + bs->UnclipRects[1].AddX(box_select_intersection.Max.x); + } + + // Merge both rectangles into one. + // FIXME-OPT: When UnclipRect.Area() is much larger than the sum of UnclipRects[0]/[1] Areas, widgets should + // ideally first use UnclipRect as a first coarse cull layer + the individual ones as a second validation. + bs->UnclipRect = bs->UnclipRects[0]; + bs->UnclipRect.Add(bs->UnclipRects[1]); + if (!bs->UnclipRect.IsInverted() && (!window->ClipRect.Contains(bs->UnclipRect.Min) || !window->ClipRect.Contains(bs->UnclipRect.Max))) // !! Don't use Contains(ImRect) + bs->UnclipMode = true; + if (bs->UnclipMode && g.CurrentTable != NULL) + TableApplyExternalUnclipRect(g.CurrentTable, bs->UnclipRect); // No need submitting both + } + +#ifdef IMGUI_DEBUG_BOXSELECT + //GetForegroundDrawList()->AddRect(scope_rect.Min, scope_rect.Max, IM_COL32(0, 255, 0, 200), 0.0f, 0, 4.0f); //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); + if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + for (ImRect& unclip_r : bs->UnclipRects) + if (!unclip_r.IsInverted()) + GetForegroundDrawList()->AddRect(unclip_r.Min, unclip_r.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 4.0f); + GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 2.0f); + } +#endif return true; } @@ -7849,8 +7933,9 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view ImRect box_select_r = bs->BoxSelectRectCurr; box_select_r.ClipWith(scope_rect); - window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling - window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling + ImGuiWindow* draw_window = FindFrontMostVisibleChildWindow(window); + draw_window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling + draw_window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling // Scroll const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; @@ -7890,18 +7975,18 @@ static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSe static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window) { - ImGuiContext& g = *GImGui; if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) { // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + // This probably doesn't work inside a table as there are ample ambiguities related to exact time of calling BeginMultiSelect()/EndMultiSelect(). return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin)); } else { - // When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) + //// When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) ImRect scope_rect = window->InnerClipRect; - if (g.CurrentTable != NULL) - scope_rect = g.CurrentTable->HostClipRect; + //if (g.CurrentTable != NULL) + // scope_rect = g.CurrentTable->HostClipRect; // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max); @@ -7937,18 +8022,24 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) // They should perhaps be stacked properly? if (ImGuiTable* table = g.CurrentTable) - if (table->CurrentColumn != -1) + { + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + else if (table->CurrentColumn != -1) TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + } // FIXME: BeginFocusScope() const ImGuiID id = window->IDStack.back(); ms->Clear(); ms->FocusScopeId = id; ms->Flags = flags; - ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId); ms->BackupCursorMaxPos = window->DC.CursorMaxPos; - ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos; + ms->ScopeRectMin = window->DC.CursorPos; + if (flags & ImGuiMultiSelectFlags_ScopeRect) + window->DC.CursorMaxPos = ms->ScopeRectMin; // CalcScopeRect() for ImGuiMultiSelectFlags_ScopeRect will measure in EndMultiSelect(). PushFocusScope(ms->FocusScopeId); + ms->IsFocused = IsInNavFocusRoute(g.CurrentFocusScopeId); if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; @@ -8030,7 +8121,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel storage->LastSelectionSize = 0; } ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; - ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid; + //ms->PrevSubmittedItem = ImGuiSelectionUserData_Invalid; if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); @@ -8076,7 +8167,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() // Clear selection when clicking void? // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! // The InnerRect test is necessary for non-child/decorated windows. - bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos); + bool scope_hovered = window->InnerRect.Contains(g.IO.MousePos) && IsWindowHovered(ImGuiHoveredFlags_ChildWindows); if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)) scope_hovered &= scope_rect.Contains(g.IO.MousePos); if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0) @@ -8102,10 +8193,13 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX) { IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope - ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); + NavMoveRequestTryWrapping(GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); } // Unwind + if (ImGuiTable* table = g.CurrentTable) + if (table->IsInsideRow) + TableEndRow(table); window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos); PopFocusScope(); @@ -8133,6 +8227,8 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; if (ms->IO.RangeSrcItem == selection_user_data) ms->RangeSrcPassedBy = true; + //ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. + //ms->CurrSubmittedItem = selection_user_data; } else { @@ -8281,8 +8377,31 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) if (ms->BoxSelectId != 0) if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId)) { - const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect); - const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect); + ImRect item_rect = g.LastItemData.Rect; + if (!window->DC.NavIsScrollPushableX) // FIXME: Rename to be more generic. + if (ImGuiTable* table = g.CurrentTable) + if (table->CurrentColumn != -1) + { + // FIXME: We cannot solely use current ClipRect as it includes HostClipRect. + // However we account for ClipRect being larger than current column (e.g. when using SpanAllColumns) + // A more generic version would be nice, but window->WorkRect.Min/Max exclude CellPadding. (#7994, #9383) + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + float clip_min_x = (g.LastItemData.ItemFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect.Min.x : window->ClipRect.Min.x; + float clip_max_x = (g.LastItemData.ItemFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect.Max.x : window->ClipRect.Max.x; + if (clip_min_x != clip_max_x) // When zero sized we expect that bounds have been clamped and thus are unreliable + { + item_rect.Min.x = ImMax(item_rect.Min.x, ImMin(column->MinX, clip_min_x)); + item_rect.Max.x = ImMin(item_rect.Max.x, ImMax(column->MaxX, clip_max_x)); + } + else + { + item_rect.Min.x = ImMax(item_rect.Min.x, column->MinX); + item_rect.Max.x = ImMin(item_rect.Max.x, column->MaxX); + } + //GetForegroundDrawList()->AddRect(item_rect.Min, item_rect.Max, IM_COL32(255, 0, 255, 255)); + } + const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(item_rect); + const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(item_rect); if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr)) { if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce) @@ -8294,6 +8413,9 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) { selected = !selected; MultiSelectAddSetRange(ms, selected, +1, item_data, item_data); +#ifdef IMGUI_DEBUG_BOXSELECT + GetForegroundDrawList()->AddRectFilled(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, selected ? IM_COL32(0, 255, 0, 200) : IM_COL32(255, 0, 0, 200)); +#endif } storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1); } @@ -8407,7 +8529,6 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) } if (storage->NavIdItem == item_data) ms->NavIdPassedBy = true; - ms->LastSubmittedItem = item_data; *p_selected = selected; *p_pressed = pressed; @@ -8423,15 +8544,20 @@ void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected) void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item) { // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + // FIXME-OPT: Disabled on 2026/04/09 as this would break with any form of coarse clipping that we don't know about (e.g. TableNextColumn() return value). + // The low-hanging fruit would be to know that ImGuiSelectionUserData are sequential indices, in which case we can trivially compare PrevSubmittedItem + RangeDir == FirstItem. + // User can always perform this merge if required. +#if 0 if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) { ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1]; - if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected) + if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->PrevSubmittedItem && prev->Selected == selected) { prev->RangeLastItem = last_item; return; } } +#endif ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item }; ms->IO.Requests.push_back(req); // Add new request @@ -8669,7 +8795,8 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) const ImGuiStyle& style = g.Style; const ImGuiID id = GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); // Size default to hold ~7.25 items. // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. @@ -8692,7 +8819,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) if (label_size.x > 0.0f) { ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y); - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); AlignTextToFramePadding(); } @@ -8773,9 +8900,7 @@ bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)( // - PlotHistogram() //------------------------------------------------------------------------- // Plot/Graph widgets are not very good. -// Consider writing your own, or using a third-party one, see: -// - ImPlot https://github.com/epezent/implot -// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions +// Consider using ImPlot (https://github.com/epezent/implot) which is much better! //------------------------------------------------------------------------- int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg) @@ -8788,7 +8913,8 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); @@ -8887,10 +9013,11 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label, label_end, false); // Return hovered index or -1 if none are hovered. // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return idx_hovered; } @@ -8920,6 +9047,7 @@ void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } +// Plot Histogram (the data provided _is_ histogram data. it doesn't compute the histogram of your data) void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) { ImGuiPlotArrayGetterData data(values, stride); @@ -9257,7 +9385,8 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu g.MenusIdSubmittedThisFrame.push_back(id); - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window) // This is only done for items for the menu set and not the full parent window. @@ -9276,8 +9405,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) bool pressed; - // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups; + const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoAutoClosePopups | (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnClick; ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { @@ -9289,7 +9417,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, pos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags, label_size); LogSetNextTextDecoration("[", "]"); - RenderText(text_pos, label); + RenderText(text_pos, label, label_end, false); PopStyleVar(); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), text_pos.y - style.FramePadding.y + window->MenuBarHeight); @@ -9306,7 +9434,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) ImVec2 text_pos(window->DC.CursorPos.x, pos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); LogSetNextTextDecoration("", ">"); - RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label); + RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label, label_end, false); if (icon_w > 0.0f) RenderText(ImVec2(text_pos.x + offsets->OffsetIcon, text_pos.y), icon); RenderArrow(window->DrawList, ImVec2(text_pos.x + offsets->OffsetMark + extra_w + g.FontSize * 0.30f, text_pos.y), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); @@ -9315,6 +9443,14 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) if (!enabled) EndDisabled(); + // Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394) + // Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here. + if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0)) + { + ClearActiveID(); + SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner); + } + const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav; if (menuset_is_open) PopItemFlag(); @@ -9395,6 +9531,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); PopID(); + if (g.ActiveId == id && want_open) + g.ActiveIdNoClearOnFocusLoss = true; + if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame. @@ -9471,7 +9610,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; ImVec2 pos = window->DC.CursorPos; - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); // See BeginMenuEx() for comments about this. const bool menuset_is_open = IsRootOfOpenMenuSet(); @@ -9486,7 +9626,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut BeginDisabled(); // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover; + const ImGuiSelectableFlags selectable_flags = (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnRelease | (ImGuiSelectableFlags)ImGuiSelectableFlags_SetNavIdOnHover; ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { @@ -9498,7 +9638,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut pressed = Selectable("", selected, selectable_flags, ImVec2(label_size.x, 0.0f)); PopStyleVar(); if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) - RenderText(text_pos, label); + RenderText(text_pos, label, label_end, false); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). } else @@ -9515,7 +9655,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) { - RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label); + RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label, label_end, false); if (icon_w > 0.0f) RenderText(text_pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); if (shortcut_w > 0.0f) @@ -9529,6 +9669,17 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); } } + + // Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394) + // Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here. + const ImGuiID id = g.LastItemData.ID; + if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0)) + { + ClearActiveID(); + SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner); + } + + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); if (!enabled) EndDisabled(); @@ -10616,7 +10767,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX); if (want_clip_rect) - PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); + PushClipRect(ImVec2(ImClamp(bb.Min.x, tab_bar->ScrollingRectMinX, tab_bar->ScrollingRectMaxX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; ItemSize(bb.GetSize(), style.FramePadding.y); @@ -10740,7 +10891,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, float rounding = style.TabRounding; display_draw_list->PathArcToFast(tl + ImVec2(+rounding, +rounding), rounding, 7, 9); display_draw_list->PathArcToFast(tr + ImVec2(-rounding, +rounding), rounding, 9, 11); - display_draw_list->PathStroke(overline_col, 0, style.TabBarOverlineSize); + display_draw_list->PathStroke(overline_col, style.TabBarOverlineSize); } else { @@ -10858,7 +11009,7 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); - draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize); + draw_list->PathStroke(GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize); } } @@ -10867,7 +11018,8 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped) { ImGuiContext& g = *GImGui; - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); if (out_just_closed) *out_just_closed = false; @@ -10952,7 +11104,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, } } LogSetNextTextDecoration("/", "\\"); - RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, NULL, &label_size); + RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, label_end, &label_size); #if 0 if (!is_contents_visible) diff --git a/extensions/ImGui/src/ImGui/implot/implot.cpp b/extensions/ImGui/src/ImGui/implot/implot.cpp index 5550d03130a6..00b09f06b6e9 100644 --- a/extensions/ImGui/src/ImGui/implot/implot.cpp +++ b/extensions/ImGui/src/ImGui/implot/implot.cpp @@ -1,7 +1,7 @@ // MIT License // Copyright (c) 2020-2024 Evan Pezent -// Copyright (c) 2025 Breno Cunha Queiroz +// Copyright (c) 2025-2026 Breno Cunha Queiroz // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// ImPlot v0.17 +// ImPlot v1.1 WIP /* @@ -32,6 +32,68 @@ Below is a change-log of API breaking changes only. If you are using one of the When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files. You can read releases logs https://github.com/epezent/implot/releases for more details. +- 2026/02/12 (1.0) - ImPlotSpec replaces the SetNextXXX style functions. The guide below shows show to migrate from SetNextXXX to ImPlotSpec. + - `SetNextLineStyle` has been removed, styling should be set via ImPlotSpec. + ``` + // Before + ImPlot::SetNextLineStyle(line_color, line_weight); + ImPlot::PlotLine("Line", xs, ys, count); + + // After + ImPlotSpec spec; + spec.LineColor = line_color; + spec.LineWeight = line_weight; + ImPlot::PlotLine("Line", xs, ys, count, spec); + ``` + - `SetNextFillStyle` has been removed, styling should be set via ImPlotSpec. + ``` + // Before + ImPlot::SetNextFillStyle(fill_color, fill_alpha); + ImPlot::PlotLine("Shaded", xs, ys, count, ImPlotLineFlags_Shaded); + + // After + ImPlotSpec spec; + spec.FillColor = fill_color; + spec.FillAlpha = fill_alpha; + spec.Flags = ImPlotLineFlags_Shaded; + ImPlot::PlotTLine("Shaded", xs, ys, count, spec); + ``` + - SetNextMarkerStyle has been removed, styling should be set via ImPlotSpec. + ``` + // Before + ImPlot::SetNextMarkerStyle(marker, marker_size, fill_color, line_weight, marker_outline_color); + ImPlot::PlotScatter("Scatter", xs, ys, count); + + // After + ImPlotSpec spec; + spec.LineWeight = line_weight; + spec.Marker = marker; + spec.MarkerSize = marker_size; + spec.MarkerLineColor = marker_outline_color; + spec.MarkerFillColor = fill_color; + ImPlot::PlotScatter("Scatter", xs, ys, count, spec); + ``` + - SetNextErrorBarStyle has been removed, styling should be set via ImPlotSpec. + ``` + // Before + ImPlot::SetNextErrorBarStyle(color, size, weight); + ImPlot::PlotErrorBars("ErrorBar", xs, ys, err, count); + + // After + ImPlotSpec spec; + spec.LineColor = color; + spec.Size = size; + spec.LineWeight = weight; + ImPlot::PlotErrorBars("ErrorBar", xs, ys, err, count, spec); + ``` + - Flags, Offset and Stride should also be set via ImPlotSpec now. +- 2023/10/02 (1.0) - ImPlotSpec was made the default and _only_ way of styling plot items. Therefore the following features were removed: + - ImPlotCol_Line, ImPlotCol_Fill, ImPlotCol_MarkerOutline, ImPlotCol_MarkerFill, ImPlotCol_ErrorBar have been removed and thus are no longer supported by PushStyleColor. + You can use a common ImPlotSpec instance across multiple PlotX calls to emulate PushStyleColor behavior. + - ImPlotStyleVar_LineWeight, ImPlotStyleVar_Marker, ImPlotStyleVar_MarkerSize, ImPlotStyleVar_MarkerWeight, ImPlotStyleVar_FillAlpha, ImPlotStyleVar_ErrorBarSize, and ImPlotStyleVar_ErrorBarWeight + have been removed and thus are no longer supported by PushStyleVar. You can use a common ImPlotSpec instance across multiple PlotX calls to emulate PushStyleVar behavior. + - ImPlotStyle/ImPlotStyleVar_ DigitalBitGap was renamed to DigitalSpacing; DigitalBitHeight was removed (use ImPlotSpec::Size); DigitalPadding was added for padding from bottom. + - PlotX offset, stride, and flags parameters are now incorporated into ImPlotSpec; specify these variables in the ImPlotSpec passed to PlotX. - 2023/08/20 (0.17) - ImPlotFlags_NoChild was removed as child windows are no longer needed to capture scroll. You can safely remove this flag if you were using it. - 2023/06/26 (0.15) - Various build fixes related to updates in Dear ImGui internals. - 2022/11/25 (0.15) - Make PlotText honor ImPlotItemFlags_NoFit. @@ -153,8 +215,11 @@ You can read releases logs https://github.com/epezent/implot/releases for more d // Clang/GCC warnings with -Weverything #if defined(__clang__) #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #endif // Global plot context @@ -171,17 +236,8 @@ ImPlotInputMap::ImPlotInputMap() { } ImPlotStyle::ImPlotStyle() { - - LineWeight = 1; - Marker = ImPlotMarker_None; - MarkerSize = 4; - MarkerWeight = 1; - FillAlpha = 1; - ErrorBarSize = 5; - ErrorBarWeight = 1.5f; - DigitalBitHeight = 8; - DigitalBitGap = 4; - + PlotDefaultSize = ImVec2(400,300); + PlotMinSize = ImVec2(200,150); PlotBorderSize = 1; MinorAlpha = 0.25f; MajorTickLen = ImVec2(10,10); @@ -198,9 +254,9 @@ ImPlotStyle::ImPlotStyle() { MousePosPadding = ImVec2(10,10); AnnotationPadding = ImVec2(2,2); FitPadding = ImVec2(0,0); - PlotDefaultSize = ImVec2(400,300); - PlotMinSize = ImVec2(200,150); - + DigitalPadding = 20; + DigitalSpacing = 4; + ImPlot::StyleColorsAuto(this); Colormap = ImPlotColormap_Deep; @@ -218,11 +274,6 @@ namespace ImPlot { const char* GetStyleColorName(ImPlotCol col) { static const char* col_names[ImPlotCol_COUNT] = { - "Line", - "Fill", - "MarkerOutline", - "MarkerFill", - "ErrorBar", "FrameBg", "PlotBg", "PlotBorder", @@ -246,6 +297,7 @@ const char* GetStyleColorName(ImPlotCol col) { const char* GetMarkerName(ImPlotMarker marker) { switch (marker) { case ImPlotMarker_None: return "None"; + case ImPlotMarker_Auto: return "Auto"; case ImPlotMarker_Circle: return "Circle"; case ImPlotMarker_Square: return "Square"; case ImPlotMarker_Diamond: return "Diamond"; @@ -256,6 +308,8 @@ const char* GetMarkerName(ImPlotMarker marker) { case ImPlotMarker_Cross: return "Cross"; case ImPlotMarker_Plus: return "Plus"; case ImPlotMarker_Asterisk: return "Asterisk"; + case ImPlotMarker_Vertical: return "Vertical"; + case ImPlotMarker_Horizontal: return "Horizontal"; default: return ""; } } @@ -263,11 +317,6 @@ const char* GetMarkerName(ImPlotMarker marker) { ImVec4 GetAutoColor(ImPlotCol idx) { ImVec4 col(0,0,0,1); switch(idx) { - case ImPlotCol_Line: return col; // these are plot dependent! - case ImPlotCol_Fill: return col; // these are plot dependent! - case ImPlotCol_MarkerOutline: return col; // these are plot dependent! - case ImPlotCol_MarkerFill: return col; // these are plot dependent! - case ImPlotCol_ErrorBar: return ImGui::GetStyleColorVec4(ImGuiCol_Text); case ImPlotCol_FrameBg: return ImGui::GetStyleColorVec4(ImGuiCol_FrameBg); case ImPlotCol_PlotBg: return ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); case ImPlotCol_PlotBorder: return ImGui::GetStyleColorVec4(ImGuiCol_Border); @@ -297,16 +346,8 @@ struct ImPlotStyleVarInfo { static const ImPlotStyleVarInfo GPlotStyleVarInfo[] = { - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, LineWeight) }, // ImPlotStyleVar_LineWeight - { ImGuiDataType_S32, 1, (ImU32)offsetof(ImPlotStyle, Marker) }, // ImPlotStyleVar_Marker - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MarkerSize) }, // ImPlotStyleVar_MarkerSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MarkerWeight) }, // ImPlotStyleVar_MarkerWeight - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, FillAlpha) }, // ImPlotStyleVar_FillAlpha - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, ErrorBarSize) }, // ImPlotStyleVar_ErrorBarSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, ErrorBarWeight) }, // ImPlotStyleVar_ErrorBarWeight - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalBitHeight) }, // ImPlotStyleVar_DigitalBitHeight - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalBitGap) }, // ImPlotStyleVar_DigitalBitGap - + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotDefaultSize) }, // ImPlotStyleVar_PlotDefaultSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotMinSize) }, // ImPlotStyleVar_PlotMinSize { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, PlotBorderSize) }, // ImPlotStyleVar_PlotBorderSize { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MinorAlpha) }, // ImPlotStyleVar_MinorAlpha { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorTickLen) }, // ImPlotStyleVar_MajorTickLen @@ -320,12 +361,11 @@ static const ImPlotStyleVarInfo GPlotStyleVarInfo[] = { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendPadding) }, // ImPlotStyleVar_LegendPadding { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendInnerPadding) }, // ImPlotStyleVar_LegendInnerPadding { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendSpacing) }, // ImPlotStyleVar_LegendSpacing - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MousePosPadding) }, // ImPlotStyleVar_MousePosPadding { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, AnnotationPadding) }, // ImPlotStyleVar_AnnotationPadding { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, FitPadding) }, // ImPlotStyleVar_FitPadding - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotDefaultSize) }, // ImPlotStyleVar_PlotDefaultSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotMinSize) } // ImPlotStyleVar_PlotMinSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalPadding) }, // ImPlotStyleVar_DigitalPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalSpacing) }, // ImPlotStyleVar_DigitalSpacing }; static const ImPlotStyleVarInfo* GetPlotStyleVarInfo(ImPlotStyleVar idx) { @@ -466,22 +506,22 @@ void Initialize(ImPlotContext* ctx) { ResetCtxForNextAlignedPlots(ctx); ResetCtxForNextSubplot(ctx); - const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396 }; - const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409 }; - const ImU32 Pastel[] = {4289639675, 4293119411, 4291161036, 4293184478, 4289124862, 4291624959, 4290631909, 4293712637, 4294111986 }; - const ImU32 Paired[] = {4293119554, 4290017311, 4287291314, 4281114675, 4288256763, 4280031971, 4285513725, 4278222847, 4292260554, 4288298346, 4288282623, 4280834481}; - const ImU32 Viridis[] = {4283695428, 4285867080, 4287054913, 4287455029, 4287526954, 4287402273, 4286883874, 4285579076, 4283552122, 4280737725, 4280674301 }; - const ImU32 Plasma[] = {4287039501, 4288480321, 4289200234, 4288941455, 4287638193, 4286072780, 4284638433, 4283139314, 4281771772, 4280667900, 4280416752 }; - const ImU32 Hot[] = {4278190144, 4278190208, 4278190271, 4278190335, 4278206719, 4278223103, 4278239231, 4278255615, 4283826175, 4289396735, 4294967295 }; - const ImU32 Cool[] = {4294967040, 4294960666, 4294954035, 4294947661, 4294941030, 4294934656, 4294928025, 4294921651, 4294915020, 4294908646, 4294902015 }; - const ImU32 Pink[] = {4278190154, 4282532475, 4284308894, 4285690554, 4286879686, 4287870160, 4288794330, 4289651940, 4291685869, 4293392118, 4294967295 }; - const ImU32 Jet[] = {4289331200, 4294901760, 4294923520, 4294945280, 4294967040, 4289396565, 4283826090, 4278255615, 4278233855, 4278212095, 4278190335 }; + const ImU32 Deep[] = {IM_RGB(76,114,176),IM_RGB(221,132,82),IM_RGB(85,168,104),IM_RGB(196,78,82),IM_RGB(129,114,179),IM_RGB(147,120,96),IM_RGB(218,139,195),IM_RGB(140,140,140),IM_RGB(204,185,116),IM_RGB(100,181,205)}; + const ImU32 Dark[] = {IM_RGB(228,26,28),IM_RGB(55,126,184),IM_RGB(77,175,74),IM_RGB(152,78,163),IM_RGB(255,127,0),IM_RGB(255,255,51),IM_RGB(166,86,40),IM_RGB(247,129,191),IM_RGB(153,153,153)}; + const ImU32 Pastel[] = {IM_RGB(251,180,174),IM_RGB(179,205,227),IM_RGB(204,235,197),IM_RGB(222,203,228),IM_RGB(254,217,166),IM_RGB(255,255,204),IM_RGB(229,216,189),IM_RGB(253,218,236),IM_RGB(242,242,242)}; + const ImU32 Paired[] = {IM_RGB(66,206,227),IM_RGB(31,120,180),IM_RGB(178,223,138),IM_RGB(51,160,44),IM_RGB(251,154,153),IM_RGB(227,26,28),IM_RGB(253,191,111),IM_RGB(255,127,0),IM_RGB(202,178,214),IM_RGB(106,61,154),IM_RGB(255,255,153),IM_RGB(177,89,40)}; + const ImU32 Viridis[] = {IM_RGB(68,1,84),IM_RGB(72,36,117),IM_RGB(65,68,135),IM_RGB(53,95,141),IM_RGB(42,120,142),IM_RGB(33,145,140),IM_RGB(34,168,132),IM_RGB(68,191,112),IM_RGB(122,209,81),IM_RGB(189,223,38),IM_RGB(253,231,37)}; + const ImU32 Plasma[] = {IM_RGB(13,8,135),IM_RGB(65,4,157),IM_RGB(106,0,168),IM_RGB(143,13,164),IM_RGB(177,42,144),IM_RGB(204,71,120),IM_RGB(225,100,98),IM_RGB(242,132,75),IM_RGB(252,166,54),IM_RGB(252,206,37),IM_RGB(240,249,33)}; + const ImU32 Hot[] = {IM_RGB(64,0,0),IM_RGB(128,0,0),IM_RGB(191,0,0),IM_RGB(255,0,0),IM_RGB(255,64,0),IM_RGB(255,128,0),IM_RGB(255,191,0),IM_RGB(255,255,0),IM_RGB(255,255,85),IM_RGB(255,255,170),IM_RGB(255,255,255)}; + const ImU32 Cool[] = {IM_RGB(0,255,255),IM_RGB(26,230,255),IM_RGB(51,204,255),IM_RGB(77,179,255),IM_RGB(102,153,255),IM_RGB(128,128,255),IM_RGB(153,102,255),IM_RGB(179,77,255),IM_RGB(204,51,255),IM_RGB(230,26,255),IM_RGB(255,0,255)}; + const ImU32 Pink[] = {IM_RGB(74,0,0),IM_RGB(123,66,66),IM_RGB(158,93,93),IM_RGB(186,114,114),IM_RGB(198,151,132),IM_RGB(208,180,147),IM_RGB(218,206,161),IM_RGB(228,228,174),IM_RGB(237,237,205),IM_RGB(246,246,231),IM_RGB(255,255,255)}; + const ImU32 Jet[] = {IM_RGB(0,0,170),IM_RGB(0,0,255),IM_RGB(0,85,255),IM_RGB(0,170,255),IM_RGB(0,255,255),IM_RGB(85,255,170),IM_RGB(170,255,85),IM_RGB(255,255,0),IM_RGB(255,170,0),IM_RGB(255,85,0),IM_RGB(255,0,0)}; const ImU32 Twilight[] = {IM_RGB(226,217,226),IM_RGB(166,191,202),IM_RGB(109,144,192),IM_RGB(95,88,176),IM_RGB(83,30,124),IM_RGB(47,20,54),IM_RGB(100,25,75),IM_RGB(159,60,80),IM_RGB(192,117,94),IM_RGB(208,179,158),IM_RGB(226,217,226)}; const ImU32 RdBu[] = {IM_RGB(103,0,31),IM_RGB(178,24,43),IM_RGB(214,96,77),IM_RGB(244,165,130),IM_RGB(253,219,199),IM_RGB(247,247,247),IM_RGB(209,229,240),IM_RGB(146,197,222),IM_RGB(67,147,195),IM_RGB(33,102,172),IM_RGB(5,48,97)}; const ImU32 BrBG[] = {IM_RGB(84,48,5),IM_RGB(140,81,10),IM_RGB(191,129,45),IM_RGB(223,194,125),IM_RGB(246,232,195),IM_RGB(245,245,245),IM_RGB(199,234,229),IM_RGB(128,205,193),IM_RGB(53,151,143),IM_RGB(1,102,94),IM_RGB(0,60,48)}; const ImU32 PiYG[] = {IM_RGB(142,1,82),IM_RGB(197,27,125),IM_RGB(222,119,174),IM_RGB(241,182,218),IM_RGB(253,224,239),IM_RGB(247,247,247),IM_RGB(230,245,208),IM_RGB(184,225,134),IM_RGB(127,188,65),IM_RGB(77,146,33),IM_RGB(39,100,25)}; const ImU32 Spectral[] = {IM_RGB(158,1,66),IM_RGB(213,62,79),IM_RGB(244,109,67),IM_RGB(253,174,97),IM_RGB(254,224,139),IM_RGB(255,255,191),IM_RGB(230,245,152),IM_RGB(171,221,164),IM_RGB(102,194,165),IM_RGB(50,136,189),IM_RGB(94,79,162)}; - const ImU32 Greys[] = {IM_COL32_WHITE, IM_COL32_BLACK }; + const ImU32 Greys[] = {IM_COL32_WHITE, IM_COL32_BLACK}; IMPLOT_APPEND_CMAP(Deep, true); IMPLOT_APPEND_CMAP(Dark, true); @@ -717,8 +757,8 @@ bool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool hov // Locators //----------------------------------------------------------------------------- -static const float TICK_FILL_X = 0.8f; -static const float TICK_FILL_Y = 1.0f; +constexpr float TICK_FILL_X = 0.8f; +constexpr float TICK_FILL_Y = 1.0f; void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { if (range.Min == range.Max) @@ -854,7 +894,7 @@ void AddTicksCustom(const double* values, const char* const labels[], int n, ImP //----------------------------------------------------------------------------- // this may not be thread safe? -static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = { +constexpr double TimeUnitSpans[ImPlotTimeUnit_COUNT] = { 0.000001, 0.001, 1, @@ -866,7 +906,7 @@ static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = { }; inline ImPlotTimeUnit GetUnitForRange(double range) { - static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME}; + constexpr double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME}; for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) { if (range <= cutoffs[i]) return (ImPlotTimeUnit)i; @@ -886,28 +926,28 @@ inline int LowerBoundStep(int max_divs, const int* divs, const int* step, int si inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) { if (unit == ImPlotTimeUnit_Ms || unit == ImPlotTimeUnit_Us) { - static const int step[] = {500,250,200,100,50,25,20,10,5,2,1}; - static const int divs[] = {2,4,5,10,20,40,50,100,200,500,1000}; + constexpr int step[] = {500,250,200,100,50,25,20,10,5,2,1}; + constexpr int divs[] = {2,4,5,10,20,40,50,100,200,500,1000}; return LowerBoundStep(max_divs, divs, step, 11); } if (unit == ImPlotTimeUnit_S || unit == ImPlotTimeUnit_Min) { - static const int step[] = {30,15,10,5,1}; - static const int divs[] = {2,4,6,12,60}; + constexpr int step[] = {30,15,10,5,1}; + constexpr int divs[] = {2,4,6,12,60}; return LowerBoundStep(max_divs, divs, step, 5); } else if (unit == ImPlotTimeUnit_Hr) { - static const int step[] = {12,6,3,2,1}; - static const int divs[] = {2,4,8,12,24}; + constexpr int step[] = {12,6,3,2,1}; + constexpr int divs[] = {2,4,8,12,24}; return LowerBoundStep(max_divs, divs, step, 5); } else if (unit == ImPlotTimeUnit_Day) { - static const int step[] = {14,7,2,1}; - static const int divs[] = {2,4,14,28}; + constexpr int step[] = {14,7,2,1}; + constexpr int divs[] = {2,4,14,28}; return LowerBoundStep(max_divs, divs, step, 4); } else if (unit == ImPlotTimeUnit_Mo) { - static const int step[] = {6,3,2,1}; - static const int divs[] = {2,4,6,12}; + constexpr int step[] = {6,3,2,1}; + constexpr int divs[] = {2,4,6,12}; return LowerBoundStep(max_divs, divs, step, 4); } return 0; @@ -1651,8 +1691,10 @@ void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignm if (opp) { if (count_T++ > 0) pad_T += K + P; - if (label) - pad_T += T + P; + if (label) { + ImVec2 label_size = ImGui::CalcTextSize(plot.GetAxisLabel(axis)); + pad_T += label_size.y + P; + } if (ticks) pad_T += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0); axis.Datum1 = plot.CanvasRect.Min.y + pad_T; @@ -1662,8 +1704,10 @@ void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignm else { if (count_B++ > 0) pad_B += K + P; - if (label) - pad_B += T + P; + if (label) { + ImVec2 label_size = ImGui::CalcTextSize(plot.GetAxisLabel(axis)); + pad_B += label_size.y + P; + } if (ticks) pad_B += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0); axis.Datum1 = plot.CanvasRect.Max.y - pad_B; @@ -1823,8 +1867,8 @@ static inline void RenderSelectionRect(ImDrawList& DrawList, const ImVec2& p_min // Input Handling //----------------------------------------------------------------------------- -static const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f; -static const float BOX_SELECT_DRAG_THRESHOLD = 4.0f; +constexpr float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f; +constexpr float BOX_SELECT_DRAG_THRESHOLD = 4.0f; bool UpdateInput(ImPlotPlot& plot) { @@ -1991,6 +2035,9 @@ bool UpdateInput(ImPlotPlot& plot) { float tx = ImRemap(IO.MousePos.x, plot.PlotRect.Min.x, plot.PlotRect.Max.x, 0.0f, 1.0f); float ty = ImRemap(IO.MousePos.y, plot.PlotRect.Min.y, plot.PlotRect.Max.y, 0.0f, 1.0f); + // Track which axis to use as reference for equal aspect + ImPlotAxis* equal_ref_axis = nullptr; + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { ImPlotAxis& x_axis = plot.XAxis(i); const bool equal_zoom = axis_equal && x_axis.OrthoAxis != nullptr; @@ -1998,13 +2045,12 @@ bool UpdateInput(ImPlotPlot& plot) { if (x_hov[i] && !x_axis.IsInputLocked() && !equal_locked) { ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.ID); if (zoom_rate != 0.0f) { - float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f; - const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - rect_size.x * tx * zoom_rate * correction); - const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x + rect_size.x * (1 - tx) * zoom_rate * correction); + const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - rect_size.x * tx * zoom_rate); + const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x + rect_size.x * (1 - tx) * zoom_rate); x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l); x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r); - if (axis_equal && x_axis.OrthoAxis != nullptr) - x_axis.OrthoAxis->SetAspect(x_axis.GetAspect()); + if (equal_zoom) + equal_ref_axis = &x_axis; changed = true; } } @@ -2016,17 +2062,21 @@ bool UpdateInput(ImPlotPlot& plot) { if (y_hov[i] && !y_axis.IsInputLocked() && !equal_locked) { ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.ID); if (zoom_rate != 0.0f) { - float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f; - const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - rect_size.y * ty * zoom_rate * correction); - const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y + rect_size.y * (1 - ty) * zoom_rate * correction); + const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - rect_size.y * ty * zoom_rate); + const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y + rect_size.y * (1 - ty) * zoom_rate); y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b); y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t); - if (axis_equal && y_axis.OrthoAxis != nullptr) - y_axis.OrthoAxis->SetAspect(y_axis.GetAspect()); + if (equal_zoom) + equal_ref_axis = &y_axis; changed = true; } } } + + // Apply equal aspect constraint after zooming both axes + if (equal_ref_axis != nullptr && equal_ref_axis->OrthoAxis != nullptr) { + equal_ref_axis->OrthoAxis->SetAspect(equal_ref_axis->GetAspect()); + } } // BOX-SELECTION ---------------------------------------------------------- @@ -2212,6 +2262,8 @@ void SetupAxisTicks(ImAxis idx, double v_min, double v_max, int n_ticks, const c ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + IM_ASSERT_USER_ERROR(labels == nullptr || n_ticks >= 2, + "When providing custom labels, n_ticks must be at least 2!"); n_ticks = n_ticks < 2 ? 2 : n_ticks; FillRange(gp.TempDouble1, n_ticks, v_min, v_max); SetupAxisTicks(idx, gp.TempDouble1.Data, n_ticks, labels, show_default); @@ -2589,6 +2641,17 @@ void SetupFinish() { } } + // (4.5) recalc padding now that we have actual X-axis tick labels (handles multi-line labels) + // Save title padding before resetting + const float title_pad = (title_size.x > 0) ? (title_size.y + gp.Style.LabelPadding.y) : 0.0f; + pad_top = title_pad; + pad_bot = 0; + PadAndDatumAxesX(plot,pad_top,pad_bot,gp.CurrentAlignmentH); + // Update AxesRect to account for title padding (was done in step 0) + if (title_size.x > 0) { + plot.AxesRect.Min.y = plot.FrameRect.Min.y + gp.Style.PlotPadding.y + title_pad; + } + // (5) calc plot bb plot.PlotRect = ImRect(plot.CanvasRect.Min + ImVec2(pad_left, pad_top), plot.CanvasRect.Max - ImVec2(pad_right, pad_bot)); @@ -3111,8 +3174,13 @@ void EndPlot() { } // render border +#if IMGUI_VERSION_NUM < 19276 if (render_border) - DrawList.AddRect(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBorder), 0, ImDrawFlags_RoundCornersAll, gp.Style.PlotBorderSize); + DrawList.AddRect(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBorder), 0, ImDrawFlags_None, gp.Style.PlotBorderSize); +#else + if (render_border) + DrawList.AddRect(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBorder), 0, gp.Style.PlotBorderSize, ImDrawFlags_None); +#endif // render tags for (int i = 0; i < gp.Tags.Size; ++i) { @@ -3271,9 +3339,9 @@ void EndPlot() { // BEGIN/END SUBPLOT //----------------------------------------------------------------------------- -static const float SUBPLOT_BORDER_SIZE = 1.0f; -static const float SUBPLOT_SPLITTER_HALF_THICKNESS = 4.0f; -static const float SUBPLOT_SPLITTER_FEEDBACK_TIMER = 0.06f; +constexpr float SUBPLOT_BORDER_SIZE = 1.0f; +constexpr float SUBPLOT_SPLITTER_HALF_THICKNESS = 4.0f; +constexpr float SUBPLOT_SPLITTER_FEEDBACK_TIMER = 0.06f; void SubplotSetCell(int row, int col) { ImPlotContext& gp = *GImPlot; @@ -3901,7 +3969,7 @@ IMPLOT_API void TagYV(double y, const ImVec4& color, const char* fmt, va_list ar TagV(gp.CurrentPlot->CurrentY, y, color, fmt, args); } -static const float DRAG_GRAB_HALF_SIZE = 4.0f; +constexpr float DRAG_GRAB_HALF_SIZE = 4.0f; bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) { ImGui::PushID("#IMPLOT_DRAG_POINT"); @@ -4102,31 +4170,34 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m bool modified = false; bool clicked = false, hovered = false, held = false; - ImRect b_rect(pc.x-DRAG_GRAB_HALF_SIZE,pc.y-DRAG_GRAB_HALF_SIZE,pc.x+DRAG_GRAB_HALF_SIZE,pc.y+DRAG_GRAB_HALF_SIZE); - ImGui::KeepAliveID(id); - if (input) { - // middle point - clicked = ImGui::ButtonBehavior(b_rect,id,&hovered,&held); - if (out_clicked) *out_clicked = clicked; - if (out_hovered) *out_hovered = hovered; - if (out_held) *out_held = held; - } + const bool is_movable = *x_min != *x_max || *y_min != *y_max; + if (is_movable) { + ImGui::KeepAliveID(id); + if (input) { + // middle point + ImRect b_rect(pc.x-DRAG_GRAB_HALF_SIZE,pc.y-DRAG_GRAB_HALF_SIZE,pc.x+DRAG_GRAB_HALF_SIZE,pc.y+DRAG_GRAB_HALF_SIZE); + clicked = ImGui::ButtonBehavior(b_rect,id,&hovered,&held); + if (out_clicked) *out_clicked = clicked; + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + } - if ((hovered || held) && show_curs) - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); - if (held && ImGui::IsMouseDragging(0)) { - for (int i = 0; i < 4; ++i) { - ImPlotPoint pp = PixelsToPlot(p[i] + ImGui::GetIO().MouseDelta,IMPLOT_AUTO,IMPLOT_AUTO); - *y[i] = pp.y; - *x[i] = pp.x; + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + if (held && ImGui::IsMouseDragging(0)) { + for (int i = 0; i < 4; ++i) { + ImPlotPoint pp = PixelsToPlot(p[i] + ImGui::GetIO().MouseDelta,IMPLOT_AUTO,IMPLOT_AUTO); + *y[i] = pp.y; + *x[i] = pp.x; + } + modified = true; } - modified = true; } for (int i = 0; i < 4; ++i) { // points - b_rect = ImRect(p[i].x-DRAG_GRAB_HALF_SIZE,p[i].y-DRAG_GRAB_HALF_SIZE,p[i].x+DRAG_GRAB_HALF_SIZE,p[i].y+DRAG_GRAB_HALF_SIZE); + ImRect b_rect(p[i].x - DRAG_GRAB_HALF_SIZE, p[i].y - DRAG_GRAB_HALF_SIZE, p[i].x + DRAG_GRAB_HALF_SIZE, p[i].y + DRAG_GRAB_HALF_SIZE); ImGuiID p_id = id + i + 1; ImGui::KeepAliveID(p_id); if (input) { @@ -4488,6 +4559,14 @@ void PopStyleVar(int count) { } } +ImPlotMarker NextMarker() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "NextMarker() needs to be called between BeginPlot() and EndPlot()!"); + const int idx = gp.CurrentItems->MarkerIdx % ImPlotMarker_COUNT; + ++gp.CurrentItems->MarkerIdx; + return idx; +} + //------------------------------------------------------------------------------ // [Section] Colormaps //------------------------------------------------------------------------------ @@ -4561,7 +4640,7 @@ ImU32 NextColormapColorU32() { ImVec4 NextColormapColor() { return ImGui::ColorConvertU32ToFloat4(NextColormapColorU32()); -} +} int GetColormapSize(ImPlotColormap cmap) { ImPlotContext& gp = *GImPlot; @@ -4957,16 +5036,9 @@ void ShowStyleEditor(ImPlotStyle* ref) { "Use \"Export\" below to save them somewhere."); if (ImGui::BeginTabBar("##StyleEditor")) { if (ImGui::BeginTabItem("Variables")) { - ImGui::Text("Item Styling"); - ImGui::SliderFloat("LineWeight", &style.LineWeight, 0.0f, 5.0f, "%.1f"); - ImGui::SliderFloat("MarkerSize", &style.MarkerSize, 2.0f, 10.0f, "%.1f"); - ImGui::SliderFloat("MarkerWeight", &style.MarkerWeight, 0.0f, 5.0f, "%.1f"); - ImGui::SliderFloat("FillAlpha", &style.FillAlpha, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat("ErrorBarSize", &style.ErrorBarSize, 0.0f, 10.0f, "%.1f"); - ImGui::SliderFloat("ErrorBarWeight", &style.ErrorBarWeight, 0.0f, 5.0f, "%.1f"); - ImGui::SliderFloat("DigitalBitHeight", &style.DigitalBitHeight, 0.0f, 20.0f, "%.1f"); - ImGui::SliderFloat("DigitalBitGap", &style.DigitalBitGap, 0.0f, 20.0f, "%.1f"); ImGui::Text("Plot Styling"); + ImGui::SliderFloat2("PlotDefaultSize", (float*)&style.PlotDefaultSize, 0.0f, 1000, "%.0f"); + ImGui::SliderFloat2("PlotMinSize", (float*)&style.PlotMinSize, 0.0f, 300, "%.0f"); ImGui::SliderFloat("PlotBorderSize", &style.PlotBorderSize, 0.0f, 2.0f, "%.0f"); ImGui::SliderFloat("MinorAlpha", &style.MinorAlpha, 0.0f, 1.0f, "%.2f"); ImGui::SliderFloat2("MajorTickLen", (float*)&style.MajorTickLen, 0.0f, 20.0f, "%.0f"); @@ -4975,8 +5047,6 @@ void ShowStyleEditor(ImPlotStyle* ref) { ImGui::SliderFloat2("MinorTickSize", (float*)&style.MinorTickSize, 0.0f, 2.0f, "%.1f"); ImGui::SliderFloat2("MajorGridSize", (float*)&style.MajorGridSize, 0.0f, 2.0f, "%.1f"); ImGui::SliderFloat2("MinorGridSize", (float*)&style.MinorGridSize, 0.0f, 2.0f, "%.1f"); - ImGui::SliderFloat2("PlotDefaultSize", (float*)&style.PlotDefaultSize, 0.0f, 1000, "%.0f"); - ImGui::SliderFloat2("PlotMinSize", (float*)&style.PlotMinSize, 0.0f, 300, "%.0f"); ImGui::Text("Plot Padding"); ImGui::SliderFloat2("PlotPadding", (float*)&style.PlotPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("LabelPadding", (float*)&style.LabelPadding, 0.0f, 20.0f, "%.0f"); @@ -4986,7 +5056,8 @@ void ShowStyleEditor(ImPlotStyle* ref) { ImGui::SliderFloat2("MousePosPadding", (float*)&style.MousePosPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("AnnotationPadding", (float*)&style.AnnotationPadding, 0.0f, 5.0f, "%.0f"); ImGui::SliderFloat2("FitPadding", (float*)&style.FitPadding, 0, 0.2f, "%.2f"); - + ImGui::SliderFloat("DigitalPadding", &style.DigitalPadding, 0.0f, 20.0f, "%.1f"); + ImGui::SliderFloat("DigitalSpacing", &style.DigitalSpacing, 0.0f, 10.0f, "%.1f"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Colors")) { @@ -5069,9 +5140,7 @@ void ShowStyleEditor(ImPlotStyle* ref) { ImGui::PopItemWidth(); ImGui::Separator(); ImGui::Text("Colors that are set to Auto (i.e. IMPLOT_AUTO_COL) will\n" - "be automatically deduced from your ImGui style or the\n" - "current ImPlot Colormap. If you want to style individual\n" - "plot items, use Push/PopStyleColor around its function."); + "be automatically deduced from your ImGui style."); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Colormaps")) { @@ -5328,7 +5397,7 @@ void ShowMetricsWindow(bool* p_popen) { ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color); if (ImGui::ColorEdit4("Color",&temp.x, ImGuiColorEditFlags_NoInputs)) item->Color = ImGui::ColorConvertFloat4ToU32(temp); - + ImGui::BulletText("Marker: %s", GetMarkerName(item->Marker)); ImGui::BulletText("NameOffset: %d",item->NameOffset); ImGui::BulletText("Name: %s", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : "N/A"); ImGui::BulletText("Hovered: %s",item->LegendHovered ? "true" : "false"); @@ -5763,11 +5832,6 @@ void StyleColorsAuto(ImPlotStyle* dst) { style->MinorAlpha = 0.25f; - colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; - colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; colors[ImPlotCol_FrameBg] = IMPLOT_AUTO_COL; colors[ImPlotCol_PlotBg] = IMPLOT_AUTO_COL; colors[ImPlotCol_PlotBorder] = IMPLOT_AUTO_COL; @@ -5792,12 +5856,7 @@ void StyleColorsClassic(ImPlotStyle* dst) { ImVec4* colors = style->Colors; style->MinorAlpha = 0.5f; - - colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; - colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_ErrorBar] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); colors[ImPlotCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.35f); colors[ImPlotCol_PlotBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); @@ -5822,11 +5881,6 @@ void StyleColorsDark(ImPlotStyle* dst) { style->MinorAlpha = 0.25f; - colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; - colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImPlotCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f); colors[ImPlotCol_PlotBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); @@ -5851,11 +5905,6 @@ void StyleColorsLight(ImPlotStyle* dst) { style->MinorAlpha = 1.0f; - colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; - colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImPlotCol_PlotBg] = ImVec4(0.42f, 0.57f, 1.00f, 0.13f); colors[ImPlotCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); @@ -5880,20 +5929,7 @@ void StyleColorsLight(ImPlotStyle* dst) { #ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS -bool BeginPlot(const char* title, const char* x_label, const char* y1_label, const ImVec2& size, - ImPlotFlags flags, ImPlotAxisFlags x_flags, ImPlotAxisFlags y1_flags, ImPlotAxisFlags y2_flags, ImPlotAxisFlags y3_flags, - const char* y2_label, const char* y3_label) -{ - if (!BeginPlot(title, size, flags)) - return false; - SetupAxis(ImAxis_X1, x_label, x_flags); - SetupAxis(ImAxis_Y1, y1_label, y1_flags); - if (ImHasFlag(flags, ImPlotFlags_YAxis2)) - SetupAxis(ImAxis_Y2, y2_label, y2_flags); - if (ImHasFlag(flags, ImPlotFlags_YAxis3)) - SetupAxis(ImAxis_Y3, y3_label, y3_flags); - return true; -} +// Deprecated method will go in here #endif diff --git a/extensions/ImGui/src/ImGui/implot/implot.h b/extensions/ImGui/src/ImGui/implot/implot.h index 2ab2f4012b51..6cc5bbbb8fe7 100644 --- a/extensions/ImGui/src/ImGui/implot/implot.h +++ b/extensions/ImGui/src/ImGui/implot/implot.h @@ -1,7 +1,7 @@ // MIT License // Copyright (c) 2020-2024 Evan Pezent -// Copyright (c) 2025 Breno Cunha Queiroz +// Copyright (c) 2025-2026 Breno Cunha Queiroz // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// ImPlot v0.17 +// ImPlot v1.1 WIP // Table of Contents: // @@ -62,16 +62,17 @@ #endif // ImPlot version string. -#define IMPLOT_VERSION "0.17" +#define IMPLOT_VERSION "1.1 WIP" // ImPlot version integer encoded as XYYZZ (X=major, YY=minor, ZZ=patch). -#define IMPLOT_VERSION_NUM 1700 -// Indicates variable should deduced automatically. -#define IMPLOT_AUTO -1 -// Special color used to indicate that a color should be deduced automatically. -#define IMPLOT_AUTO_COL ImVec4(0,0,0,-1) +#define IMPLOT_VERSION_NUM 10100 // Macro for templated plotting functions; keeps header clean. #define IMPLOT_TMP template IMPLOT_API +// Indicates variable should deduced automatically. +constexpr int IMPLOT_AUTO = -1; +// Special color used to indicate that a color should be deduced automatically. +constexpr ImVec4 IMPLOT_AUTO_COL = ImVec4(0,0,0,-1); + //----------------------------------------------------------------------------- // [SECTION] Enums and Types //----------------------------------------------------------------------------- @@ -81,6 +82,7 @@ struct ImPlotContext; // ImPlot context (opaque struct, see implot_i // Enums/Flags typedef int ImAxis; // -> enum ImAxis_ +typedef int ImPlotProp; // -> enum ImPlotProp_ typedef int ImPlotFlags; // -> enum ImPlotFlags_ typedef int ImPlotAxisFlags; // -> enum ImPlotAxisFlags_ typedef int ImPlotSubplotFlags; // -> enum ImPlotSubplotFlags_ @@ -92,6 +94,8 @@ typedef int ImPlotColormapScaleFlags; // -> ImPlotColormapScaleFlags_ typedef int ImPlotItemFlags; // -> ImPlotItemFlags_ typedef int ImPlotLineFlags; // -> ImPlotLineFlags_ typedef int ImPlotScatterFlags; // -> ImPlotScatterFlags +typedef int ImPlotBubblesFlags; // -> ImPlotBubblesFlags +typedef int ImPlotPolygonFlags; // -> ImPlotPolygonFlags_ typedef int ImPlotStairsFlags; // -> ImPlotStairsFlags_ typedef int ImPlotShadedFlags; // -> ImPlotShadedFlags_ typedef int ImPlotBarsFlags; // -> ImPlotBarsFlags_ @@ -116,6 +120,7 @@ typedef int ImPlotColormap; // -> enum ImPlotColormap_ typedef int ImPlotLocation; // -> enum ImPlotLocation_ typedef int ImPlotBin; // -> enum ImPlotBin_ + // Axis indices. The values assigned may change; NEVER hardcode these. enum ImAxis_ { // horizontal axes @@ -130,6 +135,27 @@ enum ImAxis_ { ImAxis_COUNT }; +// Plotting properties. These provide syntactic sugar for creating ImPlotSpecs from (ImPlotProp,value) pairs. See ImPlotSpec documentation. +enum ImPlotProp_ { + ImPlotProp_LineColor, // line color (applies to lines, bar edges); IMPLOT_AUTO_COL will use next Colormap color or current item color + ImPlotProp_LineColors, // array of colors for each line; if nullptr, use LineColor for all lines + ImPlotProp_LineWeight, // line weight in pixels (applies to lines, bar edges, marker edges) + ImPlotProp_FillColor, // fill color (applies to shaded regions, bar faces); IMPLOT_AUTO_COL will use next Colormap color or current item color + ImPlotProp_FillColors, // array of colors for each fill; if nullptr, use FillColor for all fills + ImPlotProp_FillAlpha, // alpha multiplier (applies to FillColor, FillColors, MarkerFillColor, and MarkerFillColors) + ImPlotProp_Marker, // marker type; specify ImPlotMarker_Auto to use the next unused marker + ImPlotProp_MarkerSize, // size of markers (radius) *in pixels* + ImPlotProp_MarkerSizes, // array of sizes for each marker; if nullptr, use MarkerSize for all markers + ImPlotProp_MarkerLineColor, // marker edge color; IMPLOT_AUTO_COL will use LineColor + ImPlotProp_MarkerLineColors, // array of colors for each marker edge; if nullptr, use MarkerLineColor for all markers + ImPlotProp_MarkerFillColor, // marker face color; IMPLOT_AUTO_COL will use LineColor + ImPlotProp_MarkerFillColors, // array of colors for each marker face; if nullptr, use MarkerFillColor for all markers + ImPlotProp_Size, // size of error bar whiskers (width or height), and digital bars (height) *in pixels* + ImPlotProp_Offset, // data index offset + ImPlotProp_Stride, // data stride in bytes; IMPLOT_AUTO will result in sizeof(T) where T is the type passed to PlotX + ImPlotProp_Flags // optional item flags; can be composed from common ImPlotItemFlags and/or specialized ImPlotXFlags +}; + // Options for plots (see BeginPlot). enum ImPlotFlags_ { ImPlotFlags_None = 0, // default @@ -223,14 +249,14 @@ enum ImPlotColormapScaleFlags_ { ImPlotColormapScaleFlags_Invert = 1 << 2, // invert the colormap bar and axis scale (this only affects rendering; if you only want to reverse the scale mapping, make scale_min > scale_max) }; -// Flags for ANY PlotX function +// Flags for ANY PlotX function. Used by setting ImPlotSpec::Flags. enum ImPlotItemFlags_ { ImPlotItemFlags_None = 0, ImPlotItemFlags_NoLegend = 1 << 0, // the item won't have a legend entry displayed ImPlotItemFlags_NoFit = 1 << 1, // the item won't be considered for plot fits }; -// Flags for PlotLine +// Flags for PlotLine. Used by setting ImPlotSpec::Flags. enum ImPlotLineFlags_ { ImPlotLineFlags_None = 0, // default ImPlotLineFlags_Segments = 1 << 10, // a line segment will be rendered from every two consecutive points @@ -240,70 +266,82 @@ enum ImPlotLineFlags_ { ImPlotLineFlags_Shaded = 1 << 14, // a filled region between the line and horizontal origin will be rendered; use PlotShaded for more advanced cases }; -// Flags for PlotScatter +// Flags for PlotScatter. Used by setting ImPlotSpec::Flags. enum ImPlotScatterFlags_ { ImPlotScatterFlags_None = 0, // default ImPlotScatterFlags_NoClip = 1 << 10, // markers on the edge of a plot will not be clipped }; -// Flags for PlotStairs +// Flags for PlotBubbles. Used by setting ImPlotSpec::Flags. +enum ImPlotBubblesFlags_ { + ImPlotBubblesFlags_None = 0, // default +}; + +// Flags for PlotPolygon. Used by setting ImPlotSpec::Flags. +enum ImPlotPolygonFlags_ { + ImPlotPolygonFlags_None = 0, // default (closed, convex polygon) + ImPlotPolygonFlags_Concave = 1 << 10, // use concave polygon filling (slower but supports concave shapes) +}; + +// Flags for PlotStairs. Used by setting ImPlotSpec::Flags. enum ImPlotStairsFlags_ { ImPlotStairsFlags_None = 0, // default ImPlotStairsFlags_PreStep = 1 << 10, // the y value is continued constantly to the left from every x position, i.e. the interval (x[i-1], x[i]] has the value y[i] ImPlotStairsFlags_Shaded = 1 << 11 // a filled region between the stairs and horizontal origin will be rendered; use PlotShaded for more advanced cases }; -// Flags for PlotShaded (placeholder) +// Flags for PlotShaded (placeholder). Used by setting ImPlotSpec::Flags. enum ImPlotShadedFlags_ { ImPlotShadedFlags_None = 0 // default }; -// Flags for PlotBars +// Flags for PlotBars. Used by setting ImPlotSpec::Flags. enum ImPlotBarsFlags_ { ImPlotBarsFlags_None = 0, // default ImPlotBarsFlags_Horizontal = 1 << 10, // bars will be rendered horizontally on the current y-axis }; -// Flags for PlotBarGroups +// Flags for PlotBarGroups. Used by setting ImPlotSpec::Flags. enum ImPlotBarGroupsFlags_ { ImPlotBarGroupsFlags_None = 0, // default ImPlotBarGroupsFlags_Horizontal = 1 << 10, // bar groups will be rendered horizontally on the current y-axis ImPlotBarGroupsFlags_Stacked = 1 << 11, // items in a group will be stacked on top of each other }; -// Flags for PlotErrorBars +// Flags for PlotErrorBars. Used by setting ImPlotSpec::Flags. enum ImPlotErrorBarsFlags_ { ImPlotErrorBarsFlags_None = 0, // default ImPlotErrorBarsFlags_Horizontal = 1 << 10, // error bars will be rendered horizontally on the current y-axis }; -// Flags for PlotStems +// Flags for PlotStems. Used by setting ImPlotSpec::Flags. enum ImPlotStemsFlags_ { ImPlotStemsFlags_None = 0, // default ImPlotStemsFlags_Horizontal = 1 << 10, // stems will be rendered horizontally on the current y-axis }; -// Flags for PlotInfLines +// Flags for PlotInfLines. Used by setting ImPlotSpec::Flags. enum ImPlotInfLinesFlags_ { ImPlotInfLinesFlags_None = 0, // default ImPlotInfLinesFlags_Horizontal = 1 << 10 // lines will be rendered horizontally on the current y-axis }; -// Flags for PlotPieChart +// Flags for PlotPieChart. Used by setting ImPlotSpec::Flags. enum ImPlotPieChartFlags_ { - ImPlotPieChartFlags_None = 0, // default - ImPlotPieChartFlags_Normalize = 1 << 10, // force normalization of pie chart values (i.e. always make a full circle if sum < 0) - ImPlotPieChartFlags_IgnoreHidden = 1 << 11, // ignore hidden slices when drawing the pie chart (as if they were not there) - ImPlotPieChartFlags_Exploding = 1 << 12 // Explode legend-hovered slice + ImPlotPieChartFlags_None = 0, // default + ImPlotPieChartFlags_Normalize = 1 << 10, // force normalization of pie chart values (i.e. always make a full circle if sum < 0) + ImPlotPieChartFlags_IgnoreHidden = 1 << 11, // ignore hidden slices when drawing the pie chart (as if they were not there) + ImPlotPieChartFlags_Exploding = 1 << 12, // explode legend-hovered slice + ImPlotPieChartFlags_NoSliceBorder = 1 << 13 // do not draw slice borders }; -// Flags for PlotHeatmap +// Flags for PlotHeatmap. Used by setting ImPlotSpec::Flags. enum ImPlotHeatmapFlags_ { ImPlotHeatmapFlags_None = 0, // default ImPlotHeatmapFlags_ColMajor = 1 << 10, // data will be read in column major order }; -// Flags for PlotHistogram and PlotHistogram2D +// Flags for PlotHistogram and PlotHistogram2D. Used by setting ImPlotSpec::Flags. enum ImPlotHistogramFlags_ { ImPlotHistogramFlags_None = 0, // default ImPlotHistogramFlags_Horizontal = 1 << 10, // histogram bars will be rendered horizontally (not supported by PlotHistogram2D) @@ -313,23 +351,23 @@ enum ImPlotHistogramFlags_ { ImPlotHistogramFlags_ColMajor = 1 << 14 // data will be read in column major order (not supported by PlotHistogram) }; -// Flags for PlotDigital (placeholder) +// Flags for PlotDigital (placeholder). Used by setting ImPlotSpec::Flags. enum ImPlotDigitalFlags_ { ImPlotDigitalFlags_None = 0 // default }; -// Flags for PlotImage (placeholder) +// Flags for PlotImage (placeholder). Used by setting ImPlotSpec::Flags. enum ImPlotImageFlags_ { ImPlotImageFlags_None = 0 // default }; -// Flags for PlotText +// Flags for PlotText. Used by setting ImPlotSpec::Flags. enum ImPlotTextFlags_ { ImPlotTextFlags_None = 0, // default ImPlotTextFlags_Vertical = 1 << 10 // text will be rendered vertically }; -// Flags for PlotDummy (placeholder) +// Flags for PlotDummy (placeholder). Used by setting ImPlotSpec::Flags. enum ImPlotDummyFlags_ { ImPlotDummyFlags_None = 0 // default }; @@ -344,13 +382,6 @@ enum ImPlotCond_ // Plot styling colors. enum ImPlotCol_ { - // item styling colors - ImPlotCol_Line, // plot line/outline color (defaults to next unused color in current colormap) - ImPlotCol_Fill, // plot fill color for bars (defaults to the current line color) - ImPlotCol_MarkerOutline, // marker outline color (defaults to the current line color) - ImPlotCol_MarkerFill, // marker fill color (defaults to the current line color) - ImPlotCol_ErrorBar, // error bar color (defaults to ImGuiCol_Text) - // plot styling colors ImPlotCol_FrameBg, // plot frame background color (defaults to ImGuiCol_FrameBg) ImPlotCol_PlotBg, // plot area background color (defaults to ImGuiCol_WindowBg) ImPlotCol_PlotBorder, // plot area border color (defaults to ImGuiCol_Border) @@ -372,17 +403,8 @@ enum ImPlotCol_ { // Plot styling variables. enum ImPlotStyleVar_ { - // item styling variables - ImPlotStyleVar_LineWeight, // float, plot item line weight in pixels - ImPlotStyleVar_Marker, // int, marker specification - ImPlotStyleVar_MarkerSize, // float, marker size in pixels (roughly the marker's "radius") - ImPlotStyleVar_MarkerWeight, // float, plot outline weight of markers in pixels - ImPlotStyleVar_FillAlpha, // float, alpha modifier applied to all plot item fills - ImPlotStyleVar_ErrorBarSize, // float, error bar whisker width in pixels - ImPlotStyleVar_ErrorBarWeight, // float, error bar whisker weight in pixels - ImPlotStyleVar_DigitalBitHeight, // float, digital channels bit height (at 1) in pixels - ImPlotStyleVar_DigitalBitGap, // float, digital channels bit padding gap in pixels - // plot styling variables + ImPlotStyleVar_PlotDefaultSize, // ImVec2, default size used when ImVec2(0,0) is passed to BeginPlot + ImPlotStyleVar_PlotMinSize, // ImVec2, minimum size plot frame can be when shrunk ImPlotStyleVar_PlotBorderSize, // float, thickness of border around plot area ImPlotStyleVar_MinorAlpha, // float, alpha multiplier applied to minor axis grid lines ImPlotStyleVar_MajorTickLen, // ImVec2, major tick lengths for X and Y axes @@ -399,8 +421,8 @@ enum ImPlotStyleVar_ { ImPlotStyleVar_MousePosPadding, // ImVec2, padding between plot edge and interior info text ImPlotStyleVar_AnnotationPadding, // ImVec2, text padding around annotation labels ImPlotStyleVar_FitPadding, // ImVec2, additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y) - ImPlotStyleVar_PlotDefaultSize, // ImVec2, default size used when ImVec2(0,0) is passed to BeginPlot - ImPlotStyleVar_PlotMinSize, // ImVec2, minimum size plot frame can be when shrunk + ImPlotStyleVar_DigitalPadding, // float, digital plot padding from bottom in pixels + ImPlotStyleVar_DigitalSpacing, // float, digital plot spacing gap in pixels ImPlotStyleVar_COUNT }; @@ -414,7 +436,8 @@ enum ImPlotScale_ { // Marker specifications. enum ImPlotMarker_ { - ImPlotMarker_None = -1, // no marker + ImPlotMarker_None = -2, // no marker + ImPlotMarker_Auto = -1, // automatic marker selection ImPlotMarker_Circle, // a circle marker (default) ImPlotMarker_Square, // a square maker ImPlotMarker_Diamond, // a diamond marker @@ -422,9 +445,11 @@ enum ImPlotMarker_ { ImPlotMarker_Down, // an downward-pointing triangle marker ImPlotMarker_Left, // an leftward-pointing triangle marker ImPlotMarker_Right, // an rightward-pointing triangle marker - ImPlotMarker_Cross, // a cross marker (not fillable) - ImPlotMarker_Plus, // a plus marker (not fillable) - ImPlotMarker_Asterisk, // a asterisk marker (not fillable) + ImPlotMarker_Cross, // a cross marker (not fill-able) + ImPlotMarker_Plus, // a plus marker (not fill-able) + ImPlotMarker_Asterisk, // a asterisk marker (not fill-able) + ImPlotMarker_Vertical, // a vertical line marker (not fill-able) + ImPlotMarker_Horizontal, // a horizontal line marker (not fill-able) ImPlotMarker_COUNT }; @@ -469,6 +494,117 @@ enum ImPlotBin_ { ImPlotBin_Scott = -4, // w = 3.49 * sigma / cbrt(n) }; +// Plot item styling specification. Provide these to PlotX functions to override styling, specify +// offsetting or stride, or set optional flags. This struct can be used in the following ways: +// +// 1. By declaring and defining a struct instance: +// +// ImPlotSpec spec; +// spec.LineColor = ImVec4(1,0,0,1); +// spec.LineWeight = 2.0f; +// spec.Marker = ImPlotMarker_Circle; +// spec.Flags = ImPlotItemFlags_NoLegend | ImPlotLineFlags_Segments; +// ImPlot::PlotLine("MyLine", xs, ys, 100, spec); +// +// 2. Inline using (ImPlotProp,value) pairs (order does NOT matter): +// +// ImPlot::PlotLine("MyLine", xs, ys, 100, { +// ImPlotProp_LineColor, ImVec4(1,0,0,1), +// ImPlotProp_LineWeight, 2.0f, +// ImPlotProp_Marker, ImPlotMarker_Circle, +// ImPlotProp_Flags, ImPlotItemFlags_NoLegend | ImPlotLineFlags_Segments +// }); +struct ImPlotSpec { + ImVec4 LineColor = IMPLOT_AUTO_COL; // line color (applies to lines, bar edges); IMPLOT_AUTO_COL will use next Colormap color or current item color + ImU32* LineColors = nullptr; // array of colors for each line; if nullptr, use LineColor for all lines + float LineWeight = 1.0f; // line weight in pixels (applies to lines, bar edges, marker edges) + ImVec4 FillColor = IMPLOT_AUTO_COL; // fill color (applies to shaded regions, bar faces); IMPLOT_AUTO_COL will use next Colormap color or current item color + ImU32* FillColors = nullptr; // array of colors for each fill; if nullptr, use FillColor for all fills + float FillAlpha = 1.0f; // alpha multiplier (applies to FillColor, FillColors, MarkerFillColor, and MarkerFillColors) + ImPlotMarker Marker = ImPlotMarker_None; // marker type; specify ImPlotMarker_Auto to use the next unused marker + float MarkerSize = 4; // size of markers (radius) *in pixels* + float* MarkerSizes = nullptr; // array of sizes for each marker; if nullptr, use MarkerSize for all markers + ImVec4 MarkerLineColor = IMPLOT_AUTO_COL; // marker edge color; IMPLOT_AUTO_COL will use LineColor + ImU32* MarkerLineColors = nullptr; // array of colors for each marker edge; if nullptr, use MarkerLineColor for all markers + ImVec4 MarkerFillColor = IMPLOT_AUTO_COL; // marker face color; IMPLOT_AUTO_COL will use LineColor + ImU32* MarkerFillColors = nullptr; // array of colors for each marker face; if nullptr, use MarkerFillColor for all markers + float Size = 4; // size of error bar whiskers (width or height), and digital bars (height) *in pixels* + int Offset = 0; // data index offset + int Stride = IMPLOT_AUTO; // data stride in bytes; IMPLOT_AUTO will result in sizeof(T) where T is the type passed to PlotX + ImPlotItemFlags Flags = ImPlotItemFlags_None; // optional item flags; can be composed from common ImPlotItemFlags and/or specialized ImPlotXFlags + + ImPlotSpec() { } + + // Construct a plot item specification from (ImPlotProp,value) pairs in any order, e.g. ImPlotSpec(ImPlotProp_LineColor, my_color, ImPlotProp_Marker, 4.0f) + template + ImPlotSpec(Args... args) { + static_assert((sizeof ...(Args)) % 2 == 0, "Odd number of arguments! You must provide (ImPlotProp, value) pairs!"); + SetProp(args...); + } + + // Set properties from (ImPlotProp,value) pairs in any order, e.g. SetProp(ImPlotProp_LineColor, my_color, ImPlotProp_Marker, 4.0f) + template + void SetProp(ImPlotProp prop, Arg arg, Args... args) { + static_assert((sizeof ...(Args)) % 2 == 0, "Odd number of arguments! You must provide (ImPlotProp,value) pairs!"); + SetProp(prop, arg); + SetProp(args...); + } + + // Set a property from a scalar value. + template + void SetProp(ImPlotProp prop, T v) { + switch (prop) { + case ImPlotProp_LineColor : LineColor = ImGui::ColorConvertU32ToFloat4((ImU32)v); return; + case ImPlotProp_LineWeight : LineWeight = (float)v; return; + case ImPlotProp_FillColor : FillColor = ImGui::ColorConvertU32ToFloat4((ImU32)v); return; + case ImPlotProp_FillAlpha : FillAlpha = (float)v; return; + case ImPlotProp_Marker : Marker = (ImPlotMarker)v; return; + case ImPlotProp_MarkerSize : MarkerSize = (float)v; return; + case ImPlotProp_MarkerLineColor : MarkerLineColor = ImGui::ColorConvertU32ToFloat4((ImU32)v); return; + case ImPlotProp_MarkerFillColor : MarkerFillColor = ImGui::ColorConvertU32ToFloat4((ImU32)v); return; + case ImPlotProp_Size : Size = (float)v; return; + case ImPlotProp_Offset : Offset = (int)v; return; + case ImPlotProp_Stride : Stride = (int)v; return; + case ImPlotProp_Flags : Flags = (ImPlotItemFlags)v; return; + default: break; + } + IM_ASSERT(0 && "User provided an ImPlotProp which cannot be set from scalar value!"); + } + + // Set a property from a pointer value. + void SetProp(ImPlotProp prop, ImU32* v) { + switch (prop) { + case ImPlotProp_LineColors : LineColors = v; return; + case ImPlotProp_FillColors : FillColors = v; return; + case ImPlotProp_MarkerLineColors : MarkerLineColors = v; return; + case ImPlotProp_MarkerFillColors : MarkerFillColors = v; return; + default: break; + } + IM_ASSERT(0 && "User provided an ImPlotProp which cannot be set from pointer value!"); + } + + // Set a property from a float pointer value. + void SetProp(ImPlotProp prop, float* v) { + switch (prop) { + case ImPlotProp_MarkerSizes : MarkerSizes = v; return; + default: break; + } + IM_ASSERT(0 && "User provided an ImPlotProp which cannot be set from float pointer value!"); + } + + // Set a property from an ImVec4 value. + void SetProp(ImPlotProp prop, const ImVec4& v) { + switch (prop) { + case ImPlotProp_LineColor : LineColor = v; return; + case ImPlotProp_FillColor : FillColor = v; return; + case ImPlotProp_MarkerLineColor : MarkerLineColor = v; return; + case ImPlotProp_MarkerFillColor : MarkerFillColor = v; return; + default: break; + } + IM_ASSERT(0 && "User provided an ImPlotProp which cannot be set from ImVec4 value!"); + } +}; + // Double precision version of ImVec2 used by ImPlot. Extensible by end users. IM_MSVC_RUNTIME_CHECKS_OFF struct ImPlotPoint { @@ -503,25 +639,17 @@ struct ImPlotRect { IMPLOT_API bool Contains(const ImPlotPoint& p) const { return Contains(p.x, p.y); } IMPLOT_API bool Contains(double x, double y) const { return X.Contains(x) && Y.Contains(y); } IMPLOT_API ImPlotPoint Size() const { return ImPlotPoint(X.Size(), Y.Size()); } - IMPLOT_API ImPlotPoint Clamp(const ImPlotPoint& p) { return Clamp(p.x, p.y); } - IMPLOT_API ImPlotPoint Clamp(double x, double y) { return ImPlotPoint(X.Clamp(x),Y.Clamp(y)); } + IMPLOT_API ImPlotPoint Clamp(const ImPlotPoint& p) const { return Clamp(p.x, p.y); } + IMPLOT_API ImPlotPoint Clamp(double x, double y) const { return ImPlotPoint(X.Clamp(x),Y.Clamp(y)); } IMPLOT_API ImPlotPoint Min() const { return ImPlotPoint(X.Min, Y.Min); } IMPLOT_API ImPlotPoint Max() const { return ImPlotPoint(X.Max, Y.Max); } }; // Plot style structure struct ImPlotStyle { - // item styling variables - float LineWeight; // = 1, item line weight in pixels - int Marker; // = ImPlotMarker_None, marker specification - float MarkerSize; // = 4, marker size in pixels (roughly the marker's "radius") - float MarkerWeight; // = 1, outline weight of markers in pixels - float FillAlpha; // = 1, alpha modifier applied to plot fills - float ErrorBarSize; // = 5, error bar whisker width in pixels - float ErrorBarWeight; // = 1.5, error bar whisker weight in pixels - float DigitalBitHeight; // = 8, digital channels bit height (at y = 1.0f) in pixels - float DigitalBitGap; // = 4, digital channels bit padding gap in pixels - // plot styling variables + // plot styling + ImVec2 PlotDefaultSize; // = 400,300 default size used when ImVec2(0,0) is passed to BeginPlot + ImVec2 PlotMinSize; // = 200,150 minimum size plot frame can be when shrunk float PlotBorderSize; // = 1, line thickness of border around plot area float MinorAlpha; // = 0.25 alpha multiplier applied to minor axis grid lines ImVec2 MajorTickLen; // = 10,10 major tick lengths for X and Y axes @@ -530,6 +658,7 @@ struct ImPlotStyle { ImVec2 MinorTickSize; // = 1,1 line thickness of minor ticks ImVec2 MajorGridSize; // = 1,1 line thickness of major grid lines ImVec2 MinorGridSize; // = 1,1 line thickness of minor grid lines + // plot padding ImVec2 PlotPadding; // = 10,10 padding between widget frame and plot area, labels, or outside legends (i.e. main padding) ImVec2 LabelPadding; // = 5,5 padding between axes labels, tick labels, and plot edge ImVec2 LegendPadding; // = 10,10 legend padding from plot edges @@ -538,8 +667,8 @@ struct ImPlotStyle { ImVec2 MousePosPadding; // = 10,10 padding between plot edge and interior mouse location text ImVec2 AnnotationPadding; // = 2,2 text padding around annotation labels ImVec2 FitPadding; // = 0,0 additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y) - ImVec2 PlotDefaultSize; // = 400,300 default size used when ImVec2(0,0) is passed to BeginPlot - ImVec2 PlotMinSize; // = 200,150 minimum size plot frame can be when shrunk + float DigitalPadding; // = 20, digital plot padding from bottom in pixels + float DigitalSpacing; // = 4, digital plot spacing gap in pixels // style colors ImVec4 Colors[ImPlotCol_COUNT]; // Array of styling colors. Indexable with ImPlotCol_ enums. // colormap @@ -702,7 +831,7 @@ IMPLOT_API bool BeginSubplots(const char* title_id, float* col_ratios = nullptr); // Only call EndSubplots() if BeginSubplots() returns true! Typically called at the end -// of an if statement conditioned on BeginSublots(). See example above. +// of an if statement conditioned on BeginSubplots(). See example above. IMPLOT_API void EndSubplots(); //----------------------------------------------------------------------------- @@ -740,7 +869,7 @@ IMPLOT_API void SetupAxis(ImAxis axis, const char* label=nullptr, ImPlotAxisFlag IMPLOT_API void SetupAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond = ImPlotCond_Once); // Links an axis range limits to external values. Set to nullptr for no linkage. The pointer data must remain valid until EndPlot. IMPLOT_API void SetupAxisLinks(ImAxis axis, double* link_min, double* link_max); -// Sets the format of numeric axis labels via formater specifier (default="%g"). Formated values will be double (i.e. use %f). +// Sets the format of numeric axis labels via formatter specifier (default="%g"). Formatted values will be double (i.e. use %f). IMPLOT_API void SetupAxisFormat(ImAxis axis, const char* fmt); // Sets the format of numeric axis labels via formatter callback. Given #value, write a label into #buff. Optionally pass user data. IMPLOT_API void SetupAxisFormat(ImAxis axis, ImPlotFormatter formatter, void* data=nullptr); @@ -823,13 +952,13 @@ IMPLOT_API void SetNextAxesToFit(); // // If you need to plot custom or non-homogenous data you have a few options: // -// 1. If your data is a simple struct/class (e.g. Vector2f), you can use striding. +// 1. If your data is a simple struct/class (e.g. Vector2f), you can use striding in your ImPlotSpec. // This is the most performant option if applicable. // // struct Vector2f { float X, Y; }; // ... // Vector2f data[42]; -// ImPlot::PlotLine("line", &data[0].x, &data[0].y, 42, 0, 0, sizeof(Vector2f)); +// ImPlot::PlotLine("line", &data[0].x, &data[0].y, 42, {ImPlotProp_Stride, sizeof(Vector2f}); // // 2. Write a custom getter C function or C++ lambda and pass it and optionally your data to // an ImPlot function post-fixed with a G (e.g. PlotScatterG). This has a slight performance @@ -859,76 +988,83 @@ IMPLOT_API void SetNextAxesToFit(); // if you try plotting extremely large 64-bit integral types. Proceed with caution! // Plots a standard 2D line plot. -IMPLOT_TMP void PlotLine(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotLineFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, int count, ImPlotLineFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_API void PlotLineG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotLineFlags flags=0); +IMPLOT_TMP void PlotLine(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, int count, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_API void PlotLineG(const char* label_id, ImPlotGetter getter, void* data, int count, const ImPlotSpec& spec=ImPlotSpec()); // Plots a standard 2D scatter plot. Default marker is ImPlotMarker_Circle. -IMPLOT_TMP void PlotScatter(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotScatterFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, int count, ImPlotScatterFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_API void PlotScatterG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotScatterFlags flags=0); +IMPLOT_TMP void PlotScatter(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, int count, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_API void PlotScatterG(const char* label_id, ImPlotGetter getter, void* data, int count, const ImPlotSpec& spec=ImPlotSpec()); + +// Plots a bubble graph. #szs are the radius of each bubble in plot units. +IMPLOT_TMP void PlotBubbles(const char* label_id, const T* values, const T* szs, int count, double xscale=1, double xstart=0, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotBubbles(const char* label_id, const T* xs, const T* ys, const T* szs, int count, const ImPlotSpec& spec=ImPlotSpec()); + +// Plots a polygon. Points are specified in counter-clockwise order. If concave, make sure to set the Concave flag. +IMPLOT_TMP void PlotPolygon(const char* label_id, const T* xs, const T* ys, int count, const ImPlotSpec& spec=ImPlotSpec()); // Plots a a stairstep graph. The y value is continued constantly to the right from every x position, i.e. the interval [x[i], x[i+1]) has the value y[i] -IMPLOT_TMP void PlotStairs(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotStairsFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_TMP void PlotStairs(const char* label_id, const T* xs, const T* ys, int count, ImPlotStairsFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_API void PlotStairsG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotStairsFlags flags=0); +IMPLOT_TMP void PlotStairs(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotStairs(const char* label_id, const T* xs, const T* ys, int count, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_API void PlotStairsG(const char* label_id, ImPlotGetter getter, void* data, int count, const ImPlotSpec& spec=ImPlotSpec()); // Plots a shaded (filled) region between two lines, or a line and a horizontal reference. Set yref to +/-INFINITY for infinite fill extents. -IMPLOT_TMP void PlotShaded(const char* label_id, const T* values, int count, double yref=0, double xscale=1, double xstart=0, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys, int count, double yref=0, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys1, const T* ys2, int count, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_API void PlotShadedG(const char* label_id, ImPlotGetter getter1, void* data1, ImPlotGetter getter2, void* data2, int count, ImPlotShadedFlags flags=0); +IMPLOT_TMP void PlotShaded(const char* label_id, const T* values, int count, double yref=0, double xscale=1, double xstart=0, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys, int count, double yref=0, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys1, const T* ys2, int count, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_API void PlotShadedG(const char* label_id, ImPlotGetter getter1, void* data1, ImPlotGetter getter2, void* data2, int count, const ImPlotSpec& spec=ImPlotSpec()); // Plots a bar graph. Vertical by default. #bar_size and #shift are in plot units. -IMPLOT_TMP void PlotBars(const char* label_id, const T* values, int count, double bar_size=0.67, double shift=0, ImPlotBarsFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_TMP void PlotBars(const char* label_id, const T* xs, const T* ys, int count, double bar_size, ImPlotBarsFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_API void PlotBarsG(const char* label_id, ImPlotGetter getter, void* data, int count, double bar_size, ImPlotBarsFlags flags=0); +IMPLOT_TMP void PlotBars(const char* label_id, const T* values, int count, double bar_size=0.67, double shift=0, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotBars(const char* label_id, const T* xs, const T* ys, int count, double bar_size, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_API void PlotBarsG(const char* label_id, ImPlotGetter getter, void* data, int count, double bar_size, const ImPlotSpec& spec=ImPlotSpec()); // Plots a group of bars. #values is a row-major matrix with #item_count rows and #group_count cols. #label_ids should have #item_count elements. -IMPLOT_TMP void PlotBarGroups(const char* const label_ids[], const T* values, int item_count, int group_count, double group_size=0.67, double shift=0, ImPlotBarGroupsFlags flags=0); +IMPLOT_TMP void PlotBarGroups(const char* const label_ids[], const T* values, int item_count, int group_count, double group_size=0.67, double shift=0, const ImPlotSpec& spec=ImPlotSpec()); // Plots vertical error bar. The label_id should be the same as the label_id of the associated line or bar plot. -IMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* err, int count, ImPlotErrorBarsFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* neg, const T* pos, int count, ImPlotErrorBarsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* err, int count, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* neg, const T* pos, int count, const ImPlotSpec& spec=ImPlotSpec()); // Plots stems. Vertical by default. -IMPLOT_TMP void PlotStems(const char* label_id, const T* values, int count, double ref=0, double scale=1, double start=0, ImPlotStemsFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_TMP void PlotStems(const char* label_id, const T* xs, const T* ys, int count, double ref=0, ImPlotStemsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotStems(const char* label_id, const T* values, int count, double ref=0, double scale=1, double start=0, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotStems(const char* label_id, const T* xs, const T* ys, int count, double ref=0, const ImPlotSpec& spec=ImPlotSpec()); // Plots infinite vertical or horizontal lines (e.g. for references or asymptotes). -IMPLOT_TMP void PlotInfLines(const char* label_id, const T* values, int count, ImPlotInfLinesFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotInfLines(const char* label_id, const T* values, int count, const ImPlotSpec& spec=ImPlotSpec()); // Plots a pie chart. Center and radius are in plot units. #label_fmt can be set to nullptr for no labels. -IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, ImPlotFormatter fmt, void* fmt_data=nullptr, double angle0=90, ImPlotPieChartFlags flags=0); -IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* label_fmt="%.1f", double angle0=90, ImPlotPieChartFlags flags=0); +IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, ImPlotFormatter fmt, void* fmt_data=nullptr, double angle0=90, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* label_fmt="%.1f", double angle0=90, const ImPlotSpec& spec=ImPlotSpec()); // Plots a 2D heatmap chart. Values are expected to be in row-major order by default. Leave #scale_min and scale_max both at 0 for automatic color scaling, or set them to a predefined range. #label_fmt can be set to nullptr for no labels. -IMPLOT_TMP void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, double scale_min=0, double scale_max=0, const char* label_fmt="%.1f", const ImPlotPoint& bounds_min=ImPlotPoint(0,0), const ImPlotPoint& bounds_max=ImPlotPoint(1,1), ImPlotHeatmapFlags flags=0); +IMPLOT_TMP void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, double scale_min=0, double scale_max=0, const char* label_fmt="%.1f", const ImPlotPoint& bounds_min=ImPlotPoint(0,0), const ImPlotPoint& bounds_max=ImPlotPoint(1,1), const ImPlotSpec& spec=ImPlotSpec()); // Plots a horizontal histogram. #bins can be a positive integer or an ImPlotBin_ method. If #range is left unspecified, the min/max of #values will be used as the range. // Otherwise, outlier values outside of the range are not binned. The largest bin count or density is returned. -IMPLOT_TMP double PlotHistogram(const char* label_id, const T* values, int count, int bins=ImPlotBin_Sturges, double bar_scale=1.0, ImPlotRange range=ImPlotRange(), ImPlotHistogramFlags flags=0); +IMPLOT_TMP double PlotHistogram(const char* label_id, const T* values, int count, int bins=ImPlotBin_Sturges, double bar_scale=1.0, ImPlotRange range=ImPlotRange(), const ImPlotSpec& spec=ImPlotSpec()); // Plots two dimensional, bivariate histogram as a heatmap. #x_bins and #y_bins can be a positive integer or an ImPlotBin. If #range is left unspecified, the min/max of // #xs an #ys will be used as the ranges. Otherwise, outlier values outside of range are not binned. The largest bin count or density is returned. -IMPLOT_TMP double PlotHistogram2D(const char* label_id, const T* xs, const T* ys, int count, int x_bins=ImPlotBin_Sturges, int y_bins=ImPlotBin_Sturges, ImPlotRect range=ImPlotRect(), ImPlotHistogramFlags flags=0); +IMPLOT_TMP double PlotHistogram2D(const char* label_id, const T* xs, const T* ys, int count, int x_bins=ImPlotBin_Sturges, int y_bins=ImPlotBin_Sturges, ImPlotRect range=ImPlotRect(), const ImPlotSpec& spec=ImPlotSpec()); // Plots digital data. Digital plots do not respond to y drag or zoom, and are always referenced to the bottom of the plot. -IMPLOT_TMP void PlotDigital(const char* label_id, const T* xs, const T* ys, int count, ImPlotDigitalFlags flags=0, int offset=0, int stride=sizeof(T)); -IMPLOT_API void PlotDigitalG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotDigitalFlags flags=0); +IMPLOT_TMP void PlotDigital(const char* label_id, const T* xs, const T* ys, int count, const ImPlotSpec& spec=ImPlotSpec()); +IMPLOT_API void PlotDigitalG(const char* label_id, ImPlotGetter getter, void* data, int count, const ImPlotSpec& spec=ImPlotSpec()); // Plots an axis-aligned image. #bounds_min/bounds_max are in plot coordinates (y-up) and #uv0/uv1 are in texture coordinates (y-down). #ifdef IMGUI_HAS_TEXTURES -IMPLOT_API void PlotImage(const char* label_id, ImTextureRef tex_ref, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), ImPlotImageFlags flags = 0); +IMPLOT_API void PlotImage(const char* label_id, ImTextureRef tex_ref, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImPlotSpec& spec=ImPlotSpec()); #else -IMPLOT_API void PlotImage(const char* label_id, ImTextureID tex_ref, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, const ImVec2& uv0=ImVec2(0,0), const ImVec2& uv1=ImVec2(1,1), const ImVec4& tint_col=ImVec4(1,1,1,1), ImPlotImageFlags flags=0); +IMPLOT_API void PlotImage(const char* label_id, ImTextureID tex_ref, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, const ImVec2& uv0=ImVec2(0,0), const ImVec2& uv1=ImVec2(1,1), const ImVec4& tint_col=ImVec4(1,1,1,1), const ImPlotSpec& spec=ImPlotSpec()); #endif // Plots a centered text label at point x,y with an optional pixel offset. Text color can be changed with ImPlot::PushStyleColor(ImPlotCol_InlayText, ...). -IMPLOT_API void PlotText(const char* text, double x, double y, const ImVec2& pix_offset=ImVec2(0,0), ImPlotTextFlags flags=0); +IMPLOT_API void PlotText(const char* text, double x, double y, const ImVec2& pix_offset=ImVec2(0,0), const ImPlotSpec& spec=ImPlotSpec()); // Plots a dummy item (i.e. adds a legend entry colored by ImPlotCol_Line) -IMPLOT_API void PlotDummy(const char* label_id, ImPlotDummyFlags flags=0); +IMPLOT_API void PlotDummy(const char* label_id, const ImPlotSpec& spec=ImPlotSpec()); //----------------------------------------------------------------------------- // [SECTION] Plot Tools @@ -1058,35 +1194,18 @@ IMPLOT_API void EndDragDropSource(); //----------------------------------------------------------------------------- // [SECTION] Styling //----------------------------------------------------------------------------- - + // Styling colors in ImPlot works similarly to styling colors in ImGui, but // with one important difference. Like ImGui, all style colors are stored in an // indexable array in ImPlotStyle. You can permanently modify these values through // GetStyle().Colors, or temporarily modify them with Push/Pop functions below. // However, by default all style colors in ImPlot default to a special color -// IMPLOT_AUTO_COL. The behavior of this color depends upon the style color to -// which it as applied: -// -// 1) For style colors associated with plot items (e.g. ImPlotCol_Line), -// IMPLOT_AUTO_COL tells ImPlot to color the item with the next unused -// color in the current colormap. Thus, every item will have a different -// color up to the number of colors in the colormap, at which point the -// colormap will roll over. For most use cases, you should not need to -// set these style colors to anything but IMPLOT_COL_AUTO; you are -// probably better off changing the current colormap. However, if you -// need to explicitly color a particular item you may either Push/Pop -// the style color around the item in question, or use the SetNextXXXStyle -// API below. If you permanently set one of these style colors to a specific -// color, or forget to call Pop, then all subsequent items will be styled -// with the color you set. -// -// 2) For style colors associated with plot styling (e.g. ImPlotCol_PlotBg), -// IMPLOT_AUTO_COL tells ImPlot to set that color from color data in your -// **ImGuiStyle**. The ImGuiCol_ that these style colors default to are -// detailed above, and in general have been mapped to produce plots visually -// consistent with your current ImGui style. Of course, you are free to -// manually set these colors to whatever you like, and further can Push/Pop -// them around individual plots for plot-specific styling (e.g. coloring axes). +// IMPLOT_AUTO_COL. IMPLOT_AUTO_COL tells ImPlot to set that color from color data +// in your **ImGuiStyle**. The ImGuiCol_ that these style colors default to are +// detailed above, and in general have been mapped to produce plots visually +// consistent with your current ImGui style. Of course, you are free to +// manually set these colors to whatever you like, and further can Push/Pop +// them around individual plots for plot-specific styling (e.g. coloring axes). // Provides access to plot style structure for permanent modifications to colors, sizes, etc. IMPLOT_API ImPlotStyle& GetStyle(); @@ -1119,20 +1238,6 @@ IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, const ImVec2& val); // Undo temporary style variable modification(s). Undo multiple pushes at once by increasing count. IMPLOT_API void PopStyleVar(int count = 1); -// The following can be used to modify the style of the next plot item ONLY. They do -// NOT require calls to PopStyleX. Leave style attributes you don't want modified to -// IMPLOT_AUTO or IMPLOT_AUTO_COL. Automatic styles will be deduced from the current -// values in your ImPlotStyle or from Colormap data. - -// Set the line color and weight for the next item only. -IMPLOT_API void SetNextLineStyle(const ImVec4& col = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO); -// Set the fill color for the next item only. -IMPLOT_API void SetNextFillStyle(const ImVec4& col = IMPLOT_AUTO_COL, float alpha_mod = IMPLOT_AUTO); -// Set the marker style for the next item only. -IMPLOT_API void SetNextMarkerStyle(ImPlotMarker marker = IMPLOT_AUTO, float size = IMPLOT_AUTO, const ImVec4& fill = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO, const ImVec4& outline = IMPLOT_AUTO_COL); -// Set the error bar style for the next item only. -IMPLOT_API void SetNextErrorBarStyle(const ImVec4& col = IMPLOT_AUTO_COL, float size = IMPLOT_AUTO, float weight = IMPLOT_AUTO); - // Gets the last item primary color (i.e. its legend icon color) IMPLOT_API ImVec4 GetLastItemColor(); @@ -1141,6 +1246,9 @@ IMPLOT_API const char* GetStyleColorName(ImPlotCol idx); // Returns the null terminated string name for an ImPlotMarker. IMPLOT_API const char* GetMarkerName(ImPlotMarker idx); +// Returns the next marker and advances the marker for the current plot. You need to call this between Begin/EndPlot! +IMPLOT_API ImPlotMarker NextMarker(); + //----------------------------------------------------------------------------- // [SECTION] Colormaps //----------------------------------------------------------------------------- @@ -1282,25 +1390,17 @@ IMPLOT_API void ShowDemoWindow(bool* p_open = nullptr); #define IMPLOT_DEPRECATED(method) method #endif -enum ImPlotFlagsObsolete_ { - ImPlotFlags_YAxis2 = 1 << 20, - ImPlotFlags_YAxis3 = 1 << 21, -}; - namespace ImPlot { -// OBSOLETED in v0.13 -> PLANNED REMOVAL in v1.0 -IMPLOT_DEPRECATED( IMPLOT_API bool BeginPlot(const char* title_id, - const char* x_label, // = nullptr, - const char* y_label, // = nullptr, - const ImVec2& size = ImVec2(-1,0), - ImPlotFlags flags = ImPlotFlags_None, - ImPlotAxisFlags x_flags = 0, - ImPlotAxisFlags y_flags = 0, - ImPlotAxisFlags y2_flags = ImPlotAxisFlags_AuxDefault, - ImPlotAxisFlags y3_flags = ImPlotAxisFlags_AuxDefault, - const char* y2_label = nullptr, - const char* y3_label = nullptr) ); +// OBSOLETED in v1.0 (from February 2026) +// IMPLOT_API void SetNextLineStyle(const ImVec4& col = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO); // OBSOLETED IN v1.0 // Set ImPlotSpec.LineColor/LineWeight or construct ImPlotSpec with { ImPlotSpec_LineColor, color, ImPlotSpec_LineWeight, weight }. + +// IMPLOT_API void SetNextFillStyle(const ImVec4& col = IMPLOT_AUTO_COL, float alpha_mod = IMPLOT_AUTO);// OBSOLETED IN v1.0 // Set ImPlotSpec.FillColor/FillAlpha or construct ImPlotSpec with { ImPlotSpec_FillColor, color, ImPlotSpec_FillAlpha, alpha }. + +// IMPLOT_API void SetNextMarkerStyle(ImPlotMarker marker = IMPLOT_AUTO, float size = IMPLOT_AUTO, const ImVec4& fill = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO, const ImVec4& outline = IMPLOT_AUTO_COL); // OBSOLETED IN v1.0 // Set ImPlotSpec.Marker/MarkerSize/MarkerFillColor/LineWeight/MarkerLineColor or construct ImPlotSpec with { ImPlotSpec_Marker, marker, ImPlotSpec_MarkerSize, size, ImPlotSpec_MarkerFillColor, fill_color, ImPlotSpec_LineWeight, weight, ImPlotSpec_MarkerLineColor, outline }. + +// IMPLOT_API void SetNextErrorBarStyle(const ImVec4& col = IMPLOT_AUTO_COL, float size = IMPLOT_AUTO, float weight = IMPLOT_AUTO); // OBSOLETED IN v1.0 // Set ImPlotSpec.LineColor/Size/LineWeight or construct ImPlotSpec with { ImPlotSpec_LineColor, col, ImPlotSpec_Size, size, ImPlotSpec_LineWeight, weight }. + } // namespace ImPlot diff --git a/extensions/ImGui/src/ImGui/implot/implot_demo.cpp b/extensions/ImGui/src/ImGui/implot/implot_demo.cpp index 990b1a269748..97b06567fbb8 100644 --- a/extensions/ImGui/src/ImGui/implot/implot_demo.cpp +++ b/extensions/ImGui/src/ImGui/implot/implot_demo.cpp @@ -1,7 +1,7 @@ // MIT License // Copyright (c) 2020-2024 Evan Pezent -// Copyright (c) 2025 Breno Cunha Queiroz +// Copyright (c) 2025-2026 Breno Cunha Queiroz // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// ImPlot v0.17 +// ImPlot v1.1 WIP // We define this so that the demo does not accidentally use deprecated API #ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS @@ -35,6 +35,12 @@ #include #include +// Clang warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#endif + #ifdef _MSC_VER #define sprintf sprintf_s #endif @@ -45,6 +51,14 @@ #define CHECKBOX_FLAG(flags, flag) ImGui::CheckboxFlags(#flag, (unsigned int*)&flags, flag) +// Helper to wire demo markers located in code to an interactive browser (e.g. imgui_explorer) +#if IMGUI_VERSION_NUM >= 19263 +namespace ImGui { extern IMGUI_API void DemoMarker(const char* file, int line, const char* section); }; +#define IMGUI_DEMO_MARKER(section) do { ImGui::DemoMarker("implot_demo.cpp", __LINE__, section); } while (0) +#else +#define IMGUI_DEMO_MARKER(section) +#endif + #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Encapsulates examples for customizing ImPlot. @@ -188,6 +202,7 @@ struct HugeTimeData { //----------------------------------------------------------------------------- void Demo_Help() { + IMGUI_DEMO_MARKER("Demo_Help"); ImGui::Text("ABOUT THIS DEMO:"); ImGui::BulletText("Sections below are demonstrating many aspects of the library."); ImGui::BulletText("The \"Tools\" menu above gives access to: Style Editors (ImPlot/ImGui)\n" @@ -261,6 +276,7 @@ void ShowInputMapping() { } void Demo_Config() { + IMGUI_DEMO_MARKER("Config"); ImGui::ShowFontSelector("Font"); ImGui::ShowStyleSelector("ImGui Style"); ImPlot::ShowStyleSelector("ImPlot Style"); @@ -289,6 +305,7 @@ void Demo_Config() { //----------------------------------------------------------------------------- void Demo_LinePlots() { + IMGUI_DEMO_MARKER("Plots/Line Plots"); static float xs1[1001], ys1[1001]; for (int i = 0; i < 1001; ++i) { xs1[i] = i * 0.001f; @@ -302,8 +319,10 @@ void Demo_LinePlots() { if (ImPlot::BeginPlot("Line Plots")) { ImPlot::SetupAxes("x","y"); ImPlot::PlotLine("f(x)", xs1, ys1, 1001); - ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); - ImPlot::PlotLine("g(x)", xs2, ys2, 20,ImPlotLineFlags_Segments); + ImPlot::PlotLine("g(x)", xs2, ys2, 20,{ + ImPlotProp_Marker, ImPlotMarker_Circle, + ImPlotProp_Flags, ImPlotLineFlags_Segments + }); ImPlot::EndPlot(); } } @@ -311,6 +330,7 @@ void Demo_LinePlots() { //----------------------------------------------------------------------------- void Demo_FilledLinePlots() { + IMGUI_DEMO_MARKER("Plots/Filled Line Plots"); static double xs1[101], ys1[101], ys2[101], ys3[101]; srand(0); for (int i = 0; i < 101; ++i) { @@ -347,11 +367,12 @@ void Demo_FilledLinePlots() { ImPlot::SetupAxes("Days","Price"); ImPlot::SetupAxesLimits(0,100,0,500); if (show_fills) { - ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); - ImPlot::PlotShaded("Stock 1", xs1, ys1, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); - ImPlot::PlotShaded("Stock 2", xs1, ys2, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); - ImPlot::PlotShaded("Stock 3", xs1, ys3, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); - ImPlot::PopStyleVar(); + ImPlotSpec spec; + spec.Flags = flags; + spec.FillAlpha = 0.25f; + ImPlot::PlotShaded("Stock 1", xs1, ys1, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, spec); + ImPlot::PlotShaded("Stock 2", xs1, ys2, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, spec); + ImPlot::PlotShaded("Stock 3", xs1, ys3, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, spec); } if (show_lines) { ImPlot::PlotLine("Stock 1", xs1, ys1, 101); @@ -365,6 +386,7 @@ void Demo_FilledLinePlots() { //----------------------------------------------------------------------------- void Demo_ShadedPlots() { + IMGUI_DEMO_MARKER("Plots/Shaded Plots"); static float xs[1001], ys[1001], ys1[1001], ys2[1001], ys3[1001], ys4[1001]; srand(0); for (int i = 0; i < 1001; ++i) { @@ -375,18 +397,16 @@ void Demo_ShadedPlots() { ys3[i] = 0.75f + 0.2f * sinf(25 * xs[i]); ys4[i] = 0.75f + 0.1f * cosf(25 * xs[i]); } - static float alpha = 0.25f; - ImGui::DragFloat("Alpha",&alpha,0.01f,0,1); + static ImPlotSpec spec(ImPlotProp_FillAlpha, 0.25f); + ImGui::DragFloat("Alpha",&spec.FillAlpha,0.01f,0,1); if (ImPlot::BeginPlot("Shaded Plots")) { ImPlot::SetupLegend(ImPlotLocation_NorthWest, ImPlotLegendFlags_Reverse); // reverse legend to match vertical order on plot - ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, alpha); - ImPlot::PlotShaded("Uncertain Data",xs,ys1,ys2,1001); - ImPlot::PlotLine("Uncertain Data", xs, ys, 1001); - ImPlot::PlotShaded("Overlapping",xs,ys3,ys4,1001); - ImPlot::PlotLine("Overlapping",xs,ys3,1001); - ImPlot::PlotLine("Overlapping",xs,ys4,1001); - ImPlot::PopStyleVar(); + ImPlot::PlotShaded("Uncertain Data",xs,ys1,ys2,1001, spec); + ImPlot::PlotLine("Uncertain Data", xs, ys, 1001, spec); + ImPlot::PlotShaded("Overlapping",xs,ys3,ys4,1001, spec); + ImPlot::PlotLine("Overlapping",xs,ys3,1001, spec); + ImPlot::PlotLine("Overlapping",xs,ys4,1001, spec); ImPlot::EndPlot(); } } @@ -394,6 +414,7 @@ void Demo_ShadedPlots() { //----------------------------------------------------------------------------- void Demo_ScatterPlots() { + IMGUI_DEMO_MARKER("Plots/Scatter Plots"); srand(0); static float xs1[100], ys1[100]; for (int i = 0; i < 100; ++i) { @@ -408,10 +429,80 @@ void Demo_ScatterPlots() { if (ImPlot::BeginPlot("Scatter Plot")) { ImPlot::PlotScatter("Data 1", xs1, ys1, 100); - ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); - ImPlot::SetNextMarkerStyle(ImPlotMarker_Square, 6, ImPlot::GetColormapColor(1), IMPLOT_AUTO, ImPlot::GetColormapColor(1)); - ImPlot::PlotScatter("Data 2", xs2, ys2, 50); - ImPlot::PopStyleVar(); + ImPlot::PlotScatter("Data 2", xs2, ys2, 50, { + ImPlotProp_Marker, ImPlotMarker_Square, + ImPlotProp_MarkerSize, 6, + ImPlotProp_LineColor, GetColormapColor(1), + ImPlotProp_FillColor, GetColormapColor(1), + ImPlotProp_FillAlpha, 0.25f + }); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_BubblePlots() { + IMGUI_DEMO_MARKER("Plots/Bubble Plots"); + srand(0); + static float xs[20], ys1[20], ys2[20], szs1[20], szs2[20]; + for (int i = 0; i < 20; ++i) { + xs[i] = i * 0.1f; + ys1[i] = (float)rand() / (float)RAND_MAX; + ys2[i] = (float)rand() / (float)RAND_MAX; + + szs1[i] = 0.02f + 0.08f * ((float)rand() / (float)RAND_MAX); + szs2[i] = 0.02f + 0.08f * ((float)rand() / (float)RAND_MAX); + } + + + if (ImPlot::BeginPlot("Bubble Plot", ImVec2(-1,0), ImPlotFlags_Equal)) { + ImPlot::PlotBubbles("Data 1", xs, ys1, szs1, 20, {ImPlotProp_FillAlpha, 0.5f}); + ImPlot::PlotBubbles("Data 2", xs, ys2, szs2, 20, {ImPlotProp_FillAlpha, 0.5f, ImPlotProp_LineColor, ImVec4(0,0,0,0.0)}); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_PolygonPlots() { + IMGUI_DEMO_MARKER("Plots/Polygon Plots"); + // Triangle (convex) + static float tri_xs[3] = {0.5f, 1.0f, 0.0f}; + static float tri_ys[3] = {1.0f, 0.0f, 0.0f}; + + // Pentagon (convex) + static float pent_xs[5], pent_ys[5]; + for (int i = 0; i < 5; ++i) { + float angle = (float)i * 2.0f * 3.14159f / 5.0f - 3.14159f / 2.0f; + pent_xs[i] = 3.0f + 0.8f * cosf(angle); + pent_ys[i] = 0.5f + 0.8f * sinf(angle); + } + + // Star (concave), counter-clockwise + static float star_xs[10], star_ys[10]; + for (int i = 0; i < 10; ++i) { + float angle = (float)i * 2.0f * 3.14159f / 10.0f - 3.14159f / 2.0f; + float radius = (i % 2 == 0) ? 0.8f : 0.3f; + star_xs[i] = 5.5f + radius * cosf(angle); + star_ys[i] = 0.5f + radius * sinf(angle); + } + + if (ImPlot::BeginPlot("Polygon Plot", ImVec2(-1,0), ImPlotFlags_Equal)) { + ImPlot::PlotPolygon("Triangle", tri_xs, tri_ys, 3, { + ImPlotProp_FillAlpha, 0.5f, + }); + ImPlot::PlotPolygon("Pentagon", pent_xs, pent_ys, 5, { + ImPlotProp_FillAlpha, 0.5f, + ImPlotProp_FillColor, ImVec4(0,1,0,1), + }); + ImPlot::PlotPolygon("Star (Concave)", star_xs, star_ys, 10, { + ImPlotProp_FillAlpha, 0.5f, + ImPlotProp_FillColor, ImVec4(1,1,0,1), + ImPlotProp_Flags, ImPlotPolygonFlags_Concave, + }); + ImPlot::EndPlot(); } } @@ -419,6 +510,7 @@ void Demo_ScatterPlots() { //----------------------------------------------------------------------------- void Demo_StairstepPlots() { + IMGUI_DEMO_MARKER("Plots/Stairstep Plots"); static float ys1[21], ys2[21]; for (int i = 0; i < 21; ++i) { ys1[i] = 0.75f + 0.2f * sinf(10 * i * 0.05f); @@ -429,18 +521,19 @@ void Demo_StairstepPlots() { if (ImPlot::BeginPlot("Stairstep Plot")) { ImPlot::SetupAxes("x","f(x)"); ImPlot::SetupAxesLimits(0,1,0,1); + ImPlot::PlotLine("##1",ys1,21,0.05f, 0, {ImPlotProp_LineColor, ImVec4(0.5f,0.5f,0.5f,1.0f)}); + ImPlot::PlotLine("##2",ys2,21,0.05f, 0, {ImPlotProp_LineColor, ImVec4(0.5f,0.5f,0.5f,1.0f)}); - ImPlot::PushStyleColor(ImPlotCol_Line, ImVec4(0.5f,0.5f,0.5f,1.0f)); - ImPlot::PlotLine("##1",ys1,21,0.05f); - ImPlot::PlotLine("##2",ys2,21,0.05f); - ImPlot::PopStyleColor(); + ImPlotSpec spec; + spec.Flags = flags; + spec.FillAlpha = 0.25f; + spec.Marker = ImPlotMarker_Auto; + ImPlot::PlotStairs("Post Step (default)", ys1, 21, 0.05f, 0, spec); - ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); - ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.25f); - ImPlot::PlotStairs("Post Step (default)", ys1, 21, 0.05f, 0, flags); - ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); - ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.25f); - ImPlot::PlotStairs("Pre Step", ys2, 21, 0.05f, 0, flags|ImPlotStairsFlags_PreStep); + spec.Flags = flags|ImPlotStairsFlags_PreStep; + spec.FillAlpha = 0.25f; + spec.Marker = ImPlotMarker_Auto; + ImPlot::PlotStairs("Pre Step", ys2, 21, 0.05f, 0, spec); ImPlot::EndPlot(); } @@ -449,10 +542,11 @@ void Demo_StairstepPlots() { //----------------------------------------------------------------------------- void Demo_BarPlots() { + IMGUI_DEMO_MARKER("Plots/Bar Plots"); static ImS8 data[10] = {1,2,3,4,5,6,7,8,9,10}; if (ImPlot::BeginPlot("Bar Plot")) { ImPlot::PlotBars("Vertical",data,10,0.7,1); - ImPlot::PlotBars("Horizontal",data,10,0.4,1,ImPlotBarsFlags_Horizontal); + ImPlot::PlotBars("Horizontal",data,10,0.4,1,{ImPlotProp_Flags, ImPlotBarsFlags_Horizontal}); ImPlot::EndPlot(); } } @@ -460,6 +554,7 @@ void Demo_BarPlots() { //----------------------------------------------------------------------------- void Demo_BarGroups() { + IMGUI_DEMO_MARKER("Plots/Bar Groups"); static ImS8 data[30] = {83, 67, 23, 89, 83, 78, 91, 82, 85, 90, // midterm 80, 62, 56, 99, 55, 78, 88, 78, 90, 100, // final 80, 69, 52, 92, 72, 78, 75, 76, 89, 95}; // course @@ -487,12 +582,12 @@ void Demo_BarGroups() { if (horz) { ImPlot::SetupAxes("Score","Student",ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); ImPlot::SetupAxisTicks(ImAxis_Y1,positions, groups, glabels); - ImPlot::PlotBarGroups(ilabels,data,items,groups,size,0,flags|ImPlotBarGroupsFlags_Horizontal); + ImPlot::PlotBarGroups(ilabels,data,items,groups,size,0,{ImPlotProp_Flags, flags|ImPlotBarGroupsFlags_Horizontal}); } else { ImPlot::SetupAxes("Student","Score",ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); ImPlot::SetupAxisTicks(ImAxis_X1,positions, groups, glabels); - ImPlot::PlotBarGroups(ilabels,data,items,groups,size,0,flags); + ImPlot::PlotBarGroups(ilabels,data,items,groups,size,0,{ImPlotProp_Flags, flags}); } ImPlot::EndPlot(); } @@ -501,6 +596,7 @@ void Demo_BarGroups() { //----------------------------------------------------------------------------- void Demo_BarStacks() { + IMGUI_DEMO_MARKER("Plots/Bar Stacks"); static ImPlotColormap Liars = -1; if (Liars == -1) { @@ -537,10 +633,11 @@ void Demo_BarStacks() { ImPlot::SetupLegend(ImPlotLocation_South, ImPlotLegendFlags_Outside|ImPlotLegendFlags_Horizontal); ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_Invert); ImPlot::SetupAxisTicks(ImAxis_Y1,0,19,20,politicians,false); + ImPlotSpec spec; spec.Flags = ImPlotBarGroupsFlags_Stacked|ImPlotBarGroupsFlags_Horizontal; if (diverging) - ImPlot::PlotBarGroups(labels_div,data_div,9,20,0.75,0,ImPlotBarGroupsFlags_Stacked|ImPlotBarGroupsFlags_Horizontal); + ImPlot::PlotBarGroups(labels_div,data_div,9,20,0.75,0,spec); else - ImPlot::PlotBarGroups(labels_reg,data_reg,6,20,0.75,0,ImPlotBarGroupsFlags_Stacked|ImPlotBarGroupsFlags_Horizontal); + ImPlot::PlotBarGroups(labels_reg,data_reg,6,20,0.75,0,spec); ImPlot::EndPlot(); } ImPlot::PopColormap(); @@ -549,6 +646,7 @@ void Demo_BarStacks() { //----------------------------------------------------------------------------- void Demo_ErrorBars() { + IMGUI_DEMO_MARKER("Plots/Error Bars"); static float xs[5] = {1,2,3,4,5}; static float bar[5] = {1,2,5,3,4}; static float lin1[5] = {8,8,9,7,8}; @@ -561,17 +659,22 @@ void Demo_ErrorBars() { if (ImPlot::BeginPlot("##ErrorBars")) { ImPlot::SetupAxesLimits(0, 6, 0, 10); + ImPlot::PlotBars("Bar", xs, bar, 5, 0.5f); ImPlot::PlotErrorBars("Bar", xs, bar, err1, 5); - ImPlot::SetNextErrorBarStyle(ImPlot::GetColormapColor(1), 0); - ImPlot::PlotErrorBars("Line", xs, lin1, err1, err2, 5); - ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); - ImPlot::PlotLine("Line", xs, lin1, 5); - ImPlot::PushStyleColor(ImPlotCol_ErrorBar, ImPlot::GetColormapColor(2)); - ImPlot::PlotErrorBars("Scatter", xs, lin2, err2, 5); - ImPlot::PlotErrorBars("Scatter", xs, lin2, err3, err4, 5, ImPlotErrorBarsFlags_Horizontal); - ImPlot::PopStyleColor(); + + ImPlot::PlotErrorBars("Line", xs, lin1, err1, err2, 5, {ImPlotProp_LineColor, GetColormapColor(1), ImPlotProp_Size, 0}); + ImPlot::PlotLine("Line", xs, lin1, 5, {ImPlotProp_Marker, ImPlotMarker_Square}); + + ImPlotSpec spec; + spec.LineColor = GetColormapColor(2); + spec.Size = 6; + spec.LineWeight = 1.5f; + ImPlot::PlotErrorBars("Scatter", xs, lin2, err2, 5, spec); + spec.Flags = ImPlotErrorBarsFlags_Horizontal; + ImPlot::PlotErrorBars("Scatter", xs, lin2, err3, err4, 5, spec); ImPlot::PlotScatter("Scatter", xs, lin2, 5); + ImPlot::EndPlot(); } } @@ -579,6 +682,7 @@ void Demo_ErrorBars() { //----------------------------------------------------------------------------- void Demo_StemPlots() { + IMGUI_DEMO_MARKER("Plots/Stem Plots"); static double xs[51], ys1[51], ys2[51]; for (int i = 0; i < 51; ++i) { xs[i] = i * 0.02; @@ -589,8 +693,7 @@ void Demo_StemPlots() { ImPlot::SetupAxisLimits(ImAxis_X1,0,1.0); ImPlot::SetupAxisLimits(ImAxis_Y1,0,1.6); ImPlot::PlotStems("Stems 1",xs,ys1,51); - ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); - ImPlot::PlotStems("Stems 2", xs, ys2,51); + ImPlot::PlotStems("Stems 2", xs, ys2,51, 0, {ImPlotProp_Marker, ImPlotMarker_Circle}); ImPlot::EndPlot(); } } @@ -598,11 +701,12 @@ void Demo_StemPlots() { //----------------------------------------------------------------------------- void Demo_InfiniteLines() { + IMGUI_DEMO_MARKER("Plots/Infinite Lines"); static double vals[] = {0.25, 0.5, 0.75}; if (ImPlot::BeginPlot("##Infinite")) { ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoInitialFit,ImPlotAxisFlags_NoInitialFit); ImPlot::PlotInfLines("Vertical",vals,3); - ImPlot::PlotInfLines("Horizontal",vals,3,ImPlotInfLinesFlags_Horizontal); + ImPlot::PlotInfLines("Horizontal",vals,3,{ImPlotProp_Flags, ImPlotInfLinesFlags_Horizontal}); ImPlot::EndPlot(); } } @@ -610,6 +714,7 @@ void Demo_InfiniteLines() { //----------------------------------------------------------------------------- void Demo_PieCharts() { + IMGUI_DEMO_MARKER("Plots/Pie Charts"); static const char* labels1[] = {"Frogs","Hogs","Dogs","Logs"}; static float data1[] = {0.15f, 0.30f, 0.2f, 0.05f}; static ImPlotPieChartFlags flags = 0; @@ -618,11 +723,12 @@ void Demo_PieCharts() { CHECKBOX_FLAG(flags, ImPlotPieChartFlags_Normalize); CHECKBOX_FLAG(flags, ImPlotPieChartFlags_IgnoreHidden); CHECKBOX_FLAG(flags, ImPlotPieChartFlags_Exploding); + CHECKBOX_FLAG(flags, ImPlotPieChartFlags_NoSliceBorder); if (ImPlot::BeginPlot("##Pie1", ImVec2(ImGui::GetTextLineHeight()*16,ImGui::GetTextLineHeight()*16), ImPlotFlags_Equal | ImPlotFlags_NoMouseText)) { ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); ImPlot::SetupAxesLimits(0, 1, 0, 1); - ImPlot::PlotPieChart(labels1, data1, 4, 0.5, 0.5, 0.4, "%.2f", 90, flags); + ImPlot::PlotPieChart(labels1, data1, 4, 0.5, 0.5, 0.4, "%.2f", 90, {ImPlotProp_Flags, flags}); ImPlot::EndPlot(); } @@ -635,7 +741,7 @@ void Demo_PieCharts() { if (ImPlot::BeginPlot("##Pie2", ImVec2(ImGui::GetTextLineHeight()*16,ImGui::GetTextLineHeight()*16), ImPlotFlags_Equal | ImPlotFlags_NoMouseText)) { ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); ImPlot::SetupAxesLimits(0, 1, 0, 1); - ImPlot::PlotPieChart(labels2, data2, 5, 0.5, 0.5, 0.4, "%.0f", 180, flags); + ImPlot::PlotPieChart(labels2, data2, 5, 0.5, 0.5, 0.4, "%.0f", 180, {ImPlotProp_Flags, flags}); ImPlot::EndPlot(); } ImPlot::PopColormap(); @@ -644,6 +750,7 @@ void Demo_PieCharts() { //----------------------------------------------------------------------------- void Demo_Heatmaps() { + IMGUI_DEMO_MARKER("Plots/Heatmaps"); static float values1[7][7] = {{0.8f, 2.4f, 2.5f, 3.9f, 0.0f, 4.0f, 0.0f}, {2.4f, 0.0f, 4.0f, 1.0f, 2.7f, 0.0f, 0.0f}, {1.1f, 2.4f, 0.8f, 4.3f, 1.9f, 4.4f, 0.0f}, @@ -683,7 +790,7 @@ void Demo_Heatmaps() { ImPlot::SetupAxes(nullptr, nullptr, axes_flags, axes_flags); ImPlot::SetupAxisTicks(ImAxis_X1,0 + 1.0/14.0, 1 - 1.0/14.0, 7, xlabels); ImPlot::SetupAxisTicks(ImAxis_Y1,1 - 1.0/14.0, 0 + 1.0/14.0, 7, ylabels); - ImPlot::PlotHeatmap("heat",values1[0],7,7,scale_min,scale_max,"%g",ImPlotPoint(0,0),ImPlotPoint(1,1),hm_flags); + ImPlot::PlotHeatmap("heat",values1[0],7,7,scale_min,scale_max,"%g",ImPlotPoint(0,0),ImPlotPoint(1,1), {ImPlotProp_Flags, hm_flags}); ImPlot::EndPlot(); } ImGui::SameLine(); @@ -711,6 +818,7 @@ void Demo_Heatmaps() { //----------------------------------------------------------------------------- void Demo_Histogram() { + IMGUI_DEMO_MARKER("Plots/Histogram"); static ImPlotHistogramFlags hist_flags = ImPlotHistogramFlags_Density; static int bins = 50; static double mu = 5; @@ -761,8 +869,10 @@ void Demo_Histogram() { if (ImPlot::BeginPlot("##Histograms")) { ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); - ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL,0.5f); - ImPlot::PlotHistogram("Empirical", dist.Data, 10000, bins, 1.0, range ? ImPlotRange(rmin,rmax) : ImPlotRange(), hist_flags); + ImPlot::PlotHistogram("Empirical", dist.Data, 10000, bins, 1.0, range ? ImPlotRange(rmin,rmax) : ImPlotRange(), { + ImPlotProp_FillAlpha, 0.5f, + ImPlotProp_Flags, hist_flags + }); if ((hist_flags & ImPlotHistogramFlags_Density) && !(hist_flags & ImPlotHistogramFlags_NoOutliers)) { if (hist_flags & ImPlotHistogramFlags_Horizontal) ImPlot::PlotLine("Theoretical",y,x,100); @@ -776,6 +886,7 @@ void Demo_Histogram() { //----------------------------------------------------------------------------- void Demo_Histogram2D() { + IMGUI_DEMO_MARKER("Plots/Histogram 2D"); static int count = 50000; static int xybins[2] = {100,100}; @@ -794,7 +905,7 @@ void Demo_Histogram2D() { if (ImPlot::BeginPlot("##Hist2D",ImVec2(ImGui::GetContentRegionAvail().x-100-ImGui::GetStyle().ItemSpacing.x,0))) { ImPlot::SetupAxes(nullptr, nullptr, flags, flags); ImPlot::SetupAxesLimits(-6,6,-6,6); - max_count = ImPlot::PlotHistogram2D("Hist2D",dist1.Data,dist2.Data,count,xybins[0],xybins[1],ImPlotRect(-6,6,-6,6), hist_flags); + max_count = ImPlot::PlotHistogram2D("Hist2D",dist1.Data,dist2.Data,count,xybins[0],xybins[1],ImPlotRect(-6,6,-6,6), {ImPlotProp_Flags, hist_flags}); ImPlot::EndPlot(); } ImGui::SameLine(); @@ -805,20 +916,23 @@ void Demo_Histogram2D() { //----------------------------------------------------------------------------- void Demo_DigitalPlots() { + IMGUI_DEMO_MARKER("Plots/Digital Plots"); ImGui::BulletText("Digital plots do not respond to Y drag and zoom, so that"); ImGui::Indent(); ImGui::Text("you can drag analog plots over the rising/falling digital edge."); ImGui::Unindent(); static bool paused = false; - static ScrollingBuffer dataDigital[2]; + static ScrollingBuffer dataDigital[3]; static ScrollingBuffer dataAnalog[2]; - static bool showDigital[2] = {true, false}; + static bool showDigital[3] = {true, false, false}; static bool showAnalog[2] = {true, false}; char label[32]; + ImGui::Checkbox("Pause", &paused); ImGui::Checkbox("digital_0", &showDigital[0]); ImGui::SameLine(); ImGui::Checkbox("digital_1", &showDigital[1]); ImGui::SameLine(); + ImGui::Checkbox("digital_2", &showDigital[2]); ImGui::SameLine(); ImGui::Checkbox("analog_0", &showAnalog[0]); ImGui::SameLine(); ImGui::Checkbox("analog_1", &showAnalog[1]); @@ -832,6 +946,8 @@ void Demo_DigitalPlots() { dataDigital[0].AddPoint(t, sinf(2*t) > 0.45); if (showDigital[1]) dataDigital[1].AddPoint(t, sinf(2*t) < 0.45); + if (showDigital[2]) + dataDigital[2].AddPoint(t, sinf(50*t) > 0.5); // Analog signal values if (showAnalog[0]) dataAnalog[0].AddPoint(t, sinf(2*t)); @@ -842,17 +958,25 @@ void Demo_DigitalPlots() { if (ImPlot::BeginPlot("##Digital")) { ImPlot::SetupAxisLimits(ImAxis_X1, t - 10.0, t, paused ? ImGuiCond_Once : ImGuiCond_Always); ImPlot::SetupAxisLimits(ImAxis_Y1, -1, 1); - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < 3; ++i) { if (showDigital[i] && dataDigital[i].Data.size() > 0) { snprintf(label, sizeof(label), "digital_%d", i); - ImPlot::PlotDigital(label, &dataDigital[i].Data[0].x, &dataDigital[i].Data[0].y, dataDigital[i].Data.size(), 0, dataDigital[i].Offset, 2 * sizeof(float)); + ImPlot::PlotDigital(label, &dataDigital[i].Data[0].x, &dataDigital[i].Data[0].y, dataDigital[i].Data.size(), { + ImPlotProp_Offset, dataDigital[i].Offset, + ImPlotProp_Stride, 2 * sizeof(float), + ImPlotProp_Size, (i+1) * 4 + }); } } for (int i = 0; i < 2; ++i) { if (showAnalog[i]) { snprintf(label, sizeof(label), "analog_%d", i); - if (dataAnalog[i].Data.size() > 0) - ImPlot::PlotLine(label, &dataAnalog[i].Data[0].x, &dataAnalog[i].Data[0].y, dataAnalog[i].Data.size(), 0, dataAnalog[i].Offset, 2 * sizeof(float)); + if (dataAnalog[i].Data.size() > 0) { + ImPlot::PlotLine(label, &dataAnalog[i].Data[0].x, &dataAnalog[i].Data[0].y, dataAnalog[i].Data.size(), { + ImPlotProp_Offset, dataAnalog[i].Offset, + ImPlotProp_Stride, 2 * sizeof(float) + }); + } } } ImPlot::EndPlot(); @@ -862,6 +986,7 @@ void Demo_DigitalPlots() { //----------------------------------------------------------------------------- void Demo_Images() { + IMGUI_DEMO_MARKER("Plots/Images"); ImGui::BulletText("Below we are displaying the font texture, which is the only texture we have\naccess to in this demo."); ImGui::BulletText("Use the 'ImTextureID' type as storage to pass pointers or identifiers to your\nown texture data."); ImGui::BulletText("See ImGui Wiki page 'Image Loading and Displaying Examples'."); @@ -891,6 +1016,7 @@ void Demo_Images() { //----------------------------------------------------------------------------- void Demo_RealtimePlots() { + IMGUI_DEMO_MARKER("Plots/Realtime Plots"); ImGui::BulletText("Move your mouse to change the data!"); static ScrollingBuffer sdata1, sdata2; static RollingBuffer rdata1, rdata2; @@ -918,17 +1044,25 @@ void Demo_RealtimePlots() { ImPlot::SetupAxes(nullptr, nullptr, flags, flags); ImPlot::SetupAxisLimits(ImAxis_X1,t - history, t, ImGuiCond_Always); ImPlot::SetupAxisLimits(ImAxis_Y1,0,1); - ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL,0.5f); - ImPlot::PlotShaded("Mouse X", &sdata1.Data[0].x, &sdata1.Data[0].y, sdata1.Data.size(), -INFINITY, 0, sdata1.Offset, 2 * sizeof(float)); - ImPlot::PlotLine("Mouse Y", &sdata2.Data[0].x, &sdata2.Data[0].y, sdata2.Data.size(), 0, sdata2.Offset, 2*sizeof(float)); + ImPlotSpec spec; + spec.Offset = sdata1.Offset; + spec.Stride = 2 * sizeof(float); + spec.FillAlpha = 0.5f; + ImPlot::PlotShaded("Mouse X", &sdata1.Data[0].x, &sdata1.Data[0].y, sdata1.Data.size(), -INFINITY, spec); + spec.Offset = sdata2.Offset; + spec.Stride = 2 * sizeof(float); + ImPlot::PlotLine("Mouse Y", &sdata2.Data[0].x, &sdata2.Data[0].y, sdata2.Data.size(), spec); ImPlot::EndPlot(); } if (ImPlot::BeginPlot("##Rolling", ImVec2(-1,ImGui::GetTextLineHeight()*10))) { ImPlot::SetupAxes(nullptr, nullptr, flags, flags); ImPlot::SetupAxisLimits(ImAxis_X1,0,history, ImGuiCond_Always); ImPlot::SetupAxisLimits(ImAxis_Y1,0,1); - ImPlot::PlotLine("Mouse X", &rdata1.Data[0].x, &rdata1.Data[0].y, rdata1.Data.size(), 0, 0, 2 * sizeof(float)); - ImPlot::PlotLine("Mouse Y", &rdata2.Data[0].x, &rdata2.Data[0].y, rdata2.Data.size(), 0, 0, 2 * sizeof(float)); + ImPlotSpec spec; + spec.Offset = 0; + spec.Stride = 2 * sizeof(float); + ImPlot::PlotLine("Mouse X", &rdata1.Data[0].x, &rdata1.Data[0].y, rdata1.Data.size(), spec); + ImPlot::PlotLine("Mouse Y", &rdata2.Data[0].x, &rdata2.Data[0].y, rdata2.Data.size(), spec); ImPlot::EndPlot(); } } @@ -936,15 +1070,15 @@ void Demo_RealtimePlots() { //----------------------------------------------------------------------------- void Demo_MarkersAndText() { - static float mk_size = ImPlot::GetStyle().MarkerSize; - static float mk_weight = ImPlot::GetStyle().MarkerWeight; - ImGui::DragFloat("Marker Size",&mk_size,0.1f,2.0f,10.0f,"%.2f px"); - ImGui::DragFloat("Marker Weight", &mk_weight,0.05f,0.5f,3.0f,"%.2f px"); + IMGUI_DEMO_MARKER("Plots/Markers and Text"); + static ImPlotSpec spec(ImPlotProp_Marker, ImPlotMarker_Auto); + ImGui::DragFloat("Marker Size",&spec.MarkerSize,0.1f,2.0f,10.0f,"%.2f px"); + ImGui::DragFloat("Marker Weight", &spec.LineWeight,0.05f,0.5f,3.0f,"%.2f px"); if (ImPlot::BeginPlot("##MarkerStyles", ImVec2(-1,0), ImPlotFlags_CanvasOnly)) { ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); - ImPlot::SetupAxesLimits(0, 10, 0, 12); + ImPlot::SetupAxesLimits(0, 10, -2, 12); ImS8 xs[2] = {1,4}; ImS8 ys[2] = {10,11}; @@ -952,8 +1086,8 @@ void Demo_MarkersAndText() { // filled markers for (int m = 0; m < ImPlotMarker_COUNT; ++m) { ImGui::PushID(m); - ImPlot::SetNextMarkerStyle(m, mk_size, IMPLOT_AUTO_COL, mk_weight); - ImPlot::PlotLine("##Filled", xs, ys, 2); + spec.FillAlpha = 1.0f; + ImPlot::PlotLine("##Filled", xs, ys, 2, spec); ImGui::PopID(); ys[0]--; ys[1]--; } @@ -961,17 +1095,17 @@ void Demo_MarkersAndText() { // open markers for (int m = 0; m < ImPlotMarker_COUNT; ++m) { ImGui::PushID(m); - ImPlot::SetNextMarkerStyle(m, mk_size, ImVec4(0,0,0,0), mk_weight); - ImPlot::PlotLine("##Open", xs, ys, 2); + spec.FillAlpha = 0.0f; + ImPlot::PlotLine("##Open", xs, ys, 2, spec); ImGui::PopID(); ys[0]--; ys[1]--; } - ImPlot::PlotText("Filled Markers", 2.5f, 6.0f); - ImPlot::PlotText("Open Markers", 7.5f, 6.0f); + ImPlot::PlotText("Filled Markers", 2.5f, 5.0f); + ImPlot::PlotText("Open Markers", 7.5f, 5.0f); ImPlot::PushStyleColor(ImPlotCol_InlayText, ImVec4(1,0,1,1)); - ImPlot::PlotText("Vertical Text", 5.0f, 6.0f, ImVec2(0,0), ImPlotTextFlags_Vertical); + ImPlot::PlotText("Vertical Text", 5.0f, 5.0f, ImVec2(0,0), {ImPlotProp_Flags, ImPlotTextFlags_Vertical}); ImPlot::PopStyleColor(); ImPlot::EndPlot(); @@ -981,6 +1115,7 @@ void Demo_MarkersAndText() { //----------------------------------------------------------------------------- void Demo_NaNValues() { + IMGUI_DEMO_MARKER("Plots/NaN Values"); static bool include_nan = true; static ImPlotLineFlags flags = 0; @@ -996,8 +1131,10 @@ void Demo_NaNValues() { ImGui::CheckboxFlags("Skip NaN", (unsigned int*)&flags, ImPlotLineFlags_SkipNaN); if (ImPlot::BeginPlot("##NaNValues")) { - ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); - ImPlot::PlotLine("line", data1, data2, 5, flags); + ImPlot::PlotLine("line", data1, data2, 5, { + ImPlotProp_Flags, flags, + ImPlotProp_Marker, ImPlotMarker_Square + }); ImPlot::PlotBars("bars", data1, 5); ImPlot::EndPlot(); } @@ -1005,7 +1142,329 @@ void Demo_NaNValues() { //----------------------------------------------------------------------------- +void Demo_PerIndexColors() { + // Colorful Lines + static float xs1[1001], ys1[1001]; + static ImU32 colors1[1001]; + for (int i = 0; i < 1001; ++i) { + xs1[i] = i * 0.001f; + ys1[i] = 0.5f + 0.5f * sinf(50 * (xs1[i] + (float)ImGui::GetTime() / 10)); + // Rainbow colors for f(x) + float hue = (float)i / 1000.0f; + colors1[i] = ImColor::HSV(hue, 0.8f, 0.9f); + } + static double xs2[20], ys2[20]; + static ImU32 colors2[20]; + for (int i = 0; i < 20; ++i) { + xs2[i] = i * 1/19.0f; + ys2[i] = xs2[i] * xs2[i]; + // Colormap colors for g(x) + float t = i / 19.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Viridis); + colors2[i] = ImGui::GetColorU32(color); + } + if (ImPlot::BeginPlot("Colorful Lines")) { + ImPlot::SetupAxes("x","y"); + ImPlot::PlotLine("f(x)", xs1, ys1, 1001, { + ImPlotProp_LineColors, colors1 + }); + ImPlot::PlotLine("g(x)", xs2, ys2, 20, { + ImPlotProp_Marker, ImPlotMarker_Circle, + ImPlotProp_Flags, ImPlotLineFlags_Segments, + ImPlotProp_LineColors, colors2, + ImPlotProp_MarkerFillColors, colors2, + ImPlotProp_MarkerLineColors, colors2 + }); + ImPlot::EndPlot(); + } + + // Colorful Shaded Plots + static float xs_shaded[1001], ys_shaded[1001], ys1_shaded[1001], ys2_shaded[1001], ys3_shaded[1001], ys4_shaded[1001]; + static ImU32 colors_shaded1[1001], colors_shaded2[1001]; + srand(0); + for (int i = 0; i < 1001; ++i) { + xs_shaded[i] = i * 0.001f; + ys_shaded[i] = 0.25f + 0.25f * sinf(25 * xs_shaded[i]) * sinf(5 * xs_shaded[i]) + RandomRange(-0.01f, 0.01f); + ys1_shaded[i] = ys_shaded[i] + RandomRange(0.1f, 0.12f); + ys2_shaded[i] = ys_shaded[i] - RandomRange(0.1f, 0.12f); + ys3_shaded[i] = 0.75f + 0.2f * sinf(25 * xs_shaded[i]); + ys4_shaded[i] = 0.75f + 0.1f * cosf(25 * xs_shaded[i]); + + // Rainbow colors for Uncertain Data + float hue = i / 1000.0f; + colors_shaded1[i] = ImColor::HSV(hue, 0.8f, 0.9f); + + // Colormap colors for Overlapping + float t = i / 1000.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Viridis); + colors_shaded2[i] = ImGui::GetColorU32(color); + } + static ImPlotSpec spec_shaded(ImPlotProp_FillAlpha, 0.25f); + + if (ImPlot::BeginPlot("Colorful Shaded Plots")) { + ImPlot::SetupLegend(ImPlotLocation_NorthWest, ImPlotLegendFlags_Reverse); + ImPlot::PlotShaded("Uncertain Data", xs_shaded, ys1_shaded, ys2_shaded, 1001, { + ImPlotProp_FillColors, colors_shaded1, + ImPlotProp_FillAlpha, spec_shaded.FillAlpha + }); + ImPlot::PlotLine("Uncertain Data", xs_shaded, ys_shaded, 1001, { + ImPlotProp_LineColors, colors_shaded1 + }); + ImPlot::PlotShaded("Overlapping", xs_shaded, ys3_shaded, ys4_shaded, 1001, { + ImPlotProp_FillColors, colors_shaded2, + ImPlotProp_FillAlpha, spec_shaded.FillAlpha + }); + ImPlot::PlotLine("Overlapping", xs_shaded, ys3_shaded, 1001, { + ImPlotProp_LineColors, colors_shaded2 + }); + ImPlot::PlotLine("Overlapping", xs_shaded, ys4_shaded, 1001, { + ImPlotProp_LineColors, colors_shaded2 + }); + ImPlot::EndPlot(); + } + + // Colorful Scatter + srand(0); + static float xs_scatter1[100], ys_scatter1[100]; + static ImU32 colors_scatter1_fill[100], colors_scatter1_line[100]; + static float sizes_scatter1[100]; + for (int i = 0; i < 100; ++i) { + xs_scatter1[i] = i * 0.01f; + ys_scatter1[i] = xs_scatter1[i] + 0.1f * ((float)rand() / (float)RAND_MAX); + // Rainbow hue colors + float hue = i / 99.0f; + colors_scatter1_fill[i] = ImColor::HSV(hue, 0.8f, 0.9f); + colors_scatter1_line[i] = ImColor::HSV(hue, 0.9f, 0.7f); + // Random sizes between 2 and 6 + sizes_scatter1[i] = 2.0f + 4.0f * ((float)rand() / (float)RAND_MAX); + } + static float xs_scatter2[50], ys_scatter2[50]; + static ImU32 colors_scatter2[50]; + static float sizes_scatter2[50]; + for (int i = 0; i < 50; i++) { + xs_scatter2[i] = 0.25f + 0.2f * ((float)rand() / (float)RAND_MAX); + ys_scatter2[i] = 0.75f + 0.2f * ((float)rand() / (float)RAND_MAX); + // Colormap colors (Viridis) + float t = i / 49.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Viridis); + colors_scatter2[i] = ImGui::GetColorU32(color); + // Random sizes between 2 and 6 + sizes_scatter2[i] = 2.0f + 4.0f * ((float)rand() / (float)RAND_MAX); + } + + if (ImPlot::BeginPlot("Colorful Scatter", ImVec2(-1,0))) { + ImPlot::PlotScatter("Data 1", xs_scatter1, ys_scatter1, 100, { + ImPlotProp_MarkerFillColors, colors_scatter1_fill, + ImPlotProp_MarkerLineColors, colors_scatter1_line, + ImPlotProp_MarkerSizes, sizes_scatter1 + }); + ImPlot::PlotScatter("Data 2", xs_scatter2, ys_scatter2, 50, { + ImPlotProp_Marker, ImPlotMarker_Square, + ImPlotProp_MarkerFillColors, colors_scatter2, + ImPlotProp_MarkerLineColors, colors_scatter2, + ImPlotProp_MarkerSizes, sizes_scatter2, + ImPlotProp_FillAlpha, 0.5f + }); + ImPlot::EndPlot(); + } + + // Colorful Bubbles + srand(0); + static float xs_bubble[20], ys1_bubble[20], ys2_bubble[20], szs1_bubble[20], szs2_bubble[20]; + static ImU32 colors1_bubble[20], colors2_bubble[20]; + for (int i = 0; i < 20; ++i) { + xs_bubble[i] = i * 0.1f; + ys1_bubble[i] = (float)rand() / (float)RAND_MAX; + ys2_bubble[i] = (float)rand() / (float)RAND_MAX; + + szs1_bubble[i] = 0.02f + 0.08f * ((float)rand() / (float)RAND_MAX); + szs2_bubble[i] = 0.02f + 0.08f * ((float)rand() / (float)RAND_MAX); + + // Rainbow colors for Data 1 + float hue = i / 19.0f; + colors1_bubble[i] = ImColor::HSV(hue, 0.8f, 0.9f); + + // Colormap colors for Data 2 + float t = i / 19.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Viridis); + colors2_bubble[i] = ImGui::GetColorU32(color); + } + + if (ImPlot::BeginPlot("Colorful Bubbles", ImVec2(-1,0), ImPlotFlags_Equal)) { + ImPlot::PlotBubbles("Data 1", xs_bubble, ys1_bubble, szs1_bubble, 20, { + ImPlotProp_FillAlpha, 0.5f, + ImPlotProp_FillColors, colors1_bubble, + ImPlotProp_LineColors, colors1_bubble + }); + ImPlot::PlotBubbles("Data 2", xs_bubble, ys2_bubble, szs2_bubble, 20, { + ImPlotProp_FillAlpha, 0.5f, + ImPlotProp_LineColor, ImVec4(0,0,0,0.0), + ImPlotProp_FillColors, colors2_bubble + }); + + ImPlot::EndPlot(); + } + + // Colorful Stairstep + static float ys1_stairs[21], ys2_stairs[21]; + static ImU32 colors1_stairs[21], colors2_stairs[21]; + for (int i = 0; i < 21; ++i) { + ys1_stairs[i] = 0.75f + 0.2f * sinf(10 * i * 0.05f); + ys2_stairs[i] = 0.25f + 0.2f * sinf(10 * i * 0.05f); + + // Rainbow colors for Post Step + float hue = i / 20.0f; + colors1_stairs[i] = ImColor::HSV(hue, 0.8f, 0.9f); + + // Colormap colors for Pre Step + float t = i / 20.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Viridis); + colors2_stairs[i] = ImGui::GetColorU32(color); + } + static ImPlotStairsFlags flags_stairs = 0; + CHECKBOX_FLAG(flags_stairs, ImPlotStairsFlags_Shaded); + + if (ImPlot::BeginPlot("Colorful Stairstep Plot")) { + ImPlot::SetupAxes("x","f(x)"); + ImPlot::SetupAxesLimits(0,1,0,1); + ImPlot::PlotLine("##1", ys1_stairs, 21, 0.05f, 0, { + ImPlotProp_LineColor, ImVec4(0.5f,0.5f,0.5f,1.0f) + }); + ImPlot::PlotLine("##2", ys2_stairs, 21, 0.05f, 0, { + ImPlotProp_LineColor, ImVec4(0.5f,0.5f,0.5f,1.0f) + }); + + ImPlot::PlotStairs("Post Step (default)", ys1_stairs, 21, 0.05f, 0, { + ImPlotProp_Flags, flags_stairs, + ImPlotProp_FillAlpha, 0.25f, + ImPlotProp_Marker, ImPlotMarker_Auto, + ImPlotProp_LineColors, colors1_stairs, + ImPlotProp_FillColors, colors1_stairs, + ImPlotProp_MarkerFillColors, colors1_stairs, + ImPlotProp_MarkerLineColors, colors1_stairs + }); + + ImPlot::PlotStairs("Pre Step", ys2_stairs, 21, 0.05f, 0, { + ImPlotProp_Flags, flags_stairs | ImPlotStairsFlags_PreStep, + ImPlotProp_FillAlpha, 0.25f, + ImPlotProp_Marker, ImPlotMarker_Auto, + ImPlotProp_LineColors, colors2_stairs, + ImPlotProp_FillColors, colors2_stairs, + ImPlotProp_MarkerFillColors, colors2_stairs, + ImPlotProp_MarkerLineColors, colors2_stairs + }); + + ImPlot::EndPlot(); + } + + // Colorful Bar Plots + static ImS8 data_bars[10] = {1,2,3,4,5,6,7,8,9,10}; + static ImU32 colors_bars_v[10], colors_bars_h[10]; + for (int i = 0; i < 10; ++i) { + // Rainbow colors for Vertical + float hue = i / 9.0f; + colors_bars_v[i] = ImColor::HSV(hue, 0.8f, 0.9f); + + // Colormap colors for Horizontal + float t = i / 9.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Viridis); + colors_bars_h[i] = ImGui::GetColorU32(color); + } + + if (ImPlot::BeginPlot("Colorful Bar Plot")) { + ImPlot::PlotBars("Vertical", data_bars, 10, 0.7, 1, { + ImPlotProp_FillColors, colors_bars_v, + ImPlotProp_LineColors, colors_bars_v + }); + ImPlot::PlotBars("Horizontal", data_bars, 10, 0.4, 1, { + ImPlotProp_Flags, ImPlotBarsFlags_Horizontal, + ImPlotProp_FillColors, colors_bars_h, + ImPlotProp_LineColors, colors_bars_h + }); + ImPlot::EndPlot(); + } + + // Colorful Stem Plots + static double xs_stems[51], ys1_stems[51], ys2_stems[51]; + static ImU32 colors1_stems[51], colors2_stems[51]; + for (int i = 0; i < 51; ++i) { + xs_stems[i] = i * 0.02; + ys1_stems[i] = 1.0 + 0.5 * sin(25*xs_stems[i])*cos(2*xs_stems[i]); + ys2_stems[i] = 0.5 + 0.25 * sin(10*xs_stems[i]) * sin(xs_stems[i]); + + // Rainbow colors for Stems 1 + float hue = i / 50.0f; + colors1_stems[i] = ImColor::HSV(hue, 0.8f, 0.9f); + + // Colormap colors for Stems 2 + float t = i / 50.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Viridis); + colors2_stems[i] = ImGui::GetColorU32(color); + } + + if (ImPlot::BeginPlot("Colorful Stem Plots")) { + ImPlot::SetupAxisLimits(ImAxis_X1,0,1.0); + ImPlot::SetupAxisLimits(ImAxis_Y1,0,1.6); + ImPlot::PlotStems("Stems 1", xs_stems, ys1_stems, 51, 0, { + ImPlotProp_LineColors, colors1_stems, + ImPlotProp_MarkerFillColors, colors1_stems, + ImPlotProp_MarkerLineColors, colors1_stems + }); + ImPlot::PlotStems("Stems 2", xs_stems, ys2_stems, 51, 0, { + ImPlotProp_Marker, ImPlotMarker_Circle, + ImPlotProp_LineColors, colors2_stems, + ImPlotProp_MarkerFillColors, colors2_stems, + ImPlotProp_MarkerLineColors, colors2_stems + }); + ImPlot::EndPlot(); + } + + // Colorful Infinite Lines + if (ImPlot::BeginPlot("Colorful Infinite Lines", ImVec2(-1,0))) { + ImPlot::SetupAxes("x","y"); + ImPlot::SetupAxesLimits(0, 10, -1, 10); + + // 1. Constant color infinite lines + static double vals1[5] = {1.0, 2.5, 4.0, 5.5, 7.0}; + ImPlot::PlotInfLines("Const Color", vals1, 5, { + ImPlotProp_LineColor, ImVec4(0.0f, 0.7f, 1.0f, 1.0f), + }); + + // 2. Per-line rainbow colors (horizontal) + static double vals2[8]; + static ImU32 colors_infline_rainbow[8]; + for (int i = 0; i < 8; ++i) { + vals2[i] = 1.0 + i * 1.0; + float t = i / 7.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Jet); + colors_infline_rainbow[i] = ImGui::GetColorU32(color); + } + ImPlot::PlotInfLines("Rainbow Horizontal", vals2, 8, { + ImPlotProp_LineColors, colors_infline_rainbow, + ImPlotProp_Flags, ImPlotInfLinesFlags_Horizontal + }); + + // 3. Per-line colormap colors (vertical) + static double vals3[6]; + static ImU32 colors_infline_viridis[6]; + for (int i = 0; i < 6; ++i) { + vals3[i] = 1.5 + i * 1.5; + float t = i / 5.0f; + ImVec4 color = ImPlot::SampleColormap(t, ImPlotColormap_Plasma); + colors_infline_viridis[i] = ImGui::GetColorU32(color); + } + ImPlot::PlotInfLines("Plasma Vertical", vals3, 6, { + ImPlotProp_LineColors, colors_infline_viridis, + }); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + void Demo_LogScale() { + IMGUI_DEMO_MARKER("Axes/Log Scale"); static double xs[1001], ys1[1001], ys2[1001], ys3[1001]; for (int i = 0; i < 1001; ++i) { xs[i] = i*0.1f; @@ -1027,6 +1486,7 @@ void Demo_LogScale() { //----------------------------------------------------------------------------- void Demo_SymmetricLogScale() { + IMGUI_DEMO_MARKER("Axes/Symmetric Log Scale"); static double xs[1001], ys1[1001], ys2[1001]; for (int i = 0; i < 1001; ++i) { xs[i] = i*0.1f-50; @@ -1044,6 +1504,7 @@ void Demo_SymmetricLogScale() { //----------------------------------------------------------------------------- void Demo_TimeScale() { + IMGUI_DEMO_MARKER("Axes/Time Scale"); static double t_min = 1609459200; // 01/01/2021 @ 12:00:00am (UTC) static double t_max = 1640995200; // 01/01/2022 @ 12:00:00am (UTC) @@ -1079,7 +1540,7 @@ void Demo_TimeScale() { end = end < 0 ? 0 : end > HugeTimeData::Size - 1 ? HugeTimeData::Size - 1 : end; int size = (end - start)/downsample; // plot it - ImPlot::PlotLine("Time Series", &data->Ts[start], &data->Ys[start], size, 0, 0, sizeof(double)*downsample); + ImPlot::PlotLine("Time Series", &data->Ts[start], &data->Ys[start], size, {ImPlotProp_Stride, sizeof(double)*downsample}); } // plot time now double t_now = (double)time(nullptr); @@ -1101,6 +1562,7 @@ static inline double TransformInverse_Sqrt(double v, void*) { } void Demo_CustomScale() { + IMGUI_DEMO_MARKER("Axes/Custom Scale"); static float v[100]; for (int i = 0; i < 100; ++i) { v[i] = i*0.01f; @@ -1118,6 +1580,7 @@ void Demo_CustomScale() { //----------------------------------------------------------------------------- void Demo_MultipleAxes() { + IMGUI_DEMO_MARKER("Axes/Multiple Axes"); static float xs[1001], xs2[1001], ys1[1001], ys2[1001], ys3[1001]; for (int i = 0; i < 1001; ++i) { xs[i] = (i*0.1f); @@ -1176,6 +1639,7 @@ void Demo_MultipleAxes() { //----------------------------------------------------------------------------- void Demo_LinkedAxes() { + IMGUI_DEMO_MARKER("Axes/Linked Axes"); static ImPlotRect lims(0,1,0,1); static bool linkx = true, linky = true; int data[2] = {0,1}; @@ -1205,6 +1669,7 @@ void Demo_LinkedAxes() { //----------------------------------------------------------------------------- void Demo_AxisConstraints() { + IMGUI_DEMO_MARKER("Axes/Axis Constraints"); static float constraints[4] = {-10,10,1,20}; static ImPlotAxisFlags flags; ImGui::DragFloat2("Limits Constraints", &constraints[0], 0.01f); @@ -1224,6 +1689,7 @@ void Demo_AxisConstraints() { //----------------------------------------------------------------------------- void Demo_EqualAxes() { + IMGUI_DEMO_MARKER("Axes/Equal Axes"); ImGui::BulletText("Equal constraint applies to axis pairs (e.g ImAxis_X1/Y1, ImAxis_X2/Y2)"); static double xs1[360], ys1[360]; for (int i = 0; i < 360; ++i) { @@ -1245,6 +1711,7 @@ void Demo_EqualAxes() { //----------------------------------------------------------------------------- void Demo_AutoFittingData() { + IMGUI_DEMO_MARKER("Axes/Auto-Fitting Data"); ImGui::BulletText("The Y-axis has been configured to auto-fit to only the data visible in X-axis range."); ImGui::BulletText("Zoom and pan the X-axis. Disable Stems to see a difference in fit."); ImGui::BulletText("If ImPlotAxisFlags_RangeFit is disabled, the axis will fit ALL data."); @@ -1281,6 +1748,7 @@ ImPlotPoint SinewaveGetter(int i, void* data) { } void Demo_SubplotsSizing() { + IMGUI_DEMO_MARKER("Subplots/Sizing"); static ImPlotSubplotFlags flags = ImPlotSubplotFlags_ShareItems|ImPlotSubplotFlags_NoLegend; ImGui::CheckboxFlags("ImPlotSubplotFlags_NoResize", (unsigned int*)&flags, ImPlotSubplotFlags_NoResize); @@ -1304,12 +1772,13 @@ void Demo_SubplotsSizing() { if (ImPlot::BeginPlot("",ImVec2(),ImPlotFlags_NoLegend)) { ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations); float fi = 0.01f * (i+1); + ImVec4 col = GetColormapColor(0); if (rows*cols > 1) { - ImPlot::SetNextLineStyle(SampleColormap((float)i/(float)(rows*cols-1),ImPlotColormap_Jet)); + col = SampleColormap((float)i/(float)(rows*cols-1),ImPlotColormap_Jet); } char label[16]; snprintf(label, sizeof(label), "data%d", id++); - ImPlot::PlotLineG(label,SinewaveGetter,&fi,1000); + ImPlot::PlotLineG(label,SinewaveGetter,&fi,1000, {ImPlotProp_LineColor, col}); ImPlot::EndPlot(); } } @@ -1320,6 +1789,7 @@ void Demo_SubplotsSizing() { //----------------------------------------------------------------------------- void Demo_SubplotItemSharing() { + IMGUI_DEMO_MARKER("Subplots/Item Sharing"); static ImPlotSubplotFlags flags = ImPlotSubplotFlags_ShareItems; ImGui::CheckboxFlags("ImPlotSubplotFlags_ShareItems", (unsigned int*)&flags, ImPlotSubplotFlags_ShareItems); ImGui::CheckboxFlags("ImPlotSubplotFlags_ColMajor", (unsigned int*)&flags, ImPlotSubplotFlags_ColMajor); @@ -1364,6 +1834,7 @@ void Demo_SubplotItemSharing() { //----------------------------------------------------------------------------- void Demo_SubplotAxisLinking() { + IMGUI_DEMO_MARKER("Subplots/Axis Linking"); static ImPlotSubplotFlags flags = ImPlotSubplotFlags_LinkRows | ImPlotSubplotFlags_LinkCols; ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkRows", (unsigned int*)&flags, ImPlotSubplotFlags_LinkRows); ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkCols", (unsigned int*)&flags, ImPlotSubplotFlags_LinkCols); @@ -1388,6 +1859,7 @@ void Demo_SubplotAxisLinking() { //----------------------------------------------------------------------------- void Demo_LegendOptions() { + IMGUI_DEMO_MARKER("Tools/Legend Options"); static ImPlotLocation loc = ImPlotLocation_East; ImGui::CheckboxFlags("North", (unsigned int*)&loc, ImPlotLocation_North); ImGui::SameLine(); ImGui::CheckboxFlags("South", (unsigned int*)&loc, ImPlotLocation_South); ImGui::SameLine(); @@ -1434,6 +1906,7 @@ void Demo_LegendOptions() { //----------------------------------------------------------------------------- void Demo_DragPoints() { + IMGUI_DEMO_MARKER("Tools/Drag Points"); ImGui::BulletText("Click and drag each point."); static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); @@ -1464,12 +1937,22 @@ void Demo_DragPoints() { B[i] = ImPlotPoint(w1*P[0].x + w2*P[1].x + w3*P[2].x + w4*P[3].x, w1*P[0].y + w2*P[1].y + w3*P[2].y + w4*P[3].y); } - ImPlot::SetNextLineStyle(ImVec4(1,0.5f,1,1),hovered[1]||held[1] ? 2.0f : 1.0f); - ImPlot::PlotLine("##h1",&P[0].x, &P[0].y, 2, 0, 0, sizeof(ImPlotPoint)); - ImPlot::SetNextLineStyle(ImVec4(0,0.5f,1,1), hovered[2]||held[2] ? 2.0f : 1.0f); - ImPlot::PlotLine("##h2",&P[2].x, &P[2].y, 2, 0, 0, sizeof(ImPlotPoint)); - ImPlot::SetNextLineStyle(ImVec4(0,0.9f,0,1), hovered[0]||held[0]||hovered[3]||held[3] ? 3.0f : 2.0f); - ImPlot::PlotLine("##bez",&B[0].x, &B[0].y, 100, 0, 0, sizeof(ImPlotPoint)); + ImPlotSpec spec; + spec.Offset = 0; + spec.Stride = sizeof(ImPlotPoint); + + spec.LineColor = ImVec4(1,0.5f,1,1); + spec.LineWeight = hovered[1]||held[1] ? 2.0f : 1.0f; + ImPlot::PlotLine("##h1",&P[0].x, &P[0].y, 2, spec); + + spec.LineColor = ImVec4(0,0.5f,1,1); + spec.LineWeight = hovered[2]||held[2] ? 2.0f : 1.0f; + ImPlot::PlotLine("##h2",&P[2].x, &P[2].y, 2, spec); + + spec.LineColor = ImVec4(0,0.9f,0,1); + spec.LineWeight = hovered[0]||held[0]||hovered[3]||held[3] ? 3.0f : 2.0f; + ImPlot::PlotLine("##bez",&B[0].x, &B[0].y, 100, spec); + ImPlot::EndPlot(); } } @@ -1477,6 +1960,7 @@ void Demo_DragPoints() { //----------------------------------------------------------------------------- void Demo_DragLines() { + IMGUI_DEMO_MARKER("Tools/Drag Lines"); ImGui::BulletText("Click and drag the horizontal and vertical lines."); static double x1 = 0.2; static double x2 = 0.8; @@ -1502,8 +1986,7 @@ void Demo_DragLines() { ys[i] = (y1+y2)/2+fabs(y2-y1)/2*sin(f*i/10); } ImPlot::DragLineY(120482,&f,ImVec4(1,0.5f,1,1),1,flags, &clicked, &hovered, &held); - ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, hovered||held ? 2.0f : 1.0f); - ImPlot::PlotLine("Interactive Data", xs, ys, 1000); + ImPlot::PlotLine("Interactive Data", xs, ys, 1000, {ImPlotProp_LineWeight, hovered||held ? 2.0f : 1.0f}); ImPlot::EndPlot(); } } @@ -1511,6 +1994,7 @@ void Demo_DragLines() { //----------------------------------------------------------------------------- void Demo_DragRects() { + IMGUI_DEMO_MARKER("Tools/Drag Rects"); static float x_data[512]; static float y_data1[512]; @@ -1587,6 +2071,7 @@ ImPlotPoint FindCentroid(const ImVector& data, const ImPlotRect& bo //----------------------------------------------------------------------------- void Demo_Querying() { + IMGUI_DEMO_MARKER("Tools/Querying"); static ImVector data; static ImVector rects; static ImPlotRect limits, select; @@ -1613,14 +2098,19 @@ void Demo_Querying() { ImPlotPoint pt = ImPlot::GetPlotMousePos(); data.push_back(pt); } - ImPlot::PlotScatter("Points", &data[0].x, &data[0].y, data.size(), 0, 0, 2 * sizeof(double)); + ImPlotSpec spec; + spec.Offset = 0; + spec.Stride = 2 * sizeof(double); + ImPlotSpec cent_spec; + cent_spec.Marker = ImPlotMarker_Square; + cent_spec.MarkerSize = 6; + ImPlot::PlotScatter("Points", &data[0].x, &data[0].y, data.size(), spec); if (ImPlot::IsPlotSelected()) { select = ImPlot::GetPlotSelection(); int cnt; ImPlotPoint centroid = FindCentroid(data,select,cnt); if (cnt > 0) { - ImPlot::SetNextMarkerStyle(ImPlotMarker_Square,6); - ImPlot::PlotScatter("Centroid", ¢roid.x, ¢roid.y, 1); + ImPlot::PlotScatter("Centroid", ¢roid.x, ¢roid.y, 1, cent_spec); } if (ImGui::IsMouseClicked(ImPlot::GetInputMap().SelectCancel)) { CancelPlotSelection(); @@ -1631,8 +2121,7 @@ void Demo_Querying() { int cnt; ImPlotPoint centroid = FindCentroid(data,rects[i],cnt); if (cnt > 0) { - ImPlot::SetNextMarkerStyle(ImPlotMarker_Square,6); - ImPlot::PlotScatter("Centroid", ¢roid.x, ¢roid.y, 1); + ImPlot::PlotScatter("Centroid", ¢roid.x, ¢roid.y, 1, cent_spec); } ImPlot::DragRect(i,&rects[i].X.Min,&rects[i].Y.Min,&rects[i].X.Max,&rects[i].Y.Max,ImVec4(1,0,1,1)); } @@ -1644,6 +2133,7 @@ void Demo_Querying() { //----------------------------------------------------------------------------- void Demo_Annotations() { + IMGUI_DEMO_MARKER("Tools/Annotations"); static bool clamp = false; ImGui::Checkbox("Clamp",&clamp); if (ImPlot::BeginPlot("##Annotations")) { @@ -1671,6 +2161,7 @@ void Demo_Annotations() { //----------------------------------------------------------------------------- void Demo_Tags() { + IMGUI_DEMO_MARKER("Tools/Tags"); static bool show = true; ImGui::Checkbox("Show Tags",&show); if (ImPlot::BeginPlot("##Tags")) { @@ -1693,6 +2184,7 @@ void Demo_Tags() { //----------------------------------------------------------------------------- void Demo_DragAndDrop() { + IMGUI_DEMO_MARKER("Tools/Drag and Drop"); ImGui::BulletText("Drag/drop items from the left column."); ImGui::BulletText("Drag/drop items between plots."); ImGui::Indent(); @@ -1771,8 +2263,11 @@ void Demo_DragAndDrop() { for (int k = 0; k < k_dnd; ++k) { if (dnd[k].Plt == 1 && dnd[k].Data.size() > 0) { ImPlot::SetAxis(dnd[k].Yax); - ImPlot::SetNextLineStyle(dnd[k].Color); - ImPlot::PlotLine(dnd[k].Label, &dnd[k].Data[0].x, &dnd[k].Data[0].y, dnd[k].Data.size(), 0, 0, 2 * sizeof(float)); + ImPlotSpec spec; + spec.Offset = 0; + spec.Stride = 2 * sizeof(float); + spec.LineColor = dnd[k].Color; + ImPlot::PlotLine(dnd[k].Label, &dnd[k].Data[0].x, &dnd[k].Data[0].y, dnd[k].Data.size(), spec); // allow legend item labels to be DND sources if (ImPlot::BeginDragDropSourceItem(dnd[k].Label)) { ImGui::SetDragDropPayload("MY_DND", &k, sizeof(int)); @@ -1816,8 +2311,11 @@ void Demo_DragAndDrop() { ImPlot::PopStyleColor(2); if (dndx != nullptr && dndy != nullptr) { ImVec4 mixed((dndx->Color.x + dndy->Color.x)/2,(dndx->Color.y + dndy->Color.y)/2,(dndx->Color.z + dndy->Color.z)/2,(dndx->Color.w + dndy->Color.w)/2); - ImPlot::SetNextLineStyle(mixed); - ImPlot::PlotLine("##dndxy", &dndx->Data[0].y, &dndy->Data[0].y, dndx->Data.size(), 0, 0, 2 * sizeof(float)); + ImPlotSpec spec; + spec.Offset = 0; + spec.Stride = 2 * sizeof(float); + spec.LineColor = mixed; + ImPlot::PlotLine("##dndxy", &dndx->Data[0].y, &dndy->Data[0].y, dndx->Data.size(), spec); } // allow the x-axis to be a DND target if (ImPlot::BeginDragDropTargetAxis(ImAxis_X1)) { @@ -1866,6 +2364,7 @@ void Demo_DragAndDrop() { //----------------------------------------------------------------------------- void Demo_Tables() { + IMGUI_DEMO_MARKER("Subplots/Tables"); #ifdef IMGUI_HAS_TABLE static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable; @@ -1906,7 +2405,54 @@ void Demo_Tables() { //----------------------------------------------------------------------------- +void Demo_ItemStylingAndSpec() { + static ImVec2 data1[20]; + for (int i = 0; i < 20; ++i) { + data1[i].x = i * 1/19.0f; + data1[i].y = data1[i].x * data1[i].x; + } + static ImVec2 data2[20]; + for (int i = 0; i < 20; ++i) { + data2[i].x = i * 1/19.0f; + data2[i].y = data2[i].x * data2[i].x * data2[i].x; + } + if (ImPlot::BeginPlot("##SpecStyling")) { + ImPlot::SetupAxes("x","y"); + + // Two options for using ImPlotSpec: + + // 1. By declaring and defining a struct instance: + ImPlotSpec spec; + spec.LineColor = ImVec4(1,1,0,1); + spec.LineWeight = 1.0f; + spec.FillColor = ImVec4(1,0.5f,0,1); + spec.FillAlpha = 0.5f; + spec.Marker = ImPlotMarker_Square; + spec.MarkerSize = 6; + spec.Stride = sizeof(ImVec2); + spec.Flags = ImPlotItemFlags_NoLegend | ImPlotLineFlags_Shaded; + ImPlot::PlotLine("Line 1", &data1[0].x, &data1[0].y, 20, spec); + + // 2. Inline using ImPlotProp,value pairs (order does NOT matter): + ImPlot::PlotLine("Line 2", &data2[0].x, &data2[0].y, 20, { + ImPlotProp_LineColor, ImVec4(0,1,1,1), + ImPlotProp_LineWeight, 1.0f, + ImPlotProp_FillColor, ImVec4(0,0,1,1), + ImPlotProp_FillAlpha, 0.5f, + ImPlotProp_Marker, ImPlotMarker_Diamond, + ImPlotProp_Size, 6, + ImPlotProp_Stride, sizeof(ImVec2), + ImPlotProp_Flags, ImPlotItemFlags_NoLegend | ImPlotLineFlags_Shaded + }); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + void Demo_OffsetAndStride() { + IMGUI_DEMO_MARKER("Tools/Offset and Stride"); static const int k_circles = 11; static const int k_points_per = 50; static const int k_size = 2 * k_points_per * k_circles; @@ -1931,7 +2477,7 @@ void Demo_OffsetAndStride() { char buff[32]; for (int c = 0; c < k_circles; ++c) { snprintf(buff, sizeof(buff), "Circle %d", c); - ImPlot::PlotLine(buff, &interleaved_data[c*2 + 0], &interleaved_data[c*2 + 1], k_points_per, 0, offset, 2*k_circles*sizeof(double)); + ImPlot::PlotLine(buff, &interleaved_data[c*2 + 0], &interleaved_data[c*2 + 1], k_points_per, {ImPlotProp_Offset, offset, ImPlotProp_Stride, 2 * k_circles*sizeof(double)}); } ImPlot::EndPlot(); ImPlot::PopColormap(); @@ -1942,6 +2488,7 @@ void Demo_OffsetAndStride() { //----------------------------------------------------------------------------- void Demo_CustomDataAndGetters() { + IMGUI_DEMO_MARKER("Custom/Custom Data and Getters"); ImGui::BulletText("You can plot custom structs using the stride feature."); ImGui::BulletText("Most plotters can also be passed a function pointer for getting data."); ImGui::Indent(); @@ -1954,7 +2501,7 @@ void Demo_CustomDataAndGetters() { if (ImPlot::BeginPlot("##Custom Data")) { // custom structs using stride example: - ImPlot::PlotLine("Vector2f", &vec2_data[0].x, &vec2_data[0].y, 2, 0, 0, sizeof(MyImPlot::Vector2f) /* or sizeof(float) * 2 */); + ImPlot::PlotLine("Vector2f", &vec2_data[0].x, &vec2_data[0].y, 2, {ImPlotProp_Stride, sizeof(MyImPlot::Vector2f)}); // custom getter example 1: ImPlot::PlotLineG("Spiral", MyImPlot::Spiral, nullptr, 1000); @@ -1964,9 +2511,7 @@ void Demo_CustomDataAndGetters() { static MyImPlot::WaveData data2(0.001, 0.2, 4, 0.25); ImPlot::PlotLineG("Waves", MyImPlot::SineWave, &data1, 1000); ImPlot::PlotLineG("Waves", MyImPlot::SawWave, &data2, 1000); - ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); - ImPlot::PlotShadedG("Waves", MyImPlot::SineWave, &data1, MyImPlot::SawWave, &data2, 1000); - ImPlot::PopStyleVar(); + ImPlot::PlotShadedG("Waves", MyImPlot::SineWave, &data1, MyImPlot::SawWave, &data2, 1000, {ImPlotProp_FillAlpha, 0.25f}); // you can also pass C++ lambdas: // auto lambda = [](void* data, int idx) { ... return ImPlotPoint(x,y); }; @@ -1994,6 +2539,7 @@ int MetricFormatter(double value, char* buff, int size, void* data) { } void Demo_TickLabels() { + IMGUI_DEMO_MARKER("Axes/Tick Labels"); static bool custom_fmt = true; static bool custom_ticks = false; static bool custom_labels = true; @@ -2033,6 +2579,7 @@ void Demo_TickLabels() { //----------------------------------------------------------------------------- void Demo_CustomStyles() { + IMGUI_DEMO_MARKER("Custom/Custom Styles"); ImPlot::PushColormap(ImPlotColormap_Deep); // normally you wouldn't change the entire style each frame ImPlotStyle backup = ImPlot::GetStyle(); @@ -2056,6 +2603,7 @@ void Demo_CustomStyles() { //----------------------------------------------------------------------------- void Demo_CustomRendering() { + IMGUI_DEMO_MARKER("Custom/Custom Rendering"); if (ImPlot::BeginPlot("##CustomRend")) { ImVec2 cntr = ImPlot::PlotToPixels(ImPlotPoint(0.5f, 0.5f)); ImVec2 rmin = ImPlot::PlotToPixels(ImPlotPoint(0.25f, 0.75f)); @@ -2071,6 +2619,7 @@ void Demo_CustomRendering() { //----------------------------------------------------------------------------- void Demo_LegendPopups() { + IMGUI_DEMO_MARKER("Tools/Legend Popups"); ImGui::BulletText("You can implement legend context menus to inject per-item controls and widgets."); ImGui::BulletText("Right click the legend label/icon to edit custom item attributes."); @@ -2090,18 +2639,29 @@ void Demo_LegendPopups() { if (ImPlot::BeginPlot("Right Click the Legend")) { ImPlot::SetupAxesLimits(0,100,-1,1); // rendering logic - ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, alpha); if (!line) { - ImPlot::SetNextFillStyle(color); - ImPlot::PlotBars("Right Click Me", vals, 101); + ImPlot::PlotBars("Right Click Me", vals, 101, 0.67, 0, { + ImPlotProp_FillAlpha, alpha, + ImPlotProp_FillColor, color + }); } else { - if (markers) ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); - ImPlot::SetNextLineStyle(color, thickness); - ImPlot::PlotLine("Right Click Me", vals, 101); - if (shaded) ImPlot::PlotShaded("Right Click Me",vals,101); + ImPlot::PlotLine("Right Click Me", vals, 101, 1, 0, { + ImPlotProp_LineColor, color, + ImPlotProp_LineWeight, thickness + }); + if (markers) { + ImPlot::PlotScatter("Right Click Me", vals, 101, 1, 0, { + ImPlotProp_Marker, ImPlotMarker_Square, + ImPlotProp_LineColor, color + }); + } + if (shaded) { + ImPlot::PlotShaded("Right Click Me",vals,101, 0, 1, 0, { + ImPlotProp_FillAlpha, alpha, + }); + } } - ImPlot::PopStyleVar(); // custom legend context menu if (ImPlot::BeginLegendPopup("Right Click Me")) { ImGui::SliderFloat("Frequency",&frequency,0,1,"%0.2f"); @@ -2124,6 +2684,7 @@ void Demo_LegendPopups() { //----------------------------------------------------------------------------- void Demo_ColormapWidgets() { + IMGUI_DEMO_MARKER("Tools/Colormap Widgets"); static int cmap = ImPlotColormap_Viridis; if (ImPlot::ColormapButton("Button",ImVec2(0,0),cmap)) { @@ -2150,6 +2711,7 @@ void Demo_ColormapWidgets() { //----------------------------------------------------------------------------- void Demo_CustomPlottersAndTooltips() { + IMGUI_DEMO_MARKER("Custom/Custom Plotters and Tooltips"); ImGui::BulletText("You can create custom plotters or extend ImPlot using implot_internal.h."); double dates[] = {1546300800,1546387200,1546473600,1546560000,1546819200,1546905600,1546992000,1547078400,1547164800,1547424000,1547510400,1547596800,1547683200,1547769600,1547942400,1548028800,1548115200,1548201600,1548288000,1548374400,1548633600,1548720000,1548806400,1548892800,1548979200,1549238400,1549324800,1549411200,1549497600,1549584000,1549843200,1549929600,1550016000,1550102400,1550188800,1550361600,1550448000,1550534400,1550620800,1550707200,1550793600,1551052800,1551139200,1551225600,1551312000,1551398400,1551657600,1551744000,1551830400,1551916800,1552003200,1552262400,1552348800,1552435200,1552521600,1552608000,1552867200,1552953600,1553040000,1553126400,1553212800,1553472000,1553558400,1553644800,1553731200,1553817600,1554076800,1554163200,1554249600,1554336000,1554422400,1554681600,1554768000,1554854400,1554940800,1555027200,1555286400,1555372800,1555459200,1555545600,1555632000,1555891200,1555977600,1556064000,1556150400,1556236800,1556496000,1556582400,1556668800,1556755200,1556841600,1557100800,1557187200,1557273600,1557360000,1557446400,1557705600,1557792000,1557878400,1557964800,1558051200,1558310400,1558396800,1558483200,1558569600,1558656000,1558828800,1558915200,1559001600,1559088000,1559174400,1559260800,1559520000,1559606400,1559692800,1559779200,1559865600,1560124800,1560211200,1560297600,1560384000,1560470400,1560729600,1560816000,1560902400,1560988800,1561075200,1561334400,1561420800,1561507200,1561593600,1561680000,1561939200,1562025600,1562112000,1562198400,1562284800,1562544000,1562630400,1562716800,1562803200,1562889600,1563148800,1563235200,1563321600,1563408000,1563494400,1563753600,1563840000,1563926400,1564012800,1564099200,1564358400,1564444800,1564531200,1564617600,1564704000,1564963200,1565049600,1565136000,1565222400,1565308800,1565568000,1565654400,1565740800,1565827200,1565913600,1566172800,1566259200,1566345600,1566432000,1566518400,1566777600,1566864000,1566950400,1567036800,1567123200,1567296000,1567382400,1567468800,1567555200,1567641600,1567728000,1567987200,1568073600,1568160000,1568246400,1568332800,1568592000,1568678400,1568764800,1568851200,1568937600,1569196800,1569283200,1569369600,1569456000,1569542400,1569801600,1569888000,1569974400,1570060800,1570147200,1570406400,1570492800,1570579200,1570665600,1570752000,1571011200,1571097600,1571184000,1571270400,1571356800,1571616000,1571702400,1571788800,1571875200,1571961600}; double opens[] = {1284.7,1319.9,1318.7,1328,1317.6,1321.6,1314.3,1325,1319.3,1323.1,1324.7,1321.3,1323.5,1322,1281.3,1281.95,1311.1,1315,1314,1313.1,1331.9,1334.2,1341.3,1350.6,1349.8,1346.4,1343.4,1344.9,1335.6,1337.9,1342.5,1337,1338.6,1337,1340.4,1324.65,1324.35,1349.5,1371.3,1367.9,1351.3,1357.8,1356.1,1356,1347.6,1339.1,1320.6,1311.8,1314,1312.4,1312.3,1323.5,1319.1,1327.2,1332.1,1320.3,1323.1,1328,1330.9,1338,1333,1335.3,1345.2,1341.1,1332.5,1314,1314.4,1310.7,1314,1313.1,1315,1313.7,1320,1326.5,1329.2,1314.2,1312.3,1309.5,1297.4,1293.7,1277.9,1295.8,1295.2,1290.3,1294.2,1298,1306.4,1299.8,1302.3,1297,1289.6,1302,1300.7,1303.5,1300.5,1303.2,1306,1318.7,1315,1314.5,1304.1,1294.7,1293.7,1291.2,1290.2,1300.4,1284.2,1284.25,1301.8,1295.9,1296.2,1304.4,1323.1,1340.9,1341,1348,1351.4,1351.4,1343.5,1342.3,1349,1357.6,1357.1,1354.7,1361.4,1375.2,1403.5,1414.7,1433.2,1438,1423.6,1424.4,1418,1399.5,1435.5,1421.25,1434.1,1412.4,1409.8,1412.2,1433.4,1418.4,1429,1428.8,1420.6,1441,1460.4,1441.7,1438.4,1431,1439.3,1427.4,1431.9,1439.5,1443.7,1425.6,1457.5,1451.2,1481.1,1486.7,1512.1,1515.9,1509.2,1522.3,1513,1526.6,1533.9,1523,1506.3,1518.4,1512.4,1508.8,1545.4,1537.3,1551.8,1549.4,1536.9,1535.25,1537.95,1535.2,1556,1561.4,1525.6,1516.4,1507,1493.9,1504.9,1506.5,1513.1,1506.5,1509.7,1502,1506.8,1521.5,1529.8,1539.8,1510.9,1511.8,1501.7,1478,1485.4,1505.6,1511.6,1518.6,1498.7,1510.9,1510.8,1498.3,1492,1497.7,1484.8,1494.2,1495.6,1495.6,1487.5,1491.1,1495.1,1506.4}; @@ -2248,6 +2810,8 @@ void ShowDemoWindow(bool* p_open) { DemoHeader("Filled Line Plots", Demo_FilledLinePlots); DemoHeader("Shaded Plots##", Demo_ShadedPlots); DemoHeader("Scatter Plots", Demo_ScatterPlots); + DemoHeader("Bubble Plots", Demo_BubblePlots); + DemoHeader("Polygon Plots", Demo_PolygonPlots); DemoHeader("Realtime Plots", Demo_RealtimePlots); DemoHeader("Stairstep Plots", Demo_StairstepPlots); DemoHeader("Bar Plots", Demo_BarPlots); @@ -2264,6 +2828,7 @@ void ShowDemoWindow(bool* p_open) { DemoHeader("Images", Demo_Images); DemoHeader("Markers and Text", Demo_MarkersAndText); DemoHeader("NaN Values", Demo_NaNValues); + DemoHeader("Per-Index Colors", Demo_PerIndexColors); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Subplots")) { @@ -2287,6 +2852,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Tools")) { + DemoHeader("Item Styling and Spec", Demo_ItemStylingAndSpec); DemoHeader("Offset and Stride", Demo_OffsetAndStride); DemoHeader("Drag Points", Demo_DragPoints); DemoHeader("Drag Lines", Demo_DragLines); @@ -2352,9 +2918,13 @@ void Sparkline(const char* id, const float* values, int count, float min_v, floa if (ImPlot::BeginPlot(id,size,ImPlotFlags_CanvasOnly)) { ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations); ImPlot::SetupAxesLimits(0, count - 1, min_v, max_v, ImGuiCond_Always); - ImPlot::SetNextLineStyle(col); - ImPlot::SetNextFillStyle(col, 0.25); - ImPlot::PlotLine(id, values, count, 1, 0, ImPlotLineFlags_Shaded, offset); + ImPlot::PlotLine(id, values, count, 1, 0, { + ImPlotProp_LineColor, col, + ImPlotProp_FillColor, col, + ImPlotProp_FillAlpha, 0.25f, + ImPlotProp_Offset, offset, + ImPlotProp_Flags, ImPlotLineFlags_Shaded + }); ImPlot::EndPlot(); } ImPlot::PopStyleVar(); @@ -2365,11 +2935,6 @@ void StyleSeaborn() { ImPlotStyle& style = ImPlot::GetStyle(); ImVec4* colors = style.Colors; - colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; - colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; - colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; - colors[ImPlotCol_ErrorBar] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImPlotCol_PlotBg] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); colors[ImPlotCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); @@ -2380,20 +2945,13 @@ void StyleSeaborn() { colors[ImPlotCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); colors[ImPlotCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImPlotCol_AxisBgHovered] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); - colors[ImPlotCol_AxisBgActive] = ImVec4(0.92f, 0.92f, 0.95f, 0.75f); + colors[ImPlotCol_AxisBgHovered] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); + colors[ImPlotCol_AxisBgActive] = ImVec4(0.92f, 0.92f, 0.95f, 0.75f); colors[ImPlotCol_Selection] = ImVec4(1.00f, 0.65f, 0.00f, 1.00f); colors[ImPlotCol_Crosshairs] = ImVec4(0.23f, 0.10f, 0.64f, 0.50f); - style.LineWeight = 1.5; - style.Marker = ImPlotMarker_None; - style.MarkerSize = 4; - style.MarkerWeight = 1; - style.FillAlpha = 1.0f; - style.ErrorBarSize = 5; - style.ErrorBarWeight = 1.5f; - style.DigitalBitHeight = 8; - style.DigitalBitGap = 4; + style.MousePosPadding = ImVec2(5,5); + style.PlotMinSize = ImVec2(300,225); style.PlotBorderSize = 0; style.MinorAlpha = 1.0f; style.MajorTickLen = ImVec2(0,0); @@ -2405,8 +2963,8 @@ void StyleSeaborn() { style.PlotPadding = ImVec2(12,12); style.LabelPadding = ImVec2(5,5); style.LegendPadding = ImVec2(5,5); - style.MousePosPadding = ImVec2(5,5); - style.PlotMinSize = ImVec2(300,225); + style.DigitalPadding = 20; + style.DigitalSpacing = 4; } } // namespace MyImPlot diff --git a/extensions/ImGui/src/ImGui/implot/implot_internal.h b/extensions/ImGui/src/ImGui/implot/implot_internal.h index 46dfaa5eba5d..e77832705cbd 100644 --- a/extensions/ImGui/src/ImGui/implot/implot_internal.h +++ b/extensions/ImGui/src/ImGui/implot/implot_internal.h @@ -1,7 +1,7 @@ // MIT License // Copyright (c) 2020-2024 Evan Pezent -// Copyright (c) 2025 Breno Cunha Queiroz +// Copyright (c) 2025-2026 Breno Cunha Queiroz // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// ImPlot v0.17 +// ImPlot v1.1 WIP // You may use this file to debug, understand or extend ImPlot features but we // don't provide any guarantee of forward compatibility! @@ -53,21 +53,24 @@ // to ImPlotStyleVar_ over time. // Minimum allowable timestamp value 01/01/1970 @ 12:00am (UTC) (DO NOT DECREASE THIS) -#define IMPLOT_MIN_TIME 0 +constexpr double IMPLOT_MIN_TIME = 0; // Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) (DO NOT INCREASE THIS) -#define IMPLOT_MAX_TIME 32503680000 +constexpr double IMPLOT_MAX_TIME = 32503680000; + // Default label format for axis labels -#define IMPLOT_LABEL_FORMAT "%g" +constexpr const char* IMPLOT_LABEL_FORMAT = "%g"; // Max character size for tick labels -#define IMPLOT_LABEL_MAX_SIZE 32 +constexpr int IMPLOT_LABEL_MAX_SIZE = 32; + +// Number of X axes +constexpr int IMPLOT_NUM_X_AXES = ImAxis_Y1; +// Number of Y axes +constexpr int IMPLOT_NUM_Y_AXES = ImAxis_COUNT - IMPLOT_NUM_X_AXES; //----------------------------------------------------------------------------- // [SECTION] Macros //----------------------------------------------------------------------------- -#define IMPLOT_NUM_X_AXES ImAxis_Y1 -#define IMPLOT_NUM_Y_AXES (ImAxis_COUNT - IMPLOT_NUM_X_AXES) - // Split ImU32 color into RGB components [0 255] #define IM_COL32_SPLIT_RGB(col,r,g,b) \ ImU32 r = ((col >> IM_COL32_R_SHIFT) & 0xFF); \ @@ -115,7 +118,7 @@ static inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? se // Linearly remaps x from [x0 x1] to [y0 y1]. template static inline T ImRemap(T x, T x0, T x1, T y0, T y1) { return y0 + (x - x0) * (y1 - y0) / (x1 - x0); } -// Linear rempas x from [x0 x1] to [0 1] +// Linearly remaps x from [x0 x1] to [0 1] template static inline T ImRemap01(T x, T x0, T x1) { return (x - x0) / (x1 - x0); } // Returns always positive modulo (assumes r != 0) @@ -134,6 +137,7 @@ static inline double ImConstrainLog(double val) { return val <= 0 ? 0.001f : val static inline double ImConstrainTime(double val) { return val < IMPLOT_MIN_TIME ? IMPLOT_MIN_TIME : (val > IMPLOT_MAX_TIME ? IMPLOT_MAX_TIME : val); } // True if two numbers are approximately equal using units in the last place. static inline bool ImAlmostEqual(double v1, double v2, int ulp = 2) { return ImAbs(v1-v2) < DBL_EPSILON * ImAbs(v1+v2) * ulp || ImAbs(v1-v2) < DBL_MIN; } + // Finds min value in an unsorted array template static inline T ImMinArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] < m) { m = values[i]; } } return m; } @@ -177,6 +181,7 @@ static inline double ImStdDev(const T* values, int count) { x += ((double)values[i] - mu) * ((double)values[i] - mu) * den; return sqrt(x); } + // Mix color a and b by factor s in [0 256] static inline ImU32 ImMixU32(ImU32 a, ImU32 b, ImU32 s) { #ifdef IMPLOT_MIX64 @@ -227,9 +232,10 @@ static inline bool ImOverlaps(T min_a, T max_a, T min_b, T max_b) { // [SECTION] ImPlot Enums //----------------------------------------------------------------------------- -typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ -typedef int ImPlotDateFmt; // -> enum ImPlotDateFmt_ -typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ +typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ +typedef int ImPlotDateFmt; // -> enum ImPlotDateFmt_ +typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ +typedef int ImPlotMarkerInternal; // -> enum ImPlotMarkerInternal_ enum ImPlotTimeUnit_ { ImPlotTimeUnit_Us, // microsecond @@ -265,6 +271,10 @@ enum ImPlotTimeFmt_ { // default [ 24 Hour Clock ] ImPlotTimeFmt_Hr // 7pm [ 19:00 ] }; +enum ImPlotMarkerInternal_ { + ImPlotMarker_Invalid = -3 +}; + //----------------------------------------------------------------------------- // [SECTION] Callbacks //----------------------------------------------------------------------------- @@ -752,7 +762,7 @@ struct ImPlotAxis PickerTimeMin = ImPlotTime::FromDouble(Range.Min); UpdateTransformCache(); return true; - }; + } inline bool SetMax(double _max, bool force=false) { if (!force && IsLockedMax()) @@ -771,7 +781,7 @@ struct ImPlotAxis PickerTimeMax = ImPlotTime::FromDouble(Range.Max); UpdateTransformCache(); return true; - }; + } inline void SetRange(double v1, double v2) { Range.Min = ImMin(v1,v2); @@ -962,6 +972,7 @@ struct ImPlotItem { ImGuiID ID; ImU32 Color; + ImPlotMarker Marker; ImRect LegendHoverRect; int NameOffset; bool Show; @@ -971,6 +982,7 @@ struct ImPlotItem ImPlotItem() { ID = 0; Color = IM_COL32_WHITE; + Marker = ImPlotMarker_None; NameOffset = -1; Show = true; SeenThisFrame = false; @@ -1014,8 +1026,9 @@ struct ImPlotItemGroup ImPlotLegend Legend; ImPool ItemPool; int ColormapIdx; + ImPlotMarker MarkerIdx; - ImPlotItemGroup() { ID = 0; ColormapIdx = 0; } + ImPlotItemGroup() { ID = 0; ColormapIdx = 0; MarkerIdx = 0; } int GetItemCount() const { return ItemPool.GetBufSize(); } ImGuiID GetItemID(const char* label_id) { return ImGui::GetID(label_id); /* GetIDWithSeed */ } @@ -1194,30 +1207,20 @@ struct ImPlotNextPlotData // Temporary data storage for upcoming item struct ImPlotNextItemData { - ImVec4 Colors[5]; // ImPlotCol_Line, ImPlotCol_Fill, ImPlotCol_MarkerOutline, ImPlotCol_MarkerFill, ImPlotCol_ErrorBar - float LineWeight; - ImPlotMarker Marker; - float MarkerSize; - float MarkerWeight; - float FillAlpha; - float ErrorBarSize; - float ErrorBarWeight; - float DigitalBitHeight; - float DigitalBitGap; + ImPlotSpec Spec; bool RenderLine; bool RenderFill; bool RenderMarkerLine; bool RenderMarkerFill; + bool RenderMarkers; bool HasHidden; bool Hidden; ImPlotCond HiddenCond; ImPlotNextItemData() { Reset(); } void Reset() { - for (int i = 0; i < 5; ++i) - Colors[i] = IMPLOT_AUTO_COL; - LineWeight = MarkerSize = MarkerWeight = FillAlpha = ErrorBarSize = ErrorBarWeight = DigitalBitHeight = DigitalBitGap = IMPLOT_AUTO; - Marker = IMPLOT_AUTO; - HasHidden = Hidden = false; + Spec = ImPlotSpec(); + HasHidden = Hidden = false; + HiddenCond = ImPlotCond_None; } }; @@ -1330,14 +1333,14 @@ IMPLOT_API void ShowSubplotsContextMenu(ImPlotSubplot& subplot); //----------------------------------------------------------------------------- // Begins a new item. Returns false if the item should not be plotted. Pushes PlotClipRect. -IMPLOT_API bool BeginItem(const char* label_id, ImPlotItemFlags flags=0, ImPlotCol recolor_from=IMPLOT_AUTO); +IMPLOT_API bool BeginItem(const char* label_id, const ImPlotSpec& spec = ImPlotSpec(), const ImVec4& item_col = IMPLOT_AUTO_COL, ImPlotMarker item_mkr = ImPlotMarker_Invalid); // Same as above but with fitting functionality. template -bool BeginItemEx(const char* label_id, const _Fitter& fitter, ImPlotItemFlags flags=0, ImPlotCol recolor_from=IMPLOT_AUTO) { - if (BeginItem(label_id, flags, recolor_from)) { +bool BeginItemEx(const char* label_id, const _Fitter& fitter, const ImPlotSpec& spec, const ImVec4& item_col = IMPLOT_AUTO_COL, ImPlotMarker item_mkr = ImPlotMarker_Invalid) { + if (BeginItem(label_id, spec, item_col, item_mkr)) { ImPlotPlot& plot = *GetCurrentPlot(); - if (plot.FitThisFrame && !ImHasFlag(flags, ImPlotItemFlags_NoFit)) + if (plot.FitThisFrame && !ImHasFlag(spec.Flags, ImPlotItemFlags_NoFit)) fitter.Fit(plot.Axes[plot.CurrentX], plot.Axes[plot.CurrentY]); return true; } @@ -1538,8 +1541,8 @@ void FillRange(ImVector& buffer, int n, T vmin, T vmax) { } // Calculate histogram bin counts and widths -template -static inline void CalculateBins(const T* values, int count, ImPlotBin meth, const ImPlotRange& range, int& bins_out, double& width_out) { +template +static inline void CalculateBins(const TContainer& values, int count, ImPlotBin meth, const ImPlotRange& range, int& bins_out, double& width_out) { switch (meth) { case ImPlotBin_Sqrt: bins_out = (int)ceil(sqrt(count)); @@ -1568,7 +1571,7 @@ static inline bool IsLeapYear(int year) { } // Returns the number of days in a month, accounting for Feb. leap years. #month is zero indexed. static inline int GetDaysInMonth(int year, int month) { - static const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + constexpr int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; return days[month] + (int)(month == 1 && IsLeapYear(year)); } diff --git a/extensions/ImGui/src/ImGui/implot/implot_items.cpp b/extensions/ImGui/src/ImGui/implot/implot_items.cpp index af03bf164dd8..807df1258cef 100644 --- a/extensions/ImGui/src/ImGui/implot/implot_items.cpp +++ b/extensions/ImGui/src/ImGui/implot/implot_items.cpp @@ -1,7 +1,7 @@ // MIT License // Copyright (c) 2020-2024 Evan Pezent -// Copyright (c) 2025 Breno Cunha Queiroz +// Copyright (c) 2025-2026 Breno Cunha Queiroz // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// ImPlot v0.17 +// ImPlot v1.1 WIP #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS @@ -120,6 +120,43 @@ struct MaxIdx { static const unsigned int Value; }; template <> const unsigned int MaxIdx::Value = 65535; template <> const unsigned int MaxIdx::Value = 4294967295; +template +int Stride(const ImPlotSpec& spec) { + return spec.Stride == IMPLOT_AUTO ? sizeof(T) : spec.Stride; +} + +// Finds the min and max value in an unsorted array +template +static inline void ImMinMaxIndexer(const Indexer& values, int count, T* min_out, T* max_out) { + T Min = values[0]; T Max = values[0]; + for (int i = 1; i < count; ++i) { + if (values[i] < Min) { Min = values[i]; } + if (values[i] > Max) { Max = values[i]; } + } + *min_out = Min; *max_out = Max; +} + +// Finds the mean of a container +template +static inline double ImMean(const TContainer& values, int count) { + double den = 1.0 / count; + double mu = 0; + for (int i = 0; i < count; ++i) + mu += (double)values[i] * den; + return mu; +} + +// Finds the sample standard deviation of a container +template +static inline double ImStdDev(const TContainer& values, int count) { + double den = 1.0 / (count - 1.0); + double mu = ImMean(values, count); + double x = 0; + for (int i = 0; i < count; ++i) + x += ((double)values[i] - mu) * ((double)values[i] - mu) * den; + return sqrt(x); +} + IMPLOT_INLINE void GetLineRenderProps(const ImDrawList& draw_list, float& half_weight, ImVec2& tex_uv0, ImVec2& tex_uv1) { const bool aa = ImHasFlag(draw_list.Flags, ImDrawListFlags_AntiAliasedLines) && ImHasFlag(draw_list.Flags, ImDrawListFlags_AntiAliasedLinesUseTex); @@ -323,34 +360,6 @@ ImPlotItem* GetCurrentItem() { return gp.CurrentItem; } -void SetNextLineStyle(const ImVec4& col, float weight) { - ImPlotContext& gp = *GImPlot; - gp.NextItemData.Colors[ImPlotCol_Line] = col; - gp.NextItemData.LineWeight = weight; -} - -void SetNextFillStyle(const ImVec4& col, float alpha) { - ImPlotContext& gp = *GImPlot; - gp.NextItemData.Colors[ImPlotCol_Fill] = col; - gp.NextItemData.FillAlpha = alpha; -} - -void SetNextMarkerStyle(ImPlotMarker marker, float size, const ImVec4& fill, float weight, const ImVec4& outline) { - ImPlotContext& gp = *GImPlot; - gp.NextItemData.Marker = marker; - gp.NextItemData.Colors[ImPlotCol_MarkerFill] = fill; - gp.NextItemData.MarkerSize = size; - gp.NextItemData.Colors[ImPlotCol_MarkerOutline] = outline; - gp.NextItemData.MarkerWeight = weight; -} - -void SetNextErrorBarStyle(const ImVec4& col, float size, float weight) { - ImPlotContext& gp = *GImPlot; - gp.NextItemData.Colors[ImPlotCol_ErrorBar] = col; - gp.NextItemData.ErrorBarSize = size; - gp.NextItemData.ErrorBarWeight = weight; -} - ImVec4 GetLastItemColor() { ImPlotContext& gp = *GImPlot; if (gp.PreviousItem) @@ -392,36 +401,41 @@ void BustColorCache(const char* plot_title_id) { // [SECTION] BeginItem / EndItem //----------------------------------------------------------------------------- -static const float ITEM_HIGHLIGHT_LINE_SCALE = 2.0f; -static const float ITEM_HIGHLIGHT_MARK_SCALE = 1.25f; +constexpr float ITEM_HIGHLIGHT_LINE_SCALE = 2.0f; +constexpr float ITEM_HIGHLIGHT_MARK_SCALE = 1.25f; // Begins a new item. Returns false if the item should not be plotted. -bool BeginItem(const char* label_id, ImPlotItemFlags flags, ImPlotCol recolor_from) { +bool BeginItem(const char* label_id, const ImPlotSpec& spec, const ImVec4& item_col, ImPlotMarker item_mkr) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotX() needs to be called between BeginPlot() and EndPlot()!"); SetupLock(); bool just_created; - ImPlotItem* item = RegisterOrGetItem(label_id, flags, &just_created); + ImPlotItem* item = RegisterOrGetItem(label_id, spec.Flags, &just_created); // set current item gp.CurrentItem = item; ImPlotNextItemData& s = gp.NextItemData; // set/override item color - if (recolor_from != -1) { - if (!IsColorAuto(s.Colors[recolor_from])) - item->Color = ImGui::ColorConvertFloat4ToU32(s.Colors[recolor_from]); - else if (!IsColorAuto(gp.Style.Colors[recolor_from])) - item->Color = ImGui::ColorConvertFloat4ToU32(gp.Style.Colors[recolor_from]); - else if (just_created) - item->Color = NextColormapColorU32(); - } - else if (just_created) { + if (!IsColorAuto(item_col)) + item->Color = ImGui::ColorConvertFloat4ToU32(item_col); + else if (just_created) item->Color = NextColormapColorU32(); - } - // hide/show item if (gp.NextItemData.HasHidden) { if (just_created || gp.NextItemData.HiddenCond == ImGuiCond_Always) item->Show = !gp.NextItemData.Hidden; } + // set/override item marker + if (item_mkr != ImPlotMarker_Invalid) { + if (item_mkr != ImPlotMarker_Auto) { + item->Marker = item_mkr; + } + else if (just_created && item_mkr == ImPlotMarker_Auto) { + item->Marker = NextMarker(); + } + else if (item_mkr == ImPlotMarker_Auto && item->Marker == ImPlotMarker_None) { + item->Marker = NextMarker(); + } + } + // return false if not shown if (!item->Show) { // reset next item data gp.NextItemData.Reset(); @@ -431,31 +445,21 @@ bool BeginItem(const char* label_id, ImPlotItemFlags flags, ImPlotCol recolor_fr } else { ImVec4 item_color = ImGui::ColorConvertU32ToFloat4(item->Color); - // stage next item colors - s.Colors[ImPlotCol_Line] = IsColorAuto(s.Colors[ImPlotCol_Line]) ? ( IsColorAuto(ImPlotCol_Line) ? item_color : gp.Style.Colors[ImPlotCol_Line] ) : s.Colors[ImPlotCol_Line]; - s.Colors[ImPlotCol_Fill] = IsColorAuto(s.Colors[ImPlotCol_Fill]) ? ( IsColorAuto(ImPlotCol_Fill) ? item_color : gp.Style.Colors[ImPlotCol_Fill] ) : s.Colors[ImPlotCol_Fill]; - s.Colors[ImPlotCol_MarkerOutline] = IsColorAuto(s.Colors[ImPlotCol_MarkerOutline]) ? ( IsColorAuto(ImPlotCol_MarkerOutline) ? s.Colors[ImPlotCol_Line] : gp.Style.Colors[ImPlotCol_MarkerOutline] ) : s.Colors[ImPlotCol_MarkerOutline]; - s.Colors[ImPlotCol_MarkerFill] = IsColorAuto(s.Colors[ImPlotCol_MarkerFill]) ? ( IsColorAuto(ImPlotCol_MarkerFill) ? s.Colors[ImPlotCol_Line] : gp.Style.Colors[ImPlotCol_MarkerFill] ) : s.Colors[ImPlotCol_MarkerFill]; - s.Colors[ImPlotCol_ErrorBar] = IsColorAuto(s.Colors[ImPlotCol_ErrorBar]) ? ( GetStyleColorVec4(ImPlotCol_ErrorBar) ) : s.Colors[ImPlotCol_ErrorBar]; - // stage next item style vars - s.LineWeight = s.LineWeight < 0 ? gp.Style.LineWeight : s.LineWeight; - s.Marker = s.Marker < 0 ? gp.Style.Marker : s.Marker; - s.MarkerSize = s.MarkerSize < 0 ? gp.Style.MarkerSize : s.MarkerSize; - s.MarkerWeight = s.MarkerWeight < 0 ? gp.Style.MarkerWeight : s.MarkerWeight; - s.FillAlpha = s.FillAlpha < 0 ? gp.Style.FillAlpha : s.FillAlpha; - s.ErrorBarSize = s.ErrorBarSize < 0 ? gp.Style.ErrorBarSize : s.ErrorBarSize; - s.ErrorBarWeight = s.ErrorBarWeight < 0 ? gp.Style.ErrorBarWeight : s.ErrorBarWeight; - s.DigitalBitHeight = s.DigitalBitHeight < 0 ? gp.Style.DigitalBitHeight : s.DigitalBitHeight; - s.DigitalBitGap = s.DigitalBitGap < 0 ? gp.Style.DigitalBitGap : s.DigitalBitGap; - // apply alpha modifier(s) - s.Colors[ImPlotCol_Fill].w *= s.FillAlpha; - s.Colors[ImPlotCol_MarkerFill].w *= s.FillAlpha; // TODO: this should be separate, if it at all + // stage next item spec + s.Spec = spec; + s.Spec.LineColor = IsColorAuto(s.Spec.LineColor) ? item_color : s.Spec.LineColor; + s.Spec.FillColor = IsColorAuto(s.Spec.FillColor) ? item_color : s.Spec.FillColor; + s.Spec.FillColor.w *= s.Spec.FillAlpha; + s.Spec.Marker = item->Marker; + s.Spec.MarkerLineColor = IsColorAuto(s.Spec.MarkerLineColor) ? s.Spec.LineColor : s.Spec.MarkerLineColor; + s.Spec.MarkerFillColor = IsColorAuto(s.Spec.MarkerFillColor) ? s.Spec.LineColor : s.Spec.MarkerFillColor; + s.Spec.MarkerFillColor.w *= s.Spec.FillAlpha; // apply highlight mods if (item->LegendHovered) { if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoHighlightItem)) { - s.LineWeight *= ITEM_HIGHLIGHT_LINE_SCALE; - s.MarkerSize *= ITEM_HIGHLIGHT_MARK_SCALE; - s.MarkerWeight *= ITEM_HIGHLIGHT_LINE_SCALE; + s.Spec.LineWeight *= ITEM_HIGHLIGHT_LINE_SCALE; + s.Spec.MarkerSize *= ITEM_HIGHLIGHT_MARK_SCALE; + s.Spec.Size *= ITEM_HIGHLIGHT_MARK_SCALE; // TODO: how to highlight fills? } if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoHighlightAxis)) { @@ -466,10 +470,11 @@ bool BeginItem(const char* label_id, ImPlotItemFlags flags, ImPlotCol recolor_fr } } // set render flags - s.RenderLine = s.Colors[ImPlotCol_Line].w > 0 && s.LineWeight > 0; - s.RenderFill = s.Colors[ImPlotCol_Fill].w > 0; - s.RenderMarkerFill = s.Colors[ImPlotCol_MarkerFill].w > 0; - s.RenderMarkerLine = s.Colors[ImPlotCol_MarkerOutline].w > 0 && s.MarkerWeight > 0; + s.RenderLine = s.Spec.LineColor.w > 0 && s.Spec.LineWeight > 0; + s.RenderFill = s.Spec.FillColor.w > 0; + s.RenderMarkerLine = s.Spec.MarkerLineColor.w > 0 && s.Spec.LineWeight > 0; + s.RenderMarkerFill = s.Spec.MarkerFillColor.w > 0; + s.RenderMarkers = s.Spec.Marker >= 0 && (s.RenderMarkerFill || s.RenderMarkerLine); // push rendering clip rect PushPlotClipRect(); return true; @@ -512,13 +517,14 @@ struct IndexerIdx { Offset(count ? ImPosMod(offset, count) : 0), Stride(stride) { } - template IMPLOT_INLINE double operator()(I idx) const { + template IMPLOT_INLINE double operator[](I idx) const { return (double)IndexData(Data, idx, Count, Offset, Stride); } const T* Data; int Count; int Offset; int Stride; + typedef double value_type; }; template @@ -530,29 +536,32 @@ struct IndexerAdd { Scale2(scale2), Count(ImMin(Indexer1.Count, Indexer2.Count)) { } - template IMPLOT_INLINE double operator()(I idx) const { - return Scale1 * Indexer1(idx) + Scale2 * Indexer2(idx); + template IMPLOT_INLINE double operator[](I idx) const { + return Scale1 * Indexer1[idx] + Scale2 * Indexer2[idx]; } const _Indexer1& Indexer1; const _Indexer2& Indexer2; double Scale1; double Scale2; int Count; + typedef double value_type; }; struct IndexerLin { IndexerLin(double m, double b) : M(m), B(b) { } - template IMPLOT_INLINE double operator()(I idx) const { + template IMPLOT_INLINE double operator[](I idx) const { return M * idx + B; } const double M; const double B; + typedef double value_type; }; struct IndexerConst { IndexerConst(double ref) : Ref(ref) { } - template IMPLOT_INLINE double operator()(I) const { return Ref; } + template IMPLOT_INLINE double operator[](I) const { return Ref; } const double Ref; + typedef double value_type; }; //----------------------------------------------------------------------------- @@ -561,13 +570,35 @@ struct IndexerConst { template struct GetterXY { - GetterXY(_IndexerX x, _IndexerY y, int count) : IndxerX(x), IndxerY(y), Count(count) { } - template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { - return ImPlotPoint(IndxerX(idx),IndxerY(idx)); + GetterXY(_IndexerX x, _IndexerY y, int count) : IndexerX(x), IndexerY(y), Count(count) { } + template IMPLOT_INLINE ImPlotPoint operator[](I idx) const { + return ImPlotPoint(IndexerX[idx],IndexerY[idx]); } - const _IndexerX IndxerX; - const _IndexerY IndxerY; + const _IndexerX IndexerX; + const _IndexerY IndexerY; const int Count; + typedef ImPlotPoint value_type; +}; + +// Double precision point with three coordinates used by ImPlot. +struct ImPlotPoint3D { + double x, y, z; + constexpr ImPlotPoint3D() : x(0.0), y(0.0), z(0.0) { } + constexpr ImPlotPoint3D(double _x, double _y, double _z) : x(_x), y(_y), z(_z) { } + double& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1 || idx == 2); return ((double*)(void*)(char*)this)[idx]; } + double operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1 || idx == 2); return ((const double*)(const void*)(const char*)this)[idx]; } +}; + +template +struct GetterXYZ { + GetterXYZ(_IndexerX x, _IndexerY y, _IndexerZ z, int count) : IndxerX(x), IndxerY(y), IndxerZ(z), Count(count) { } + template IMPLOT_INLINE ImPlotPoint3D operator()(I idx) const { + return ImPlotPoint3D(IndxerX[idx],IndxerY[idx],IndxerZ[idx]); + } + const _IndexerX IndxerX; + const _IndexerY IndxerY; + const _IndexerZ IndxerZ; + const int Count; }; /// Interprets a user's function pointer as ImPlotPoints @@ -577,49 +608,53 @@ struct GetterFuncPtr { Data(data), Count(count) { } - template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + template IMPLOT_INLINE ImPlotPoint operator[](I idx) const { return Getter(idx, Data); } ImPlotGetter Getter; void* const Data; const int Count; + typedef ImPlotPoint value_type; }; template struct GetterOverrideX { GetterOverrideX(_Getter getter, double x) : Getter(getter), X(x), Count(getter.Count) { } - template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { - ImPlotPoint p = Getter(idx); + template IMPLOT_INLINE ImPlotPoint operator[](I idx) const { + ImPlotPoint p = Getter[idx]; p.x = X; return p; } const _Getter Getter; const double X; const int Count; + typedef ImPlotPoint value_type; }; template struct GetterOverrideY { GetterOverrideY(_Getter getter, double y) : Getter(getter), Y(y), Count(getter.Count) { } - template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { - ImPlotPoint p = Getter(idx); + template IMPLOT_INLINE ImPlotPoint operator[](I idx) const { + ImPlotPoint p = Getter[idx]; p.y = Y; return p; } const _Getter Getter; const double Y; const int Count; + typedef ImPlotPoint value_type; }; template struct GetterLoop { GetterLoop(_Getter getter) : Getter(getter), Count(getter.Count + 1) { } - template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + template IMPLOT_INLINE ImPlotPoint operator[](I idx) const { idx = idx % (Count - 1); - return Getter(idx); + return Getter[idx]; } const _Getter Getter; const int Count; + typedef ImPlotPoint value_type; }; template @@ -633,7 +668,7 @@ struct GetterError { Offset(count ? ImPosMod(offset, count) : 0), Stride(stride) { } - template IMPLOT_INLINE ImPlotPointError operator()(I idx) const { + template IMPLOT_INLINE ImPlotPointError operator[](I idx) const { return ImPlotPointError((double)IndexData(Xs, idx, Count, Offset, Stride), (double)IndexData(Ys, idx, Count, Offset, Stride), (double)IndexData(Neg, idx, Count, Offset, Stride), @@ -646,6 +681,62 @@ struct GetterError { const int Count; const int Offset; const int Stride; + typedef ImPlotPointError value_type; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Color Getters +//----------------------------------------------------------------------------- + +struct GetterConstColor { + GetterConstColor(ImU32 color, float alpha = 1.0f) { + ImU32 col = color; + if (alpha < 1.0f) { + ImVec4 col_vec = ImGui::ColorConvertU32ToFloat4(col); + col_vec.w *= alpha; + col = ImGui::GetColorU32(col_vec); + } + Color = col; + } + template IMPLOT_INLINE ImU32 operator[](I) const { return Color; } + ImU32 Color; +}; + +struct GetterIdxColor { + GetterIdxColor(const ImU32* data, int count, float alpha = 1.0f) : Data(data), Count(count), Alpha(alpha) { } + template IMPLOT_INLINE ImU32 operator[](I idx) const { + IM_ASSERT(idx >= 0 && idx < Count); + ImU32 col = Data[idx]; + if (Alpha < 1.0f) { + ImVec4 col_vec = ImGui::ColorConvertU32ToFloat4(col); + col_vec.w *= Alpha; + col = ImGui::GetColorU32(col_vec); + } + return col; + } + const ImU32* Data; + const int Count; + const float Alpha; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Size Getters +//----------------------------------------------------------------------------- + +struct GetterConstSize { + GetterConstSize(float size) : Size(size) { } + template IMPLOT_INLINE float operator[](I) const { return Size; } + float Size; +}; + +struct GetterIdxSize { + GetterIdxSize(const float* data, int count) : Data(data), Count(count) { } + template IMPLOT_INLINE float operator[](I idx) const { + IM_ASSERT(idx >= 0 && idx < Count); + return Data[idx]; + } + const float* Data; + const int Count; }; //----------------------------------------------------------------------------- @@ -657,7 +748,7 @@ struct Fitter1 { Fitter1(const _Getter1& getter) : Getter(getter) { } void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { for (int i = 0; i < Getter.Count; ++i) { - ImPlotPoint p = Getter(i); + ImPlotPoint p = Getter[i]; x_axis.ExtendFitWith(y_axis, p.x, p.y); y_axis.ExtendFitWith(x_axis, p.y, p.x); } @@ -665,12 +756,30 @@ struct Fitter1 { const _Getter1& Getter; }; +template +struct FitterBubbles1 { + FitterBubbles1(const _Getter1& getter) : Getter(getter) { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + for (int i = 0; i < Getter.Count; ++i) { + ImPlotPoint3D p = Getter(i); + double half_size = p.z; + // Fit left and right edges + x_axis.ExtendFitWith(y_axis, p.x - half_size, p.y); + x_axis.ExtendFitWith(y_axis, p.x + half_size, p.y); + // Fit top and bottom edges + y_axis.ExtendFitWith(x_axis, p.y - half_size, p.x); + y_axis.ExtendFitWith(x_axis, p.y + half_size, p.x); + } + } + const _Getter1& Getter; +}; + template struct FitterX { FitterX(const _Getter1& getter) : Getter(getter) { } void Fit(ImPlotAxis& x_axis, ImPlotAxis&) const { for (int i = 0; i < Getter.Count; ++i) { - ImPlotPoint p = Getter(i); + ImPlotPoint p = Getter[i]; x_axis.ExtendFit(p.x); } } @@ -682,7 +791,7 @@ struct FitterY { FitterY(const _Getter1& getter) : Getter(getter) { } void Fit(ImPlotAxis&, ImPlotAxis& y_axis) const { for (int i = 0; i < Getter.Count; ++i) { - ImPlotPoint p = Getter(i); + ImPlotPoint p = Getter[i]; y_axis.ExtendFit(p.y); } } @@ -694,12 +803,12 @@ struct Fitter2 { Fitter2(const _Getter1& getter1, const _Getter2& getter2) : Getter1(getter1), Getter2(getter2) { } void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { for (int i = 0; i < Getter1.Count; ++i) { - ImPlotPoint p = Getter1(i); + ImPlotPoint p = Getter1[i]; x_axis.ExtendFitWith(y_axis, p.x, p.y); y_axis.ExtendFitWith(x_axis, p.y, p.x); } for (int i = 0; i < Getter2.Count; ++i) { - ImPlotPoint p = Getter2(i); + ImPlotPoint p = Getter2[i]; x_axis.ExtendFitWith(y_axis, p.x, p.y); y_axis.ExtendFitWith(x_axis, p.y, p.x); } @@ -718,8 +827,8 @@ struct FitterBarV { void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { int count = ImMin(Getter1.Count, Getter2.Count); for (int i = 0; i < count; ++i) { - ImPlotPoint p1 = Getter1(i); p1.x -= HalfWidth; - ImPlotPoint p2 = Getter2(i); p2.x += HalfWidth; + ImPlotPoint p1 = Getter1[i]; p1.x -= HalfWidth; + ImPlotPoint p2 = Getter2[i]; p2.x += HalfWidth; x_axis.ExtendFitWith(y_axis, p1.x, p1.y); y_axis.ExtendFitWith(x_axis, p1.y, p1.x); x_axis.ExtendFitWith(y_axis, p2.x, p2.y); @@ -741,8 +850,8 @@ struct FitterBarH { void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { int count = ImMin(Getter1.Count, Getter2.Count); for (int i = 0; i < count; ++i) { - ImPlotPoint p1 = Getter1(i); p1.y -= HalfHeight; - ImPlotPoint p2 = Getter2(i); p2.y += HalfHeight; + ImPlotPoint p1 = Getter1[i]; p1.y -= HalfHeight; + ImPlotPoint p2 = Getter2[i]; p2.y += HalfHeight; x_axis.ExtendFitWith(y_axis, p1.x, p1.y); y_axis.ExtendFitWith(x_axis, p1.y, p1.x); x_axis.ExtendFitWith(y_axis, p2.x, p2.y); @@ -864,139 +973,143 @@ struct RendererBase { const int VtxConsumed; }; -template +template struct RendererLineStrip : RendererBase { - RendererLineStrip(const _Getter& getter, ImU32 col, float weight) : + RendererLineStrip(const _Getter& getter, const _GetterColor& getter_color, float weight) : RendererBase(getter.Count - 1, 6, 4), Getter(getter), - Col(col), + GetterColor(getter_color), HalfWeight(ImMax(1.0f,weight)*0.5f) { - P1 = this->Transformer(Getter(0)); + P1 = this->Transformer(Getter[0]); } void Init(ImDrawList& draw_list) const { GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 P2 = this->Transformer(Getter[prim + 1]); if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { P1 = P2; return false; } - PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + ImU32 col = GetterColor[prim]; + PrimLine(draw_list,P1,P2,HalfWeight,col,UV0,UV1); P1 = P2; return true; } const _Getter& Getter; - const ImU32 Col; + const _GetterColor& GetterColor; mutable float HalfWeight; mutable ImVec2 P1; mutable ImVec2 UV0; mutable ImVec2 UV1; }; -template +template struct RendererLineStripSkip : RendererBase { - RendererLineStripSkip(const _Getter& getter, ImU32 col, float weight) : + RendererLineStripSkip(const _Getter& getter, const _GetterColor& getter_color, float weight) : RendererBase(getter.Count - 1, 6, 4), Getter(getter), - Col(col), + GetterColor(getter_color), HalfWeight(ImMax(1.0f,weight)*0.5f) { - P1 = this->Transformer(Getter(0)); + P1 = this->Transformer(Getter[0]); } void Init(ImDrawList& draw_list) const { GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 P2 = this->Transformer(Getter[prim + 1]); if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { if (!ImNan(P2.x) && !ImNan(P2.y)) P1 = P2; return false; } - PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + ImU32 col = GetterColor[prim]; + PrimLine(draw_list,P1,P2,HalfWeight,col,UV0,UV1); if (!ImNan(P2.x) && !ImNan(P2.y)) P1 = P2; return true; } const _Getter& Getter; - const ImU32 Col; + const _GetterColor& GetterColor; mutable float HalfWeight; mutable ImVec2 P1; mutable ImVec2 UV0; mutable ImVec2 UV1; }; -template +template struct RendererLineSegments1 : RendererBase { - RendererLineSegments1(const _Getter& getter, ImU32 col, float weight) : + RendererLineSegments1(const _Getter& getter, const _GetterColor& getter_color, float weight) : RendererBase(getter.Count / 2, 6, 4), Getter(getter), - Col(col), + GetterColor(getter_color), HalfWeight(ImMax(1.0f,weight)*0.5f) { } void Init(ImDrawList& draw_list) const { GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P1 = this->Transformer(Getter(prim*2+0)); - ImVec2 P2 = this->Transformer(Getter(prim*2+1)); + ImVec2 P1 = this->Transformer(Getter[prim*2+0]); + ImVec2 P2 = this->Transformer(Getter[prim*2+1]); if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) return false; - PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + ImU32 col = GetterColor[prim*2]; + PrimLine(draw_list,P1,P2,HalfWeight,col,UV0,UV1); return true; } const _Getter& Getter; - const ImU32 Col; + const _GetterColor& GetterColor; mutable float HalfWeight; mutable ImVec2 UV0; mutable ImVec2 UV1; }; -template +template struct RendererLineSegments2 : RendererBase { - RendererLineSegments2(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, float weight) : - RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + RendererLineSegments2(const _Getter1& getter1, const _Getter2& getter2, const _GetterColor& getter_color, float weight) : + RendererBase(ImMin(getter1.Count, getter2.Count), 6, 4), Getter1(getter1), Getter2(getter2), - Col(col), + GetterColor(getter_color), HalfWeight(ImMax(1.0f,weight)*0.5f) {} void Init(ImDrawList& draw_list) const { GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P1 = this->Transformer(Getter1(prim)); - ImVec2 P2 = this->Transformer(Getter2(prim)); + ImVec2 P1 = this->Transformer(Getter1[prim]); + ImVec2 P2 = this->Transformer(Getter2[prim]); if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) return false; - PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + ImU32 col = GetterColor[prim]; + PrimLine(draw_list,P1,P2,HalfWeight,col,UV0,UV1); return true; } const _Getter1& Getter1; const _Getter2& Getter2; - const ImU32 Col; + const _GetterColor& GetterColor; mutable float HalfWeight; mutable ImVec2 UV0; mutable ImVec2 UV1; }; -template +template struct RendererBarsFillV : RendererBase { - RendererBarsFillV(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double width) : - RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + RendererBarsFillV(const _Getter1& getter1, const _Getter2& getter2, const _GetterColor& getter_color, double width) : + RendererBase(ImMin(getter1.Count, getter2.Count), 6, 4), Getter1(getter1), Getter2(getter2), - Col(col), + GetterColor(getter_color), HalfWidth(width/2) {} void Init(ImDrawList& draw_list) const { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImPlotPoint p1 = Getter1(prim); - ImPlotPoint p2 = Getter2(prim); + ImPlotPoint p1 = Getter1[prim]; + ImPlotPoint p2 = Getter2[prim]; p1.x += HalfWidth; p2.x -= HalfWidth; ImVec2 P1 = this->Transformer(p1); @@ -1010,31 +1123,32 @@ struct RendererBarsFillV : RendererBase { ImVec2 PMax = ImMax(P1, P2); if (!cull_rect.Overlaps(ImRect(PMin, PMax))) return false; - PrimRectFill(draw_list,PMin,PMax,Col,UV); + ImU32 col = GetterColor[prim]; + PrimRectFill(draw_list,PMin,PMax,col,UV); return true; } const _Getter1& Getter1; const _Getter2& Getter2; - const ImU32 Col; + const _GetterColor& GetterColor; const double HalfWidth; mutable ImVec2 UV; }; -template +template struct RendererBarsFillH : RendererBase { - RendererBarsFillH(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double height) : - RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + RendererBarsFillH(const _Getter1& getter1, const _Getter2& getter2, const _GetterColor& getter_color, double height) : + RendererBase(ImMin(getter1.Count, getter2.Count), 6, 4), Getter1(getter1), Getter2(getter2), - Col(col), + GetterColor(getter_color), HalfHeight(height/2) {} void Init(ImDrawList& draw_list) const { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImPlotPoint p1 = Getter1(prim); - ImPlotPoint p2 = Getter2(prim); + ImPlotPoint p1 = Getter1[prim]; + ImPlotPoint p2 = Getter2[prim]; p1.y += HalfHeight; p2.y -= HalfHeight; ImVec2 P1 = this->Transformer(p1); @@ -1048,23 +1162,24 @@ struct RendererBarsFillH : RendererBase { ImVec2 PMax = ImMax(P1, P2); if (!cull_rect.Overlaps(ImRect(PMin, PMax))) return false; - PrimRectFill(draw_list,PMin,PMax,Col,UV); + ImU32 col = GetterColor[prim]; + PrimRectFill(draw_list,PMin,PMax,col,UV); return true; } const _Getter1& Getter1; const _Getter2& Getter2; - const ImU32 Col; + const _GetterColor& GetterColor; const double HalfHeight; mutable ImVec2 UV; }; -template +template struct RendererBarsLineV : RendererBase { - RendererBarsLineV(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double width, float weight) : - RendererBase(ImMin(getter1.Count, getter1.Count), 24, 8), + RendererBarsLineV(const _Getter1& getter1, const _Getter2& getter2, const _GetterColor& getter_color, double width, float weight) : + RendererBase(ImMin(getter1.Count, getter2.Count), 24, 8), Getter1(getter1), Getter2(getter2), - Col(col), + GetterColor(getter_color), HalfWidth(width/2), Weight(weight) {} @@ -1072,8 +1187,8 @@ struct RendererBarsLineV : RendererBase { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImPlotPoint p1 = Getter1(prim); - ImPlotPoint p2 = Getter2(prim); + ImPlotPoint p1 = Getter1[prim]; + ImPlotPoint p2 = Getter2[prim]; p1.x += HalfWidth; p2.x -= HalfWidth; ImVec2 P1 = this->Transformer(p1); @@ -1087,24 +1202,25 @@ struct RendererBarsLineV : RendererBase { ImVec2 PMax = ImMax(P1, P2); if (!cull_rect.Overlaps(ImRect(PMin, PMax))) return false; - PrimRectLine(draw_list,PMin,PMax,Weight,Col,UV); + ImU32 col = GetterColor[prim]; + PrimRectLine(draw_list,PMin,PMax,Weight,col,UV); return true; } const _Getter1& Getter1; const _Getter2& Getter2; - const ImU32 Col; + const _GetterColor& GetterColor; const double HalfWidth; const float Weight; mutable ImVec2 UV; }; -template +template struct RendererBarsLineH : RendererBase { - RendererBarsLineH(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double height, float weight) : - RendererBase(ImMin(getter1.Count, getter1.Count), 24, 8), + RendererBarsLineH(const _Getter1& getter1, const _Getter2& getter2, const _GetterColor& getter_color, double height, float weight) : + RendererBase(ImMin(getter1.Count, getter2.Count), 24, 8), Getter1(getter1), Getter2(getter2), - Col(col), + GetterColor(getter_color), HalfHeight(height/2), Weight(weight) {} @@ -1112,8 +1228,8 @@ struct RendererBarsLineH : RendererBase { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImPlotPoint p1 = Getter1(prim); - ImPlotPoint p2 = Getter2(prim); + ImPlotPoint p1 = Getter1[prim]; + ImPlotPoint p2 = Getter2[prim]; p1.y += HalfHeight; p2.y -= HalfHeight; ImVec2 P1 = this->Transformer(p1); @@ -1127,139 +1243,144 @@ struct RendererBarsLineH : RendererBase { ImVec2 PMax = ImMax(P1, P2); if (!cull_rect.Overlaps(ImRect(PMin, PMax))) return false; - PrimRectLine(draw_list,PMin,PMax,Weight,Col,UV); + ImU32 col = GetterColor[prim]; + PrimRectLine(draw_list,PMin,PMax,Weight,col,UV); return true; } const _Getter1& Getter1; const _Getter2& Getter2; - const ImU32 Col; + const _GetterColor& GetterColor; const double HalfHeight; const float Weight; mutable ImVec2 UV; }; -template +template struct RendererStairsPre : RendererBase { - RendererStairsPre(const _Getter& getter, ImU32 col, float weight) : + RendererStairsPre(const _Getter& getter, const _GetterColor& getter_color, float weight) : RendererBase(getter.Count - 1, 12, 8), Getter(getter), - Col(col), + GetterColor(getter_color), HalfWeight(ImMax(1.0f,weight)*0.5f) { - P1 = this->Transformer(Getter(0)); + P1 = this->Transformer(Getter[0]); } void Init(ImDrawList& draw_list) const { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 P2 = this->Transformer(Getter[prim + 1]); if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { P1 = P2; return false; } - PrimRectFill(draw_list, ImVec2(P1.x - HalfWeight, P1.y), ImVec2(P1.x + HalfWeight, P2.y), Col, UV); - PrimRectFill(draw_list, ImVec2(P1.x, P2.y + HalfWeight), ImVec2(P2.x, P2.y - HalfWeight), Col, UV); + ImU32 col = GetterColor[prim]; + PrimRectFill(draw_list, ImVec2(P1.x - HalfWeight, P1.y), ImVec2(P1.x + HalfWeight, P2.y), col, UV); + PrimRectFill(draw_list, ImVec2(P1.x, P2.y + HalfWeight), ImVec2(P2.x, P2.y - HalfWeight), col, UV); P1 = P2; return true; } const _Getter& Getter; - const ImU32 Col; + const _GetterColor& GetterColor; mutable float HalfWeight; mutable ImVec2 P1; mutable ImVec2 UV; }; -template +template struct RendererStairsPost : RendererBase { - RendererStairsPost(const _Getter& getter, ImU32 col, float weight) : + RendererStairsPost(const _Getter& getter, const _GetterColor& getter_color, float weight) : RendererBase(getter.Count - 1, 12, 8), Getter(getter), - Col(col), + GetterColor(getter_color), HalfWeight(ImMax(1.0f,weight) * 0.5f) { - P1 = this->Transformer(Getter(0)); + P1 = this->Transformer(Getter[0]); } void Init(ImDrawList& draw_list) const { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 P2 = this->Transformer(Getter[prim + 1]); if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { P1 = P2; return false; } - PrimRectFill(draw_list, ImVec2(P1.x, P1.y + HalfWeight), ImVec2(P2.x, P1.y - HalfWeight), Col, UV); - PrimRectFill(draw_list, ImVec2(P2.x - HalfWeight, P2.y), ImVec2(P2.x + HalfWeight, P1.y), Col, UV); + ImU32 col = GetterColor[prim]; + PrimRectFill(draw_list, ImVec2(P1.x, P1.y + HalfWeight), ImVec2(P2.x, P1.y - HalfWeight), col, UV); + PrimRectFill(draw_list, ImVec2(P2.x - HalfWeight, P2.y), ImVec2(P2.x + HalfWeight, P1.y), col, UV); P1 = P2; return true; } const _Getter& Getter; - const ImU32 Col; + const _GetterColor& GetterColor; mutable float HalfWeight; mutable ImVec2 P1; mutable ImVec2 UV; }; -template +template struct RendererStairsPreShaded : RendererBase { - RendererStairsPreShaded(const _Getter& getter, ImU32 col) : + RendererStairsPreShaded(const _Getter& getter, const _GetterColor& getter_color) : RendererBase(getter.Count - 1, 6, 4), Getter(getter), - Col(col) + GetterColor(getter_color) { - P1 = this->Transformer(Getter(0)); + P1 = this->Transformer(Getter[0]); Y0 = this->Transformer(ImPlotPoint(0,0)).y; } void Init(ImDrawList& draw_list) const { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 P2 = this->Transformer(Getter[prim + 1]); ImVec2 PMin(ImMin(P1.x, P2.x), ImMin(Y0, P2.y)); ImVec2 PMax(ImMax(P1.x, P2.x), ImMax(Y0, P2.y)); if (!cull_rect.Overlaps(ImRect(PMin, PMax))) { P1 = P2; return false; } - PrimRectFill(draw_list, PMin, PMax, Col, UV); + ImU32 col = GetterColor[prim]; + PrimRectFill(draw_list, PMin, PMax, col, UV); P1 = P2; return true; } const _Getter& Getter; - const ImU32 Col; + const _GetterColor& GetterColor; float Y0; mutable ImVec2 P1; mutable ImVec2 UV; }; -template +template struct RendererStairsPostShaded : RendererBase { - RendererStairsPostShaded(const _Getter& getter, ImU32 col) : + RendererStairsPostShaded(const _Getter& getter, const _GetterColor& getter_color) : RendererBase(getter.Count - 1, 6, 4), Getter(getter), - Col(col) + GetterColor(getter_color) { - P1 = this->Transformer(Getter(0)); + P1 = this->Transformer(Getter[0]); Y0 = this->Transformer(ImPlotPoint(0,0)).y; } void Init(ImDrawList& draw_list) const { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 P2 = this->Transformer(Getter[prim + 1]); ImVec2 PMin(ImMin(P1.x, P2.x), ImMin(P1.y, Y0)); ImVec2 PMax(ImMax(P1.x, P2.x), ImMax(P1.y, Y0)); if (!cull_rect.Overlaps(ImRect(PMin, PMax))) { P1 = P2; return false; } - PrimRectFill(draw_list, PMin, PMax, Col, UV); + ImU32 col = GetterColor[prim]; + PrimRectFill(draw_list, PMin, PMax, col, UV); P1 = P2; return true; } const _Getter& Getter; - const ImU32 Col; + const _GetterColor& GetterColor; float Y0; mutable ImVec2 P1; mutable ImVec2 UV; @@ -1267,46 +1388,47 @@ struct RendererStairsPostShaded : RendererBase { -template +template struct RendererShaded : RendererBase { - RendererShaded(const _Getter1& getter1, const _Getter2& getter2, ImU32 col) : + RendererShaded(const _Getter1& getter1, const _Getter2& getter2, const _GetterColor& getter_color) : RendererBase(ImMin(getter1.Count, getter2.Count) - 1, 6, 5), Getter1(getter1), Getter2(getter2), - Col(col) + GetterColor(getter_color) { - P11 = this->Transformer(Getter1(0)); - P12 = this->Transformer(Getter2(0)); + P11 = this->Transformer(Getter1[0]); + P12 = this->Transformer(Getter2[0]); } void Init(ImDrawList& draw_list) const { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - ImVec2 P21 = this->Transformer(Getter1(prim+1)); - ImVec2 P22 = this->Transformer(Getter2(prim+1)); + ImVec2 P21 = this->Transformer(Getter1[prim+1]); + ImVec2 P22 = this->Transformer(Getter2[prim+1]); ImRect rect(ImMin(ImMin(ImMin(P11,P12),P21),P22), ImMax(ImMax(ImMax(P11,P12),P21),P22)); if (!cull_rect.Overlaps(rect)) { P11 = P21; P12 = P22; return false; } + ImU32 col = GetterColor[prim]; const int intersect = (P11.y > P12.y && P22.y > P21.y) || (P12.y > P11.y && P21.y > P22.y); const ImVec2 intersection = intersect == 0 ? ImVec2(0,0) : Intersection(P11,P21,P12,P22); draw_list._VtxWritePtr[0].pos = P11; draw_list._VtxWritePtr[0].uv = UV; - draw_list._VtxWritePtr[0].col = Col; + draw_list._VtxWritePtr[0].col = col; draw_list._VtxWritePtr[1].pos = P21; draw_list._VtxWritePtr[1].uv = UV; - draw_list._VtxWritePtr[1].col = Col; + draw_list._VtxWritePtr[1].col = col; draw_list._VtxWritePtr[2].pos = intersection; draw_list._VtxWritePtr[2].uv = UV; - draw_list._VtxWritePtr[2].col = Col; + draw_list._VtxWritePtr[2].col = col; draw_list._VtxWritePtr[3].pos = P12; draw_list._VtxWritePtr[3].uv = UV; - draw_list._VtxWritePtr[3].col = Col; + draw_list._VtxWritePtr[3].col = col; draw_list._VtxWritePtr[4].pos = P22; draw_list._VtxWritePtr[4].uv = UV; - draw_list._VtxWritePtr[4].col = Col; + draw_list._VtxWritePtr[4].col = col; draw_list._VtxWritePtr += 5; draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1 + intersect); @@ -1322,7 +1444,7 @@ struct RendererShaded : RendererBase { } const _Getter1& Getter1; const _Getter2& Getter2; - const ImU32 Col; + const _GetterColor& GetterColor; mutable ImVec2 P11; mutable ImVec2 P12; mutable ImVec2 UV; @@ -1344,7 +1466,7 @@ struct RendererRectC : RendererBase { UV = draw_list._Data->TexUvWhitePixel; } IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { - RectC rect = Getter(prim); + RectC rect = Getter[prim]; ImVec2 P1 = this->Transformer(rect.Pos.x - rect.HalfSize.x , rect.Pos.y - rect.HalfSize.y); ImVec2 P2 = this->Transformer(rect.Pos.x + rect.HalfSize.x , rect.Pos.y + rect.HalfSize.y); if ((rect.Color & IM_COL32_A_MASK) == 0 || !cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) @@ -1414,31 +1536,40 @@ void RenderPrimitives2(const _Getter1& getter1, const _Getter2& getter2, Args... RenderPrimitivesEx(_Renderer<_Getter1,_Getter2>(getter1,getter2,args...), draw_list, cull_rect); } +template