💅 style(colors): update color scheme for better contrast and consistency

This commit is contained in:
sankalp committed 2025-06-22 19:38:03 +05:45
1 parent 84d3628c6e
commit 814ee1b9fd
6 files changed
+992 -576

No files matched your search

+11 -11
View File
@@ -1,13 +1,13 @@
$wallpaper = /home/sankalp/Pictures/wallpapers/173.png
$background = 121318
$foreground = e3e1e9
$wallpaper = /home/sankalp/Pictures/wallpapers/122.jpg
$background = 111318
$foreground = e2e2e9
$primary = bac3ff
$secondary = c3c5dd
$tertiary = e5bad8
$surface = 121318
$surface_bright = 38393f
$outline = 90909a
$primary = adc6ff
$secondary = bfc6dc
$tertiary = debcdf
$surface = 111318
$surface_bright = 37393e
$outline = 8e9099
$error = ff5945
$shadow = 000000
@@ -15,13 +15,13 @@ $shadow = 000000
$red = ffb2b9
$green = 95d5a7
$yellow = b8cf84
$blue = bcc2ff
$blue = b6c4ff
$magenta = e4b7f3
$cyan = 82d3e2
$white = 82d3e0
$red_dim = ff4c5c
$green_dim = 4cb86a
$yellow_dim = 8baa43
$blue_dim = 5665ff
$blue_dim = 5072ff
$magenta_dim = c360e4
$cyan_dim = 30b5ce
+7 -7
View File
@@ -10,24 +10,24 @@ $axMessage = notify-send "Modus" "FIRE IN THE HOLE‼️🗣️🔥🕳️" -i "
# Reload Modus
bind = ALT SHIFT, R, exec, killall modus; uwsm-app $(python /home/sankalp/Modus/main.py)
# Message
bind = SUPER, A, exec, $axMessage
bind = SUPER, A, exec, $Message
# Application Switcher
bind = ALT, TAB, exec, $fabricSend 'switcher.show_switcher()'
# Kanban
bind = SUPER, T, exec, $fabricSend "launcher.show_launcher('kanban ')" # Kanban
bind = SUPER, T, exec, $fabricSend "launcher.show_launcher('kanban')" # Kanban
# App Launcher
bind = SUPER, SPACE, exec, $fabricSend "launcher.show_launcher()"
bind = SUPER, D, exec, $fabricSend "launcher.show_launcher('app ')"
bind = SUPER, D, exec, $fabricSend "launcher.show_launcher('app')"
# Clipboard History
bind = SUPER, V, exec, $fabricSend "launcher.show_launcher('clip ')"
bind = SUPER, V, exec, $fabricSend "launcher.show_launcher('clip')"
# Wallpapers
bind = SUPER, W, exec, $fabricSend "launcher.show_launcher('wall ')"
bind = SUPER, W, exec, $fabricSend "launcher.show_launcher('wall')"
# Random Wallpaper
bind = SHIFT ALT, W, exec, $fabricSend "launcher.show_launcher('wall random', external=True)"
# Emoji Picker
bind = SUPER, E, exec, $fabricSend "launcher.show_launcher('emoji ')"
bind = SUPER, E, exec, $fabricSend "launcher.show_launcher('emoji')"
# Power Menu
bind = SUPER, ESCAPE, exec, $fabricSend "launcher.show_launcher('power ')"
bind = SUPER, ESCAPE, exec, $fabricSend "launcher.show_launcher('power')"
# Toggle Caffeine
bind = SUPER SHIFT, M, exec, $fabricSend "launcher.show_launcher('caffeine on', external=True)"
# Settings
+1 -1
View File
@@ -35,7 +35,7 @@ if __name__ == "__main__":
current_wallpaper = os.path.expanduser("~/.current.wall")
if not os.path.exists(current_wallpaper):
example_wallpaper = os.path.expanduser(
f"~/{APP_NAME_CAP}/assets/wallpapers_example/example-1.jpg"
f"~/{APP_NAME_CAP}/assets/wallpapers_example/example-1.png"
)
os.symlink(example_wallpaper, current_wallpaper)
+959
View File
@@ -0,0 +1,959 @@
import colorsys
import hashlib
import json
import os
import random
import re
import threading
import time
from typing import Dict, List, Optional
import config.data as data
import utils.icons as icons
from fabric.utils.helpers import exec_shell_command_async
from gi.repository import GdkPixbuf
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
from PIL import Image
class WallpaperPlugin(PluginBase):
"""
Plugin for wallpaper management with search, random selection, and matugen integration.
"""
def __init__(self):
super().__init__()
self.display_name = "Wallpaper"
self.wallpapers = []
self.cache_dir = f"{data.CACHE_DIR}/thumbs"
self.thumbnail_cache: Dict[str, Optional[GdkPixbuf.Pixbuf]] = {}
self.thumbnail_loading = set() # Track which thumbnails are being loaded
self.schemes = {
"scheme-tonal-spot": "Tonal Spot",
"scheme-content": "Content",
"scheme-expressive": "Expressive",
"scheme-fidelity": "Fidelity",
"scheme-fruit-salad": "Fruit Salad",
"scheme-monochrome": "Monochrome",
"scheme-neutral": "Neutral",
"scheme-rainbow": "Rainbow",
}
def initialize(self):
"""Initialize the wallpaper plugin."""
self.set_triggers(["wall"])
self._load_wallpapers()
os.makedirs(self.cache_dir, exist_ok=True)
# Start background thumbnail creation
self._start_background_thumbnail_creation()
def cleanup(self):
"""Cleanup the wallpaper plugin."""
pass
def _load_wallpapers(self):
"""Load available wallpapers from the wallpapers directory."""
try:
if os.path.exists(data.WALLPAPERS_DIR):
self.wallpapers = sorted(
[f for f in os.listdir(data.WALLPAPERS_DIR) if self._is_image(f)]
)
else:
print(f"Wallpapers directory not found: {data.WALLPAPERS_DIR}")
except Exception as e:
print(f"Error loading wallpapers: {e}")
def _is_image(self, filename: str) -> bool:
"""Check if file is a supported image format."""
return filename.lower().endswith(
(".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
)
def _get_matugen_state(self) -> bool:
"""Get current matugen state from config.json."""
self.matugen_enabled = True # Default to True
try:
with open(data.CONFIG_FILE, "r") as f:
config = json.load(f)
self.matugen_enabled = config.get("matugen_enabled", True)
except FileNotFoundError:
# File doesn't exist, keep default True
pass
except Exception as e:
print(f"Error reading config file: {e}")
# Keep default True on error
return self.matugen_enabled
def _set_matugen_state(self, enabled: bool):
"""Set matugen state and save to config.json."""
self.matugen_enabled = enabled
# Save the state to config.json
try:
# Read current config
config = {}
if os.path.exists(data.CONFIG_FILE):
with open(data.CONFIG_FILE, "r") as f:
config = json.load(f)
# Update matugen state
config["matugen_enabled"] = enabled
# Write back to config file
with open(data.CONFIG_FILE, "w") as f:
json.dump(config, f, indent=4)
except Exception as e:
print(f"Error writing matugen state to config: {e}")
# # Send notification
# status = "enabled" if enabled else "disabled"
# exec_shell_command_async(
# f"notify-send '🎨 Matugen' 'Dynamic colors {status}' -a '{
# data.APP_NAME_CAP
# }' -e"
# )
def _get_cache_path(self, filename: str) -> str:
"""Get cache path for wallpaper thumbnail."""
file_hash = hashlib.md5(filename.encode("utf-8")).hexdigest()
return os.path.join(self.cache_dir, f"{file_hash}.png")
def _create_thumbnail(self, filename: str) -> str:
"""Create thumbnail for wallpaper if it doesn't exist."""
full_path = os.path.join(data.WALLPAPERS_DIR, filename)
cache_path = self._get_cache_path(filename)
if not os.path.exists(cache_path):
try:
with Image.open(full_path) as img:
# Use faster thumbnail creation with smaller size for better performance
img.thumbnail((32, 32), Image.Resampling.LANCZOS)
img.save(cache_path, "PNG", optimize=True)
except Exception as e:
print(f"Error creating thumbnail for {filename}: {e}")
return None
return cache_path
def _start_background_thumbnail_creation(self):
"""Start background thread to create thumbnails for all wallpapers."""
def create_thumbnails():
for wallpaper in self.wallpapers:
if wallpaper not in self.thumbnail_loading:
self.thumbnail_loading.add(wallpaper)
try:
cache_path = self._create_thumbnail(wallpaper)
if cache_path and os.path.exists(cache_path):
# Load thumbnail into memory cache
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
cache_path, 32, 32, True
)
self.thumbnail_cache[wallpaper] = pixbuf
except Exception as e:
print(f"Error loading thumbnail for {wallpaper}: {e}")
self.thumbnail_cache[wallpaper] = None
else:
self.thumbnail_cache[wallpaper] = None
except Exception as e:
print(f"Error processing thumbnail for {wallpaper}: {e}")
self.thumbnail_cache[wallpaper] = None
finally:
self.thumbnail_loading.discard(wallpaper)
# Small delay to prevent overwhelming the system
time.sleep(0.01)
# Start background thread
thread = threading.Thread(target=create_thumbnails, daemon=True)
thread.start()
def _get_thumbnail_fast(self, filename: str) -> Optional[GdkPixbuf.Pixbuf]:
"""Get thumbnail quickly from cache or return None if not ready."""
# Return cached thumbnail if available
if filename in self.thumbnail_cache:
return self.thumbnail_cache[filename]
# Check if thumbnail file exists and load it immediately
cache_path = self._get_cache_path(filename)
if os.path.exists(cache_path):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
cache_path, 32, 32, True
)
self.thumbnail_cache[filename] = pixbuf
return pixbuf
except Exception as e:
print(f"Error loading thumbnail for {filename}: {e}")
self.thumbnail_cache[filename] = None
return None
# If not in cache and file doesn't exist, trigger background creation
if filename not in self.thumbnail_loading:
self.thumbnail_loading.add(filename)
def create_async():
try:
cache_path = self._create_thumbnail(filename)
if cache_path and os.path.exists(cache_path):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
cache_path, 32, 32, True
)
self.thumbnail_cache[filename] = pixbuf
except Exception as e:
print(f"Error loading thumbnail for {filename}: {e}")
self.thumbnail_cache[filename] = None
else:
self.thumbnail_cache[filename] = None
except Exception as e:
print(f"Error creating thumbnail for {filename}: {e}")
self.thumbnail_cache[filename] = None
finally:
self.thumbnail_loading.discard(filename)
thread = threading.Thread(target=create_async, daemon=True)
thread.start()
return None
def _set_wallpaper(self, filename: str, scheme: str = None):
"""Set wallpaper with optional matugen integration."""
full_path = os.path.join(data.WALLPAPERS_DIR, filename)
current_wall = os.path.expanduser("~/.current.wall")
if scheme is None:
scheme = self._get_current_scheme()
# Update current wallpaper symlink
if os.path.isfile(current_wall) or os.path.islink(current_wall):
os.remove(current_wall)
os.symlink(full_path, current_wall)
# Apply wallpaper with or without matugen
matugen_enabled = self._get_matugen_state()
if matugen_enabled:
exec_shell_command_async(f'matugen image "{full_path}" -t {scheme}')
else:
exec_shell_command_async(
f'swww img "{
full_path
}" -t outer --transition-duration 1.5 --transition-step 255 --transition-fps 60 -f Nearest'
)
def _set_random_wallpaper(self):
"""Set a random wallpaper."""
if not self.wallpapers:
return
filename = random.choice(self.wallpapers)
self._set_wallpaper(filename)
# Show notification for immediate feedback
exec_shell_command_async(
f"notify-send '🎲 Random Wallpaper' 'Applied: {filename}' -a '{
data.APP_NAME_CAP
}' -e"
)
return filename
def _hsl_to_rgb_hex(self, h: float, s: float = 1.0, l: float = 0.5) -> str:
"""Convert HSL color value to RGB HEX string."""
# colorsys uses HLS, not HSL, and expects values between 0.0 and 1.0
hue = h / 360.0
r, g, b = colorsys.hls_to_rgb(hue, l, s) # Note the order: H, L, S
r_int, g_int, b_int = int(r * 255), int(g * 255), int(b * 255)
return f"#{r_int:02X}{g_int:02X}{b_int:02X}"
def _is_valid_hex_color(self, hex_color: str) -> bool:
"""Check if string is a valid hex color."""
if not hex_color.startswith("#"):
hex_color = "#" + hex_color
return bool(re.match(r"^#[0-9A-Fa-f]{6}$", hex_color))
def _get_current_scheme(self) -> str:
"""Get current color scheme from config (default to tonal-spot)."""
try:
with open(data.CONFIG_FILE, "r") as f:
config = json.load(f)
return config.get("current_scheme", "scheme-tonal-spot")
except FileNotFoundError:
# File doesn't exist, return default
return "scheme-tonal-spot"
except Exception as e:
print(f"Error reading current scheme from config: {e}")
return "scheme-tonal-spot"
def _set_current_scheme(self, scheme: str):
"""Set current color scheme and apply it."""
scheme_name = self.schemes.get(scheme, scheme)
matugen_enabled = self._get_matugen_state()
# Save the scheme to config
try:
# Read current config
config = {}
if os.path.exists(data.CONFIG_FILE):
with open(data.CONFIG_FILE, "r") as f:
config = json.load(f)
# Update current scheme
config["current_scheme"] = scheme
# Write back to config file
with open(data.CONFIG_FILE, "w") as f:
json.dump(config, f, indent=4)
except Exception as e:
print(f"Error saving current scheme to config: {e}")
return
# Apply the scheme to current wallpaper if matugen is enabled
if matugen_enabled:
current_wall = os.path.expanduser("~/.current.wall")
if os.path.exists(current_wall) and os.path.islink(current_wall):
# Get the current wallpaper path
wallpaper_path = os.readlink(current_wall)
if os.path.exists(wallpaper_path):
# Apply the new scheme to current wallpaper
exec_shell_command_async(f'matugen image "{wallpaper_path}" -t {scheme}')
# Send notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Applied {scheme_name} scheme' -a '{
data.APP_NAME_CAP
}' -e"
)
else:
# No current wallpaper, just show scheme change notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {scheme_name} (will apply to next wallpaper)' -a '{
data.APP_NAME_CAP
}' -e"
)
else:
# No current wallpaper, just show scheme change notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {scheme_name} (will apply to next wallpaper)' -a '{
data.APP_NAME_CAP
}' -e"
)
else:
# Matugen is disabled, just save the setting
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {scheme_name} (matugen disabled)' -a '{
data.APP_NAME_CAP
}' -e"
)
def _apply_hex_color(self, hex_color: str, scheme: str = None):
"""Apply hex color using matugen. Assumes matugen is enabled."""
if not hex_color.startswith("#"):
hex_color = "#" + hex_color
if scheme is None:
scheme = self._get_current_scheme()
exec_shell_command_async(f'matugen color hex "{hex_color}" -t {scheme}')
def _apply_hex_color_direct(self, hex_color: str, scheme: str = None):
"""Apply hex color using matugen (following example_wallpapers.py pattern)."""
if not hex_color.startswith("#"):
hex_color = "#" + hex_color
if scheme is None:
scheme = self._get_current_scheme()
exec_shell_command_async(f'matugen color hex "{hex_color}" -t {scheme}')
# Send notification
exec_shell_command_async(
f"notify-send '🎨 Hex Color Applied' 'Applied color: {hex_color}' -a '{
data.APP_NAME_CAP
}' -e"
)
def _generate_random_hex_color(self) -> str:
"""Generate a random hex color."""
hue = random.randint(0, 360)
return self._hsl_to_rgb_hex(hue)
def _get_status_indicators(self) -> tuple:
"""Get current status indicators for display."""
current_scheme = self._get_current_scheme()
matugen_enabled = self._get_matugen_state()
scheme_name = self.schemes.get(current_scheme, current_scheme)
indicators = []
if not matugen_enabled:
indicators.append("Matugen Off")
indicator_text = " • " + " • ".join(indicators) if indicators else ""
status_text = f"Matugen: {
'Enabled' if matugen_enabled else 'Disabled'
} • Scheme: {scheme_name}"
return indicator_text, status_text, current_scheme, matugen_enabled
def query(self, query_string: str) -> List[Result]:
"""Search wallpapers and provide management options."""
results = []
query = query_string.lower().strip()
# Get status indicators for consistent display
indicator_text, status_text, current_scheme, matugen_enabled = (
self._get_status_indicators()
)
# Special commands
if query.strip() == "random":
# Show result for random wallpaper (execute on Enter)
results.append(
Result(
title=f"Random Wallpaper{indicator_text}",
subtitle=f"Set a random wallpaper • {status_text}",
icon_markup=random.choice(
[
icons.dice_1,
icons.dice_2,
icons.dice_3,
icons.dice_4,
icons.dice_5,
icons.dice_6,
]
),
action=lambda: self._set_random_wallpaper(),
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "random",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif query.startswith("random") and query.strip() != "random":
# Show suggestion for random wallpaper (partial match)
results.append(
Result(
title=f"Random Wallpaper{indicator_text}",
subtitle=f"Set a random wallpaper • {status_text}",
icon_markup=random.choice(
[
icons.dice_1,
icons.dice_2,
icons.dice_3,
icons.dice_4,
icons.dice_5,
icons.dice_6,
]
),
action=lambda: self._set_random_wallpaper(),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "random_suggestion",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
# Hex color commands
if "color" in query or "hex" in query or query.startswith("#"):
# Check for scheme specification in the query
scheme = self._get_current_scheme()
for scheme_id, scheme_name in self.schemes.items():
if (
scheme_name.lower() in query.lower()
or scheme_id.lower() in query.lower()
):
scheme = scheme_id
break
# Check for hex color patterns
hex_match = re.search(r"#?([0-9A-Fa-f]{6})", query)
if hex_match:
hex_color = "#" + hex_match.group(1)
scheme_name = self.schemes.get(scheme, scheme)
# Check if this is a complete hex color input (6 digits)
# Execute immediately when we have a complete 6-digit hex color
if len(hex_match.group(1)) == 6:
# Check if there's additional text after the hex color
hex_end_pos = hex_match.end()
remaining_text = query[hex_end_pos:].strip()
# Only show result if no additional text after hex color (exact match)
if not remaining_text:
# Hex colors work when matugen is OFF (following example_wallpapers.py pattern)
if not matugen_enabled:
# Show result for hex color (execute on Enter)
results.append(
Result(
title=f"Apply Hex Color: {hex_color}{
indicator_text
}",
subtitle=f"Apply with {scheme_name} scheme • {
status_text
}",
icon_markup=icons.palette,
action=lambda c=hex_color, s=scheme: self._apply_hex_color_direct(c, s),
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "hex_color",
"color": hex_color,
"scheme": scheme,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Show error result when matugen is enabled
results.append(
Result(
title=f"Cannot Apply Hex Color: {hex_color}{
indicator_text
}",
subtitle="Matugen is enabled • Disable matugen to use hex colors",
icon_markup=icons.palette,
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "hex_color_failed",
"color": hex_color,
"bypass_max_results": True,
},
)
)
else:
# Show suggestion for hex color with additional text (partial match)
results.append(
Result(
title=f"Apply Hex Color: {hex_color}{indicator_text}",
subtitle=f"Apply with {scheme_name} scheme • {
status_text
}",
icon_markup=icons.palette,
action=lambda c=hex_color, s=scheme: self._apply_hex_color_direct(c, s)
if not matugen_enabled
else None,
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "hex_color_suggestion",
"color": hex_color,
"scheme": scheme,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Incomplete hex color, show as suggestion
results.append(
Result(
title=f"Hex Color (incomplete): {hex_color}",
subtitle="Complete the 6-digit hex color to apply",
icon_markup=icons.palette,
action=lambda: None,
relevance=0.7,
plugin_name=self.display_name,
data={
"action": "hex_color_incomplete",
"color": hex_color,
"bypass_max_results": True,
},
)
)
elif "random" in query and ("color" in query or "hex" in query):
# Check for exact matches for random color commands
if (
query.strip() == "color random"
or query.strip() == "hex random"
or query.strip() == "random color"
or query.strip() == "random hex"
):
# Random hex color - show result (execute on Enter)
scheme_name = self.schemes.get(scheme, scheme)
# Check if matugen is disabled (hex colors work when matugen is OFF)
if not matugen_enabled:
results.append(
Result(
title=f"Random Hex Color{indicator_text}",
subtitle=f"Generate and apply random color with {scheme_name} scheme • {status_text}",
icon_markup=icons.palette,
action=lambda s=scheme: self._apply_hex_color_direct(
self._generate_random_hex_color(), s
),
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "random_hex",
"scheme": scheme,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Show error result when matugen is enabled
results.append(
Result(
title=f"Cannot Apply Random Color{indicator_text}",
subtitle="Matugen is enabled • Disable matugen to use hex colors",
icon_markup=icons.palette,
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "random_hex_failed",
"bypass_max_results": True,
},
)
)
else:
# Show suggestion for random hex color (partial match)
results.append(
Result(
title=f"Random Hex Color{indicator_text}",
subtitle=f"Generate and apply random color • {status_text}",
icon_markup=icons.palette,
action=lambda s=scheme: self._apply_hex_color_direct(
self._generate_random_hex_color(), s
)
if not matugen_enabled
else None,
relevance=0.8,
plugin_name=self.display_name,
data={
"action": "random_hex_suggestion",
"scheme": scheme,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Show hex color help
results.append(
Result(
title="Hex Color Commands",
subtitle="Use: color #FF5733, hex #00FF00, color random, or add scheme name",
icon_markup=icons.palette,
action=lambda: None,
relevance=0.8,
plugin_name=self.display_name,
data={"action": "hex_help", "bypass_max_results": True},
)
)
# Color scheme commands
if "scheme" in query:
current_scheme = self._get_current_scheme()
matugen_enabled = self._get_matugen_state()
# Show all available schemes
for scheme_id, scheme_name in self.schemes.items():
# Check if this scheme matches the query (for filtering)
if query.strip() == "scheme":
# Show all schemes when just "scheme" is typed
scheme_matches = True
else:
# Extract search terms from query (remove "scheme" keyword)
search_terms = query.replace("scheme", "").strip().split()
scheme_matches = False
# Check if any search term matches the scheme name or ID
for term in search_terms:
if (
term.lower() in scheme_name.lower()
or term.lower() in scheme_id.lower()
):
scheme_matches = True
break
if scheme_matches:
# Create indicators
indicators = []
if scheme_id == current_scheme:
indicators.append("Current")
if not matugen_enabled:
indicators.append("Matugen Off")
indicator_text = (
" • " + " • ".join(indicators) if indicators else ""
)
results.append(
Result(
title=f"{scheme_name}{indicator_text}",
subtitle=f"Set color scheme to {scheme_name}"
+ (
" (requires matugen enabled)"
if not matugen_enabled
else ""
),
icon_markup=icons.palette,
action=lambda s=scheme_id: self._set_current_scheme(s),
relevance=1.0 if scheme_id == current_scheme else 0.8,
plugin_name=self.display_name,
data={
"action": "scheme_select",
"scheme": scheme_id,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
# Matugen controls
if "matugen" in query:
current_state = self._get_matugen_state()
# Check for exact command matches
if query.strip() == "matugen on" or query.strip() == "matugen enable":
# Show result for enabling matugen (execute on Enter)
results.append(
Result(
title=f"Enable Matugen{indicator_text}",
subtitle=f"Turn on dynamic color generation • {status_text}",
icon_markup=icons.palette,
action=lambda: self._set_matugen_state(True),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "matugen_on",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif query.strip() == "matugen off" or query.strip() == "matugen disable":
# Show result for disabling matugen (execute on Enter)
results.append(
Result(
title=f"Disable Matugen{indicator_text}",
subtitle=f"Turn off dynamic color generation • {status_text}",
icon_markup=icons.palette,
action=lambda: self._set_matugen_state(False),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "matugen_off",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif query.strip() == "matugen toggle":
# Show result for toggling matugen (execute on Enter)
new_state = not current_state
results.append(
Result(
title=f"Toggle Matugen to {
'Enabled' if new_state else 'Disabled'
}{indicator_text}",
subtitle=f"Switch matugen to {
'enabled' if new_state else 'disabled'
} • {status_text}",
icon_markup=icons.palette,
action=lambda: self._set_matugen_state(new_state),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "matugen_toggle",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif ("on" in query or "enable" in query) and not query.strip().endswith(
("on", "enable")
):
# Show suggestion for enabling (partial match)
results.append(
Result(
title=f"Enable Matugen{indicator_text}",
subtitle=f"Turn on dynamic color generation • {status_text}",
icon_markup=icons.palette,
action=lambda: self._set_matugen_state(True),
relevance=0.8,
plugin_name=self.display_name,
data={
"action": "matugen_on_suggestion",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif ("off" in query or "disable" in query) and not query.strip().endswith(
("off", "disable")
):
# Show suggestion for disabling (partial match)
results.append(
Result(
title=f"Disable Matugen{indicator_text}",
subtitle=f"Turn off dynamic color generation • {status_text}",
icon_markup=icons.palette,
action=lambda: self._set_matugen_state(False),
relevance=0.8,
plugin_name=self.display_name,
data={
"action": "matugen_off_suggestion",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif "toggle" in query and not query.strip().endswith("toggle"):
# Show suggestion for toggling (partial match)
results.append(
Result(
title=f"Toggle Matugen{indicator_text}",
subtitle=f"Switch matugen state • {status_text}",
icon_markup=icons.palette,
action=lambda: self._set_matugen_state(not current_state),
relevance=0.8,
plugin_name=self.display_name,
data={
"action": "matugen_toggle_suggestion",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Show current state with scheme info
results.append(
Result(
title=f"Matugen: {'Enabled' if current_state else 'Disabled'}{
indicator_text
}",
subtitle=f"Dynamic colors • {status_text}",
icon_markup=icons.palette,
action=lambda: None,
relevance=0.8,
plugin_name=self.display_name,
data={"action": "matugen_status", "bypass_max_results": True},
)
)
# Status command
if query == "status" or query == "info":
results.append(
Result(
title=f"Wallpaper System Status{indicator_text}",
subtitle=status_text,
icon_markup=icons.palette,
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"action": "status", "bypass_max_results": True},
)
)
# Handle empty query (just trigger keywords: wall, wallpaper, wp)
if not query:
# Show status and quick actions when just typing trigger keywords
results.append(
Result(
title=f"Wallpaper System{indicator_text}",
subtitle=f"{
status_text
} • Type commands: random, scheme, status, matugen",
icon_markup=icons.wallpapers,
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"action": "overview", "bypass_max_results": True},
)
)
# Show quick actions
results.append(
Result(
title=f"Random Wallpaper{indicator_text}",
subtitle=f"Set a random wallpaper • {status_text}",
icon_markup=random.choice(
[
icons.dice_1,
icons.dice_2,
icons.dice_3,
icons.dice_4,
icons.dice_5,
icons.dice_6,
]
),
action=lambda: self._set_random_wallpaper(),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "random_quick",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
# Search wallpapers by filename
if not query or (
query
and "matugen" not in query
and "random" not in query
and "scheme" not in query
and "status" not in query
and "info" not in query
and "color" not in query
and "hex" not in query
):
matching_wallpapers = []
for wallpaper in self.wallpapers:
if not query or query in wallpaper.lower():
# Calculate relevance
relevance = 1.0 if query == wallpaper.lower() else 0.7
if query and query in wallpaper.lower():
relevance = 0.8
matching_wallpapers.append((wallpaper, relevance))
# Sort by relevance and limit results for better performance
matching_wallpapers.sort(key=lambda x: x[1], reverse=True)
# Limit to first 50 results for performance (can be increased if needed)
max_results = 50 if query else 20 # Show fewer when no query to load faster
matching_wallpapers = matching_wallpapers[:max_results]
for wallpaper, relevance in matching_wallpapers:
# Use fast thumbnail loading
icon = self._get_thumbnail_fast(wallpaper)
results.append(
Result(
title=f"{wallpaper}{indicator_text if not query else ''}",
subtitle=f"Set as wallpaper{
' • ' + status_text if not query else ''
}",
icon=icon,
icon_markup=icons.wallpapers if not icon else None,
action=lambda w=wallpaper: self._set_wallpaper(w),
relevance=relevance,
plugin_name=self.display_name,
data={
"wallpaper": wallpaper,
"action": "set",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
# Sort by relevance - no limit since we use bypass_max_results
results.sort(key=lambda x: x.relevance, reverse=True)
return results
-543
View File
@@ -1,543 +0,0 @@
import colorsys
import concurrent.futures
import hashlib
import os
import random # <--- AÑADIDO
import shutil
from concurrent.futures import ThreadPoolExecutor
from fabric.utils.helpers import exec_shell_command_async
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.entry import Entry
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk, Pango
from PIL import Image
import config.config
import config.data as data
import modules.icons as icons
class WallpaperSelector(Box):
CACHE_DIR = f"{data.CACHE_DIR}/thumbs" # Changed from wallpapers to thumbs
def __init__(self, **kwargs):
# Delete the old cache directory if it exists
old_cache_dir = f"{data.CACHE_DIR}/wallpapers"
if os.path.exists(old_cache_dir):
shutil.rmtree(old_cache_dir)
super().__init__(name="wallpapers", spacing=4, orientation="v", h_expand=False, v_expand=False, **kwargs)
os.makedirs(self.CACHE_DIR, exist_ok=True)
# Process old wallpapers: use os.scandir for efficiency and only loop
# over image files that actually need renaming (they're not already lowercase
# and with hyphens instead of spaces)
with os.scandir(data.WALLPAPERS_DIR) as entries:
for entry in entries:
if entry.is_file() and self._is_image(entry.name):
# Check if the file needs renaming: file should be lowercase and have hyphens instead of spaces
if entry.name != entry.name.lower() or " " in entry.name:
new_name = entry.name.lower().replace(" ", "-")
full_path = os.path.join(data.WALLPAPERS_DIR, entry.name)
new_full_path = os.path.join(data.WALLPAPERS_DIR, new_name)
try:
os.rename(full_path, new_full_path)
print(f"Renamed old wallpaper '{full_path}' to '{new_full_path}'")
except Exception as e:
print(f"Error renaming file {full_path}: {e}")
# Refresh the file list after potential renaming
self.files = sorted([f for f in os.listdir(data.WALLPAPERS_DIR) if self._is_image(f)])
self.thumbnails = []
self.thumbnail_queue = []
self.executor = ThreadPoolExecutor(max_workers=4) # Shared executor
# Variable to control the selection (similar to AppLauncher)
self.selected_index = -1
# Initialize UI components
self.viewport = Gtk.IconView(name="wallpaper-icons")
self.viewport.set_model(Gtk.ListStore(GdkPixbuf.Pixbuf, str))
self.viewport.set_pixbuf_column(0)
# Hide text column so only the image is shown
self.viewport.set_text_column(-1)
self.viewport.set_item_width(0)
self.viewport.connect("item-activated", self.on_wallpaper_selected)
# self.viewport.connect("selection-changed", self._on_selection_changed) # Removed connection
self.scrolled_window = ScrolledWindow(
name="scrolled-window",
spacing=10,
h_expand=True,
v_expand=True,
h_align="fill",
v_align="fill",
child=self.viewport,
propagate_width=False,
propagate_height=False,
)
self.search_entry = Entry(
name="search-entry-walls",
placeholder="Search Wallpapers...",
h_expand=True,
h_align="fill",
notify_text=lambda entry, *_: self.arrange_viewport(entry.get_text()),
on_key_press_event=self.on_search_entry_key_press,
)
self.search_entry.props.xalign = 0.5
self.search_entry.connect("focus-out-event", self.on_search_entry_focus_out)
self.schemes = {
"scheme-tonal-spot": "Tonal Spot",
"scheme-content": "Content",
"scheme-expressive": "Expressive",
"scheme-fidelity": "Fidelity",
"scheme-fruit-salad": "Fruit Salad",
"scheme-monochrome": "Monochrome",
"scheme-neutral": "Neutral",
"scheme-rainbow": "Rainbow",
}
self.scheme_dropdown = Gtk.ComboBoxText()
self.scheme_dropdown.set_name("scheme-dropdown")
self.scheme_dropdown.set_tooltip_text("Select color scheme")
for key, display_name in self.schemes.items():
self.scheme_dropdown.append(key, display_name)
self.scheme_dropdown.set_active_id("scheme-tonal-spot")
self.scheme_dropdown.connect("changed", self.on_scheme_changed)
# Load matugen state from the dedicated file
self.matugen_enabled = True # Default to True
try:
with open(data.MATUGEN_STATE_FILE, 'r') as f:
content = f.read().strip().lower()
if content == "false":
self.matugen_enabled = False
elif content == "true":
self.matugen_enabled = True
# Any other content defaults to True
except FileNotFoundError:
# File doesn't exist, keep default True and create it on first toggle
pass
except Exception as e:
print(f"Error reading matugen state file: {e}")
# Keep default True on error
# Create a switcher to enable/disable Matugen (enabled by default)
self.matugen_switcher = Gtk.Switch(name="matugen-switcher")
self.matugen_switcher.set_tooltip_text("Toggle dynamic colors")
self.matugen_switcher.set_vexpand(False)
self.matugen_switcher.set_hexpand(False)
self.matugen_switcher.set_valign(Gtk.Align.CENTER)
self.matugen_switcher.set_halign(Gtk.Align.CENTER)
self.matugen_switcher.set_active(self.matugen_enabled)
self.matugen_switcher.connect("notify::active", self.on_switch_toggled)
self.mat_icon = Label(name="mat-label", markup=icons.palette)
self.random_wall = Button(
name="random-wall-button",
child=Label(name="random-wall-label", markup=icons.dice_1),
tooltip_text="Random Wallpaper",
)
self.random_wall.connect("clicked", self.set_random_wallpaper) # <--- AÑADIDO
# Add the switcher to the header_box's start_children
self.header_box = Box(
name="header-box",
spacing=8,
orientation="h",
children=[self.random_wall, self.search_entry, self.scheme_dropdown, self.matugen_switcher],
)
self.add(self.header_box)
# Create the custom color selector components
self.hue_slider = Gtk.Scale(
orientation=Gtk.Orientation.HORIZONTAL, # Changed from VERTICAL
adjustment=Gtk.Adjustment(value=0, lower=0, upper=360, step_increment=1, page_increment=10),
draw_value=False, # Hide the default value text
digits=0,
# inverted=True, # Removed inverted for horizontal
name="hue-slider", # For CSS styling
)
# Changed expand/align for horizontal orientation
self.hue_slider.set_hexpand(True)
self.hue_slider.set_halign(Gtk.Align.FILL)
self.hue_slider.set_vexpand(False) # Ensure it doesn't expand vertically
self.hue_slider.set_valign(Gtk.Align.CENTER) # Center vertically within its box
self.apply_color_button = Button(name="apply-color-button", child=Label(name="apply-color-label", markup=icons.accept))
self.apply_color_button.connect("clicked", self.on_apply_color_clicked)
self.apply_color_button.set_vexpand(False) # Ensure button doesn't expand vertically
self.apply_color_button.set_valign(Gtk.Align.CENTER) # Center button vertically
self.custom_color_selector_box = Box(
orientation="h", spacing=5, name="custom-color-selector-box", # Changed orientation to horizontal
h_align="center" # Center the horizontal box
)
self.custom_color_selector_box.add(self.hue_slider)
self.custom_color_selector_box.add(self.apply_color_button)
self.custom_color_selector_box.set_halign(Gtk.Align.FILL)
# Add the scrolled window (grid) and the custom color selector box directly
# to the main WallpaperSelector box (which is already vertical)
self.pack_start(self.scrolled_window, True, True, 0) # Add grid, expand
self.pack_start(self.custom_color_selector_box, False, False, 0) # Add custom selector, don't expand
# Removed the old main_content_box and its add
self._start_thumbnail_thread()
self.connect("map", self.on_map)
self.setup_file_monitor()
self.show_all()
self.randomize_dice_icon()
# Ensure the search entry gets focus when starting
self.search_entry.grab_focus()
def randomize_dice_icon(self):
dice_icons = [
icons.dice_1,
icons.dice_2,
icons.dice_3,
icons.dice_4,
icons.dice_5,
icons.dice_6,
]
chosen_icon = random.choice(dice_icons)
label = self.random_wall.get_child()
if isinstance(label, Label):
label.set_markup(chosen_icon)
def set_random_wallpaper(self, widget, external=False):
if not self.files:
print("No wallpapers available to set a random one.")
return
file_name = random.choice(self.files)
full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
selected_scheme = self.scheme_dropdown.get_active_id()
current_wall = os.path.expanduser(f"~/.current.wall")
if os.path.isfile(current_wall) or os.path.islink(current_wall): # Check for link too
os.remove(current_wall)
os.symlink(full_path, current_wall)
if self.matugen_switcher.get_active():
exec_shell_command_async(f'matugen image "{full_path}" -t {selected_scheme}')
else:
exec_shell_command_async(
f'swww img "{full_path}" -t outer --transition-duration 1.5 --transition-step 255 --transition-fps 60 -f Nearest'
)
print(f"Set random wallpaper: {file_name}")
if external:
exec_shell_command_async(f"notify-send '🎲 Wallpaper' 'Setting a random wallpaper 🎨' -a '{data.APP_NAME_CAP}' -i '{full_path}' -e")
self.randomize_dice_icon()
def setup_file_monitor(self):
gfile = Gio.File.new_for_path(data.WALLPAPERS_DIR)
self.file_monitor = gfile.monitor_directory(Gio.FileMonitorFlags.NONE, None)
self.file_monitor.connect("changed", self.on_directory_changed)
def on_directory_changed(self, monitor, file, other_file, event_type):
file_name = file.get_basename()
if event_type == Gio.FileMonitorEvent.DELETED:
if file_name in self.files:
self.files.remove(file_name)
cache_path = self._get_cache_path(file_name)
if os.path.exists(cache_path):
try:
os.remove(cache_path)
except Exception as e:
print(f"Error deleting cache {cache_path}: {e}")
self.thumbnails = [(p, n) for p, n in self.thumbnails if n != file_name]
GLib.idle_add(self.arrange_viewport, self.search_entry.get_text())
elif event_type == Gio.FileMonitorEvent.CREATED:
if self._is_image(file_name):
# Convert filename to lowercase and replace spaces with "-"
new_name = file_name.lower().replace(" ", "-")
full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
new_full_path = os.path.join(data.WALLPAPERS_DIR, new_name)
if new_name != file_name:
try:
os.rename(full_path, new_full_path)
file_name = new_name
print(f"Renamed file '{full_path}' to '{new_full_path}')")
except Exception as e:
print(f"Error renaming file {full_path}: {e}")
if file_name not in self.files:
self.files.append(file_name)
self.files.sort()
self.executor.submit(self._process_file, file_name)
elif event_type == Gio.FileMonitorEvent.CHANGED:
if self._is_image(file_name) and file_name in self.files:
cache_path = self._get_cache_path(file_name)
if os.path.exists(cache_path):
try:
os.remove(cache_path)
except Exception as e:
print(f"Error deleting cache for changed file {file_name}: {e}")
self.executor.submit(self._process_file, file_name)
def arrange_viewport(self, query: str = ""):
model = self.viewport.get_model()
model.clear()
filtered_thumbnails = [
(thumb, name)
for thumb, name in self.thumbnails
if query.casefold() in name.casefold()
]
filtered_thumbnails.sort(key=lambda x: x[1].lower())
for pixbuf, file_name in filtered_thumbnails:
model.append([pixbuf, file_name])
# If the search entry is empty, no icon is selected; otherwise, select the first one.
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()
file_name = model[path][1]
full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
selected_scheme = self.scheme_dropdown.get_active_id()
current_wall = os.path.expanduser(f"~/.current.wall")
if os.path.isfile(current_wall) or os.path.islink(current_wall):
os.remove(current_wall)
os.symlink(full_path, current_wall)
if self.matugen_switcher.get_active():
# Matugen is enabled: run the normal command.
exec_shell_command_async(f'matugen image "{full_path}" -t {selected_scheme}')
else:
# Matugen is disabled: run the alternative swww command.
exec_shell_command_async(
f'swww img "{full_path}" -t outer --transition-duration 1.5 --transition-step 255 --transition-fps 60 -f Nearest'
)
def on_scheme_changed(self, combo):
selected_scheme = combo.get_active_id()
print(f"Color scheme selected: {selected_scheme}")
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
new_index = (current_index - 1) % len(schemes_list) if event.keyval == Gdk.KEY_Up else (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
# Removed _on_selection_changed method
def move_selection_2d(self, keyval):
model = self.viewport.get_model()
total_items = len(model)
if total_items == 0:
return
# --- Determine Column Count ---
columns = self.viewport.get_columns()
# If get_columns returns 0 or -1 (auto), try to estimate by checking item rows
if columns <= 0 and total_items > 0:
estimated_cols = 0
try:
# Check the row of the first item (should be 0)
first_item_path = Gtk.TreePath.new_from_indices([0])
base_row = self.viewport.get_item_row(first_item_path)
# Find the index of the first item in the *next* row
for i in range(1, total_items):
path = Gtk.TreePath.new_from_indices([i])
row = self.viewport.get_item_row(path)
if row > base_row:
estimated_cols = i # The number of items in the first row
break
# If loop finished without finding a new row, all items are in one row
if estimated_cols == 0:
estimated_cols = total_items
columns = max(1, estimated_cols)
except Exception:
# Fallback if get_item_row fails (e.g., widget not realized)
columns = 1
elif columns <= 0 and total_items == 0:
columns = 1 # Should not happen due to early return, but safe
# Ensure columns is at least 1 after all checks
columns = max(1, columns)
# --- Navigation Logic ---
current_index = self.selected_index
new_index = current_index
if current_index == -1:
# If nothing is selected, select the first or last item based on direction
if keyval in (Gdk.KEY_Down, Gdk.KEY_Right):
new_index = 0
elif keyval in (Gdk.KEY_Up, Gdk.KEY_Left):
new_index = total_items - 1
if total_items == 0: new_index = -1 # Handle edge case
else:
# Calculate potential new index based on key press
if keyval == Gdk.KEY_Up:
potential_new_index = current_index - columns
# Only update if the new index is valid (>= 0)
if potential_new_index >= 0:
new_index = potential_new_index
elif keyval == Gdk.KEY_Down:
potential_new_index = current_index + columns
# Only update if the new index is valid (< total_items)
if potential_new_index < total_items:
new_index = potential_new_index
elif keyval == Gdk.KEY_Left:
# Only update if not already in the first column (index % columns != 0)
# and the index is greater than 0
if current_index > 0 and current_index % columns != 0:
new_index = current_index - 1
elif keyval == Gdk.KEY_Right:
# Only update if not in the last column ((index + 1) % columns != 0)
# and not the very last item (index < total_items - 1)
if current_index < total_items - 1 and (current_index + 1) % columns != 0:
new_index = current_index + 1
# Only update if the index actually changed and is valid
if new_index != self.selected_index and 0 <= new_index < total_items:
self.update_selection(new_index)
elif total_items > 0 and self.selected_index == -1 and 0 <= new_index < total_items:
# Handle selecting the first item when starting from -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) # Ensure the selected icon is visible
self.selected_index = new_index
def _start_thumbnail_thread(self):
thread = GLib.Thread.new("thumbnail-loader", self._preload_thumbnails, None)
def _preload_thumbnails(self, _data):
futures = [self.executor.submit(self._process_file, file_name) for file_name in self.files]
concurrent.futures.wait(futures)
GLib.idle_add(self._process_batch)
def _process_file(self, file_name):
full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
cache_path = self._get_cache_path(file_name)
if not os.path.exists(cache_path):
try:
with Image.open(full_path) as img:
width, height = img.size
side = min(width, height)
left = (img.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
self.thumbnail_queue.append((cache_path, file_name))
GLib.idle_add(self._process_batch)
def _process_batch(self):
batch = self.thumbnail_queue[:10]
del self.thumbnail_queue[:10]
for cache_path, file_name in batch:
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(cache_path)
self.thumbnails.append((pixbuf, file_name))
self.viewport.get_model().append([pixbuf, file_name])
except Exception as e:
print(f"Error loading thumbnail {cache_path}: {e}")
if self.thumbnail_queue:
GLib.idle_add(self._process_batch)
return False
def _get_cache_path(self, file_name: str) -> str:
file_hash = hashlib.md5(file_name.encode("utf-8")).hexdigest()
return os.path.join(self.CACHE_DIR, f"{file_hash}.png")
@staticmethod
def _is_image(file_name: str) -> bool:
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 on_map(self, widget):
"""Handles the map signal to set initial visibility of the color selector."""
# Set visibility based on the loaded state when the widget becomes visible
self.custom_color_selector_box.set_visible(not self.matugen_enabled)
def hsl_to_rgb_hex(self, h: float, s: float = 1.0, l: float = 0.5) -> str:
"""Converts HSL color value to RGB HEX string."""
# colorsys uses HLS, not HSL, and expects values between 0.0 and 1.0
hue = h / 360.0
r, g, b = colorsys.hls_to_rgb(hue, l, s) # Note the order: H, L, S
r_int, g_int, b_int = int(r * 255), int(g * 255), int(b * 255)
return f"#{r_int:02X}{g_int:02X}{b_int:02X}"
def rgba_to_hex(self, rgba: Gdk.RGBA) -> str:
"""Converts Gdk.RGBA to a HEX color string."""
r = int(rgba.red * 255)
g = int(rgba.green * 255)
b = int(rgba.blue * 255)
return f"#{r:02X}{g:02X}{b:02X}"
def on_switch_toggled(self, switch, gparam):
"""Handles the toggling of the Matugen switch."""
is_active = switch.get_active()
self.matugen_enabled = is_active
# self.scheme_dropdown.set_sensitive(is_active)
self.custom_color_selector_box.set_visible(not is_active) # Toggle visibility
# Save the state to the dedicated file
try:
with open(data.MATUGEN_STATE_FILE, 'w') as f:
f.write(str(is_active))
except Exception as e:
print(f"Error writing matugen state file: {e}")
def on_apply_color_clicked(self, button):
"""Applies the color selected by the hue slider via matugen."""
hue_value = self.hue_slider.get_value() # Get value from 0-360
hex_color = self.hsl_to_rgb_hex(hue_value) # Convert HSL(hue, 1.0, 0.5) to HEX
print(f"Applying color from slider: H={hue_value}, HEX={hex_color}")
selected_scheme = self.scheme_dropdown.get_active_id()
# Run matugen with the chosen hex color and selected scheme
exec_shell_command_async(f'matugen color hex "{hex_color}" -t {selected_scheme}')
# Optionally save the chosen color to config if needed later
# config.config.bind_vars["matugen_hex_color"] = hex_color
# config.config.save_config() # Removed as save_config doesn't exist
+14 -14
View File
@@ -1,20 +1,20 @@
:vars {
--foreground: #e3e1e9;
--background: #121318;
--cursor: #e3e1e9;
--primary: #bac3ff;
--on-primary: #222c61;
--secondary: #c3c5dd;
--on-secondary: #2c2f42;
--tertiary: #e5bad8;
--on-tertiary: #44263e;
--surface: #121318;
--surface-bright: #38393f;
--foreground: #e2e2e9;
--background: #111318;
--cursor: #e2e2e9;
--primary: #adc6ff;
--on-primary: #102f60;
--secondary: #bfc6dc;
--on-secondary: #293041;
--tertiary: #debcdf;
--on-tertiary: #402843;
--surface: #111318;
--surface-bright: #37393e;
--error: #ffb4ab;
--error-dim: #ff8678;
--on-error: #690005;
--error-container: #93000a;
--outline: #90909a;
--outline: #8e9099;
--shadow: #000000;
--red: #ffb2b9;
--red-dim: #ff7f8b;
@@ -22,8 +22,8 @@
--green-dim: #70c789;
--yellow: #b8cf84;
--yellow-dim: #a3c15f;
--blue: #bcc2ff;
--blue-dim: #8994ff;
--blue: #b6c4ff;
--blue-dim: #839bff;
--magenta: #e4b7f3;
--magenta-dim: #d48bec;
--cyan: #82d3e2;