// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #include "pch.h" #include "CalculationResult.h" #include "CalculationResultAutomationPeer.h" #include "CalcViewModel/Common/LocalizationSettings.h" using namespace CalculatorApp; using namespace CalculatorApp::Controls; using namespace CalculatorApp::Common; using namespace Platform; using namespace Windows::Devices::Input; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Xaml::Controls::Primitives; using namespace Windows::UI::Xaml::Data; using namespace Windows::UI::Xaml::Input; using namespace Windows::UI::Xaml::Media; using namespace Windows::UI::Xaml::Navigation; using namespace Windows::UI::Xaml::Automation; using namespace Windows::UI::Xaml::Automation::Peers; using namespace std; DEPENDENCY_PROPERTY_INITIALIZATION(CalculationResult, IsActive); DEPENDENCY_PROPERTY_INITIALIZATION(CalculationResult, MinFontSize); DEPENDENCY_PROPERTY_INITIALIZATION(CalculationResult, MaxFontSize); DEPENDENCY_PROPERTY_INITIALIZATION(CalculationResult, DisplayMargin); DEPENDENCY_PROPERTY_INITIALIZATION(CalculationResult, DisplayValue); DEPENDENCY_PROPERTY_INITIALIZATION(CalculationResult, IsInError); DEPENDENCY_PROPERTY_INITIALIZATION(CalculationResult, IsOperatorCommand); #define SCALEFACTOR 0.357143 #define SMALLHEIGHTSCALEFACTOR 0 #define HEIGHTCUTOFF 100 #define INCREMENTOFFSET 1 #define MAXFONTINCREMENT 5 #define WIDTHTOFONTSCALAR 0.0556513 #define WIDTHTOFONTOFFSET 3 #define WIDTHCUTOFF 50 #define FONTTOLERANCE 0.001 #define SCROLL_RATIO 0.7 // We need a safety margin to guarantee we correctly always show/hide ScrollLeft and ScrollRight buttons when necessary. // In rare cases, ScrollViewer::HorizontalOffset is a little low by a few (sub)pixels when users scroll to one of the extremity // and no events are launched when they scroll again in the same direction #define SCROLL_BUTTONS_APPROXIMATION_RANGE 4 CalculationResult::CalculationResult() : m_isScalingText(false) , m_haveCalculatedMax(false) { } Platform::String ^ CalculationResult::GetRawDisplayValue() { return LocalizationSettings::GetInstance().RemoveGroupSeparators(DisplayValue); } void CalculationResult::OnApplyTemplate() { if (m_textContainer) { if (m_textContainerLayoutChangedToken.Value != 0) { m_textContainer->LayoutUpdated -= m_textContainerLayoutChangedToken; m_textContainerLayoutChangedToken.Value = 0; } if (m_textContainerSizeChangedToken.Value != 0) { m_textContainer->SizeChanged -= m_textContainerSizeChangedToken; m_textContainerSizeChangedToken.Value = 0; } if (m_textContainerViewChangedToken.Value != 0) { m_textContainer->ViewChanged -= m_textContainerViewChangedToken; m_textContainerViewChangedToken.Value = 0; } } if (m_textBlock != nullptr && m_textBlockSizeChangedToken.Value != 0) { m_textBlock->SizeChanged -= m_textBlockSizeChangedToken; m_textBlockSizeChangedToken.Value = 0; } if (m_scrollLeft != nullptr && m_scrollLeftClickToken.Value != 0) { m_scrollLeft->Click -= m_scrollLeftClickToken; m_scrollLeftClickToken.Value = 0; } if (m_scrollRight != nullptr && m_scrollRightClickToken.Value != 0) { m_scrollRight->Click -= m_scrollRightClickToken; m_scrollRightClickToken.Value = 0; } m_textContainer = dynamic_cast(GetTemplateChild("TextContainer")); if (m_textContainer) { // We want to know when the size of the container changes so // we can rescale the textbox m_textContainerSizeChangedToken = m_textContainer->SizeChanged += ref new SizeChangedEventHandler(this, &CalculationResult::OnTextContainerSizeChanged); m_textContainerViewChangedToken = m_textContainer->ViewChanged += ref new Windows::Foundation::EventHandler( this, &CalculatorApp::Controls::CalculationResult::OnTextContainerOnViewChanged); m_textContainerLayoutChangedToken = m_textContainer->LayoutUpdated += ref new EventHandler(this, &CalculationResult::OnTextContainerLayoutUpdated); m_textContainer->ChangeView(m_textContainer->ExtentWidth - m_textContainer->ViewportWidth, nullptr, nullptr); m_scrollLeft = dynamic_cast(GetTemplateChild("ScrollLeft")); if (m_scrollLeft) { m_scrollLeftClickToken = m_scrollLeft->Click += ref new RoutedEventHandler(this, &CalculationResult::OnScrollLeftClick); } m_scrollRight = dynamic_cast(GetTemplateChild("ScrollRight")); if (m_scrollRight) { m_scrollRightClickToken = m_scrollRight->Click += ref new RoutedEventHandler(this, &CalculationResult::OnScrollRightClick); } m_textBlock = dynamic_cast(GetTemplateChild("NormalOutput")); if (m_textBlock) { m_textBlock->Visibility = ::Visibility::Visible; m_textBlockSizeChangedToken = m_textBlock->SizeChanged += ref new SizeChangedEventHandler(this, &CalculationResult::OnTextBlockSizeChanged); } } UpdateVisualState(); UpdateTextState(); } void CalculationResult::OnTextContainerLayoutUpdated(Object ^ /*sender*/, Object ^ /*e*/) { if (m_isScalingText) { UpdateTextState(); } } void CalculationResult::OnTextContainerSizeChanged(Object ^ /*sender*/, SizeChangedEventArgs ^ /*e*/) { UpdateTextState(); } void CalculationResult::OnIsActivePropertyChanged(bool /*oldValue*/, bool /*newValue */) { UpdateVisualState(); } void CalculationResult::OnDisplayValuePropertyChanged(String ^ /*oldValue*/, String ^ /*newValue*/) { UpdateTextState(); } void CalculationResult::OnMinFontSizePropertyChanged(double /*oldValue*/, double /*newValue*/) { UpdateTextState(); } void CalculationResult::OnMaxFontSizePropertyChanged(double /*oldValue*/, double /*newValue*/) { UpdateTextState(); } void CalculationResult::OnIsInErrorPropertyChanged(bool /*oldValue*/, bool newValue) { // We need to have a good template for this to work if (m_textBlock == nullptr) { return; } if (newValue) { // If there's an error message we need to override the normal display font // with the font appropriate for this language. This is because the error // message is localized and therefore can contain characters that are not // available in the normal font. // We use UIText as the font type because this is the most common font type to use m_textBlock->FontFamily = LocalizationService::GetInstance()->GetLanguageFontFamilyForType(LanguageFontType::UIText); } else { // The error result is no longer an error so we will restore the // value to FontFamily property to the value provided in the style // for the TextBlock in the template. m_textBlock->ClearValue(TextBlock::FontFamilyProperty); } } void CalculationResult::UpdateVisualState() { if (IsActive) { VisualStateManager::GoToState(this, "Active", true); } else { VisualStateManager::GoToState(this, "Normal", true); } } void CalculationResult::UpdateTextState() { if ((m_textContainer == nullptr) || (m_textBlock == nullptr)) { return; } auto containerSize = m_textContainer->ActualWidth; String ^ oldText = m_textBlock->Text; String ^ newText = Utils::LRO + DisplayValue + Utils::PDF; // Initiate the scaling operation // UpdateLayout will keep calling us until we make it through the below 2 if-statements if (!m_isScalingText || oldText != newText) { m_textBlock->Text = newText; m_isScalingText = true; m_haveCalculatedMax = false; m_textBlock->InvalidateArrange(); return; } if (containerSize > 0) { double widthDiff = abs(m_textBlock->ActualWidth - containerSize); double fontSizeChange = INCREMENTOFFSET; if (widthDiff > WIDTHCUTOFF) { fontSizeChange = min(max(floor(WIDTHTOFONTSCALAR * widthDiff) - WIDTHTOFONTOFFSET, INCREMENTOFFSET), MAXFONTINCREMENT); } if (m_textBlock->ActualWidth < containerSize && abs(m_textBlock->FontSize - MaxFontSize) > FONTTOLERANCE && !m_haveCalculatedMax) { ModifyFontAndMargin(m_textBlock, fontSizeChange); m_textBlock->InvalidateArrange(); return; } if (fontSizeChange < 5) { m_haveCalculatedMax = true; } if (m_textBlock->ActualWidth >= containerSize && abs(m_textBlock->FontSize - MinFontSize) > FONTTOLERANCE) { ModifyFontAndMargin(m_textBlock, -1 * fontSizeChange); m_textBlock->InvalidateArrange(); return; } assert(m_textBlock->FontSize >= MinFontSize && m_textBlock->FontSize <= MaxFontSize); m_isScalingText = false; if (IsOperatorCommand) { m_textContainer->ChangeView(0.0, nullptr, nullptr); } else { m_textContainer->ChangeView(m_textContainer->ExtentWidth - m_textContainer->ViewportWidth, nullptr, nullptr); } } } void CalculationResult::ScrollLeft() { if (m_textContainer == nullptr) { return; } if (m_textContainer->HorizontalOffset > 0) { double offset = m_textContainer->HorizontalOffset - (SCROLL_RATIO * m_textContainer->ViewportWidth); m_textContainer->ChangeView(offset, nullptr, nullptr); } } void CalculationResult::ScrollRight() { if (m_textContainer == nullptr) { return; } if (m_textContainer->HorizontalOffset < m_textContainer->ExtentWidth - m_textContainer->ViewportWidth) { double offset = m_textContainer->HorizontalOffset + (SCROLL_RATIO * m_textContainer->ViewportWidth); m_textContainer->ChangeView(offset, nullptr, nullptr); } } void CalculationResult::OnKeyDown(KeyRoutedEventArgs ^ e) { switch (e->Key) { case Windows::System::VirtualKey::Left: this->ScrollLeft(); break; case Windows::System::VirtualKey::Right: this->ScrollRight(); break; } } void CalculationResult::OnScrollLeftClick(Object ^ sender, RoutedEventArgs ^ /*e*/) { ScrollLeft(); } void CalculationResult::OnScrollRightClick(Object ^ sender, RoutedEventArgs ^ /*e*/) { ScrollRight(); } void CalculationResult::UpdateScrollButtons() { if (m_textContainer == nullptr) { return; } if (m_scrollLeft != nullptr) { m_scrollLeft->Visibility = m_textContainer->HorizontalOffset > SCROLL_BUTTONS_APPROXIMATION_RANGE ? ::Visibility::Visible : ::Visibility::Collapsed; } if (m_scrollRight != nullptr) { m_scrollRight->Visibility = m_textContainer->HorizontalOffset + m_textContainer->ViewportWidth + SCROLL_BUTTONS_APPROXIMATION_RANGE < m_textContainer->ExtentWidth ? ::Visibility::Visible : ::Visibility::Collapsed; } } void CalculationResult::ModifyFontAndMargin(TextBlock ^ textBox, double fontChange) { double cur = textBox->FontSize; double newFontSize = 0.0; double scaleFactor = SCALEFACTOR; if (m_textContainer->ActualHeight <= HEIGHTCUTOFF) { scaleFactor = SMALLHEIGHTSCALEFACTOR; } newFontSize = min(max(cur + fontChange, MinFontSize), MaxFontSize); m_textContainer->Padding = Thickness(0, 0, 0, scaleFactor * abs(cur - newFontSize)); textBox->FontSize = newFontSize; } void CalculationResult::OnTapped(TappedRoutedEventArgs ^ e) { this->Focus(::FocusState::Programmatic); RaiseSelectedEvent(); } void CalculationResult::OnRightTapped(RightTappedRoutedEventArgs ^ e) { auto requestedElement = e->OriginalSource; if (requestedElement->Equals(dynamic_cast(m_textBlock))) { m_textBlock->Focus(::FocusState::Programmatic); } else { this->Focus(::FocusState::Programmatic); } } AutomationPeer ^ CalculationResult::OnCreateAutomationPeer() { return ref new CalculationResultAutomationPeer(this); } void CalculationResult::ProgrammaticSelect() { RaiseSelectedEvent(); } void CalculationResult::RaiseSelectedEvent() { Selected(this); } void CalculationResult::OnTextContainerOnViewChanged(Object ^ /*sender*/, ScrollViewerViewChangedEventArgs ^ e) { UpdateScrollButtons(); } void CalculationResult::OnTextBlockSizeChanged(Object ^ /*sender*/, SizeChangedEventArgs ^ /*e*/) { UpdateScrollButtons(); }