%% New in r2020b: Record snapshots of your app
% Created for Matlab Central Community Highlights, by Adam Danz on Oct 17, 2020.
%
% Table of Contents
% Use the new exportapp function to capture an image of your app|uifigure
% Matlab's getframe now supports apps & uifigures
% Review: How to get the handle to an app figure
% Recreate the getframe demo
%% Use the new exportapp function to capture an image of your app|uifigure
% Imagine these scenarios:
% Your app contains several adjustable parameters that update an embedded plot and you'd like to remember the values of each app component so that you can recreate the plot with the same dataset
% You're constructing a manual for your app and would like to include images of your app
% You're app contains a process that automatically updates regularly and you'd like to store periodic snap shots of your app.
% As of Matlab's r2020b release, we no longer must rely on 3rd party software to record an image of an app or uifigure.
% exportapp(fig,filename) saves an image (JPEG | PNG | TIFF | PDF) of a uifigure (fig) with the specified file name or full file path (filename).  Matlab's documentation includes an example of how to add an [Export] button to an app that allows the user to select a path, filename, and extension for their exported image.
% Here's another example that merely saves the image as a PDF to the app's main folder.
%     1. Add a button to the app and assign a ButtonPushed callback function to the button.  This one also assigns an Icon to the button in the form of a svg file.
%
%     2. Define the callback function to name the image after the app's name and include a datetime stamp.  The image will be saved to the app's main folder.
% % Button pushed function: SnapshotButton
% function SnapshotButtonPushed(app, ~)
%     % create filename containing the app's figure name (spaces removed)
%     % and a datetime stamp in format yymmdd_hhmmss
%     filename = sprintf('%s_%s.pdf',regexprep(app.MyApp.Name,' +',''), datestr(now(),'yymmdd_HHMMSS'));
%     % Get the app's path
%     filepath = fileparts(which([mfilename,'.mlapp']));
%     % Store snapshot
%     exportapp(app.MyApp, fullfile(filepath,filename))
% end
%% Matlab's getframe now supports apps & uifigures
% getframe(h) captures images of axes or a uifigure as a structure containing the image data which defines a movie frame.  This function has been around for a while but as of r2020b, it now supports uifigures.  By capturing consecutive frames, you can create a movie that can be played back within a Matlab figure (using movie) or as an AVI file (using VideoWriter).  This is useful when demonstrating the effects of changes to app components.
% The general steps to recording a process within an app as a movie are,
% 1. Add a button or some other event to your app that can invoke the frame recording process.
% 2. Animation is typically controlled by a loop with n iterations.  Preallocate the structure array that will store the outputs to getframe.  The example below stores the outputs within the app so that they are available by other functions within the app.  That will require you to define the variable as a property in the app.
% % nFrames is the number of iterations that will be recorded.
% % recordedFrames is defined as a private property within the app
% app.recordedFrames(1:nFrames) = struct('cdata',[],'colormap',[]);
% 3. Call getframe from within the loop that controlls the animation.  If you're using VideoWriter to create an AVI file, you'll also do that here (not shown, but see an example in the documentation).
% % app.myAppUIFigure: the app's figure handle
% % getframe() also accepts axis handles
% for i = 1:nFrames
%
%     ... % code that updates the app for the next frame
%
%     app.recordedFrames(i) = getframe(app.myAppUIFigure);
% end
% 4. Now the frame data are stored in app.recordedFrames and can be accessed from anywhere within the app. To play them back as a movie,
% movie(app.recordedFrames)
%
% % or
% movie(app.recordedFrames, n) % to play the movie n-times
% movie(app.recordedFrames, n, fps) % to specify the number of frames per second
% To demonstrate this, I adapted a copy of Matlab's built-in PulseGenerator.mlapp by adding*
% a record button
% a record status lamp with frame counter
% a playback button
% a function that animates the effects of the Edge Knob
% *See a complete list of changes below to recreate the app.
% Recording process (The GIF is a lot faster than realtime and only shows part of the recording)
%
% Playback process
%
%
%% Review: How to get the handle to an app figure
% To use either of these functions outside of app designer, you'll need to access the App's figure handle.  By default, the HandleVisibility property of uifigures is set to off preventing the use of gcf to retrieve the figure handle.  Here are 4 ways to access the app's figure handle from outside of the app.
% 1. Store the app's handle when opening the app.
% app = myApp;
% % Get the figure handle
% figureHandle = app.myAppUIFigure;
% 2. Search for the figure handle using the app's name, tag, or any other unique property
% allfigs = findall(0, 'Type', 'figure'); % handle to all existing figures
% figureHandle = findall(allfigs, 'Name', 'MyApp', 'Tag', 'MyUniqueTagName');
% 3. Change the HandleVisibility property to on or callback so that the figure handle is accessible by gcf anywhere or from within callback functions.  This can be changed programmatically or from within the app designer component browser.  Note, this is not recommended since any function that uses gcf such as axes(), clf(), etc can now access your app!.
% 4. If the app's figure handle is needed within a callback function external to the app, you could pass the app's figure handle in as an input variable or you could use gcbf() even if the HandleVisibility is off.
%
%% Recreate the getframe demo
% Here are the steps to recreate the app in the GIF images above.
% 1. Create a copy of Matlab's PulseGenerator.mlapp and name it PulseGenerator_demo.mlapp.  The path to this file can be found with which('PulseGenerator').
% 2. Add the following objects in Design View
% State button (app.RecordButton) with properties Text=Record, (optional: set Icon & IconAlignment), and define a RecordButtonValueChanged callback function (details below).
% Lamp (app.Lamp) with properties Label= (keep this empty), Color=[.7 . 7 .7].
% Play button (app.PlayButton) with properties Text=Play, BackgroundColor=[0 1 0], Visible=off, and define a PlayButtonPushed callback function (details below).
% I also had to move down the knobs' GridLayout to make some extra space.
% 3.  Add recordedFrames as a private property [instructions].
% properties (Access = private)
%     autoUpdate
%     recordedFrames = struct('cdata',[],'colormap',[]); % used by RecordButtonValueChanged
% end
% 4. Define the RecordButtonValueChanged callback function.
% % Value changed function: RecordButton
% function RecordButtonValueChanged(app, event)
%     % Responds to the [Record] button and records image data of the
%     % App's figure during animation of the Edge Knob value changing.
%     % Also controls the recording lamp.  The getframe process is slow!
%
%     % Button state was changed to off, do nothing
%     if ~app.RecordButton.Value
%         return
%     end
%
%     % Save pre-recording state for easy reset.
%     state.Grid = app.PulsePlotUIAxes.XGrid;  % assumes equal YGrid
%     state.YLimMode = app.PulsePlotUIAxes.YLimMode;
%     state.EdgeFontColor = app.EdgeKnob.FontColor;
%     state.EdgeFontWeight = app.EdgeKnob.FontWeight;
%     state.LampColor = app.Lamp.Color;
%
%     % Set up demo
%     grid(app.PulsePlotUIAxes, 'on')
%     ylim(app.PulsePlotUIAxes, [-.4, 1])
%     % Change Edge knob font color to spotlight the knob
%     app.EdgeKnob.FontColor = 'r';
%     app.EdgeKnob.FontWeight = 'bold';
%
%     % Loop through Edge values and record app
%     EdgeValues = linspace(0, 4, 50); %3rd input determines number of frames
%     nFrames = numel(EdgeValues);
%     app.recordedFrames(1:nFrames) = struct('cdata',[],'colormap',[]);
%     app.Lamp.Color = [1 0 0];
%     for i = 1:nFrames
%         % End early if Record state is ended manually
%         if ~app.RecordButton.Value
%             continue
%         end
%         % Update knob and plot
%         app.EdgeKnob.Value = EdgeValues(i);
%
%         % Update line
%         updatePlot(app)
%
%         % Add text to indicate exact Edge value
%         th = text(app.PulsePlotUIAxes, min(xlim(app.PulsePlotUIAxes)), max(ylim(app.PulsePlotUIAxes)), ...
%             sprintf(' Edge = %.3f', EdgeValues(i)), 'HorizontalAlignment','Left','VerticalAlignment','Top');
%         app.LampLabel.Text = sprintf('%d/%d',i,nFrames);
%
%         % Record frame
%         drawnow();
%         app.recordedFrames(i) = getframe(app.PulseGeneratorUIFigure);
%     end
%
%     % Return pre-recording state
%     grid(app.PulsePlotUIAxes, state.Grid);
%     app.PulsePlotUIAxes.YLimMode = state.YLimMode;
%     app.EdgeKnob.FontColor = state.EdgeFontColor;
%     app.EdgeKnob.FontWeight = state.EdgeFontWeight;
%     app.Lamp.Color = state.LampColor;
%
%     delete(th)
%     app.LampLabel.Text = '';
%     app.RecordButton.Value = false;
%
%     % Remove leftover empties if recording was manually stopped
%     app.recordedFrames(arrayfun(@(s)isempty(s.cdata),app.recordedFrames)) = [];
%
%     % If there's more than 1 frame, enable playback button
%     if numel(app.recordedFrames)>1
%         app.PlayButton.Visible = 'on';
%     else
%         app.PlayButton.Visible = 'off';
%     end
%
% end
% 5. Define the PlayButtonPushed callback function.
% function PlayButtonPushed(app, event)
%     % Playback movie stored in app.recordedFrames
%     % Do nothing if there isn't more than 1 frame
%     if numel(app.recordedFrames)<2
%         app.PlayButton.Visible = 'off';
%         return
%     end
%     % For some reason the movie() size is smaller than the original so I'm
%     % using imshow() to set up the figure to the move size.
%     % Reuse figure if it already exists
%     fig= findall(0,'Type','Figure','Name','AppPlayback','Tag',mfilename);
%     if isempty(fig)
%         fig = figure('MenuBar','None','Color','w','Name','AppPlayback','Tag',mfilename,'Visible','off');
%         ax = axes('Visible','off');
%         I = imshow(repmat(uint8(255),size(app.recordedFrames(1).cdata)),'Parent',ax);
%         movegui(fig,'center')
%     else
%         ax = gca(fig);
%     end
%     cla(ax)
%     % Determine playback rate and number of playbacks
%     movieDuration = 3; % seconds
%     nFrames = numel(app.recordedFrames);
%     fps = nFrames/movieDuration;
%     % Show animation
%     title(ax, sprintf('App playback (%.1f fps, %d frames)', fps, nFrames),'FontSize', 18)
%     figure(fig); % bring to top
%     fig.Visible = 'on';
%     movie(ax, app.recordedFrames, 1, fps)
% end
%