Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Build and run the presentation
# Build and run the presentation - Linux

```sh
# Install the rust toolchain:
Expand All @@ -12,17 +12,35 @@ cd faust-dsp-ujm
cargo run --release
```

> **Note**: only tested on *Linux*, any feedback on **macOS** or **Windows** would be appreciated :-)
# Build and run the presentation - OSX

```sh
# Install the rust toolchain:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add Rust to Your PATH
export PATH="$HOME/.cargo/bin:$PATH"
# Then reload your shell:
source ~/.zshrc

# Clone this repository:
git clone https://github.com/pchdev/faust-dsp-ujm

# Build and run the presentation:
cd faust-dsp-ujm
cargo run --release
```

> **Note**: only tested on *Linux* and *macOS*, **Windows** would be appreciated :-)

# Keyboard shortcuts:

- `Ctrl+space`: Next slide;
- `Ctrl+backspace`: Previous slide;
- `Right arrow`: Next slide;
- `Left arrow`: Previous slide;
- `F4`: jump to specific slide;
- `Up/Down` arrows for navigating the slide paragraphs;
- `Enter` on a paragraph to see the animation (if there's one);
- `Ctrl+Shift+Left or Right`: switch between left-side and right-side windows;
- `Ctrl+q`: quit the presentation.
- `Ctrl+W`: quit the presentation.

# Resources

Expand Down
2 changes: 2 additions & 0 deletions run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#/bin/bash
cargo run --release
38 changes: 38 additions & 0 deletions src/keymap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Command {
NextScreen,
PrevScreen,
Quit,
OpenMenu,
Passthrough,
}

pub fn map_key(ev: KeyEvent) -> Option<Command> {
if ev.kind != KeyEventKind::Press {
return None;
}

use Command::*;
use KeyCode::*;

match ev.code {
// 👉 left/right
Right => Some(NextScreen),
Left => Some(PrevScreen),

// 🔁 Fallback: Ctrl+Space, Ctrl+H itd. (opcjonalnie)
Char(' ') if ev.modifiers.contains(KeyModifiers::CONTROL) => Some(NextScreen),
Null if ev.modifiers.contains(KeyModifiers::CONTROL) => Some(NextScreen),
Backspace => Some(PrevScreen),

// ❌ exit
Char('w') if ev.modifiers.contains(KeyModifiers::CONTROL) => Some(Quit),

// 📜 Menu
F(4) => Some(OpenMenu),

_ => Some(Passthrough),
}
}
197 changes: 89 additions & 108 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,49 @@
mod screens;
mod widgets;
mod keymap;

use std::{io, time::{Duration, Instant}};
use crossterm::event::{
self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers
use std::{
io,
time::{Duration, Instant},
};

use crossterm::event::{self, Event, KeyEvent, KeyEventKind};
use ratatui::{
style::{Color, Stylize},
symbols::border,
widgets::{Block, WidgetRef},
DefaultTerminal, Frame
style::{Color, Stylize},
symbols::border,
widgets::{Block, WidgetRef},
DefaultTerminal, Frame,
};
use ratatui_macros::{line, };
use ratatui_macros::line;
use tachyonfx::{fx, EffectManager, Interpolation};

use crate::{screens::{
agenda::Agenda,
digital::{Digital, Digital2},
faust::{basics::FaustBasics, basics2::FaustBasics2, intro::FaustIntro},
myself::Myself,
signal::{Signal, Signal2},
sound::{Sound, Sound2},
splash::Splash, Screen
}, widgets::popup_menu::PopupMenu
use crate::screens::{
agenda::Agenda,
digital::{Digital, Digital2},
faust::{basics::FaustBasics, basics2::FaustBasics2, intro::FaustIntro},
myself::Myself,
signal::{Signal, Signal2},
sound::{Sound, Sound2},
splash::Splash,
Screen,
};
use crate::widgets::popup_menu::PopupMenu;
use crate::keymap::{map_key, Command};

fn main() -> io::Result<()> {
let mut term = ratatui::init();
let res = App::new().run(&mut term);
ratatui::restore();
return res;
res
}

#[derive(Default)]
pub struct App<'a> {
index: usize,
index: usize,
screens: Vec<Box<dyn Screen>>,
menu: PopupMenu<'a>,
fx: EffectManager<()>,
exit: bool,
menu: PopupMenu<'a>,
fx: EffectManager<()>,
exit: bool,
}

impl<'a> App<'a> {
Expand All @@ -52,19 +56,15 @@ impl<'a> App<'a> {
Box::new(Sound::default()),
Box::new(Sound2::default()),
Box::new(Signal::default()),
Box::new(Signal2::default()),
Box::new(Signal2::default()),
Box::new(Digital::default()),
Box::new(Digital2::default()),
Box::new(Digital2::default()),
Box::new(FaustIntro::default()),
Box::new(FaustBasics::default()),
Box::new(FaustBasics2::default()),
];
// Populate menu popup:
app.menu.populate_from_string(
app.screens.iter().map(|s| s.title().into()).collect()
);
app.menu.populate_from_string(app.screens.iter().map(|s| s.title().into()).collect());

// TODO:
let fade = fx::fade_to(Color::Cyan, Color::White, (1000, Interpolation::SineIn));
app.fx.add_effect(fade);
app
Expand All @@ -74,130 +74,111 @@ impl<'a> App<'a> {
let tick_rate = Duration::from_millis(5);
let mut last_tick = Instant::now();
let mut tick_count = 0usize;

while !self.exit {
term.draw(|frame| {
self.draw(frame);
term.draw(|frame| self.draw(frame))?;

})?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if event::poll(timeout)? {
match event::read()? {
Event::Key(k) if k.kind == KeyEventKind::Press => {
self.on_key_event(k)
}
_ => ()
Event::Key(k) if k.kind == KeyEventKind::Press => self.on_key_event(k),
_ => {}
}
}

if last_tick.elapsed() >= tick_rate {
let screen = &mut self.screens[self.index];
screen.on_tick(tick_count);
self.screens[self.index].on_tick(tick_count);
last_tick = Instant::now();
tick_count += 1;
}
}

Ok(())
}

fn on_key_event(&mut self, k: KeyEvent) {
if k.modifiers.contains(KeyModifiers::CONTROL) {
match k.code {
// Next screen (Ctrl + Esp):
KeyCode::Char(' ') => {
if self.index < self.screens.len() -1 {
self.index += 1;
}
}
// Previous screen (Ctrl + Backspace):
KeyCode::Char('h') => {
if self.index > 0 {
self.index -= 1;
}
}
// Quit app (Ctrl + q):
KeyCode::Char('q') => {
self.exit();
}
// Otherwise, dispatch to current screen:
_ => {
let screen = &mut self.screens[self.index];
screen.on_key_event(k);
}
}
} else {
// No modifier, pass to menu:
if self.menu.open {
match self.menu.on_key_event(k) {
Some(index) => {
self.index = index;
}
None => ()
if let Some(cmd) = map_key(k) {
match cmd {
Command::NextScreen => self.next_screen(),
Command::PrevScreen => self.prev_screen(),
Command::Quit => self.exit(),
Command::OpenMenu => {
self.menu.open();
self.menu.select(Some(self.index));
}
} else {
// Open popup, or pass to underlying screen:
match k.code {
KeyCode::F(4) => {
// Open popup:
self.menu.open();
self.menu.select(Some(self.index));
}
_ => {
let screen = &mut self.screens[self.index];
screen.on_key_event(k);
Command::Passthrough => {
if self.menu.open {
if let Some(i) = self.menu.on_key_event(k) {
self.index = i;
}
} else {
self.screens[self.index].on_key_event(k);
}
}
}
}
}

fn next_screen(&mut self) {
if self.index < self.screens.len() - 1 {
self.index += 1;
}
}
fn animate(&self, frame: &mut Frame) {
todo!()

fn prev_screen(&mut self) {
if self.index > 0 {
self.index -= 1;
}
}

fn draw(&self, frame: &mut Frame) {
// The main frame block:
let block = Block::bordered()
.title(
line![
" Master CCNT".bold(),"(3), ",
"DIGICREA".bold(), "(1), ",
"UJM".italic(), " - ",
"Saint-Etienne".italic(), ", ",
"Oct-Nov 2025 ".italic()]
.centered()
" Master CCNT".bold(),
"(3), ",
"DIGICREA".bold(),
"(1), ",
"UJM".italic(),
" - ",
"Saint-Etienne".italic(),
", ",
"Oct-Nov 2025 ".italic()
]
.centered(),
)
.title_bottom(
line![
" @pchdev - ",
"Emeraude".bold(), " - ",
"Inria".italic(), ", ",
"INSA-Lyon".italic(), ", ",
"CITI Lab ".italic()]
.left_aligned()
"Emeraude".bold(),
" - ",
"Inria".italic(),
", ",
"INSA-Lyon".italic(),
", ",
"CITI Lab ".italic()
]
.left_aligned(),
)
.title_bottom(
line![
"Ctrl + (",
"[q] Quit | ",
"[bsp] Prev | ",
"[spc] Next | ",
"[F4] Jump)"
"← Prev | → Next | Ctrl+W Quit | F4 Menu"
]
.white().on_black()
.right_aligned()
.white()
.on_black()
.right_aligned(),
)
.border_set(border::ROUNDED)
.black()
.on_white()
;
// Get the block's inner area:
.on_white();

let inner = block.inner(frame.area());
let screen = &self.screens[self.index];
frame.render_widget(&block, frame.area());
screen.render_ref(inner, frame.buffer_mut());
self.screens[self.index].render_ref(inner, frame.buffer_mut());
self.menu.render_ref(frame.area(), frame.buffer_mut());
}

fn exit(&mut self) {
self.exit = true;
}
}
}