Adding share functionality to Graphing Calculator (#601)

* Plumebd with data transfer

* Getting mainpage to talk to getbitmap.  moving share callbacks from mainpage to graphingcalculator

* Trying to get bitmap from renderer.

* work

* Share worked

* cleanups

* Cleanups progressing

* Share working, need loc for title string and user notification incase of a failure.  Then add the equations key.

* More cleanup, now using share icon image and resources for strings.  Still need to do the graph equation key.

* Change share to html based start.

* Key working, with UL but going to try changing to table.

* Fix a html formating error, generating a new UL for each equation.

* Switched over to a table for equation key and have color block formating

* Updates from PR feedback, using Graphing::IBitmap abstraction.

* Update src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h

Fixed

Co-Authored-By: Pepe Rivera <joseartrivera@gmail.com>

* PR Updates.

* Add variables to the graph key.

* PR Updates.
This commit is contained in:
David Shoemaker 2019-08-13 12:57:13 -07:00 committed by Stephanie Anderl
parent 46f11c7c72
commit c1efa3d3e3
8 changed files with 239 additions and 23 deletions

View File

@ -3439,6 +3439,22 @@
<value>Add Equation</value>
<comment>Placeholder text for the equation input button</comment>
</data>
<data name="ShareActionErrorMessage" xml:space="preserve">
<value>Unable to share at this time.</value>
<comment>If there is an error in the sharing action will display a dialog with this text.</comment>
</data>
<data name="ShareActionErrorOk" xml:space="preserve">
<value>OK</value>
<comment>Used on the dismiss button of the share action error dialog.</comment>
</data>
<data name="ShareActionTitle" xml:space="preserve">
<value>Look what I graphed.</value>
<comment>Sent as part of the shared content. The title for the share.</comment>
</data>
<data name="EmptyEquationString" xml:space="preserve">
<value>Empty graph equation</value>
<comment>When sharing and one of the equations has no content this will be shown in the graph key for that equation.</comment>
</data>
<data name="VaiablesHeader.Text" xml:space="preserve">
<value>Variables</value>
<comment>Header text for variables area</comment>

View File

@ -31,7 +31,7 @@
Grid.Row="1"
Grid.Column="0">
<graphControl:Grapher Name="GraphingControl"
<graphControl:Grapher Name="GraphingControl" Grid.Row="0"
Margin="4,7,4,4"
EquationsSource="{x:Bind ViewModel.Equations, Mode=OneWay}"
ForceProportionalAxes="True"
@ -229,7 +229,12 @@
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<local:EquationInputArea Grid.Row="0" Equations="{x:Bind ViewModel.Equations}"/>
<StackPanel>
<Button x:Name="Share" Click="OnShareClick">
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72D;"/>
</Button>
<local:EquationInputArea Grid.Row="0" Equations="{x:Bind ViewModel.Equations}"/>
</StackPanel>
<Grid x:Name="ButtonContainerGrid"
Grid.Row="2"

View File

@ -1,5 +1,6 @@
#include "pch.h"
#include "CalcViewModel/Common/TraceLogger.h"
#include "GraphingCalculator.xaml.h"
#include "CalcViewModel/Common/KeyboardShortcutManager.h"
#include "Controls/CalculationResult.h"
@ -11,27 +12,38 @@ using namespace CalculatorApp::ViewModel;
using namespace concurrency;
using namespace GraphControl;
using namespace Platform;
using namespace Platform::Collections;
using namespace std;
using namespace std::chrono;
using namespace Utils;
using namespace Windows::ApplicationModel::DataTransfer;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage::Streams;
using namespace Windows::System;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Media::Imaging;
using namespace Windows::UI::Popups;
constexpr auto sc_ViewModelPropertyName = L"ViewModel";
GraphingCalculator::GraphingCalculator()
{
Equation::RegisterDependencyProperties();
Grapher::RegisterDependencyProperties();
InitializeComponent();
DataTransferManager^ dataTransferManager = DataTransferManager::GetForCurrentView();
// Register the current control as a share source.
m_dataRequestedToken = dataTransferManager->DataRequested += ref new TypedEventHandler<DataTransferManager^, DataRequestedEventArgs^>(this, &GraphingCalculator::OnDataRequested);
}
void GraphingCalculator::GraphingCalculator_DataContextChanged(FrameworkElement^ sender, DataContextChangedEventArgs^ args)
@ -55,6 +67,118 @@ void GraphingCalculator::ViewModel::set(GraphingCalculatorViewModel^ vm)
}
}
void CalculatorApp::GraphingCalculator::OnShareClick(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
// Ask the OS to start a share action.
DataTransferManager::ShowShareUI();
}
// When share is invoked (by the user or programmatically) the event handler we registered will be called to populate the data package with the
// data to be shared. We will request the current graph image from the grapher as a stream that will pass to the share request.
void GraphingCalculator::OnDataRequested(DataTransferManager^ sender, DataRequestedEventArgs^ args)
{
auto resourceLoader = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView();
try
{
// Get our title from the localized resources
auto EmptyEquationString = resourceLoader->GetString(L"EmptyEquationString");
std::wstring rawHtml = L"<p><img src='graph.png'></p>";
auto equations = ViewModel->Equations;
rawHtml += L"<p><table cellpadding=\"10\">";
rawHtml += L"<col width=\"20\">";
rawHtml += L"<row height=\"20\">";
for (unsigned i = 0; i < equations->Size; i++)
{
auto expression = equations->GetAt(i)->Expression->Data();
auto color = equations->GetAt(i)->LineColor;
if (equations->GetAt(i)->Expression->Length() == 0)
{
expression = EmptyEquationString->Data();
}
rawHtml += L"<tr>";
rawHtml += L"<td style=\"background-color:rgb(";
rawHtml += color.R.ToString()->Data();
rawHtml += L",";
rawHtml += color.G.ToString()->Data();
rawHtml += L",";
rawHtml += color.B.ToString()->Data();
rawHtml += L"); \">";
rawHtml += L"</td>";
rawHtml += L"<td>";
rawHtml += expression;
rawHtml += L"</td>";
rawHtml += L"</tr>";
}
rawHtml += L"</table></p>";
auto variables = ViewModel->Variables;
rawHtml += L"<p><table cellpadding=\"10\">";
rawHtml += L"<col width=\"20\">";
rawHtml += L"<row height=\"20\">";
for (unsigned i = 0; i < variables->Size; i++)
{
auto name = variables->GetAt(i)->Name;
auto value = variables->GetAt(i)->Value;
if (name->Length() >= 0)
{
rawHtml += L"<tr>";
rawHtml += L"<td>";
rawHtml += name->Data();
rawHtml += L"</td>";
rawHtml += L"<td>";
rawHtml += std::to_wstring(value);
rawHtml += L"</td>";
rawHtml += L"</tr>";
}
}
rawHtml += L"</table></p>";
// Shortcut to the request data
auto requestData = args->Request->Data;
DataPackage^ dataPackage = ref new DataPackage();
auto html = HtmlFormatHelper::CreateHtmlFormat(ref new String(rawHtml.c_str()));
auto titleString = resourceLoader->GetString(L"ShareActionTitle");
requestData->Properties->Title = titleString;
requestData->SetHtmlFormat(html);
auto bitmapStream = GraphingControl->GetGraphBitmapStream();
requestData->ResourceMap->Insert(ref new String(L"graph.png"), bitmapStream);
// Set the thumbnail image (in case the share target can't handle HTML)
requestData->Properties->Thumbnail = bitmapStream;
// And the bitmap (in case the share target can't handle HTML)
requestData->SetBitmap(bitmapStream);
}
catch(Exception ^ ex)
{
TraceLogger::GetInstance().LogPlatformException(__FUNCTIONW__, ex);
// Something went wrong, notify the user.
auto errorTitleString = resourceLoader->GetString(L"ShareActionErrorMessage");
auto errorOkString = resourceLoader->GetString(L"ShareActionErrorOk");
auto errDialog = ref new Windows::UI::Xaml::Controls::ContentDialog();
errDialog->Content = errorTitleString;
errDialog->CloseButtonText = errorOkString;
errDialog->ShowAsync();
}
}
void GraphingCalculator::GraphVariablesUpdated(Object^, Object^)
{
m_viewModel->UpdateVariables(GraphingControl->Variables);

View File

@ -33,6 +33,15 @@ namespace CalculatorApp
CalculatorApp::ViewModel::GraphingCalculatorViewModel^ m_viewModel;
void OnShareClick(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
private:
Windows::Foundation::EventRegistrationToken m_dataRequestedToken;
void OnDataRequested(Windows::ApplicationModel::DataTransfer::DataTransferManager^ sender, Windows::ApplicationModel::DataTransfer::DataRequestedEventArgs^ e);
void CommandInvokedHandler(Windows::UI::Popups::IUICommand^ command);
void TextBoxGotFocus(Windows::UI::Xaml::Controls::TextBox^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
};
}

View File

@ -10,6 +10,7 @@ using namespace std;
using namespace Windows::Devices::Input;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage::Streams;
using namespace Windows::System;
using namespace Windows::UI;
using namespace Windows::UI::Input;
@ -49,7 +50,7 @@ namespace GraphControl
DependencyProperty^ Grapher::s_equationsSourceProperty;
DependencyProperty^ Grapher::s_variablesProperty;
DependencyProperty^ Grapher::s_forceProportionalAxesTemplateProperty;
Grapher::Grapher()
: m_solver{ IMathSolver::CreateMathSolver() }
, m_graph{ m_solver->CreateGrapher() }
@ -122,7 +123,7 @@ namespace GraphControl
{
s_equationsProperty = DependencyProperty::Register(
StringReference(s_propertyName_Equations),
EquationCollection::typeid ,
EquationCollection::typeid,
Grapher::typeid,
ref new PropertyMetadata(
nullptr,
@ -555,7 +556,7 @@ namespace GraphControl
// For scaling, the graphing engine interprets x,y position between the range [-1, 1].
// Translate the pointer position to the [-1, 1] bounds.
const auto& pos = currentPointer->Position;
const auto[centerX, centerY] = PointerPositionToGraphPosition(pos.X, pos.Y, ActualWidth, ActualHeight);
const auto [centerX, centerY] = PointerPositionToGraphPosition(pos.X, pos.Y, ActualWidth, ActualHeight);
ScaleRange(centerX, centerY, scale);
@ -563,16 +564,16 @@ namespace GraphControl
}
void Grapher::OnPointerPressed(PointerRoutedEventArgs^ e)
{
{
// Set the pointer capture to the element being interacted with so that only it
// will fire pointer-related events
CapturePointer(e->Pointer);
}
}
void Grapher::OnPointerReleased(PointerRoutedEventArgs^ e)
{
{
ReleasePointerCapture(e->Pointer);
}
}
void Grapher::OnPointerCanceled(PointerRoutedEventArgs^ e)
{
@ -618,7 +619,7 @@ namespace GraphControl
// Convert from PointerPosition to graph position.
const auto& pos = e->Position;
const auto[centerX, centerY] = PointerPositionToGraphPosition(pos.X, pos.Y, width, height);
const auto [centerX, centerY] = PointerPositionToGraphPosition(pos.X, pos.Y, width, height);
if (FAILED(renderer->ScaleRange(centerX, centerY, scale)))
{
@ -635,4 +636,47 @@ namespace GraphControl
}
}
}
RandomAccessStreamReference^ Grapher::GetGraphBitmapStream()
{
RandomAccessStreamReference^ outputStream;
if (m_renderMain != nullptr && m_graph != nullptr)
{
if (auto renderer = m_graph->GetRenderer())
{
std::shared_ptr < Graphing::IBitmap> BitmapOut;
bool hasSomeMissingDataOut = false;
HRESULT hr = E_FAIL;
hr = renderer->GetBitmap(BitmapOut, hasSomeMissingDataOut);
if (SUCCEEDED(hr))
{
// Get the raw date
std::vector<BYTE> byteVector = BitmapOut->GetData();
auto arr = ref new Array<BYTE>(&byteVector[0], (unsigned int)byteVector.size());
// create a memory stream wrapper
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get a writer to transfer the data
auto writer = ref new DataWriter(stream->GetOutputStreamAt(0));
// write the data
writer->WriteBytes(arr);
writer->StoreAsync()->GetResults();
// Get a reference stream to return;
outputStream = RandomAccessStreamReference::CreateFromStream(stream);
}
else
{
OutputDebugString(L"Grapher::GetGraphBitmapStream() unable to get graph image from renderer\r\n");
winrt::throw_hresult(hr);
}
}
}
return outputStream;
}
}

View File

@ -194,5 +194,8 @@ namespace GraphControl
const std::unique_ptr<Graphing::IMathSolver> m_solver;
const std::shared_ptr<Graphing::IGraph> m_graph;
public:
Windows::Storage::Streams::RandomAccessStreamReference^ GetGraphBitmapStream();
};
}

View File

@ -0,0 +1,11 @@
#pragma once
#include <vector>
namespace Graphing
{
struct IBitmap
{
virtual const std::vector<BYTE>& GetData() const = 0;
};
}

View File

@ -1,25 +1,29 @@
#pragma once
#include "Common.h"
#include "IBitmap.h"
struct ID2D1Factory;
struct ID2D1RenderTarget;
namespace Graphing::Renderer
{
struct IGraphRenderer : public NonCopyable, public NonMoveable
{
virtual ~IGraphRenderer() = default;
struct IGraphRenderer : public NonCopyable, public NonMoveable
{
virtual ~IGraphRenderer() = default;
virtual HRESULT SetGraphSize(unsigned int width, unsigned int height) = 0;
virtual HRESULT SetDpi(float dpiX, float dpiY) = 0;
virtual HRESULT SetGraphSize(unsigned int width, unsigned int height) = 0;
virtual HRESULT SetDpi(float dpiX, float dpiY) = 0;
virtual HRESULT DrawD2D1(ID2D1Factory* pDirect2dFactory, ID2D1RenderTarget* pRenderTarget, bool& hasSomeMissingDataOut) = 0;
virtual HRESULT GetClosePointData(float inScreenPointX, float inScreenPointY, int& formulaIdOut, float& xScreenPointOut, float& yScreenPointOut, float& xValueOut, float& yValueOut) = 0;
virtual HRESULT ScaleRange(double centerX, double centerY, double scale) = 0;
virtual HRESULT ChangeRange(ChangeRangeAction action) = 0;
virtual HRESULT MoveRangeByRatio(double ratioX, double ratioY) = 0;
virtual HRESULT ResetRange() = 0;
};
virtual HRESULT DrawD2D1(ID2D1Factory* pDirect2dFactory, ID2D1RenderTarget* pRenderTarget, bool& hasSomeMissingDataOut) = 0;
virtual HRESULT GetClosePointData(float inScreenPointX, float inScreenPointY, int& formulaIdOut, float& xScreenPointOut, float& yScreenPointOut, float& xValueOut, float& yValueOut) = 0;
virtual HRESULT ScaleRange(double centerX, double centerY, double scale) = 0;
virtual HRESULT ChangeRange(ChangeRangeAction action) = 0;
virtual HRESULT MoveRangeByRatio(double ratioX, double ratioY) = 0;
virtual HRESULT ResetRange() = 0;
virtual HRESULT GetBitmap(std::shared_ptr<Graphing::IBitmap>& bitmapOut, bool& hasSomeMissingDataOut) = 0;
};
}