not working example to canvas crate, improving and fixing now
This commit is contained in:
@@ -29,3 +29,7 @@ tokio-test = "0.4.4"
|
|||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
gui = ["ratatui"]
|
gui = ["ratatui"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "ratatui_demo"
|
||||||
|
path = "examples/ratatui_demo.rs"
|
||||||
|
|||||||
316
canvas/examples/canvas_gui_demo.rs
Normal file
316
canvas/examples/canvas_gui_demo.rs
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
// examples/canvas_gui_demo.rs
|
||||||
|
|
||||||
|
use crossterm::{
|
||||||
|
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||||
|
execute,
|
||||||
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
|
};
|
||||||
|
use ratatui::{
|
||||||
|
backend::{Backend, CrosstermBackend},
|
||||||
|
layout::{Constraint, Direction, Layout},
|
||||||
|
style::{Color, Style},
|
||||||
|
widgets::{Block, Borders, Clear},
|
||||||
|
Frame, Terminal,
|
||||||
|
};
|
||||||
|
use std::{error::Error, io};
|
||||||
|
|
||||||
|
// Import canvas library components
|
||||||
|
use canvas::{
|
||||||
|
canvas::{
|
||||||
|
state::{CanvasState, ActionContext},
|
||||||
|
gui::render_canvas,
|
||||||
|
theme::Theme,
|
||||||
|
},
|
||||||
|
autocomplete::{
|
||||||
|
state::AutocompleteCanvasState,
|
||||||
|
gui::render_autocomplete,
|
||||||
|
types::{AutocompleteState, SuggestionItem},
|
||||||
|
},
|
||||||
|
config::config::CanvasConfig,
|
||||||
|
dispatcher::ActionDispatcher,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example form data structure
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct LoginForm {
|
||||||
|
fields: Vec<String>,
|
||||||
|
field_labels: Vec<String>,
|
||||||
|
current_field: usize,
|
||||||
|
cursor_position: usize,
|
||||||
|
autocomplete_state: AutocompleteState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoginForm {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
fields: vec![
|
||||||
|
String::new(), // username
|
||||||
|
String::new(), // password
|
||||||
|
String::new(), // email
|
||||||
|
],
|
||||||
|
field_labels: vec![
|
||||||
|
"Username".to_string(),
|
||||||
|
"Password".to_string(),
|
||||||
|
"Email".to_string(),
|
||||||
|
],
|
||||||
|
current_field: 0,
|
||||||
|
cursor_position: 0,
|
||||||
|
autocomplete_state: AutocompleteState::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement CanvasState trait for your form
|
||||||
|
impl CanvasState for LoginForm {
|
||||||
|
fn field_count(&self) -> usize {
|
||||||
|
self.fields.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_field(&self) -> usize {
|
||||||
|
self.current_field
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_field(&mut self, field_index: usize) {
|
||||||
|
if field_index < self.fields.len() {
|
||||||
|
self.current_field = field_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_position(&self) -> usize {
|
||||||
|
self.cursor_position
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_position(&mut self, position: usize) {
|
||||||
|
if let Some(field) = self.fields.get(self.current_field) {
|
||||||
|
self.cursor_position = position.min(field.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_value(&self, field_index: usize) -> Option<&str> {
|
||||||
|
self.fields.get(field_index).map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_field_value(&mut self, field_index: usize, value: String) {
|
||||||
|
if let Some(field) = self.fields.get_mut(field_index) {
|
||||||
|
*field = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_label(&self, field_index: usize) -> Option<&str> {
|
||||||
|
self.field_labels.get(field_index).map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_action(&mut self, _action: &str, _context: ActionContext) -> Result<(), Box<dyn Error>> {
|
||||||
|
// Custom action handling can go here
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement autocomplete support
|
||||||
|
impl AutocompleteCanvasState for LoginForm {
|
||||||
|
type SuggestionData = String;
|
||||||
|
|
||||||
|
fn supports_autocomplete(&self, field_index: usize) -> bool {
|
||||||
|
// Only username and email fields support autocomplete
|
||||||
|
field_index == 0 || field_index == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autocomplete_state(&self) -> &AutocompleteState {
|
||||||
|
&self.autocomplete_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autocomplete_state_mut(&mut self) -> &mut AutocompleteState {
|
||||||
|
&mut self.autocomplete_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate_autocomplete(&mut self) {
|
||||||
|
if self.supports_autocomplete(self.current_field) {
|
||||||
|
self.autocomplete_state.activate(self.current_field);
|
||||||
|
|
||||||
|
// Simulate loading suggestions
|
||||||
|
let suggestions = match self.current_field {
|
||||||
|
0 => vec![ // Username suggestions
|
||||||
|
SuggestionItem::simple("admin"),
|
||||||
|
SuggestionItem::simple("user"),
|
||||||
|
SuggestionItem::simple("guest"),
|
||||||
|
],
|
||||||
|
2 => vec![ // Email suggestions
|
||||||
|
SuggestionItem::simple("user@example.com"),
|
||||||
|
SuggestionItem::simple("admin@domain.com"),
|
||||||
|
SuggestionItem::simple("test@test.org"),
|
||||||
|
],
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
self.autocomplete_state.set_suggestions(suggestions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_autocomplete_selection(&mut self) {
|
||||||
|
if let Some(suggestion) = self.autocomplete_state.selected_suggestion() {
|
||||||
|
self.set_field_value(self.current_field, suggestion.insert_value.clone());
|
||||||
|
self.cursor_position = suggestion.insert_value.len();
|
||||||
|
self.autocomplete_state.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple theme implementation
|
||||||
|
struct SimpleTheme;
|
||||||
|
|
||||||
|
impl Theme for SimpleTheme {
|
||||||
|
fn field_style(&self, is_current: bool, _is_highlighted: bool) -> Style {
|
||||||
|
if is_current {
|
||||||
|
Style::default().bg(Color::DarkGray).fg(Color::White)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::Gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_style(&self, is_current: bool) -> Style {
|
||||||
|
if is_current {
|
||||||
|
Style::default().fg(Color::Cyan)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::Blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_style(&self) -> Style {
|
||||||
|
Style::default().bg(Color::White).fg(Color::Black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
form: LoginForm,
|
||||||
|
config: CanvasConfig,
|
||||||
|
dispatcher: ActionDispatcher,
|
||||||
|
theme: SimpleTheme,
|
||||||
|
should_quit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn new() -> Result<Self, Box<dyn Error>> {
|
||||||
|
Ok(App {
|
||||||
|
form: LoginForm::new(),
|
||||||
|
config: CanvasConfig::default(),
|
||||||
|
dispatcher: ActionDispatcher::new(),
|
||||||
|
theme: SimpleTheme,
|
||||||
|
should_quit: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key(&mut self, key: KeyCode) -> Result<(), Box<dyn Error>> {
|
||||||
|
match key {
|
||||||
|
KeyCode::Char('q') | KeyCode::Esc => {
|
||||||
|
self.should_quit = true;
|
||||||
|
}
|
||||||
|
KeyCode::Tab => {
|
||||||
|
// Activate autocomplete on tab
|
||||||
|
self.form.activate_autocomplete();
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
// Apply autocomplete selection or just move to next field
|
||||||
|
if self.form.autocomplete_state().is_ready() {
|
||||||
|
self.form.apply_autocomplete_selection();
|
||||||
|
} else {
|
||||||
|
let next_field = (self.form.current_field() + 1) % self.form.field_count();
|
||||||
|
self.form.set_current_field(next_field);
|
||||||
|
self.form.set_cursor_position(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Use canvas dispatcher for all other keys
|
||||||
|
self.dispatcher.dispatch_key(key, &mut self.form, &self.config)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Setup terminal
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
// Create app and run
|
||||||
|
let mut app = App::new()?;
|
||||||
|
let res = run_app(&mut terminal, &mut app);
|
||||||
|
|
||||||
|
// Restore terminal
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture
|
||||||
|
)?;
|
||||||
|
terminal.show_cursor()?;
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
println!("{err:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<(), Box<dyn Error>> {
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| ui(f, app))?;
|
||||||
|
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
app.handle_key(key.code)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.should_quit {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.margin(1)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Min(10),
|
||||||
|
Constraint::Length(3),
|
||||||
|
])
|
||||||
|
.split(f.size());
|
||||||
|
|
||||||
|
// Header
|
||||||
|
let header_block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Canvas Library - Login Form Demo");
|
||||||
|
f.render_widget(header_block, chunks[0]);
|
||||||
|
|
||||||
|
// Main form area - use canvas GUI rendering
|
||||||
|
let form_area = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.margin(1)
|
||||||
|
.constraints([Constraint::Min(0)])
|
||||||
|
.split(chunks[1])[0];
|
||||||
|
|
||||||
|
// Use canvas library's GUI rendering
|
||||||
|
render_canvas(f, form_area, &app.form, &app.theme);
|
||||||
|
|
||||||
|
// Render autocomplete overlay if active
|
||||||
|
if app.form.autocomplete_state().is_active() {
|
||||||
|
render_autocomplete(f, form_area, &app.form, &app.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer with help
|
||||||
|
let footer_block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Controls");
|
||||||
|
|
||||||
|
let help_text = ratatui::widgets::Paragraph::new(
|
||||||
|
"↑↓ - Navigate fields | ←→ - Move cursor | Tab - Autocomplete | Enter - Select/Next | Esc/q - Quit"
|
||||||
|
)
|
||||||
|
.block(footer_block)
|
||||||
|
.style(Style::default().fg(Color::Gray));
|
||||||
|
|
||||||
|
f.render_widget(help_text, chunks[2]);
|
||||||
|
}
|
||||||
55
canvas/view_docs.sh
Executable file
55
canvas/view_docs.sh
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Enhanced documentation viewer for your canvas library
|
||||||
|
echo "=========================================="
|
||||||
|
echo "CANVAS LIBRARY DOCUMENTATION"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# Function to display module docs with colors
|
||||||
|
show_module() {
|
||||||
|
local module=$1
|
||||||
|
local title=$2
|
||||||
|
|
||||||
|
echo -e "\n\033[1;34m=== $title ===\033[0m"
|
||||||
|
echo -e "\033[33mFiles in $module:\033[0m"
|
||||||
|
find src/$module -name "*.rs" 2>/dev/null | sort
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Show doc comments for this module
|
||||||
|
find src/$module -name "*.rs" 2>/dev/null | while read file; do
|
||||||
|
if grep -q "///" "$file"; then
|
||||||
|
echo -e "\033[32m--- $file ---\033[0m"
|
||||||
|
grep -n "^\s*///" "$file" | sed 's/^\([0-9]*:\)\s*\/\/\/ /\1 /' | head -10
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main modules
|
||||||
|
show_module "canvas" "CANVAS SYSTEM"
|
||||||
|
show_module "autocomplete" "AUTOCOMPLETE SYSTEM"
|
||||||
|
show_module "config" "CONFIGURATION SYSTEM"
|
||||||
|
|
||||||
|
# Show lib.rs and other root files
|
||||||
|
echo -e "\n\033[1;34m=== ROOT DOCUMENTATION ===\033[0m"
|
||||||
|
if [ -f "src/lib.rs" ]; then
|
||||||
|
echo -e "\033[32m--- src/lib.rs ---\033[0m"
|
||||||
|
grep -n "^\s*///" src/lib.rs | sed 's/^\([0-9]*:\)\s*\/\/\/ /\1 /' 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "src/dispatcher.rs" ]; then
|
||||||
|
echo -e "\033[32m--- src/dispatcher.rs ---\033[0m"
|
||||||
|
grep -n "^\s*///" src/dispatcher.rs | sed 's/^\([0-9]*:\)\s*\/\/\/ /\1 /' 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n\033[1;36m=========================================="
|
||||||
|
echo "To view specific module documentation:"
|
||||||
|
echo " ./view_canvas_docs.sh canvas"
|
||||||
|
echo " ./view_canvas_docs.sh autocomplete"
|
||||||
|
echo " ./view_canvas_docs.sh config"
|
||||||
|
echo "==========================================\033[0m"
|
||||||
|
|
||||||
|
# If specific module requested
|
||||||
|
if [ $# -eq 1 ]; then
|
||||||
|
show_module "$1" "$(echo $1 | tr '[:lower:]' '[:upper:]') MODULE DETAILS"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user