|
34 | 34 | return flutter::SemanticsAction::kScrollUp; |
35 | 35 | } |
36 | 36 |
|
37 | | -SkM44 GetGlobalTransform(SemanticsObject* reference) { |
38 | | - SkM44 globalTransform = [reference node].transform; |
39 | | - for (SemanticsObject* parent = [reference parent]; parent; parent = parent.parent) { |
40 | | - globalTransform = parent.node.transform * globalTransform; |
41 | | - } |
42 | | - return globalTransform; |
43 | | -} |
44 | | - |
45 | | -SkPoint ApplyTransform(SkPoint& point, const SkM44& transform) { |
46 | | - SkV4 vector = transform.map(point.x(), point.y(), 0, 1); |
47 | | - return SkPoint::Make(vector.x / vector.w, vector.y / vector.w); |
48 | | -} |
49 | | - |
50 | | -CGPoint ConvertPointToGlobal(SemanticsObject* reference, CGPoint local_point) { |
51 | | - SkM44 globalTransform = GetGlobalTransform(reference); |
52 | | - SkPoint point = SkPoint::Make(local_point.x, local_point.y); |
53 | | - point = ApplyTransform(point, globalTransform); |
54 | | - // `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in |
55 | | - // the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to |
56 | | - // convert. |
57 | | - CGFloat scale = [[[reference bridge]->view() window] screen].scale; |
58 | | - auto result = CGPointMake(point.x() / scale, point.y() / scale); |
59 | | - return [[reference bridge]->view() convertPoint:result toView:nil]; |
60 | | -} |
61 | | - |
62 | | -CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) { |
63 | | - SkM44 globalTransform = GetGlobalTransform(reference); |
64 | | - |
65 | | - SkPoint quad[4] = { |
66 | | - SkPoint::Make(local_rect.origin.x, local_rect.origin.y), // top left |
67 | | - SkPoint::Make(local_rect.origin.x + local_rect.size.width, local_rect.origin.y), // top right |
68 | | - SkPoint::Make(local_rect.origin.x + local_rect.size.width, |
69 | | - local_rect.origin.y + local_rect.size.height), // bottom right |
70 | | - SkPoint::Make(local_rect.origin.x, |
71 | | - local_rect.origin.y + local_rect.size.height) // bottom left |
72 | | - }; |
73 | | - for (auto& point : quad) { |
74 | | - point = ApplyTransform(point, globalTransform); |
75 | | - } |
76 | | - SkRect rect; |
77 | | - NSCAssert(rect.setBoundsCheck(quad, 4), @"Transformed points can't form a rect"); |
78 | | - rect.setBounds(quad, 4); |
79 | | - |
80 | | - // `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in |
81 | | - // the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to |
82 | | - // convert. |
83 | | - CGFloat scale = [[[reference bridge]->view() window] screen].scale; |
84 | | - auto result = |
85 | | - CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale); |
86 | | - return UIAccessibilityConvertFrameToScreenCoordinates(result, [reference bridge]->view()); |
87 | | -} |
88 | | - |
89 | 37 | } // namespace |
90 | 38 |
|
91 | 39 | @implementation FlutterSwitchSemanticsObject { |
@@ -140,152 +88,6 @@ - (UIAccessibilityTraits)accessibilityTraits { |
140 | 88 |
|
141 | 89 | @end // FlutterSwitchSemanticsObject |
142 | 90 |
|
143 | | -@interface FlutterScrollableSemanticsObject () |
144 | | -@property(nonatomic, strong) SemanticsObject* semanticsObject; |
145 | | -@end |
146 | | - |
147 | | -@implementation FlutterScrollableSemanticsObject { |
148 | | - fml::scoped_nsobject<SemanticsObjectContainer> _container; |
149 | | -} |
150 | | - |
151 | | -- (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject { |
152 | | - self = [super initWithFrame:CGRectZero]; |
153 | | - if (self) { |
154 | | - _semanticsObject = [semanticsObject retain]; |
155 | | - [semanticsObject.bridge->view() addSubview:self]; |
156 | | - } |
157 | | - return self; |
158 | | -} |
159 | | - |
160 | | -- (void)dealloc { |
161 | | - _container.get().semanticsObject = nil; |
162 | | - [_semanticsObject release]; |
163 | | - [self removeFromSuperview]; |
164 | | - [super dealloc]; |
165 | | -} |
166 | | - |
167 | | -- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { |
168 | | - return nil; |
169 | | -} |
170 | | - |
171 | | -- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel { |
172 | | - NSMethodSignature* result = [super methodSignatureForSelector:sel]; |
173 | | - if (!result) { |
174 | | - result = [_semanticsObject methodSignatureForSelector:sel]; |
175 | | - } |
176 | | - return result; |
177 | | -} |
178 | | - |
179 | | -- (void)forwardInvocation:(NSInvocation*)anInvocation { |
180 | | - [anInvocation setTarget:_semanticsObject]; |
181 | | - [anInvocation invoke]; |
182 | | -} |
183 | | - |
184 | | -- (void)accessibilityBridgeDidFinishUpdate { |
185 | | - // In order to make iOS think this UIScrollView is scrollable, the following |
186 | | - // requirements must be true. |
187 | | - // 1. contentSize must be bigger than the frame size. |
188 | | - // 2. The scrollable isAccessibilityElement must return YES |
189 | | - // |
190 | | - // Once the requirements are met, the iOS uses contentOffset to determine |
191 | | - // what scroll actions are available. e.g. If the view scrolls vertically and |
192 | | - // contentOffset is 0.0, only the scroll down action is available. |
193 | | - [self setFrame:[_semanticsObject accessibilityFrame]]; |
194 | | - [self setContentSize:[self contentSizeInternal]]; |
195 | | - [self setContentOffset:[self contentOffsetInternal] animated:NO]; |
196 | | - if (self.contentSize.width > self.frame.size.width || |
197 | | - self.contentSize.height > self.frame.size.height) { |
198 | | - self.isAccessibilityElement = YES; |
199 | | - } else { |
200 | | - self.isAccessibilityElement = NO; |
201 | | - ; |
202 | | - } |
203 | | -} |
204 | | - |
205 | | -- (void)setChildren:(NSArray<SemanticsObject*>*)children { |
206 | | - [_semanticsObject setChildren:children]; |
207 | | - // The children's parent is pointing to _semanticsObject, need to manually |
208 | | - // set it this object. |
209 | | - for (SemanticsObject* child in _semanticsObject.children) { |
210 | | - child.parent = (SemanticsObject*)self; |
211 | | - } |
212 | | -} |
213 | | - |
214 | | -- (id)accessibilityContainer { |
215 | | - if (_container == nil) { |
216 | | - _container.reset([[SemanticsObjectContainer alloc] |
217 | | - initWithSemanticsObject:(SemanticsObject*)self |
218 | | - bridge:[_semanticsObject bridge]]); |
219 | | - } |
220 | | - return _container.get(); |
221 | | -} |
222 | | - |
223 | | -// private methods |
224 | | - |
225 | | -- (CGSize)contentSizeInternal { |
226 | | - CGRect result; |
227 | | - const SkRect& rect = _semanticsObject.node.rect; |
228 | | - if (_semanticsObject.node.actions & flutter::kVerticalScrollSemanticsActions) { |
229 | | - result = CGRectMake(rect.x(), rect.y(), rect.width(), |
230 | | - rect.height() + _semanticsObject.node.scrollExtentMax); |
231 | | - } else if (_semanticsObject.node.actions & flutter::kHorizontalScrollSemanticsActions) { |
232 | | - result = CGRectMake(rect.x(), rect.y(), rect.width() + _semanticsObject.node.scrollExtentMax, |
233 | | - rect.height()); |
234 | | - } else { |
235 | | - result = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); |
236 | | - } |
237 | | - return ConvertRectToGlobal(_semanticsObject, result).size; |
238 | | -} |
239 | | - |
240 | | -- (CGPoint)contentOffsetInternal { |
241 | | - CGPoint result; |
242 | | - CGPoint origin = self.frame.origin; |
243 | | - const SkRect& rect = _semanticsObject.node.rect; |
244 | | - if (_semanticsObject.node.actions & flutter::kVerticalScrollSemanticsActions) { |
245 | | - result = ConvertPointToGlobal( |
246 | | - _semanticsObject, CGPointMake(rect.x(), rect.y() + _semanticsObject.node.scrollPosition)); |
247 | | - } else if (_semanticsObject.node.actions & flutter::kHorizontalScrollSemanticsActions) { |
248 | | - result = ConvertPointToGlobal( |
249 | | - _semanticsObject, CGPointMake(rect.x() + _semanticsObject.node.scrollPosition, rect.y())); |
250 | | - } else { |
251 | | - result = origin; |
252 | | - } |
253 | | - return CGPointMake(result.x - origin.x, result.y - origin.y); |
254 | | -} |
255 | | - |
256 | | -// The following methods are explicitly forwarded to the wrapped SemanticsObject because the |
257 | | -// forwarding logic above doesn't apply to them since they are also implemented in the |
258 | | -// UIScrollView class, the base class. |
259 | | - |
260 | | -- (BOOL)accessibilityActivate { |
261 | | - return [_semanticsObject accessibilityActivate]; |
262 | | -} |
263 | | - |
264 | | -- (void)accessibilityIncrement { |
265 | | - [_semanticsObject accessibilityIncrement]; |
266 | | -} |
267 | | - |
268 | | -- (void)accessibilityDecrement { |
269 | | - [_semanticsObject accessibilityDecrement]; |
270 | | -} |
271 | | - |
272 | | -- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction { |
273 | | - return [_semanticsObject accessibilityScroll:direction]; |
274 | | -} |
275 | | - |
276 | | -- (BOOL)accessibilityPerformEscape { |
277 | | - return [_semanticsObject accessibilityPerformEscape]; |
278 | | -} |
279 | | - |
280 | | -- (void)accessibilityElementDidBecomeFocused { |
281 | | - [_semanticsObject accessibilityElementDidBecomeFocused]; |
282 | | -} |
283 | | - |
284 | | -- (void)accessibilityElementDidLoseFocus { |
285 | | - [_semanticsObject accessibilityElementDidLoseFocus]; |
286 | | -} |
287 | | -@end // FlutterScrollableSemanticsObject |
288 | | - |
289 | 91 | @implementation FlutterCustomAccessibilityAction { |
290 | 92 | } |
291 | 93 | @end |
@@ -372,9 +174,6 @@ - (void)setSemanticsNode:(const flutter::SemanticsNode*)node { |
372 | 174 | _node = *node; |
373 | 175 | } |
374 | 176 |
|
375 | | -- (void)accessibilityBridgeDidFinishUpdate { /* Do nothing by default */ |
376 | | -} |
377 | | - |
378 | 177 | /** |
379 | 178 | * Whether calling `setSemanticsNode:` with `node` would cause a layout change. |
380 | 179 | */ |
@@ -599,9 +398,27 @@ - (CGRect)accessibilityFrame { |
599 | 398 | } |
600 | 399 |
|
601 | 400 | - (CGRect)globalRect { |
602 | | - const SkRect& rect = [self node].rect; |
603 | | - CGRect localRect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); |
604 | | - return ConvertRectToGlobal(self, localRect); |
| 401 | + SkM44 globalTransform = [self node].transform; |
| 402 | + for (SemanticsObject* parent = [self parent]; parent; parent = parent.parent) { |
| 403 | + globalTransform = parent.node.transform * globalTransform; |
| 404 | + } |
| 405 | + |
| 406 | + SkPoint quad[4]; |
| 407 | + [self node].rect.toQuad(quad); |
| 408 | + for (auto& point : quad) { |
| 409 | + SkV4 vector = globalTransform.map(point.x(), point.y(), 0, 1); |
| 410 | + point.set(vector.x / vector.w, vector.y / vector.w); |
| 411 | + } |
| 412 | + SkRect rect; |
| 413 | + rect.setBounds(quad, 4); |
| 414 | + |
| 415 | + // `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in |
| 416 | + // the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to |
| 417 | + // convert. |
| 418 | + CGFloat scale = [[[self bridge]->view() window] screen].scale; |
| 419 | + auto result = |
| 420 | + CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale); |
| 421 | + return UIAccessibilityConvertFrameToScreenCoordinates(result, [self bridge]->view()); |
605 | 422 | } |
606 | 423 |
|
607 | 424 | #pragma mark - UIAccessibilityElement protocol |
|
0 commit comments