Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support speaker notes #389

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
de42655
add speaker notes mode cli option
dmackdev Nov 4, 2024
fd6d57b
use ipc to navigate to specfic slides in speaker notes presentation w…
dmackdev Nov 5, 2024
3151fbd
process only speaker notes and end slide comments when in speaker not…
dmackdev Nov 5, 2024
cbadf07
fix layout on speaker note slides
dmackdev Nov 6, 2024
93a0dcb
move `SpeakerNotesChannel` to `Presenter` from `PresentationStateInner`
dmackdev Nov 8, 2024
22d70f7
restore original examples/code.md
dmackdev Nov 8, 2024
4c7726e
add speaker notes example presentation
dmackdev Nov 8, 2024
df051d3
show titles in speaker notes mode
dmackdev Nov 8, 2024
81cfbbc
remove ipc event service duplication
dmackdev Nov 8, 2024
4416af8
remove SpeakerNoteChannel enum
dmackdev Nov 11, 2024
5f781ac
bump CI rust version to 1.75.0
dmackdev Nov 11, 2024
0fd812e
Merge remote-tracking branch 'origin/master' into speaker-notes
dmackdev Nov 11, 2024
0f83b5a
split process_element for speaker notes and presentation modes
dmackdev Nov 11, 2024
dc0e039
fix formatting
dmackdev Nov 16, 2024
27a6151
use pubsub ipc messaging pattern instead of event
dmackdev Nov 16, 2024
43b44c5
incorporate presentation file name in ipc service name
dmackdev Nov 17, 2024
ddbaf41
split process comment command for speaker notes mode
dmackdev Nov 19, 2024
3114df9
propagate iceoryx2 publisher errors
dmackdev Nov 19, 2024
52d3f04
propagate iceoryx2 receiver error
dmackdev Nov 19, 2024
13e2d46
send new SpeakerNotesCommand::Exit ipc message to exit the speaker no…
dmackdev Nov 21, 2024
2964ac9
use string interpolation for ipc service name
dmackdev Nov 21, 2024
209cd10
propagate error instead of expect
dmackdev Nov 21, 2024
64dcc88
set iceoryx2 log level to error to hide misleading "missing config" w…
dmackdev Dec 4, 2024
7924524
add better error messages for iceoryx2 service open/create errors
dmackdev Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
484 changes: 481 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ thiserror = "1"
unicode-width = "0.2"
os_pipe = "1.1.5"
libc = "0.2.155"
iceoryx2 = "0.4.1"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires rust 1.75 or newer, but CI runs rust 1.74.
A rust-toolchain file and Cargo.toml entry would help make this more visible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any thoughts on bumping the rust version @mfontanini?
Otherwise a different version of iceoryx2 (if one exists for rust 1.74) or different IPC crate will need to be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to investigate some warnings and proper configuration for usage of this crate.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go for it, 1.75 is almost a year old already


[dependencies.syntect]
version = "5.2"
Expand Down
26 changes: 15 additions & 11 deletions examples/code.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ theme:
background: false
---

Code styling
===
# Code styling
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not equivalent, I presume this was done by some prettifier? Can you roll back?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think my editor extension did this - I will revert this and add a separate example presentation as you suggested, with consistent syntax.


This presentation shows how to:

* Left-align code blocks.
* Have code blocks without background.
* Execute code snippets.
- Left-align code blocks.
- Have code blocks without background.
- Execute code snippets.

```rust
pub struct Greeter {
Expand All @@ -35,10 +34,11 @@ fn main() {
}
```

<!-- speaker_note: These are speaker notes on slide 1. -->
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd create a separate example presentation on speaker notes specifically. I think the speaker notes require enough manual intervention to show up that I don't think it belongs in the main demo one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Started a new speaker notes presentation.


<!-- end_slide -->

Column layouts
===
# Column layouts

The same code as the one before but split into two columns to split the API definition with its usage:

Expand Down Expand Up @@ -76,10 +76,11 @@ fn main() {
}
```

<!-- speaker_note: These are speaker notes on slide 2. -->

<!-- end_slide -->

Snippet execution
===
# Snippet execution

Run code snippets from the presentation and display their output dynamically.

Expand All @@ -90,10 +91,11 @@ for i in range(0, 5):
time.sleep(0.5)
```

