♻️ refactor(launcher): improve code structure and plugin functionality
This commit is contained in:
31 files changed
+536
-618
No files matched your search
+12
-4
@@ -32,8 +32,12 @@ class CircleImage(Gtk.DrawingArea, Widget):
|
||||
style: str | None = None,
|
||||
tooltip_text: str | None = None,
|
||||
tooltip_markup: str | None = None,
|
||||
h_align: Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None = None,
|
||||
v_align: Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None = None,
|
||||
h_align: (
|
||||
Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None
|
||||
) = None,
|
||||
v_align: (
|
||||
Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None
|
||||
) = None,
|
||||
h_expand: bool = False,
|
||||
v_expand: bool = False,
|
||||
size: int | None = None,
|
||||
@@ -57,7 +61,9 @@ class CircleImage(Gtk.DrawingArea, Widget):
|
||||
)
|
||||
self.size = size if size is not None else 100 # Default size if not provided
|
||||
self._angle = 0
|
||||
self._orig_image: GdkPixbuf.Pixbuf | None = None # Original image for reprocessing
|
||||
self._orig_image: GdkPixbuf.Pixbuf | None = (
|
||||
None # Original image for reprocessing
|
||||
)
|
||||
self._image: GdkPixbuf.Pixbuf | None = None
|
||||
if image_file:
|
||||
pix = GdkPixbuf.Pixbuf.new_from_file(image_file)
|
||||
@@ -79,7 +85,9 @@ class CircleImage(Gtk.DrawingArea, Widget):
|
||||
else:
|
||||
square_size = width
|
||||
if square_size != self.size:
|
||||
pixbuf = pixbuf.scale_simple(self.size, self.size, GdkPixbuf.InterpType.BILINEAR)
|
||||
pixbuf = pixbuf.scale_simple(
|
||||
self.size, self.size, GdkPixbuf.InterpType.BILINEAR
|
||||
)
|
||||
return pixbuf
|
||||
|
||||
def on_draw(self, widget: "CircleImage", ctx: cairo.Context):
|
||||
|
||||
+4
-3
@@ -556,9 +556,10 @@ class Conversion:
|
||||
to_type = self.clean_type(to_type)
|
||||
|
||||
if from_type1 == from_type2:
|
||||
return self.convert(
|
||||
value1 + value2, from_type1, to_type
|
||||
), to_type + addition
|
||||
return (
|
||||
self.convert(value1 + value2, from_type1, to_type),
|
||||
to_type + addition,
|
||||
)
|
||||
else:
|
||||
res = 0
|
||||
res += self.convert(value1, from_type1, to_type)
|
||||
|
||||
@@ -16,7 +16,9 @@ if not os.path.exists(data.CACHE_DIR):
|
||||
|
||||
|
||||
class IconResolver:
|
||||
def __init__(self, default_applicaiton_icon: str = "application-x-executable-symbolic"):
|
||||
def __init__(
|
||||
self, default_applicaiton_icon: str = "application-x-executable-symbolic"
|
||||
):
|
||||
if os.path.exists(ICON_CACHE_FILE):
|
||||
with open(ICON_CACHE_FILE) as f:
|
||||
try:
|
||||
@@ -79,7 +81,9 @@ class IconResolver:
|
||||
data_dir = os.path.join(data_dir, "applications")
|
||||
if os.path.exists(data_dir):
|
||||
files = os.listdir(data_dir)
|
||||
matching = [s for s in files if "".join(app_id.lower().split()) in s.lower()]
|
||||
matching = [
|
||||
s for s in files if "".join(app_id.lower().split()) in s.lower()
|
||||
]
|
||||
if matching:
|
||||
return os.path.join(data_dir, matching[0])
|
||||
for word in list(filter(None, re.split(r"-|\.|_|\s", app_id))):
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
from fabric.widgets.box import Box
|
||||
from utils.wayland import WaylandWindow
|
||||
|
||||
from config.c import c
|
||||
|
||||
import gi # type: ignore
|
||||
gi.require_version("GtkLayerShell", "0.1")
|
||||
|
||||
from gi.repository import Gtk, GtkLayerShell # type: ignore
|
||||
|
||||
|
||||
class PopupWindow(WaylandWindow):
|
||||
def __init__(
|
||||
self,
|
||||
parent: WaylandWindow,
|
||||
pointing_to: Gtk.Widget | None = None,
|
||||
margin: tuple[int, ...] | str = "0px 0px 0px 0px",
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.exclusivity = "none"
|
||||
|
||||
self._parent = parent
|
||||
self._pointing_widget = pointing_to
|
||||
self._base_margin = self.extract_margin(margin)
|
||||
self.margin = self._base_margin.values()
|
||||
|
||||
self.connect("notify::visible", self.do_update_handlers)
|
||||
|
||||
def get_coords_for_widget(self, widget: Gtk.Widget) -> tuple[int, int]:
|
||||
if not ((toplevel := widget.get_toplevel()) and toplevel.is_toplevel()): # type: ignore
|
||||
return 0, 0
|
||||
allocation = widget.get_allocation()
|
||||
x, y = widget.translate_coordinates(toplevel, allocation.x, allocation.y) or (
|
||||
0,
|
||||
0,
|
||||
)
|
||||
return round(x / 2), round(y / 2)
|
||||
|
||||
def set_pointing_to(self, widget: Gtk.Widget | None):
|
||||
if self._pointing_widget:
|
||||
try:
|
||||
self._pointing_widget.disconnect_by_func(self.do_handle_size_allocate)
|
||||
except Exception:
|
||||
pass
|
||||
self._pointing_widget = widget
|
||||
return self.do_update_handlers()
|
||||
|
||||
def do_update_handlers(self, *_):
|
||||
if not self._pointing_widget:
|
||||
return
|
||||
|
||||
if not self.get_visible():
|
||||
try:
|
||||
self._pointing_widget.disconnect_by_func(self.do_handle_size_allocate)
|
||||
self.disconnect_by_func(self.do_handle_size_allocate)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
self._pointing_widget.connect("size-allocate", self.do_handle_size_allocate)
|
||||
self.connect("size-allocate", self.do_handle_size_allocate)
|
||||
|
||||
return self.do_handle_size_allocate()
|
||||
|
||||
def do_handle_size_allocate(self, *_):
|
||||
return self.do_reposition(self.do_calculate_edges())
|
||||
|
||||
def do_calculate_edges(self):
|
||||
move_axe = "x"
|
||||
parent_anchor = self._parent.anchor
|
||||
|
||||
if len(parent_anchor) != 3:
|
||||
return move_axe
|
||||
|
||||
if (
|
||||
GtkLayerShell.Edge.LEFT in parent_anchor
|
||||
and GtkLayerShell.Edge.RIGHT in parent_anchor
|
||||
):
|
||||
# horizontal -> move on x-axies
|
||||
move_axe = "x"
|
||||
if GtkLayerShell.Edge.TOP in parent_anchor:
|
||||
self.anchor = "left top"
|
||||
else:
|
||||
self.anchor = "left bottom"
|
||||
elif (
|
||||
GtkLayerShell.Edge.TOP in parent_anchor
|
||||
and GtkLayerShell.Edge.BOTTOM in parent_anchor
|
||||
):
|
||||
# vertical -> move on y-axies
|
||||
move_axe = "y"
|
||||
if GtkLayerShell.Edge.RIGHT in parent_anchor:
|
||||
self.anchor = "top right"
|
||||
else:
|
||||
self.anchor = "top left"
|
||||
|
||||
return move_axe
|
||||
|
||||
def do_reposition(self, move_axe: str):
|
||||
parent_margin = self._parent.margin
|
||||
parent_x_margin, parent_y_margin = parent_margin[0], parent_margin[3]
|
||||
|
||||
height = self.get_allocated_height()
|
||||
width = self.get_allocated_width()
|
||||
|
||||
if self._pointing_widget:
|
||||
coords = self.get_coords_for_widget(self._pointing_widget)
|
||||
if coords[0] + width > int(c.get_rule("Display.resolution").split("x")[0]):
|
||||
coords_centered = (
|
||||
round(coords[0] - (width - self._pointing_widget.get_allocated_width())),
|
||||
round(coords[1] + self._pointing_widget.get_allocated_height() / 2),
|
||||
)
|
||||
else:
|
||||
coords_centered = (
|
||||
round(coords[0]),
|
||||
round(coords[1] + self._pointing_widget.get_allocated_height() / 2),
|
||||
)
|
||||
else:
|
||||
coords_centered = (
|
||||
round(self._parent.get_allocated_width() / 2),
|
||||
round(self._parent.get_allocated_height() / 2),
|
||||
)
|
||||
self.margin = tuple(
|
||||
a + b
|
||||
for a, b in zip(
|
||||
(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
round((parent_x_margin + coords_centered[0])),
|
||||
)
|
||||
if move_axe == "x"
|
||||
else (
|
||||
round((parent_y_margin + coords_centered[1]) - (height / 2)),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
),
|
||||
self._base_margin.values(),
|
||||
)
|
||||
)
|
||||
+31
-21
@@ -132,13 +132,15 @@ class WaylandWindow(Window):
|
||||
@keyboard_mode.setter
|
||||
def keyboard_mode(
|
||||
self,
|
||||
value: Literal[
|
||||
"none",
|
||||
"exclusive",
|
||||
"on-demand",
|
||||
"entry-number",
|
||||
]
|
||||
| KeyboardMode,
|
||||
value: (
|
||||
Literal[
|
||||
"none",
|
||||
"exclusive",
|
||||
"on-demand",
|
||||
"entry-number",
|
||||
]
|
||||
| KeyboardMode
|
||||
),
|
||||
):
|
||||
self._keyboard_mode = get_enum_member(
|
||||
KeyboardMode, value, default=KeyboardMode.NONE
|
||||
@@ -226,10 +228,12 @@ class WaylandWindow(Window):
|
||||
layer: Literal["background", "bottom", "top", "overlay"] | Layer = Layer.TOP,
|
||||
anchor: str = "",
|
||||
margin: str | Iterable[int] = "0px 0px 0px 0px",
|
||||
exclusivity: Literal["auto", "normal", "none"]
|
||||
| WaylandWindowExclusivity = WaylandWindowExclusivity.NONE,
|
||||
keyboard_mode: Literal["none", "exclusive", "on-demand"]
|
||||
| KeyboardMode = KeyboardMode.NONE,
|
||||
exclusivity: (
|
||||
Literal["auto", "normal", "none"] | WaylandWindowExclusivity
|
||||
) = WaylandWindowExclusivity.NONE,
|
||||
keyboard_mode: (
|
||||
Literal["none", "exclusive", "on-demand"] | KeyboardMode
|
||||
) = KeyboardMode.NONE,
|
||||
pass_through: bool = False,
|
||||
monitor: int | Gdk.Monitor | None = None,
|
||||
title: str = "fabric",
|
||||
@@ -242,12 +246,12 @@ class WaylandWindow(Window):
|
||||
style_classes: Iterable[str] | str | None = None,
|
||||
tooltip_text: str | None = None,
|
||||
tooltip_markup: str | None = None,
|
||||
h_align: Literal["fill", "start", "end", "center", "baseline"]
|
||||
| Gtk.Align
|
||||
| None = None,
|
||||
v_align: Literal["fill", "start", "end", "center", "baseline"]
|
||||
| Gtk.Align
|
||||
| None = None,
|
||||
h_align: (
|
||||
Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None
|
||||
) = None,
|
||||
v_align: (
|
||||
Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None
|
||||
) = None,
|
||||
h_expand: bool = False,
|
||||
v_expand: bool = False,
|
||||
size: Iterable[int] | int | None = None,
|
||||
@@ -292,7 +296,11 @@ class WaylandWindow(Window):
|
||||
self.keyboard_mode = keyboard_mode
|
||||
self.exclusivity = exclusivity
|
||||
self.pass_through = pass_through
|
||||
self.show_all() if all_visible is True else self.show() if visible is True else None
|
||||
(
|
||||
self.show_all()
|
||||
if all_visible is True
|
||||
else self.show() if visible is True else None
|
||||
)
|
||||
|
||||
def steal_input(self) -> None:
|
||||
return GtkLayerShell.set_keyboard_interactivity(self, True)
|
||||
@@ -347,9 +355,11 @@ class WaylandWindow(Window):
|
||||
margins = (
|
||||
extract_css_values(input.lower())
|
||||
if isinstance(input, str)
|
||||
else input
|
||||
if isinstance(input, (tuple, list)) and len(input) == 4
|
||||
else (0, 0, 0, 0)
|
||||
else (
|
||||
input
|
||||
if isinstance(input, (tuple, list)) and len(input) == 4
|
||||
else (0, 0, 0, 0)
|
||||
)
|
||||
)
|
||||
return {
|
||||
Edge.TOP: margins[0],
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import math
|
||||
from fabric.core.service import Service, Signal
|
||||
from gi.repository import Gtk, Gdk, GLib
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
|
||||
class WigglyWidget(Gtk.DrawingArea, Service):
|
||||
@Signal
|
||||
def on_seek(self, ratio: float) -> None: ...
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(name="wiggle-bar")
|
||||
self.phase = 0
|
||||
self.set_size_request(-1, 20)
|
||||
self.connect("draw", self.on_draw)
|
||||
|
||||
# mouse events
|
||||
self.add_events(
|
||||
Gdk.EventMask.BUTTON_PRESS_MASK
|
||||
| Gdk.EventMask.BUTTON_RELEASE_MASK
|
||||
| Gdk.EventMask.POINTER_MOTION_MASK
|
||||
)
|
||||
|
||||
self.connect("button-press-event", self.on_button_press)
|
||||
self.connect("motion-notify-event", self.on_motion)
|
||||
self.connect("button-release-event", self.on_button_release)
|
||||
|
||||
GLib.timeout_add(16, self.update) # 60 FPS
|
||||
self.value = 0.0
|
||||
self.amplitude = 2
|
||||
self.dragging = False
|
||||
self.pause = False
|
||||
|
||||
self.show_all()
|
||||
|
||||
def animate_amplitude_to(self):
|
||||
if abs(self.amplitude - self.amplitude_target) < 0.01:
|
||||
self.amplitude = self.amplitude_target
|
||||
self.queue_draw()
|
||||
return False
|
||||
else:
|
||||
self.amplitude += self.amplitude_step
|
||||
self.queue_draw()
|
||||
return True
|
||||
|
||||
def update_amplitude(self, decrease):
|
||||
if decrease:
|
||||
self.amplitude_target = 0
|
||||
self.amplitude_step = (0 - self.amplitude) / 10.0
|
||||
else:
|
||||
self.amplitude_target = 2
|
||||
self.amplitude_step = (2 - self.amplitude) / 10.0
|
||||
|
||||
# animation stops when False is returned btw
|
||||
GLib.timeout_add(16, self.animate_amplitude_to)
|
||||
|
||||
def update_value_from_x(self, x):
|
||||
width = self.get_allocated_width()
|
||||
self.value = max(0.0, min(1.0, x / width))
|
||||
self.on_seek(self.value)
|
||||
self.queue_draw()
|
||||
|
||||
def on_button_press(self, widget, event):
|
||||
self.dragging = True
|
||||
self.update_amplitude(True)
|
||||
self.update_value_from_x(event.x)
|
||||
return True
|
||||
|
||||
# def animate_progress_to(self):
|
||||
# print("frame")
|
||||
# if self.value < self.value_target:
|
||||
# self.value = self.value_target
|
||||
# print(self.value)
|
||||
# self.queue_draw()
|
||||
# return False
|
||||
# else:
|
||||
# self.value += self.value_step
|
||||
# self.queue_draw()
|
||||
# return True
|
||||
|
||||
# def update_value_from_signal(self, new_value):
|
||||
# self.value_target = min(1.0, new_value)
|
||||
# steps = 60 # 1s / 16ms ≈ 60 frames
|
||||
# self.value_step = (self.value_target - self.value) / steps
|
||||
# GLib.timeout_add(16, self.animate_progress_to)
|
||||
|
||||
def update_value_from_signal(self, new_value):
|
||||
self.value = min(1.0, new_value)
|
||||
|
||||
def on_motion(self, widget, event):
|
||||
if self.dragging and not self.pause:
|
||||
self.update_value_from_x(event.x)
|
||||
|
||||
cursor = Gdk.Cursor.new(Gdk.CursorType.HAND1)
|
||||
widget.get_window().set_cursor(cursor)
|
||||
|
||||
return True
|
||||
|
||||
def on_button_release(self, widget, event):
|
||||
if self.pause == False:
|
||||
self.dragging = False
|
||||
self.update_amplitude(False)
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
if self.dragging == False:
|
||||
self.phase += 0.1
|
||||
# if (self.value<1):
|
||||
# self.value += 0.001
|
||||
self.queue_draw()
|
||||
return True
|
||||
|
||||
def on_draw(self, widget, cr):
|
||||
alloc_width = self.get_allocated_width()
|
||||
height = self.get_allocated_height()
|
||||
center_y = height / 2
|
||||
# self.amplitude = 2
|
||||
frequency = 0.3
|
||||
slider_diameter = 6
|
||||
|
||||
width = int(alloc_width * self.value) - slider_diameter + 1
|
||||
|
||||
cr.set_source_rgb(1, 1, 1) # color here btw
|
||||
cr.set_line_width(2)
|
||||
|
||||
last_x = 0 # fallback values in case the slider gets dragged out of range
|
||||
last_y = 0
|
||||
|
||||
cr.move_to(0, center_y)
|
||||
for x in range(width):
|
||||
y = center_y + self.amplitude * math.sin((x * frequency) + self.phase)
|
||||
cr.line_to(x, y)
|
||||
last_x, last_y = x, y
|
||||
|
||||
cr.stroke()
|
||||
|
||||
# Set the color using CSS - the widget should have CSS styling applied
|
||||
# that sets the color to var(--primary). For now, use a fallback approach.
|
||||
style_context = self.get_style_context()
|
||||
|
||||
try:
|
||||
# Get the foreground color which should be styled with CSS
|
||||
color = style_context.get_color(Gtk.StateFlags.NORMAL)
|
||||
cr.set_source_rgba(color.red, color.green, color.blue, color.alpha)
|
||||
except:
|
||||
# Fallback to a nice blue color that matches the primary theme
|
||||
cr.set_source_rgb(0.2, 0.6, 1.0)
|
||||
|
||||
rect_width = 6
|
||||
arc_radius = slider_diameter / 2
|
||||
self.draw_rounded_rect(cr, last_x, height, rect_width, rect_width, arc_radius)
|
||||
cr.fill()
|
||||
|
||||
cr.stroke()
|
||||
|
||||
cr.set_source_rgb(1, 1, 1)
|
||||
cr.set_line_width(1)
|
||||
|
||||
# This is for anti aliasing. When line width becomes 1px and the position is not an integer, two lines of opacity 50% is drawn on two pixel rows.
|
||||
# This moves the line to make it pixel perfect hence not needing anti aliasing
|
||||
cr.translate(0, 0.5)
|
||||
|
||||
cr.move_to(last_x + 2 * arc_radius, height / 2)
|
||||
cr.line_to(alloc_width, height / 2)
|
||||
|
||||
cr.stroke()
|
||||
|
||||
def draw_rounded_rect(self, cr, x, y, width, _height, radius):
|
||||
cr.new_sub_path()
|
||||
cr.arc(x + width - radius, radius, radius, -math.pi, 0)
|
||||
cr.arc(x + width - radius, y - radius, radius, 0, -math.pi)
|
||||
cr.close_path()
|
||||
Reference in new issue
Block a user