Feat: entretien de la codebase
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line as RLine, Span};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
|
||||
use ratatui::widgets::{Block, Borders, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
@@ -9,6 +9,7 @@ use crate::model::categories::{get_category_name, CatEntry, CATEGORIES};
|
||||
use crate::model::{Word, WORDS};
|
||||
use crate::state::DictFocus;
|
||||
use crate::theme;
|
||||
use crate::widgets::{render_search_bar, CategoryItem, CategoryList};
|
||||
|
||||
use CatEntry::{Category, Section};
|
||||
|
||||
@@ -47,84 +48,35 @@ fn render_categories(frame: &mut Frame, app: &App, area: Rect, dimmed: bool) {
|
||||
let theme = theme::get();
|
||||
let focused = app.ui.dict_focus == DictFocus::Categories && !dimmed;
|
||||
|
||||
let visible_height = area.height.saturating_sub(2) as usize;
|
||||
let total_items = CATEGORIES.len();
|
||||
|
||||
// Find the visual index of the selected category (including sections)
|
||||
let selected_visual_idx = {
|
||||
let mut visual = 0;
|
||||
let mut cat_count = 0;
|
||||
for entry in CATEGORIES.iter() {
|
||||
if let Category(_) = entry {
|
||||
if cat_count == app.ui.dict_category {
|
||||
break;
|
||||
}
|
||||
cat_count += 1;
|
||||
}
|
||||
visual += 1;
|
||||
}
|
||||
visual
|
||||
};
|
||||
|
||||
// Calculate scroll to keep selection visible (centered when possible)
|
||||
let scroll = if selected_visual_idx < visible_height / 2 {
|
||||
0
|
||||
} else if selected_visual_idx > total_items.saturating_sub(visible_height / 2) {
|
||||
total_items.saturating_sub(visible_height)
|
||||
} else {
|
||||
selected_visual_idx.saturating_sub(visible_height / 2)
|
||||
};
|
||||
|
||||
// Count categories before the scroll offset to track cat_idx correctly
|
||||
let mut cat_idx = CATEGORIES
|
||||
let items: Vec<CategoryItem> = CATEGORIES
|
||||
.iter()
|
||||
.take(scroll)
|
||||
.filter(|e| matches!(e, Category(_)))
|
||||
.count();
|
||||
|
||||
let items: Vec<ListItem> = CATEGORIES
|
||||
.iter()
|
||||
.skip(scroll)
|
||||
.take(visible_height)
|
||||
.map(|entry| match entry {
|
||||
Section(name) => {
|
||||
let style = Style::new().fg(theme.ui.text_dim);
|
||||
ListItem::new(format!("─ {name} ─")).style(style)
|
||||
}
|
||||
Category(name) => {
|
||||
let is_selected = cat_idx == app.ui.dict_category;
|
||||
let style = if dimmed {
|
||||
Style::new().fg(theme.dict.category_dimmed)
|
||||
} else if is_selected && focused {
|
||||
Style::new()
|
||||
.fg(theme.dict.category_focused)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if is_selected {
|
||||
Style::new().fg(theme.dict.category_selected)
|
||||
} else {
|
||||
Style::new().fg(theme.dict.category_normal)
|
||||
};
|
||||
let prefix = if is_selected && !dimmed { "> " } else { " " };
|
||||
cat_idx += 1;
|
||||
ListItem::new(format!("{prefix}{name}")).style(style)
|
||||
}
|
||||
Section(name) => CategoryItem {
|
||||
label: name,
|
||||
is_section: true,
|
||||
},
|
||||
Category(name) => CategoryItem {
|
||||
label: name,
|
||||
is_section: false,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let border_color = if focused { theme.dict.border_focused } else { theme.dict.border_normal };
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().fg(border_color))
|
||||
let mut list = CategoryList::new(&items, app.ui.dict_category)
|
||||
.focused(focused)
|
||||
.title("Categories");
|
||||
let list = List::new(items).block(block);
|
||||
frame.render_widget(list, area);
|
||||
|
||||
if dimmed {
|
||||
list = list.dimmed(theme.dict.category_dimmed);
|
||||
}
|
||||
|
||||
list.render(frame, area);
|
||||
}
|
||||
|
||||
fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
let theme = theme::get();
|
||||
let focused = app.ui.dict_focus == DictFocus::Words;
|
||||
|
||||
// Filter words by search query or category
|
||||
let words: Vec<&Word> = if is_searching {
|
||||
let query = app.ui.dict_search_query.to_lowercase();
|
||||
WORDS
|
||||
@@ -142,7 +94,6 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Split area for search bar when search is active or has query
|
||||
let show_search = app.ui.dict_search_active || is_searching;
|
||||
let (search_area, content_area) = if show_search {
|
||||
let [s, c] =
|
||||
@@ -152,9 +103,8 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
(None, area)
|
||||
};
|
||||
|
||||
// Render search bar
|
||||
if let Some(sa) = search_area {
|
||||
render_search_bar(frame, app, sa);
|
||||
render_search_bar(frame, sa, &app.ui.dict_search_query, app.ui.dict_search_active);
|
||||
}
|
||||
|
||||
let content_width = content_area.width.saturating_sub(2) as usize;
|
||||
@@ -229,17 +179,3 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
.block(block);
|
||||
frame.render_widget(para, content_area);
|
||||
}
|
||||
|
||||
fn render_search_bar(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let style = if app.ui.dict_search_active {
|
||||
Style::new().fg(theme.search.active)
|
||||
} else {
|
||||
Style::new().fg(theme.search.inactive)
|
||||
};
|
||||
let cursor = if app.ui.dict_search_active { "_" } else { "" };
|
||||
let text = format!(" /{}{}", app.ui.dict_search_query, cursor);
|
||||
let line = RLine::from(Span::styled(text, style));
|
||||
frame.render_widget(Paragraph::new(vec![line]), area);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ use ratatui::Frame;
|
||||
use crate::app::App;
|
||||
use crate::state::{DeviceKind, EngineSection, SettingKind};
|
||||
use crate::theme;
|
||||
use crate::widgets::{Orientation, Scope, Spectrum};
|
||||
use crate::widgets::{
|
||||
render_scroll_indicators, render_section_header, IndicatorAlign, Orientation, Scope, Spectrum,
|
||||
};
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let [left_col, _, right_col] = Layout::horizontal([
|
||||
@@ -122,27 +124,15 @@ fn render_settings_section(frame: &mut Frame, app: &App, area: Rect) {
|
||||
render_samples(frame, app, samples_area);
|
||||
}
|
||||
|
||||
// Scroll indicators
|
||||
let indicator_style = Style::new().fg(theme.engine.scroll_indicator);
|
||||
let indicator_x = padded.x + padded.width.saturating_sub(1);
|
||||
|
||||
if scroll_offset > 0 {
|
||||
let up_indicator = Paragraph::new("▲").style(indicator_style);
|
||||
frame.render_widget(up_indicator, Rect::new(indicator_x, padded.y, 1, 1));
|
||||
}
|
||||
|
||||
if scroll_offset + max_visible < total_lines {
|
||||
let down_indicator = Paragraph::new("▼").style(indicator_style);
|
||||
frame.render_widget(
|
||||
down_indicator,
|
||||
Rect::new(
|
||||
indicator_x,
|
||||
padded.y + padded.height.saturating_sub(1),
|
||||
1,
|
||||
1,
|
||||
),
|
||||
);
|
||||
}
|
||||
render_scroll_indicators(
|
||||
frame,
|
||||
padded,
|
||||
scroll_offset,
|
||||
max_visible,
|
||||
total_lines,
|
||||
theme.engine.scroll_indicator,
|
||||
IndicatorAlign::Right,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_visualizers(frame: &mut Frame, app: &App, area: Rect) {
|
||||
@@ -210,30 +200,6 @@ fn devices_section_height(app: &App) -> u16 {
|
||||
3 + output_h.max(input_h)
|
||||
}
|
||||
|
||||
fn render_section_header(frame: &mut Frame, title: &str, focused: bool, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let [header_area, divider_area] =
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area);
|
||||
|
||||
let header_style = if focused {
|
||||
Style::new()
|
||||
.fg(theme.engine.header_focused)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::new()
|
||||
.fg(theme.engine.header)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
};
|
||||
|
||||
frame.render_widget(Paragraph::new(title).style(header_style), header_area);
|
||||
|
||||
let divider = "─".repeat(area.width as usize);
|
||||
frame.render_widget(
|
||||
Paragraph::new(divider).style(Style::new().fg(theme.engine.divider)),
|
||||
divider_area,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_devices(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let section_focused = app.audio.section == EngineSection::Devices;
|
||||
|
||||
@@ -2,7 +2,7 @@ use cagire_markdown::{CodeHighlighter, MarkdownTheme};
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line as RLine, Span};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Wrap};
|
||||
use ratatui::widgets::{Block, Borders, Padding, Paragraph, Wrap};
|
||||
use ratatui::Frame;
|
||||
#[cfg(not(feature = "desktop"))]
|
||||
use tui_big_text::{BigText, PixelSize};
|
||||
@@ -12,6 +12,7 @@ use crate::model::docs::{get_topic, DocEntry, DOCS};
|
||||
use crate::state::HelpFocus;
|
||||
use crate::theme;
|
||||
use crate::views::highlight;
|
||||
use crate::widgets::{render_search_bar, CategoryItem, CategoryList};
|
||||
|
||||
use DocEntry::{Section, Topic};
|
||||
|
||||
@@ -100,80 +101,28 @@ pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
||||
|
||||
fn render_topics(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let focused = app.ui.help_focus == HelpFocus::Topics;
|
||||
|
||||
let visible_height = area.height.saturating_sub(2) as usize;
|
||||
let total_items = DOCS.len();
|
||||
|
||||
// Find the visual index of the selected topic (including sections)
|
||||
let selected_visual_idx = {
|
||||
let mut visual = 0;
|
||||
let mut topic_count = 0;
|
||||
for entry in DOCS.iter() {
|
||||
if let Topic(_, _) = entry {
|
||||
if topic_count == app.ui.help_topic {
|
||||
break;
|
||||
}
|
||||
topic_count += 1;
|
||||
}
|
||||
visual += 1;
|
||||
}
|
||||
visual
|
||||
};
|
||||
|
||||
// Calculate scroll to keep selection visible (centered when possible)
|
||||
let scroll = if selected_visual_idx < visible_height / 2 {
|
||||
0
|
||||
} else if selected_visual_idx > total_items.saturating_sub(visible_height / 2) {
|
||||
total_items.saturating_sub(visible_height)
|
||||
} else {
|
||||
selected_visual_idx.saturating_sub(visible_height / 2)
|
||||
};
|
||||
|
||||
// Count topics before the scroll offset to track topic_idx correctly
|
||||
let mut topic_idx = DOCS
|
||||
let items: Vec<CategoryItem> = DOCS
|
||||
.iter()
|
||||
.take(scroll)
|
||||
.filter(|e| matches!(e, Topic(_, _)))
|
||||
.count();
|
||||
|
||||
let items: Vec<ListItem> = DOCS
|
||||
.iter()
|
||||
.skip(scroll)
|
||||
.take(visible_height)
|
||||
.map(|entry| match entry {
|
||||
Section(name) => {
|
||||
let style = Style::new().fg(theme.ui.text_dim);
|
||||
ListItem::new(format!("─ {name} ─")).style(style)
|
||||
}
|
||||
Topic(name, _) => {
|
||||
let selected = topic_idx == app.ui.help_topic;
|
||||
let style = if selected {
|
||||
Style::new()
|
||||
.fg(theme.dict.category_selected)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::new().fg(theme.ui.text_primary)
|
||||
};
|
||||
let prefix = if selected { "> " } else { " " };
|
||||
topic_idx += 1;
|
||||
ListItem::new(format!("{prefix}{name}")).style(style)
|
||||
}
|
||||
Section(name) => CategoryItem {
|
||||
label: name,
|
||||
is_section: true,
|
||||
},
|
||||
Topic(name, _) => CategoryItem {
|
||||
label: name,
|
||||
is_section: false,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let focused = app.ui.help_focus == HelpFocus::Topics;
|
||||
let border_color = if focused {
|
||||
theme.dict.border_focused
|
||||
} else {
|
||||
theme.dict.border_normal
|
||||
};
|
||||
let list = List::new(items).block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().fg(border_color))
|
||||
.title("Topics"),
|
||||
);
|
||||
frame.render_widget(list, area);
|
||||
CategoryList::new(&items, app.ui.help_topic)
|
||||
.focused(focused)
|
||||
.title("Topics")
|
||||
.selected_color(theme.dict.category_selected)
|
||||
.normal_color(theme.ui.text_primary)
|
||||
.render(frame, area);
|
||||
}
|
||||
|
||||
const WELCOME_TOPIC: usize = 0;
|
||||
@@ -237,7 +186,7 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let content_area = if has_search_bar {
|
||||
let [content, search] =
|
||||
Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).areas(md_area);
|
||||
render_search_bar(frame, app, search);
|
||||
render_search_bar(frame, search, &app.ui.help_search_query, app.ui.help_search_active);
|
||||
content
|
||||
} else {
|
||||
md_area
|
||||
@@ -292,18 +241,6 @@ fn wrapped_line_count(line: &RLine, width: usize) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_search_bar(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let style = if app.ui.help_search_active {
|
||||
Style::new().fg(theme.search.active)
|
||||
} else {
|
||||
Style::new().fg(theme.search.inactive)
|
||||
};
|
||||
let cursor = if app.ui.help_search_active { "█" } else { "" };
|
||||
let text = format!(" /{}{cursor}", app.ui.help_search_query);
|
||||
frame.render_widget(Paragraph::new(text).style(style), area);
|
||||
}
|
||||
|
||||
fn highlight_line<'a>(line: RLine<'a>, query: &str) -> RLine<'a> {
|
||||
let theme = theme::get();
|
||||
let mut result: Vec<Span<'a>> = Vec::new();
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::engine::LinkState;
|
||||
use crate::midi;
|
||||
use crate::state::OptionsFocus;
|
||||
use crate::theme;
|
||||
use crate::widgets::{render_scroll_indicators, IndicatorAlign};
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
let theme = theme::get();
|
||||
@@ -243,24 +244,15 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
|
||||
frame.render_widget(Paragraph::new(visible_lines), padded);
|
||||
|
||||
let indicator_style = Style::new().fg(theme.ui.text_dim);
|
||||
let indicator_x = padded.x + padded.width.saturating_sub(1);
|
||||
|
||||
if scroll_offset > 0 {
|
||||
let up_indicator = Paragraph::new("▲").style(indicator_style);
|
||||
frame.render_widget(
|
||||
up_indicator,
|
||||
Rect::new(indicator_x, padded.y, 1, 1),
|
||||
);
|
||||
}
|
||||
|
||||
if visible_end < total_lines {
|
||||
let down_indicator = Paragraph::new("▼").style(indicator_style);
|
||||
frame.render_widget(
|
||||
down_indicator,
|
||||
Rect::new(indicator_x, padded.y + padded.height.saturating_sub(1), 1, 1),
|
||||
);
|
||||
}
|
||||
render_scroll_indicators(
|
||||
frame,
|
||||
padded,
|
||||
scroll_offset,
|
||||
visible_end - scroll_offset,
|
||||
total_lines,
|
||||
theme.ui.text_dim,
|
||||
IndicatorAlign::Right,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_section_header(title: &str, theme: &theme::ThemeColors) -> Line<'static> {
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::engine::SequencerSnapshot;
|
||||
use crate::model::{MAX_BANKS, MAX_PATTERNS};
|
||||
use crate::state::PatternsColumn;
|
||||
use crate::theme;
|
||||
use crate::widgets::{render_scroll_indicators, IndicatorAlign};
|
||||
|
||||
const MIN_ROW_HEIGHT: u16 = 1;
|
||||
|
||||
@@ -171,21 +172,15 @@ fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area
|
||||
frame.render_widget(para, text_area);
|
||||
}
|
||||
|
||||
// Scroll indicators
|
||||
let indicator_style = Style::new().fg(theme.ui.text_muted);
|
||||
if scroll_offset > 0 {
|
||||
let indicator = Paragraph::new("▲")
|
||||
.style(indicator_style)
|
||||
.alignment(ratatui::layout::Alignment::Center);
|
||||
frame.render_widget(indicator, Rect { height: 1, ..inner });
|
||||
}
|
||||
if scroll_offset + visible_count < MAX_BANKS {
|
||||
let y = inner.y + inner.height.saturating_sub(1);
|
||||
let indicator = Paragraph::new("▼")
|
||||
.style(indicator_style)
|
||||
.alignment(ratatui::layout::Alignment::Center);
|
||||
frame.render_widget(indicator, Rect { y, height: 1, ..inner });
|
||||
}
|
||||
render_scroll_indicators(
|
||||
frame,
|
||||
inner,
|
||||
scroll_offset,
|
||||
visible_count,
|
||||
MAX_BANKS,
|
||||
theme.ui.text_muted,
|
||||
IndicatorAlign::Center,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
||||
@@ -419,19 +414,13 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
|
||||
frame.render_widget(Paragraph::new(Line::from(spans)), text_area);
|
||||
}
|
||||
|
||||
// Scroll indicators
|
||||
let indicator_style = Style::new().fg(theme.ui.text_muted);
|
||||
if scroll_offset > 0 {
|
||||
let indicator = Paragraph::new("▲")
|
||||
.style(indicator_style)
|
||||
.alignment(ratatui::layout::Alignment::Center);
|
||||
frame.render_widget(indicator, Rect { height: 1, ..inner });
|
||||
}
|
||||
if scroll_offset + visible_count < MAX_PATTERNS {
|
||||
let y = inner.y + inner.height.saturating_sub(1);
|
||||
let indicator = Paragraph::new("▼")
|
||||
.style(indicator_style)
|
||||
.alignment(ratatui::layout::Alignment::Center);
|
||||
frame.render_widget(indicator, Rect { y, height: 1, ..inner });
|
||||
}
|
||||
render_scroll_indicators(
|
||||
frame,
|
||||
inner,
|
||||
scroll_offset,
|
||||
visible_count,
|
||||
MAX_PATTERNS,
|
||||
theme.ui.text_muted,
|
||||
IndicatorAlign::Center,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,12 +12,14 @@ use crate::engine::{LinkState, SequencerSnapshot};
|
||||
use crate::model::SourceSpan;
|
||||
use crate::page::Page;
|
||||
use crate::state::{
|
||||
EditorTarget, EuclideanField, FlashKind, Modal, PanelFocus, PatternField, SidePanel,
|
||||
EditorTarget, EuclideanField, FlashKind, Modal, PanelFocus, PatternField, RenameTarget,
|
||||
SidePanel,
|
||||
};
|
||||
use crate::theme;
|
||||
use crate::views::highlight::{self, highlight_line_with_runtime};
|
||||
use crate::widgets::{
|
||||
ConfirmModal, ModalFrame, NavMinimap, NavTile, SampleBrowser, TextInputModal,
|
||||
hint_line, render_props_form, render_search_bar, ConfirmModal, ModalFrame, NavMinimap, NavTile,
|
||||
SampleBrowser, TextInputModal,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -497,42 +499,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
let user_words: HashSet<String> = app.dict.lock().keys().cloned().collect();
|
||||
let inner = match &app.ui.modal {
|
||||
Modal::None => return None,
|
||||
Modal::ConfirmQuit { selected } => {
|
||||
ConfirmModal::new("Confirm", "Quit?", *selected).render_centered(frame, term)
|
||||
}
|
||||
Modal::ConfirmDeleteStep { step, selected, .. } => {
|
||||
ConfirmModal::new("Confirm", &format!("Delete step {}?", step + 1), *selected)
|
||||
Modal::Confirm { action, selected } => {
|
||||
ConfirmModal::new("Confirm", &action.message(), *selected)
|
||||
.render_centered(frame, term)
|
||||
}
|
||||
Modal::ConfirmDeleteSteps {
|
||||
steps, selected, ..
|
||||
} => {
|
||||
let nums: Vec<String> = steps.iter().map(|s| format!("{:02}", s + 1)).collect();
|
||||
let label = format!("Delete steps {}?", nums.join(", "));
|
||||
ConfirmModal::new("Confirm", &label, *selected).render_centered(frame, term)
|
||||
}
|
||||
Modal::ConfirmResetPattern {
|
||||
pattern, selected, ..
|
||||
} => ConfirmModal::new(
|
||||
"Confirm",
|
||||
&format!("Reset pattern {}?", pattern + 1),
|
||||
*selected,
|
||||
)
|
||||
.render_centered(frame, term),
|
||||
Modal::ConfirmResetBank { bank, selected } => {
|
||||
ConfirmModal::new("Confirm", &format!("Reset bank {}?", bank + 1), *selected)
|
||||
.render_centered(frame, term)
|
||||
}
|
||||
Modal::ConfirmResetPatterns {
|
||||
patterns, selected, ..
|
||||
} => {
|
||||
let label = format!("Reset {} patterns?", patterns.len());
|
||||
ConfirmModal::new("Confirm", &label, *selected).render_centered(frame, term)
|
||||
}
|
||||
Modal::ConfirmResetBanks { banks, selected } => {
|
||||
let label = format!("Reset {} banks?", banks.len());
|
||||
ConfirmModal::new("Confirm", &label, *selected).render_centered(frame, term)
|
||||
}
|
||||
Modal::FileBrowser(state) => {
|
||||
use crate::state::file_browser::FileBrowserMode;
|
||||
use crate::widgets::FileBrowserModal;
|
||||
@@ -553,27 +523,14 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
.height(18)
|
||||
.render_centered(frame, term)
|
||||
}
|
||||
Modal::RenameBank { bank, name } => {
|
||||
TextInputModal::new(&format!("Rename Bank {:02}", bank + 1), name)
|
||||
Modal::Rename { target, name } => {
|
||||
let border_color = match target {
|
||||
RenameTarget::Step { .. } => theme.modal.input,
|
||||
_ => theme.modal.rename,
|
||||
};
|
||||
TextInputModal::new(&target.title(), name)
|
||||
.width(40)
|
||||
.border_color(theme.modal.rename)
|
||||
.render_centered(frame, term)
|
||||
}
|
||||
Modal::RenamePattern {
|
||||
bank,
|
||||
pattern,
|
||||
name,
|
||||
} => TextInputModal::new(
|
||||
&format!("Rename B{:02}:P{:02}", bank + 1, pattern + 1),
|
||||
name,
|
||||
)
|
||||
.width(40)
|
||||
.border_color(theme.modal.rename)
|
||||
.render_centered(frame, term),
|
||||
Modal::RenameStep { step, name, .. } => {
|
||||
TextInputModal::new(&format!("Name Step {:02}", step + 1), name)
|
||||
.width(40)
|
||||
.border_color(theme.modal.input)
|
||||
.border_color(border_color)
|
||||
.render_centered(frame, term)
|
||||
}
|
||||
Modal::SetPattern { field, input } => {
|
||||
@@ -794,18 +751,12 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
let hint_area = Rect::new(inner.x, y, inner.width, 1);
|
||||
|
||||
if let Some(sa) = search_area {
|
||||
let style = if app.editor_ctx.editor.search_active() {
|
||||
Style::default().fg(theme.search.active)
|
||||
} else {
|
||||
Style::default().fg(theme.search.inactive)
|
||||
};
|
||||
let cursor = if app.editor_ctx.editor.search_active() {
|
||||
"_"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let text = format!("/{}{}", app.editor_ctx.editor.search_query(), cursor);
|
||||
frame.render_widget(Paragraph::new(Line::from(Span::styled(text, style))), sa);
|
||||
render_search_bar(
|
||||
frame,
|
||||
sa,
|
||||
app.editor_ctx.editor.search_query(),
|
||||
app.editor_ctx.editor.search_active(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(kind) = flash_kind {
|
||||
@@ -821,17 +772,9 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
.editor
|
||||
.render(frame, editor_area, &highlighter);
|
||||
|
||||
let dim = Style::default().fg(theme.hint.text);
|
||||
let key = Style::default().fg(theme.hint.key);
|
||||
|
||||
if app.editor_ctx.editor.search_active() {
|
||||
let hint = Line::from(vec![
|
||||
Span::styled("Enter", key),
|
||||
Span::styled(" confirm ", dim),
|
||||
Span::styled("Esc", key),
|
||||
Span::styled(" cancel", dim),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint).alignment(Alignment::Right), hint_area);
|
||||
let hints = hint_line(&[("Enter", "confirm"), ("Esc", "cancel")]);
|
||||
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
|
||||
} else if app.editor_ctx.show_stack {
|
||||
let stack_text = app
|
||||
.editor_ctx
|
||||
@@ -840,42 +783,29 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
.as_ref()
|
||||
.map(|c| c.result.clone())
|
||||
.unwrap_or_else(|| "Stack: []".to_string());
|
||||
let hint = Line::from(vec![
|
||||
Span::styled("Esc", key),
|
||||
Span::styled(" save ", dim),
|
||||
Span::styled("C-e", key),
|
||||
Span::styled(" eval ", dim),
|
||||
Span::styled("C-s", key),
|
||||
Span::styled(" hide", dim),
|
||||
]);
|
||||
let hints = hint_line(&[("Esc", "save"), ("C-e", "eval"), ("C-s", "hide")]);
|
||||
let [hint_left, stack_right] = Layout::horizontal([
|
||||
Constraint::Length(hint.width() as u16),
|
||||
Constraint::Length(hints.width() as u16),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.areas(hint_area);
|
||||
frame.render_widget(Paragraph::new(hint), hint_left);
|
||||
frame.render_widget(Paragraph::new(hints), hint_left);
|
||||
let dim = Style::default().fg(theme.hint.text);
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled(stack_text, dim)).alignment(Alignment::Right),
|
||||
stack_right,
|
||||
);
|
||||
} else {
|
||||
let hint = Line::from(vec![
|
||||
Span::styled("Esc", key),
|
||||
Span::styled(" save ", dim),
|
||||
Span::styled("C-e", key),
|
||||
Span::styled(" eval ", dim),
|
||||
Span::styled("C-f", key),
|
||||
Span::styled(" find ", dim),
|
||||
Span::styled("C-b", key),
|
||||
Span::styled(" samples ", dim),
|
||||
Span::styled("C-s", key),
|
||||
Span::styled(" stack ", dim),
|
||||
Span::styled("C-u", key),
|
||||
Span::styled("/", dim),
|
||||
Span::styled("C-r", key),
|
||||
Span::styled(" undo/redo", dim),
|
||||
let hints = hint_line(&[
|
||||
("Esc", "save"),
|
||||
("C-e", "eval"),
|
||||
("C-f", "find"),
|
||||
("C-b", "samples"),
|
||||
("C-s", "stack"),
|
||||
("C-u", "/"),
|
||||
("C-r", "undo/redo"),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint).alignment(Alignment::Right), hint_area);
|
||||
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
|
||||
}
|
||||
|
||||
inner
|
||||
@@ -899,70 +829,20 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
.border_color(theme.modal.input)
|
||||
.render_centered(frame, term);
|
||||
|
||||
let fields = [
|
||||
let speed_label = speed.label();
|
||||
let fields: Vec<(&str, &str, bool)> = vec![
|
||||
("Name", name.as_str(), *field == PatternPropsField::Name),
|
||||
(
|
||||
"Length",
|
||||
length.as_str(),
|
||||
*field == PatternPropsField::Length,
|
||||
),
|
||||
("Speed", &speed.label(), *field == PatternPropsField::Speed),
|
||||
(
|
||||
"Quantization",
|
||||
quantization.label(),
|
||||
*field == PatternPropsField::Quantization,
|
||||
),
|
||||
(
|
||||
"Sync Mode",
|
||||
sync_mode.label(),
|
||||
*field == PatternPropsField::SyncMode,
|
||||
),
|
||||
("Length", length.as_str(), *field == PatternPropsField::Length),
|
||||
("Speed", &speed_label, *field == PatternPropsField::Speed),
|
||||
("Quantization", quantization.label(), *field == PatternPropsField::Quantization),
|
||||
("Sync Mode", sync_mode.label(), *field == PatternPropsField::SyncMode),
|
||||
];
|
||||
|
||||
for (i, (label, value, selected)) in fields.iter().enumerate() {
|
||||
let y = inner.y + i as u16;
|
||||
if y >= inner.y + inner.height {
|
||||
break;
|
||||
}
|
||||
|
||||
let (label_style, value_style) = if *selected {
|
||||
(
|
||||
Style::default()
|
||||
.fg(theme.hint.key)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
Style::default()
|
||||
.fg(theme.ui.text_primary)
|
||||
.bg(theme.ui.surface),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Style::default().fg(theme.ui.text_muted),
|
||||
Style::default().fg(theme.ui.text_primary),
|
||||
)
|
||||
};
|
||||
|
||||
let label_area = Rect::new(inner.x + 1, y, 14, 1);
|
||||
let value_area = Rect::new(inner.x + 16, y, inner.width.saturating_sub(18), 1);
|
||||
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!("{label}:")).style(label_style),
|
||||
label_area,
|
||||
);
|
||||
frame.render_widget(Paragraph::new(*value).style(value_style), value_area);
|
||||
}
|
||||
render_props_form(frame, inner, &fields);
|
||||
|
||||
let hint_area = Rect::new(inner.x, inner.y + inner.height - 1, inner.width, 1);
|
||||
let hint_line = Line::from(vec![
|
||||
Span::styled("↑↓", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" nav ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("←→", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" change ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("Enter", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" save ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("Esc", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" cancel", Style::default().fg(theme.hint.text)),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint_line), hint_area);
|
||||
let hints = hint_line(&[("↑↓", "nav"), ("←→", "change"), ("Enter", "save"), ("Esc", "cancel")]);
|
||||
frame.render_widget(Paragraph::new(hints), hint_area);
|
||||
|
||||
inner
|
||||
}
|
||||
@@ -1024,16 +904,9 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
width: inner.width,
|
||||
height: 1,
|
||||
};
|
||||
let keybind_hint = Line::from(vec![
|
||||
Span::styled("↑↓", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" scroll ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("PgUp/Dn", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" page ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("Esc/?", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" close", Style::default().fg(theme.hint.text)),
|
||||
]);
|
||||
let hints = hint_line(&[("↑↓", "scroll"), ("PgUp/Dn", "page"), ("Esc/?", "close")]);
|
||||
frame.render_widget(
|
||||
Paragraph::new(keybind_hint).alignment(Alignment::Right),
|
||||
Paragraph::new(hints).alignment(Alignment::Right),
|
||||
hint_area,
|
||||
);
|
||||
|
||||
@@ -1056,51 +929,13 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
.border_color(theme.modal.input)
|
||||
.render_centered(frame, term);
|
||||
|
||||
let fields = [
|
||||
(
|
||||
"Pulses",
|
||||
pulses.as_str(),
|
||||
*field == EuclideanField::Pulses,
|
||||
),
|
||||
let fields: Vec<(&str, &str, bool)> = vec![
|
||||
("Pulses", pulses.as_str(), *field == EuclideanField::Pulses),
|
||||
("Steps", steps.as_str(), *field == EuclideanField::Steps),
|
||||
(
|
||||
"Rotation",
|
||||
rotation.as_str(),
|
||||
*field == EuclideanField::Rotation,
|
||||
),
|
||||
("Rotation", rotation.as_str(), *field == EuclideanField::Rotation),
|
||||
];
|
||||
|
||||
for (i, (label, value, selected)) in fields.iter().enumerate() {
|
||||
let row_y = inner.y + i as u16;
|
||||
if row_y >= inner.y + inner.height {
|
||||
break;
|
||||
}
|
||||
|
||||
let (label_style, value_style) = if *selected {
|
||||
(
|
||||
Style::default()
|
||||
.fg(theme.hint.key)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
Style::default()
|
||||
.fg(theme.ui.text_primary)
|
||||
.bg(theme.ui.surface),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Style::default().fg(theme.ui.text_muted),
|
||||
Style::default().fg(theme.ui.text_primary),
|
||||
)
|
||||
};
|
||||
|
||||
let label_area = Rect::new(inner.x + 1, row_y, 14, 1);
|
||||
let value_area = Rect::new(inner.x + 16, row_y, inner.width.saturating_sub(18), 1);
|
||||
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!("{label}:")).style(label_style),
|
||||
label_area,
|
||||
);
|
||||
frame.render_widget(Paragraph::new(*value).style(value_style), value_area);
|
||||
}
|
||||
render_props_form(frame, inner, &fields);
|
||||
|
||||
let preview_y = inner.y + 4;
|
||||
if preview_y < inner.y + inner.height {
|
||||
@@ -1118,17 +953,8 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
}
|
||||
|
||||
let hint_area = Rect::new(inner.x, inner.y + inner.height - 1, inner.width, 1);
|
||||
let hint_line = Line::from(vec![
|
||||
Span::styled("↑↓", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" nav ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("←→", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" adjust ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("Enter", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" apply ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("Esc", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" cancel", Style::default().fg(theme.hint.text)),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint_line), hint_area);
|
||||
let hints = hint_line(&[("↑↓", "nav"), ("←→", "adjust"), ("Enter", "apply"), ("Esc", "cancel")]);
|
||||
frame.render_widget(Paragraph::new(hints), hint_area);
|
||||
|
||||
inner
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user