Once I made demonstrations /within/ Emacs with my [[https://github.com/howardabrams/demo-it][demo-it]] project. While on MELPA, I wanted to use my own cloned version to make sure I can keep debugging it.
A demonstration begins with an Org file where the screen shows a /single heading/ with a larger font. Not much more. I have two projects that I like to use.
When showing a presentation, I never want the =#+business= to lines to completely disappear. First attempt turned the foreground color to the background color, but that still leaves a blank, but occupied line. Using the invisible overlays removes them completely:
#+BEGIN_SRC emacs-lisp
(defun ha-org-blocks-hide-headers ()
"Make the headers and other block metadata invisible.
See `ha-org-blocks-show-headers' to return their appearance."
Converted to use [[https://github.com/rlister/org-present][org-present]]. I love the /hooks/ as that makes it easier to handle. My problem with =org-present= is that it doesn’t always display images.
I’ve used [[https://github.com/takaxp/org-tree-slide][org-tree-slide]] for years for showing org files as presentations. I like the /simple/ presentation and it seems to shows all the images.
#+BEGIN_SRC emacs-lisp
(use-package org-tree-slide
:config
(setq org-tree-slide-heading-emphasis nil
org-tree-slide-activate-message "† This demonstration is running in Emacs"
Instead of executing a sequence of demonstration steps, demonstrations key on “state”, that is, the active buffer or major-mode, or the heading of an Org file, etc. I described the [[https://howardism.org/Technical/Emacs/demonstrations-part-two.html][guts of writing this code]], but we bind a key to calling =ha-demo-step= with a list of /state matchers/ to functions to call when matched. For instance:
To make the contents of the expression easier to write, the =define-ha-demo= as a macro. Otherwise we write a complicated =cond= with lots of duplicated calls to =ha-demo-state-match= (defined later). This macro creates a function, so the first parameter is the name of the function:
The matching function, =ha-demo-state-match= looks in a cache, the =demo-prev-state= hash table, for the number of times we have triggered that state, and /add/ that value into a new state variable we use to match, =:itful-state= (yeah, naming is hard).
*Note:* If we match, we want to return non-nil, and update this new incremented value back in our cache:
#+BEGIN_SRC emacs-lisp
(defun ha-demo-state-match (triggers state)
"Return non-nil if STATE contains all TRIGGERS.
The state also includes the number of times the triggers
matched during previous calls. We do this by keeping track
of the number of successful calls, and incrementing
the iteration... if this function returns non-nil."
;; If the first element is either parameter is NOT a list,
;; we group it into a list of tuples:
(when (not (listp (car triggers)))
(setq triggers (seq-partition triggers 2)))
(when (not (listp (car state)))
(setq state (seq-partition state 2)))
(let* ((iteration (gethash state ha-demo-prev-state 0))
(itful-state (cons `(:i ,iteration) state)))
(when (ha-demo-match triggers itful-state)
(puthash state (1+ iteration) ha-demo-prev-state))))
#+END_SRC
Notice the two =when= expressions for using =seq-partition= for converting a /property-style/ list like =(:a 1 :b 2 :c 3)= into an more standard /associative/ list, like =((:a 1) (:b 2) (:c 3))=.
But can I check if I have triggered a state once before? Let’s keep track of the /states/ that have returned true before, in a hash table where the key is the /state/ (a list of =:buffer=, =:mode=, =:heading=, etc.) and the /value/ is the number of times triggered at that state:
"Matched states in keys, and store number of matches as values.")
#+END_SRC
Now, we have a new match function takes the /state/ and /triggers/, where the trigger could include an /iteration/, =:i= that limits a match. For instance:
- =(:buffer "foobar.txt" :i 0)= :: triggers the first time we call this function in this buffer.
- =(:buffer "foobar.txt" :i 1)= :: triggers the second time we call this function in this buffer.
If the =triggers= doesn’t contain an =:i=, it matches every time when meeting the other conditions.
Let’s create a function that could accept a list of /triggering keys/, and then compare that with another list representing the “current state” of the point, including the buffer, the mode, or the heading in an Org file. In this case, the magic happens by calling =seq-difference=:
#+BEGIN_SRC emacs-lisp
(defun ha-demo-match (triggers state)
"Return t if all elements of TRIGGERS are in STATE.
Where TRIGGERS and STATE are lists of key/value tuple
The typical presentation software has an issue for hiding the cursor when working with Evil mode, and since setting =cursor-type= to =nil= doesn’t work in a graphical display (where we typically run a presentation), the following functions turn on/off the displayed cursor.
#+BEGIN_SRC emacs-lisp
(defvar ha-demo-cursor nil
"List of cursor states stored during `ha-demo-hide-cursor' and
For Org file displayed as presentations as well as images, we probably don’t want the distraction associated with the modeline, but when we finish the presentation, let’s turn it back on …
#+BEGIN_SRC emacs-lisp
(defvar ha-demo-mode-line nil)
(make-variable-buffer-local 'ha-demo-mode-line)
(defun ha-demo-hide-mode-line ()
"Hide mode line for a particular buffer."
(interactive)
(when mode-line-format
(setq ha-demo-mode-line mode-line-format)
(setq mode-line-format nil)))
(defun ha-demo-show-mode-line ()
"Restore mode hidden with `ha-demo-hide-mode-line'."
(interactive)
(if ha-demo-mode-line
(setq mode-line-format ha-demo-mode-line)))
#+END_SRC
** Presentation Frame Properties
Like the work I’m doing to the mode-line, can we make the frame cleaner for a presentation?
#+BEGIN_SRC emacs-lisp
(defvar ha-demo-frame-state nil
"Store frame properties during `ha-demo-presentation-frame' before
altering them, and then restore them with `ha-demo-normalize-frame'.")