630 lines
26 KiB
Python
630 lines
26 KiB
Python
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)
|
|
|
|
self.files = []
|
|
GLib.idle_add(self._load_wallpapers_async().__next__)
|
|
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 _load_wallpapers_async(self):
|
|
"""Non-blocking wallpaper processing."""
|
|
|
|
# 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}")
|
|
yield
|
|
|
|
# Process files in small batches to keep UI responsive
|
|
file_list = os.listdir(data.WALLPAPERS_DIR)
|
|
batch_size = 20
|
|
|
|
# Process files in batches
|
|
for i in range(0, len(file_list), batch_size):
|
|
batch = file_list[i : i + batch_size]
|
|
for filename in batch:
|
|
if self._is_image(filename):
|
|
self.files.append(filename)
|
|
|
|
# Sort the current batch to maintain order
|
|
self.files.sort()
|
|
|
|
# Yield to let the main loop process events
|
|
yield True
|
|
|
|
# Final sort of the complete list
|
|
self.files.sort()
|
|
|
|
# Start thumbnail loading after files are processed
|
|
self._start_thumbnail_thread()
|
|
|
|
# Return False to stop the idle callback
|
|
yield False
|
|
|
|
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'awww 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 awww command.
|
|
exec_shell_command_async(
|
|
f'awww 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
|