mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-11 22:07:11 +00:00
268 lines
7.3 KiB
Rust
268 lines
7.3 KiB
Rust
use std::{hash::Hash, time::Instant};
|
|
|
|
use anyhow::Result;
|
|
use ratatui::{layout::Rect, Frame};
|
|
use rustc_hash::FxHashSet;
|
|
|
|
use crate::{
|
|
action::Action,
|
|
channels::entry::Entry,
|
|
config::Config,
|
|
picker::Picker,
|
|
preview::PreviewState,
|
|
screen::{
|
|
colors::Colorscheme, help::draw_help_bar, input::draw_input_box,
|
|
keybindings::build_keybindings_table, layout::Layout,
|
|
preview::draw_preview_content_block,
|
|
remote_control::draw_remote_control, results::draw_results_list,
|
|
spinner::Spinner,
|
|
},
|
|
television::Mode,
|
|
utils::metadata::AppMetadata,
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
/// The state of the current television channel.
|
|
///
|
|
/// This struct is passed along to the UI thread as part of the `TvState` struct.
|
|
pub struct ChannelState {
|
|
pub current_channel_name: String,
|
|
pub selected_entries: FxHashSet<Entry>,
|
|
pub total_count: u32,
|
|
pub running: bool,
|
|
}
|
|
|
|
impl ChannelState {
|
|
pub fn new(
|
|
current_channel_name: String,
|
|
selected_entries: FxHashSet<Entry>,
|
|
total_count: u32,
|
|
running: bool,
|
|
) -> Self {
|
|
Self {
|
|
current_channel_name,
|
|
selected_entries,
|
|
total_count,
|
|
running,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Hash for ChannelState {
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
self.current_channel_name.hash(state);
|
|
self.selected_entries
|
|
.iter()
|
|
.for_each(|entry| entry.hash(state));
|
|
self.total_count.hash(state);
|
|
self.running.hash(state);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
/// The state of the main thread `Television` struct.
|
|
///
|
|
/// This struct is passed along to the UI thread as part of the `Ctx` struct.
|
|
pub struct TvState {
|
|
pub mode: Mode,
|
|
pub selected_entry: Option<Entry>,
|
|
pub results_picker: Picker,
|
|
pub rc_picker: Picker,
|
|
pub channel_state: ChannelState,
|
|
pub spinner: Spinner,
|
|
pub preview_state: PreviewState,
|
|
}
|
|
|
|
impl TvState {
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn new(
|
|
mode: Mode,
|
|
selected_entry: Option<Entry>,
|
|
results_picker: Picker,
|
|
rc_picker: Picker,
|
|
channel_state: ChannelState,
|
|
spinner: Spinner,
|
|
preview_state: PreviewState,
|
|
) -> Self {
|
|
Self {
|
|
mode,
|
|
selected_entry,
|
|
results_picker,
|
|
rc_picker,
|
|
channel_state,
|
|
spinner,
|
|
preview_state,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
/// A drawing context that holds the current state of the application.
|
|
///
|
|
/// This is used as a message passing object between the main thread
|
|
/// and the UI thread and should contain all the information needed to
|
|
/// draw a frame.
|
|
pub struct Ctx {
|
|
pub tv_state: TvState,
|
|
pub config: Config,
|
|
pub colorscheme: Colorscheme,
|
|
pub app_metadata: AppMetadata,
|
|
pub instant: Instant,
|
|
pub layout: Layout,
|
|
}
|
|
|
|
impl Ctx {
|
|
pub fn new(
|
|
tv_state: TvState,
|
|
config: Config,
|
|
colorscheme: Colorscheme,
|
|
app_metadata: AppMetadata,
|
|
instant: Instant,
|
|
layout: Layout,
|
|
) -> Self {
|
|
Self {
|
|
tv_state,
|
|
config,
|
|
colorscheme,
|
|
app_metadata,
|
|
instant,
|
|
layout,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq for Ctx {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.tv_state == other.tv_state
|
|
&& self.config == other.config
|
|
&& self.colorscheme == other.colorscheme
|
|
&& self.app_metadata == other.app_metadata
|
|
}
|
|
}
|
|
|
|
impl Eq for Ctx {}
|
|
|
|
impl Hash for Ctx {
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
self.tv_state.hash(state);
|
|
self.config.hash(state);
|
|
self.colorscheme.hash(state);
|
|
self.app_metadata.hash(state);
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for Ctx {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
Some(self.instant.cmp(&other.instant))
|
|
}
|
|
}
|
|
|
|
impl Ord for Ctx {
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
self.instant.cmp(&other.instant)
|
|
}
|
|
}
|
|
|
|
/// Draw the current UI frame based on the given context.
|
|
///
|
|
/// This function is responsible for drawing the entire UI frame based on the given context by
|
|
/// ultimately flushing buffers down to the underlying terminal.
|
|
///
|
|
/// This function is executed by the UI thread whenever it receives a render message from the main
|
|
/// thread.
|
|
///
|
|
/// It will draw the help bar, the results list, the input box, the preview content block, and the
|
|
/// remote control.
|
|
///
|
|
/// # Returns
|
|
/// A `Result` containing the layout of the current frame if the drawing was successful.
|
|
/// This layout can then be sent back to the main thread to serve for tasks where having that
|
|
/// information can be useful or lead to optimizations.
|
|
pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
|
let show_preview =
|
|
ctx.config.ui.show_preview_panel && ctx.tv_state.preview_state.enabled;
|
|
let show_remote = !matches!(ctx.tv_state.mode, Mode::Channel);
|
|
|
|
let layout =
|
|
Layout::build(area, &ctx.config.ui, show_remote, show_preview);
|
|
|
|
// help bar (metadata, keymaps, logo)
|
|
draw_help_bar(
|
|
f,
|
|
&layout.help_bar,
|
|
&ctx.tv_state.channel_state.current_channel_name,
|
|
build_keybindings_table(
|
|
&ctx.config.keybindings.to_displayable(),
|
|
ctx.tv_state.mode,
|
|
&ctx.colorscheme,
|
|
),
|
|
ctx.tv_state.mode,
|
|
&ctx.app_metadata,
|
|
&ctx.colorscheme,
|
|
);
|
|
|
|
// results list
|
|
draw_results_list(
|
|
f,
|
|
layout.results,
|
|
&ctx.tv_state.results_picker.entries,
|
|
&ctx.tv_state.channel_state.selected_entries,
|
|
&mut ctx.tv_state.results_picker.relative_state.clone(),
|
|
ctx.config.ui.input_bar_position,
|
|
ctx.config.ui.use_nerd_font_icons,
|
|
&ctx.colorscheme,
|
|
&ctx.config
|
|
.keybindings
|
|
.get(&Action::ToggleHelp)
|
|
// just display the first keybinding
|
|
.unwrap()
|
|
.to_string(),
|
|
&ctx.config
|
|
.keybindings
|
|
.get(&Action::TogglePreview)
|
|
// just display the first keybinding
|
|
.unwrap()
|
|
.to_string(),
|
|
)?;
|
|
|
|
// input box
|
|
draw_input_box(
|
|
f,
|
|
layout.input,
|
|
ctx.tv_state.results_picker.total_items,
|
|
ctx.tv_state.channel_state.total_count,
|
|
&ctx.tv_state.results_picker.input,
|
|
&ctx.tv_state.results_picker.state,
|
|
ctx.tv_state.channel_state.running,
|
|
&ctx.tv_state.channel_state.current_channel_name,
|
|
&ctx.tv_state.spinner,
|
|
&ctx.colorscheme,
|
|
)?;
|
|
|
|
if layout.preview_window.is_some() {
|
|
draw_preview_content_block(
|
|
f,
|
|
layout.preview_window.unwrap(),
|
|
&ctx.tv_state.preview_state,
|
|
ctx.config.ui.use_nerd_font_icons,
|
|
&ctx.colorscheme,
|
|
)?;
|
|
}
|
|
|
|
// remote control
|
|
if show_remote {
|
|
draw_remote_control(
|
|
f,
|
|
layout.remote_control.unwrap(),
|
|
&ctx.tv_state.rc_picker.entries,
|
|
ctx.config.ui.use_nerd_font_icons,
|
|
&mut ctx.tv_state.rc_picker.state.clone(),
|
|
&mut ctx.tv_state.rc_picker.input.clone(),
|
|
&ctx.tv_state.mode,
|
|
&ctx.colorscheme,
|
|
)?;
|
|
}
|
|
|
|
Ok(layout)
|
|
}
|