Feat: better UI in the main view
This commit is contained in:
@@ -4,7 +4,7 @@ use ratatui::layout::{Constraint, Layout, Rect};
|
|||||||
use crate::commands::AppCommand;
|
use crate::commands::AppCommand;
|
||||||
use crate::page::Page;
|
use crate::page::Page;
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
DeviceKind, DictFocus, EngineSection, HelpFocus, MainLayout, MinimapMode, Modal, OptionsFocus,
|
DeviceKind, DictFocus, EngineSection, HelpFocus, MinimapMode, Modal, OptionsFocus,
|
||||||
PatternsColumn, SettingKind,
|
PatternsColumn, SettingKind,
|
||||||
};
|
};
|
||||||
use crate::views::{dict_view, engine_view, help_view, main_view, patterns_view};
|
use crate::views::{dict_view, engine_view, help_view, main_view, patterns_view};
|
||||||
@@ -35,10 +35,11 @@ pub fn handle_mouse(ctx: &mut InputContext, mouse: MouseEvent, term: Rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn padded(term: Rect) -> Rect {
|
fn padded(term: Rect) -> Rect {
|
||||||
|
let h_pad = crate::views::horizontal_padding(term.width);
|
||||||
Rect {
|
Rect {
|
||||||
x: term.x + 4,
|
x: term.x + h_pad,
|
||||||
y: term.y + 1,
|
y: term.y + 1,
|
||||||
width: term.width.saturating_sub(8),
|
width: term.width.saturating_sub(h_pad * 2),
|
||||||
height: term.height.saturating_sub(2),
|
height: term.height.saturating_sub(2),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,114 +304,38 @@ fn handle_main_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replay viz/sequencer split
|
let pattern = ctx.app.current_edit_pattern();
|
||||||
let show_scope = ctx.app.audio.config.show_scope;
|
let has_viz = ctx.app.audio.config.show_scope
|
||||||
let show_spectrum = ctx.app.audio.config.show_spectrum;
|
|| ctx.app.audio.config.show_spectrum
|
||||||
let show_preview = ctx.app.audio.config.show_preview;
|
|| ctx.app.audio.config.show_preview;
|
||||||
let has_viz = show_scope || show_spectrum || show_preview;
|
let seq_h = main_view::sequencer_height(pattern.length, ctx.app.editor_ctx.step);
|
||||||
let layout = ctx.app.audio.config.layout;
|
let (_, sequencer_area) =
|
||||||
|
main_view::viz_seq_split(main_area, ctx.app.audio.config.layout, has_viz, seq_h);
|
||||||
let sequencer_area = match layout {
|
|
||||||
MainLayout::Top => {
|
|
||||||
let viz_height = if has_viz { 16 } else { 0 };
|
|
||||||
let [_viz, seq] =
|
|
||||||
Layout::vertical([Constraint::Length(viz_height), Constraint::Fill(1)])
|
|
||||||
.areas(main_area);
|
|
||||||
seq
|
|
||||||
}
|
|
||||||
MainLayout::Bottom => {
|
|
||||||
let viz_height = if has_viz { 16 } else { 0 };
|
|
||||||
let [seq, _viz] =
|
|
||||||
Layout::vertical([Constraint::Fill(1), Constraint::Length(viz_height)])
|
|
||||||
.areas(main_area);
|
|
||||||
seq
|
|
||||||
}
|
|
||||||
MainLayout::Left => {
|
|
||||||
let viz_width = if has_viz { 33 } else { 0 };
|
|
||||||
let [_viz, _spacer, seq] = Layout::horizontal([
|
|
||||||
Constraint::Percentage(viz_width),
|
|
||||||
Constraint::Length(2),
|
|
||||||
Constraint::Fill(1),
|
|
||||||
])
|
|
||||||
.areas(main_area);
|
|
||||||
seq
|
|
||||||
}
|
|
||||||
MainLayout::Right => {
|
|
||||||
let viz_width = if has_viz { 33 } else { 0 };
|
|
||||||
let [seq, _spacer, _viz] = Layout::horizontal([
|
|
||||||
Constraint::Fill(1),
|
|
||||||
Constraint::Length(2),
|
|
||||||
Constraint::Percentage(viz_width),
|
|
||||||
])
|
|
||||||
.areas(main_area);
|
|
||||||
seq
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !contains(sequencer_area, col, row) {
|
if !contains(sequencer_area, col, row) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replay grid layout to find which step was clicked
|
|
||||||
if let Some(step) = hit_test_grid(ctx, col, row, sequencer_area) {
|
if let Some(step) = hit_test_grid(ctx, col, row, sequencer_area) {
|
||||||
ctx.dispatch(AppCommand::GoToStep(step));
|
ctx.dispatch(AppCommand::GoToStep(step));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hit_test_grid(ctx: &InputContext, col: u16, row: u16, area: Rect) -> Option<usize> {
|
fn hit_test_grid(ctx: &InputContext, col: u16, row: u16, area: Rect) -> Option<usize> {
|
||||||
if area.width < 50 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pattern = ctx.app.current_edit_pattern();
|
let pattern = ctx.app.current_edit_pattern();
|
||||||
let length = pattern.length;
|
let length = pattern.length;
|
||||||
let page = ctx.app.editor_ctx.step / STEPS_PER_PAGE;
|
let page = ctx.app.editor_ctx.step / STEPS_PER_PAGE;
|
||||||
let page_start = page * STEPS_PER_PAGE;
|
let page_start = page * STEPS_PER_PAGE;
|
||||||
let steps_on_page = (page_start + STEPS_PER_PAGE).min(length) - page_start;
|
let steps_on_page = (page_start + STEPS_PER_PAGE).min(length) - page_start;
|
||||||
|
|
||||||
let num_rows = steps_on_page.div_ceil(8);
|
for (tile_rect, step_offset) in main_view::grid_layout(area, steps_on_page) {
|
||||||
let steps_per_row = steps_on_page.div_ceil(num_rows);
|
if contains(tile_rect, col, row) {
|
||||||
|
let step_idx = page_start + step_offset;
|
||||||
let row_height = area.height / num_rows as u16;
|
|
||||||
|
|
||||||
let row_constraints: Vec<Constraint> = (0..num_rows)
|
|
||||||
.map(|_| Constraint::Length(row_height))
|
|
||||||
.collect();
|
|
||||||
let rows = Layout::vertical(row_constraints).split(area);
|
|
||||||
|
|
||||||
for row_idx in 0..num_rows {
|
|
||||||
let row_area = rows[row_idx];
|
|
||||||
if !contains(row_area, col, row) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let start_step = row_idx * steps_per_row;
|
|
||||||
let end_step = (start_step + steps_per_row).min(steps_on_page);
|
|
||||||
let cols_in_row = end_step - start_step;
|
|
||||||
|
|
||||||
let col_constraints: Vec<Constraint> = (0..cols_in_row * 2 - 1)
|
|
||||||
.map(|i| {
|
|
||||||
if i % 2 == 0 {
|
|
||||||
Constraint::Fill(1)
|
|
||||||
} else if i == cols_in_row - 1 {
|
|
||||||
Constraint::Length(2)
|
|
||||||
} else {
|
|
||||||
Constraint::Length(1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let cols = Layout::horizontal(col_constraints).split(row_area);
|
|
||||||
|
|
||||||
for col_idx in 0..cols_in_row {
|
|
||||||
let tile_area = cols[col_idx * 2];
|
|
||||||
if contains(tile_area, col, row) {
|
|
||||||
let step_idx = page_start + start_step + col_idx;
|
|
||||||
if step_idx < length {
|
if step_idx < length {
|
||||||
return Some(step_idx);
|
return Some(step_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,52 +27,11 @@ pub fn layout(area: Rect) -> [Rect; 3] {
|
|||||||
pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
||||||
let [main_area, _, vu_area] = layout(area);
|
let [main_area, _, vu_area] = layout(area);
|
||||||
|
|
||||||
let show_scope = app.audio.config.show_scope;
|
let has_viz = app.audio.config.show_scope
|
||||||
let show_spectrum = app.audio.config.show_spectrum;
|
|| app.audio.config.show_spectrum
|
||||||
let show_preview = app.audio.config.show_preview;
|
|| app.audio.config.show_preview;
|
||||||
let has_viz = show_scope || show_spectrum || show_preview;
|
let seq_h = sequencer_height(app.current_edit_pattern().length, app.editor_ctx.step);
|
||||||
let layout = app.audio.config.layout;
|
let (viz_area, sequencer_area) = viz_seq_split(main_area, app.audio.config.layout, has_viz, seq_h);
|
||||||
|
|
||||||
let (viz_area, sequencer_area) = match layout {
|
|
||||||
MainLayout::Top => {
|
|
||||||
let viz_height = if has_viz { 16 } else { 0 };
|
|
||||||
let [viz, seq] = Layout::vertical([
|
|
||||||
Constraint::Length(viz_height),
|
|
||||||
Constraint::Fill(1),
|
|
||||||
])
|
|
||||||
.areas(main_area);
|
|
||||||
(viz, seq)
|
|
||||||
}
|
|
||||||
MainLayout::Bottom => {
|
|
||||||
let viz_height = if has_viz { 16 } else { 0 };
|
|
||||||
let [seq, viz] = Layout::vertical([
|
|
||||||
Constraint::Fill(1),
|
|
||||||
Constraint::Length(viz_height),
|
|
||||||
])
|
|
||||||
.areas(main_area);
|
|
||||||
(viz, seq)
|
|
||||||
}
|
|
||||||
MainLayout::Left => {
|
|
||||||
let viz_width = if has_viz { 33 } else { 0 };
|
|
||||||
let [viz, _spacer, seq] = Layout::horizontal([
|
|
||||||
Constraint::Percentage(viz_width),
|
|
||||||
Constraint::Length(2),
|
|
||||||
Constraint::Fill(1),
|
|
||||||
])
|
|
||||||
.areas(main_area);
|
|
||||||
(viz, seq)
|
|
||||||
}
|
|
||||||
MainLayout::Right => {
|
|
||||||
let viz_width = if has_viz { 33 } else { 0 };
|
|
||||||
let [seq, _spacer, viz] = Layout::horizontal([
|
|
||||||
Constraint::Fill(1),
|
|
||||||
Constraint::Length(2),
|
|
||||||
Constraint::Percentage(viz_width),
|
|
||||||
])
|
|
||||||
.areas(main_area);
|
|
||||||
(viz, seq)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if has_viz {
|
if has_viz {
|
||||||
render_viz_area(frame, app, snapshot, viz_area);
|
render_viz_area(frame, app, snapshot, viz_area);
|
||||||
@@ -128,6 +87,131 @@ fn render_viz_area(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const STEPS_PER_PAGE: usize = 32;
|
const STEPS_PER_PAGE: usize = 32;
|
||||||
|
const TILE_HEIGHT: u16 = 3;
|
||||||
|
const ROW_GAP: u16 = 1;
|
||||||
|
|
||||||
|
pub fn sequencer_height(pattern_length: usize, current_step: usize) -> u16 {
|
||||||
|
let page = current_step / STEPS_PER_PAGE;
|
||||||
|
let page_start = page * STEPS_PER_PAGE;
|
||||||
|
let steps_on_page = (page_start + STEPS_PER_PAGE).min(pattern_length) - page_start;
|
||||||
|
if steps_on_page == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let num_rows = steps_on_page.div_ceil(8);
|
||||||
|
let grid_h = (num_rows as u16) * TILE_HEIGHT + (num_rows.saturating_sub(1) as u16) * ROW_GAP;
|
||||||
|
grid_h + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn viz_seq_split(
|
||||||
|
main_area: Rect,
|
||||||
|
layout: MainLayout,
|
||||||
|
has_viz: bool,
|
||||||
|
seq_h: u16,
|
||||||
|
) -> (Rect, Rect) {
|
||||||
|
match layout {
|
||||||
|
MainLayout::Top => {
|
||||||
|
if has_viz {
|
||||||
|
let [viz, seq] = Layout::vertical([
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(seq_h),
|
||||||
|
])
|
||||||
|
.areas(main_area);
|
||||||
|
(viz, seq)
|
||||||
|
} else {
|
||||||
|
(Rect::default(), main_area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MainLayout::Bottom => {
|
||||||
|
if has_viz {
|
||||||
|
let [seq, viz] = Layout::vertical([
|
||||||
|
Constraint::Length(seq_h),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
])
|
||||||
|
.areas(main_area);
|
||||||
|
(viz, seq)
|
||||||
|
} else {
|
||||||
|
(Rect::default(), main_area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MainLayout::Left => {
|
||||||
|
let viz_width = if has_viz { 33 } else { 0 };
|
||||||
|
let [viz, _spacer, seq] = Layout::horizontal([
|
||||||
|
Constraint::Percentage(viz_width),
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
])
|
||||||
|
.areas(main_area);
|
||||||
|
(viz, seq)
|
||||||
|
}
|
||||||
|
MainLayout::Right => {
|
||||||
|
let viz_width = if has_viz { 33 } else { 0 };
|
||||||
|
let [seq, _spacer, viz] = Layout::horizontal([
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Percentage(viz_width),
|
||||||
|
])
|
||||||
|
.areas(main_area);
|
||||||
|
(viz, seq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grid_layout(area: Rect, steps_on_page: usize) -> Vec<(Rect, usize)> {
|
||||||
|
if area.width < 50 || steps_on_page == 0 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_rows = steps_on_page.div_ceil(8);
|
||||||
|
let steps_per_row = steps_on_page.div_ceil(num_rows);
|
||||||
|
|
||||||
|
let total_grid_height =
|
||||||
|
(num_rows as u16) * TILE_HEIGHT + (num_rows.saturating_sub(1) as u16) * ROW_GAP;
|
||||||
|
let y_offset = area.height.saturating_sub(total_grid_height) / 2;
|
||||||
|
|
||||||
|
let grid_area = Rect {
|
||||||
|
x: area.x,
|
||||||
|
y: area.y + y_offset,
|
||||||
|
width: area.width,
|
||||||
|
height: total_grid_height.min(area.height),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut row_constraints: Vec<Constraint> = Vec::new();
|
||||||
|
for i in 0..num_rows {
|
||||||
|
row_constraints.push(Constraint::Length(TILE_HEIGHT));
|
||||||
|
if i < num_rows - 1 {
|
||||||
|
row_constraints.push(Constraint::Length(ROW_GAP));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let row_areas = Layout::vertical(row_constraints).split(grid_area);
|
||||||
|
|
||||||
|
let mut tiles = Vec::with_capacity(steps_on_page);
|
||||||
|
|
||||||
|
for row_idx in 0..num_rows {
|
||||||
|
let row_area = row_areas[row_idx * 2];
|
||||||
|
let start_step = row_idx * steps_per_row;
|
||||||
|
let end_step = (start_step + steps_per_row).min(steps_on_page);
|
||||||
|
let cols_in_row = end_step - start_step;
|
||||||
|
|
||||||
|
let mut col_constraints: Vec<Constraint> = Vec::new();
|
||||||
|
for col in 0..cols_in_row {
|
||||||
|
col_constraints.push(Constraint::Fill(1));
|
||||||
|
if col < cols_in_row - 1 {
|
||||||
|
if (col + 1) % 4 == 0 {
|
||||||
|
col_constraints.push(Constraint::Length(2));
|
||||||
|
} else {
|
||||||
|
col_constraints.push(Constraint::Length(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let col_areas = Layout::horizontal(col_constraints).split(row_area);
|
||||||
|
|
||||||
|
for col_idx in 0..cols_in_row {
|
||||||
|
tiles.push((col_areas[col_idx * 2], start_step + col_idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles
|
||||||
|
}
|
||||||
|
|
||||||
fn render_sequencer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
fn render_sequencer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
||||||
let theme = theme::get();
|
let theme = theme::get();
|
||||||
@@ -146,45 +230,14 @@ fn render_sequencer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot,
|
|||||||
let page_start = page * STEPS_PER_PAGE;
|
let page_start = page * STEPS_PER_PAGE;
|
||||||
let steps_on_page = (page_start + STEPS_PER_PAGE).min(length) - page_start;
|
let steps_on_page = (page_start + STEPS_PER_PAGE).min(length) - page_start;
|
||||||
|
|
||||||
let num_rows = steps_on_page.div_ceil(8);
|
for (tile_rect, step_offset) in grid_layout(area, steps_on_page) {
|
||||||
let steps_per_row = steps_on_page.div_ceil(num_rows);
|
let step_idx = page_start + step_offset;
|
||||||
|
|
||||||
let row_height = area.height / num_rows as u16;
|
|
||||||
|
|
||||||
let row_constraints: Vec<Constraint> = (0..num_rows)
|
|
||||||
.map(|_| Constraint::Length(row_height))
|
|
||||||
.collect();
|
|
||||||
let rows = Layout::vertical(row_constraints).split(area);
|
|
||||||
|
|
||||||
for row_idx in 0..num_rows {
|
|
||||||
let row_area = rows[row_idx];
|
|
||||||
let start_step = row_idx * steps_per_row;
|
|
||||||
let end_step = (start_step + steps_per_row).min(steps_on_page);
|
|
||||||
let cols_in_row = end_step - start_step;
|
|
||||||
|
|
||||||
let col_constraints: Vec<Constraint> = (0..cols_in_row * 2 - 1)
|
|
||||||
.map(|i| {
|
|
||||||
if i % 2 == 0 {
|
|
||||||
Constraint::Fill(1)
|
|
||||||
} else if i == cols_in_row - 1 {
|
|
||||||
Constraint::Length(2)
|
|
||||||
} else {
|
|
||||||
Constraint::Length(1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let cols = Layout::horizontal(col_constraints).split(row_area);
|
|
||||||
|
|
||||||
for col_idx in 0..cols_in_row {
|
|
||||||
let step_idx = page_start + start_step + col_idx;
|
|
||||||
if step_idx < length {
|
if step_idx < length {
|
||||||
render_tile(frame, cols[col_idx * 2], app, snapshot, step_idx);
|
render_tile(frame, tile_rect, app, snapshot, step_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_tile(
|
fn render_tile(
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
@@ -199,7 +252,9 @@ fn render_tile(
|
|||||||
let has_content = step.map(|s| s.has_content()).unwrap_or(false);
|
let has_content = step.map(|s| s.has_content()).unwrap_or(false);
|
||||||
let is_linked = step.map(|s| s.source.is_some()).unwrap_or(false);
|
let is_linked = step.map(|s| s.source.is_some()).unwrap_or(false);
|
||||||
let is_selected = step_idx == app.editor_ctx.step;
|
let is_selected = step_idx == app.editor_ctx.step;
|
||||||
let in_selection = app.editor_ctx.selection_range()
|
let in_selection = app
|
||||||
|
.editor_ctx
|
||||||
|
.selection_range()
|
||||||
.map(|r| r.contains(&step_idx))
|
.map(|r| r.contains(&step_idx))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
@@ -228,7 +283,11 @@ fn render_tile(
|
|||||||
(Color::Rgb(r, g, b), theme.tile.active_fg)
|
(Color::Rgb(r, g, b), theme.tile.active_fg)
|
||||||
}
|
}
|
||||||
(false, true, false, false, _) => {
|
(false, true, false, false, _) => {
|
||||||
let bg = if has_content { theme.tile.content_bg } else { theme.tile.active_bg };
|
let bg = if has_content {
|
||||||
|
theme.tile.content_bg
|
||||||
|
} else {
|
||||||
|
theme.tile.active_bg
|
||||||
|
};
|
||||||
(bg, theme.tile.active_fg)
|
(bg, theme.tile.active_fg)
|
||||||
}
|
}
|
||||||
(false, false, true, _, _) => (theme.selection.selected, theme.selection.cursor_fg),
|
(false, false, true, _, _) => (theme.selection.selected, theme.selection.cursor_fg),
|
||||||
@@ -236,11 +295,8 @@ fn render_tile(
|
|||||||
(false, false, false, _, _) => (theme.tile.inactive_bg, theme.tile.inactive_fg),
|
(false, false, false, _, _) => (theme.tile.inactive_bg, theme.tile.inactive_fg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let block = Block::default()
|
let bg_fill = Paragraph::new("").style(Style::new().bg(bg));
|
||||||
.borders(Borders::ALL)
|
frame.render_widget(bg_fill, area);
|
||||||
.border_style(Style::new().fg(theme.ui.border));
|
|
||||||
let inner = block.inner(area);
|
|
||||||
frame.render_widget(block, area);
|
|
||||||
|
|
||||||
let source_idx = step.and_then(|s| s.source);
|
let source_idx = step.and_then(|s| s.source);
|
||||||
let symbol = if is_playing {
|
let symbol = if is_playing {
|
||||||
@@ -253,53 +309,54 @@ fn render_tile(
|
|||||||
format!("{:02}", step_idx + 1)
|
format!("{:02}", step_idx + 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
// For linked steps, get the name from the source step
|
|
||||||
let step_name = if let Some(src) = source_idx {
|
let step_name = if let Some(src) = source_idx {
|
||||||
pattern.step(src as usize).and_then(|s| s.name.as_ref())
|
pattern.step(src as usize).and_then(|s| s.name.as_ref())
|
||||||
} else {
|
} else {
|
||||||
step.and_then(|s| s.name.as_ref())
|
step.and_then(|s| s.name.as_ref())
|
||||||
};
|
};
|
||||||
let num_lines = if step_name.is_some() { 2u16 } else { 1u16 };
|
|
||||||
let content_height = num_lines;
|
|
||||||
let y_offset = inner.height.saturating_sub(content_height) / 2;
|
|
||||||
|
|
||||||
// Fill background for inner area
|
let center_y = area.y + area.height / 2;
|
||||||
let bg_fill = Paragraph::new("").style(Style::new().bg(bg));
|
|
||||||
frame.render_widget(bg_fill, inner);
|
|
||||||
|
|
||||||
if let Some(name) = step_name {
|
if let Some(name) = step_name {
|
||||||
|
if center_y > area.y {
|
||||||
let name_area = Rect {
|
let name_area = Rect {
|
||||||
x: inner.x,
|
x: area.x,
|
||||||
y: inner.y + y_offset,
|
y: center_y - 1,
|
||||||
width: inner.width,
|
width: area.width,
|
||||||
height: 1,
|
height: 1,
|
||||||
};
|
};
|
||||||
let name_widget = Paragraph::new(name.as_str())
|
let name_widget = Paragraph::new(name.as_str())
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.style(Style::new().bg(bg).fg(fg).add_modifier(Modifier::BOLD));
|
.style(Style::new().bg(bg).fg(fg).add_modifier(Modifier::BOLD));
|
||||||
frame.render_widget(name_widget, name_area);
|
frame.render_widget(name_widget, name_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let symbol_area = Rect {
|
let symbol_area = Rect {
|
||||||
x: inner.x,
|
x: area.x,
|
||||||
y: inner.y + y_offset + 1,
|
y: center_y,
|
||||||
width: inner.width,
|
width: area.width,
|
||||||
height: 1,
|
height: 1,
|
||||||
};
|
};
|
||||||
let symbol_widget = Paragraph::new(symbol)
|
let symbol_widget = Paragraph::new(symbol)
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.style(Style::new().bg(bg).fg(fg).add_modifier(Modifier::BOLD));
|
.style(Style::new().bg(bg).fg(fg).add_modifier(Modifier::BOLD));
|
||||||
frame.render_widget(symbol_widget, symbol_area);
|
frame.render_widget(symbol_widget, symbol_area);
|
||||||
} else {
|
|
||||||
let centered_area = Rect {
|
if has_content && center_y + 1 < area.y + area.height {
|
||||||
x: inner.x,
|
let script = pattern.resolve_script(step_idx).unwrap_or("");
|
||||||
y: inner.y + y_offset,
|
if let Some(first_token) = script.split_whitespace().next() {
|
||||||
width: inner.width,
|
let hint_area = Rect {
|
||||||
|
x: area.x,
|
||||||
|
y: center_y + 1,
|
||||||
|
width: area.width,
|
||||||
height: 1,
|
height: 1,
|
||||||
};
|
};
|
||||||
let tile = Paragraph::new(symbol)
|
let hint_widget = Paragraph::new(first_token)
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.style(Style::new().bg(bg).fg(fg).add_modifier(Modifier::BOLD));
|
.style(Style::new().bg(bg).fg(theme.ui.text_dim));
|
||||||
frame.render_widget(tile, centered_area);
|
frame.render_widget(hint_widget, hint_area);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ pub mod patterns_view;
|
|||||||
mod render;
|
mod render;
|
||||||
pub mod title_view;
|
pub mod title_view;
|
||||||
|
|
||||||
pub use render::render;
|
pub use render::{horizontal_padding, render};
|
||||||
|
|||||||
@@ -62,6 +62,16 @@ pub fn adjust_resolved_for_line(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn horizontal_padding(width: u16) -> u16 {
|
||||||
|
if width >= 120 {
|
||||||
|
4
|
||||||
|
} else if width >= 80 {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render(
|
pub fn render(
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
app: &App,
|
app: &App,
|
||||||
@@ -90,10 +100,11 @@ pub fn render(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let h_pad = horizontal_padding(term.width);
|
||||||
let padded = Rect {
|
let padded = Rect {
|
||||||
x: term.x + 4,
|
x: term.x + h_pad,
|
||||||
y: term.y + 1,
|
y: term.y + 1,
|
||||||
width: term.width.saturating_sub(8),
|
width: term.width.saturating_sub(h_pad * 2),
|
||||||
height: term.height.saturating_sub(2),
|
height: term.height.saturating_sub(2),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user