83
|
1 from kivy.uix.relativelayout import RelativeLayout |
|
2 from kivy.uix.stacklayout import StackLayout |
|
3 from kivy.uix.behaviors import ToggleButtonBehavior |
|
4 from kivy.uix.togglebutton import ToggleButton |
|
5 from kivy.lang import Builder |
|
6 import kivy.properties as kp |
|
7 import os |
|
8 |
|
9 from .context_menu import AbstractMenu, AbstractMenuItem, AbstractMenuItemHoverable |
|
10 |
|
11 |
|
12 class AppMenu(StackLayout, AbstractMenu): |
|
13 bounding_box = kp.ObjectProperty(None) |
|
14 |
|
15 def __init__(self, *args, **kwargs): |
|
16 super(AppMenu, self).__init__(*args, **kwargs) |
|
17 self.hovered_menu_item = None |
|
18 |
|
19 def update_height(self): |
|
20 max_height = 0 |
|
21 for widget in self.menu_item_widgets: |
|
22 if widget.height > max_height: |
|
23 max_height = widget.height |
|
24 return max_height |
|
25 |
|
26 def on_children(self, obj, new_children): |
|
27 for w in new_children: |
|
28 # bind events that update app menu height when any of its children resize |
|
29 w.bind(on_size=self.update_height) |
|
30 w.bind(on_height=self.update_height) |
|
31 |
|
32 def get_context_menu_root_parent(self): |
|
33 return self |
|
34 |
|
35 def self_or_submenu_collide_with_point(self, x, y): |
|
36 collide_widget = None |
|
37 |
|
38 # Iterate all siblings and all children |
|
39 for widget in self.menu_item_widgets: |
|
40 widget_pos = widget.to_window(0, 0) |
|
41 if widget.collide_point(x - widget_pos[0], y - widget_pos[1]) and not widget.disabled: |
|
42 if self.hovered_menu_item is None: |
|
43 self.hovered_menu_item = widget |
|
44 |
|
45 if self.hovered_menu_item != widget: |
|
46 self.hovered_menu_item = widget |
|
47 for sibling in widget.siblings: |
|
48 sibling.state = 'normal' |
|
49 |
|
50 if widget.state == 'normal': |
|
51 widget.state = 'down' |
|
52 widget.on_release() |
|
53 |
|
54 for sib in widget.siblings: |
|
55 sib.hovered = False |
|
56 elif widget.get_submenu() is not None and not widget.get_submenu().visible: |
|
57 widget.state = 'normal' |
|
58 |
|
59 return collide_widget |
|
60 |
|
61 def close_all(self): |
|
62 for submenu in [w.get_submenu() for w in self.menu_item_widgets if w.get_submenu() is not None]: |
|
63 submenu.hide() |
|
64 for w in self.menu_item_widgets: |
|
65 w.state = 'normal' |
|
66 |
|
67 def hide_app_menus(self, obj, pos): |
|
68 if not self.collide_point(pos.x, pos.y): |
|
69 for w in [w for w in self.menu_item_widgets if not w.disabled and w.get_submenu().visible]: |
|
70 submenu = w.get_submenu() |
|
71 if submenu.self_or_submenu_collide_with_point(pos.x, pos.y) is None: |
|
72 self.close_all() |
|
73 self._cancel_hover_timer() |
|
74 |
|
75 |
|
76 class AppMenuTextItem(ToggleButton, AbstractMenuItem): |
|
77 label = kp.ObjectProperty(None) |
|
78 text = kp.StringProperty('') |
|
79 font_size = kp.NumericProperty(14) |
|
80 color = kp.ListProperty([1, 1, 1, 1]) |
|
81 |
|
82 def on_release(self): |
|
83 submenu = self.get_submenu() |
|
84 |
|
85 if self.state == 'down': |
|
86 root = self._root_parent |
|
87 submenu.bounding_box_widget = root.bounding_box if root.bounding_box else root.parent |
|
88 |
|
89 submenu.bind(visible=self.on_visible) |
|
90 submenu.show(self.x, self.y - 1) |
|
91 |
|
92 for sibling in self.siblings: |
|
93 if sibling.get_submenu() is not None: |
|
94 sibling.state = 'normal' |
|
95 sibling.get_submenu().hide() |
|
96 |
|
97 self.parent._setup_hover_timer() |
|
98 else: |
|
99 self.parent._cancel_hover_timer() |
|
100 submenu.hide() |
|
101 |
|
102 def on_visible(self, *args): |
|
103 submenu = self.get_submenu() |
|
104 if self.width > submenu.get_max_width(): |
|
105 submenu.width = self.width |
|
106 |
|
107 def _check_submenu(self): |
|
108 super(AppMenuTextItem, self)._check_submenu() |
|
109 self.disabled = (self.get_submenu() is None) |
|
110 |
|
111 # def on_mouse_down(self): |
|
112 # print('on_mouse_down') |
|
113 # return True |
|
114 |
|
115 |
|
116 _path = os.path.dirname(os.path.realpath(__file__)) |
|
117 Builder.load_file(os.path.join(_path, 'app_menu.kv')) |