Commit 420364fc authored by Kirill Unitsaev's avatar Kirill Unitsaev

widgets: init theme_chooser widget

parent 244440d3
...@@ -39,6 +39,7 @@ class BaseSetting: ...@@ -39,6 +39,7 @@ class BaseSetting:
self.search_target = setting_data.get('search_target', None) self.search_target = setting_data.get('search_target', None)
self.map = setting_data.get('map') self.map = setting_data.get('map')
self.previews = setting_data.get('previews')
self.prepare_map() self.prepare_map()
......
from gi.repository import Gtk, Adw, GObject, Gdk
from .BaseWidget import BaseWidget
class ThemeChooserWidget(BaseWidget):
"""
Widget for choosing themes with visual previews.
YAML usage:
- name: Style
type: theme_chooser
gtype: string
backend: gsettings
key: org.gnome.desktop.interface.color-scheme
default: "default"
map:
Light: prefer-light
Dark: prefer-dark
previews: # optional, auto-generates if not specified
prefer-light: {tuneit_images}/light.png
prefer-dark: {tuneit_images}/dark.png
Path templates:
{tuneit_images} - expands to resource:///ru.ximperlinux.TuneIt/images
Example: {tuneit_images}/theme-dark.png
"""
# Built-in color schemes for common theme types
THEME_COLORS = {
'prefer-light': ('#EBEBED', '#FFFFFF'), # Light theme
'prefer-dark': ('#222226', '#2E2E32'), # Dark theme
}
# Path template expansions
PATH_TEMPLATES = {
'{tuneit_images}': 'resource:///ru.ximperlinux.TuneIt/images',
}
@classmethod
def _expand_path(cls, path):
"""Expand path templates like {tuneit_images} to full resource paths."""
if path is None:
return None
for template, replacement in cls.PATH_TEMPLATES.items():
if template in path:
path = path.replace(template, replacement)
return path
def create_row(self):
self.row = Adw.PreferencesRow(
activatable=False,
focusable=False
)
content_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=12,
margin_top=12,
margin_bottom=12,
margin_start=12,
margin_end=12
)
self.row.set_child(content_box)
# Header with title and reset button
header_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL,
hexpand=True,
)
content_box.append(header_box)
title_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
hexpand=True,
spacing=2,
)
header_box.append(title_box)
title_label = Gtk.Label(
label=self.setting.name,
halign=Gtk.Align.START,
)
title_box.append(title_label)
if self.setting.help:
subtitle_label = Gtk.Label(
label=self.setting.help,
halign=Gtk.Align.START,
wrap=True,
)
subtitle_label.add_css_class("caption")
subtitle_label.add_css_class("dim-label")
title_box.append(subtitle_label)
self.reset_revealer.set_halign(Gtk.Align.END)
header_box.append(self.reset_revealer)
# FlowBox for theme cards
self.flowbox = Gtk.FlowBox(
homogeneous=True,
selection_mode=Gtk.SelectionMode.SINGLE,
min_children_per_line=2,
max_children_per_line=2,
row_spacing=12,
column_spacing=12,
)
content_box.append(self.flowbox)
self.theme_cards = {}
current_value = self.setting._get_backend_value()
previews = self.setting.previews or {}
for label, value in self.setting.map.items():
card = self._create_theme_card(label, value, previews.get(value))
flowbox_child = Gtk.FlowBoxChild()
flowbox_child.set_child(card)
flowbox_child.value = value
self.flowbox.append(flowbox_child)
self.theme_cards[value] = flowbox_child
if value == current_value:
self.flowbox.select_child(flowbox_child)
self.handler_id = self.flowbox.connect(
"child-activated", self._on_theme_selected)
self._update_reset_visibility()
return self.row
def _create_theme_card(self, label, value, preview_path=None):
"""Create a theme preview card."""
card = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=8,
width_request=120,
)
card.add_css_class("card")
# Preview area
preview_path = self._expand_path(preview_path)
if preview_path and preview_path.startswith('resource://'):
# Load image from resource
preview = Gtk.Picture()
preview.set_resource(preview_path.replace('resource://', ''))
preview.set_size_request(100, 90)
preview.set_content_fit(Gtk.ContentFit.COVER)
else:
# Generate color preview
preview = self._create_color_preview(value)
preview_frame = Gtk.Frame()
preview_frame.set_child(preview)
preview_frame.add_css_class("theme-preview-frame")
card_inner = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=6,
margin_top=8,
margin_bottom=8,
margin_start=8,
margin_end=8,
)
card_inner.append(preview_frame)
# Label
name_label = Gtk.Label(
label=label,
halign=Gtk.Align.CENTER,
)
name_label.add_css_class("caption")
card_inner.append(name_label)
card.append(card_inner)
return card
def _create_color_preview(self, value):
"""Create a colored preview box for the theme."""
preview = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
hexpand=True,
)
preview.set_size_request(100, 90)
colors = self.THEME_COLORS.get(value, ('#f6f5f4', '#deddda'))
bg_color, accent_color = colors
# Create a drawing area for the preview
drawing = Gtk.DrawingArea()
drawing.set_size_request(100, 90)
drawing.set_draw_func(self._draw_theme_preview,
(bg_color, accent_color))
preview.append(drawing)
return preview
def _draw_theme_preview(self, area, cr, width, height, colors):
"""Draw a simple theme preview."""
bg_color, accent_color = colors
# Parse colors
bg_rgba = Gdk.RGBA()
bg_rgba.parse(bg_color)
accent_rgba = Gdk.RGBA()
accent_rgba.parse(accent_color)
# Draw background
cr.set_source_rgba(bg_rgba.red, bg_rgba.green, bg_rgba.blue, 1.0)
cr.rectangle(0, 0, width, height)
cr.fill()
# Draw sidebar
sidebar_width = 28
cr.set_source_rgba(accent_rgba.red, accent_rgba.green,
accent_rgba.blue, 0.5)
cr.rectangle(0, 0, sidebar_width, height)
cr.fill()
# Draw header bar simulation
cr.set_source_rgba(accent_rgba.red, accent_rgba.green,
accent_rgba.blue, 1.0)
cr.rectangle(0, 0, width, 16)
cr.fill()
# Draw window controls (circles)
cr.set_source_rgba(bg_rgba.red, bg_rgba.green, bg_rgba.blue, 0.8)
for x in [width - 12, width - 24, width - 36]:
cr.arc(x, 8, 4, 0, 2 * 3.14159)
cr.fill()
# Draw sidebar items
cr.set_source_rgba(bg_rgba.red, bg_rgba.green, bg_rgba.blue, 0.6)
for y in [26, 40, 54, 68]:
cr.rectangle(6, y, sidebar_width - 12, 8)
cr.fill()
# Draw content lines
content_start = sidebar_width + 8
cr.set_source_rgba(accent_rgba.red, accent_rgba.green,
accent_rgba.blue, 0.6)
for y in [28, 46, 64]:
cr.rectangle(content_start, y, width - content_start - 8, 6)
cr.fill()
def update_display(self):
current_value = self.setting._get_backend_value()
with GObject.signal_handler_block(self.flowbox, self.handler_id):
if current_value in self.theme_cards:
self.flowbox.select_child(self.theme_cards[current_value])
self._update_reset_visibility()
def _on_theme_selected(self, flowbox, child):
value = child.value
self.setting._set_backend_value(value)
self._update_reset_visibility()
def _on_reset_clicked(self, button):
default_value = self.setting.default
if default_value is not None:
self.setting._set_backend_value(default_value)
if default_value in self.theme_cards:
with GObject.signal_handler_block(self.flowbox, self.handler_id):
self.flowbox.select_child(self.theme_cards[default_value])
self._update_reset_visibility()
def _update_reset_visibility(self):
current_value = self.setting._get_backend_value()
default_value = self.setting.default
self.reset_revealer.set_reveal_child(
current_value != default_value if default_value is not None else False
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment