From 1561033fb76a5ddded2c77a70932f1bc8f65d8ae Mon Sep 17 00:00:00 2001 From: "thibault.capt" <thibault.capt@etu.hesge.ch> Date: Tue, 17 Oct 2023 16:57:13 +0200 Subject: [PATCH] create TUI app. Need the SQLite db --- Cargo.toml | 4 +- src/app.rs | 153 +++++++++++++++++++++++++++++++++++++++++++++ src/gui.rs | 70 --------------------- src/main.rs | 14 ++--- src/participant.rs | 16 ----- src/terminal.rs | 55 ---------------- src/training.rs | 50 ++++++++++++--- 7 files changed, 200 insertions(+), 162 deletions(-) create mode 100644 src/app.rs delete mode 100644 src/gui.rs delete mode 100644 src/participant.rs delete mode 100644 src/terminal.rs diff --git a/Cargo.toml b/Cargo.toml index 135ac2a..55a9725 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -termion = "2.0.1" -crossterm = "0.25.0" -cursive = "0.15.0" \ No newline at end of file +cursive = "0.19.0" \ No newline at end of file diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..71b86e9 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,153 @@ +/// Importer le module `Training` depuis le fichier externe +use crate::training::Training; + +/// Importations des bibliothèques standard et de cursive +use std::rc::Rc; +use cursive::Cursive; +use cursive::{ + views::{Button, Dialog, DummyView, EditView, LinearLayout, SelectView, TextView, NamedView, ResizedView}, + traits::*, +}; + +/// Fonction publique pour générer le TUI +pub fn run() { + // Initialisation de Cursive + let mut siv = cursive::default(); + + // Vue des entraînements + let select = SelectView::<Training>::new() + .on_submit(on_submit) // Appelé lors de la soumission de la sélection + .with_name("select") // Nom de la vue + .fixed_size((10, 5)); // Taille fixe de la vue + + // Buttons pour ajouter et supprimer un entraînement + let buttons = LinearLayout::vertical() + .child(Button::new("Add new", add_training)) // Bouton pour ajouter un nouvel entraînement + .child(Button::new("Delete", delete_training)) // Bouton pour supprimer un entraînement + .child(DummyView) // Vue fictive pour ajouter de l'espace + .child(Button::new("Quit", Cursive::quit)); // Bouton pour quitter l'application + + // Ajout de la couche de dialogue principale à Cursive + siv.add_layer(Dialog::around(LinearLayout::horizontal() + .child(select) // Ajout de la vue de sélection + .child(DummyView) // Ajout d'espace vide + .child(buttons)) // Ajout des boutons + .title("Select a training")); // Titre de la couche de dialogue + + // Exécution de la boucle principale de Cursive + siv.run(); +} + +/// Fonction interne pour gérer l'ajout d'un nouvel entraînement +/// +/// # Arguments +/// +/// * `s` - Une référence mutable à Cursive +fn add_training(s: &mut Cursive) { + fn ok(s: &mut Cursive, training: Training) { + // Ajout de l'entraînement à la vue de sélection + s.call_on_name("select", |view: &mut SelectView<Training>| { + view.add_item(training.name.clone(), training.clone()); // Using the cloned_training + }); + + s.pop_layer(); // Fermer la couche de dialogue + } + + // Ajout d'une couche de dialogue pour l'ajout d'un nouvel entraînement + s.add_layer(Dialog::around( + LinearLayout::vertical() + .with( + |list| for item in Training::TRAINING_ITEMS.iter() { + list.add_child(create_label(item)); // Ajout d'un label pour chaque élément de l'entraînement + list.add_child(create_edit_view(item)); // Ajout d'un champ de saisie pour chaque élément + list.add_child(DummyView); // Ajout d'espace vide + }) + .child(DummyView) // Ajout d'espace vide + .child(LinearLayout::horizontal() + .child(Button::new( + "Ok", |s| { // Bouton "Ok" pour valider l'ajout + // Récupération des données saisies par l'utilisateur + let name = get_view_content(s, "name"); + let description = get_view_content(s, "description"); + let date = get_view_content(s, "date"); + let location = get_view_content(s, "location"); + let duration = get_view_content(s, "duration").parse::<i32>().unwrap_or(0); + let participants_str = get_view_content(s, "participants").to_string(); + let participants = participants_str.split(",").map(str::trim).collect(); + + // Vérification si le nom est vide + if name.is_empty() { + s.add_layer(Dialog::info("Name cannot be empty")); // Affichage d'un message d'erreur + } else { + ok(s, Training::new(&name, &description, &date, &location, duration, participants)); // Appel de la fonction `ok` pour gérer l'ajout + } + })) + .child(Button::new("Cancel", |s| { + s.pop_layer(); // Annuler l'ajout et fermer la couche de dialogue + })))) + .title("Enter a new training") // Titre de la couche de dialogue + .scrollable() // Permettre le défilement si le contenu dépasse + ); +} + +/// Récupérer le contenu de la vue +/// +/// # Arguments +/// +/// * `s` - Une référence mutable à Cursive +/// * `name` - Le nom du champ de saisie +fn get_view_content(s: &mut Cursive, name: &str) -> Rc<String> { + s.call_on_name(name, |view: &mut EditView| view.get_content()).unwrap() +} + +/// Créer un label pour un champ de l'entraînement +/// +/// # Arguments +/// +/// * `name` - Le nom du champ +fn create_label(name: &str) -> NamedView<TextView> { + TextView::new(format!("{}:", name)).with_name(format!("{}_label", &name)) +} + +/// Créer un champ de saisie pour un champ de l'entraînement +/// +/// # Arguments +/// +/// * `name` - Le nom du champ +fn create_edit_view(name: &str) -> ResizedView<NamedView<EditView>> { + EditView::new() + .with_name(name) + .fixed_width(18) +} + +/// Supprimer un entraînement +/// +/// # Arguments +/// +/// * `s` - Une référence mutable à Cursive +fn delete_training(s: &mut Cursive) { + let mut select = s.find_name::<SelectView<Training>>("select").unwrap(); // Trouver la vue de sélection + match select.selected_id() { + None => s.add_layer(Dialog::info("No train to remove")), // Afficher un message s'il n'y a pas de formation sélectionnée + Some(focus) => { + select.remove_item(focus); // Supprimer l'entraînement sélectionnée + } + } +} + +/// Appelé lorsque l'utilisateur soumet un entraînement +/// +/// # Arguments +/// +/// * `s` - Une référence mutable à Cursive +/// * `training` - Une référence à la structure Training soumise +fn on_submit(s: &mut Cursive, training: &Training) { + // Afficher les informations de l'entraînement dans une couche de dialogue + s.add_layer(Dialog::text(training.to_string()) + .title(format!("{}'s info", training.name)) // Titre de la couche de dialogue + .button("Quit", |s| { + s.pop_layer(); // Ajouter un bouton pour fermer la couche de dialogue + }) + .scrollable() // Permettre le défilement si le contenu dépasse + ); +} \ No newline at end of file diff --git a/src/gui.rs b/src/gui.rs deleted file mode 100644 index 7878c2b..0000000 --- a/src/gui.rs +++ /dev/null @@ -1,70 +0,0 @@ -use cursive::Cursive; -use cursive::CursiveExt; -use cursive::views::{TextView, Dialog, LinearLayout, Button}; -use crate::training::Training; - -pub struct App { - cursive: Cursive, - training_list: Vec<Training>, -} - -impl App { - pub fn new() -> Self { - let mut cursive = Cursive::default(); - let training_list = Vec::new(); - - // Créez une vue TextView pour afficher la liste des entraînements - let training_view = TextView::new("Liste des entraînements"); - - // Créez un bouton pour lire tous les entraînements - let btn_list = Button::new("Voir", |s| { - // Vous pouvez ajouter votre logique d'ajout ici - s.add_layer(Dialog::info("Fonctionnalité non implémentée")); - }); - - // Créez un bouton pour ajouter un nouveau entraînement - let btn_add = Button::new("Ajouter", |s| { - // Vous pouvez ajouter votre logique d'ajout ici - s.add_layer(Dialog::info("Fonctionnalité non implémentée")); - }); - - // Créez un bouton pour modifier un entraînement - let btn_update = Button::new("Modifier", |s| { - // Vous pouvez ajouter votre logique d'ajout ici - s.add_layer(Dialog::info("Fonctionnalité non implémentée")); - }); - - // Créez un bouton pour supprimer un entraînement - let btn_delete = Button::new("Supprimer", |s| { - // Vous pouvez ajouter votre logique d'ajout ici - s.add_layer(Dialog::info("Fonctionnalité non implémentée")); - }); - - // Créez un bouton pour supprimer un entraînement - let btn_exit = Button::new("Quitter", |s| { - // Vous pouvez ajouter votre logique d'ajout ici - s.quit(); - }); - - // Créez un LinearLayout pour organiser la vue TextView et le bouton - let layout = LinearLayout::vertical() - .child(training_view) - .child(btn_list) - .child(btn_add) - .child(btn_update) - .child(btn_delete) - .child(btn_exit); - - - cursive.add_layer(layout); - - cursive.add_global_callback('q', |s| s.quit()); - - - App { cursive, training_list } - } - - pub fn run(&mut self) { - self.cursive.run(); - } -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dc5bf99..cca2835 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,7 @@ +/// Module de l'application +mod app; +/// Module pour la gestion de l'entraînement mod training; -mod participant; -//mod terminal; -mod gui; - -use gui::App; - fn main() { - let mut app = App::new(); - let _ = app.run(); -} + app::run(); +} \ No newline at end of file diff --git a/src/participant.rs b/src/participant.rs deleted file mode 100644 index 91cf267..0000000 --- a/src/participant.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[derive(Debug)] -pub struct Participant { - pub firstname: String, - pub lastname: String, - pub age: u8 -} - -impl Participant { - pub fn new(firstname: &str, lastname: &str, age: u8) -> Participant { - Participant { - firstname: String::from(firstname), - lastname: String::from(lastname), - age - } - } -} \ No newline at end of file diff --git a/src/terminal.rs b/src/terminal.rs deleted file mode 100644 index fdb241d..0000000 --- a/src/terminal.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::io; -use crate::training::Training; - -use termion::event::Key; -use termion::input::TermRead; -use crate::participant::Participant; - -pub struct App { - training_list: Vec<Training> -} - -impl App { - pub fn new() -> Self { - App { training_list: Vec::new() } - } - - fn insert_training( - &mut self, - name: String, - description: String, - date: String, - lieu: String, - duration: i32, - participants: Vec<Participant>) { - self.training_list.push( - Training::new(name, description, date, lieu, duration, participants) - ); - } - - pub fn run(&mut self) { - loop { - println!("Choisir: "); - println!("'i' -> Insérer"); - println!("'a' -> Afficher"); - println!("'q' -> Quitter"); - println!(); - - if let Some(Ok(event)) = io::stdin().keys().next() { - match event { - Key::Char('i') => self.insert_training( - "Foot".to_string(), - "Entrainment".to_string(), - "22.09.2023".to_string(), - "Bernex".to_string(), - 120, - vec![Participant::new("Thibault", "Capt", 20)] - ), - Key::Char('a') => println!("{:?}", self.training_list), - Key::Esc | Key::Char('q') => break, - _ => println!("Choix incorrecte") - } - } - } - } -} diff --git a/src/training.rs b/src/training.rs index 7054d41..90631e0 100644 --- a/src/training.rs +++ b/src/training.rs @@ -1,23 +1,55 @@ -use crate::participant::Participant; -#[derive(Debug)] +/// Structure représentant un entraînement +#[derive(Clone)] pub struct Training { + /// Nom de l'entraînement pub name: String, + /// Description de l'entraînement pub description: String, + /// Date de l'entraînement pub date: String, + /// Lieu de l'entraînement pub location: String, + /// Durée de l'entraînement en heures pub duration: i32, - pub participants: Vec<Participant> + /// Liste des participants à l'entraînement + pub participants: Vec<String>, } impl Training { + /// Tableau des éléments de l'entraînement + pub const TRAINING_ITEMS: [&'static str; 6] = ["name", "description", "date", "location", "duration", "participants"]; + + /// Crée une nouvelle instance de la structure Training + /// + /// # Arguments + /// + /// * `name` - Le nom de l'entraînement + /// * `description` - La description de l'entraînement + /// * `date` - La date de l'entraînement + /// * `location` - Le lieu de l'entraînement + /// * `duration` - La durée de l'entraînement en heures + /// * `participants` - Les participants à l'entraînement sous forme de vecteur de chaînes de caractères pub fn new( - name: String, - description: String, - date: String, - location: String, + name: &str, + description: &str, + date: &str, + location: &str, duration: i32, - participants: Vec<Participant> + participants: Vec<&str> ) -> Training { - Training { name, description, date, location, duration, participants } + Training { + name: name.to_string(), + description: description.to_string(), + date: date.to_string(), + location: location.to_string(), + duration, + participants: participants.iter().map(ToString::to_string).collect(), + } + } + + /// Convertit la structure Training en une chaîne de caractères formatée + pub fn to_string(&self) -> String { + format!("Name: {}\nDescription: {}\nDate: {}\nLocation: {}\nDuration: {}\nParticipants: {}", + self.name, self.description, self.date, self.location, self.duration.to_string(), self.participants.join(",")) } } \ No newline at end of file -- GitLab