diff --git a/src/widgets/request_item.py b/src/widgets/request_item.py index 4f78bd6..d06ea6f 100644 --- a/src/widgets/request_item.py +++ b/src/widgets/request_item.py @@ -62,11 +62,17 @@ class RequestItem(Gtk.Box): self._setup_menu() - # Click gesture for loading (on the whole row, but not on the menu button) + # Click gesture for loading gesture = Gtk.GestureClick.new() gesture.connect('released', self._on_clicked) self.add_controller(gesture) + # Show/hide menu button on hover + motion = Gtk.EventControllerMotion.new() + motion.connect('enter', self._on_hover_enter) + motion.connect('leave', self._on_hover_leave) + self.add_controller(motion) + def _setup_menu(self): actions = Gio.SimpleActionGroup.new() @@ -107,17 +113,31 @@ class RequestItem(Gtk.Box): self.menu_button.set_menu_model(menu) + # Hidden by default; shown on hover via EventControllerMotion. + # When the popover closes, hide the button again. + self.menu_button.set_opacity(0.0) + popover = self.menu_button.get_popover() + if popover: + popover.connect('closed', lambda p: self.menu_button.set_opacity(0.0)) + def _on_move_to_project_activated(self, action, param): self.emit('move-to-project-requested', param.get_string()) def _on_clicked(self, gesture, n_press, x, y): """Load request on row click (but not if the menu button was clicked).""" - # Check if the click was on the menu button — if so, ignore alloc = self.menu_button.get_allocation() if alloc.x <= x <= alloc.x + alloc.width and alloc.y <= y <= alloc.y + alloc.height: return self.emit('load-requested') + def _on_hover_enter(self, controller, x, y): + self.menu_button.set_opacity(1.0) + + def _on_hover_leave(self, controller): + popover = self.menu_button.get_popover() + if not (popover and popover.get_visible()): + self.menu_button.set_opacity(0.0) + def set_can_move_up(self, can: bool): self._move_up_action.set_enabled(can) diff --git a/src/window.py b/src/window.py index 3baeaa1..85e1670 100644 --- a/src/window.py +++ b/src/window.py @@ -224,16 +224,6 @@ class RosterWindow(Adw.ApplicationWindow): min-width: 0; } - /* Request menu button: hidden by default, visible on row hover or when open */ - .request-menu-button { - opacity: 0; - transition: opacity 150ms ease; - } - row:hover .request-menu-button, - .request-menu-button:checked { - opacity: 1; - } - /* Method chips in sidebar request list */ .method-chip { border-radius: 4px; @@ -971,13 +961,16 @@ class RosterWindow(Adw.ApplicationWindow): def _load_projects(self) -> None: """Load and display projects.""" - # Remember which projects are currently expanded + # Remember which projects are currently expanded. + # GtkListBox wraps each child in a GtkListBoxRow, so we call get_child() + # on the row to reach the actual ProjectItem widget. expanded_ids = set() - child = self.projects_listbox.get_first_child() - while child: - if isinstance(child, ProjectItem) and child.expanded: - expanded_ids.add(child.project.id) - child = child.get_next_sibling() + row = self.projects_listbox.get_first_child() + while row: + project_item = row.get_child() + if isinstance(project_item, ProjectItem) and project_item.expanded: + expanded_ids.add(project_item.project.id) + row = row.get_next_sibling() # Clear existing while child := self.projects_listbox.get_first_child():