diff --git a/Frameworks/UIKit.Xaml/Button.xaml b/Frameworks/UIKit.Xaml/Button.xaml index 264bdbe42c..2b4b156b82 100644 --- a/Frameworks/UIKit.Xaml/Button.xaml +++ b/Frameworks/UIKit.Xaml/Button.xaml @@ -10,7 +10,9 @@ - + + diff --git a/Frameworks/UIKit.Xaml/Button.xaml.cpp b/Frameworks/UIKit.Xaml/Button.xaml.cpp index b47a1dc2f4..45e909cd63 100644 --- a/Frameworks/UIKit.Xaml/Button.xaml.cpp +++ b/Frameworks/UIKit.Xaml/Button.xaml.cpp @@ -178,8 +178,8 @@ Windows::Foundation::Size Button::ArrangeOverride(Windows::Foundation::Size fina void Button::OnApplyTemplate() { // Call GetTemplateChild to grab references to UIElements in our custom control template - _textBlock = safe_cast(GetTemplateChild("buttonText")); _image = safe_cast(GetTemplateChild("buttonImage")); + _border = safe_cast(GetTemplateChild("buttonBorder")); _contentCanvas = safe_cast(GetTemplateChild(L"contentCanvas")); } @@ -204,21 +204,11 @@ UIKIT_XAML_EXPORT void XamlRemoveLayoutEvent(const ComPtr& inspect button->RemoveLayoutEvent(); } -UIKIT_XAML_EXPORT void XamlButtonApplyVisuals(const ComPtr& inspectableButton, - const ComPtr& inspectableText, +UIKIT_XAML_EXPORT void XamlButtonApplyVisuals( + const ComPtr& inspectableButton, const ComPtr& inspectableButtonImage, - const ComPtr& inspectableTitleColor) { - + const ComPtr& inspectableBorderBackgroundBrush) { auto button = safe_cast(reinterpret_cast(inspectableButton.Get())); - auto title = safe_cast(reinterpret_cast(inspectableText.Get())); - auto titleColor = safe_cast(reinterpret_cast(inspectableTitleColor.Get())); - if (!titleColor) { - titleColor = UIKit::Xaml::GetDefaultWhiteForegroundBrush(); - } - - // Set the Textblock's title and Foreground Brush color - button->_textBlock->Text = title; - button->_textBlock->Foreground = titleColor; // Set the Button's Image auto image = safe_cast(reinterpret_cast(inspectableButtonImage.Get())); @@ -226,6 +216,9 @@ UIKIT_XAML_EXPORT void XamlButtonApplyVisuals(const ComPtr& inspec ImageBrush^ imageBrush = safe_cast(image); button->_image->Source = safe_cast(imageBrush->ImageSource); } + + // Set the border background brush (if any) + button->_border->Background = safe_cast(reinterpret_cast(inspectableBorderBackgroundBrush.Get())); } UIKIT_XAML_EXPORT void XamlHookButtonPointerEvents( diff --git a/Frameworks/UIKit.Xaml/Button.xaml.h b/Frameworks/UIKit.Xaml/Button.xaml.h index 48a2b9ea7d..319becfb7b 100644 --- a/Frameworks/UIKit.Xaml/Button.xaml.h +++ b/Frameworks/UIKit.Xaml/Button.xaml.h @@ -67,8 +67,8 @@ public ref class Button sealed : public Private::CoreAnimation::ILayer { void RemovePointerEvents(); void RemoveLayoutEvent(); - Windows::UI::Xaml::Controls::TextBlock^ _textBlock; Windows::UI::Xaml::Controls::Image^ _image; + Windows::UI::Xaml::Controls::Border^ _border; private: Windows::UI::Xaml::Controls::Canvas^ _contentCanvas; // Contains pre-canned button content, as well as any sublayers added by CoreAnimation. diff --git a/Frameworks/UIKit.Xaml/Layer.xaml.h b/Frameworks/UIKit.Xaml/Layer.xaml.h index 0d7a8bc7f3..38420ad8f1 100644 --- a/Frameworks/UIKit.Xaml/Layer.xaml.h +++ b/Frameworks/UIKit.Xaml/Layer.xaml.h @@ -69,7 +69,7 @@ public ref class Layer sealed : public ILayer { } // Allows arbitrary framework elements to opt-into hosting sublayers - static property Windows::UI::Xaml::DependencyProperty^ SublayerCanvasProperty { + static property Windows::UI::Xaml::DependencyProperty^ SublayerCanvasProperty { Windows::UI::Xaml::DependencyProperty^ get(); } diff --git a/Frameworks/UIKit.Xaml/ObjCXamlControls.h b/Frameworks/UIKit.Xaml/ObjCXamlControls.h index 36c5c72d16..39fb433354 100644 --- a/Frameworks/UIKit.Xaml/ObjCXamlControls.h +++ b/Frameworks/UIKit.Xaml/ObjCXamlControls.h @@ -32,9 +32,8 @@ enum ControlStates { ControlStateNormal = 0, ControlStateHighlighted = 1 << 0, C UIKIT_XAML_EXPORT void XamlCreateButton(IInspectable** created); UIKIT_XAML_EXPORT void XamlButtonApplyVisuals(const Microsoft::WRL::ComPtr& inspectableButton, - const Microsoft::WRL::ComPtr& inspectableText, const Microsoft::WRL::ComPtr& inspectableImage, - const Microsoft::WRL::ComPtr& inspectableTitleColor); + const Microsoft::WRL::ComPtr& inspectableBorderBackgroundBrush); // Hooks pointer events on a UIKit::Button passed in as IInspectable UIKIT_XAML_EXPORT void XamlHookButtonPointerEvents(const Microsoft::WRL::ComPtr& inspectableButton, @@ -70,7 +69,7 @@ UIKIT_XAML_EXPORT IInspectable* XamlGetLabelTextBox(const Microsoft::WRL::ComPtr UIKIT_XAML_EXPORT void XamlSetFrameworkElementLayerProperties(const Microsoft::WRL::ComPtr& targetElement, const Microsoft::WRL::ComPtr& sublayerCanvasProperty, const Microsoft::WRL::ComPtr& layerContentProperty); - + // Get the layerContentProperty for the specified target xaml element UIKIT_XAML_EXPORT IInspectable* XamlGetFrameworkElementLayerContentProperty(const Microsoft::WRL::ComPtr& targetElement); diff --git a/Frameworks/UIKit/UIButton.mm b/Frameworks/UIKit/UIButton.mm index be10f0a748..f1dd269537 100644 --- a/Frameworks/UIKit/UIButton.mm +++ b/Frameworks/UIKit/UIButton.mm @@ -1,6 +1,6 @@ //****************************************************************************** // -// Copyright (c) 2015 Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // @@ -39,9 +39,16 @@ #include +using namespace Microsoft::WRL; + static const wchar_t* TAG = L"UIButton"; struct ButtonState { + // Returns whether or not the state contains any currently-set values + bool IsEmpty() const { + return !title && !textColor && !image && !backgroundImage; + } + StrongId image; StrongId backgroundImage; StrongId textColor; @@ -49,18 +56,9 @@ // We also save the converted data types that we need to set on the XAML Button, so that we do not convert // from UIKit datatype to XAML datatype every time layoutSubviews is called. - Microsoft::WRL::ComPtr inspectableImage; - Microsoft::WRL::ComPtr inspectableTitleColor; - Microsoft::WRL::ComPtr inspectableTitle; + ComPtr inspectableImage; }; -@interface UIRoundedRectButton : UIButton { -} -@end - -@implementation UIRoundedRectButton -@end - @implementation UIButton { StrongId _xamlButton; @@ -71,16 +69,20 @@ @implementation UIButton { UIEdgeInsets _imageInsets; UIEdgeInsets _titleInsets; - // Proxies - StrongId<_UILabel_Proxy> _proxyLabel; + // Child elements + StrongId _titleLabel; StrongId<_UIImageView_Proxy> _proxyImageView; bool _isPressed; + UIButtonType _buttonType; + + ComPtr _inspectableAdjustsWhenDisabledBrush; + ComPtr _inspectableAdjustsWhenHighlightedBrush; } /** @Status Caveat - @Notes May not be fully implemented + @Notes Not all properties are supported. */ - (instancetype)initWithCoder:(NSCoder*)coder { if (self = [super initWithCoder:coder]) { @@ -167,19 +169,29 @@ - (void)_initUIButton { FAIL_FAST(); } + // Default to a custom button type + _buttonType = UIButtonTypeCustom; + + // Default to showing pressed/disabled states + self.adjustsImageWhenDisabled = YES; + self.adjustsImageWhenHighlighted = YES; + // Force-load the template, and get the TextBlock and Image for use in our proxies. [_xamlButton applyTemplate]; - + [_xamlButton updateLayout]; + + // Create our child UILabel; its frame will be updated in layoutSubviews + // TODO: Ideally we'd grab this directly from the Xaml, but we're not able to launch some apps when doing so due to a + // XamlParseException. + // Tracked as #1919. When fixed, we'll need to initWithXamlElement we retrieve from the control template, and *not* addSubView. + _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + [self addSubview:_titleLabel]; + _titleLabel.font = [UIFont buttonFont]; + _titleLabel.userInteractionEnabled = NO; + + // Create our child UIImageView; its frame will be updated in layoutSubviews WXCImage* templateImage = rt_dynamic_cast([WXCImage class], [_xamlButton getTemplateChild:@"buttonImage"]); - WXCTextBlock* templateText = rt_dynamic_cast([WXCTextBlock class], [_xamlButton getTemplateChild:@"buttonText"]); - - if (templateText) { - _proxyLabel = [[_UILabel_Proxy alloc] initWithXamlElement:templateText font:[UIFont buttonFont]]; - } - - if (templateImage) { - _proxyImageView = [[_UIImageView_Proxy alloc] initWithXamlElement:templateImage]; - } + _proxyImageView = [[_UIImageView_Proxy alloc] initWithXamlElement:templateImage]; _contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; _contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; @@ -264,44 +276,27 @@ - (void)initAccessibility { } /** - @Status Caveat - @Notes UIControlStateSelected, UIControlStateApplication and UIControlStateReserved states not supported + @Status Interoperable */ -- (void)setImage:(UIImage*)image forState:(UIControlState)state { - _states[state].image = image; - - // NOTE: check if image is nil before creating inspectableImage - // ConvertUIImageToWUXMImageBrush:nil creates a valid imageBrush with null comObj - // which isn't what we want - if (image) { - WUXMImageBrush* imageBrush = XamlUtilities::ConvertUIImageToWUXMImageBrush(image); - if (imageBrush) { - _states[state].inspectableImage = [imageBrush comObj]; - } - } else { - // this enforces the fallback of using Image of normalState - // when a image for other states does not exis - _states[state].inspectableImage = nullptr; +- (void)layoutSubviews { + // Grab our current values + NSString* currentTitle = self.currentTitle; + ComPtr currentImage = _currentInspectableImage(self); + UIImage* currentBackgroundImage = self.currentBackgroundImage; + + // Determine whether or not we should use a disabled or highlighted overlay. + // Only do so if we have any content to render and we're in a disabled or highlighted state. + ComPtr currentBorderBackgroundBrush; + if (([currentTitle length] > 0) || currentImage || currentBackgroundImage) { + currentBorderBackgroundBrush = _currentInspectableBorderBackgroundBrush(self); } - // Update the Xaml elements immediately, so the proxies reflect reality - XamlButtonApplyVisuals([_xamlButton comObj], - _currentInspectableTitle(self), - _currentInspectableImage(self), - _currentInspectableTitleColor(self)); + // Update our image and border background brush (which we use for applying 'adjustImageWhen' treatment) + XamlButtonApplyVisuals([_xamlButton comObj], currentImage, currentBorderBackgroundBrush); - [self invalidateIntrinsicContentSize]; - [self setNeedsLayout]; -} - -/** - @Status Interoperable -*/ -- (void)layoutSubviews { - XamlButtonApplyVisuals([_xamlButton comObj], - _currentInspectableTitle(self), - _currentInspectableImage(self), - _currentInspectableTitleColor(self)); + // Update our title label + self.titleLabel.text = currentTitle; + self.titleLabel.textColor = self.currentTitleColor; // Set frame after updating the visuals CGRect contentFrame = [self contentRectForBounds:self.bounds]; @@ -311,7 +306,7 @@ - (void)layoutSubviews { self.imageView.frame = imageFrame; // Use the layer contents to draw the background image, similar to UIImageView. - UIImageSetLayerContents([self layer], self.currentBackgroundImage); + UIImageSetLayerContents([self layer], currentBackgroundImage); // UIButton should always stretch its background. Since we're leveraging our backing CALayer's background for // the UIButton background, we stretch it via the CALayer's contentsGravity. @@ -322,7 +317,7 @@ - (void)layoutSubviews { } static CGRect calculateContentRect(UIButton* self, CGSize size, CGRect contentRect) { - CGRect rect = { { 0, 0 }, size }; + CGRect rect = CGRectMake(0, 0, size.width, size.height); switch (self.contentHorizontalAlignment) { case UIControlContentHorizontalAlignmentCenter: @@ -409,7 +404,10 @@ - (CGRect)imageRectForContentRect:(CGRect)contentRect { @Status Interoperable */ - (CGRect)titleRectForContentRect:(CGRect)contentRect { - CGSize titleSize = [self.currentTitle sizeWithFont:self.font]; + // TODO: Should we be asking our label for its intrinsicContentSize? + // We need to round the font size to the nearest pixel value to avoid rounding errors when used in conjunction with the + // frame values that are rounded by UIView. + CGSize titleSize = doPixelRound([self.currentTitle sizeWithFont:self.font]); CGSize totalSize = titleSize; // TODO #1365 :: Currently cannot assume getting size from nil will return CGSizeZero CGSize imageSize = self.currentImage ? [self.currentImage size] : CGSizeZero; @@ -445,6 +443,7 @@ - (CGRect)titleRectForContentRect:(CGRect)contentRect { */ - (void)setEnabled:(BOOL)enabled { _xamlButton.isEnabled = enabled; + [super setEnabled:enabled]; } /** @@ -456,42 +455,85 @@ - (BOOL)isEnabled { /** @Status Interoperable - @Notes The xaml element may be modified directly and we could still return stale values. */ -- (void)setBackgroundImage:(UIImage*)image forState:(UIControlState)state { - _states[state].backgroundImage = image; +- (void)setImage:(UIImage*)image forState:(UIControlState)state { + _states[state].image = image; + + // NOTE: check if image is nil before creating inspectableImage + // ConvertUIImageToWUXMImageBrush:nil creates a valid imageBrush with null comObj + // which isn't what we want + if (image) { + WUXMImageBrush* imageBrush = XamlUtilities::ConvertUIImageToWUXMImageBrush(image); + if (imageBrush) { + _states[state].inspectableImage = [imageBrush comObj]; + } + } else { + // this enforces the fallback of using Image of normalState + // when a image for other states does not exis + _states[state].inspectableImage = nullptr; + } + + // Update the Xaml elements immediately, so the image proxy reflects reality + XamlButtonApplyVisuals([_xamlButton comObj], _currentInspectableImage(self), _currentInspectableBorderBackgroundBrush(self)); [self invalidateIntrinsicContentSize]; [self setNeedsLayout]; } /** - @Status Interoperable - @Notes The xaml element may be modified directly and we could still return stale values. +@Status Interoperable +@Notes The xaml element may be modified directly and we could return stale values. */ -- (UIImage*)backgroundImageForState:(UIControlState)state { - return _states[state].backgroundImage; +- (UIImage*)imageForState:(UIControlState)state { + // Don't do index-based lookup to avoid inserting empty entries into the map + const auto& statePair = _states.find(state); + return statePair != _states.end() ? statePair->second.image : nullptr; } /** @Status Interoperable - @Notes The xaml element may be modified directly and we could still return stale values. + @Notes The xaml element may be modified directly and we could return stale values. */ - (UIImage*)currentImage { - if (self->_states[self->_curState].image != nil) { - return self->_states[self->_curState].image; + // Don't do index-based lookup to avoid inserting empty entries into the map + const auto& statePair = _states.find(self->_curState); + if (statePair != _states.end() && statePair->second.image != nil) { + return statePair->second.image; } return self->_states[UIControlStateNormal].image; } /** - @Status Interoperable - @Notes The xaml element may be modified directly and we could still return stale values. +@Status Interoperable +@Notes The xaml element may be modified directly and we could return stale values. +*/ +- (void)setBackgroundImage:(UIImage*)image forState:(UIControlState)state { + _states[state].backgroundImage = image; + + [self invalidateIntrinsicContentSize]; + [self setNeedsLayout]; +} + +/** +@Status Interoperable +@Notes The xaml element may be modified directly and we could return stale values. +*/ +- (UIImage*)backgroundImageForState:(UIControlState)state { + // Don't do index-based lookup to avoid inserting empty entries into the map + const auto& statePair = _states.find(state); + return statePair != _states.end() ? statePair->second.backgroundImage : nullptr; +} + +/** +@Status Interoperable +@Notes The xaml element may be modified directly and we could return stale values. */ - (UIImage*)currentBackgroundImage { - if (self->_states[self->_curState].backgroundImage != nil) { - return self->_states[self->_curState].backgroundImage; + // Don't do index-based lookup to avoid inserting empty entries into the map + const auto& statePair = _states.find(self->_curState); + if (statePair != _states.end() && statePair->second.backgroundImage != nil) { + return statePair->second.backgroundImage; } return self->_states[UIControlStateNormal].backgroundImage; @@ -503,25 +545,8 @@ - (UIImage*)currentBackgroundImage { - (void)setTitle:(NSString*)title forState:(UIControlState)state { _states[state].title = [title copy]; - // NOTE: check if title is nil before creating inspectableTitle - // createString:nil creates a valid rtString with null comObj - // which isn't what we want - if (title) { - RTObject* rtString = [WFPropertyValue createString:title]; - if (rtString) { - _states[state].inspectableTitle = [rtString comObj]; - } - } else { - // this enforces the fallback of using title of normalState - // when a title for other states does not exist - _states[state].inspectableTitle = nullptr; - } - - // Update the Xaml elements immediately, so the proxies reflect reality - XamlButtonApplyVisuals([_xamlButton comObj], - _currentInspectableTitle(self), - _currentInspectableImage(self), - _currentInspectableTitleColor(self)); + // Update our title label immediately + self.titleLabel.text = self.currentTitle; [self invalidateIntrinsicContentSize]; [self setNeedsLayout]; @@ -529,18 +554,25 @@ - (void)setTitle:(NSString*)title forState:(UIControlState)state { /** @Status Interoperable - @Notes The xaml element may be modified directly and we could still return stale values. + @Notes The xaml element may be modified directly and we could return stale values. */ - (NSString*)titleForState:(UIControlState)state { - return _states[state].title; + // Don't do index-based lookup to avoid inserting empty entries into the map + const auto& statePair = _states.find(state); + return statePair != _states.end() ? statePair->second.title : nullptr; } /** @Status Interoperable - @Notes The xaml element may be modified directly and we could still return stale values. */ -- (UIImage*)imageForState:(UIControlState)state { - return _states[state].image; +- (NSString*)currentTitle { + // Don't do index-based lookup to avoid inserting empty entries into the map + const auto& statePair = _states.find(self->_curState); + if (statePair != _states.end() && statePair->second.title != nil) { + return statePair->second.title; + } + + return self->_states[UIControlStateNormal].title; } /** @@ -549,25 +581,8 @@ - (UIImage*)imageForState:(UIControlState)state { - (void)setTitleColor:(UIColor*)color forState:(UIControlState)state { _states[state].textColor = color; - // NOTE: check if image is nil before creating convertedColor - // ConvertUIColorToWUColor:nil creates a valid WUColor with null comObj - // which isn't what we want - if (color) { - WUXMSolidColorBrush* titleColorBrush = [WUXMSolidColorBrush makeInstanceWithColor:XamlUtilities::ConvertUIColorToWUColor(color)]; - if (titleColorBrush) { - _states[state].inspectableTitleColor = [titleColorBrush comObj]; - } - } else { - // this enforces the fallback of using titleColor of normalState - // when a titleColor for other states does not exist - _states[state].inspectableTitleColor = nullptr; - } - - // Update the Xaml elements immediately, so the proxies reflect reality - XamlButtonApplyVisuals([_xamlButton comObj], - _currentInspectableTitle(self), - _currentInspectableImage(self), - _currentInspectableTitleColor(self)); + // Update our title label color + self.titleLabel.textColor = self.currentTitleColor; [self invalidateIntrinsicContentSize]; [self setNeedsLayout]; @@ -575,39 +590,60 @@ - (void)setTitleColor:(UIColor*)color forState:(UIControlState)state { /** @Status Interoperable - @Notes The xaml element may be modified directly and we could still return stale values. + @Notes The xaml element may be modified directly and we could return stale values. */ - (UIColor*)titleColorForState:(UIControlState)state { - return _states[state].textColor; + // Don't do index-based lookup to avoid inserting empty entries into the map + const auto& statePair = _states.find(state); + return statePair != _states.end() ? statePair->second.textColor : nullptr; } /** - @Status Stub + @Status Interoperable +*/ +- (UIColor*)currentTitleColor { + // Don't do index-based lookup to avoid inserting empty entries into the map + const auto& statePair = _states.find(self->_curState); + if (statePair != _states.end() && statePair->second.textColor != nil) { + return statePair->second.textColor; + } else if (self->_states[UIControlStateNormal].textColor != nil) { + return self->_states[UIControlStateNormal].textColor; + } else { + return [UIColor whiteColor]; + } +} + +/** + @Status NotInPlan + @Notes TintColor is not a feature currently supported by Xaml. */ - (void)setTintColor:(UIColor*)color { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("TintColor is not a feature currently supported by Xaml."); } /** - @Status Stub + @Status NotInPlan + @Notes Text shadows are not currently supported by Xaml. */ - (void)setTitleShadowColor:(UIColor*)color forState:(UIControlState)state { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("Text shadows are not currently supported by Xaml."); } /** - @Status Stub + @Status NotInPlan + @Notes Text shadows are not currently supported by Xaml. */ - (UIColor*)titleShadowColorForState:(UIControlState)state { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("Text shadows are not currently supported by Xaml."); return StubReturn(); } /** - @Status Stub + @Status NotInPlan + @Notes Text shadows are not currently supported by Xaml. */ - (UIColor*)currentTitleShadowColor { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("Text shadows are not currently supported by Xaml."); return StubReturn(); } @@ -691,26 +727,79 @@ - (void)touchesCancelled:(NSSet*)touchSet withEvent:(UIEvent*)event { [_xamlButton releasePointerCapture:routedEvent.pointer]; } +static bool _isStateCustomizationSet(UIButton* self, UIControlState state) { + // Returns whether or not we have any customization set for the specified state. + // For example; checking for 'UIControlStateDisabled' would return true for a 'UIControlStateDisabled' state as well as + // for a 'UIControlStateHighlighted | UIControlStateDisabled' state. + for (const auto& statePair : self->_states) { + if ((statePair.first & state) && !statePair.second.IsEmpty()) { + return true; + } + } + + return false; +} + +static ComPtr _currentInspectableBorderBackgroundBrush(UIButton* self) { + // Only use the brush if we're in the current state, and no customization has yet been set + // for any permutation of that state. + if (!self.isEnabled && !_isStateCustomizationSet(self, UIControlStateDisabled)) { + return self->_inspectableAdjustsWhenDisabledBrush; + } else if (self.isHighlighted && !_isStateCustomizationSet(self, UIControlStateHighlighted)) { + return self->_inspectableAdjustsWhenHighlightedBrush; + } + + return nullptr; +} + /** - @Status Stub + @Status Caveat + @Notes We use this value to determine whether or not we add a lightening overlay to the button background, background image, and image, + whereas on the reference platform the tint is only applied directly to the images. You can opt out by disabling this setting, + or by setting any UIControlStateDisabled properties. */ -- (void)setAdjustsImageWhenHighlighted:(BOOL)doAdjust { - UNIMPLEMENTED(); +- (void)setAdjustsImageWhenDisabled:(BOOL)shouldAdjust { + if (shouldAdjust && !_inspectableAdjustsWhenDisabledBrush) { + // Semi-transparent white overlay + WUXMSolidColorBrush* colorBrush = [WUXMSolidColorBrush makeInstanceWithColor:[WUColorHelper fromArgb:150 r:255 g:255 b:255]]; + _inspectableAdjustsWhenDisabledBrush = [colorBrush comObj]; + [self setNeedsLayout]; + } else if (!shouldAdjust && _inspectableAdjustsWhenDisabledBrush) { + _inspectableAdjustsWhenDisabledBrush = nullptr; + [self setNeedsLayout]; + } } /** - @Status Stub + @Status Interoperable */ -- (BOOL)adjustsImageWhenHighlighted { - UNIMPLEMENTED(); - return StubReturn(); +- (BOOL)adjustsImageWhenDisabled { + return _inspectableAdjustsWhenDisabledBrush != nullptr; } /** - @Status Stub + @Status Caveat + @Notes We use this value to determine whether or not we add a darkening overlay to the button background, background image, and image, + whereas on the reference platform the tint is only applied directly to the images. You can opt out by disabling this setting, + or by setting any UIControlStateHighlighted properties. */ -- (void)setAdjustsImageWhenDisabled:(BOOL)doAdjust { - UNIMPLEMENTED(); +- (void)setAdjustsImageWhenHighlighted:(BOOL)shouldAdjust { + if (shouldAdjust && !_inspectableAdjustsWhenHighlightedBrush) { + // Mostly transparent black overlay + WUXMSolidColorBrush* colorBrush = [WUXMSolidColorBrush makeInstanceWithColor:[WUColorHelper fromArgb:65 r:0 g:0 b:0]]; + _inspectableAdjustsWhenHighlightedBrush = [colorBrush comObj]; + [self setNeedsLayout]; + } else if (!shouldAdjust && _inspectableAdjustsWhenHighlightedBrush) { + _inspectableAdjustsWhenHighlightedBrush = nullptr; + [self setNeedsLayout]; + } +} + +/** + @Status Interoperable +*/ +- (BOOL)adjustsImageWhenHighlighted { + return _inspectableAdjustsWhenHighlightedBrush != nullptr; } /** @@ -731,8 +820,7 @@ - (UIFont*)font { @Status Stub */ - (UIButtonType)buttonType { - UNIMPLEMENTED(); - return StubReturn(); + return _buttonType; } /** @@ -747,17 +835,15 @@ - (void)setTitleEdgeInsets:(UIEdgeInsets)insets { /** @Status Interoperable */ -- (void)setImageEdgeInsets:(UIEdgeInsets)insets { - _imageInsets = insets; - [self invalidateIntrinsicContentSize]; - [self setNeedsLayout]; +- (UIEdgeInsets)titleEdgeInsets { + return _titleInsets; } /** @Status Interoperable */ -- (void)setContentEdgeInsets:(UIEdgeInsets)insets { - _contentInsets = insets; +- (void)setImageEdgeInsets:(UIEdgeInsets)insets { + _imageInsets = insets; [self invalidateIntrinsicContentSize]; [self setNeedsLayout]; } @@ -772,8 +858,10 @@ - (UIEdgeInsets)imageEdgeInsets { /** @Status Interoperable */ -- (UIEdgeInsets)titleEdgeInsets { - return _titleInsets; +- (void)setContentEdgeInsets:(UIEdgeInsets)insets { + _contentInsets = insets; + [self invalidateIntrinsicContentSize]; + [self setNeedsLayout]; } /** @@ -784,17 +872,19 @@ - (UIEdgeInsets)contentEdgeInsets { } /** - @Status Stub + @Status NotInPlan + @Notes Xaml doesn't have built in support for showing 'glowing' touch feedback. */ - (void)setShowsTouchWhenHighlighted:(BOOL)showsTouch { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("Xaml doesn't have built in support for showing 'glowing' touch feedback."); } /** - @Status Stub + @Status NotInPlan + @Notes Xaml doesn't have built in support for showing 'glowing' touch feedback. */ - (BOOL)showsTouchWhenHighlighted { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("Xaml doesn't have built in support for showing 'glowing' touch feedback."); return StubReturn(); } @@ -808,52 +898,31 @@ - (void)dealloc { /** @Status Caveat - @Notes type not supported fully + @Notes Only UIButtonTypeSystem and UIButtonTypeRoundedRect receive special treatment; all other types are treated as UIButtonTypeCustom. */ + (UIButton*)buttonWithType:(UIButtonType)type { UIButton* ret = [[UIButton alloc] initWithFrame:CGRectZero]; if (type == UIButtonTypeRoundedRect || type == UIButtonTypeSystem) { + ret->_buttonType = type; + // Default title color [ret setTitleColor:[UIColor colorWithRed:0.0f green:0.47843137f blue:1.0f alpha:1.0f] forState:UIControlStateNormal]; + + // Default disabled title color + [ret setTitleColor:[UIColor lightTextColor] forState:UIControlStateDisabled]; } return ret; } /** - @Status Stub + @Status NotInPlan + @Notes Text shadows are not currently supported by Xaml. */ - (void)setReversesTitleShadowWhenHighlighted:(BOOL)reverses { - UNIMPLEMENTED(); -} - -/** - @Status Interoperable -*/ -- (NSString*)currentTitle { - if (self->_states[self->_curState].title != nil) { - return self->_states[self->_curState].title; - } - - return self->_states[UIControlStateNormal].title; -} - -static Microsoft::WRL::ComPtr _currentInspectableTitle(UIButton* self) { - if (self->_states[self->_curState].inspectableTitle) { - return self->_states[self->_curState].inspectableTitle; - } - - return self->_states[UIControlStateNormal].inspectableTitle; -} - -static Microsoft::WRL::ComPtr _currentInspectableTitleColor(UIButton* self) { - if (self->_states[self->_curState].inspectableTitleColor) { - return self->_states[self->_curState].inspectableTitleColor; - } - - return self->_states[UIControlStateNormal].inspectableTitleColor; + UNIMPLEMENTED_WITH_MSG("Text shadows are not currently supported by Xaml."); } -static Microsoft::WRL::ComPtr _currentInspectableImage(UIButton* self) { +static ComPtr _currentInspectableImage(UIButton* self) { if (self->_states[self->_curState].inspectableImage) { return self->_states[self->_curState].inspectableImage; } @@ -864,22 +933,8 @@ - (NSString*)currentTitle { /** @Status Interoperable */ -- (UIColor*)currentTitleColor { - if (self->_states[self->_curState].textColor != nil) { - return self->_states[self->_curState].textColor; - } else if (self->_states[UIControlStateNormal].textColor != nil) { - return self->_states[UIControlStateNormal].textColor; - } else { - return [UIColor whiteColor]; - } -} - -/** - @Status Caveat - @Notes Returns a mock UILabel that proxies some common properties and selectors to the underlying TextBlock -*/ - (UILabel*)titleLabel { - return (UILabel*)_proxyLabel; + return _titleLabel; } /** @@ -890,14 +945,6 @@ - (UIImageView*)imageView { return (UIImageView*)_proxyImageView; } -/** - @Status Caveat - @Notes Returns the receiving view -*/ -- (UIView*)viewForBaselineLayout { - return self; -} - /** @Status Interoperable */ @@ -906,7 +953,11 @@ - (CGSize)intrinsicContentSize { UIImage* img = self.currentImage; UIEdgeInsets contentInsets = self.contentEdgeInsets; - CGSize textSize = [[self currentTitle] sizeWithFont:self.font]; + + // TODO: Should we be asking our label for its intrinsicContentSize? + // We need to round the font size to the nearest pixel value to avoid rounding errors when used in conjunction with the + // frame values that are rounded by UIView. + CGSize textSize = doPixelRound([[self currentTitle] sizeWithFont:self.font]); // Size should at least fit the image in a normal state. if (img != nil) { @@ -939,26 +990,43 @@ - (CGSize)intrinsicContentSize { } /** - @Status Stub + @Status NotInPlan + @Notes Custom rendering over derived UIButtons is not currently supported. */ - (CGRect)backgroundRectForBounds:(CGRect)bounds { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("Custom rendering over derived UIButtons is not currently supported."); return StubReturn(); } /** - @Status Stub + @Status NotInPlan + @Notes Attributed text is not easily supported by Xaml. */ - (NSAttributedString*)attributedTitleForState:(UIControlState)state { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("Attributed text is not easily supported by Xaml."); return StubReturn(); } /** - @Status Stub + @Status NotInPlan + @Notes Attributed text is not easily supported by Xaml. */ - (void)setAttributedTitle:(NSAttributedString*)title forState:(UIControlState)state { - UNIMPLEMENTED(); + UNIMPLEMENTED_WITH_MSG("Attributed text is not easily supported by Xaml."); +} + +/** + @Status Interoperable +*/ +- (void)setLineBreakMode:(UILineBreakMode)mode { + self.titleLabel.lineBreakMode = mode; +} + +/** + @Status Interoperable +*/ +- (UILineBreakMode)lineBreakMode { + return self.titleLabel.lineBreakMode; } @end diff --git a/Frameworks/UIKit/UIButtonProxies.h b/Frameworks/UIKit/UIButtonProxies.h index ddd520399a..2b24471701 100644 --- a/Frameworks/UIKit/UIButtonProxies.h +++ b/Frameworks/UIKit/UIButtonProxies.h @@ -35,16 +35,6 @@ @end -// NOTE: current _UILabel_Proxy is merely a bridge to xaml TextBlock, which is only -// used to in a very confined scope - that is when accessing some properties -// of UIButton.titleLabel. So it has very limited usage right now. -// However, we will replace UILabel the full implemention of _UILabel in the future -@interface _UILabel_Proxy : _UIView_Proxy - -- (instancetype)initWithXamlElement:(WXFrameworkElement*)xamlElement font:(UIFont*)font; - -@end - @interface _UIImageView_Proxy : _UIView_Proxy @property (nonatomic, retain) UIImage* image; diff --git a/Frameworks/UIKit/UIButtonProxies.mm b/Frameworks/UIKit/UIButtonProxies.mm index e47dac8380..19a6f08c77 100644 --- a/Frameworks/UIKit/UIButtonProxies.mm +++ b/Frameworks/UIKit/UIButtonProxies.mm @@ -116,169 +116,6 @@ - (CGRect)frame { @end -@implementation _UILabel_Proxy { - StrongId _font; - StrongId _dummyTextBlock; - - // For convenience - WXCTextBlock* _xamlTextBlock; -} - -+ (Class)_mockClass { - return [UILabel class]; -} - -- (void)setFrame:(CGRect)frame { - // UILabels are vertically aligned - [super setFrame:frame]; - [_xamlTextBlock measure:[WXSizeHelper fromDimensions:frame.size.width height:frame.size.height]]; - - float centerOffset = (frame.size.height - _xamlTextBlock.actualHeight) / 2.0f; - _xamlTextBlock.margin = [WXThicknessHelper fromLengths:0 top:centerOffset right:0 bottom:0]; -} - -- (instancetype)initWithXamlElement:(WXFrameworkElement*)xamlElement font:(UIFont*)font { - FAIL_FAST_IF_NULL(xamlElement); - WXCTextBlock* textBlock = rt_dynamic_cast(xamlElement); - FAIL_FAST_IF_NULL(textBlock); - - if (self = [super initWithXamlElement:textBlock]) { - _xamlTextBlock = textBlock; - - // TODO: Copy attributes from xamlElement? - _dummyTextBlock = [WXCTextBlock make]; - FAIL_FAST_IF_NULL(_dummyTextBlock); - - // set up default font - if (font == nil) { - [self setFont:[UIFont defaultFont]]; - } else { - [self setFont:font]; - } - } - - return self; -} - -- (void)setNumberOfLines:(NSInteger)numberOfLines { - _xamlTextBlock.maxLines = numberOfLines; -} - -- (NSInteger)numberOfLines { - return _xamlTextBlock.maxLines; -} - -- (void)setTextAlignment:(UITextAlignment)alignment { - _xamlTextBlock.textAlignment = XamlUtilities::ConvertUITextAlignmentToWXTextAlignment(alignment); -} - -- (UITextAlignment)textAlignment { - return XamlUtilities::ConvertWXTextAlignmentToUITextAlignment(_xamlTextBlock.textAlignment); -} - -- (void)setText:(NSString*)text { - _xamlTextBlock.text = text; -} - -- (NSString*)text { - return _xamlTextBlock.text; -} - -- (void)setFont:(UIFont*)font { - if ([font isEqual:_font]) { - return; - } - - _font = font; - _xamlTextBlock.fontSize = [_font pointSize]; - - // TOOD: Bug 8706843:Constructor or Helper to create FontFamily isn't projected - thus no way to create a FontFamily from Objective C - // side. we can remove all WRL related stuff and use projection API directly when it is ready - ComPtr fontFamilyFactory; - ABI::Windows::Foundation::GetActivationFactory( - Microsoft::WRL::Wrappers::HString::MakeReference(L"Windows.UI.Xaml.Media.FontFamily").Get(), &fontFamilyFactory); - - ComPtr fontFamily; - auto fontName = Strings::NarrowToWide([_font fontName]); - fontFamilyFactory->CreateInstanceWithName(fontName.Get(), nullptr, nullptr, fontFamily.GetAddressOf()); - _xamlTextBlock.fontFamily = [WUXMFontFamily createWith:fontFamily.Get()]; - - _xamlTextBlock.lineStackingStrategy = WXLineStackingStrategyBlockLineHeight; - _xamlTextBlock.lineHeight = [_font ascender] - [_font descender]; - - int mask = [_font fontDescriptor].symbolicTraits; - if ((mask & UIFontDescriptorTraitBold) > 0) { - _xamlTextBlock.fontWeight = [WUTFontWeights bold]; - } else { - _xamlTextBlock.fontWeight = [WUTFontWeights normal]; - } - - if ((mask & UIFontDescriptorTraitItalic) > 0) { - _xamlTextBlock.fontStyle = WUTFontStyleItalic; - } else { - _xamlTextBlock.fontStyle = WUTFontStyleNormal; - } -} - -- (UIFont*)font { - return _font; -} - -- (UIColor*)textColor { - WUXMSolidColorBrush* brush = rt_dynamic_cast([WUXMSolidColorBrush class], _xamlTextBlock.foreground); - - if (brush) { - return [UIColor colorWithRed:brush.color.r green:brush.color.g blue:brush.color.b alpha:brush.color.a]; - } else { - return nil; - } -} - -- (void)setLineBreakMode:(UILineBreakMode)mode { - XamlUtilities::ApplyLineBreakModeOnTextBlock(_xamlTextBlock, mode, self.numberOfLines); -} - -- (UILineBreakMode)lineBreakMode { - switch (_xamlTextBlock.textWrapping) { - case WXTextWrappingWrapWholeWords: - return UILineBreakModeWordWrap; - - case WXTextWrappingWrap: - return UILineBreakModeCharacterWrap; - - case WXTextWrappingNoWrap: - if (_xamlTextBlock.textTrimming == WXTextTrimmingClip) { - return UILineBreakModeClip; - } - } - - return WXTextTrimmingCharacterEllipsis; -} - -- (CGSize)intrinsicContentSize { - WFSize* inf = [WXSizeHelper fromDimensions:FLT_MAX height:FLT_MAX]; - [self _updateDummyTextBlock]; - [_dummyTextBlock measure:inf]; - return CGSizeMake(_dummyTextBlock.desiredSize.width, _dummyTextBlock.desiredSize.height); -} - -- (void)_updateDummyTextBlock { - // When a TextBlock is in the scene graph, it may have implicit constraints on it keeping it from being - // correctly Measured. To ensure an unbounded Measure we replicate the TextBlock, but never add it to - // the graph; it's used solely for measurement. - _dummyTextBlock.textWrapping = _xamlTextBlock.textWrapping; - _dummyTextBlock.textTrimming = _xamlTextBlock.textTrimming; - _dummyTextBlock.lineStackingStrategy = _xamlTextBlock.lineStackingStrategy; - _dummyTextBlock.lineHeight = _xamlTextBlock.lineHeight; - _dummyTextBlock.fontStyle = _xamlTextBlock.fontStyle; - _dummyTextBlock.fontFamily = _xamlTextBlock.fontFamily; - _dummyTextBlock.fontSize = _xamlTextBlock.fontSize; - _dummyTextBlock.textAlignment = _xamlTextBlock.textAlignment; - _dummyTextBlock.text = _xamlTextBlock.text; -} - -@end - @implementation _UIImageView_Proxy { // For convenience WXCImage* _xamlImage; diff --git a/Frameworks/UIKit/UIControl.mm b/Frameworks/UIKit/UIControl.mm index 4f1987ffa7..c3b7aec6ac 100644 --- a/Frameworks/UIKit/UIControl.mm +++ b/Frameworks/UIKit/UIControl.mm @@ -221,7 +221,7 @@ - (NSSet*)allTargets { */ - (void)setEnabled:(BOOL)enabled { // only update the value and then relayout if value actually changed - if ((_curState & UIControlStateDisabled) != enabled) { + if ((_curState & UIControlStateDisabled) != !enabled) { if (!enabled) { _curState |= UIControlStateDisabled; } else { diff --git a/Frameworks/UIKit/UILabel.mm b/Frameworks/UIKit/UILabel.mm index 755fd5c342..86b71d345d 100644 --- a/Frameworks/UIKit/UILabel.mm +++ b/Frameworks/UIKit/UILabel.mm @@ -24,6 +24,7 @@ #import #import "UIFontInternal.h" +#import "UILabelInternal.h" #import "CGContextInternal.h" #import "StarboardXaml/DisplayProperties.h" #import "XamlControls.h" @@ -262,6 +263,14 @@ - (instancetype)initWithCoder:(NSCoder*)coder { return self; } +// Returns access to the underlying TextBlock within the UILabel's Xaml representation +// Note: This is used for UX testing and won't be necessary when we are projecting +// UIKit.Label into ObjectiveC, as at that point we can just expose the TextBlock directly +// off of our UIKit.Label implementation. +- (WXCTextBlock*)_getXamlTextBlock { + return _textBlock; +} + - (void)_initUILabel { //////////////////////////////////////////////////////////////////// // TODO: Ultimately this will be a UIKit.Label projected to us and diff --git a/Frameworks/UIKit/UIView.mm b/Frameworks/UIKit/UIView.mm index 999288a402..9c21532cbc 100644 --- a/Frameworks/UIKit/UIView.mm +++ b/Frameworks/UIKit/UIView.mm @@ -1365,10 +1365,6 @@ static void adjustSubviews(UIView* self, CGSize parentSize, CGSize delta) { return; } -static float doRound(float f) { - return (float)(floor((f * 2) + 0.5) / 2.0f); -} - /** @Status Interoperable */ @@ -1382,17 +1378,11 @@ - (void)setFrame:(CGRect)frame { [self _updateHitTestability]; // Get our existing frame - CGRect curFrame; - curFrame = [self frame]; + CGRect curFrame = [self frame]; - if (memcmp(&frame, &curFrame, sizeof(CGRect)) == 0) { - return; - } - - frame.origin.x = doRound(frame.origin.x); - frame.origin.y = doRound(frame.origin.y); - frame.size.width = doRound(frame.size.width); - frame.size.height = doRound(frame.size.height); + // Round off the frame values to avoid sub-pixel layout + frame = doPixelRound(frame); + curFrame = doPixelRound(curFrame); if (DEBUG_LAYOUT) { TraceVerbose(TAG, @@ -1405,10 +1395,8 @@ - (void)setFrame:(CGRect)frame { frame.size.height); } - CGRect startFrame = frame; - - if (frame.origin.x == doRound(curFrame.origin.x) && frame.origin.y == doRound(curFrame.origin.y) && - frame.size.width == doRound(curFrame.size.width) && frame.size.height == doRound(curFrame.size.height)) { + // Don't bother doing work if there's no work to be done + if (memcmp(&frame, &curFrame, sizeof(CGRect)) == 0) { return; } @@ -1493,6 +1481,7 @@ - (void)setBounds:(CGRect)bounds { adjustSubviews(self, curBounds.size, delta); } + // TODO: Should we be rounding to the nearest pixel like we do in setFrame? [layer setBounds:bounds]; } @@ -1505,6 +1494,7 @@ - (void)setOrigin:(CGPoint)origin { curFrame = [layer frame]; curFrame.origin = origin; + // TODO: Should we be rounding to the nearest pixel like we do in setFrame? [layer setFrame:curFrame]; } @@ -2303,7 +2293,7 @@ - (UIWindow*)_getWindowInternal { } if (!priv->superview) { - // This is probably safe to do in all cases, but for now, let's constrain + // This is probably safe to do in all cases, but for now, let's constrain // this to middleware scenarios. We need to return a window in such cases // so point/rect/etc. conversion works properly. if (GetCACompositor()->IsRunningAsFramework()) { diff --git a/Frameworks/include/UILabelInternal.h b/Frameworks/include/UILabelInternal.h new file mode 100644 index 0000000000..11c23df6bd --- /dev/null +++ b/Frameworks/include/UILabelInternal.h @@ -0,0 +1,30 @@ +//****************************************************************************** +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// 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 + +@class WXCTextBlock; + +@interface UILabel (Internal) { +} +// Returns access to the underlying TextBlock within the UILabel's Xaml representation +// Note: This is used for UX testing and won't be necessary when we are projecting +// UIKit.Label into ObjectiveC, as at that point we can just expose the TextBlock directly +// off of our UIKit.Label implementation. +- (WXCTextBlock*)_getXamlTextBlock; +@end diff --git a/Frameworks/include/UIViewInternal.h b/Frameworks/include/UIViewInternal.h index 7d66f64bfb..eda8f7f694 100644 --- a/Frameworks/include/UIViewInternal.h +++ b/Frameworks/include/UIViewInternal.h @@ -33,6 +33,27 @@ @class WXFrameworkElement; @class WUXIPointerRoutedEventArgs; +// Round subpixel values to be able to perform per-pixel UI placement/calculations +inline float doPixelRound(float f) { + return (float)(floorf((f * 2) + 0.5) / 2.0f); +} + +// Round subpixel values to be able to perform per-pixel UI placement/calculations +inline CGSize doPixelRound(CGSize size) { + size.width = doPixelRound(size.width); + size.height = doPixelRound(size.height); + return size; +} + +// Round subpixel values to be able to perform per-pixel UI placement/calculations +inline CGRect doPixelRound(CGRect frame) { + frame.origin.x = doPixelRound(frame.origin.x); + frame.origin.y = doPixelRound(frame.origin.y); + frame.size.width = doPixelRound(frame.size.width); + frame.size.height = doPixelRound(frame.size.height); + return frame; +} + class UIViewPrivateState : public LLTreeNode { public: id superview; // id diff --git a/include/UIKit/UIButton.h b/include/UIKit/UIButton.h index 5c69e92de8..fa5cda77a3 100644 --- a/include/UIKit/UIButton.h +++ b/include/UIKit/UIButton.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2011, The Iconfactory. All rights reserved. - * Copyright (c) 2016 Microsoft Corporation. All rights reserved. + * Copyright (c) Microsoft Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -60,14 +60,14 @@ UIKIT_EXPORT_CLASS UITraitEnvironment> + (instancetype)buttonWithType:(UIButtonType)buttonType; -- (CGRect)backgroundRectForBounds:(CGRect)bounds; +- (CGRect)backgroundRectForBounds:(CGRect)bounds STUB_METHOD; - (CGRect)contentRectForBounds:(CGRect)bounds; - (CGRect)imageRectForContentRect:(CGRect)contentRect; - (CGRect)titleRectForContentRect:(CGRect)contentRect; - (NSAttributedString*)attributedTitleForState:(UIControlState)state STUB_METHOD; - (NSString*)titleForState:(UIControlState)state; - (UIColor*)titleColorForState:(UIControlState)state; -- (UIColor*)titleShadowColorForState:(UIControlState)state; +- (UIColor*)titleShadowColorForState:(UIControlState)state STUB_METHOD; - (UIImage*)backgroundImageForState:(UIControlState)state; - (UIImage*)imageForState:(UIControlState)state; - (void)setAttributedTitle:(NSAttributedString*)title forState:(UIControlState)state STUB_METHOD; @@ -75,14 +75,14 @@ UIKIT_EXPORT_CLASS - (void)setImage:(UIImage*)image forState:(UIControlState)state; - (void)setTitle:(NSString*)title forState:(UIControlState)state; - (void)setTitleColor:(UIColor*)color forState:(UIControlState)state; -- (void)setTitleShadowColor:(UIColor*)color forState:(UIControlState)state; +- (void)setTitleShadowColor:(UIColor*)color forState:(UIControlState)state STUB_METHOD; -@property (nonatomic) BOOL adjustsImageWhenDisabled STUB_PROPERTY; -@property (nonatomic) BOOL adjustsImageWhenHighlighted STUB_PROPERTY; +@property (nonatomic) BOOL adjustsImageWhenDisabled; +@property (nonatomic) BOOL adjustsImageWhenHighlighted; @property (nonatomic) BOOL reversesTitleShadowWhenHighlighted STUB_PROPERTY; @property (nonatomic) BOOL showsTouchWhenHighlighted STUB_PROPERTY; @property (nonatomic) CGSize titleShadowOffset STUB_PROPERTY; -@property (nonatomic) NSLineBreakMode lineBreakMode STUB_PROPERTY; +@property (nonatomic) NSLineBreakMode lineBreakMode; @property (nonatomic) UIEdgeInsets contentEdgeInsets; @property (nonatomic) UIEdgeInsets imageEdgeInsets; @property (nonatomic) UIEdgeInsets titleEdgeInsets; diff --git a/samples/XAMLCatalog/XAMLCatalog/MiscellaneousViewController.m b/samples/XAMLCatalog/XAMLCatalog/MiscellaneousViewController.m index b88b1ea12b..66bc6f4984 100644 --- a/samples/XAMLCatalog/XAMLCatalog/MiscellaneousViewController.m +++ b/samples/XAMLCatalog/XAMLCatalog/MiscellaneousViewController.m @@ -16,6 +16,7 @@ #import "MiscellaneousViewController.h" #import +#import #import @implementation MiscellaneousViewController { diff --git a/tests/functionaltests/Tests/UIKitTests/UIButtonTests.mm b/tests/functionaltests/Tests/UIKitTests/UIButtonTests.mm index c3c32d51f5..7618e608f0 100644 --- a/tests/functionaltests/Tests/UIKitTests/UIButtonTests.mm +++ b/tests/functionaltests/Tests/UIKitTests/UIButtonTests.mm @@ -16,6 +16,7 @@ #import #import "FunctionalTestHelpers.h" #import "UXTestHelpers.h" +#import "UILabelInternal.h" // Re-use existing sample code for validation #import "UIKitControls/UIButtonViewController.h" @@ -127,7 +128,7 @@ - (void)dealloc { dispatch_async(dispatch_get_main_queue(), ^{ // Extract UIButton.titleLabel control to verify its visual state - WXFrameworkElement* titleElement = [buttonVC.button.titleLabel xamlElement]; + WXFrameworkElement* titleElement = [buttonVC.button.titleLabel _getXamlTextBlock]; ASSERT_OBJCNE(titleElement, nil); // Register RAII event subscription handler @@ -135,6 +136,7 @@ - (void)dealloc { // Extract the foreground color from the XAML object WUXMSolidColorBrush* solidBrush = rt_dynamic_cast([WUXMSolidColorBrush class], [sender getValue:dp]); + // Validation UIColor* titleColorNormal = [buttonVC.button titleColorForState:UIControlStateNormal]; EXPECT_TRUE_MSG(UXTestAPI::IsRGBAEqual(solidBrush, titleColorNormal), @"Failed to match XAML- and UIButton title color"); @@ -161,7 +163,7 @@ - (void)dealloc { dispatch_async(dispatch_get_main_queue(), ^{ // Extract UIButton.titleLabel control to verify its visual state - WXFrameworkElement* titleElement = [buttonVC.button.titleLabel xamlElement]; + WXFrameworkElement* titleElement = [buttonVC.button.titleLabel _getXamlTextBlock]; ASSERT_TRUE(titleElement); // Register RAII event subscription handler