feat(layout): allow reversing the layout and placing input bar on top (#76)

This commit is contained in:
Alexandre Pasmantier 2024-11-25 21:32:41 +01:00 committed by GitHub
parent 9998b9d9f8
commit 220671106e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 117 additions and 79 deletions

View File

@ -25,6 +25,8 @@ use_nerd_font_icons = false
ui_scale = 100 ui_scale = 100
# Whether to show the top help bar in the UI # Whether to show the top help bar in the UI
show_help_bar = true show_help_bar = true
# Where to place the input bar in the UI (top or bottom)
input_bar_position = "bottom"
# Previewers settings # Previewers settings
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------

View File

@ -59,8 +59,6 @@ impl Keymap {
/// The main application struct that holds the state of the application. /// The main application struct that holds the state of the application.
pub struct App { pub struct App {
/// The configuration of the application.
config: Config,
keymap: Keymap, keymap: Keymap,
// maybe move these two into config instead of passing them // maybe move these two into config instead of passing them
// via the cli? // via the cli?
@ -123,13 +121,12 @@ impl App {
channel: TelevisionChannel, channel: TelevisionChannel,
tick_rate: f64, tick_rate: f64,
frame_rate: f64, frame_rate: f64,
passthrough_keybindings: Vec<String>, passthrough_keybindings: &[String],
) -> Result<Self> { ) -> Result<Self> {
let (action_tx, action_rx) = mpsc::unbounded_channel(); let (action_tx, action_rx) = mpsc::unbounded_channel();
let (render_tx, _) = mpsc::unbounded_channel(); let (render_tx, _) = mpsc::unbounded_channel();
let (_, event_rx) = mpsc::unbounded_channel(); let (_, event_rx) = mpsc::unbounded_channel();
let (event_abort_tx, _) = mpsc::unbounded_channel(); let (event_abort_tx, _) = mpsc::unbounded_channel();
let television = Arc::new(Mutex::new(Television::new(channel)));
let config = Config::new()?; let config = Config::new()?;
let keymap = Keymap::from(&config.keybindings).with_mode_mappings( let keymap = Keymap::from(&config.keybindings).with_mode_mappings(
Mode::Channel, Mode::Channel,
@ -142,9 +139,10 @@ impl App {
.collect(), .collect(),
)?; )?;
debug!("{:?}", keymap); debug!("{:?}", keymap);
let television =
Arc::new(Mutex::new(Television::new(channel, config.clone())));
Ok(Self { Ok(Self {
config,
keymap, keymap,
tick_rate, tick_rate,
frame_rate, frame_rate,
@ -184,14 +182,12 @@ impl App {
let (render_tx, render_rx) = mpsc::unbounded_channel(); let (render_tx, render_rx) = mpsc::unbounded_channel();
self.render_tx = render_tx.clone(); self.render_tx = render_tx.clone();
let action_tx_r = self.action_tx.clone(); let action_tx_r = self.action_tx.clone();
let config_r = self.config.clone();
let television_r = self.television.clone(); let television_r = self.television.clone();
let frame_rate = self.frame_rate; let frame_rate = self.frame_rate;
let rendering_task = tokio::spawn(async move { let rendering_task = tokio::spawn(async move {
render( render(
render_rx, render_rx,
action_tx_r, action_tx_r,
config_r,
television_r, television_r,
frame_rate, frame_rate,
is_output_tty, is_output_tty,

View File

@ -2,6 +2,8 @@ use config::ValueKind;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use crate::ui::layout::InputPosition;
const DEFAULT_UI_SCALE: u16 = 90; const DEFAULT_UI_SCALE: u16 = 90;
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@ -9,6 +11,7 @@ pub struct UiConfig {
pub use_nerd_font_icons: bool, pub use_nerd_font_icons: bool,
pub ui_scale: u16, pub ui_scale: u16,
pub show_help_bar: bool, pub show_help_bar: bool,
pub input_bar_position: InputPosition,
} }
impl Default for UiConfig { impl Default for UiConfig {
@ -17,6 +20,7 @@ impl Default for UiConfig {
use_nerd_font_icons: false, use_nerd_font_icons: false,
ui_scale: DEFAULT_UI_SCALE, ui_scale: DEFAULT_UI_SCALE,
show_help_bar: true, show_help_bar: true,
input_bar_position: InputPosition::Bottom,
} }
} }
} }
@ -36,6 +40,10 @@ impl From<UiConfig> for ValueKind {
String::from("show_help_bar"), String::from("show_help_bar"),
ValueKind::Boolean(val.show_help_bar).into(), ValueKind::Boolean(val.show_help_bar).into(),
); );
m.insert(
String::from("input_position"),
ValueKind::String(val.input_bar_position.to_string()).into(),
);
ValueKind::Table(m) ValueKind::Table(m)
} }
} }

View File

@ -45,7 +45,7 @@ async fn main() -> Result<()> {
}, },
args.tick_rate, args.tick_rate,
args.frame_rate, args.frame_rate,
args.passthrough_keybindings, &args.passthrough_keybindings,
) { ) {
Ok(mut app) => { Ok(mut app) => {
stdout().flush()?; stdout().flush()?;

View File

@ -12,7 +12,7 @@ use tokio::{
}; };
use crate::television::Television; use crate::television::Television;
use crate::{action::Action, config::Config, tui::Tui}; use crate::{action::Action, tui::Tui};
#[derive(Debug)] #[derive(Debug)]
pub enum RenderingTask { pub enum RenderingTask {
@ -42,7 +42,6 @@ impl IoStream {
pub async fn render( pub async fn render(
mut render_rx: mpsc::UnboundedReceiver<RenderingTask>, mut render_rx: mpsc::UnboundedReceiver<RenderingTask>,
action_tx: mpsc::UnboundedSender<Action>, action_tx: mpsc::UnboundedSender<Action>,
config: Config,
television: Arc<Mutex<Television>>, television: Arc<Mutex<Television>>,
frame_rate: f64, frame_rate: f64,
is_output_tty: bool, is_output_tty: bool,
@ -59,15 +58,11 @@ pub async fn render(
debug!("Entering tui"); debug!("Entering tui");
tui.enter()?; tui.enter()?;
debug!("Registering action handler and config handler"); debug!("Registering action handler");
television television
.lock() .lock()
.await .await
.register_action_handler(action_tx.clone())?; .register_action_handler(action_tx.clone())?;
television
.lock()
.await
.register_config_handler(config.clone())?;
// Rendering loop // Rendering loop
loop { loop {

View File

@ -1,7 +1,7 @@
use crate::app::Keymap; use crate::app::Keymap;
use crate::picker::Picker; use crate::picker::Picker;
use crate::ui::input::actions::InputActionHandler; use crate::ui::input::actions::InputActionHandler;
use crate::ui::layout::{Dimensions, Layout}; use crate::ui::layout::{Dimensions, InputPosition, Layout};
use crate::ui::spinner::Spinner; use crate::ui::spinner::Spinner;
use crate::ui::spinner::SpinnerState; use crate::ui::spinner::SpinnerState;
use crate::{action::Action, config::Config}; use crate::{action::Action, config::Config};
@ -14,7 +14,6 @@ use television_channels::channels::{
remote_control::RemoteControl, OnAir, TelevisionChannel, UnitChannel, remote_control::RemoteControl, OnAir, TelevisionChannel, UnitChannel,
}; };
use television_channels::entry::{Entry, ENTRY_PLACEHOLDER}; use television_channels::entry::{Entry, ENTRY_PLACEHOLDER};
use television_previewers::previewers;
use television_previewers::previewers::Previewer; use television_previewers::previewers::Previewer;
use television_utils::strings::EMPTY_STRING; use television_utils::strings::EMPTY_STRING;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
@ -54,23 +53,30 @@ pub struct Television {
impl Television { impl Television {
#[must_use] #[must_use]
pub fn new(mut channel: TelevisionChannel) -> Self { pub fn new(mut channel: TelevisionChannel, config: Config) -> Self {
let results_picker = match config.ui.input_bar_position {
InputPosition::Bottom => Picker::default().inverted(),
InputPosition::Top => Picker::default(),
};
let previewer = Previewer::new(Some(config.previewers.clone().into()));
let keymap = Keymap::from(&config.keybindings);
channel.find(EMPTY_STRING); channel.find(EMPTY_STRING);
let spinner = Spinner::default(); let spinner = Spinner::default();
Self { Self {
action_tx: None, action_tx: None,
config: Config::default(), config,
keymap: Keymap::default(), keymap,
channel, channel,
remote_control: TelevisionChannel::RemoteControl( remote_control: TelevisionChannel::RemoteControl(
RemoteControl::default(), RemoteControl::default(),
), ),
mode: Mode::Channel, mode: Mode::Channel,
current_pattern: EMPTY_STRING.to_string(), current_pattern: EMPTY_STRING.to_string(),
results_picker: Picker::default().inverted(), results_picker,
rc_picker: Picker::default(), rc_picker: Picker::default(),
results_area_height: 0, results_area_height: 0,
previewer: Previewer::default(), previewer,
preview_scroll: None, preview_scroll: None,
preview_pane_height: 0, preview_pane_height: 0,
current_preview_total_lines: 0, current_preview_total_lines: 0,
@ -220,24 +226,6 @@ impl Television {
Ok(()) Ok(())
} }
/// Register a configuration handler that provides configuration settings if necessary.
///
/// # Arguments
/// * `config` - Configuration settings.
///
/// # Returns
/// * `Result<()>` - An Ok result or an error.
pub fn register_config_handler(&mut self, config: Config) -> Result<()> {
self.config = config;
self.keymap = Keymap::from(&self.config.keybindings);
let previewer_config =
std::convert::Into::<previewers::PreviewerConfig>::into(
self.config.previewers.clone(),
);
self.previewer.set_config(previewer_config);
Ok(())
}
/// Update the state of the component based on a received action. /// Update the state of the component based on a received action.
/// ///
/// # Arguments /// # Arguments
@ -388,33 +376,34 @@ impl Television {
area, area,
!matches!(self.mode, Mode::Channel), !matches!(self.mode, Mode::Channel),
self.config.ui.show_help_bar, self.config.ui.show_help_bar,
self.config.ui.input_bar_position,
); );
// help bar (metadata, keymaps, logo) // help bar (metadata, keymaps, logo)
self.draw_help_bar(f, &layout)?; self.draw_help_bar(f, &layout.help_bar)?;
self.results_area_height = u32::from(layout.results.height - 2); // 2 for the borders self.results_area_height = u32::from(layout.results.height - 2); // 2 for the borders
self.preview_pane_height = layout.preview_window.height; self.preview_pane_height = layout.preview_window.height;
// top left block: results // results list
self.draw_results_list(f, &layout)?; self.draw_results_list(f, layout.results)?;
// bottom left block: input // input box
self.draw_input_box(f, &layout)?; self.draw_input_box(f, layout.input)?;
let selected_entry = self let selected_entry = self
.get_selected_entry(Some(Mode::Channel)) .get_selected_entry(Some(Mode::Channel))
.unwrap_or(ENTRY_PLACEHOLDER); .unwrap_or(ENTRY_PLACEHOLDER);
let preview = self.previewer.preview(&selected_entry); let preview = self.previewer.preview(&selected_entry);
// top right block: preview title // preview title
self.current_preview_total_lines = preview.total_lines(); self.current_preview_total_lines = preview.total_lines();
self.draw_preview_title_block(f, &layout, &preview)?; self.draw_preview_title_block(f, layout.preview_title, &preview)?;
// bottom right block: preview content // preview content
self.draw_preview_content_block( self.draw_preview_content_block(
f, f,
&layout, layout.preview_window,
selected_entry selected_entry
.line_number .line_number
.map(|l| u16::try_from(l).unwrap_or(0)), .map(|l| u16::try_from(l).unwrap_or(0)),
@ -423,7 +412,7 @@ impl Television {
// remote control // remote control
if matches!(self.mode, Mode::RemoteControl | Mode::SendToChannel) { if matches!(self.mode, Mode::RemoteControl | Mode::SendToChannel) {
self.draw_remote_control(f, &layout.remote_control.unwrap())?; self.draw_remote_control(f, layout.remote_control.unwrap())?;
} }
Ok(()) Ok(())
} }

View File

@ -1,5 +1,4 @@
use crate::television::Television; use crate::television::Television;
use crate::ui::layout::Layout;
use crate::ui::logo::build_logo_paragraph; use crate::ui::logo::build_logo_paragraph;
use crate::ui::mode::mode_color; use crate::ui::mode::mode_color;
use ratatui::layout::Rect; use ratatui::layout::Rect;
@ -7,6 +6,8 @@ use ratatui::prelude::{Color, Style};
use ratatui::widgets::{Block, BorderType, Borders, Padding}; use ratatui::widgets::{Block, BorderType, Borders, Padding};
use ratatui::Frame; use ratatui::Frame;
use super::layout::HelpBarLayout;
pub fn draw_logo_block(f: &mut Frame, area: Rect, color: Color) { pub fn draw_logo_block(f: &mut Frame, area: Rect, color: Color) {
let logo_block = Block::default() let logo_block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
@ -24,9 +25,9 @@ impl Television {
pub(crate) fn draw_help_bar( pub(crate) fn draw_help_bar(
&self, &self,
f: &mut Frame, f: &mut Frame,
layout: &Layout, layout: &Option<HelpBarLayout>,
) -> color_eyre::Result<()> { ) -> color_eyre::Result<()> {
if let Some(help_bar) = layout.help_bar { if let Some(help_bar) = layout {
self.draw_metadata_block(f, help_bar.left); self.draw_metadata_block(f, help_bar.left);
self.draw_keymaps_block(f, help_bar.middle)?; self.draw_keymaps_block(f, help_bar.middle)?;
draw_logo_block(f, help_bar.right, mode_color(self.mode)); draw_logo_block(f, help_bar.right, mode_color(self.mode));

View File

@ -1,9 +1,8 @@
use crate::television::Television; use crate::television::Television;
use crate::ui::layout::Layout;
use crate::ui::BORDER_COLOR; use crate::ui::BORDER_COLOR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use ratatui::layout::{ use ratatui::layout::{
Alignment, Constraint, Direction, Layout as RatatuiLayout, Alignment, Constraint, Direction, Layout as RatatuiLayout, Rect,
}; };
use ratatui::prelude::{Span, Style}; use ratatui::prelude::{Span, Style};
use ratatui::style::Stylize; use ratatui::style::Stylize;
@ -416,7 +415,7 @@ impl Television {
pub(crate) fn draw_input_box( pub(crate) fn draw_input_box(
&mut self, &mut self,
f: &mut Frame, f: &mut Frame,
layout: &Layout, rect: Rect,
) -> Result<()> { ) -> Result<()> {
let input_block = Block::default() let input_block = Block::default()
.title_top(Line::from(" Pattern ").alignment(Alignment::Center)) .title_top(Line::from(" Pattern ").alignment(Alignment::Center))
@ -425,12 +424,12 @@ impl Television {
.border_style(Style::default().fg(BORDER_COLOR)) .border_style(Style::default().fg(BORDER_COLOR))
.style(Style::default()); .style(Style::default());
let input_block_inner = input_block.inner(layout.input); let input_block_inner = input_block.inner(rect);
if input_block_inner.area() == 0 { if input_block_inner.area() == 0 {
return Ok(()); return Ok(());
} }
f.render_widget(input_block, layout.input); f.render_widget(input_block, rect);
// split input block into 4 parts: prompt symbol, input, result count, spinner // split input block into 4 parts: prompt symbol, input, result count, spinner
let total_count = self.channel.total_count(); let total_count = self.channel.total_count();

View File

@ -1,5 +1,8 @@
use std::fmt::Display;
use ratatui::layout; use ratatui::layout;
use ratatui::layout::{Constraint, Direction, Rect}; use ratatui::layout::{Constraint, Direction, Rect};
use serde::Deserialize;
pub struct Dimensions { pub struct Dimensions {
pub x: u16, pub x: u16,
@ -41,6 +44,24 @@ impl HelpBarLayout {
} }
} }
#[derive(Debug, Clone, Copy, Deserialize, Default)]
pub enum InputPosition {
#[serde(rename = "top")]
Top,
#[serde(rename = "bottom")]
#[default]
Bottom,
}
impl Display for InputPosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InputPosition::Top => write!(f, "top"),
InputPosition::Bottom => write!(f, "bottom"),
}
}
}
pub struct Layout { pub struct Layout {
pub help_bar: Option<HelpBarLayout>, pub help_bar: Option<HelpBarLayout>,
pub results: Rect, pub results: Rect,
@ -75,6 +96,7 @@ impl Layout {
area: Rect, area: Rect,
with_remote: bool, with_remote: bool,
with_help_bar: bool, with_help_bar: bool,
input_position: InputPosition,
) -> Self { ) -> Self {
let main_block = centered_rect(dimensions.x, dimensions.y, area); let main_block = centered_rect(dimensions.x, dimensions.y, area);
// split the main block into two vertical chunks (help bar + rest) // split the main block into two vertical chunks (help bar + rest)
@ -127,23 +149,47 @@ impl Layout {
.split(main_rect); .split(main_rect);
// left block: results + input field // left block: results + input field
let results_constraints =
vec![Constraint::Min(3), Constraint::Length(3)];
let left_chunks = layout::Layout::default() let left_chunks = layout::Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([Constraint::Min(3), Constraint::Length(3)]) .constraints(match input_position {
InputPosition::Top => {
results_constraints.into_iter().rev().collect()
}
InputPosition::Bottom => results_constraints,
})
.split(vt_chunks[0]); .split(vt_chunks[0]);
let (input, results) = match input_position {
InputPosition::Bottom => (left_chunks[1], left_chunks[0]),
InputPosition::Top => (left_chunks[0], left_chunks[1]),
};
// right block: preview title + preview // right block: preview title + preview
let preview_constraints =
vec![Constraint::Length(3), Constraint::Min(3)];
let right_chunks = layout::Layout::default() let right_chunks = layout::Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(3)]) .constraints(match input_position {
InputPosition::Top => {
preview_constraints.into_iter().rev().collect()
}
InputPosition::Bottom => preview_constraints,
})
.split(vt_chunks[1]); .split(vt_chunks[1]);
let (preview_title, preview_window) = match input_position {
InputPosition::Bottom => (right_chunks[0], right_chunks[1]),
InputPosition::Top => (right_chunks[1], right_chunks[0]),
};
Self::new( Self::new(
help_bar_layout, help_bar_layout,
left_chunks[0], results,
left_chunks[1], input,
right_chunks[0], preview_title,
right_chunks[1], preview_window,
if with_remote { if with_remote {
Some(vt_chunks[2]) Some(vt_chunks[2])
} else { } else {

View File

@ -1,5 +1,4 @@
use crate::television::Television; use crate::television::Television;
use crate::ui::layout::Layout;
use crate::ui::BORDER_COLOR; use crate::ui::BORDER_COLOR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use ratatui::layout::{Alignment, Rect}; use ratatui::layout::{Alignment, Rect};
@ -26,7 +25,7 @@ impl Television {
pub(crate) fn draw_preview_title_block( pub(crate) fn draw_preview_title_block(
&self, &self,
f: &mut Frame, f: &mut Frame,
layout: &Layout, rect: Rect,
preview: &Arc<Preview>, preview: &Arc<Preview>,
) -> Result<()> { ) -> Result<()> {
let mut preview_title_spans = Vec::new(); let mut preview_title_spans = Vec::new();
@ -44,7 +43,7 @@ impl Television {
preview_title_spans.push(Span::styled( preview_title_spans.push(Span::styled(
shrink_with_ellipsis( shrink_with_ellipsis(
&preview.title, &preview.title,
layout.preview_window.width.saturating_sub(4) as usize, rect.width.saturating_sub(4) as usize,
), ),
Style::default().fg(DEFAULT_PREVIEW_TITLE_FG).bold(), Style::default().fg(DEFAULT_PREVIEW_TITLE_FG).bold(),
)); ));
@ -57,14 +56,14 @@ impl Television {
.border_style(Style::default().fg(BORDER_COLOR)), .border_style(Style::default().fg(BORDER_COLOR)),
) )
.alignment(Alignment::Left); .alignment(Alignment::Left);
f.render_widget(preview_title, layout.preview_title); f.render_widget(preview_title, rect);
Ok(()) Ok(())
} }
pub(crate) fn draw_preview_content_block( pub(crate) fn draw_preview_content_block(
&mut self, &mut self,
f: &mut Frame, f: &mut Frame,
layout: &Layout, rect: Rect,
target_line: Option<u16>, target_line: Option<u16>,
preview: &Arc<Preview>, preview: &Arc<Preview>,
) { ) {
@ -83,8 +82,8 @@ impl Television {
bottom: 0, bottom: 0,
left: 1, left: 1,
}); });
let inner = preview_outer_block.inner(layout.preview_window); let inner = preview_outer_block.inner(rect);
f.render_widget(preview_outer_block, layout.preview_window); f.render_widget(preview_outer_block, rect);
//if let PreviewContent::Image(img) = &preview.content { //if let PreviewContent::Image(img) = &preview.content {
// let image_component = StatefulImage::new(None); // let image_component = StatefulImage::new(None);

View File

@ -18,7 +18,7 @@ impl Television {
pub fn draw_remote_control( pub fn draw_remote_control(
&mut self, &mut self,
f: &mut Frame, f: &mut Frame,
area: &Rect, rect: Rect,
) -> Result<()> { ) -> Result<()> {
let layout = Layout::default() let layout = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@ -30,7 +30,7 @@ impl Television {
] ]
.as_ref(), .as_ref(),
) )
.split(*area); .split(rect);
self.draw_rc_channels(f, &layout[0])?; self.draw_rc_channels(f, &layout[0])?;
self.draw_rc_input(f, &layout[1])?; self.draw_rc_input(f, &layout[1])?;
draw_rc_logo(f, layout[2], mode_color(self.mode)); draw_rc_logo(f, layout[2], mode_color(self.mode));

View File

@ -1,8 +1,8 @@
use crate::television::Television; use crate::television::Television;
use crate::ui::layout::Layout; use crate::ui::layout::InputPosition;
use crate::ui::BORDER_COLOR; use crate::ui::BORDER_COLOR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use ratatui::layout::Alignment; use ratatui::layout::{Alignment, Rect};
use ratatui::prelude::{Color, Line, Span, Style}; use ratatui::prelude::{Color, Line, Span, Style};
use ratatui::widgets::{ use ratatui::widgets::{
Block, BorderType, Borders, List, ListDirection, Padding, Block, BorderType, Borders, List, ListDirection, Padding,
@ -164,7 +164,7 @@ impl Television {
pub(crate) fn draw_results_list( pub(crate) fn draw_results_list(
&mut self, &mut self,
f: &mut Frame, f: &mut Frame,
layout: &Layout, rect: Rect,
) -> Result<()> { ) -> Result<()> {
let results_block = Block::default() let results_block = Block::default()
.title_top(Line::from(" Results ").alignment(Alignment::Center)) .title_top(Line::from(" Results ").alignment(Alignment::Center))
@ -181,21 +181,24 @@ impl Television {
} }
let entries = self.channel.results( let entries = self.channel.results(
layout.results.height.saturating_sub(2).into(), rect.height.saturating_sub(2).into(),
u32::try_from(self.results_picker.offset())?, u32::try_from(self.results_picker.offset())?,
); );
let results_list = build_results_list( let results_list = build_results_list(
results_block, results_block,
&entries, &entries,
ListDirection::BottomToTop, match self.config.ui.input_bar_position {
InputPosition::Bottom => ListDirection::BottomToTop,
InputPosition::Top => ListDirection::TopToBottom,
},
None, None,
self.config.ui.use_nerd_font_icons, self.config.ui.use_nerd_font_icons,
); );
f.render_stateful_widget( f.render_stateful_widget(
results_list, results_list,
layout.results, rect,
&mut self.results_picker.relative_state, &mut self.results_picker.relative_state,
); );
Ok(()) Ok(())