refactor! : drop subprocess usage and centralize brightness service initialization
This commit is contained in:
21 files changed
+834
-828
No files matched your search
@@ -0,0 +1,3 @@
|
||||
from .main import Dock
|
||||
|
||||
__all__ = ["Dock"]
|
||||
@@ -0,0 +1,72 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from fabric.utils import GLib
|
||||
|
||||
from .constants import ANIM_INTERVAL_MS, IDLE_THRESHOLD, LERP_FACTOR
|
||||
from .layout import DockLayout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .canvas import DockCanvas
|
||||
|
||||
|
||||
class DockAnimator:
|
||||
def __init__(self, canvas: DockCanvas):
|
||||
self._canvas = canvas
|
||||
self._timer_id: Optional[int] = None
|
||||
self._running = False
|
||||
|
||||
def start(self) -> None:
|
||||
if self._running:
|
||||
return
|
||||
self._running = True
|
||||
self._timer_id = GLib.timeout_add(ANIM_INTERVAL_MS, self._tick)
|
||||
|
||||
def stop(self) -> None:
|
||||
self._running = False
|
||||
if self._timer_id is not None:
|
||||
GLib.source_remove(self._timer_id)
|
||||
self._timer_id = None
|
||||
|
||||
def _tick(self) -> bool:
|
||||
if not self._running:
|
||||
return False
|
||||
|
||||
canvas = self._canvas
|
||||
items = canvas.model.items
|
||||
dirty = False
|
||||
|
||||
for item in items:
|
||||
delta = item.target_scale - item.current_scale
|
||||
if abs(delta) > IDLE_THRESHOLD:
|
||||
item.current_scale += delta * LERP_FACTOR
|
||||
dirty = True
|
||||
else:
|
||||
item.current_scale = item.target_scale
|
||||
|
||||
model_w = canvas._canvas_max_width()
|
||||
model_h = canvas._canvas_height()
|
||||
DockLayout.compute(
|
||||
items,
|
||||
canvas._mouse_x,
|
||||
canvas._mouse_y,
|
||||
canvas._base_icon_size(),
|
||||
model_w,
|
||||
model_h,
|
||||
canvas._mouse_inside,
|
||||
)
|
||||
|
||||
if (
|
||||
model_w != canvas._last_requested_width
|
||||
or model_h != canvas._last_requested_height
|
||||
):
|
||||
canvas._last_requested_width = model_w
|
||||
canvas._last_requested_height = model_h
|
||||
canvas._update_size_request()
|
||||
|
||||
if dirty or canvas._needs_redraw:
|
||||
canvas._needs_redraw = False
|
||||
canvas.queue_draw()
|
||||
|
||||
return self._running
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from utils.utils import toml_file
|
||||
|
||||
PINNED_APPS_FILE = toml_file("dock.toml")
|
||||
|
||||
LERP_FACTOR = 0.20
|
||||
IDLE_THRESHOLD = 0.0005
|
||||
ANIM_FPS = 60
|
||||
ANIM_INTERVAL_MS = 1000 // ANIM_FPS
|
||||
|
||||
MAX_SCALE = 2.0
|
||||
MIN_SCALE = 1.0
|
||||
SIGMA_FACTOR = 1.3
|
||||
|
||||
ICON_GAP = 8
|
||||
BG_PADDING_H = 14
|
||||
BG_PADDING_V = 12
|
||||
CANVAS_TOP_PAD = 28
|
||||
INDICATOR_H = 10
|
||||
SEPARATOR_WIDTH = 1
|
||||
SEPARATOR_GAP = 4
|
||||
|
||||
BG_RADIUS = 20.0
|
||||
BG_ALPHA = 0.82
|
||||
BORDER_ALPHA = 0.50
|
||||
BORDER_WIDTH = 1.0
|
||||
|
||||
ICON_CORNER_RADIUS_FACTOR = 0.22
|
||||
|
||||
INDICATOR_RADIUS = 2.5
|
||||
INDICATOR_COLOR = (1.0, 1.0, 1.0, 0.95)
|
||||
|
||||
BADGE_FONT_SIZE = 9.0
|
||||
BADGE_PADDING = 2
|
||||
BADGE_RADIUS = 4.0
|
||||
BADGE_BG = (0.0, 0.0, 0.0, 0.70)
|
||||
BADGE_FG = (1.0, 1.0, 1.0, 1.0)
|
||||
|
||||
SEPARATOR_COLOR = (1.0, 1.0, 1.0, 0.25)
|
||||
@@ -0,0 +1,94 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import TYPE_CHECKING, List, Tuple
|
||||
|
||||
from .constants import (
|
||||
BG_PADDING_H,
|
||||
BG_PADDING_V,
|
||||
ICON_GAP,
|
||||
INDICATOR_H,
|
||||
MAX_SCALE,
|
||||
MIN_SCALE,
|
||||
SIGMA_FACTOR,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .canvas import DockItem
|
||||
|
||||
|
||||
class DockLayout:
|
||||
@staticmethod
|
||||
def compute(
|
||||
items: List[DockItem],
|
||||
mouse_x: float,
|
||||
mouse_y: float,
|
||||
base_icon_size: int,
|
||||
canvas_w: int,
|
||||
canvas_h: int,
|
||||
mouse_inside: bool,
|
||||
) -> None:
|
||||
if not items:
|
||||
return
|
||||
|
||||
sigma = base_icon_size * SIGMA_FACTOR
|
||||
amplitude = MAX_SCALE - MIN_SCALE
|
||||
|
||||
n = len(items)
|
||||
base_w = base_icon_size + ICON_GAP
|
||||
total_base = n * base_icon_size + max(n - 1, 0) * ICON_GAP
|
||||
start_x = (canvas_w - total_base) / 2.0
|
||||
|
||||
for i, item in enumerate(items):
|
||||
est_cx = start_x + i * base_w + base_icon_size / 2.0
|
||||
|
||||
if mouse_inside:
|
||||
dist = abs(est_cx - mouse_x)
|
||||
target = MIN_SCALE + amplitude * math.exp(
|
||||
-(dist * dist) / (2.0 * sigma * sigma)
|
||||
)
|
||||
else:
|
||||
target = MIN_SCALE
|
||||
|
||||
item.target_scale = target
|
||||
|
||||
bg_h = base_icon_size + 2 * BG_PADDING_V
|
||||
bg_y = canvas_h - INDICATOR_H - bg_h
|
||||
baseline_y = bg_y + BG_PADDING_V + base_icon_size
|
||||
|
||||
rendered_widths = [base_icon_size * item.current_scale for item in items]
|
||||
total_w = sum(rendered_widths) + max(n - 1, 0) * ICON_GAP
|
||||
cursor_x = (canvas_w - total_w) / 2.0
|
||||
|
||||
for i, item in enumerate(items):
|
||||
iw = rendered_widths[i]
|
||||
ih = base_icon_size * item.current_scale
|
||||
ix = cursor_x
|
||||
iy = baseline_y - ih
|
||||
item.render_x = ix
|
||||
item.render_y = iy
|
||||
item.render_w = iw
|
||||
item.render_h = ih
|
||||
item.drag_index = i
|
||||
cursor_x += iw + ICON_GAP
|
||||
|
||||
@staticmethod
|
||||
def background_rect(
|
||||
items: List[DockItem],
|
||||
base_icon_size: int,
|
||||
canvas_w: int,
|
||||
canvas_h: int,
|
||||
) -> Tuple[float, float, float, float]:
|
||||
bg_h = base_icon_size + 2 * BG_PADDING_V
|
||||
bg_y = canvas_h - INDICATOR_H - bg_h
|
||||
|
||||
if items:
|
||||
min_x = min(item.render_x for item in items)
|
||||
max_x = max(item.render_x + item.render_w for item in items)
|
||||
bg_x = min_x - BG_PADDING_H
|
||||
bg_w = (max_x - min_x) + 2 * BG_PADDING_H
|
||||
else:
|
||||
bg_x = BG_PADDING_H
|
||||
bg_w = canvas_w - 2 * BG_PADDING_H
|
||||
|
||||
return bg_x, bg_y, bg_w, bg_h
|
||||
@@ -0,0 +1,163 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fabric.utils import GLib, logger, random
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.eventbox import EventBox
|
||||
from fabric.widgets.revealer import Revealer
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
|
||||
from services.config import config, on_config_change
|
||||
from utils.occlusion import check_occlusion
|
||||
|
||||
from .canvas import DockCanvas
|
||||
|
||||
|
||||
class Dock(Window):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
layer="top",
|
||||
anchor="bottom center",
|
||||
exclusivity="none",
|
||||
title="modus-dock",
|
||||
name="dock",
|
||||
)
|
||||
|
||||
screen = self.get_screen()
|
||||
if screen:
|
||||
visual = screen.get_rgba_visual()
|
||||
if visual:
|
||||
self.set_visual(visual)
|
||||
self.set_app_paintable(True)
|
||||
|
||||
self.canvas = DockCanvas(self)
|
||||
|
||||
canvas_box = Box(
|
||||
name="dock-canvas-box",
|
||||
children=[self.canvas],
|
||||
)
|
||||
|
||||
self.revealer = Revealer(
|
||||
child=canvas_box,
|
||||
transition_duration=200,
|
||||
transition_type="slide-up",
|
||||
)
|
||||
|
||||
hover_strip = Box(
|
||||
name="dock-hover-strip",
|
||||
)
|
||||
|
||||
outer_box = Box(
|
||||
name="dock-outer-box",
|
||||
orientation="v",
|
||||
children=[self.revealer, hover_strip],
|
||||
)
|
||||
|
||||
self.children = EventBox(
|
||||
events=["enter-notify", "leave-notify"],
|
||||
child=outer_box,
|
||||
on_enter_notify_event=lambda *_: self.on_hover_enter(),
|
||||
on_leave_notify_event=lambda *_: self.on_hover_leave(),
|
||||
)
|
||||
|
||||
self.dock_height = 100
|
||||
self.is_hovered = False
|
||||
self.hide_ticket = 0
|
||||
self._startup_grace = True
|
||||
|
||||
self.revealer.set_reveal_child(True)
|
||||
self.show_all()
|
||||
|
||||
on_config_change(self._on_config_change)
|
||||
|
||||
if config().get("dock_auto_hide", True):
|
||||
GLib.timeout_add(2000, self._start_occlusion_monitoring)
|
||||
else:
|
||||
self._occlusion_timer_id = None
|
||||
|
||||
def _start_occlusion_monitoring(self) -> bool:
|
||||
self._startup_grace = False
|
||||
self.setup_occlusion_monitoring()
|
||||
return False
|
||||
|
||||
def _on_config_change(self, new_config, old_config) -> None:
|
||||
if config().has_changed("dock_enabled", old_config):
|
||||
self._update_visibility()
|
||||
|
||||
if config().has_changed("dock_icon_size", old_config):
|
||||
self.canvas.update_icon_size()
|
||||
|
||||
if config().has_changed("dock_auto_hide", old_config):
|
||||
if new_config.get("dock_auto_hide", True):
|
||||
self.setup_occlusion_monitoring()
|
||||
else:
|
||||
if hasattr(self, "_occlusion_timer_id") and self._occlusion_timer_id:
|
||||
GLib.source_remove(self._occlusion_timer_id)
|
||||
self._occlusion_timer_id = None
|
||||
self.revealer.set_reveal_child(True)
|
||||
|
||||
if config().has_changed("dock_hide_special_workspace_apps", old_config):
|
||||
self.canvas._rebuild_model()
|
||||
|
||||
def _update_visibility(self) -> None:
|
||||
if config().get("dock_enabled", True):
|
||||
self.show()
|
||||
self.revealer.set_reveal_child(True)
|
||||
else:
|
||||
self.hide()
|
||||
|
||||
def on_hover_enter(self) -> None:
|
||||
self.is_hovered = True
|
||||
self.hide_ticket = random.getrandbits(32)
|
||||
self.revealer.set_reveal_child(True)
|
||||
|
||||
def on_hover_leave(self) -> None:
|
||||
self.is_hovered = False
|
||||
ticket = random.getrandbits(32)
|
||||
self.hide_ticket = ticket
|
||||
|
||||
def delayed_hide(t):
|
||||
if t == self.hide_ticket and not self.is_hovered:
|
||||
if config().get("dock_auto_hide", True):
|
||||
is_occ = config().get(
|
||||
"dock_always_occluded", False
|
||||
) or check_occlusion(("bottom", self.dock_height))
|
||||
if is_occ:
|
||||
self.revealer.set_reveal_child(False)
|
||||
return False
|
||||
|
||||
GLib.timeout_add(500, delayed_hide, ticket)
|
||||
|
||||
def setup_occlusion_monitoring(self) -> None:
|
||||
if hasattr(self, "_occlusion_timer_id") and self._occlusion_timer_id:
|
||||
GLib.source_remove(self._occlusion_timer_id)
|
||||
self._occlusion_timer_id = None
|
||||
|
||||
def check_dock_occlusion() -> bool:
|
||||
try:
|
||||
if config().get("dock_always_occluded", False):
|
||||
is_occ = True
|
||||
else:
|
||||
is_occ = check_occlusion(("bottom", self.dock_height))
|
||||
|
||||
if is_occ and not self.is_hovered and self.revealer.get_reveal_child():
|
||||
self.revealer.set_reveal_child(False)
|
||||
elif not is_occ and not self.revealer.get_reveal_child():
|
||||
self.revealer.set_reveal_child(True)
|
||||
elif is_occ and self.is_hovered:
|
||||
if not self.revealer.get_reveal_child():
|
||||
self.revealer.set_reveal_child(True)
|
||||
except Exception as e:
|
||||
logger.error(f"[Dock] Occlusion check error: {e}")
|
||||
return True
|
||||
|
||||
self._occlusion_timer_id = GLib.timeout_add(300, check_dock_occlusion)
|
||||
|
||||
def destroy(self) -> None:
|
||||
if hasattr(self, "_occlusion_timer_id") and self._occlusion_timer_id:
|
||||
GLib.source_remove(self._occlusion_timer_id)
|
||||
self._occlusion_timer_id = None
|
||||
|
||||
if hasattr(self, "canvas") and self.canvas:
|
||||
self.canvas.destroy()
|
||||
|
||||
super().destroy()
|
||||
Reference in new issue
Block a user