diff --git a/README.md b/README.md index e3aacbe..da468a7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Build and run the presentation +# Build and run the presentation - Linux ```sh # Install the rust toolchain: @@ -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 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..b89e71e --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +#/bin/bash +cargo run --release \ No newline at end of file diff --git a/src/keymap.rs b/src/keymap.rs new file mode 100644 index 0000000..a951bda --- /dev/null +++ b/src/keymap.rs @@ -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 { + 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), + } +} diff --git a/src/main.rs b/src/main.rs index 2cb19b9..16faf72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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>, - menu: PopupMenu<'a>, - fx: EffectManager<()>, - exit: bool, + menu: PopupMenu<'a>, + fx: EffectManager<()>, + exit: bool, } impl<'a> App<'a> { @@ -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 @@ -74,23 +74,20 @@ 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; } @@ -98,106 +95,90 @@ impl<'a> App<'a> { 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; } -} +} \ No newline at end of file