Added all stuff to one stack and fixed seekbar

This commit is contained in:
tr1x_em committed 2025-08-06 14:59:16 +05:30
1 parent 331a704243
commit 6895b346cd
5 files changed
+288 -108

No files matched your search

+174 -45
View File
@@ -50,6 +50,64 @@ def cleanup_old_cache_files():
pass # Ignore all errors in cleanup
class EmbeddedExpandedPlayer(Box):
"""Embedded expanded player widget for use inside control center."""
def __init__(self, control_center, **kwargs):
super().__init__(
orientation="vertical",
h_expand=True,
name="embedded-expanded-player",
**kwargs,
)
self.control_center = control_center
self.mpris_manager = MprisPlayerManager()
# Create back button
self.back_button = Button(
name="back-button",
child=Label(label="← Back"),
on_clicked=self._on_back_clicked,
)
# Create expanded player content
self.player_content = PlayerBoxStack(self.mpris_manager)
self.children = [
Box(
orientation="horizontal",
h_expand=True,
style_classes="menu",
children=[
self.back_button,
Box(h_expand=True), # Spacer
Label(label="Now Playing", style_classes="title"),
Box(h_expand=True), # Spacer
],
),
self.player_content,
]
def _on_back_clicked(self, *_):
"""Handle back button click"""
if self.control_center and hasattr(
self.control_center, "close_expanded_player"
):
self.control_center.close_expanded_player()
def refresh(self):
"""Refresh the player content"""
# This will automatically update as MPRIS players change
pass
def destroy(self):
"""Clean up resources"""
if hasattr(self.player_content, "destroy"):
self.player_content.destroy()
super().destroy()
class PlayerBoxStack(Box):
"""A widget that displays the current player information."""
@@ -187,6 +245,7 @@ class PlayerBoxStack(Box):
v_align="center",
h_align="start",
orientation="v",
h_expand=True,
children=[track_info, controls_box],
)
@@ -406,12 +465,13 @@ class PlayerBox(Box):
self.player_stack = player_stack
self.fallback_cover_path = f"{data.HOME_DIR}/.current.wall"
self.image_size = 120
self.image_size = 100 # Smaller album art
self.icon_size = 15
# State
self.exit = False
self.skipped = False
self._user_seeking = False # Flag to prevent choppy seeking
# Memory management
self.temp_artwork_files = [] # Track temp files for cleanup
@@ -434,16 +494,39 @@ class PlayerBox(Box):
label="No Title",
name="player-title",
justification="left",
max_chars_width=15,
max_chars_width=35,
ellipsization="end",
h_align="start",
h_expand=True,
)
self.track_artist = Label(
label="No Artist",
name="player-artist",
justification="left",
max_chars_width=15,
max_chars_width=35,
ellipsization="end",
h_align="start",
h_expand=True,
visible=True,
)
self.track_album = Label(
label="No Album",
name="player-album",
justification="left",
max_chars_width=35,
ellipsization="end",
h_align="start",
h_expand=True,
visible=True,
)
self.track_artist = Label(
label="No Artist",
name="player-artist",
justification="left",
max_chars_width=25,
ellipsization="end",
h_align="start",
visible=True,
@@ -453,22 +536,27 @@ class PlayerBox(Box):
label="No Album",
name="player-album",
justification="left",
max_chars_width=15,
max_chars_width=25,
ellipsization="end",
h_align="start",
visible=True,
)
# Seek bar should not update automatically during user interaction
self._user_seeking = False
self.seek_bar = Scale(
value=0,
min_value=0,
max_value=100,
increments=(5, 5),
name="seek-bar",
size=2,
increments=(1, 1),
name="apple-seek-bar",
size=1,
h_expand=True,
)
self.seek_bar.connect("value-changed", self._on_scale_value_changed)
self.seek_bar.connect("button-press-event", self._on_seek_start)
self.seek_bar.connect("button-release-event", self._on_seek_end)
self.player.bind("can-seek", "sensitive", self.seek_bar)
self.player.bind_property(
@@ -519,7 +607,6 @@ class PlayerBox(Box):
self.labels_box = Box(
name="labels-box",
orientation="h",
h_expand=True,
children=[
self.position_label,
Box(h_expand=True), # Spacer to push labels to ends
@@ -531,7 +618,7 @@ class PlayerBox(Box):
self.seek_box = Box(
name="seek-box",
orientation="v",
spacing=2,
spacing=1,
children=[
self.seek_bar,
self.labels_box,
@@ -540,10 +627,11 @@ class PlayerBox(Box):
self.track_info = Box(
name="track-info",
spacing=5,
spacing=3,
h_expand=True,
orientation="v",
v_align="start",
h_align="start",
h_align="fill",
children=[
self.track_title,
self.track_artist,
@@ -561,6 +649,8 @@ class PlayerBox(Box):
# Stack switcher buttons box
self.stack_buttons_box = Box(
h_expand=False,
v_expand=True,
name="stack-buttons-box",
spacing=4,
orientation="v",
@@ -571,6 +661,7 @@ class PlayerBox(Box):
self.controls_box = CenterBox(
name="player-controls",
h_expand=True,
center_children=self.button_box,
)
@@ -614,8 +705,9 @@ class PlayerBox(Box):
)
self.player_info_box = Box(
name="player-info-box",
h_expand=True,
v_align="center",
h_align="start",
h_align="fill",
orientation="v",
children=[self.track_info, self.controls_box],
)
@@ -628,32 +720,39 @@ class PlayerBox(Box):
# resize the inner box
self.outer_box = Box(
name="outer-player-box",
h_expand=True,
orientation="horizontal",
children=[
self.image_stack,
self.player_info_box,
Box(
orientation="v",
children=[
Box(
children=Image(
icon_name=self.player.player_name, icon_size=18
),
h_align="end",
h_expand=False,
v_align="start",
style="margin-top: 5px; margin-right: 10px;",
tooltip_text=self.player.player_name, # type: ignore
),
Box(
children=self.stack_buttons_box,
h_align="end",
v_expand=True,
v_align="center",
h_expand=False,
style="margin-right: 15px;",
),
],
),
],
h_align="start",
)
self.overlay_box = Overlay(
child=self.outer_box,
overlays=[
self.inner_box,
self.player_info_box,
self.image_stack,
Box(
children=Image(icon_name=self.player.player_name, icon_size=18),
h_align="end",
v_align="start",
style="margin-top: 5px; margin-right: 10px;",
tooltip_text=self.player.player_name, # type: ignore
),
Box(
children=self.stack_buttons_box,
h_align="end",
v_align="center",
style="margin-right: 15px;",
),
],
)
self.children = [*self.children, self.overlay_box]
self.children = [*self.children, self.outer_box]
# Track signal connections for cleanup
connections = bulk_connect(
@@ -770,7 +869,10 @@ class PlayerBox(Box):
if duration:
self.length_label.set_label(self.length_str(duration))
self.seek_bar.set_range(0, duration)
# Clamp duration to avoid 32-bit integer overflow in the scale widget
max_int32 = 2147483647 # 2^31 - 1
safe_duration = min(max_int32, duration)
self.seek_bar.set_range(0, safe_duration)
invoke_repeater(1000, self._move_seekbar)
@@ -919,20 +1021,47 @@ class PlayerBox(Box):
return None
def _move_seekbar(self, *_):
if self.player is None or self.exit:
return False
if self.player is None or self.exit or self._user_seeking:
return True # Continue the timer but don't update while user is seeking
self.position_label.set_label(self.length_str(self.player.position))
self.seek_bar.set_value(self.player.position)
position = self.player.position
self.position_label.set_label(self.length_str(position))
# Only update seek bar if user is not currently seeking
if not self._user_seeking:
# Clamp position to avoid 32-bit integer overflow
max_int32 = 2147483647 # 2^31 - 1
safe_position = min(max_int32, position) if position else 0
self.seek_bar.set_value(safe_position)
return True
def _on_seek_start(self, widget, event):
"""User started seeking - disable automatic updates"""
self._user_seeking = True
return False
def _on_seek_end(self, widget, event):
"""User finished seeking - re-enable automatic updates"""
self._user_seeking = False
return False
def _on_scale_value_changed(self, scale: Scale):
"""Handle seek bar value changes."""
if self.player and not self.exit:
new_position = scale.get_value()
self.player.position = new_position
self.position_label.set_label(self.length_str(new_position))
"""Handle seek bar value changes - only when user is seeking"""
if self.player and not self.exit and self._user_seeking:
new_position = int(scale.get_value())
# Clamp to 32-bit signed integer range to avoid overflow
max_int32 = 2147483647 # 2^31 - 1
min_int32 = -2147483648 # -2^31
new_position = max(min_int32, min(max_int32, new_position))
try:
self.player.position = new_position
self.position_label.set_label(self.length_str(new_position))
except Exception as e:
# If setting position fails, just update the label
self.position_label.set_label(self.length_str(new_position))
except Exception:
# If setting position fails, just update the label
self.position_label.set_label(self.length_str(new_position))
@cooldown(0.1)
def _on_scale_move(self, scale: Scale, event, pos: int):
+38
View File
@@ -17,6 +17,7 @@ from utils.roam import audio_service, modus_service
from widgets.wayland import WaylandWindow as Window
from modules.controlcenter.player import PlayerBoxStack
from modules.controlcenter.per_app_volume import PerAppVolumeControl
from modules.controlcenter.expanded_player import EmbeddedExpandedPlayer
from services.mpris import MprisPlayerManager
brightness_service = Brightness.get_initial()
@@ -47,6 +48,7 @@ class ModusControlCenter(Window):
self._bluetooth_initialized = False
self._music_initialized = False
self._per_app_volume_initialized = False
self._expanded_player_initialized = False
# Store references for cleanup
self._signal_connections = []
@@ -54,6 +56,7 @@ class ModusControlCenter(Window):
self._bluetooth_man = None
self._music_widget_content = None
self._per_app_volume_widget = None
self._expanded_player_widget = None
self.add_keybinding("Escape", self.hide_controlcenter)
@@ -132,6 +135,7 @@ class ModusControlCenter(Window):
self.has_bluetooth_open = False
self.has_wifi_open = False
self.has_per_app_volume_open = False
self.has_expanded_player_open = False
self.bluetooth_svg = Svg(
name="bluetooth-icon",
@@ -304,11 +308,13 @@ class ModusControlCenter(Window):
self.bluetooth_widgets = None
self.wifi_widgets = None
self.per_app_volume_widgets = None
self.expanded_player_widgets = None
self.center_box = CenterBox(start_children=[self.widgets])
self.bluetooth_center_box = None
self.wifi_center_box = None
self.per_app_volume_center_box = None
self.expanded_player_center_box = None
self.widgets.set_size_request(300, -1)
@@ -427,6 +433,23 @@ class ModusControlCenter(Window):
self.per_app_volume_center_box = CenterBox(start_children=[self.per_app_volume_widgets])
self.per_app_volume_center_box.set_size_request(300, -1)
def _ensure_expanded_player_widgets(self):
"""Lazy load expanded player widgets"""
if self.expanded_player_widgets is None:
if self._expanded_player_widget is None:
self._expanded_player_widget = EmbeddedExpandedPlayer(self)
self.expanded_player_widgets = Box(
orientation="vertical",
h_expand=True,
name="control-center-widgets",
children=[
self._expanded_player_widget,
],
)
self.expanded_player_center_box = CenterBox(start_children=[self.expanded_player_widgets])
self.expanded_player_center_box.set_size_request(300, -1)
def set_dont_disturb(self, *_):
self.focus_mode = not self.focus_mode
modus_service.dont_disturb = self.focus_mode
@@ -521,6 +544,18 @@ class ModusControlCenter(Window):
idle_add(lambda *_: self.set_children(self.center_box))
self.has_per_app_volume_open = False
def open_expanded_player(self, *_):
self._ensure_expanded_player_widgets()
idle_add(lambda *_: self.set_children(self.expanded_player_center_box))
self.has_expanded_player_open = True
# Refresh the player when opening
if self._expanded_player_widget:
self._expanded_player_widget.refresh()
def close_expanded_player(self, *_):
idle_add(lambda *_: self.set_children(self.center_box))
self.has_expanded_player_open = False
def _set_mousecapture(self, visible: bool):
if visible:
# Lazy load music widget when becoming visible
@@ -531,6 +566,7 @@ class ModusControlCenter(Window):
self.close_bluetooth()
self.close_wifi()
self.close_per_app_volume()
self.close_expanded_player()
def volume_changed(
self,
@@ -633,5 +669,7 @@ class ModusControlCenter(Window):
self._music_widget_content.destroy()
if self._per_app_volume_widget:
self._per_app_volume_widget.destroy()
if self._expanded_player_widget:
self._expanded_player_widget.destroy()
super().destroy()
+15 -60
View File
@@ -12,7 +12,6 @@ from fabric.utils import (
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.image import Image
from modules.controlcenter.expanded_player import ExpandedPlayer
from fabric.widgets.label import Label
from fabric.widgets.overlay import Overlay
from fabric.widgets.stack import Stack
@@ -21,7 +20,6 @@ from loguru import logger
from services.mpris import MprisPlayer, MprisPlayerManager
import config.data as data
from widgets.mousecapture import DropDownMouseCapture
CACHE_DIR = f"{data.CACHE_DIR}/media"
@@ -449,11 +447,6 @@ class PlayerBox(Box):
self.control_center = control_center
self.fallback_cover_path = f"{data.HOME_DIR}/.current.wall"
# Create expanded player and mouse capture with weak references to avoid circular refs
self.expanded_player = None
self.ex_mousecapture = None
self._create_expanded_player()
self.image_size = 50
self.icon_size = 15
@@ -658,34 +651,6 @@ class PlayerBox(Box):
)
self._signal_connections.extend(connections)
def _create_expanded_player(self):
"""Create expanded player components with proper cleanup handling."""
try:
self.expanded_player = ExpandedPlayer()
self.ex_mousecapture = DropDownMouseCapture(
layer="top", child_window=self.expanded_player
)
except Exception as e:
logger.warning(f"Failed to create expanded player: {e}")
self.expanded_player = None
self.ex_mousecapture = None
def _on_outer_box_clicked(self, *_):
"""Handle outer box click with proper error handling."""
try:
# Close control center if it exists
if self.control_center and hasattr(
self.control_center, "hide_controlcenter"
):
self.control_center.hide_controlcenter()
# Open expanded player if available
if self.expanded_player and self.ex_mousecapture:
self.expanded_player.set_visible(True)
self.ex_mousecapture.toggle_mousecapture()
except Exception as e:
logger.warning(f"Failed to handle outer box click: {e}")
def destroy(self):
"""Clean up all resources when the widget is destroyed."""
# Cancel any ongoing downloads
@@ -702,17 +667,6 @@ class PlayerBox(Box):
# Clean up temp files
self._cleanup_temp_files()
# Clean up UI components
try:
if self.ex_mousecapture:
self.ex_mousecapture.destroy()
self.ex_mousecapture = None
if self.expanded_player:
self.expanded_player.destroy()
self.expanded_player = None
except Exception as e:
logger.warning(f"Failed to cleanup UI components: {e}")
super().destroy()
def __del__(self):
@@ -723,21 +677,23 @@ class PlayerBox(Box):
pass # Ignore errors during cleanup in destructor
def _on_prev_button_click(self, *_):
"""Handle prev button click: close control center then open expanded player"""
"""Handle prev button click: open expanded player in control center"""
try:
# Close control center first
if self.control_center and hasattr(
self.control_center, "hide_controlcenter"
):
self.control_center.hide_controlcenter()
# Then open expanded player
if self.expanded_player and self.ex_mousecapture:
self.expanded_player.set_visible(True)
self.ex_mousecapture.toggle_mousecapture()
# Open expanded player in control center instead of new window
if self.control_center and hasattr(self.control_center, "open_expanded_player"):
self.control_center.open_expanded_player()
except Exception as e:
logger.warning(f"Failed to handle prev button click: {e}")
def _on_outer_box_clicked(self, *_):
"""Handle outer box click with proper error handling."""
try:
# Open expanded player in control center instead of new window
if self.control_center and hasattr(self.control_center, "open_expanded_player"):
self.control_center.open_expanded_player()
except Exception as e:
logger.warning(f"Failed to handle outer box click: {e}")
def update_buttons(self, player_buttons, show_buttons):
# """Update the stack switcher buttons in this player box"""
pass
@@ -908,6 +864,5 @@ class PlayerBox(Box):
GLib.idle_add(self._update_image, local_arturl)
def close_bluetooth(self, *args):
"""Close Bluetooth control center"""
if self.ex_mousecapture:
self.ex_mousecapture.hide_child_window()
"""Placeholder method for compatibility"""
pass
+58
View File
@@ -496,3 +496,61 @@
color: #007aff;
text-shadow: none;
}
/* Apple-style seek bar for expanded player */
#apple-seek-bar {
background-color: transparent;
margin-top: 8px;
margin-bottom: 4px;
min-height: 20px;
}
#apple-seek-bar trough {
min-width: 28px;
min-height: 3px;
border-radius: 1.5px;
background-color: alpha(#fff, 0.3);
border: none;
box-shadow: inset 0 1px 1px alpha(#000, 0.1);
}
#apple-seek-bar highlight {
background: #007aff;
border-radius: 1.5px;
border: none;
box-shadow: 0 0 2px alpha(#007aff, 0.4);
}
#apple-seek-bar slider {
background: #ffffff;
border: 1px solid alpha(#007aff, 0.8);
border-radius: 50%;
min-width: 12px;
min-height: 12px;
margin: -4.5px;
box-shadow:
0 1px 3px alpha(#000, 0.2),
0 0 0 1px alpha(#fff, 0.8);
transition: all 0.1s ease;
}
#apple-seek-bar slider:hover {
background: #ffffff;
border-color: #007aff;
min-width: 14px;
min-height: 14px;
margin: -5.5px;
box-shadow:
0 2px 6px alpha(#000, 0.25),
0 0 0 2px alpha(#007aff, 0.3);
}
#apple-seek-bar slider:active {
background: #f8f9fa;
min-width: 13px;
min-height: 13px;
margin: -5px;
box-shadow:
0 1px 3px alpha(#000, 0.3),
0 0 0 2px alpha(#007aff, 0.5);
}
+3 -3
View File
@@ -57,7 +57,7 @@
margin-left: 10px;
}
#player-info-box {
margin-left: 130px;
margin-left: 10px;
}
#player-title-c {
@@ -129,13 +129,13 @@
#seek-bar slider {
background-image: none;
background-color: transparent;
padding: 2px;
padding: 0px;
}
#seek-bar scale {
background-color: transparent;
margin-top: 10px;
border-radius: 20px;
border-radius: 10px;
}
#seek-bar trough {