blob: 4b69edcbb63788e8bcdda2213f627b97717e2c44 [file] [log] [blame]
//
// Copyright 2020 Google Inc.
//
// 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.
//
#import "NSObject+GTXAdditions.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import "GTXLogProperty.h"
#import "GTXProtoUtils.h"
#import "NSString+GTXAdditions.h"
#include "element_trait.h"
@implementation NSObject (GTXAdditions)
- (UIElementProto)gtx_toProto {
UIElementProto proto;
// Accessibility properties
proto.set_is_ax_element(self.isAccessibilityElement);
if (self.accessibilityLabel != nil) {
proto.set_ax_label([self.accessibilityLabel gtx_stdString]);
}
if (self.accessibilityHint != nil) {
proto.set_ax_hint([self.accessibilityHint gtx_stdString]);
}
proto.set_ax_traits(self.accessibilityTraits);
*proto.mutable_ax_frame() = [GTXProtoUtils protoFromCGRect:self.accessibilityFrame];
if ([self respondsToSelector:@selector(accessibilityIdentifier)]) {
NSString *axIdentifier = [(id)self accessibilityIdentifier];
if (axIdentifier != nil) {
proto.set_ax_identifier([axIdentifier gtx_stdString]);
}
}
NSArray<NSString *> *classNamesHierarchy = [self gtx_classNamesHierarchy];
for (NSString *className in classNamesHierarchy) {
proto.add_class_names_hierarchy([className gtx_stdString]);
}
// UIView properties
if ([self respondsToSelector:@selector(backgroundColor)]) {
UIColor *backgroundColor = [(UIView *)self backgroundColor];
if (backgroundColor != nil) {
*proto.mutable_background_color() = [GTXProtoUtils protoFromUIColor:backgroundColor];
}
}
if ([self respondsToSelector:@selector(isHidden)]) {
proto.set_hidden([(id)self isHidden]);
}
if ([self respondsToSelector:@selector(alpha)]) {
proto.set_alpha([(id)self alpha]);
}
if ([self respondsToSelector:@selector(isOpaque)]) {
proto.set_opaque([(id)self isOpaque]);
}
if ([self respondsToSelector:@selector(tintColor)]) {
UIColor *tintColor = [(id)self tintColor];
if (tintColor != nil) {
*proto.mutable_tint_color() = [GTXProtoUtils protoFromUIColor:tintColor];
}
}
if ([self respondsToSelector:@selector(clipsToBounds)]) {
proto.set_clips_to_bounds([(id)self clipsToBounds]);
}
if ([self respondsToSelector:@selector(isUserInteractionEnabled)]) {
proto.set_user_interaction_enabled([(id)self isUserInteractionEnabled]);
}
if ([self respondsToSelector:@selector(isMultipleTouchEnabled)]) {
proto.set_multiple_touch_enabled([(id)self isMultipleTouchEnabled]);
}
if ([self respondsToSelector:@selector(isExclusiveTouch)]) {
proto.set_exclusive_touch([(id)self isExclusiveTouch]);
}
if ([self respondsToSelector:@selector(frame)]) {
*proto.mutable_frame() = [GTXProtoUtils protoFromCGRect:[(id)self frame]];
}
if ([self respondsToSelector:@selector(bounds)]) {
*proto.mutable_bounds() = [GTXProtoUtils protoFromCGRect:[(id)self bounds]];
}
// UIControl
if ([self respondsToSelector:@selector(state)]) {
proto.set_control_state([(UIControl *)self state]);
}
if ([self respondsToSelector:@selector(isEnabled)]) {
proto.set_enabled([(UIControl *)self isEnabled]);
}
if ([self respondsToSelector:@selector(isSelected)]) {
proto.set_selected([(UIControl *)self isSelected]);
}
if ([self respondsToSelector:@selector(isHighlighted)]) {
proto.set_highlighted([(UIControl *)self isHighlighted]);
}
if ([self respondsToSelector:@selector(title)]) {
proto.set_title([[(id)self title] gtx_stdString]);
}
if ([self respondsToSelector:@selector(text)]) {
proto.set_text([[(UITextField *)self text] gtx_stdString]);
}
if ([self respondsToSelector:@selector(isOn)]) {
proto.set_on([(UISwitch *)self isOn]);
}
if ([self isKindOfClass:[UISlider class]] || [self isKindOfClass:[UIStepper class]]) {
// Both UISlider and UIStepper declare value.
proto.set_value([(UISlider *)self value]);
}
return proto;
}
#pragma mark - Private
/**
* @returns [self isAccessibilityElement] if implemented, @c false otherwise.
*/
- (bool)gtx_isAxElement {
return ([self respondsToSelector:@selector(isAccessibilityElement)]
? [self isAccessibilityElement]
: false);
}
/**
* @returns [self accessibilityLabel] if implemented, empty std::string otherwise.
*/
- (std::string)gtx_axLabel {
const char *labelCString =
([self respondsToSelector:@selector(accessibilityLabel)]
? [[self accessibilityLabel] cStringUsingEncoding:NSASCIIStringEncoding]
: NULL);
if (!labelCString) {
return std::string();
}
return std::string(labelCString);
}
/**
* @returns @c gtx::ElementTrait for the given trait of type @c UIAccessibilityTraits.
*/
+ (gtx::ElementTrait)gtx_gtxTraitFromUIAccessibilityTrait:(UIAccessibilityTraits)trait {
// Use if-else to check for trait values since UIAccessibilityTrait* are not considered "constant
// expressions".
if (trait == UIAccessibilityTraitNone) {
return gtx::ElementTrait::kNone;
} else if (trait == UIAccessibilityTraitButton) {
return gtx::ElementTrait::kButton;
} else if (trait == UIAccessibilityTraitLink) {
return gtx::ElementTrait::kLink;
} else if (trait == UIAccessibilityTraitSearchField) {
return gtx::ElementTrait::kSearchField;
} else if (trait == UIAccessibilityTraitImage) {
return gtx::ElementTrait::kImage;
} else if (trait == UIAccessibilityTraitSelected) {
return gtx::ElementTrait::kSelected;
} else if (trait == UIAccessibilityTraitPlaysSound) {
return gtx::ElementTrait::kPlaysSound;
} else if (trait == UIAccessibilityTraitKeyboardKey) {
return gtx::ElementTrait::kKeyboardKey;
} else if (trait == UIAccessibilityTraitStaticText) {
return gtx::ElementTrait::kStaticText;
} else if (trait == UIAccessibilityTraitSummaryElement) {
return gtx::ElementTrait::kSummaryElement;
} else if (trait == UIAccessibilityTraitNotEnabled) {
return gtx::ElementTrait::kNotEnabled;
} else if (trait == UIAccessibilityTraitUpdatesFrequently) {
return gtx::ElementTrait::kUpdatesFrequently;
} else if (trait == UIAccessibilityTraitStartsMediaSession) {
return gtx::ElementTrait::kStartsMediaSession;
} else if (trait == UIAccessibilityTraitAdjustable) {
return gtx::ElementTrait::kAdjustable;
} else if (trait == UIAccessibilityTraitAllowsDirectInteraction) {
return gtx::ElementTrait::kAllowsDirectInteraction;
} else if (trait == UIAccessibilityTraitCausesPageTurn) {
return gtx::ElementTrait::kCausesPageTurn;
} else if (trait == UIAccessibilityTraitHeader) {
return gtx::ElementTrait::kHeader;
} else {
NSAssert(NO, @"Unhandled trait %ld", (long)trait);
}
return gtx::ElementTrait::kNone;
}
/**
* @returns [self accessibilityTraits] (converted to gtx::ElementTrait) if implemented,
* kNone otherwise.
*/
- (gtx::ElementTrait)gtx_axTraits {
UIAccessibilityTraits traits =
([self respondsToSelector:@selector(accessibilityTraits)] ? [self accessibilityTraits] : 0);
NSArray<NSNumber *> *allApplicableTraits = @[
@(UIAccessibilityTraitButton),
@(UIAccessibilityTraitLink),
@(UIAccessibilityTraitSearchField),
@(UIAccessibilityTraitImage),
@(UIAccessibilityTraitSelected),
@(UIAccessibilityTraitPlaysSound),
@(UIAccessibilityTraitKeyboardKey),
@(UIAccessibilityTraitStaticText),
@(UIAccessibilityTraitSummaryElement),
@(UIAccessibilityTraitNotEnabled),
@(UIAccessibilityTraitUpdatesFrequently),
@(UIAccessibilityTraitStartsMediaSession),
@(UIAccessibilityTraitAdjustable),
@(UIAccessibilityTraitAllowsDirectInteraction),
@(UIAccessibilityTraitCausesPageTurn),
@(UIAccessibilityTraitHeader),
];
gtx::ElementTrait gtxTraits = gtx::ElementTrait::kNone;
for (NSNumber *trait in allApplicableTraits) {
UIAccessibilityTraits uikitTrait = [trait integerValue];
if (uikitTrait & traits) {
gtxTraits = gtxTraits | [NSObject gtx_gtxTraitFromUIAccessibilityTrait:uikitTrait];
}
}
return gtxTraits;
}
/**
* @returns An array of string representations of this element's class and all classes
* its class descends from.
*/
- (NSArray<NSString *> *)gtx_classNamesHierarchy {
NSMutableArray<NSString *> *classNames = [[NSMutableArray alloc] init];
Class currentClass = [self class];
do {
[classNames addObject:NSStringFromClass(currentClass)];
} while ((currentClass = class_getSuperclass(currentClass)) != nil);
return classNames;
}
@end