<!-- speaker_note: These are speaker notes on slide 3. -->

<!-- end_slide -->

Snippet execution - `stderr`
===
# Snippet execution - `stderr`

Output from `stderr` will also be shown as output.

Expand All @@ -106,3 +108,5 @@ echo "This is a successful command again"
sleep 0.5
man # Missing argument
```

<!-- speaker_note: These are speaker notes on slide 4. -->
3 changes: 2 additions & 1 deletion src/custom.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
GraphicsMode,
input::user::KeyBinding,
media::{emulator::TerminalEmulator, kitty::KittyMode},
processing::code::SnippetLanguage,
GraphicsMode, SpeakerNotesMode,
};
use clap::ValueEnum;
use schemars::JsonSchema;
Expand Down Expand Up @@ -123,6 +123,7 @@ pub struct OptionsConfig {

/// Whether to be strict about parsing the presentation's front matter.
pub strict_front_matter_parsing: Option<bool>,
pub speaker_notes_mode: Option<SpeakerNotesMode>,
}

#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub use crate::{
input::source::CommandSource,
markdown::parse::MarkdownParser,
media::{graphics::GraphicsMode, printer::ImagePrinter, register::ImageRegistry},
presenter::{PresentMode, Presenter, PresenterOptions},
presenter::{PresentMode, Presenter, PresenterOptions, SpeakerNotesMode},
processing::builder::{PresentationBuilderOptions, Themes},
render::highlighting::{CodeHighlighter, HighlightThemeSet},
resource::Resources,
Expand Down
11 changes: 8 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use clap::{CommandFactory, Parser, error::ErrorKind};
use clap::{error::ErrorKind, CommandFactory, Parser};
use comrak::Arena;
use directories::ProjectDirs;
use presenterm::{
CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry,
MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet, Presenter,
PresenterOptions, Resources, SnippetExecutor, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender,
ValidateOverflows,
PresenterOptions, Resources, SnippetExecutor, SpeakerNotesMode, Themes, ThemesDemo, ThirdPartyConfigs,
ThirdPartyRender, ValidateOverflows,
};
use std::{
env::{self, current_dir},
Expand Down Expand Up @@ -77,6 +77,9 @@ struct Cli {
/// The path to the configuration file.
#[clap(short, long)]
config_file: Option<String>,

#[clap(short, long)]
speaker_notes_mode: Option<SpeakerNotesMode>,
}

fn create_splash() -> String {
Expand Down Expand Up @@ -151,6 +154,7 @@ fn make_builder_options(config: &Config, mode: &PresentMode, force_default_theme
strict_front_matter_parsing: config.options.strict_front_matter_parsing.unwrap_or(true),
enable_snippet_execution: config.snippet.exec.enable,
enable_snippet_execution_replace: config.snippet.exec_replace.enable,
speaker_notes_mode: config.options.speaker_notes_mode.clone(),
}
}

Expand Down Expand Up @@ -272,6 +276,7 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
} else {
let commands = CommandSource::new(config.bindings.clone())?;
options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. });
options.speaker_notes_mode = cli.speaker_notes_mode;

let options = PresenterOptions {
builder_options: options,
Expand Down
32 changes: 32 additions & 0 deletions src/presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ use crate::{
style::{Color, Colors},
theme::{Alignment, Margin, PresentationTheme},
};
use iceoryx2::{
port::{listener::Listener, notifier::Notifier},
prelude::EventId,
service::ipc::Service,
};
use serde::Deserialize;
use std::{
cell::RefCell,
Expand All @@ -30,6 +35,12 @@ pub(crate) struct Presentation {
pub(crate) state: PresentationState,
}

#[derive(Debug)]
pub enum SpeakerNoteChannel {
Notifier(Notifier<Service>),
Listener(Listener<Service>),
}

impl Presentation {
/// Construct a new presentation.
pub(crate) fn new(slides: Vec<Slide>, modals: Modals, state: PresentationState) -> Self {
Expand Down Expand Up @@ -253,6 +264,10 @@ impl Presentation {
false
}
}

pub(crate) fn listen_for_speaker_note_evt(&self) -> Option<usize> {
self.state.listen_for_speaker_note_evt()
}
}

impl From<Vec<Slide>> for Presentation {
Expand All @@ -274,6 +289,7 @@ pub(crate) type AsyncPresentationErrorHolder = Arc<Mutex<Option<AsyncPresentatio
pub(crate) struct PresentationStateInner {
current_slide_index: usize,
async_error_holder: AsyncPresentationErrorHolder,
pub(crate) channel: Option<SpeakerNoteChannel>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This belongs in the presenter type. The channel itself should be created outside and is not part of the presentation, it's part of "the app". e.g. you shouldn't have to re-set the channel every time the presentation is reloaded.

}

#[derive(Clone, Debug, Default)]
Expand All @@ -292,6 +308,22 @@ impl PresentationState {

fn set_current_slide_index(&self, value: usize) {
self.inner.deref().borrow_mut().current_slide_index = value;
if let Some(SpeakerNoteChannel::Notifier(notifier)) = &self.inner.deref().borrow().channel {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the idea of moving this to presenter, you can always emit this event after a command is handled. That would help detach this logic from out of here.

notifier.notify_with_custom_event_id(EventId::new(value)).unwrap();
}
}

pub(crate) fn set_channel(&self, speaker_note_channel: SpeakerNoteChannel) {
self.inner.deref().borrow_mut().channel = Some(speaker_note_channel);
}

fn listen_for_speaker_note_evt(&self) -> Option<usize> {
if let Some(SpeakerNoteChannel::Listener(listener)) = &self.inner.deref().borrow().channel {
if let Some(evt) = listener.try_wait_one().unwrap() {
return Some(evt.as_value() + 1);
}
}
None
}
}

Expand Down
28 changes: 26 additions & 2 deletions src/presenter.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;

use crate::{
custom::KeyBindingsConfig,
diff::PresentationDiffer,
Expand Down Expand Up @@ -37,6 +41,13 @@ pub struct PresenterOptions {
pub validate_overflows: bool,
}

#[derive(Clone, Debug, Deserialize, ValueEnum, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum SpeakerNotesMode {
Publisher,
Receiver,
}

/// A slideshow presenter.
///
/// This type puts everything else together.
Expand Down Expand Up @@ -104,6 +115,11 @@ impl<'a> Presenter<'a> {
self.render(&mut drawer)?;

loop {
if let Some(idx) = self.state.presentation().listen_for_speaker_note_evt() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be interesting for this thing to be inside input/source.rs and act as a normal command, e.g. it could return Command::GoToSlide when such an event is found. This would make it more transparent to the presenter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make sense - it is a source of a command after all.

So are you thinking:

  • Remove the SpeakerNoteChannel enum,
  • Store the Option<iceoryx2::Listener> directly in CommandSource if viewing speaker notes,
  • Store the Option<iceoryx2::Notifier> directly in Presenter for the main presentation, if presenting with speaker notes.
    ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly 👌

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

self.apply_command(Command::GoToSlide(idx as u32));
break;
}

if self.poll_async_renders()? {
self.render(&mut drawer)?;
}
Expand Down Expand Up @@ -194,7 +210,11 @@ impl<'a> Presenter<'a> {
};
// If the screen is too small, simply ignore this. Eventually the user will resize the
// screen.
if matches!(result, Err(RenderError::TerminalTooSmall)) { Ok(()) } else { result }
if matches!(result, Err(RenderError::TerminalTooSmall)) {
Ok(())
} else {
result
}
}

fn apply_command(&mut self, command: Command) -> CommandSideEffect {
Expand Down Expand Up @@ -264,7 +284,11 @@ impl<'a> Presenter<'a> {
panic!("unreachable commands")
}
};
if needs_redraw { CommandSideEffect::Redraw } else { CommandSideEffect::None }
if needs_redraw {
CommandSideEffect::Redraw
} else {
CommandSideEffect::None
}
}

fn try_reload(&mut self, path: &Path, force: bool) {
Expand Down
Loading
Loading