jjj 0.2.0

A modal interface for Jujutsu.
use std::time::Duration;

use bevy::prelude::*;
use ratatui::prelude::{Rect, *};

use super::prelude::*;

#[derive(Deref, Event)]
pub struct NotificationEvent(pub Notification);

pub fn plugin(app: &mut App) {
    trace!("Initializing plugin...");

    app.register_type::<NotificationTimeoutTimer>();
    app.init_resource::<NotificationTimeoutTimer>();

    app.add_systems(
        Update,
        (
            NotificationTimeoutTimer::tick.in_set(AppSet::TickTimers),
            (
                NotificationTimeoutTimer::dismiss_notification.pipe(errors::forward),
                listen_for_notifications.pipe(errors::forward),
            )
                .in_set(AppSet::Update),
        )
            .run_if(in_state(Screen::Interface)),
    );

    trace!("Plugin initialized.");
}

#[derive(Component, Default)]
pub struct CommandLine {
    pub active_notification: Option<Notification>,
}

#[tracing::instrument(skip_all)]
fn listen_for_notifications(
    mut notification_timeout: ResMut<NotificationTimeoutTimer>,
    mut notification_events: EventReader<NotificationEvent>,
    mut command_line: Query<&mut CommandLine>,
) -> Result<()> {
    let mut command_line = command_line.get_single_mut()?;

    for event in notification_events.read() {
        debug!("Received notification: {:?}", event.0);
        command_line.active_notification = Some(event.0.clone());
        notification_timeout.unpause();
        notification_timeout.reset();
    }

    Ok(())
}

const NOTIFICATION_TIMEOUT: Duration = Duration::from_secs(5);

#[derive(Deref, DerefMut, Reflect, Resource)]
struct NotificationTimeoutTimer(Timer);

impl NotificationTimeoutTimer {
    pub fn tick(mut timer: ResMut<Self>, time: Res<Time>) {
        timer.tick(time.delta());
    }

    #[tracing::instrument(skip_all)]
    pub fn dismiss_notification(
        mut command_line: Query<&mut CommandLine>,
        timer: Res<Self>,
    ) -> Result<()> {
        let mut command_line = command_line.get_single_mut()?;

        if timer.just_finished() {
            debug!("Dismissed notification.");
            command_line.active_notification = None;
        }

        Ok(())
    }
}

impl Default for NotificationTimeoutTimer {
    fn default() -> Self {
        let mut timer = Timer::new(NOTIFICATION_TIMEOUT, TimerMode::Once);
        timer.pause();
        Self(timer)
    }
}

#[derive(Clone, Debug)]
pub struct Notification {
    pub message: String,
    pub angry: bool,
}

impl NotificationEvent {
    pub fn new(message: String) -> Self {
        Self(Notification {
            message,
            angry: false,
        })
    }

    pub fn angry(message: String) -> Self {
        Self(Notification {
            message,
            angry: true,
        })
    }
}

impl Widget for &CommandLine {
    fn render(self, area: Rect, buf: &mut Buffer)
    where
        Self: Sized,
    {
        if let Some(notification) = &self.active_notification {
            let span = if notification.angry {
                Span::styled(
                    notification.message.clone(),
                    Style::default().fg(Color::Red),
                )
            } else {
                Span::from(notification.message.clone())
            };

            span.render(area, buf);
        }
    }
}