/************************************************************************** * Copyright (C) 2016 by Savoir-faire Linux * * Author: J�ger Nicolas <nicolas.jager@savoirfairelinux.com> * * Author: Traczyk Andreas <andreas.traczyk@savoirfairelinux.com> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * **************************************************************************/ #include "pch.h" #include "VideoPage.xaml.h" #include <MemoryBuffer.h> // IMemoryBufferByteAccess using namespace RingClientUWP::Views; using namespace ViewModel; using namespace Video; using namespace Concurrency; using namespace Platform; using namespace Windows::Devices::Enumeration; 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::Media::Capture; using namespace Windows::ApplicationModel::Core; using namespace Windows::UI::Core; using namespace Windows::UI; using namespace Windows::Graphics::Display; using namespace Windows::Graphics::Imaging; using namespace Windows::Media; using namespace Windows::UI::Xaml::Media::Imaging; using namespace Windows::UI::Xaml::Media::Animation; using namespace Windows::UI::Xaml::Shapes; using namespace Windows::Devices::Sensors; using namespace Windows::UI::Input; VideoPage::VideoPage() { InitializeComponent(); barFading = true; Page::NavigationCacheMode = Navigation::NavigationCacheMode::Required; VideoManager::instance->rendererManager()->writeVideoFrame += ref new WriteVideoFrame([this](String^ id, uint8_t* buf, int width, int height) { CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(CoreDispatcherPriority::High, ref new DispatchedHandler([=]() { try { currentRendererWrapper = VideoManager::instance->rendererManager()->renderer(id); if (!currentRendererWrapper) { return; } else { currentRendererWrapper->isRendering = true; create_task(WriteFrameAsSoftwareBitmapAsync(id, buf, width, height)) .then([=](task<void> previousTask) { try { previousTask.get(); } catch (Platform::Exception^ e) { EXC_(e); } }); } } catch(Platform::COMException^ e) { EXC_(e); } })); }); VideoManager::instance->captureManager()->startPreviewing += ref new StartPreviewing([this]() { _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Visible; _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Visible; _PreviewImage_->FlowDirection = VideoManager::instance->captureManager()->mirroringPreview ? Windows::UI::Xaml::FlowDirection::RightToLeft : Windows::UI::Xaml::FlowDirection::LeftToRight; double aspectRatio = VideoManager::instance->captureManager()->aspectRatio(); _PreviewImage_->Height = ( _videoContent_->ActualHeight / 4 ); _PreviewImage_->Width = _PreviewImage_->Height * aspectRatio; _PreviewImageRect_->Width = _PreviewImage_->Width; _PreviewImageRect_->Height = _PreviewImage_->Height; }); VideoManager::instance->captureManager()->stopPreviewing += ref new StopPreviewing([this]() { _PreviewImage_->Source = nullptr; _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; }); VideoManager::instance->captureManager()->getSink += ref new GetSink([this]() { return _PreviewImage_; }); VideoManager::instance->rendererManager()->clearRenderTarget += ref new ClearRenderTarget([this]() { _IncomingVideoImage_->Source = nullptr; }); RingD::instance->windowResized += ref new WindowResized([=](float width, float height) { }); RingD::instance->incomingAccountMessage += ref new IncomingAccountMessage([&](String^ accountId, String^ from, String^ payload) { scrollDown(); }); RingD::instance->vCardUpdated += ref new VCardUpdated([&](Contact^ contact) { Utils::runOnUIThread([this, contact]() { SmartPanelItemsViewModel::instance->update({ "_bestName2", "_avatarImage" }); updatePageContent(); }); }); RingD::instance->stateChange += ref new StateChange([&](String^ callId, CallStatus state, int code) { switch (state) { case CallStatus::IN_PROGRESS: { for (auto it : SmartPanelItemsViewModel::instance->itemsList) if (it->_callStatus != CallStatus::IN_PROGRESS && it->_callId != callId) RingD::instance->pauseCall(Utils::toString(it->_callId)); _callPaused_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; _IncomingVideoImage_->Visibility = Windows::UI::Xaml::Visibility::Visible; callTimerUpdater->Start(); updateCallTimer(nullptr, nullptr); RingD::instance->startSmartInfo(500); videoPage_InputHandlerToken = Window::Current->CoreWindow->KeyDown += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &VideoPage::_corewindow__KeyDown); break; } case CallStatus::ENDED: { Video::VideoManager::instance->rendererManager()->raiseClearRenderTarget(); if (RingD::instance->isFullScreen) RingD::instance->setWindowedMode(); /* "close" the chat panel */ closeChatPanel(); callTimerUpdater->Stop(); //RingD::instance->stopSmartInfo(); Window::Current->CoreWindow->KeyDown -= videoPage_InputHandlerToken; _smartInfoBorder_->Visibility = VIS::Collapsed; break; } case CallStatus::PEER_PAUSED: case CallStatus::PAUSED: _callPaused_->Visibility = Windows::UI::Xaml::Visibility::Visible; _IncomingVideoImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; break; } }); RingD::instance->messageDataLoaded += ref new MessageDataLoaded([&]() { scrollDown(); }); RingD::instance->updateSmartInfo += ref new RingClientUWP::UpdateSmartInfo(this, &RingClientUWP::Views::VideoPage::OnsmartInfoUpdated); RingD::instance->incomingMessage += ref new RingClientUWP::IncomingMessage(this, &RingClientUWP::Views::VideoPage::OnincomingMessage); RingD::instance->incomingVideoMuted += ref new RingClientUWP::IncomingVideoMuted(this, &RingClientUWP::Views::VideoPage::OnincomingVideoMuted); VideoManager::instance->captureManager()->startPreviewing += ref new RingClientUWP::StartPreviewing(this, &RingClientUWP::Views::VideoPage::OnstartPreviewing); VideoManager::instance->captureManager()->stopPreviewing += ref new RingClientUWP::StopPreviewing(this, &RingClientUWP::Views::VideoPage::OnstopPreviewing); RingD::instance->audioMuted += ref new RingClientUWP::AudioMuted(this, &RingClientUWP::Views::VideoPage::OnaudioMuted); RingD::instance->videoMuted += ref new RingClientUWP::VideoMuted(this, &RingClientUWP::Views::VideoPage::OnvideoMuted); InitManipulationTransforms(); _PreviewImage_->ManipulationDelta += ref new ManipulationDeltaEventHandler(this, &VideoPage::PreviewImage_ManipulationDelta); _PreviewImage_->ManipulationCompleted += ref new ManipulationCompletedEventHandler(this, &VideoPage::PreviewImage_ManipulationCompleted); _PreviewImage_->ManipulationMode = ManipulationModes::TranslateX | ManipulationModes::TranslateY; _PreviewImageResizer_->ManipulationDelta += ref new ManipulationDeltaEventHandler(this, &VideoPage::PreviewImageResizer_ManipulationDelta); _PreviewImageResizer_->ManipulationCompleted += ref new ManipulationCompletedEventHandler(this, &VideoPage::PreviewImageResizer_ManipulationCompleted); _PreviewImageResizer_->ManipulationMode = ManipulationModes::TranslateY; _chatPanelResizeBarGrid_->ManipulationDelta += ref new ManipulationDeltaEventHandler(this, &VideoPage::_chatPanelResizeBarGrid__ManipulationDelta); _chatPanelResizeBarGrid_->ManipulationCompleted += ref new ManipulationCompletedEventHandler(this, &VideoPage::_chatPanelResizeBarGrid__ManipulationCompleted); _chatPanelResizeBarGrid_->ManipulationMode = ManipulationModes::TranslateX | ManipulationModes::TranslateY; TimeSpan timeSpan; timeSpan.Duration = static_cast<long long>(1e7); callTimerUpdater = ref new DispatcherTimer; callTimerUpdater->Interval = timeSpan; callTimerUpdater->Tick += ref new Windows::Foundation::EventHandler<Object^>(this, &VideoPage::updateCallTimer); showSmartInfo = false; } void VideoPage::OnsmartInfoUpdated(const std::map<std::string, std::string>& info) { auto smartInfo = Utils::convertMap(info); if (auto selectedItem = SmartPanelItemsViewModel::instance->_selectedItem) _si_CallId_->Text = "CallID: " + selectedItem->_callId; if (smartInfo->HasKey("local FPS")) _si_fps1_->Text = smartInfo->Lookup("local FPS"); if (smartInfo->HasKey("local video codec")) _si_vc1_->Text = smartInfo->Lookup("local video codec"); if (smartInfo->HasKey("local audio codec")) _si_ac1_->Text = smartInfo->Lookup("local audio codec"); auto localResolution = VideoManager::instance->captureManager()->activeDevice->currentResolution(); _si_res1_->Text = localResolution->getFriendlyName(); if (smartInfo->HasKey("remote FPS")) _si_fps2_->Text = smartInfo->Lookup("remote FPS") ; if (smartInfo->HasKey("remote video codec")) _si_vc2_->Text = smartInfo->Lookup("remote video codec"); if (smartInfo->HasKey("remote audio codec")) _si_ac2_->Text = smartInfo->Lookup("remote audio codec"); if (currentRendererWrapper) { auto remoteResolution = currentRendererWrapper->renderer->width.ToString() + "x" + currentRendererWrapper->renderer->height.ToString(); _si_res2_->Text = remoteResolution; } } void VideoPage::updateCallTimer(Object^ sender, Object^ e) { if(auto item = SmartPanelItemsViewModel::instance->_selectedItem) _callTime_->Text = item->_callTime; } void VideoPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) { updatePageContent(); closeChatPanel(); } void RingClientUWP::Views::VideoPage::updatePageContent() { auto item = SmartPanelItemsViewModel::instance->_selectedItem; auto contact = (item) ? item->_contact : nullptr; if (!contact) return; _contactName_->Text = contact->_bestName; //_contactBarAvatar_->ImageSource = RingClientUWP::ResourceMananger::instance->imageFromRelativePath(contact->_avatarImage); if (contact->_avatarImage != " ") { auto avatarImageUri = ref new Windows::Foundation::Uri(contact->_avatarImage); _contactBarAvatar_->ImageSource = ref new BitmapImage(avatarImageUri); _defaultContactBarAvatarGrid_->Visibility = VIS::Collapsed; _contactBarAvatarGrid_->Visibility = VIS::Visible; } else { _defaultContactBarAvatarGrid_->Visibility = VIS::Visible; _contactBarAvatarGrid_->Visibility = VIS::Collapsed; _defaultAvatar_->Fill = contact->_avatarColorBrush; _defaultAvatarInitial_->Text = Utils::getUpperInitial(contact->_bestName2); } _messagesList_->ItemsSource = contact->_conversation->_messages; scrollDown(); } void VideoPage::updatePreviewFrameDimensions() { double aspectRatio = VideoManager::instance->captureManager()->aspectRatio(); TransformGroup^ transforms = ref new TransformGroup(); double scaleValue = 1 + userPreviewHeightModifier / _PreviewImage_->Height; scaleValue = std::max(std::min(1.75, scaleValue), 0.5); userPreviewHeightModifier = _PreviewImage_->Height * (scaleValue - 1); ScaleTransform^ scale = ref new ScaleTransform(); scale->ScaleX = scaleValue; scale->ScaleY = scaleValue; TranslateTransform^ translate = ref new TranslateTransform(); switch (quadrant) { case Quadrant::SE: translate->Y = -userPreviewHeightModifier; translate->X = translate->Y * aspectRatio; break; case Quadrant::SW: translate->Y = -userPreviewHeightModifier; translate->X = 0; break; case Quadrant::NW: translate->Y = 0; translate->X = 0; break; case Quadrant::NE: translate->Y = 0; translate->X = -userPreviewHeightModifier * aspectRatio; break; default: break; } transforms->Children->Append(scale); transforms->Children->Append(translate); _PreviewImage_->RenderTransform = transforms; _PreviewImageResizer_->RenderTransform = translate; arrangeResizer(); } void RingClientUWP::Views::VideoPage::scrollDown() { _scrollView_->UpdateLayout(); _scrollView_->ScrollToVerticalOffset(_scrollView_->ScrollableHeight); } void RingClientUWP::Views::VideoPage::screenVideo(bool state) { if (state) { Video::VideoManager::instance->rendererManager()->raiseClearRenderTarget(); _callPaused_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; _IncomingVideoImage_->Visibility = Windows::UI::Xaml::Visibility::Visible; _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Visible; _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Visible; } else { _callPaused_->Visibility = Windows::UI::Xaml::Visibility::Visible; _IncomingVideoImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; } } void RingClientUWP::Views::VideoPage::_sendBtn__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { sendMessage(); } void RingClientUWP::Views::VideoPage::sendMessage() { if (auto item = SmartPanelItemsViewModel::instance->_selectedItem) { auto contact = item->_contact; auto txt = _messageTextBox_->Text; /* empty the textbox */ _messageTextBox_->Text = ""; if (!contact || txt->IsEmpty()) return; RingD::instance->sendSIPTextMessage(txt); scrollDown(); } } void RingClientUWP::Views::VideoPage::Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { } void RingClientUWP::Views::VideoPage::_btnCancel__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { } void RingClientUWP::Views::VideoPage::_btnHangUp__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { if (auto item = SmartPanelItemsViewModel::instance->_selectedItem) { _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; RingD::instance->hangUpCall2(item->_callId); pressHangUpCall(); } else WNG_("item not found, cannot hang up"); } void RingClientUWP::Views::VideoPage::_btnPause__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { auto item = SmartPanelItemsViewModel::instance->_selectedItem; if (item->_callStatus == CallStatus::IN_PROGRESS) RingD::instance->pauseCall(Utils::toString(item->_callId)); else if (item->_callStatus == CallStatus::PAUSED) RingD::instance->unPauseCall(Utils::toString(item->_callId)); pauseCall(); } void RingClientUWP::Views::VideoPage::_btnChat__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { if (!isChatPanelOpen) { openChatPanel(); } else { closeChatPanel(); } } void RingClientUWP::Views::VideoPage::_btnAddFriend__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { addContactCall(); } void RingClientUWP::Views::VideoPage::_btnSwitch__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { transferCall(); } void RingClientUWP::Views::VideoPage::_btnMicrophone__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { switchMicrophoneStateCall(); auto item = SmartPanelItemsViewModel::instance->_selectedItem; auto state = !item->_audioMuted; item->_audioMuted = state; // refacto : compare how video and audios are muted, then decide which solution is best. RingD::instance->muteAudio(Utils::toString(item->_callId), state); // nb : muteAudio == setMuteAudio } void RingClientUWP::Views::VideoPage::_btnMemo__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { reccordVideoCall(); } void RingClientUWP::Views::VideoPage::_btnHQ__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { qualityVideoLevelCall(); } void RingClientUWP::Views::VideoPage::_btnVideo__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { switchVideoStateCall(); } void RingClientUWP::Views::VideoPage::_videoControl__PointerMoved(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { if (barFading) fadeVideoControlsStoryboard->Begin(); } void RingClientUWP::Views::VideoPage::btnAny_entered(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { barFading = false; fadeVideoControlsStoryboard->Stop(); } void RingClientUWP::Views::VideoPage::btnAny_exited(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { barFading = true; } void VideoPage::SetBuffer(uint8_t* buf, int width, int height) { videoFrame = ref new VideoFrame(BitmapPixelFormat::Bgra8, width, height); softwareBitmap = videoFrame->SoftwareBitmap; BitmapBuffer^ bitmapBuffer = softwareBitmap->LockBuffer(BitmapBufferAccessMode::Write); IMemoryBufferReference^ memoryBufferReference = bitmapBuffer->CreateReference(); Microsoft::WRL::ComPtr<IMemoryBufferByteAccess> byteAccess; if (SUCCEEDED(reinterpret_cast<IUnknown*>(memoryBufferReference)->QueryInterface(IID_PPV_ARGS(&byteAccess)))) { byte* data; unsigned capacity; byteAccess->GetBuffer(&data, &capacity); std::memcpy(data, buf, static_cast<size_t>(capacity)); } delete memoryBufferReference; delete bitmapBuffer; } task<void> VideoPage::WriteFrameAsSoftwareBitmapAsync(String^ id, uint8_t* buf, int width, int height) { SetBuffer(buf, width, height); VideoManager::instance->rendererManager()->renderer(id)->isRendering = false; auto sbSource = ref new Media::Imaging::SoftwareBitmapSource(); return create_task(sbSource->SetBitmapAsync(softwareBitmap)) .then([this, sbSource]() { try { _IncomingVideoImage_->Source = sbSource; } catch (Exception^ e) { EXC_(e); } }); } void RingClientUWP::Views::VideoPage::OnincomingMessage(Platform::String ^callId, Platform::String ^payload) { openChatPanel(); scrollDown(); } void RingClientUWP::Views::VideoPage::_btnVideo__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { auto item = SmartPanelItemsViewModel::instance->_selectedItem; item->muteVideo(!item->_videoMuted); } void RingClientUWP::Views::VideoPage::OnincomingVideoMuted(Platform::String ^callId, bool state) { /*_callPaused_->Visibility = (state) ? Windows::UI::Xaml::Visibility::Visible : Windows::UI::Xaml::Visibility::Collapsed;*/ _IncomingVideoImage_->Visibility = (state) ? Windows::UI::Xaml::Visibility::Collapsed : Windows::UI::Xaml::Visibility::Visible; } void RingClientUWP::Views::VideoPage::OnstartPreviewing() { _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Visible; _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Visible; } void RingClientUWP::Views::VideoPage::OnstopPreviewing() { _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Collapsed; } void RingClientUWP::Views::VideoPage::_btnMicrophone__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { switchMicrophoneStateCall(); auto item = SmartPanelItemsViewModel::instance->_selectedItem; auto state = !item->_audioMuted; item->_audioMuted = state; // refacto : compare how video and audios are muted, then decide which solution is best. RingD::instance->muteAudio(Utils::toString(item->_callId), state); // nb : muteAudio == setMuteAudio } void RingClientUWP::Views::VideoPage::OnaudioMuted(const std::string &callId, bool state) { _txbkMicrophoneMuted_->Visibility = (state) ? Windows::UI::Xaml::Visibility::Visible : Windows::UI::Xaml::Visibility::Collapsed; } void RingClientUWP::Views::VideoPage::OnvideoMuted(const std::string &callId, bool state) { _txbkVideoMuted_->Visibility = (state) ? Windows::UI::Xaml::Visibility::Visible : Windows::UI::Xaml::Visibility::Collapsed; } void RingClientUWP::Views::VideoPage::IncomingVideoImage_DoubleTapped(Platform::Object^ sender, Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs^ e) { RingD::instance->toggleFullScreen(); anchorPreview(); } void RingClientUWP::Views::VideoPage::InitManipulationTransforms() { PreviewImage_transforms = ref new TransformGroup(); PreviewImage_previousTransform = ref new MatrixTransform(); PreviewImage_previousTransform->Matrix = Matrix::Identity; PreviewImage_deltaTransform = ref new CompositeTransform(); PreviewImage_transforms->Children->Append(PreviewImage_previousTransform); PreviewImage_transforms->Children->Append(PreviewImage_deltaTransform); _PreviewImageRect_->RenderTransform = PreviewImage_transforms; } void RingClientUWP::Views::VideoPage::PreviewImage_ManipulationDelta(Platform::Object^ sender, ManipulationDeltaRoutedEventArgs^ e) { if (!isMovingPreview) isMovingPreview = true; _PreviewImageRect_->RenderTransform = PreviewImage_transforms; PreviewImage_previousTransform->Matrix = PreviewImage_transforms->Value; PreviewImage_deltaTransform->TranslateX = e->Delta.Translation.X; PreviewImage_deltaTransform->TranslateY = e->Delta.Translation.Y; computeQuadrant(); } void RingClientUWP::Views::VideoPage::computeQuadrant() { // Compute center coordinate of _videoContent_ Point centerOfVideoFrame = Point( static_cast<float>(_videoContent_->ActualWidth - _colChatBx_->ActualWidth) / 2, static_cast<float>(_videoContent_->ActualHeight - _rowChatBx_->ActualHeight) / 2 ); // Compute the center coordinate of _PreviewImage_ relative to _videoContent_ Point centerOfPreview = Point( static_cast<float>(_PreviewImage_->ActualWidth) / 2, static_cast<float>(_PreviewImage_->ActualHeight) / 2 ); UIElement^ container = dynamic_cast<UIElement^>(VisualTreeHelper::GetParent(_videoContent_)); GeneralTransform^ transform = _PreviewImage_->TransformToVisual(container); Point relativeCenterOfPreview = transform->TransformPoint(centerOfPreview); // Compute the difference between the center of _videoContent_ // and the relative scaled center of _PreviewImageRect_ Point diff = Point( centerOfVideoFrame.X - relativeCenterOfPreview.X, centerOfVideoFrame.Y - relativeCenterOfPreview.Y ); lastQuadrant = quadrant; if (diff.X > 0) quadrant = diff.Y > 0 ? Quadrant::NW : Quadrant::SW; else quadrant = diff.Y > 0 ? Quadrant::NE : Quadrant::SE; if (lastQuadrant != quadrant) { arrangeResizer(); } } void RingClientUWP::Views::VideoPage::arrangeResizer() { double scaleValue = (userPreviewHeightModifier + _PreviewImage_->Height) / _PreviewImage_->Height; float scaledWidth = static_cast<float>(scaleValue * _PreviewImage_->ActualWidth); float scaledHeight = static_cast<float>(scaleValue * _PreviewImage_->ActualHeight); float rSize = 20; // the size of the square UIElement used to resize the preview float xOffset, yOffset; PointCollection^ resizeTrianglePoints = ref new PointCollection(); switch (quadrant) { case Quadrant::SE: xOffset = 0; yOffset = 0; resizeTrianglePoints->Append(Point(xOffset, yOffset)); resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset)); resizeTrianglePoints->Append(Point(xOffset, yOffset + rSize)); break; case Quadrant::SW: xOffset = scaledWidth - rSize; yOffset = 0; resizeTrianglePoints->Append(Point(xOffset, yOffset)); resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset)); resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset + rSize)); break; case Quadrant::NW: xOffset = scaledWidth - rSize; yOffset = scaledHeight - rSize; resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset)); resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset + rSize)); resizeTrianglePoints->Append(Point(xOffset, yOffset + rSize)); break; case Quadrant::NE: xOffset = 0; yOffset = scaledHeight - rSize; resizeTrianglePoints->Append(Point(xOffset, yOffset + rSize)); resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset + rSize)); resizeTrianglePoints->Append(Point(xOffset, yOffset)); break; default: break; } _PreviewImageResizer_->Points = resizeTrianglePoints; } void RingClientUWP::Views::VideoPage::PreviewImage_ManipulationCompleted(Platform::Object^ sender, ManipulationCompletedRoutedEventArgs^ e) { isMovingPreview = false; anchorPreview(); updatePreviewFrameDimensions(); } void VideoPage::PreviewImageResizer_Pressed(Object^ sender, PointerRoutedEventArgs^ e) { isResizingPreview = true; lastUserPreviewHeightModifier = userPreviewHeightModifier; } void RingClientUWP::Views::VideoPage::anchorPreview() { PreviewImage_previousTransform->Matrix = Matrix::Identity; _PreviewImageRect_->RenderTransform = nullptr; switch (quadrant) { case Quadrant::SE: _PreviewImageRect_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Right; _PreviewImageRect_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Bottom; break; case Quadrant::SW: _PreviewImageRect_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Left; _PreviewImageRect_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Bottom; break; case Quadrant::NW: _PreviewImageRect_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Left; _PreviewImageRect_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Top; break; case Quadrant::NE: _PreviewImageRect_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Right; _PreviewImageRect_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Top; break; default: break; }; } void VideoPage::PreviewImageResizer_ManipulationDelta(Platform::Object^ sender, ManipulationDeltaRoutedEventArgs^ e) { if (!isResizingPreview) isResizingPreview = true; if (quadrant == Quadrant::NW || quadrant == Quadrant::NE) { userPreviewHeightModifier = lastUserPreviewHeightModifier + e->Cumulative.Translation.Y; } else { userPreviewHeightModifier = lastUserPreviewHeightModifier - (e->Cumulative.Translation.Y); } updatePreviewFrameDimensions(); } void VideoPage::PreviewImageResizer_ManipulationCompleted(Platform::Object^ sender, ManipulationCompletedRoutedEventArgs^ e) { isResizingPreview = false; if (!isHoveringOnResizer) { CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0); } } void VideoPage::PreviewImage_PointerReleased(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { // For some reason, PreviewImage_ManipulationCompleted doesn't always fired when the mouse is released anchorPreview(); updatePreviewFrameDimensions(); } void VideoPage::PreviewImageResizer_PointerEntered(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { isHoveringOnResizer = true; if (!isMovingPreview) { switch (quadrant) { case Quadrant::SE: case Quadrant::NW: CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::SizeNorthwestSoutheast, 0); break; case Quadrant::SW: case Quadrant::NE: CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::SizeNortheastSouthwest, 0); break; default: break; } } } void VideoPage::PreviewImageResizer_PointerExited(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { isHoveringOnResizer = false; if (!isResizingPreview) { CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0); } } void VideoPage::PreviewImageResizer_PointerReleased(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { if (!isHoveringOnResizer) { CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0); } } void VideoPage::_chatPanelResizeBarGrid__PointerEntered(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { if (chtBoxOrientation == Orientation::Horizontal) { CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::SizeWestEast, 0); } else { CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::SizeNorthSouth, 0); } } void VideoPage::_chatPanelResizeBarGrid__PointerExited(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { if (!isResizingChatPanel) { CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0); } } void VideoPage::_chatPanelResizeBarGrid__PointerPressed(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { isResizingChatPanel = true; lastchatPanelSize = chatPanelSize; } void VideoPage::_chatPanelResizeBarGrid__PointerReleased(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { isResizingChatPanel = false; } void VideoPage::_chatPanelResizeBarGrid__ManipulationDelta(Platform::Object^ sender, ManipulationDeltaRoutedEventArgs^ e) { if (chtBoxOrientation == Orientation::Horizontal) { chatPanelSize = lastchatPanelSize - e->Cumulative.Translation.X; } else { chatPanelSize = lastchatPanelSize - e->Cumulative.Translation.Y; } resizeChatPanel(); } void VideoPage::_chatPanelResizeBarGrid__ManipulationCompleted(Platform::Object^ sender, ManipulationCompletedRoutedEventArgs^ e) { isResizingChatPanel = false; CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0); } void VideoPage::openChatPanel() { resizeChatPanel(); isChatPanelOpen = true; SmartPanelItemsViewModel::instance->_selectedItem->_contact->_unreadMessages = 0; } void VideoPage::closeChatPanel() { _colChatBx_->Width = 0; _rowChatBx_->Height = 0; isChatPanelOpen = false; } void VideoPage::resizeChatPanel() { // clamp chatPanelSize double minChatPanelSize = 176; double maxChatPanelSize = chtBoxOrientation == Orientation::Horizontal ? _videoContent_->ActualWidth * .5 : _videoContent_->ActualHeight * .5; chatPanelSize = std::max(minChatPanelSize, std::min(chatPanelSize, maxChatPanelSize)); if (chtBoxOrientation == Orientation::Horizontal) { _colChatBx_->Width = GridLength(chatPanelSize, GridUnitType::Pixel); _rowChatBx_->Height = 0; } else { _colChatBx_->Width = 0; _rowChatBx_->Height = GridLength(chatPanelSize, GridUnitType::Pixel); } } void VideoPage::_btnToggleOrientation__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { bool wasChatPanelOpen = isChatPanelOpen; closeChatPanel(); if (chtBoxOrientation == Orientation::Horizontal) { chtBoxOrientation = Orientation::Vertical; _btnToggleOrientation_->Content = L"\uE90D"; _chatPanelResizeBarGrid_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Stretch; _chatPanelResizeBarGrid_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Top; _chatPanelResizeBarGrid_->Width = std::numeric_limits<double>::quiet_NaN(); _chatPanelResizeBarGrid_->Height = 4; Grid::SetColumn(_chatPanelGrid_, 0); Grid::SetRow(_chatPanelGrid_, 2); } else { chtBoxOrientation = Orientation::Horizontal; _btnToggleOrientation_->Content = L"\uE90E"; _chatPanelResizeBarGrid_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Left; _chatPanelResizeBarGrid_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Stretch; _chatPanelResizeBarGrid_->Width = 4; _chatPanelResizeBarGrid_->Height = std::numeric_limits<double>::quiet_NaN(); Grid::SetColumn(_chatPanelGrid_, 2); Grid::SetRow(_chatPanelGrid_, 0); } if (wasChatPanelOpen) openChatPanel(); } void VideoPage::_videoContent__SizeChanged(Platform::Object^ sender, Windows::UI::Xaml::SizeChangedEventArgs^ e) { if (isChatPanelOpen) resizeChatPanel(); } void VideoPage::_messageTextBox__TextChanged(Platform::Object^ sender, TextChangedEventArgs^ e) { bool carriageReturnPressed = false; if (_messageTextBox_->Text->Length() == (lastMessageText->Length() + 1) && _messageTextBox_->Text != "\r") { unsigned cursorPos = 0; auto strMessage = Utils::toString(_messageTextBox_->Text); auto strLastMessage = Utils::toString(lastMessageText); for (std::string::size_type i = 0; i < strLastMessage.size(); ++i) { if (strMessage[i] != strLastMessage[i]) { auto changed = strMessage.substr(i, 1); if (changed == "\r") { carriageReturnPressed = true; MSG_("CR inside"); } break; } } if (strMessage.substr(strMessage.length() - 1) == "\r") { if (lastMessageText->Length() != 0) { carriageReturnPressed = true; MSG_("CR at end"); } } } if (carriageReturnPressed && !(RingD::instance->isCtrlPressed || RingD::instance->isShiftPressed)) { _messageTextBox_->Text = lastMessageText; sendMessage(); lastMessageText = ""; } DependencyObject^ child = VisualTreeHelper::GetChild(_messageTextBox_, 0); auto sendBtnElement = Utils::xaml::FindVisualChildByName(child, "_sendBtn_"); auto sendBtn = dynamic_cast<Button^>(sendBtnElement); if (_messageTextBox_->Text != "") { sendBtn->IsEnabled = true; } else { sendBtn->IsEnabled = false; } lastMessageText = _messageTextBox_->Text; } void VideoPage::_corewindow__KeyDown(CoreWindow^ sender, KeyEventArgs^ e) { auto ctrlState = CoreWindow::GetForCurrentThread()->GetKeyState(VirtualKey::Control); auto isCtrlDown = ctrlState != CoreVirtualKeyStates::None; if (isCtrlDown && e->VirtualKey == Windows::System::VirtualKey::I) { _smartInfoBorder_->Visibility = _smartInfoBorder_->Visibility == VIS::Collapsed ? VIS::Visible : VIS::Collapsed; } }