Skip to content

Commit

Permalink
feat(ui): Dynamic theme selection (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
mertwole committed Aug 25, 2024
1 parent fda14f1 commit b8da79c
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 77 deletions.
8 changes: 6 additions & 2 deletions app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
cache::Cache as LedgerApiCache, mock::LedgerApiMock, Device, DeviceInfo, LedgerApiT,
},
},
screen::{OutgoingMessage, Screen, ScreenName},
screen::{resources::Resources, OutgoingMessage, Screen, ScreenName},
};

pub struct App {
Expand Down Expand Up @@ -140,8 +140,12 @@ impl App {
mut screen: Screen<L, C, M>,
terminal: &mut Terminal<B>,
) -> (StateRegistry, ApiRegistry<L, C, M>, OutgoingMessage) {
let resources = Resources::default();

loop {
terminal.draw(|frame| screen.render(frame)).unwrap();
terminal
.draw(|frame| screen.render(frame, &resources))
.unwrap();

let event = event::poll(Duration::ZERO)
.unwrap()
Expand Down
6 changes: 3 additions & 3 deletions app/src/screen/asset/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ratatui::{crossterm::event::Event, Frame};
use rust_decimal::Decimal;
use strum::EnumIter;

use super::{OutgoingMessage, ScreenT};
use super::{resources::Resources, OutgoingMessage, ScreenT};
use crate::{
api::{
blockchain_monitoring::{BlockchainMonitoringApiT, TransactionInfo, TransactionUid},
Expand Down Expand Up @@ -104,8 +104,8 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> ScreenT<L, C,
}
}

fn render(&self, frame: &mut Frame<'_>) {
view::render(self, frame);
fn render(&self, frame: &mut Frame<'_>, resources: &Resources) {
view::render(self, frame, resources);
}

fn tick(&mut self, event: Option<Event>) -> Option<OutgoingMessage> {
Expand Down
60 changes: 40 additions & 20 deletions app/src/screen/asset/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ use crate::{
common_types::{Account, Network},
ledger::LedgerApiT,
},
screen::common::{format_address, network_symbol, render_centered_text},
screen::{
common::{format_address, network_symbol, render_centered_text, BackgroundWidget},
resources::Resources,
},
};

use super::{Model, TimePeriod};
Expand All @@ -31,15 +34,23 @@ const TX_UID_MAX_LEN: usize = 16;
pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT>(
model: &Model<L, C, M>,
frame: &mut Frame<'_>,
resources: &Resources,
) {
let area = frame.size();

frame.render_widget(BackgroundWidget::new(resources.background_color), area);

let [price_chart_area, txs_list_area] = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Fill(1); 2])
.areas(area);

let price_chart_block = Block::new().title("Price").borders(Borders::all());
let price_chart_block = Block::new()
.title("Price")
.borders(Borders::all())
.fg(resources.main_color)
.bg(resources.background_color);

let inner_price_chart_area = price_chart_block.inner(price_chart_area);
frame.render_widget(price_chart_block, price_chart_area);

Expand All @@ -49,15 +60,23 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
model.selected_time_period,
frame,
inner_price_chart_area,
resources,
);
} else {
render_price_chart_placeholder(model.selected_time_period, frame, inner_price_chart_area);
render_price_chart_placeholder(
model.selected_time_period,
frame,
inner_price_chart_area,
resources,
);
}

let txs_list_block = Block::new()
.title("Transactions")
.borders(Borders::all())
.padding(Padding::proportional(1));
.padding(Padding::proportional(1))
.fg(resources.main_color);

let inner_txs_list_area = txs_list_block.inner(txs_list_area);
frame.render_widget(txs_list_block, txs_list_area);

Expand All @@ -77,6 +96,7 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
&tx_list[..],
frame,
inner_txs_list_area,
resources,
);
}
None => {
Expand All @@ -90,8 +110,9 @@ fn render_price_chart(
selected_time_period: TimePeriod,
frame: &mut Frame<'_>,
area: Rect,
resources: &Resources,
) {
let legend = render_chart_legend(selected_time_period);
let legend = render_chart_legend(selected_time_period, resources);

let max_price = *prices.iter().max().expect("Empty `prices` vector provided");

Expand All @@ -105,23 +126,19 @@ fn render_price_chart(
.marker(symbols::Marker::Bar)
.name(legend)
.graph_type(GraphType::Line)
.style(Style::default().magenta())
.data(&price_data)];

let x_axis = Axis::default()
.style(Style::default().white())
.bounds([0.0, (price_data.len() - 1) as f64]);
let x_axis = Axis::default().bounds([0.0, (price_data.len() - 1) as f64]);

let y_axis = Axis::default()
.style(Style::default().white())
.bounds([0.0, max_price.try_into().unwrap()]);
let y_axis = Axis::default().bounds([0.0, max_price.try_into().unwrap()]);

let chart = Chart::new(datasets)
.x_axis(x_axis)
.y_axis(y_axis)
.legend_position(Some(ratatui::widgets::LegendPosition::BottomRight))
// Always show a legend(see `hidden_legend_constraints` docs).
.hidden_legend_constraints((Constraint::Min(0), Constraint::Min(0)));
.hidden_legend_constraints((Constraint::Min(0), Constraint::Min(0)))
.bg(resources.background_color);

frame.render_widget(chart, area);
}
Expand All @@ -130,21 +147,23 @@ fn render_price_chart_placeholder(
selected_time_period: TimePeriod,
frame: &mut Frame<'_>,
area: Rect,
resources: &Resources,
) {
let legend = render_chart_legend(selected_time_period);
let legend = render_chart_legend(selected_time_period, resources);

let chart = Chart::new(vec![Dataset::default().name(legend)])
.legend_position(Some(ratatui::widgets::LegendPosition::BottomRight))
// Always show a legend(see `hidden_legend_constraints` docs).
.hidden_legend_constraints((Constraint::Min(0), Constraint::Min(0)));
.hidden_legend_constraints((Constraint::Min(0), Constraint::Min(0)))
.bg(resources.background_color);

frame.render_widget(chart, area);

let text = Text::raw("Price is loading...");
render_centered_text(frame, area, text);
}

fn render_chart_legend(selected_time_period: TimePeriod) -> Line<'static> {
fn render_chart_legend(selected_time_period: TimePeriod, resources: &Resources) -> Line<'static> {
let legend = TimePeriod::iter().map(|period| {
let label = match period {
TimePeriod::Day => " d[ay]",
Expand All @@ -155,9 +174,9 @@ fn render_chart_legend(selected_time_period: TimePeriod) -> Line<'static> {
};

if period == selected_time_period {
Span::raw(label).red()
Span::raw(label).fg(resources.accent_color)
} else {
Span::raw(label).green()
Span::raw(label).fg(resources.main_color)
}
});

Expand All @@ -169,6 +188,7 @@ fn render_tx_list(
tx_list: &[(TransactionUid, TransactionInfo)],
frame: &mut Frame<'_>,
area: Rect,
resources: &Resources,
) {
let (selected_account_network, selected_account) = selected_account;

Expand All @@ -193,7 +213,7 @@ fn render_tx_list(
vec![
Span::raw(from),
Span::raw(" -> "),
Span::raw(to).green(),
Span::raw(to).fg(resources.accent_color),
Span::raw(format!(" for {}{}", amount, network_icon)),
]
}
Expand All @@ -202,7 +222,7 @@ fn render_tx_list(
let to = format_address(&to.get_info().pk, ADDRESSES_MAX_LEN);

vec![
Span::raw(from).green(),
Span::raw(from).fg(resources.accent_color),
Span::raw(" -> "),
Span::raw(to),
Span::raw(format!(" for {}{}", amount, network_icon)),
Expand Down
26 changes: 25 additions & 1 deletion app/src/screen/common.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use ratatui::{
layout::{Constraint, Flex, Layout, Rect},
text::Text,
style::{Color, Stylize},
text::{Line, Text},
widgets::Widget,
Frame,
};

Expand Down Expand Up @@ -48,6 +50,28 @@ pub fn format_address(address: &str, max_symbols: usize) -> String {
)
}

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 ratatui::prelude::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);
}
}
}

