Skip to content

Commit ea6a4a6

Browse files
committed
Add tab gestion and focus on buttons
1 parent 07dcd13 commit ea6a4a6

2 files changed

Lines changed: 100 additions & 18 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ Note:
4343

4444
## ☑️ To do
4545

46-
- Modification of exercices
47-
- Tab to switch
46+
- Modification of exercices (+tab)
47+
- First tab
48+
- Tab on all page ?
4849
- Limit characters
4950
- Return (\n) for display
5051
- Pop up when bad completion of forms

languageversion.py

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
from kivy.graphics import Color, RoundedRectangle, Line, Rectangle
1717
from kivy.uix.widget import Widget
1818
from 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)
2124
Config.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+
84153
class 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

Comments
 (0)