| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.h" |
| |
| #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h" |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h" |
| #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" |
| |
| #include "flutter/shell/platform/common/accessibility_bridge.h" |
| #include "flutter/third_party/accessibility/ax/ax_action_data.h" |
| #include "flutter/third_party/accessibility/ax/ax_node_position.h" |
| #include "flutter/third_party/accessibility/ax/platform/ax_platform_node.h" |
| #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_base.h" |
| #include "flutter/third_party/accessibility/base/string_utils.h" |
| #include "flutter/third_party/accessibility/gfx/geometry/rect_conversions.h" |
| #include "flutter/third_party/accessibility/gfx/mac/coordinate_conversion.h" |
| |
| namespace flutter { // namespace |
| |
| FlutterPlatformNodeDelegateMac::FlutterPlatformNodeDelegateMac( |
| std::weak_ptr<AccessibilityBridge> bridge, |
| __weak FlutterViewController* view_controller) |
| : bridge_(std::move(bridge)), view_controller_(view_controller) {} |
| |
| void FlutterPlatformNodeDelegateMac::Init(std::weak_ptr<OwnerBridge> bridge, ui::AXNode* node) { |
| FlutterPlatformNodeDelegate::Init(bridge, node); |
| if (GetData().IsTextField()) { |
| ax_platform_node_ = new FlutterTextPlatformNode(this, view_controller_); |
| } else { |
| ax_platform_node_ = ui::AXPlatformNode::Create(this); |
| } |
| NSCAssert(ax_platform_node_, @"Failed to create platform node."); |
| } |
| |
| FlutterPlatformNodeDelegateMac::~FlutterPlatformNodeDelegateMac() { |
| // Destroy() also calls delete on itself. |
| ax_platform_node_->Destroy(); |
| } |
| |
| gfx::NativeViewAccessible FlutterPlatformNodeDelegateMac::GetNativeViewAccessible() { |
| NSCAssert(ax_platform_node_, @"Platform node does not exist."); |
| return ax_platform_node_->GetNativeViewAccessible(); |
| } |
| |
| gfx::NativeViewAccessible FlutterPlatformNodeDelegateMac::GetParent() { |
| gfx::NativeViewAccessible parent = FlutterPlatformNodeDelegate::GetParent(); |
| if (!parent) { |
| NSCAssert(view_controller_.viewLoaded, @"Flutter view must be loaded"); |
| return view_controller_.flutterView; |
| } |
| return parent; |
| } |
| |
| gfx::Rect FlutterPlatformNodeDelegateMac::GetBoundsRect( |
| const ui::AXCoordinateSystem coordinate_system, |
| const ui::AXClippingBehavior clipping_behavior, |
| ui::AXOffscreenResult* offscreen_result) const { |
| gfx::Rect local_bounds = FlutterPlatformNodeDelegate::GetBoundsRect( |
| coordinate_system, clipping_behavior, offscreen_result); |
| gfx::RectF local_bounds_f(local_bounds); |
| gfx::RectF screen_bounds = ConvertBoundsFromLocalToScreen(local_bounds_f); |
| return gfx::ToEnclosingRect(ConvertBoundsFromScreenToGlobal(screen_bounds)); |
| } |
| |
| gfx::NativeViewAccessible FlutterPlatformNodeDelegateMac::GetNSWindow() { |
| FlutterAppDelegate* appDelegate = (FlutterAppDelegate*)[NSApp delegate]; |
| return appDelegate.mainFlutterWindow; |
| } |
| |
| std::string FlutterPlatformNodeDelegateMac::GetLiveRegionText() const { |
| if (GetAXNode()->IsIgnored()) { |
| return ""; |
| } |
| |
| std::string text = GetData().GetStringAttribute(ax::mojom::StringAttribute::kName); |
| if (!text.empty()) { |
| return text; |
| }; |
| auto bridge_ptr = bridge_.lock(); |
| NSCAssert(bridge_ptr, @"Accessibility bridge in flutter engine must not be null."); |
| for (int32_t child : GetData().child_ids) { |
| auto delegate_child = bridge_ptr->GetFlutterPlatformNodeDelegateFromID(child).lock(); |
| if (!delegate_child) { |
| continue; |
| } |
| text += std::static_pointer_cast<FlutterPlatformNodeDelegateMac>(delegate_child) |
| ->GetLiveRegionText(); |
| } |
| return text; |
| } |
| |
| gfx::RectF FlutterPlatformNodeDelegateMac::ConvertBoundsFromLocalToScreen( |
| const gfx::RectF& local_bounds) const { |
| // Converts to NSRect to use NSView rect conversion. |
| NSRect ns_local_bounds = |
| NSMakeRect(local_bounds.x(), local_bounds.y(), local_bounds.width(), local_bounds.height()); |
| // The macOS XY coordinates start at bottom-left and increase toward top-right, |
| // which is different from the Flutter's XY coordinates that start at top-left |
| // increasing to bottom-right. Therefore, this method needs to flip the y coordinate when |
| // it converts the bounds from flutter coordinates to macOS coordinates. |
| ns_local_bounds.origin.y = -ns_local_bounds.origin.y - ns_local_bounds.size.height; |
| |
| NSCAssert(view_controller_.viewLoaded, @"Flutter view must be loaded."); |
| NSRect ns_view_bounds = [view_controller_.flutterView convertRectFromBacking:ns_local_bounds]; |
| NSRect ns_window_bounds = [view_controller_.flutterView convertRect:ns_view_bounds toView:nil]; |
| NSRect ns_screen_bounds = |
| [[view_controller_.flutterView window] convertRectToScreen:ns_window_bounds]; |
| gfx::RectF screen_bounds(ns_screen_bounds.origin.x, ns_screen_bounds.origin.y, |
| ns_screen_bounds.size.width, ns_screen_bounds.size.height); |
| return screen_bounds; |
| } |
| |
| gfx::RectF FlutterPlatformNodeDelegateMac::ConvertBoundsFromScreenToGlobal( |
| const gfx::RectF& screen_bounds) const { |
| // The VoiceOver seems to only accept bounds that are relative to primary screen. |
| // Thus, this method uses [[NSScreen screens] firstObject] instead of [NSScreen mainScreen]. |
| NSScreen* screen = [[NSScreen screens] firstObject]; |
| NSRect primary_screen_bounds = [screen frame]; |
| // The screen is flipped against y axis. |
| float flipped_y = primary_screen_bounds.size.height - screen_bounds.y() - screen_bounds.height(); |
| return {screen_bounds.x(), flipped_y, screen_bounds.width(), screen_bounds.height()}; |
| } |
| |
| } // namespace flutter |