huge updates

This commit is contained in:
sankalp committed 2025-02-21 21:03:31 +05:45
1 parent 0c595432bb
commit 348fba3f8d
14 files changed
+553 -612

No files matched your search

+2 -2
View File
@@ -37,7 +37,7 @@
1. Dependencies:
```sh
fabric fabric-cli gnome-bluetooth-3.0 wf-recorder cliphist wl-clipboard grimblast power-profile-daemon adw-gtk-theme brightnessctl swww hypridle hyprlock imagemagick libnotify swappy libadwaita
fabric fabric-cli gnome-bluetooth-3.0 wf-recorder cliphist wl-clipboard grimblast power-profile-daemon adw-gtk-theme brightnessctl swww hypridle hyprlock imagemagick libnotify swappy libadwaita wlinhibit
```
```sh
@@ -92,7 +92,7 @@ cd Tela-icon-theme
- [x] Update Screenshot
- [x] Installation script
- [x] Network Manager
- [ ] Calendar with events
- [x] Calendar
- [ ] Power Manager
- [ ] Notification Panel
- [ ] Improve CSS Styling
+2 -1
View File
@@ -33,6 +33,7 @@ PACKAGES=(
libadwaita
adw-gtk-theme
brightnessctl
wlinhibit
power-profile-daemon
ttf-font-awesome
otf-font-awesome
@@ -75,7 +76,7 @@ fi
# Install required packages using paru
echo ":: Installing required packages..."
paru -S --needed --noconfirm "${PACKAGES[@]}" || true
paru -Syy --needed --noconfirm "${PACKAGES[@]}" || true
# Backup and replace GTK configs
echo ":: Updating GTK configurations..."
+8 -2
View File
@@ -8,9 +8,11 @@ from loguru import logger
from modules.bar.bar import Bar
from modules.launcher.launcher import Launcher
from modules.notification_popup import NotificationPopup
from modules.osd import OSDContainer
from modules.osd import OSD
from modules.corners import Corners
from services import sc
from config.config import open_config
for log in [
"fabric.hyprland.widgets",
@@ -19,6 +21,8 @@ for log in [
]:
logger.disable(log)
config_path = os.path.expanduser("~/Modus/config/assets/config.json")
def update_main_css():
colors_css_path = f"/home/{os.getlogin()}/.cache/material/colors.css"
@@ -42,10 +46,12 @@ def apply_style(app: Application):
if __name__ == "__main__":
update_main_css()
if not os.path.isfile(config_path):
open_config()
sc = sc
bar = Bar()
corners = Corners()
osd = OSDContainer()
osd = OSD()
notif = NotificationPopup()
launcher = Launcher()
-6
View File
@@ -11,10 +11,8 @@ from gi.repository import GLib
from modules.bar.components import (
BatteryLabel,
BluetoothIndicator,
IdleIndicator,
MicrophoneIndicator,
NetworkIndicator,
PowerProfileIndicator,
SystemInfo,
TaskBar,
VolumeIndicator,
@@ -57,10 +55,8 @@ class Bar(Window):
self.bluetooth = BluetoothIndicator()
self.network = NetworkIndicator()
self.microphone = MicrophoneIndicator()
self.idle = IdleIndicator()
self.taskbar = TaskBar()
self.info = SystemInfo()
self.powerindicator = PowerProfileIndicator()
self.tray = SystemTray(name="tray", icon_size=16, spacing=4)
self.launcher = Button(
name="logo",
@@ -76,12 +72,10 @@ class Bar(Window):
orientation="h",
children=[
self.language,
self.powerindicator,
self.bluetooth,
self.network,
self.volume,
self.microphone,
self.idle,
],
)
-27
View File
@@ -1,27 +0,0 @@
from fabric import Fabricator
from fabric.utils import exec_shell_command
from fabric.widgets.box import Box
from snippets import MaterialIcon
class IdleIndicator(Box):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.idle_icon = self.create_idle_icon()
self.fabricator = Fabricator(interval=1000, poll_from=self.check_idle_status)
self.children = [self.idle_icon]
def create_idle_icon(self):
return MaterialIcon("coffee", 16)
def check_idle_status(self, *_):
output = exec_shell_command("pidof wayland-idle-inhibitor.py")
is_idle = bool(str(output).strip())
self.update_idle_status(is_idle)
def update_idle_status(self, is_idle):
if is_idle:
self.idle_icon.set_visible(True)
else:
self.idle_icon.set_visible(False)
-44
View File
@@ -1,44 +0,0 @@
from fabric import Fabricator
from fabric.utils import exec_shell_command
from fabric.widgets.box import Box
from snippets import MaterialIcon
PROFILE_ICONS = {
"power-saver": "data_saver_on",
"balanced": None,
"performance": "mode_heat",
}
class PowerProfileIndicator(Box):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.profile_icon = None
self.fabricator = Fabricator(interval=1000, poll_from=self.update_power_profile)
def update_power_profile(self, *_):
profile = self.get_power_profile()
self.update_icon(profile)
return True
def get_power_profile(self):
result = exec_shell_command("powerprofilesctl get")
return result.strip() or "power-saver"
def update_icon(self, profile):
icon_name = PROFILE_ICONS.get(profile)
new_icon = MaterialIcon(icon_name, 16) if icon_name else None
if self.profile_icon:
self.remove(self.profile_icon)
if new_icon:
self.profile_icon = new_icon
self.add(self.profile_icon)
else:
if self.profile_icon:
self.profile_icon.hide()
self.profile_icon = None
+1
View File
@@ -8,3 +8,4 @@ from .sh import *
from .todo import *
from .wallpapers import *
from .wifi import *
from .calendar import *
+199
View File
@@ -0,0 +1,199 @@
import calendar
from datetime import datetime
import gi
from snippets import MaterialIcon
from fabric.widgets.label import Label
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
class Calendar(Gtk.Box):
def __init__(self):
super().__init__(
orientation=Gtk.Orientation.VERTICAL, spacing=8, name="calendar"
)
self.current_year = datetime.now().year
self.current_month = datetime.now().month
self.current_day = datetime.now().day
self.previous_key = (self.current_year, self.current_month)
self.cache_threshold = 1 # Allow current month +/- 1 month
self.month_views = {}
self.header = Gtk.Box(spacing=4, name="header")
self.pack_start(self.header, False, False, 0)
self.prev_month_button = Gtk.Button(
name="prev-month-button",
child=MaterialIcon("chevron_left"),
)
self.prev_month_button.connect("clicked", self.on_prev_month_clicked)
self.header.pack_start(self.prev_month_button, False, False, 0)
self.month_label = Gtk.Label(name="month-label")
self.header.pack_start(self.month_label, True, True, 0)
self.next_month_button = Gtk.Button(
name="next-month-button",
child=MaterialIcon("chevron_right"),
)
self.next_month_button.connect("clicked", self.on_next_month_clicked)
self.header.pack_start(self.next_month_button, False, False, 0)
self.weekday_row = Gtk.Box(spacing=4, name="weekday-row")
self.pack_start(self.weekday_row, False, False, 0)
self.stack = Gtk.Stack(name="calendar-stack")
self.stack.set_transition_duration(500)
self.pack_start(self.stack, True, True, 0)
self.update_header()
self.update_calendar()
GLib.timeout_add_seconds(60, self.check_date_change)
def update_header(self):
self.month_label.set_text(
datetime(self.current_year, self.current_month, 1)
.strftime("%B %Y")
.capitalize()
)
for child in self.weekday_row.get_children():
self.weekday_row.remove(child)
days = self.get_weekday_initials()
for day in days:
label = Gtk.Label(label=day.upper(), name="weekday-label")
self.weekday_row.pack_start(label, True, True, 0)
self.weekday_row.show_all()
def update_calendar(self):
new_key = (self.current_year, self.current_month)
if new_key > self.previous_key:
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT)
elif new_key < self.previous_key:
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_RIGHT)
self.previous_key = new_key
if new_key not in self.month_views:
month_view = self.create_month_view(self.current_year, self.current_month)
self.month_views[new_key] = month_view
self.stack.add_titled(
month_view,
f"{self.current_year}_{self.current_month}",
f"{self.current_year}_{self.current_month}",
)
self.stack.set_visible_child_name(f"{self.current_year}_{self.current_month}")
self.update_header()
self.stack.show_all()
self.prune_cache()
def prune_cache(self):
def month_index(key):
year, month = key
return year * 12 + (month - 1)
current_index = month_index((self.current_year, self.current_month))
keys_to_remove = [
key
for key in self.month_views
if abs(month_index(key) - current_index) > self.cache_threshold
]
for key in keys_to_remove:
widget = self.month_views.pop(key)
self.stack.remove(widget)
def create_month_view(self, year, month):
grid = Gtk.Grid(
column_homogeneous=True, row_homogeneous=False, name="calendar-grid"
)
cal = calendar.Calendar(firstweekday=6)
month_days = cal.monthdayscalendar(year, month)
prev_month = month - 1 if month > 1 else 12
next_month = month + 1 if month < 12 else 1
prev_year = year if month > 1 else year - 1
next_year = year if month < 12 else year + 1
prev_month_days_count = calendar.monthrange(prev_year, prev_month)[1]
next_month_day = 1
for row, week in enumerate(month_days):
for col, day in enumerate(week):
day_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="day-box")
top_spacer = Gtk.Box(hexpand=True, vexpand=True)
middle_box = Gtk.Box(hexpand=True, vexpand=True)
bottom_spacer = Gtk.Box(hexpand=True, vexpand=True)
if day == 0:
if row == 0:
day_num = prev_month_days_count - week[:col].count(0)
label = Gtk.Label(label=str(day_num), name="prev-month-day")
else:
label = Gtk.Label(
label=str(next_month_day), name="next-month-day"
)
next_month_day += 1
else:
label = Gtk.Label(label=str(day), name="day-label")
if (
day == self.current_day
and month == datetime.now().month
and year == datetime.now().year
):
label.get_style_context().add_class("current-day")
middle_box.pack_start(
Gtk.Box(hexpand=True, vexpand=True), True, True, 0
)
middle_box.pack_start(label, False, False, 0)
middle_box.pack_start(
Gtk.Box(hexpand=True, vexpand=True), True, True, 0
)
day_box.pack_start(top_spacer, True, True, 0)
day_box.pack_start(middle_box, True, True, 0)
day_box.pack_start(bottom_spacer, True, True, 0)
grid.attach(day_box, col, row, 1, 1)
grid.show_all()
return grid
def get_weekday_initials(self):
return [datetime(2023, 1, i + 1).strftime("%a")[:1] for i in range(7)]
def on_prev_month_clicked(self, widget):
if self.current_month == 1:
self.current_month = 12
self.current_year -= 1
else:
self.current_month -= 1
self.update_calendar()
def on_next_month_clicked(self, widget):
if self.current_month == 12:
self.current_month = 1
self.current_year += 1
else:
self.current_month += 1
self.update_calendar()
def check_date_change(self):
now = datetime.now()
if (
now.day != self.current_day
or now.month != self.current_month
or now.year != self.current_year
):
self.current_day = now.day
self.current_month = now.month
self.current_year = now.year
self.update_calendar()
return True
-242
View File
@@ -1,242 +0,0 @@
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.label import Label
from snippets import MaterialIcon
class Buttons(Box):
def __init__(self, **kwargs):
super().__init__(
name="buttons",
spacing=4,
h_align="center",
v_align="start",
h_expand=True,
v_expand=True,
visible=True,
all_visible=True,
)
self.network_status_button = Button(
name="network-status-button",
h_expand=True,
child=Box(
h_align="start",
v_align="center",
spacing=10,
children=[
MaterialIcon("wifi"),
Box(
name="network-text",
orientation="v",
h_align="start",
v_align="center",
children=[
Box(
children=[
Label(
name="network-label",
label="Wi-Fi",
justification="left",
),
Box(h_expand=True),
]
),
Box(
children=[
Label(
name="network-ssid",
label="ARGOS_5GHz",
justification="left",
),
Box(h_expand=True),
]
),
],
),
],
),
)
self.network_menu_button = Button(
name="network-menu-button",
child=MaterialIcon("chevron_right"),
)
self.network_button = Box(
name="network-button",
orientation="h",
h_align="fill",
v_align="center",
h_expand=True,
v_expand=True,
children=[
self.network_status_button,
self.network_menu_button,
],
)
self.bluetooth_status_button = Button(
name="bluetooth-status-button",
h_expand=True,
child=Box(
h_align="start",
v_align="center",
spacing=10,
children=[
MaterialIcon("bluetooth"),
Box(
name="bluetooth-text",
orientation="v",
h_align="start",
v_align="center",
children=[
Box(
children=[
Label(
name="bluetooth-label",
label="Bluetooth",
justification="left",
),
Box(h_expand=True),
]
),
Box(
children=[
Label(
name="bluetooth-status",
label="Connected",
justification="left",
),
Box(h_expand=True),
]
),
],
),
],
),
)
self.bluetooth_menu_button = Button(
name="bluetooth-menu-button",
child=MaterialIcon("chevron_right"),
)
self.bluetooth_button = Box(
name="bluetooth-button",
orientation="h",
h_align="fill",
v_align="center",
h_expand=True,
v_expand=True,
children=[
self.bluetooth_status_button,
self.bluetooth_menu_button,
],
)
self.night_mode_button = Button(
name="night-mode-button",
child=Box(
h_align="start",
v_align="center",
spacing=10,
children=[
MaterialIcon("nightlight"),
Box(
name="night-mode-text",
orientation="v",
h_align="start",
v_align="center",
children=[
Box(
children=[
Label(
name="night-mode-label",
label="Night Mode",
justification="left",
),
Box(h_expand=True),
]
),
Box(
children=[
Label(
name="night-mode-status",
label="Enabled",
justification="left",
),
Box(h_expand=True),
]
),
],
),
],
),
)
self.caffeine_button = Button(
name="caffeine-button",
child=Box(
h_align="start",
v_align="center",
spacing=10,
children=[
MaterialIcon("coffee"),
Box(
name="caffeine-text",
orientation="v",
h_align="start",
v_align="center",
children=[
Box(
children=[
Label(
name="caffeine-label",
label="Caffeine",
justification="left",
),
Box(h_expand=True),
]
),
Box(
children=[
Label(
name="caffeine-status",
label="Enabled",
justification="left",
),
Box(h_expand=True),
]
),
],
),
],
),
)
self.add(self.network_button)
self.add(self.bluetooth_button)
self.add(self.night_mode_button)
self.add(self.caffeine_button)
self.show_all()
class Dashboard(Box):
def __init__(self, **kwargs):
super().__init__(
name="dashboard",
h_align="center",
v_align="start",
h_expand=True,
v_expand=True,
visible=True,
all_visible=True,
)
self.buttons = Buttons()
self.add(self.buttons)
self.show_all()
+131 -41
View File
@@ -3,10 +3,11 @@ import json
import os
from PIL import Image
from fabric.widgets.box import Box
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.button import Button
from fabric.widgets.entry import Entry
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import GdkPixbuf, GLib, Gtk, Gio
from gi.repository import GdkPixbuf, GLib, Gtk, Gio, Gdk
from snippets import MaterialIcon
from fabric.utils import exec_shell_command, get_relative_path
import concurrent.futures
@@ -30,10 +31,16 @@ class WallpaperSelector(Box):
}
def __init__(self, **kwargs):
super().__init__(name="wallpapers", spacing=4, orientation="v", **kwargs)
super().__init__(
name="wallpapers",
spacing=4,
orientation="v",
h_expand=False,
v_expand=False,
**kwargs,
)
self.launcher = kwargs["launcher"]
# self.wallpapers_dir = os.path.expanduser("~/Pictures/wallpapers")
os.makedirs(self.CACHE_DIR, exist_ok=True)
self.files = [f for f in os.listdir(WALLPAPERS_DIR) if self._is_image(f)]
@@ -41,10 +48,13 @@ class WallpaperSelector(Box):
self.thumbnail_queue = []
self.executor = ThreadPoolExecutor(max_workers=4) # Shared executor
self.viewport = Gtk.IconView()
self.selected_index = -1
# IconView for displaying thumbnails.
self.viewport = Gtk.IconView(name="wallpaper-icons")
self.viewport.set_model(Gtk.ListStore(GdkPixbuf.Pixbuf, str))
self.viewport.set_pixbuf_column(0)
self.viewport.set_text_column(1)
self.viewport.set_text_column(-1)
self.viewport.set_item_width(0)
self.viewport.connect("item-activated", self.on_wallpaper_selected)
@@ -57,10 +67,12 @@ class WallpaperSelector(Box):
)
self.search_entry = Entry(
name="search-entry",
name="search-entry-walls",
h_expand=True,
notify_text=lambda entry, *_: self.arrange_viewport(entry.get_text()),
on_key_press_event=self.on_search_entry_key_press,
)
self.search_entry.connect("focus-out-event", self.on_search_entry_focus_out)
self.scheme_dropdown = Gtk.ComboBoxText()
self.scheme_dropdown.set_name("scheme-dropdown")
@@ -72,7 +84,6 @@ class WallpaperSelector(Box):
self.scheme_dropdown.set_active_id("tonalSpot")
self.scheme_dropdown.connect("changed", self.on_scheme_changed)
# Set the initial icon based on dark mode state
initial_icon = "light_mode" if not self.check_dark_mode_state() else "dark_mode"
self.toggle_button = Button(
child=MaterialIcon(initial_icon),
@@ -82,18 +93,17 @@ class WallpaperSelector(Box):
self.dropdown_box = Box(
name="dropdown-box",
orientation="h",
children=[
self.scheme_dropdown,
MaterialIcon("keyboard_arrow_down"),
],
)
self.header_box = Box(
self.header_box = CenterBox(
name="header-box",
spacing=10,
orientation="h",
children=[self.search_entry, self.dropdown_box, self.toggle_button],
start_children=[self.search_entry],
end_children=[self.dropdown_box, self.toggle_button],
)
self.add(self.header_box)
@@ -101,6 +111,7 @@ class WallpaperSelector(Box):
self._start_thumbnail_thread()
self.setup_file_monitor()
self.show_all()
self.search_entry.grab_focus()
def setup_file_monitor(self):
gfile = Gio.File.new_for_path(WALLPAPERS_DIR)
@@ -135,11 +146,9 @@ class WallpaperSelector(Box):
print(f"Error deleting cache for changed file {file_name}: {e}")
self.executor.submit(self._process_file, file_name)
def close_selector(self):
self.launcher.close()
def arrange_viewport(self, query: str = ""):
self.viewport.get_model().clear()
model = self.viewport.get_model()
model.clear()
filtered_thumbnails = [
(thumb, name)
for thumb, name in self.thumbnails
@@ -147,32 +156,14 @@ class WallpaperSelector(Box):
]
filtered_thumbnails.sort(key=lambda x: x[1].lower())
for pixbuf, file_name in filtered_thumbnails:
self.viewport.get_model().append([pixbuf, file_name])
model.append([pixbuf, file_name])
def toggle_dark_mode(self, *_):
current_state = self.check_dark_mode_state()
new_mode = "dark" if not current_state else "light"
# Call the shell script with the --set argument
command = f"bash {get_relative_path('../../../config/scripts/dark-theme.sh')} --set {new_mode}"
GLib.spawn_command_line_async(command)
# Set the appropriate icon based on the mode
icon_name = "dark_mode" if new_mode == "dark" else "light_mode"
# Update button icon
if self.toggle_button.get_child():
self.toggle_button.remove(self.toggle_button.get_child())
self.toggle_button.add(MaterialIcon(icon_name))
self.toggle_button.show_all()
def check_dark_mode_state(self):
result = exec_shell_command(
"gsettings get org.gnome.desktop.interface color-scheme"
)
return result.strip().replace("'", "") == "prefer-dark"
if query.strip() == "":
self.viewport.unselect_all()
self.selected_index = -1
elif len(model) > 0:
self.update_selection(0)
def on_wallpaper_selected(self, iconview, path):
model = iconview.get_model()
@@ -214,6 +205,76 @@ class WallpaperSelector(Box):
except Exception as error:
print(f"Failed to update generation-scheme in settings.json: {error}")
def on_search_entry_key_press(self, widget, event):
if event.state & Gdk.ModifierType.SHIFT_MASK:
if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down):
schemes_list = list(self.SCHEMES.keys())
current_id = self.scheme_dropdown.get_active_id()
current_index = (
schemes_list.index(current_id) if current_id in schemes_list else 0
)
if event.keyval == Gdk.KEY_Up:
new_index = (current_index - 1) % len(schemes_list)
else:
new_index = (current_index + 1) % len(schemes_list)
self.scheme_dropdown.set_active(new_index)
return True
elif event.keyval == Gdk.KEY_Right:
self.scheme_dropdown.popup()
return True
if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right):
self.move_selection_2d(event.keyval)
return True
elif event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
if self.selected_index != -1:
path = Gtk.TreePath.new_from_indices([self.selected_index])
self.on_wallpaper_selected(self.viewport, path)
return True
return False
def move_selection_2d(self, keyval):
model = self.viewport.get_model()
total_items = len(model)
if total_items == 0:
return
if self.selected_index == -1:
new_index = (
0 if keyval in (Gdk.KEY_Down, Gdk.KEY_Right) else total_items - 1
)
else:
current_index = self.selected_index
# Calculate columns based on the allocation width.
allocation = self.viewport.get_allocation()
item_width = 108
columns = max(1, allocation.width // item_width)
if keyval == Gdk.KEY_Right:
new_index = current_index + 1
elif keyval == Gdk.KEY_Left:
new_index = current_index - 1
elif keyval == Gdk.KEY_Down:
new_index = current_index + columns
elif keyval == Gdk.KEY_Up:
new_index = current_index - columns
# Ensure new_index remains within limits.
if new_index < 0:
new_index = 0
if new_index >= total_items:
new_index = total_items - 1
self.update_selection(new_index)
def update_selection(self, new_index: int):
self.viewport.unselect_all()
path = Gtk.TreePath.new_from_indices([new_index])
self.viewport.select_path(path)
self.viewport.scroll_to_path(path, False, 0.5, 0.5)
self.selected_index = new_index
def _start_thumbnail_thread(self):
thread = GLib.Thread.new("thumbnail-loader", self._preload_thumbnails, None)
@@ -231,8 +292,15 @@ class WallpaperSelector(Box):
if not os.path.exists(cache_path):
try:
with Image.open(full_path) as img:
img.thumbnail((96, 96), Image.Resampling.BILINEAR)
img.save(cache_path, "PNG")
width, height = img.size
side = min(width, height)
left = (width - side) // 2
top = (height - side) // 2
right = left + side
bottom = top + side
img_cropped = img.crop((left, top, right, bottom))
img_cropped.thumbnail((96, 96), Image.Resampling.LANCZOS)
img_cropped.save(cache_path, "PNG")
except Exception as e:
print(f"Error processing {file_name}: {e}")
return
@@ -262,3 +330,25 @@ class WallpaperSelector(Box):
return file_name.lower().endswith(
(".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
)
def on_search_entry_focus_out(self, widget, event):
if self.get_mapped():
widget.grab_focus()
return False
def toggle_dark_mode(self, *_):
current_state = self.check_dark_mode_state()
new_mode = "dark" if not current_state else "light"
command = f"bash {get_relative_path('../../../config/scripts/dark-theme.sh')} --set {new_mode}"
GLib.spawn_command_line_async(command)
icon_name = "dark_mode" if new_mode == "dark" else "light_mode"
if self.toggle_button.get_child():
self.toggle_button.remove(self.toggle_button.get_child())
self.toggle_button.add(MaterialIcon(icon_name))
self.toggle_button.show_all()
def check_dark_mode_state(self):
result = exec_shell_command(
"gsettings get org.gnome.desktop.interface color-scheme"
)
return result.strip().replace("'", "") == "prefer-dark"
+6 -15
View File
@@ -1,8 +1,6 @@
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.stack import Stack
from fabric.widgets.box import Box
from fabric.widgets.wayland import WaylandWindow as Window
from gi.repository import Gtk
from modules.launcher.components import (
AppLauncher,
BluetoothConnections,
@@ -13,6 +11,7 @@ from modules.launcher.components import (
TodoManager,
WallpaperSelector,
WifiManager,
Calendar,
)
@@ -35,6 +34,7 @@ class Launcher(Window):
self.bluetooth = BluetoothConnections()
self.sh = Sh(launcher=self)
self.wifi = WifiManager()
self.calendar = Calendar()
self.stack = Stack(
name="launcher-content",
@@ -52,6 +52,7 @@ class Launcher(Window):
self.bluetooth,
self.sh,
self.wifi,
self.calendar,
],
)
@@ -64,7 +65,6 @@ class Launcher(Window):
self.add(self.launcher_box)
self.show_all()
self.hide()
self.wallpapers.viewport.hide()
self.add_keybinding("Escape", lambda *_: self.close())
def close(self):
@@ -81,11 +81,8 @@ class Launcher(Window):
self.bluetooth,
self.sh,
self.wifi,
self.calendar,
]:
if widget == self.wallpapers:
self.wallpapers.viewport.hide()
self.wallpapers.viewport.set_property("name", None)
if hasattr(widget, "viewport") and widget.viewport:
widget.viewport.hide()
@@ -99,6 +96,7 @@ class Launcher(Window):
"bluetooth",
"sh",
"wifi",
"calendar",
]:
self.stack.remove_style_class(style)
@@ -118,6 +116,7 @@ class Launcher(Window):
"bluetooth": self.bluetooth,
"sh": self.sh,
"wifi": self.wifi,
"calendar": self.calendar,
}
for w in widgets.values():
@@ -130,13 +129,6 @@ class Launcher(Window):
self.stack.set_visible_child(widgets[widget])
widgets[widget].show()
# if widget != "launcher":
# self.launcher.hide()
if widget != "wallpapers":
self.wallpapers.viewport.hide()
self.wallpapers.viewport.set_property("name", None)
if widget == "launcher":
self.launcher.open_launcher()
self.launcher.search_entry.set_text("")
@@ -146,7 +138,6 @@ class Launcher(Window):
self.wallpapers.search_entry.set_text("")
self.wallpapers.search_entry.grab_focus()
self.wallpapers.viewport.show()
self.wallpapers.viewport.set_property("name", "wallpaper-icons")
elif widget == "emoji":
self.emoji.open_launcher()
+85 -200
View File
@@ -1,228 +1,113 @@
import time
from typing import ClassVar, Literal
from fabric.utils import invoke_repeater
from fabric.widgets.box import Box
from fabric.widgets.revealer import Revealer
from fabric.widgets.scale import Scale, ScaleMark
from fabric.widgets.wayland import WaylandWindow as Window
from gi.repository import GObject
from snippets import Animator, MaterialIcon
from fabric.widgets.label import Label
from fabric.widgets.box import Box
from gi.repository import GLib, GObject
from fabric.widgets.centerbox import CenterBox
from services import brightness, audio
from snippets import Animator, MaterialIcon
class AnimatedScale(Scale):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.animator = None # Lazily initialized
def animate_value(self, value: float):
if not self.animator:
self.animator = Animator(
bezier_curve=(0.34, 1.56, 0.64, 1.0),
duration=0.8,
min_value=self.min_value,
max_value=self.value,
tick_widget=self,
notify_value=lambda p, *_: self.set_value(p.value),
)
self.animator.pause()
self.animator.min_value = self.value
self.animator.max_value = value
self.animator.play()
def create_progress_bar(percentage, width=150):
container = Box(orientation="h", name="progress-container")
progress = Box(
name="progress-fill", style=f"min-width: {percentage * width / 100}px;"
)
background = Box(name="progress-background", style=f"min-width: {width}px;")
background.children = [progress]
container.children = [background]
return container
class BrightnessOSDContainer(Box):
def __init__(self, **kwargs):
super().__init__(**kwargs, orientation="h", spacing=12, name="osd")
self.brightness_service = brightness
self.icon = MaterialIcon(icon_name="brightness_7", size=20)
self.scale = AnimatedScale(
marks=(ScaleMark(value=i) for i in range(0, 101, 10)),
value=70,
min_value=0,
max_value=100,
increments=(1, 1),
orientation="h",
)
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 update_brightness(self) -> None:
current_brightness = self.brightness_service.screen_brightness
if current_brightness != 0:
normalized_brightness = self._normalize_brightness(current_brightness)
self.scale.animate_value(normalized_brightness)
self.update_icon(normalized_brightness)
def update_icon(self, brightness_percentage: float) -> None:
icon_name = self.get_brightness_icon(brightness_percentage)
self.icon.set_label(icon_name)
def on_brightness_changed(self, _sender: any, value: float, *_args) -> None:
normalized_brightness = self._normalize_brightness(value)
self.scale.animate_value(normalized_brightness)
self.update_icon(normalized_brightness)
def _normalize_brightness(self, brightness: float) -> float:
return (brightness / self.brightness_service.max_screen) * 100
def get_brightness_icon(self, brightness_percentage: float) -> str:
brightness_icons = [
"brightness_1",
"brightness_2",
"brightness_3",
"brightness_4",
"brightness_5",
"brightness_6",
"brightness_7",
]
thresholds = [
100 / (1.6 ** (len(brightness_icons) - i - 1))
for i in range(len(brightness_icons))
]
for i, threshold in enumerate(thresholds):
if brightness_percentage < threshold:
return brightness_icons[i]
return brightness_icons[-1]
def update_progress_bar(progress_bar, percentage, width=150):
progress_fill = progress_bar.children[0].children[0]
progress_fill.set_style(f"min-width: {percentage * width / 100}px;")
class AudioOSDContainer(Box):
__gsignals__: ClassVar[dict] = {
"volume-changed": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ()),
}
def __init__(self, **kwargs):
super().__init__(**kwargs, orientation="h", spacing=13, name="osd")
self.audio = audio
self.icon = MaterialIcon(icon_name="volume_up", size=20)
self.scale = 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",
)
self.add(self.icon)
self.add(self.scale)
self.sync_with_audio()
self.scale.connect("value-changed", self.on_volume_changed)
self.audio.connect("notify::speaker", self.on_audio_speaker_changed)
def sync_with_audio(self):
if self.audio.speaker:
volume = round(self.audio.speaker.volume)
self.scale.set_value(volume)
self.update_icon(volume)
def on_volume_changed(self, *_):
if self.audio.speaker:
volume = self.scale.value
if 0 <= volume <= 100:
self.audio.speaker.set_volume(volume)
self.update_icon(volume)
self.emit("volume-changed")
def on_audio_speaker_changed(self, *_):
if self.audio.speaker:
self.audio.speaker.connect("notify::volume", self.update_volume)
self.update_volume()
def update_volume(self, *_):
if self.audio.speaker and not self.is_hovered():
volume = round(self.audio.speaker.volume)
self.scale.set_value(volume)
self.update_icon(volume)
def update_icon(self, volume):
icon_name = self._get_audio_icon_name(volume)
self.icon.set_label(icon_name)
def _get_audio_icon_name(self, volume: int) -> str:
if volume == 101:
return "sound_detection_loud_sound"
elif volume >= 67:
return "volume_up"
elif volume >= 34:
return "volume_down"
elif volume >= 1:
return "volume_mute"
else:
return "volume_off"
def create_labeled_progress(label_text, value_text, percentage):
label = Label(name="osd-label", markup=label_text)
value = Label(name="osd-value", markup=value_text)
header = CenterBox(orientation="h", start_children=[label], end_children=[value])
progress = create_progress_bar(percentage)
return Box(
name="control-section", orientation="v", spacing=5, children=[header, progress]
)
class OSDContainer(Window):
def __init__(self, **kwargs):
self.audio_container = AudioOSDContainer()
self.brightness_container = BrightnessOSDContainer()
def get_brightness():
return (
round((brightness.screen_brightness / brightness.max_screen) * 100)
if brightness.screen_brightness
else 0
)
self.timeout = 1000
self.revealer = Revealer(
transition_type="slide-right",
transition_duration=200,
child_revealed=False,
)
def get_volume():
return round(audio.speaker.volume) if audio.speaker else 0
self.main_box = Box(
orientation="v",
spacing=13,
children=[self.revealer],
)
class OSD(Window):
def __init__(self):
super().__init__(
name="osd-menu",
layer="overlay",
anchor="bottom",
child=self.main_box,
visible=False,
pass_through=True,
margin="0 0 10px 0",
keyboard_mode="on-demand",
**kwargs,
visible=False,
style_classes="osd-panel",
)
self.last_activity_time = time.time()
self.audio_container.audio.connect("notify::speaker", self.show_audio)
self.brightness_container.brightness_service.connect(
"screen", self.show_brightness
self.last_volume = get_volume()
self.last_brightness = get_brightness()
self.volume_container = create_labeled_progress(
"Speaker", str(self.last_volume), self.last_volume
)
self.audio_container.connect("volume-changed", self.show_audio)
self.brightness_container = create_labeled_progress(
"Brightness", str(self.last_brightness), self.last_brightness
)
self.children = Box(
orientation="h",
spacing=10,
children=[self.brightness_container, self.volume_container],
)
self.hide_timeout_id = None
self._connect_signals()
GLib.timeout_add(100, self._check_changes)
invoke_repeater(100, self.check_inactivity, initial_call=True)
def _connect_signals(self):
audio.connect("notify::speaker", self._update_volume)
brightness.connect("screen", self._update_brightness)
def show_audio(self, *_):
self.show_box(box_to_show="audio")
self.reset_inactivity_timer()
def _update_volume(self, *_):
volume = get_volume()
self.volume_container.children[0].end_children[0].set_markup(str(volume))
update_progress_bar(self.volume_container.children[1], volume)
self._reset_timeout()
def show_brightness(self, *_):
self.show_box(box_to_show="brightness")
self.reset_inactivity_timer()
def _update_brightness(self, *_):
brightness_level = get_brightness()
self.brightness_container.children[0].end_children[0].set_markup(
str(brightness_level)
)
update_progress_bar(self.brightness_container.children[1], brightness_level)
self._reset_timeout()
def show_box(self, box_to_show: Literal["audio", "brightness"]):
self.set_visible(True)
if box_to_show == "audio":
self.revealer.children = self.audio_container
elif box_to_show == "brightness":
self.revealer.children = self.brightness_container
self.revealer.set_reveal_child(True)
self.reset_inactivity_timer()
def _reset_timeout(self):
if self.hide_timeout_id:
GLib.source_remove(self.hide_timeout_id)
self.hide_timeout_id = GLib.timeout_add(1500, self._hide_osd)
def start_hide_timer(self):
self.set_visible(False)
def _hide_osd(self):
self.hide()
self.hide_timeout_id = None
return False
def reset_inactivity_timer(self):
self.last_activity_time = time.time()
def check_inactivity(self):
if time.time() - self.last_activity_time >= (self.timeout / 1000):
self.start_hide_timer()
def _check_changes(self):
volume = get_volume()
brightness_level = get_brightness()
if volume != self.last_volume or brightness_level != self.last_brightness:
self.last_volume, self.last_brightness = volume, brightness_level
self.show_all()
self._update_volume()
self._update_brightness()
return True
+86 -1
View File
@@ -43,6 +43,11 @@
min-height: 52px;
}
#launcher-content.calendar {
min-height: 420px;
min-width: 420px;
}
#launcher-content {
min-width: 420px;
}
@@ -63,7 +68,7 @@
color: @onSecondaryContainer;
margin: 11px;
margin-left: 0px;
min-width: 54px;
min-width: 48px;
min-height: 44px;
border-radius: 100%;
}
@@ -125,6 +130,9 @@
/*
* ── wallpaper ─────────────────────────────────────────────────────────
*/
#search-entry-walls {
min-width: 300px;
}
#wallpaper-icons:selected {
background-color: @surfaceBright;
@@ -410,3 +418,80 @@
border-radius: 20px;
min-height: 200px;
}
/*
* ── calendar ──────────────────────────────────────────────────────────
*/
#header {
border-radius: 16px;
padding: 4px;
}
#weekday-row {
border-radius: 8px;
padding: 4px;
}
#month-label,
#weekday-label,
#day-label {
font-weight: bold;
}
#day-label,
#day-empty {
min-width: 41px;
min-height: 30px;
}
#day-empty {
color: @surfaceBright;
}
#weekday-label {
color: @primary;
}
#calendar-grid {
border-radius: 16px;
padding: 4px;
}
#day-label.current-day {
background-color: @primary;
color: @onPrimary;
border-radius: 20px;
}
#prev-month-button,
#next-month-button {
border-radius: 8px;
padding: 4px;
}
#prev-month-button:hover,
#next-month-button:hover {
background-color: @surfaceBright;
}
#prev-month-button:active,
#next-month-button:active {
background-color: @primary;
}
#month-button-label {
color: @primary;
font-size: 20px;
}
#prev-month-button:active #month-button-label,
#next-month-button:active #month-button-label {
color: @surface;
}
#prev-month-day,
#next-month-day {
/*color: lighter(@surfaceBright);*/
color: mix(@onSurfaceVariant, transparent, 0.6);
}
+33 -31
View File
@@ -1,42 +1,44 @@
#osd {
background: @surfaceContainer;
padding: 10px 4px 10px 4px;
border-radius: 14px;
margin-bottom: 16px;
min-width: 190px;
.osd-panel {
background-color: transparent;
}
#osd scale {
#osd-label {
font-size: 17px;
font-weight: bold;
}
#osd-value {
font-weight: bold;
font-size: 25px;
}
#progress-container {
border-radius: 6px;
min-height: 12px;
min-width: 150px;
}
#osd slider {
color: @onPrimary;
#progress-fill {
background-color: @primary;
background-size: 20px 20px;
min-width: 5px;
min-height: 14px;
border-radius: 100px;
padding: 0px;
background-repeat: no-repeat;
background-position: center;
margin-left: 0px;
margin-right: 0px;
margin-top: -16px;
margin-bottom: -16px;
box-shadow:
-4px 0px 0px 1px @surfaceContainer,
4px 0px 0px 1px @surfaceContainer;
border-radius: 6px;
min-height: 8px;
transition: min-width 0.3s ease-in-out;
}
#osd trough {
background: @secondaryContainer;
min-height: 10px;
margin-right: 4px;
border-radius: 100px;
#progress-background {
background-color: @surfaceContainer;
border-radius: 6px;
min-height: 8px;
min-width: 150px;
}
#osd highlight {
border-radius: 100px 0px 0px 100px;
background: @primary;
#control-section {
padding: 10px;
border-radius: 10px;
background-color: @surface;
}
.osd-hidden {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}