1616from kivy .graphics import Color , RoundedRectangle , Line , Rectangle
1717from kivy .uix .widget import Widget
1818from kivy .uix .dropdown import DropDown
19+ from kivy .uix .behaviors import FocusBehavior
20+ from kivy .properties import BooleanProperty
21+
1922
2023# Désactiver le mode multitouch par défaut (clic droit qui font des points rouges)
2124Config .set ('input' , 'mouse' , 'mouse,multitouch_on_demand' )
@@ -57,22 +60,74 @@ def on_focus(self, instance, value):
5760 else : # Quand le TextInput perd le focus
5861 self .background_color = (0.7 , 0.7 , 0.7 , 0.6 ) # Opacité 0.6
5962
60- class StyledButton (Button ):
61- def __init__ (self ,opacity = 0.6 , ** kwargs ):
63+
64+ class FocusableForm (BoxLayout ):
65+ def __init__ (self , ** kwargs ):
66+ super ().__init__ (** kwargs )
67+ self .widgets_list = []
68+ Window .bind (on_key_down = self ._on_key_down )
69+
70+ def register_focusable (self , widget ):
71+ self .widgets_list .append (widget )
72+
73+ def _on_key_down (self , window , key , scancode , codepoint , modifiers ):
74+ if key == 9 : # TAB
75+ focused = next ((i for i , w in enumerate (self .widgets_list ) if hasattr (w , "focus" ) and w .focus ), None )
76+ if focused is not None :
77+ self .widgets_list [focused ].focus = False
78+ if 'shift' in modifiers :
79+ next_index = (focused - 1 ) % len (self .widgets_list )
80+ else :
81+ next_index = (focused + 1 ) % len (self .widgets_list )
82+ self .widgets_list [next_index ].focus = True
83+ return True
84+
85+ elif key == 13 : # ENTER
86+ focused_btn = next ((w for w in self .widgets_list if isinstance (w , Button ) and w .focus ), None )
87+ if focused_btn :
88+ focused_btn .trigger_action (duration = 0 )
89+ return True
90+
91+ return False
92+
93+ class HoverBehavior :
94+ hovered = BooleanProperty (False )
95+ border_point = None
96+
97+ def __init__ (self , ** kwargs ):
98+ super ().__init__ (** kwargs )
99+ Window .bind (mouse_pos = self .on_mouse_pos )
100+
101+ def on_mouse_pos (self , * args ):
102+ if not self .get_root_window ():
103+ return # Widget pas affiché
104+ pos = args [1 ]
105+ inside = self .collide_point (* self .to_widget (* pos ))
106+ self .hovered = inside
107+ self .on_hover (inside )
108+
109+ def on_hover (self , hovered ):
110+ pass # À surcharger si besoin
111+
112+
113+ class StyledButton (FocusBehavior , HoverBehavior , Button ):
114+ def __init__ (self , opacity = 0.6 , ** kwargs ):
62115 super ().__init__ (** kwargs )
116+ self .opacity_normal = opacity
117+ self .opacity_focus = 1.0
118+ self .opacity_hover = 0.85
119+
63120 self .background_normal = ''
64- self .background_color = (0 , 0 , 0 , 0 ) # transparent
121+ self .background_color = (0 , 0 , 0 , 0 )
65122
66123 with self .canvas .before :
67- # Fond gris semi-transparent
68- self .bg_color = Color (0.7 , 0.7 , 0.7 , opacity )
124+ self .bg_color = Color (0.7 , 0.7 , 0.7 , self .opacity_normal )
69125 self .bg_rect = RoundedRectangle (pos = self .pos , size = self .size , radius = [30 ])
70-
71- # Bordure noire
72126 self .border_color = Color (0 , 0 , 0 , 1 )
73- self .border_line = Line (width = 1.5 ) # Pas de `rounded_rectangle` ici
127+ self .border_line = Line (width = 1.5 )
74128
75- self .bind (pos = self .update_graphics , size = self .update_graphics )
129+ self .bind (pos = self .update_graphics , size = self .update_graphics ,
130+ focus = self .on_focus , hovered = self .on_hover )
76131
77132 def update_graphics (self , * args ):
78133 self .bg_rect .pos = self .pos
@@ -81,6 +136,20 @@ def update_graphics(self, *args):
81136 self .x , self .y , self .width , self .height , 30
82137 )
83138
139+ def on_focus (self , instance , value ):
140+ self .update_opacity ()
141+
142+ def on_hover (self , * args ):
143+ self .update_opacity ()
144+
145+ def update_opacity (self ):
146+ if self .focus :
147+ self .bg_color .a = self .opacity_focus
148+ elif self .hovered :
149+ self .bg_color .a = self .opacity_hover
150+ else :
151+ self .bg_color .a = self .opacity_normal
152+
84153class RoutineApp (App ):
85154 FILE_PATH = "routinesV3.json" # Définition du chemin du fichier JSON
86155 dictlanguage = {
@@ -287,7 +356,8 @@ def supprimer_routine(self, nom, popup):
287356 self .set_root_content (self .page_accueil ())
288357
289358 def page_ajouter_routine (self ):
290- layout = BoxLayout (orientation = "vertical" , spacing = 10 , padding = 10 )
359+ # Utiliser FocusableForm comme conteneur principal
360+ layout = FocusableForm (orientation = "vertical" , spacing = 10 , padding = 10 )
291361
292362 # Espace vide en haut (10% de l'écran)
293363 layout .add_widget (Widget (size_hint = (1 , 0.1 )))
@@ -306,24 +376,32 @@ def page_ajouter_routine(self):
306376
307377 # Champ de saisie juste en dessous du label
308378 routine_name_input = MyTextInput (size_hint = (1 , None ), height = 40 )
379+ layout .register_focusable (routine_name_input ) # Enregistrer le champ de texte pour qu'il soit focusable
309380 layout .add_widget (routine_name_input )
310381
311382 # Espace vide pour pousser les boutons vers le bas
312- layout .add_widget (Widget ()) # prend tout l'espace restant au milieu
383+ layout .add_widget (Widget ()) # Prend tout l'espace restant au milieu
313384
314385 # Boutons en bas (20% de hauteur)
315386 btn_layout = BoxLayout (size_hint = (1 , 0.2 ), spacing = 10 )
387+
316388 terminer_btn = StyledButton (text = self .dictlanguage [self .current_language ]["add_routine" ][1 ])
389+ layout .register_focusable (terminer_btn ) # Enregistrer le bouton pour qu'il soit focusable
317390 terminer_btn .bind (on_press = lambda * args : self .ajouter_routine (routine_name_input .text ))
391+
318392 annuler_btn = StyledButton (text = self .dictlanguage [self .current_language ]["add_routine" ][2 ])
393+ layout .register_focusable (annuler_btn ) # Enregistrer l'autre bouton pour qu'il soit focusable
319394 annuler_btn .bind (on_press = lambda * args : self .set_root_content (self .page_accueil ()))
395+
320396 btn_layout .add_widget (terminer_btn )
321397 btn_layout .add_widget (annuler_btn )
322398
323399 layout .add_widget (btn_layout )
400+
324401 return layout
325402
326403
404+
327405 def ajouter_routine (self , nom ):
328406 if nom .strip ():
329407 self .routines [nom ] = {"name" : nom , "fonctions" : []}
@@ -510,9 +588,8 @@ def update_routine(self, dt):
510588
511589 def page_modifier_routine (self , nom ):
512590 routine = self .routines [nom ]
513- layout = BoxLayout (orientation = "vertical" , spacing = 5 , padding = [10 , 10 , 10 , 10 ])
591+ layout = FocusableForm (orientation = "vertical" , spacing = 5 , padding = [10 , 10 , 10 , 10 ])
514592
515- # ScrollView pour tout le contenu sauf les boutons
516593 scroll = ScrollView (size_hint = (1 , 0.85 ))
517594 content = BoxLayout (orientation = "vertical" , spacing = 10 , size_hint_y = None )
518595 content .bind (minimum_height = content .setter ("height" ))
@@ -528,9 +605,10 @@ def page_modifier_routine(self, nom):
528605 # Champs avec label + input
529606 def add_field (label_text ):
530607 content .add_widget (Label (text = label_text , size_hint = (1 , None ), height = 25 ))
531- return_input = MyTextInput (size_hint = (1 , None ), height = 40 )
532- content .add_widget (return_input )
533- return return_input
608+ input_widget = MyTextInput (size_hint = (1 , None ), height = 40 )
609+ content .add_widget (input_widget )
610+ layout .register_focusable (input_widget )
611+ return input_widget
534612
535613 exercice_name_input = add_field (self .dictlanguage [self .current_language ]["change_routine" ][1 ])
536614 exercice_duree_input = add_field (self .dictlanguage [self .current_language ]["change_routine" ][2 ])
@@ -552,9 +630,11 @@ def add_field(label_text):
552630 exercice_repos_input .text ,
553631 exercice_unites_input .text
554632 ))
633+ layout .register_focusable (ajouter_btn )
555634
556635 retour_btn = StyledButton (text = self .dictlanguage [self .current_language ]["change_routine" ][7 ], size_hint = (0.5 , None ), height = 50 )
557636 retour_btn .bind (on_press = lambda * args : self .set_root_content (self .page_routine (nom )))
637+ layout .register_focusable (retour_btn )
558638
559639 btn_layout .add_widget (ajouter_btn )
560640 btn_layout .add_widget (retour_btn )
@@ -564,6 +644,7 @@ def add_field(label_text):
564644 return layout
565645
566646
647+
567648 def ajouter_exercice (self , routine_nom , ex_nom , duree , repetitions , repos , unites ):
568649 if ex_nom .strip ():
569650 exercice = {
0 commit comments