#[cfg(test)]
mod tests {
use itertools::Itertools;
Expand Down
6 changes: 3 additions & 3 deletions app/src/screen/deposit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::time::Instant;

use ratatui::{crossterm::event::Event, Frame};

use super::{OutgoingMessage, ScreenT};
use super::{resources::Resources, OutgoingMessage, ScreenT};
use crate::{
api::{
blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT,
Expand Down Expand Up @@ -33,8 +33,8 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> ScreenT<L, C,
}
}

fn render(&self, frame: &mut Frame<'_>) {
view::render(self, frame);
fn render(&self, frame: &mut Frame<'_>, resources: &Resources) {
view::render(self, frame, resources);
}

fn tick(&mut self, event: Option<Event>) -> Option<OutgoingMessage> {
Expand Down
52 changes: 40 additions & 12 deletions app/src/screen/deposit/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ use qrcode::{Color as QrCodeColor, QrCode};
use ratatui::{
layout::{Alignment, Constraint, Flex, Layout, Rect},
prelude::Buffer,
style::Stylize,
style::{Color, Stylize},
text::Text,
widgets::{Block, BorderType, Borders, Padding, Widget},
Frame,
};

use crate::api::{
blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT, ledger::LedgerApiT,
use crate::{
api::{
blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT,
ledger::LedgerApiT,
},
screen::{common::BackgroundWidget, resources::Resources},
};

use super::Model;
Expand All @@ -21,7 +25,12 @@ const DISPLAY_COPIED_TEXT_FOR: Duration = Duration::from_secs(2);
pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT>(
model: &Model<L, C, M>,
frame: &mut Frame<'_>,
resources: &Resources,
) {
let area = frame.size();

frame.render_widget(BackgroundWidget::new(resources.background_color), area);

let pubkey = model
.state
.selected_account
Expand All @@ -31,9 +40,9 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
.get_info()
.pk;

let area = frame.size();

let address_text = Text::raw(&pubkey).alignment(Alignment::Center);
let address_text = Text::raw(&pubkey)
.alignment(Alignment::Center)
.fg(resources.main_color);

let display_copied_text = if let Some(last_copy) = model.last_address_copy {
last_copy.elapsed() <= DISPLAY_COPIED_TEXT_FOR
Expand All @@ -42,9 +51,9 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
};

let description_text = if display_copied_text {
Text::raw("copied!").green()
Text::raw("copied!").fg(resources.accent_color)
} else {
Text::raw("press `c` to copy")
Text::raw("press `c` to copy").fg(resources.main_color)
}
.alignment(Alignment::Center);

Expand All @@ -58,7 +67,10 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
.flex(Flex::Center)
.areas(address_with_description_area);

let qr_code = QrCodeWidget::new(pubkey.clone()).with_size(QrCodeSize::Small);
let qr_code = QrCodeWidget::new(pubkey.clone())
.size(QrCodeSize::Small)
.dark_color(resources.qr_code_dark_color)
.light_color(resources.qr_code_light_color);

frame.render_widget(qr_code, qr_code_area);
frame.render_widget(address_text, address_area);
Expand All @@ -68,6 +80,9 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
struct QrCodeWidget {
content: String,
size: QrCodeSize,

dark_color: Color,
light_color: Color,
}

enum QrCodeSize {
Expand Down Expand Up @@ -97,8 +112,8 @@ impl Widget for QrCodeWidget {
HORIZONTAL_BLOCK_PADDING,
VERTICAL_BLOCK_PADDING,
))
.black()
.on_white();
.bg(self.light_color)
.fg(self.dark_color);

let expected_block_height = code.height() as u16 + VERTICAL_BLOCK_PADDING * 2 + 2;
let expected_block_width = code.width() as u16 + HORIZONTAL_BLOCK_PADDING * 2 + 2;
Expand All @@ -122,14 +137,27 @@ impl QrCodeWidget {
QrCodeWidget {
content,
size: QrCodeSize::Big,

dark_color: Color::Black,
light_color: Color::White,
}
}

fn with_size(mut self, size: QrCodeSize) -> Self {
fn size(mut self, size: QrCodeSize) -> Self {
self.size = size;
self
}

fn dark_color(mut self, color: Color) -> Self {
self.dark_color = color;
self
}

fn light_color(mut self, color: Color) -> Self {
self.light_color = color;
self
}

fn render_big(&self, code: QrCode) -> String {
let width = code.width();
let colors = code.into_colors();
Expand Down
Loading

0 comments on commit b8da79c

Please sign in to comment.