diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..13566b81b018ad684f3a35fee301741b2734c8f4 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..8888326ac0b172ff17439e17817b43165b576a2b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/training-session.iml" filepath="$PROJECT_DIR$/.idea/training-session.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/.idea/training-session.iml b/.idea/training-session.iml new file mode 100644 index 0000000000000000000000000000000000000000..cf84ae4a69877a117dad3f555c9d8ebf05a4fc20 --- /dev/null +++ b/.idea/training-session.iml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="EMPTY_MODULE" version="4"> + <component name="NewModuleRootManager"> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + <excludeFolder url="file://$MODULE_DIR$/target" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 1428f80d1a3d6adba08daebf7d69aa03b51ba2ee..55a9725011bbef1cc34848cbd49d0d3540068ffb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +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 0000000000000000000000000000000000000000..71b86e9b72f12b8409e6aa812fcb12e7a8eb5b6d --- /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/main.rs b/src/main.rs index e7a11a969c037e00a796aafeff6258501ec15e9a..cca283573d2705aa3076280c49831b450131a88b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +/// Module de l'application +mod app; +/// Module pour la gestion de l'entraînement +mod training; fn main() { - println!("Hello, world!"); -} + app::run(); +} \ No newline at end of file diff --git a/src/training.rs b/src/training.rs new file mode 100644 index 0000000000000000000000000000000000000000..90631e0271587766e47b6dc11e4cef4fb392756f --- /dev/null +++ b/src/training.rs @@ -0,0 +1,55 @@ +/// 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, + /// 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: &str, + description: &str, + date: &str, + location: &str, + duration: i32, + participants: Vec<&str> + ) -> Training { + 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