blob: ab95291d504c969824a95ccea5ef147508496673 [file] [log] [blame]
// 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(
__weak FlutterEngine* engine,
__weak FlutterViewController* view_controller)
: engine_(engine), 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(engine_, @"Flutter engine should not be deallocated");
NSCAssert(engine_.viewController.viewLoaded, @"Flutter view must be loaded");
return engine_.viewController.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;
};
NSCAssert(engine_, @"Flutter engine should not be deallocated");
auto bridge_ptr = engine_.accessibilityBridge.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(engine_, @"Flutter engine should not be deallocated");
NSCAssert(engine_.viewController.viewLoaded, @"Flutter view must be loaded.");
NSRect ns_view_bounds =
[engine_.viewController.flutterView convertRectFromBacking:ns_local_bounds];
NSRect ns_window_bounds = [engine_.viewController.flutterView convertRect:ns_view_bounds
toView:nil];
NSRect ns_screen_bounds =
[[engine_.viewController.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