fix(tui): 修复 Esc 键在创建和编辑 memo 时的行为
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
# MemosCLI
|
# MemosCLI
|
||||||
|
|
||||||
|
[](https://ratatui.rs/)
|
||||||
|
|
||||||
使用命令行管理自己部署的Memos。
|
使用命令行管理自己部署的Memos。
|
||||||
|
|
||||||
[Memos](https://usememos.com/)
|
[Memos](https://usememos.com/)
|
||||||
141
src/main.rs
141
src/main.rs
@@ -142,6 +142,10 @@ async fn run_tui_mode() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if key.code == KeyCode::Esc && !app.loading {
|
if key.code == KeyCode::Esc && !app.loading {
|
||||||
|
if app.mode == AppMode::CreateMemo && app.is_editing {
|
||||||
|
app.is_editing = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
match app.mode {
|
match app.mode {
|
||||||
AppMode::MainMenu => continue,
|
AppMode::MainMenu => continue,
|
||||||
AppMode::Config => {
|
AppMode::Config => {
|
||||||
@@ -152,6 +156,7 @@ async fn run_tui_mode() -> Result<()> {
|
|||||||
AppMode::CreateMemo => {
|
AppMode::CreateMemo => {
|
||||||
app.input_content.clear();
|
app.input_content.clear();
|
||||||
app.input_visibility = "PRIVATE".to_string();
|
app.input_visibility = "PRIVATE".to_string();
|
||||||
|
app.is_editing = false;
|
||||||
app.mode = AppMode::MainMenu;
|
app.mode = AppMode::MainMenu;
|
||||||
}
|
}
|
||||||
AppMode::ListMemos => {
|
AppMode::ListMemos => {
|
||||||
@@ -160,6 +165,12 @@ async fn run_tui_mode() -> Result<()> {
|
|||||||
AppMode::ViewMemo => {
|
AppMode::ViewMemo => {
|
||||||
app.mode = AppMode::ListMemos;
|
app.mode = AppMode::ListMemos;
|
||||||
}
|
}
|
||||||
|
AppMode::SelectVisibility => {
|
||||||
|
app.mode = AppMode::CreateMemo;
|
||||||
|
}
|
||||||
|
AppMode::Help => {
|
||||||
|
app.mode = AppMode::CreateMemo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
app.message = None;
|
app.message = None;
|
||||||
}
|
}
|
||||||
@@ -211,6 +222,7 @@ async fn run_tui_mode() -> Result<()> {
|
|||||||
if app.config.is_configured() {
|
if app.config.is_configured() {
|
||||||
app.input_content.clear();
|
app.input_content.clear();
|
||||||
app.input_visibility = "PRIVATE".to_string();
|
app.input_visibility = "PRIVATE".to_string();
|
||||||
|
app.is_editing = false;
|
||||||
app.mode = AppMode::CreateMemo;
|
app.mode = AppMode::CreateMemo;
|
||||||
} else {
|
} else {
|
||||||
app.message = Some("Please configure first".to_string());
|
app.message = Some("Please configure first".to_string());
|
||||||
@@ -228,39 +240,6 @@ async fn run_tui_mode() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char('1') => {
|
|
||||||
if app.config.is_configured() {
|
|
||||||
app.loading = true;
|
|
||||||
terminal.draw(|f| tui::render_app(f, &mut app))?;
|
|
||||||
match load_memos(&app).await {
|
|
||||||
Ok(memos) => {
|
|
||||||
app.memos = memos;
|
|
||||||
app.mode = AppMode::ListMemos;
|
|
||||||
app.message = Some("Memos loaded successfully".to_string());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
app.message = Some(format!("Error: {}", e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
app.loading = false;
|
|
||||||
} else {
|
|
||||||
app.message = Some("Please configure first".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char('2') => {
|
|
||||||
if app.config.is_configured() {
|
|
||||||
app.input_content.clear();
|
|
||||||
app.input_visibility = "PRIVATE".to_string();
|
|
||||||
app.mode = AppMode::CreateMemo;
|
|
||||||
} else {
|
|
||||||
app.message = Some("Please configure first".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char('3') => {
|
|
||||||
app.input_base_url = app.config.base_url.clone();
|
|
||||||
app.input_token = app.config.user_token.clone();
|
|
||||||
app.mode = AppMode::Config;
|
|
||||||
}
|
|
||||||
KeyCode::Char('Q') => {
|
KeyCode::Char('Q') => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -277,6 +256,29 @@ async fn run_tui_mode() -> Result<()> {
|
|||||||
handle_list_memos_input(&mut app, &key).await?;
|
handle_list_memos_input(&mut app, &key).await?;
|
||||||
}
|
}
|
||||||
AppMode::ViewMemo => {}
|
AppMode::ViewMemo => {}
|
||||||
|
AppMode::SelectVisibility => {
|
||||||
|
handle_select_visibility_input(&mut app, &key).await?;
|
||||||
|
}
|
||||||
|
AppMode::Help => {
|
||||||
|
// 处理帮助模式下的滚动
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Up => {
|
||||||
|
if app.help_scroll > 0 {
|
||||||
|
app.help_scroll -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
// 帮助信息有 12 行,弹窗内容区域高度为 11 行
|
||||||
|
if app.help_scroll < 12 - 11 { // 12 是帮助信息行数,11 是弹窗内容区域高度
|
||||||
|
app.help_scroll += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// 按下其他键关闭帮助弹窗,返回 CreateMemo 模式
|
||||||
|
app.mode = AppMode::CreateMemo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -332,17 +334,27 @@ async fn handle_config_input(app: &mut App, key: &KeyEvent) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_create_memo_input(app: &mut App, key: &KeyEvent) -> Result<()> {
|
async fn handle_create_memo_input(app: &mut App, key: &KeyEvent) -> Result<()> {
|
||||||
|
if app.is_editing {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Esc => {
|
||||||
app.input_content.push(c);
|
app.is_editing = false;
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
app.input_content.push('\n');
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
app.input_content.pop();
|
app.input_content.pop();
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Char(c) => {
|
||||||
if key.modifiers.contains(event::KeyModifiers::CONTROL) {
|
app.input_content.push(c);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
if app.input_content.is_empty() {
|
if app.input_content.is_empty() {
|
||||||
app.message = Some("Content cannot be empty".to_string());
|
app.message = Some("Content cannot be empty".to_string());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -351,6 +363,10 @@ async fn handle_create_memo_input(app: &mut App, key: &KeyEvent) -> Result<()> {
|
|||||||
let client = app.get_client().await?;
|
let client = app.get_client().await?;
|
||||||
let visibility = if app.input_visibility.to_uppercase() == "PUBLIC" {
|
let visibility = if app.input_visibility.to_uppercase() == "PUBLIC" {
|
||||||
Some("PUBLIC")
|
Some("PUBLIC")
|
||||||
|
} else if app.input_visibility.to_uppercase() == "PROTECTED" {
|
||||||
|
Some("PROTECTED")
|
||||||
|
} else if app.input_visibility.to_uppercase() == "VISIBILITY_UNSPECIFIED" {
|
||||||
|
Some("VISIBILITY_UNSPECIFIED")
|
||||||
} else {
|
} else {
|
||||||
Some("PRIVATE")
|
Some("PRIVATE")
|
||||||
};
|
};
|
||||||
@@ -367,6 +383,15 @@ async fn handle_create_memo_input(app: &mut App, key: &KeyEvent) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
KeyCode::Char('v') | KeyCode::Char('V') => {
|
||||||
|
app.mode = AppMode::SelectVisibility;
|
||||||
|
}
|
||||||
|
KeyCode::Char('i') => {
|
||||||
|
app.is_editing = true;
|
||||||
|
}
|
||||||
|
KeyCode::Char('/') => {
|
||||||
|
app.mode = AppMode::Help;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -418,3 +443,45 @@ async fn handle_list_memos_input(app: &mut App, key: &KeyEvent) -> Result<()> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_select_visibility_input(app: &mut App, key: &KeyEvent) -> Result<()> {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Up => {
|
||||||
|
if let Some(selected) = app.visibility_list_state.selected() {
|
||||||
|
if selected > 0 {
|
||||||
|
app.visibility_list_state.select(Some(selected - 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.visibility_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
if let Some(selected) = app.visibility_list_state.selected() {
|
||||||
|
if selected < 3 { // 4 items, 0-3 index
|
||||||
|
app.visibility_list_state.select(Some(selected + 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.visibility_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let visibility_options = vec![
|
||||||
|
"VISIBILITY_UNSPECIFIED",
|
||||||
|
"PRIVATE",
|
||||||
|
"PROTECTED",
|
||||||
|
"PUBLIC",
|
||||||
|
];
|
||||||
|
if let Some(selected) = app.visibility_list_state.selected() {
|
||||||
|
if selected < visibility_options.len() {
|
||||||
|
app.input_visibility = visibility_options[selected].to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.mode = AppMode::CreateMemo;
|
||||||
|
}
|
||||||
|
KeyCode::Esc => {
|
||||||
|
app.mode = AppMode::CreateMemo;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
167
src/tui.rs
167
src/tui.rs
@@ -5,7 +5,7 @@ use anyhow::Result;
|
|||||||
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
|
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::{Block, Borders, Clear, List, ListDirection, ListItem, ListState, Paragraph};
|
use ratatui::widgets::{Block, Borders, Clear, List, ListDirection, ListItem, ListState, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -18,6 +18,8 @@ pub enum AppMode {
|
|||||||
CreateMemo,
|
CreateMemo,
|
||||||
ListMemos,
|
ListMemos,
|
||||||
ViewMemo,
|
ViewMemo,
|
||||||
|
SelectVisibility,
|
||||||
|
Help,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
@@ -35,12 +37,19 @@ pub struct App {
|
|||||||
client: Arc<Mutex<Option<MemosClient>>>,
|
client: Arc<Mutex<Option<MemosClient>>>,
|
||||||
pub list_selected: Option<usize>,
|
pub list_selected: Option<usize>,
|
||||||
pub list_state: ListState,
|
pub list_state: ListState,
|
||||||
|
pub visibility_list_state: ListState,
|
||||||
|
pub content_scroll: usize,
|
||||||
|
pub help_scroll: usize,
|
||||||
|
pub scrollbar_state: ScrollbarState,
|
||||||
|
pub is_editing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(config: Config) -> Self {
|
pub fn new(config: Config) -> Self {
|
||||||
let mut list_state = ListState::default();
|
let mut list_state = ListState::default();
|
||||||
list_state.select(Some(0));
|
list_state.select(Some(0));
|
||||||
|
let mut visibility_list_state = ListState::default();
|
||||||
|
visibility_list_state.select(Some(0));
|
||||||
Self {
|
Self {
|
||||||
mode: AppMode::MainMenu,
|
mode: AppMode::MainMenu,
|
||||||
config,
|
config,
|
||||||
@@ -56,6 +65,11 @@ impl App {
|
|||||||
client: Arc::new(Mutex::new(None)),
|
client: Arc::new(Mutex::new(None)),
|
||||||
list_selected: None,
|
list_selected: None,
|
||||||
list_state,
|
list_state,
|
||||||
|
visibility_list_state,
|
||||||
|
content_scroll: 0usize,
|
||||||
|
help_scroll: 0usize,
|
||||||
|
scrollbar_state: ScrollbarState::default(),
|
||||||
|
is_editing: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +104,8 @@ pub fn render_app(frame: &mut Frame, app: &mut App) {
|
|||||||
AppMode::CreateMemo => render_create_memo(frame, app),
|
AppMode::CreateMemo => render_create_memo(frame, app),
|
||||||
AppMode::ListMemos => render_list_memos(frame, app),
|
AppMode::ListMemos => render_list_memos(frame, app),
|
||||||
AppMode::ViewMemo => render_view_memo(frame, app),
|
AppMode::ViewMemo => render_view_memo(frame, app),
|
||||||
|
AppMode::SelectVisibility => render_select_visibility(frame, app),
|
||||||
|
AppMode::Help => render_help(frame, app),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(msg) = &app.message {
|
if let Some(msg) = &app.message {
|
||||||
@@ -153,13 +169,15 @@ fn render_main_menu(frame: &mut Frame, app: &mut App) {
|
|||||||
frame.render_widget(title, chunks[0]);
|
frame.render_widget(title, chunks[0]);
|
||||||
|
|
||||||
// 产品版本
|
// 产品版本
|
||||||
let version = Paragraph::new("Version: 1.0.0")
|
let version = Paragraph::new(format!("Version: {}", env!("CARGO_PKG_VERSION")))
|
||||||
.style(Style::default().fg(Color::White))
|
.style(Style::default().fg(Color::White))
|
||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center);
|
||||||
frame.render_widget(version, chunks[1]);
|
frame.render_widget(version, chunks[1]);
|
||||||
|
|
||||||
// 作者及联系方式
|
// 作者及联系方式
|
||||||
let author = Paragraph::new("Author: Your Name <your@email.com>")
|
let authors = env!("CARGO_PKG_AUTHORS");
|
||||||
|
let first_author = authors.split(';').next().unwrap_or(authors);
|
||||||
|
let author = Paragraph::new(format!("Author: {}", first_author))
|
||||||
.style(Style::default().fg(Color::White))
|
.style(Style::default().fg(Color::White))
|
||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center);
|
||||||
frame.render_widget(author, chunks[2]);
|
frame.render_widget(author, chunks[2]);
|
||||||
@@ -264,7 +282,7 @@ fn render_create_memo(frame: &mut Frame, app: &mut App) {
|
|||||||
let title = Paragraph::new("Create Memo")
|
let title = Paragraph::new("Create Memo")
|
||||||
.style(Style::default().fg(Color::Cyan).bold())
|
.style(Style::default().fg(Color::Cyan).bold())
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.block(Block::default().borders(Borders::ALL).title("New Memo"));
|
.block(Block::default().borders(Borders::ALL));
|
||||||
frame.render_widget(title, chunks[0]);
|
frame.render_widget(title, chunks[0]);
|
||||||
|
|
||||||
let visibility_label = Paragraph::new("Visibility (PUBLIC/PRIVATE):")
|
let visibility_label = Paragraph::new("Visibility (PUBLIC/PRIVATE):")
|
||||||
@@ -280,14 +298,26 @@ fn render_create_memo(frame: &mut Frame, app: &mut App) {
|
|||||||
.style(Style::default().fg(Color::White));
|
.style(Style::default().fg(Color::White));
|
||||||
frame.render_widget(content_label, chunks[2]);
|
frame.render_widget(content_label, chunks[2]);
|
||||||
|
|
||||||
|
let content_title = if app.is_editing {
|
||||||
|
"Content [Editing]"
|
||||||
|
} else {
|
||||||
|
"Content"
|
||||||
|
};
|
||||||
let content = Paragraph::new(app.input_content.clone())
|
let content = Paragraph::new(app.input_content.clone())
|
||||||
.style(Style::default().fg(Color::Yellow))
|
.style(Style::default().fg(Color::Yellow))
|
||||||
.block(Block::default().borders(Borders::ALL).title("Content"));
|
.block(Block::default().borders(Borders::ALL).title(content_title))
|
||||||
|
.scroll((app.content_scroll.try_into().unwrap(), 0));
|
||||||
frame.render_widget(content, chunks[2]);
|
frame.render_widget(content, chunks[2]);
|
||||||
|
|
||||||
let help = Paragraph::new("Enter: Submit | Esc: Cancel")
|
let help = if app.is_editing {
|
||||||
|
Paragraph::new("Esc: Exit Edit Mode | Enter: Newline | Type to input")
|
||||||
|
.style(Style::default().fg(Color::Green).bold())
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
} else {
|
||||||
|
Paragraph::new("Enter: Submit | i: Edit Mode | v: Visibility | /: Help")
|
||||||
.style(Style::default().fg(Color::DarkGray))
|
.style(Style::default().fg(Color::DarkGray))
|
||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center)
|
||||||
|
};
|
||||||
frame.render_widget(help, chunks[3]);
|
frame.render_widget(help, chunks[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,3 +415,126 @@ fn render_view_memo(frame: &mut Frame, app: &App) {
|
|||||||
frame.render_widget(help, chunks[3]);
|
frame.render_widget(help, chunks[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_select_visibility(frame: &mut Frame, app: &mut App) {
|
||||||
|
// 创建居中的弹窗区域
|
||||||
|
let width = 45;
|
||||||
|
let height = 12;
|
||||||
|
let x = (frame.area().width / 2).saturating_sub(width / 2);
|
||||||
|
let y = (frame.area().height / 2).saturating_sub(height / 2);
|
||||||
|
let area = Rect::new(x, y, width, height);
|
||||||
|
|
||||||
|
// 清除弹窗区域
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
|
||||||
|
// 渲染弹窗边框
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Select Visibility")
|
||||||
|
.title_alignment(Alignment::Center);
|
||||||
|
frame.render_widget(block, area);
|
||||||
|
|
||||||
|
// 定义 Visibility 选项
|
||||||
|
let visibility_options = vec![
|
||||||
|
"VISIBILITY_UNSPECIFIED",
|
||||||
|
"PRIVATE",
|
||||||
|
"PROTECTED",
|
||||||
|
"PUBLIC",
|
||||||
|
];
|
||||||
|
|
||||||
|
// 创建列表项
|
||||||
|
let list_items: Vec<ListItem> = visibility_options
|
||||||
|
.iter()
|
||||||
|
.map(|option| ListItem::new(*option))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// 创建列表
|
||||||
|
let list = List::new(list_items)
|
||||||
|
.block(Block::default())
|
||||||
|
.highlight_style(Style::default().bg(Color::DarkGray).fg(Color::Cyan).bold())
|
||||||
|
.direction(ListDirection::TopToBottom);
|
||||||
|
|
||||||
|
// 创建列表区域(留出标题栏和边框的空间)
|
||||||
|
let list_area = Rect::new(x + 2, y + 2, width - 4, height - 4);
|
||||||
|
|
||||||
|
// 渲染列表
|
||||||
|
frame.render_stateful_widget(list, list_area, &mut app.visibility_list_state);
|
||||||
|
|
||||||
|
// 渲染帮助信息
|
||||||
|
let help = Paragraph::new("↑↓: Navigate | Enter: Select | Esc: Cancel")
|
||||||
|
.style(Style::default().fg(Color::DarkGray))
|
||||||
|
.alignment(Alignment::Center);
|
||||||
|
let help_area = Rect::new(x, y + height, width, 2);
|
||||||
|
frame.render_widget(help, help_area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_help(frame: &mut Frame, app: &mut App) {
|
||||||
|
// 创建居中的弹窗区域
|
||||||
|
let width = 60;
|
||||||
|
let height = 15;
|
||||||
|
let x = (frame.area().width / 2).saturating_sub(width / 2);
|
||||||
|
let y = (frame.area().height / 2).saturating_sub(height / 2);
|
||||||
|
let area = Rect::new(x, y, width, height);
|
||||||
|
|
||||||
|
// 清除弹窗区域
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
|
||||||
|
// 渲染弹窗边框
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("HELP")
|
||||||
|
.title_alignment(Alignment::Center);
|
||||||
|
frame.render_widget(block, area);
|
||||||
|
|
||||||
|
// 帮助信息
|
||||||
|
let help_lines = vec![
|
||||||
|
Line::from("Input Help:"),
|
||||||
|
Line::from(""),
|
||||||
|
Line::from("- Enter: Submit memo"),
|
||||||
|
Line::from("- Esc: Cancel and return to main menu"),
|
||||||
|
Line::from("- Ctrl+Enter: Insert newline"),
|
||||||
|
Line::from("- v: Change visibility"),
|
||||||
|
Line::from("- /: Show this help"),
|
||||||
|
Line::from(""),
|
||||||
|
Line::from("Visibility Options:"),
|
||||||
|
Line::from("- PRIVATE: Only visible to you"),
|
||||||
|
Line::from("- PUBLIC: Visible to everyone"),
|
||||||
|
Line::from("- PROTECTED: Visible to shared users"),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 创建帮助信息区域(留出标题栏、边框和滚动条的空间)
|
||||||
|
let help_area = Rect::new(x + 2, y + 2, width - 6, height - 4);
|
||||||
|
|
||||||
|
// 创建滚动条区域
|
||||||
|
let scrollbar_area = Rect::new(x + width - 4, y + 2, 1, height - 4);
|
||||||
|
|
||||||
|
// 创建帮助信息段落
|
||||||
|
let help_text = Paragraph::new(help_lines.clone())
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.scroll((app.help_scroll.try_into().unwrap(), 0));
|
||||||
|
|
||||||
|
// 渲染帮助信息
|
||||||
|
frame.render_widget(help_text, help_area);
|
||||||
|
|
||||||
|
// 更新滚动条状态
|
||||||
|
app.scrollbar_state = ScrollbarState::default()
|
||||||
|
.position(app.help_scroll)
|
||||||
|
.content_length(help_lines.len())
|
||||||
|
.viewport_content_length((height - 4) as usize);
|
||||||
|
|
||||||
|
frame.render_stateful_widget(
|
||||||
|
Scrollbar::default()
|
||||||
|
.orientation(ScrollbarOrientation::VerticalRight)
|
||||||
|
.thumb_style(Style::default().bg(Color::DarkGray))
|
||||||
|
.track_style(Style::default().bg(Color::Black)),
|
||||||
|
scrollbar_area,
|
||||||
|
&mut app.scrollbar_state,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 渲染底部提示
|
||||||
|
let hint = Paragraph::new("Press any key to close | ↑↓: Scroll")
|
||||||
|
.style(Style::default().fg(Color::DarkGray))
|
||||||
|
.alignment(Alignment::Center);
|
||||||
|
let hint_area = Rect::new(x, y + height, width, 2);
|
||||||
|
frame.render_widget(hint, hint_area);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user