-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui/ux): Show navigation help when
h
is pressed (#42)
- Loading branch information
Showing
16 changed files
with
314 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use ratatui::{ | ||
buffer::Buffer, | ||
layout::Rect, | ||
style::{Color, Stylize}, | ||
text::Line, | ||
widgets::Widget, | ||
}; | ||
|
||
pub struct BackgroundWidget { | ||
color: Color, | ||
} | ||
|
||
impl BackgroundWidget { | ||
pub fn new(color: Color) -> Self { | ||
Self { color } | ||
} | ||
} | ||
|
||
impl Widget for BackgroundWidget { | ||
fn render(self, area: Rect, buf: &mut Buffer) | ||
where | ||
Self: Sized, | ||
{ | ||
let line = Line::raw(" ".repeat(area.width as usize)).bg(self.color); | ||
for y in area.y..area.y + area.height { | ||
buf.set_line(area.x, y, &line, area.width); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
use input_mapping_common::InputMapping; | ||
use ratatui::{ | ||
layout::{Alignment, Constraint, Flex, Layout, Margin, Rect}, | ||
style::Stylize, | ||
text::Text, | ||
widgets::{Block, BorderType, Borders, Padding}, | ||
Frame, | ||
}; | ||
|
||
use crate::api::common_types::Network; | ||
|
||
mod background_widget; | ||
pub use background_widget::*; | ||
|
||
mod navigation_help_widget; | ||
pub use navigation_help_widget::*; | ||
|
||
use super::resources::Resources; | ||
|
||
pub fn network_symbol(network: Network) -> String { | ||
match network { | ||
Network::Bitcoin => "₿", | ||
Network::Ethereum => "⟠", | ||
} | ||
.to_string() | ||
} | ||
|
||
pub fn render_centered_text(frame: &mut Frame, area: Rect, text: Text) { | ||
let [area] = Layout::horizontal([Constraint::Length(text.width() as u16)]) | ||
.flex(Flex::Center) | ||
.areas(area); | ||
let [area] = Layout::vertical([Constraint::Length(text.height() as u16)]) | ||
.flex(Flex::Center) | ||
.areas(area); | ||
|
||
frame.render_widget(text, area); | ||
} | ||
|
||
pub fn format_address(address: &str, max_symbols: usize) -> String { | ||
if max_symbols <= 3 { | ||
return "".to_string(); | ||
} | ||
|
||
if max_symbols <= 8 { | ||
return "...".to_string(); | ||
} | ||
|
||
let part_size = (max_symbols - 3) / 2; | ||
let part_size = part_size.min(8); | ||
|
||
if address.len() <= part_size * 2 { | ||
return address.to_string(); | ||
} | ||
|
||
format!( | ||
"{}...{}", | ||
&address[..part_size], | ||
&address[(address.len() - part_size)..] | ||
) | ||
} | ||
|
||
pub fn render_navigation_help( | ||
input_mapping: InputMapping, | ||
frame: &mut Frame<'_>, | ||
resources: &Resources, | ||
) { | ||
let area = frame.size(); | ||
|
||
let bindings = input_mapping | ||
.mapping | ||
.into_iter() | ||
.map(|map| (map.key, map.description)) | ||
.collect(); | ||
|
||
let widget = NavigationHelpWidget::new(bindings); | ||
|
||
let block_area = area.inner(Margin::new(8, 4)); | ||
|
||
let width = widget.min_width().max(block_area.width as usize / 2); | ||
let height = widget.height(); | ||
|
||
let block = Block::new() | ||
.border_type(BorderType::Double) | ||
.borders(Borders::all()) | ||
.border_style(resources.main_color) | ||
.padding(Padding::proportional(1)) | ||
.title("Help") | ||
.title_alignment(Alignment::Center) | ||
.reset() | ||
.bg(resources.background_color) | ||
.fg(resources.main_color); | ||
|
||
let block_inner = block.inner(block_area); | ||
|
||
let [widget_area] = Layout::horizontal([Constraint::Length(width as u16)]) | ||
.flex(Flex::Center) | ||
.areas(block_inner); | ||
let [widget_area] = Layout::vertical([Constraint::Length(height as u16)]) | ||
.flex(Flex::Center) | ||
.areas(widget_area); | ||
|
||
frame.render_widget( | ||
BackgroundWidget::new(resources.background_color), | ||
block_area, | ||
); | ||
frame.render_widget(block, block_area); | ||
|
||
frame.render_widget(widget, widget_area); | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use itertools::Itertools; | ||
|
||
use super::format_address; | ||
|
||
#[test] | ||
fn test_format_address() { | ||
let address_lengths = [0, 2, 8, 10, 100].into_iter(); | ||
let max_lengths = [0, 3, 5, 6, 8, 10, 100].into_iter(); | ||
|
||
for (addr_len, max_len) in address_lengths.cartesian_product(max_lengths) { | ||
let address = "0".repeat(addr_len); | ||
assert!(format_address(&address, max_len).len() <= max_len); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use ratatui::{ | ||
buffer::Buffer, | ||
crossterm::event::KeyCode, | ||
layout::{Alignment, Rect}, | ||
text::Text, | ||
widgets::Widget, | ||
}; | ||
|
||
pub struct NavigationHelpWidget { | ||
key_bindings: Vec<(KeyCode, String)>, | ||
} | ||
|
||
impl NavigationHelpWidget { | ||
pub fn new(key_bindings: Vec<(KeyCode, String)>) -> Self { | ||
Self { key_bindings } | ||
} | ||
|
||
pub fn height(&self) -> usize { | ||
self.key_bindings.len() | ||
} | ||
|
||
pub fn min_width(&self) -> usize { | ||
self.key_bindings | ||
.iter() | ||
.map(|(key, description)| min_line_length(description, &key_name(key))) | ||
.max() | ||
.unwrap_or_default() | ||
} | ||
} | ||
|
||
impl Widget for NavigationHelpWidget { | ||
fn render(self, area: Rect, buf: &mut Buffer) | ||
where | ||
Self: Sized, | ||
{ | ||
let width = area.width as usize; | ||
|
||
let text: String = self | ||
.key_bindings | ||
.iter() | ||
.map(|(key, description)| { | ||
let key_name = key_name(key); | ||
let line_len = min_line_length(description, &key_name); | ||
|
||
let padding = width - line_len.min(width); | ||
let padding_str: String = vec![".".to_string(); padding + 1].into_iter().collect(); | ||
|
||
format!("[{}]{}{}", key_name, padding_str, description) | ||
}) | ||
.intersperse("\n".to_string()) | ||
.collect(); | ||
|
||
let text = Text::raw(text).alignment(Alignment::Center); | ||
text.render(area, buf); | ||
} | ||
} | ||
|
||
fn key_name(key: &KeyCode) -> String { | ||
match key { | ||
KeyCode::Char(ch) => ch.to_string(), | ||
KeyCode::Up => "↑".to_string(), | ||
KeyCode::Down => "↓".to_string(), | ||
KeyCode::Enter => "⏎".to_string(), | ||
_ => unimplemented!(), | ||
} | ||
} | ||
|
||
fn min_line_length(description: &str, key_name: &str) -> usize { | ||
const BRACKETS_LEN: usize = 2; | ||
const MINIMAL_SPACING: usize = 1; | ||
|
||
Text::raw(description).width() + Text::raw(key_name).width() + BRACKETS_LEN + MINIMAL_SPACING | ||
} |
Oops, something went wrong.