10

When writing Beamer slides for a talk, I find that I often want to have some text (usually math) visible on a group of slides and be alerted on only the first slide in the group. For example, I can achieve this with $1 + 1 = \action<visible@+- | alert@+>{2}$, $2 + 2 = \action<visible@.(-1)- | alert@.(-1)>{4}$, etc.

However, it would be more maintainable and readable if these actions could be combined into one alertvisible action. Using a simple macro, it's easy to handle the case where the group of slides has the form <slide number>-:

\documentclass{beamer}
\newcommand{\alertvisible}[1]{visible@#1- | alert@#1}
\begin{document}
\begin{frame}{Frame Title}
  Facts:\pause
  \begin{itemize}
  \item $1 + 1 = \action<visible@+- | alert@+>{2}$
  \item $2 + 2 = \action<\alertvisible{+}>{4}$
  \item $3 + 3 = \action<\alertvisible{.(-1)}>{6}$
  \item $4 + 4 = \action<\alertvisible{+}>{8}$
  \end{itemize}
\end{frame}
\end{document}

Unfortunately, this simple solution does not handle the general case. For example, I might want to have a single alertvisible action so that \action<alertvisible@+,+(2)-+(3)> is equivalent to \action<visible@+,+(2)-+(3) | alert@+>. This would seem to require a way, say \firstslideof, to extract the logically first slide in a group so that one could define

\newenvironment<>{alertvisibleenv}%
    {\begin{visibleenv}#1\begin{alertenv}\firstslideof{#1}}%
    {\end{alertenv}\end{visibleenv}}

Is there some way to define \firstslideof to allow this sort of general definition of alertvisibleenv?

Edit:

I am attempting to use the xparse and xstring packages to build \firstslideof, which, for some reason, I decided to rename as \extract. The basic idea is to parse out the left endpoint of each range in the slide specification and evaluate it to a numeric value. The slide specification with the smallest value should be returned as the "first-slide-of". For example, \extract<+-+(1), 3, +, +(-2)-8> should return +(-2) when the value of beamerpauses is smaller than 5, and 3 otherwise.

Here is the code that I have so far:

\documentclass{beamer}
\usepackage{etoolbox}
\usepackage{etextools}
\usepackage{xparse}
\usepackage{xstring}

\makeatletter

\NewDocumentCommand{\extract}{>{\SplitList{,}}r<>}{%
  % Initially set the minimum slide spec to something large
  \def\beamer@extract@min{+(1000)}%
  \ProcessList{#1}{\beamer@extract}%
}

\newcommand*{\beamer@extract}[1]{%
  % Substitute \beamer@extract@groupplus for each +
  \saveexpandmode
  \expandarg
  \StrSubstitute{#1}{+}{\noexpand\beamer@extract@groupplus}[\@tmpa]%
  \restoreexpandmode
%
  % Once \@tmpa is fully expanded, each occurrence of
  % a possibly shifted + will be surrounded with { }.
  % The { } are used to prevent \beamer@@extract from
  % incorrectly splitting +(-1) as +( and 1).
  %
  %% How do I fully expand \@tmpa here?  Is this right?
  \ExpandNext{\beamer@@extract}{\@tmpa}%
}

\NewDocumentCommand{\beamer@@extract}{>{\SplitArgument{1}{-}}m}{%
  % Use \SplitArgument and  \@firstoftwo to get left endpoint
  % of range.
  Found: `\@firstoftwo#1'\par
%
  % Evaluate the prospective and current minima to
  % a numeric value and store the result in \@tmpa
  % and \@tmpb, respectively.
  \beamer@@extract@eval{\@tmpa}{\@firstoftwo#1}%
  \beamer@@extract@eval{\@tmpb}{\beamer@extract@min}%
%
  % If the prospective minimum is smaller, then 
  % make it the current minimum.
  \ifnumless{\@tmpa}{\@tmpb}{%
    \expandnext{\def\beamer@extract@min}{\@firstoftwo#1}%
    Updated
  }{%
    Not Updated
  }%
  Minimum: \meaning\beamer@extract@min\par
}

\newcommand*{\beamer@@extract@eval}[2]{%
  % Substitute \beamer@@extract@evalplus for each occurrence of 
  % + in #2, storing the fully expanded result in #1.
  \saveexpandmode
  \expandarg
  \StrSubstitute{#2}{+}{\noexpand\beamer@@extract@evalplus}[#1]%
  \restoreexpandmode
  %% How do I fully expand #1 here?
}

\NewDocumentCommand{\beamer@@extract@evalplus}{r()}{%
  \value{beamerpauses}+#1%
}

\NewDocumentCommand{\beamer@extract@groupplus}{D(){0}}{%
  {+(#1)}%
}

\makeatother


\begin{document}

\begin{frame}{Test Frame}
  % The following should set \beamer@extract@min to
  %   +(-2) if beamerpauses < 5
  %   3     if beamerpauses >= 5
  \extract<+-+(1), 3, +, +(-2)-8>
\end{frame}

\end{document}

Unfortunately, when I run the code, I get an error: You can't use \numexpr in horizontal mode. \ifnumcomp ...\ifnum \numexpr #1\relax #2\numexpr #3\relax \expandafter \@fi.... My guess is that this error comes from expansion problems at the lines marked above with %%.

Question:
Is the problem indeed related to expansion? If so, how can I fix it?

6
  • I've had this question favorited for a while because I'm interested in it, but I haven't found the time to take a stab at it yet. Unless there's a combination of \only, \temporal, etc., that can select the first slide in a range, one would have to mimic the structure of the overlay specification parser in beamerbasedecode.sty. Which looks complicated... Jul 9, 2012 at 15:40
  • @MatthewLeingang It's a good idea, but I haven't been able to think of a combination of \only, \temporal, etc. that works. Instead of mimicking beamerbasedecode.sty, I wonder if there is some way to leverage the xparse package to make things (partly) easier. Jul 17, 2012 at 16:57
  • One difficulty with this is that beamer tests the slide against the overlay specification to see if it is in range. It therefore never actually tries to work out "what slide numbers does this specification expand to?". This could make it difficult to figure out when the first slide something is seen on. Jul 25, 2012 at 20:36
  • Also, the visible action works by displacing its contents a large amount for slides where the contents should be invisible. This means that it is still processed making it a little tricky to determine the point at which it is actually printed on the page. Jul 25, 2012 at 22:18
  • @AndrewStacey I'm not sure I understand your second comment. I agree that it would be very tricky to figure out, from a tree of overlay specs, when something is actually printed. But for "alertvisible", I don't think I need that generality; isn't it enough to expand +s and .s and find the smallest slide in that set? (I've been working on code along this line, now that I've learned more about TeX programming. I'm running into expansion bugs; after I try some more, I will post an edit or separate question.) Maybe I'm missing your point, though? Jul 26, 2012 at 21:16

1 Answer 1

4

I have a method for you which I think could be fairly flexible and which doesn't involve delving deep into overlay specifications. However, it might be a bit rough round the edges and could probably do with a few improvements here and there (it is a bit wasteful on macros, the counter actiononfirst should probably be reset every frame).

Here's the code:

\documentclass{beamer}
%\url{https://tex.stackexchange.com/q/61975/86}
\makeatletter
\newcounter{actiononfirst}
\resetcounteronoverlays{actiononfirst}
\newcommand<>\alertvisible[1]{%
  \stepcounter{actiononfirst}%
  \only<1>{%
  \expandafter\gdef\csname
actiononfirst\the\value{actiononfirst}\endcsname{0}%
  }%
  \let\my@action=\pgfutil@firstofone
  \only#2{%
    \ifnum\csname
actiononfirst\the\value{actiononfirst}\endcsname=0\relax
      \let\my@action\alert
    \expandafter\gdef\csname
actiononfirst\the\value{actiononfirst}\endcsname{1}%
  \fi
  }%
  \visible#2{\my@action{#1}}%
  }
\makeatother
\begin{document}
\begin{frame}{Frame Title}
\action<alertvisible@+->{hereon}
\action<alertvisible@1,3->{hereoff}

  Facts:\pause
  \begin{itemize}
  \item $1 + 1 = \action<visible@+- | alert@+>{2}$
  \item $2 + 2 = \action<alertvisible@+>{4}$
  \item $3 + 3 = \action<alertvisible@.(-1)>{6}$
  \item $4 + 4 = \action<alertvisible@+>{8}$
  \end{itemize}
\end{frame}
\end{document}

Here's the explanation:

We define an overlay-aware command, \alertvisible, to be the new action (following from Ulrike's answer at How do I add custom beamer actions that change an argument?). Each invocation of this is indexed (this could be on a per-slide basis, also there's a potential problem if one of these gets inside an \only which would interfere with the counting) by the counter actiononfirst. Each invocation has a corresponding macro of the form \actiononfirst<index> which records whether or not this invocation has been seen before or not. This macro is set to 0 on the first slide (this might cause issues if the first overlay is skipped for some reason). Now comes the important bit. The overlay specification is passed as the second argument to our \alertvisible command. We use this as the overlay specification to an \only command thus ensuring that this only gets run when the text should be displayed - this avoids the issue I encountered (and mentioned in the comments) where by with \visible the argument is always processed even when the text is invisible. Inside the \only we test to see if we've been called before (by testing \actiononfirst<index>) and if we haven't then we set an auxiliary macro to be \alert. Otherwise it's just \pgfutil@firstofone. Then we mark this invocation as having happened so that next time around, the test will fail and the alert won't happen.

Just in writing this, I can think of lots of not-very-edge cases where this will fail, but it's at least a start.

alert on first action

2
  • Thanks! This seems to work well in most cases. Could you explain one more subtlety to me: why does the use of #2 twice (as part of \only and \visible) not mistakenly increment beamerpauses twice when the overlay specification contains +? Aug 6, 2012 at 17:22
  • Oh! I hadn't thought of that. My guess is that because we're inside an action, the normal incrementing is turned off. That's assuming that I haven't made a mistake and there is double incrementing. If there is, it wouldn't be hard to correct: simply save beamerpauses before the \only and restore it afterwards. Aug 6, 2012 at 17:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.