From dbbaa9d8b2d034badfe29c31c1b07bab7c69201f Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sat, 23 Nov 2024 11:12:30 +0545 Subject: [PATCH] update osd --- config/fabric/modules/osd.py | 276 ++++++++++++++++++++++------------- 1 file changed, 173 insertions(+), 103 deletions(-) diff --git a/config/fabric/modules/osd.py b/config/fabric/modules/osd.py index de34ecac..409b4d4c 100644 --- a/config/fabric/modules/osd.py +++ b/config/fabric/modules/osd.py @@ -1,15 +1,44 @@ -import time +from dataclasses import dataclass +from typing import Iterator, List -from fabric import Application from fabric.audio import Audio -from fabric.utils import get_relative_path from fabric.widgets.box import Box -from fabric.widgets.image import Image +from fabric.widgets.label import Label from fabric.widgets.scale import Scale, ScaleMark from fabric.widgets.wayland import WaylandWindow as Window from gi.repository import GLib, GObject from services import Brightness -from snippets import Animator +from snippets import Animator, MaterialIcon + + +@dataclass +class IconMapping: + threshold: float + name: str + + +class BrightnessIcons: + MAPPINGS: List[str] = [ + "brightness_1", + "brightness_2", + "brightness_3", + "brightness_4", + "brightness_5", + "brightness_6", + "brightness_7", + ] + MAX_BRIGHTNESS: float = 100 + RATIO: float = 1.6 + + +class VolumeIcons: + MAPPINGS: List[IconMapping] = [ + IconMapping(101, "sound_detection_loud_sound"), + IconMapping(67, "volume_up"), + IconMapping(34, "volume_down"), + IconMapping(1, "volume_mute"), + IconMapping(0, "volume_off"), + ] class AnimatedScale(Scale): @@ -21,38 +50,36 @@ class AnimatedScale(Scale): min_value=self.min_value, max_value=self.value, tick_widget=self, - notify_value=lambda p, *_: self.set_value(p.value), + notify_value=self._update_scale_value, ) - def animate_value(self, value: float): + def animate_value(self, value: float) -> None: self.animator.pause() self.animator.min_value = self.value self.animator.max_value = value self.animator.play() + def _update_scale_value(self, provider: any, *_) -> None: + self.set_value(provider.value) -class BrightnessOSDContainer(Box): - def __init__(self, **kwargs): + +class BaseOSDContainer(Box): + def __init__(self, icon_name: str, icon_size: str, **kwargs): super().__init__(**kwargs, orientation="h", spacing=12, name="osd-container") - self.brightness_service = Brightness() - self.icon = self._create_icon("display-brightness-symbolic", 28) - self.scale = self._create_brightness_scale() + self.icon = self._create_icon(icon_name, icon_size) + self.scale = self._create_scale() + self._setup_layout() + def _setup_layout(self) -> None: self.add(self.icon) self.add(self.scale) - self.update_brightness() - self.scale.connect("value-changed", lambda *_: self.update_brightness()) - self.brightness_service.connect("screen", self.on_brightness_changed) + def _create_icon(self, icon_name: str, icon_size: str) -> Label: + return MaterialIcon(icon_name=icon_name, size=icon_size) - def _create_icon(self, icon_name: str, pixel_size: int) -> Image: - icon = Image(icon_name=icon_name) - icon.set_pixel_size(pixel_size) - return icon - - def _create_brightness_scale(self) -> AnimatedScale: + def _create_scale(self) -> AnimatedScale: return AnimatedScale( - marks=(ScaleMark(value=i) for i in range(1, 100, 10)), + marks=self._create_scale_marks(), value=70, min_value=0, max_value=100, @@ -60,143 +87,186 @@ class BrightnessOSDContainer(Box): orientation="h", ) - def update_brightness(self): + def _create_scale_marks(self) -> Iterator[ScaleMark]: + return (ScaleMark(value=i) for i in range(1, 100, 10)) + + +class BrightnessOSDContainer(BaseOSDContainer): + def __init__(self, **kwargs): + super().__init__(icon_name="brightness_7", icon_size="28px", **kwargs) + self.brightness_service = Brightness() + self._setup_handlers() + self.update_brightness() + + def _setup_handlers(self) -> None: + self.scale.connect("value-changed", lambda *_: self.update_brightness()) + self.brightness_service.connect("screen", self.on_brightness_changed) + + def update_brightness(self) -> None: current_brightness = self.brightness_service.screen_brightness if current_brightness != 0: - normalized_brightness = ( - current_brightness / self.brightness_service.max_screen - ) * 101 - self.scale.animate_value(normalized_brightness) + normalized_brightness = self._normalize_brightness(current_brightness) + self._update_ui(normalized_brightness) - def on_brightness_changed(self, sender, value, *args): - normalized_brightness = (value / self.brightness_service.max_screen) * 101 - self.scale.animate_value(normalized_brightness) + def on_brightness_changed(self, _sender: any, value: float, *_args) -> None: + normalized_brightness = self._normalize_brightness(value) + self._update_ui(normalized_brightness) + + def _normalize_brightness(self, brightness: float) -> float: + return (brightness / self.brightness_service.max_screen) * 100 + + def _update_ui(self, brightness: float) -> None: + self.scale.animate_value(brightness) + self.update_icon(brightness) + + def update_icon(self, brightness_percentage: float) -> None: + icon_name = self._calculate_icon_name(brightness_percentage) + self.icon.set_label(icon_name) + + def _calculate_icon_name(self, brightness_percentage: float) -> str: + thresholds = [ + BrightnessIcons.MAX_BRIGHTNESS + / (BrightnessIcons.RATIO ** (len(BrightnessIcons.MAPPINGS) - i - 1)) + for i in range(len(BrightnessIcons.MAPPINGS)) + ] + + for i, threshold in enumerate(thresholds): + if brightness_percentage < threshold: + return BrightnessIcons.MAPPINGS[i] + + return BrightnessIcons.MAPPINGS[-1] -class AudioOSDContainer(Box): +class AudioOSDContainer(BaseOSDContainer): __gsignals__ = { "volume-changed": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ()), } def __init__(self, **kwargs): - super().__init__(**kwargs, orientation="h", spacing=13, name="osd-container") + super().__init__(icon_name="volume_up", icon_size="29px", **kwargs) self.audio = Audio() - self.icon = self._create_icon("audio-volume-medium-symbolic", 29) - self.scale = self._create_audio_scale() - - self.add(self.icon) - self.add(self.scale) + self._setup_handlers() self.sync_with_audio() + def _setup_handlers(self) -> None: self.scale.connect("value-changed", self.on_volume_changed) self.audio.connect("notify::speaker", self.on_audio_speaker_changed) - def _create_icon(self, icon_name: str, pixel_size: int) -> Image: - icon = Image(icon_name=icon_name) - icon.set_pixel_size(pixel_size) - return icon - - def _create_audio_scale(self) -> AnimatedScale: - return AnimatedScale( - marks=(ScaleMark(value=i) for i in range(1, 100, 10)), - value=70, - min_value=0, - max_value=100, - increments=(1, 1), - orientation="h", - ) - - def sync_with_audio(self): + def sync_with_audio(self) -> None: if self.audio.speaker: volume = round(self.audio.speaker.volume) - self.scale.set_value(volume) - self.update_icon(volume) + self._update_ui(volume) - def on_volume_changed(self, *_): + def on_volume_changed(self, *_) -> None: if self.audio.speaker: volume = self.scale.value if 1 <= volume <= 100: self.audio.speaker.set_volume(volume) - self.update_icon(volume) + self._update_ui(volume) self.emit("volume-changed") - def on_audio_speaker_changed(self, *_): + def on_audio_speaker_changed(self, *_) -> None: if self.audio.speaker: self.audio.speaker.connect("notify::volume", self.update_volume) self.update_volume() - def update_volume(self, *_): + def update_volume(self, *_) -> None: if self.audio.speaker and not self.is_hovered(): volume = round(self.audio.speaker.volume) - self.scale.set_value(volume) - self.update_icon(volume) + self._update_ui(volume) - def update_icon(self, volume): - icon_name = self._get_audio_icon_name(volume) - self.icon.set_from_icon_name(icon_name) + def _update_ui(self, volume: int) -> None: + self.scale.set_value(volume) + self.update_icon(volume) - def _get_audio_icon_name(self, volume: int) -> str: - if volume >= 81: - return "audio-volume-high-symbolic" - elif volume < 51: - return "audio-volume-low-symbolic" - else: - return "audio-volume-medium-symbolic" + def update_icon(self, volume: int) -> None: + icon_name = self._get_volume_icon(volume) + self.icon.set_label(icon_name) + + def _get_volume_icon(self, volume: int) -> str: + for mapping in VolumeIcons.MAPPINGS: + if volume >= mapping.threshold: + return mapping.name class OSDContainer(Window): def __init__(self, **kwargs): self.audio_container = AudioOSDContainer() self.brightness_container = BrightnessOSDContainer() + self.is_initial_display = True + self.last_activity_time = GLib.get_monotonic_time() - self.main_box = Box( + self.main_box = self._create_main_box() + super().__init__( + **kwargs, + layer="overlay", + anchor="bottom", + exclusive=False, + focusable=False, + child=self.main_box, + visible=True, + ) + self._initialize() + + def _create_main_box(self) -> Box: + return Box( orientation="v", spacing=13, children=[self.audio_container, self.brightness_container], ) - super().__init__( - **kwargs, - layer="top", - anchor="bottom", - child=self.main_box, - visible=False, - ) + def _initialize(self) -> None: + self._setup_containers() + self._setup_event_handlers() + self._setup_timers() - self.audio_container.set_visible(False) - self.brightness_container.set_visible(False) - - self.last_activity_time = time.time() + def _setup_containers(self) -> None: + self.audio_container.set_visible(True) + self.brightness_container.set_visible(True) + def _setup_event_handlers(self) -> None: self.audio_container.audio.connect("notify::speaker", self.show_audio) self.brightness_container.brightness_service.connect( "screen", self.show_brightness ) self.audio_container.connect("volume-changed", self.show_audio) - GLib.timeout_add(100, self.check_inactivity) + def _setup_timers(self) -> None: + GLib.timeout_add_seconds(1, self._check_inactivity) + GLib.timeout_add_seconds(1, self._end_initial_display) - def show_audio(self, *_): - self.show_box(self.audio_container) - self.reset_inactivity_timer() + def _end_initial_display(self) -> bool: + self.is_initial_display = False + self._hide_all() + return False - def show_brightness(self, *_): - self.show_box(self.brightness_container) - self.reset_inactivity_timer() - - def show_box(self, box_to_show): - self.audio_container.set_visible(box_to_show == self.audio_container) - self.brightness_container.set_visible(box_to_show == self.brightness_container) - self.set_visible(True) - self.reset_inactivity_timer() - - def start_hide_timer(self): + def _hide_all(self) -> None: self.set_visible(False) + self.audio_container.set_visible(False) + self.brightness_container.set_visible(False) - def reset_inactivity_timer(self): - self.last_activity_time = time.time() + def show_audio(self, *_) -> None: + if not self.is_initial_display: + self._show_container(show_audio=True) - def check_inactivity(self): - if time.time() - self.last_activity_time >= 2: - self.start_hide_timer() + def show_brightness(self, *_) -> None: + if not self.is_initial_display: + self._show_container(show_audio=False) + + def _show_container(self, show_audio: bool) -> None: + self.audio_container.set_visible(show_audio) + self.brightness_container.set_visible(not show_audio) + self.set_visible(True) + self._reset_inactivity_timer() + + def _reset_inactivity_timer(self) -> None: + self.last_activity_time = GLib.get_monotonic_time() + + def _check_inactivity(self) -> bool: + current_time = GLib.get_monotonic_time() + if ( + not self.is_initial_display + and (current_time - self.last_activity_time) / 1_000_000 >= 2 + ): + self.set_visible(False) return True