concord 2.4.3

A terminal user interface client for Discord
Documentation
use crate::discord::MessageState;
use crate::tui::state::DashboardState;

use super::rows::{MESSAGE_ROW_GAP, MessageRowMetrics};

#[derive(Clone, Copy, Debug)]
pub(in crate::tui) struct MessageViewportRow<'a> {
    pub(in crate::tui) global_index: usize,
    pub(in crate::tui) message: &'a MessageState,
    pub(in crate::tui) metrics: MessageRowMetrics,
    pub(in crate::tui) message_top: isize,
    pub(in crate::tui) body_top: isize,
    pub(in crate::tui) reaction_top: isize,
    pub(in crate::tui) line_offset: usize,
    pub(in crate::tui) body_skip: usize,
    pub(in crate::tui) item_line_offset: usize,
    pub(in crate::tui) show_header: bool,
    pub(in crate::tui) bottom_gap: bool,
    pub(in crate::tui) selected: bool,
    pub(in crate::tui) starts_new_day: bool,
    pub(in crate::tui) shows_unread_divider: bool,
}

#[derive(Clone, Debug)]
pub(in crate::tui) struct MessageViewportPlan<'a> {
    rows: Vec<MessageViewportRow<'a>>,
}

impl<'a> MessageViewportPlan<'a> {
    pub(in crate::tui) fn new(
        messages: &[&'a MessageState],
        selected: Option<usize>,
        state: &DashboardState,
        content_width: usize,
        preview_width: u16,
        max_preview_height: u16,
    ) -> Self {
        let state_messages = state.messages();
        let mut rendered_rows: isize = 0;
        let mut rows = Vec::with_capacity(messages.len());

        for (local_index, message) in messages.iter().enumerate() {
            let global_index = state.message_scroll().saturating_add(local_index);
            let line_offset = usize::from(local_index == 0) * state.message_line_scroll();
            let has_state_message = state_messages
                .get(global_index)
                .is_some_and(|state_message| state_message.id == message.id);
            let starts_new_day = has_state_message && state.message_starts_new_day_at(global_index);
            let shows_unread_divider =
                has_state_message && state.should_draw_unread_divider_at(global_index);
            let separator_lines = usize::from(starts_new_day) + usize::from(shows_unread_divider);
            let show_header = if has_state_message {
                state.message_starts_author_group_at(global_index)
            } else {
                true
            };
            let bottom_gap = if has_state_message {
                state.message_bottom_gap_after(global_index) > 0
            } else {
                true
            };
            let selected_row = selected == Some(local_index);
            let metrics = state.message_row_metrics_at_with_selected_bottom(
                global_index,
                message,
                content_width,
                preview_width,
                max_preview_height,
                selected_row,
            );
            let message_top = rendered_rows - line_offset as isize;
            let body_top = message_top + metrics.body_top_offset() as isize;
            let reaction_top = message_top + metrics.reaction_top_offset() as isize;
            let body_skip = line_offset.saturating_sub(separator_lines);
            let selected_grouped_continuation = selected_row && !show_header;
            let item_line_offset = if selected_grouped_continuation {
                body_skip.saturating_sub(1)
            } else {
                body_skip
            };

            rows.push(MessageViewportRow {
                global_index,
                message,
                metrics,
                message_top,
                body_top,
                reaction_top,
                line_offset,
                body_skip,
                item_line_offset,
                show_header,
                bottom_gap,
                selected: selected_row,
                starts_new_day,
                shows_unread_divider,
            });

            rendered_rows = rendered_rows
                .saturating_add(metrics.visible_rows_after_scroll(line_offset) as isize);
        }

        Self { rows }
    }

    pub(in crate::tui) fn rows(&self) -> &[MessageViewportRow<'a>] {
        &self.rows
    }

    pub(in crate::tui) fn row(&self, local_index: usize) -> Option<&MessageViewportRow<'a>> {
        self.rows.get(local_index)
    }
}

pub(in crate::tui) fn standalone_message_rendered_height(
    content_rows: usize,
    reaction_rows: usize,
    preview_rows: usize,
) -> usize {
    1 + content_rows + reaction_rows + preview_rows + MESSAGE_ROW_GAP
}