quick menu
This commit is contained in:
283
bar/modules/quick_menu.py
Normal file
283
bar/modules/quick_menu.py
Normal file
@@ -0,0 +1,283 @@
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.image import Image
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
from gi.repository import Gtk
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class QuickMenuItem(Box):
|
||||
"""Base class for quick menu items"""
|
||||
def __init__(self, title, icon_name=None, **kwargs):
|
||||
super().__init__(
|
||||
orientation="h",
|
||||
spacing=12,
|
||||
name="quick-menu-item",
|
||||
**kwargs
|
||||
)
|
||||
self.set_style("padding: 8px 12px; min-width: 280px;")
|
||||
|
||||
# Icon and title on the left
|
||||
left_box = Box(orientation="h", spacing=8)
|
||||
if icon_name:
|
||||
icon = Image(icon_name=icon_name, icon_size=16)
|
||||
left_box.add(icon)
|
||||
|
||||
self.title_label = Label(title)
|
||||
self.title_label.set_style("font-size: 14px;")
|
||||
left_box.add(self.title_label)
|
||||
|
||||
self.add(left_box)
|
||||
|
||||
# Derived classes can add controls to the right side
|
||||
|
||||
|
||||
class QuickMenuToggle(QuickMenuItem):
|
||||
"""A menu item with a toggle switch"""
|
||||
def __init__(self, title, icon_name=None, active=False, on_toggle=None, **kwargs):
|
||||
super().__init__(title, icon_name, **kwargs)
|
||||
|
||||
# Create a custom toggle using a button with state tracking
|
||||
self._active = active
|
||||
self._on_toggle = on_toggle
|
||||
|
||||
# Create toggle indicator box
|
||||
self.toggle_box = Box(
|
||||
orientation="h",
|
||||
spacing=0
|
||||
)
|
||||
self.toggle_box.set_style("min-width: 44px; min-height: 24px; border-radius: 12px; padding: 2px;")
|
||||
|
||||
# Toggle indicator (circle)
|
||||
self.toggle_indicator = Label("")
|
||||
self.toggle_indicator.set_style("min-width: 20px; min-height: 20px; border-radius: 10px; background: white;")
|
||||
|
||||
self.toggle_box.add(self.toggle_indicator)
|
||||
|
||||
# Make it clickable
|
||||
self.toggle_button = Button(
|
||||
child=self.toggle_box,
|
||||
on_clicked=self._on_click
|
||||
)
|
||||
self.toggle_button.set_style("background: transparent; border: none; padding: 0;")
|
||||
|
||||
# Add spacer to push toggle to the right
|
||||
spacer = Label("", h_expand=True)
|
||||
self.add(spacer)
|
||||
self.add(self.toggle_button)
|
||||
|
||||
# Set initial state
|
||||
self._update_appearance()
|
||||
|
||||
def _on_click(self, button):
|
||||
self._active = not self._active
|
||||
self._update_appearance()
|
||||
if self._on_toggle:
|
||||
self._on_toggle(self._active)
|
||||
|
||||
def _update_appearance(self):
|
||||
if self._active:
|
||||
self.toggle_box.set_style_classes(["toggle-active"])
|
||||
self.toggle_box.set_style(
|
||||
"min-width: 44px; min-height: 24px; border-radius: 12px; padding: 2px; "
|
||||
"transition: all 0.2s;"
|
||||
)
|
||||
self.toggle_indicator.set_style(
|
||||
"min-width: 20px; min-height: 20px; border-radius: 10px; "
|
||||
"background: white; margin-left: 20px; transition: all 0.2s;"
|
||||
)
|
||||
else:
|
||||
self.toggle_box.set_style_classes(["toggle-inactive"])
|
||||
self.toggle_box.set_style(
|
||||
"min-width: 44px; min-height: 24px; border-radius: 12px; padding: 2px; "
|
||||
"transition: all 0.2s;"
|
||||
)
|
||||
self.toggle_indicator.set_style(
|
||||
"min-width: 20px; min-height: 20px; border-radius: 10px; "
|
||||
"background: white; margin-left: 0px; transition: all 0.2s;"
|
||||
)
|
||||
|
||||
def set_active(self, active):
|
||||
self._active = active
|
||||
self._update_appearance()
|
||||
|
||||
def get_active(self):
|
||||
return self._active
|
||||
|
||||
|
||||
class QuickMenuButton(QuickMenuItem):
|
||||
"""A menu item that acts as a button"""
|
||||
def __init__(self, title, icon_name=None, on_click=None, **kwargs):
|
||||
super().__init__(title, icon_name, **kwargs)
|
||||
|
||||
if on_click:
|
||||
# Make the entire item clickable
|
||||
button_overlay = Button(
|
||||
child=Box(), # Empty box as child
|
||||
on_clicked=on_click
|
||||
)
|
||||
button_overlay.set_style("background: transparent; border: none; padding: 0; margin: 0;")
|
||||
|
||||
# Add arrow indicator on the right
|
||||
arrow = Label("›")
|
||||
arrow.set_style("font-size: 18px; opacity: 0.5;")
|
||||
spacer = Label("", h_expand=True)
|
||||
self.add(spacer)
|
||||
self.add(arrow)
|
||||
|
||||
|
||||
class QuickMenuSection(Box):
|
||||
"""A section in the quick menu with optional title"""
|
||||
def __init__(self, title=None, **kwargs):
|
||||
super().__init__(
|
||||
orientation="v",
|
||||
spacing=4,
|
||||
name="quick-menu-section",
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if title:
|
||||
title_label = Label(
|
||||
title,
|
||||
name="section-title"
|
||||
)
|
||||
title_label.set_style("font-size: 12px; opacity: 0.6; padding: 8px 12px 4px 12px; font-weight: bold;")
|
||||
self.add(title_label)
|
||||
|
||||
self.items_box = Box(orientation="v", spacing=2)
|
||||
self.add(self.items_box)
|
||||
|
||||
def add_item(self, item):
|
||||
self.items_box.add(item)
|
||||
|
||||
|
||||
class QuickMenu(Window):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
name="quick-menu",
|
||||
layer="top",
|
||||
anchor="top right",
|
||||
margin="40px 10px 0px 0px",
|
||||
exclusivity="none",
|
||||
visible=False,
|
||||
all_visible=False,
|
||||
style_classes=["popup-window"],
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Main container
|
||||
self.main_box = Box(
|
||||
orientation="v",
|
||||
spacing=8,
|
||||
name="quick-menu-container"
|
||||
)
|
||||
# Remove redundant styling since it's handled in stylix.css
|
||||
pass
|
||||
|
||||
# Title
|
||||
title_box = Box(
|
||||
orientation="h",
|
||||
spacing=8
|
||||
)
|
||||
title_box.set_style("padding: 12px;")
|
||||
title = Label("Quick Menu")
|
||||
title.set_style("font-size: 16px; font-weight: bold;")
|
||||
title_box.add(title)
|
||||
|
||||
self.main_box.add(title_box)
|
||||
# Add a simple divider line
|
||||
divider = Label("")
|
||||
divider.set_style("min-height: 1px; background: rgba(255,255,255,0.1); margin: 0px 12px;")
|
||||
self.main_box.add(divider)
|
||||
|
||||
# Sections container
|
||||
self.sections_container = Box(
|
||||
orientation="v",
|
||||
spacing=8
|
||||
)
|
||||
self.sections_container.set_style("padding: 8px 0px;")
|
||||
self.main_box.add(self.sections_container)
|
||||
|
||||
self.children = self.main_box
|
||||
self.set_size_request(360, -1)
|
||||
|
||||
# Store references to dynamic items
|
||||
self.vinyl_toggle = None
|
||||
self.sections = {}
|
||||
|
||||
def add_section(self, section_id, title=None):
|
||||
"""Add a new section to the menu"""
|
||||
section = QuickMenuSection(title=title)
|
||||
self.sections[section_id] = section
|
||||
self.sections_container.add(section)
|
||||
|
||||
# Add separator before section if not the first
|
||||
if len(self.sections) > 1:
|
||||
separator = Label("")
|
||||
separator.set_style("min-height: 1px; background: rgba(255,255,255,0.1); margin: 4px 12px;")
|
||||
self.sections_container.add(separator)
|
||||
|
||||
return section
|
||||
|
||||
def setup_audio_section(self, vinyl_service=None):
|
||||
"""Setup the audio controls section"""
|
||||
audio_section = self.add_section("audio", None) # No section title since it's the only section
|
||||
|
||||
# Vinyl passthrough toggle
|
||||
if vinyl_service:
|
||||
self.vinyl_toggle = QuickMenuToggle(
|
||||
title="Vinyl Passthrough",
|
||||
icon_name="folder-music-symbolic",
|
||||
active=vinyl_service.active,
|
||||
on_toggle=lambda active: self._on_vinyl_toggle(active, vinyl_service)
|
||||
)
|
||||
audio_section.add_item(self.vinyl_toggle)
|
||||
|
||||
# Store reference to vinyl service
|
||||
self.vinyl_service = vinyl_service
|
||||
|
||||
def _on_vinyl_toggle(self, active, vinyl_service):
|
||||
"""Handle vinyl toggle"""
|
||||
logger.info(f"[QuickMenu] Vinyl toggled: {active}")
|
||||
vinyl_service.active = active
|
||||
|
||||
def setup_system_section(self):
|
||||
"""Setup system controls section"""
|
||||
# Removed for now - can add system controls later
|
||||
pass
|
||||
|
||||
def update_vinyl_state(self, active):
|
||||
"""Update vinyl toggle state from external source"""
|
||||
if self.vinyl_toggle:
|
||||
self.vinyl_toggle.set_active(active)
|
||||
|
||||
|
||||
class QuickMenuOpener(Button):
|
||||
"""Button to open the quick menu"""
|
||||
def __init__(self, icon_name="open-menu-symbolic", **kwargs):
|
||||
super().__init__(
|
||||
name="quick-menu-button",
|
||||
child=Image(icon_name=icon_name, icon_size=16),
|
||||
on_clicked=self.toggle_menu,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
self.menu = QuickMenu()
|
||||
self.menu_visible = False
|
||||
|
||||
def toggle_menu(self, button=None):
|
||||
"""Toggle the quick menu visibility"""
|
||||
if self.menu_visible:
|
||||
logger.info("[QuickMenu] Hiding menu")
|
||||
self.menu.set_visible(False)
|
||||
self.menu_visible = False
|
||||
else:
|
||||
logger.info("[QuickMenu] Showing menu")
|
||||
self.menu.set_visible(True)
|
||||
self.menu.show_all()
|
||||
self.menu_visible = True
|
||||
|
||||
def get_menu(self):
|
||||
"""Get the menu instance for configuration"""
|
||||
return self.menu
|
||||
Reference in New Issue
Block a user