mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 03:55:23 +00:00

<img width="860" alt="Screenshot 2025-02-08 at 13 38 42" src="https://github.com/user-attachments/assets/ab1558a9-75f6-4006-be2b-9a78dc56b44f" />
264 lines
6.8 KiB
Rust
264 lines
6.8 KiB
Rust
use std::{hash::Hash, time::Instant};
|
|
|
|
use anyhow::Result;
|
|
use ratatui::{layout::Rect, Frame};
|
|
use rustc_hash::FxHashSet;
|
|
use tokio::sync::mpsc::Sender;
|
|
|
|
use crate::{
|
|
action::Action,
|
|
channels::entry::{Entry, PreviewType, ENTRY_PLACEHOLDER},
|
|
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::{Message, Mode},
|
|
utils::metadata::AppMetadata,
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
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)]
|
|
pub struct TvState {
|
|
pub mode: Mode,
|
|
pub selected_entry: Option<Entry>,
|
|
pub results_area_height: u16,
|
|
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_area_height: u16,
|
|
results_picker: Picker,
|
|
rc_picker: Picker,
|
|
channel_state: ChannelState,
|
|
spinner: Spinner,
|
|
preview_state: PreviewState,
|
|
) -> Self {
|
|
Self {
|
|
mode,
|
|
selected_entry,
|
|
results_area_height,
|
|
results_picker,
|
|
rc_picker,
|
|
channel_state,
|
|
spinner,
|
|
preview_state,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Ctx {
|
|
pub tv_state: TvState,
|
|
pub config: Config,
|
|
pub colorscheme: Colorscheme,
|
|
pub app_metadata: AppMetadata,
|
|
pub tv_tx_handle: Sender<Message>,
|
|
pub instant: Instant,
|
|
}
|
|
|
|
impl Ctx {
|
|
pub fn new(
|
|
tv_state: TvState,
|
|
config: Config,
|
|
colorscheme: Colorscheme,
|
|
app_metadata: AppMetadata,
|
|
tv_tx_handle: Sender<Message>,
|
|
instant: Instant,
|
|
) -> Self {
|
|
Self {
|
|
tv_state,
|
|
config,
|
|
colorscheme,
|
|
app_metadata,
|
|
tv_tx_handle,
|
|
instant,
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<()> {
|
|
let selected_entry = ctx
|
|
.tv_state
|
|
.selected_entry
|
|
.clone()
|
|
.unwrap_or(ENTRY_PLACEHOLDER);
|
|
|
|
let show_preview = ctx.config.ui.show_preview_panel
|
|
&& !matches!(selected_entry.preview_type, PreviewType::None);
|
|
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,
|
|
);
|
|
|
|
if layout.results.height.saturating_sub(2)
|
|
!= ctx.tv_state.results_area_height
|
|
{
|
|
ctx.tv_tx_handle.try_send(Message::ResultListHeightChanged(
|
|
layout.results.height.saturating_sub(2),
|
|
))?;
|
|
}
|
|
|
|
// 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(&ctx.tv_state.mode)
|
|
.unwrap()
|
|
.get(&Action::ToggleHelp)
|
|
// just display the first keybinding
|
|
.unwrap()
|
|
.to_string(),
|
|
&ctx.config
|
|
.keybindings
|
|
.get(&ctx.tv_state.mode)
|
|
.unwrap()
|
|
.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 show_preview {
|
|
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(())
|
|
}
|