widgets/theme_chooser: improve style

parent 82236502
from gi.repository import Gtk, Adw, GObject, Gdk import math
from gi.repository import Gtk, Adw, Gdk
from .BaseWidget import BaseWidget from .BaseWidget import BaseWidget
...@@ -98,7 +99,7 @@ class ThemeChooserWidget(BaseWidget): ...@@ -98,7 +99,7 @@ class ThemeChooserWidget(BaseWidget):
# FlowBox for theme cards # FlowBox for theme cards
self.flowbox = Gtk.FlowBox( self.flowbox = Gtk.FlowBox(
homogeneous=True, homogeneous=True,
selection_mode=Gtk.SelectionMode.SINGLE, selection_mode=Gtk.SelectionMode.NONE,
min_children_per_line=2, min_children_per_line=2,
max_children_per_line=2, max_children_per_line=2,
row_spacing=12, row_spacing=12,
...@@ -107,14 +108,22 @@ class ThemeChooserWidget(BaseWidget): ...@@ -107,14 +108,22 @@ class ThemeChooserWidget(BaseWidget):
content_box.append(self.flowbox) content_box.append(self.flowbox)
self.theme_cards = {} self.theme_cards = {}
self.handler_id = self.flowbox.connect("child-activated", self._on_theme_selected) self.border_overlays = {}
self.flowbox.connect("child-activated", self._on_theme_selected)
# Subscribe to accent color changes
style_manager = Adw.StyleManager.get_default()
style_manager.connect("notify::accent-color-rgba", self._on_accent_color_changed)
self._build_cards() self._build_cards()
self._update_reset_visibility() self._update_reset_visibility()
return self.row return self.row
def _create_theme_card(self, label, value, preview_path=None): def _create_theme_card(self, label, value, preview_path=None):
"""Create a theme preview card.""" """Create a theme preview card with selection border overlay."""
overlay = Gtk.Overlay()
card = Gtk.Box( card = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, orientation=Gtk.Orientation.VERTICAL,
spacing=8, spacing=8,
...@@ -134,10 +143,6 @@ class ThemeChooserWidget(BaseWidget): ...@@ -134,10 +143,6 @@ class ThemeChooserWidget(BaseWidget):
# Generate color preview # Generate color preview
preview = self._create_color_preview(value) 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( card_inner = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, orientation=Gtk.Orientation.VERTICAL,
spacing=6, spacing=6,
...@@ -146,7 +151,7 @@ class ThemeChooserWidget(BaseWidget): ...@@ -146,7 +151,7 @@ class ThemeChooserWidget(BaseWidget):
margin_start=8, margin_start=8,
margin_end=8, margin_end=8,
) )
card_inner.append(preview_frame) card_inner.append(preview)
# Label # Label
name_label = Gtk.Label( name_label = Gtk.Label(
...@@ -157,7 +162,36 @@ class ThemeChooserWidget(BaseWidget): ...@@ -157,7 +162,36 @@ class ThemeChooserWidget(BaseWidget):
card_inner.append(name_label) card_inner.append(name_label)
card.append(card_inner) card.append(card_inner)
return card overlay.set_child(card)
# Selection border overlay
border_area = Gtk.DrawingArea()
border_area.set_can_target(False)
border_area.set_draw_func(self._draw_selection_border, value)
overlay.add_overlay(border_area)
self.border_overlays[value] = border_area
return overlay
def _draw_selection_border(self, area, cr, width, height, value):
if value != self.setting._get_backend_value():
return
rgba = Adw.StyleManager.get_default().get_accent_color_rgba()
r, w = 12, 3
cr.new_path()
cr.arc(width - r, r, r - w / 2, -math.pi / 2, 0)
cr.arc(width - r, height - r, r - w / 2, 0, math.pi / 2)
cr.arc(r, height - r, r - w / 2, math.pi / 2, math.pi)
cr.arc(r, r, r - w / 2, math.pi, 3 * math.pi / 2)
cr.close_path()
cr.set_source_rgba(rgba.red, rgba.green, rgba.blue, 1.0)
cr.set_line_width(w)
cr.stroke()
def _clear_flowbox(self): def _clear_flowbox(self):
while True: while True:
...@@ -166,6 +200,7 @@ class ThemeChooserWidget(BaseWidget): ...@@ -166,6 +200,7 @@ class ThemeChooserWidget(BaseWidget):
break break
self.flowbox.remove(child) self.flowbox.remove(child)
self.theme_cards = {} self.theme_cards = {}
self.border_overlays = {}
def _build_cards(self): def _build_cards(self):
if not self.setting.map: if not self.setting.map:
...@@ -174,7 +209,6 @@ class ThemeChooserWidget(BaseWidget): ...@@ -174,7 +209,6 @@ class ThemeChooserWidget(BaseWidget):
self.flowbox.append(placeholder) self.flowbox.append(placeholder)
return return
current_value = self.setting._get_backend_value()
previews = self.setting.previews or {} previews = self.setting.previews or {}
for label, value in self.setting.map.items(): for label, value in self.setting.map.items():
...@@ -185,10 +219,6 @@ class ThemeChooserWidget(BaseWidget): ...@@ -185,10 +219,6 @@ class ThemeChooserWidget(BaseWidget):
self.flowbox.append(flowbox_child) self.flowbox.append(flowbox_child)
self.theme_cards[value] = flowbox_child self.theme_cards[value] = flowbox_child
if value == current_value:
with GObject.signal_handler_block(self.flowbox, self.handler_id):
self.flowbox.select_child(flowbox_child)
def _create_color_preview(self, value): def _create_color_preview(self, value):
"""Create a colored preview box for the theme.""" """Create a colored preview box for the theme."""
preview = Gtk.Box( preview = Gtk.Box(
...@@ -209,61 +239,68 @@ class ThemeChooserWidget(BaseWidget): ...@@ -209,61 +239,68 @@ class ThemeChooserWidget(BaseWidget):
preview.append(drawing) preview.append(drawing)
return preview return preview
def _rounded_rect(self, cr, x, y, w, h, r):
"""Draw a rounded rectangle path."""
cr.new_path()
cr.arc(x + w - r, y + r, r, -math.pi / 2, 0)
cr.arc(x + w - r, y + h - r, r, 0, math.pi / 2)
cr.arc(x + r, y + h - r, r, math.pi / 2, math.pi)
cr.arc(x + r, y + r, r, math.pi, 3 * math.pi / 2)
cr.close_path()
def _draw_theme_preview(self, area, cr, width, height, colors): def _draw_theme_preview(self, area, cr, width, height, colors):
"""Draw a simple theme preview.""" """Draw a window mockup preview."""
bg_color, accent_color = colors bg_color, headerbar_color = colors
bg = Gdk.RGBA()
bg.parse(bg_color)
hb = Gdk.RGBA()
hb.parse(headerbar_color)
# Parse colors r = 8
bg_rgba = Gdk.RGBA() header_h = 24
bg_rgba.parse(bg_color) margin = 4
accent_rgba = Gdk.RGBA()
accent_rgba.parse(accent_color)
# Draw background # Window shadow
cr.set_source_rgba(bg_rgba.red, bg_rgba.green, bg_rgba.blue, 1.0) cr.set_source_rgba(0, 0, 0, 0.15)
cr.rectangle(0, 0, width, height) self._rounded_rect(cr, margin + 2, margin + 2, width - margin * 2, height - margin * 2, r)
cr.fill() cr.fill()
# Draw sidebar # Window background
sidebar_width = 28 cr.set_source_rgba(bg.red, bg.green, bg.blue, 1.0)
cr.set_source_rgba(accent_rgba.red, accent_rgba.green, self._rounded_rect(cr, margin, margin, width - margin * 2, height - margin * 2, r)
accent_rgba.blue, 0.5)
cr.rectangle(0, 0, sidebar_width, height)
cr.fill() cr.fill()
# Draw header bar simulation # Headerbar (top rounded corners only)
cr.set_source_rgba(accent_rgba.red, accent_rgba.green, cr.new_path()
accent_rgba.blue, 1.0) cr.arc(margin + r, margin + r, r, math.pi, 3 * math.pi / 2)
cr.rectangle(0, 0, width, 16) cr.arc(width - margin - r, margin + r, r, -math.pi / 2, 0)
cr.line_to(width - margin, margin + header_h)
cr.line_to(margin, margin + header_h)
cr.close_path()
cr.set_source_rgba(hb.red, hb.green, hb.blue, 1.0)
cr.fill() cr.fill()
# Draw window controls (circles) # Window controls
cr.set_source_rgba(bg_rgba.red, bg_rgba.green, bg_rgba.blue, 0.8) ctrl_y = margin + header_h / 2
for x in [width - 12, width - 24, width - 36]: cr.set_source_rgba(bg.red, bg.green, bg.blue, 0.6)
cr.arc(x, 8, 4, 0, 2 * 3.14159) for i, x in enumerate([width - margin - 14, width - margin - 26, width - margin - 38]):
cr.arc(x, ctrl_y, 4, 0, 2 * math.pi)
cr.fill() cr.fill()
# Draw sidebar items # Content area mockup
cr.set_source_rgba(bg_rgba.red, bg_rgba.green, bg_rgba.blue, 0.6) content_y = margin + header_h + 10
for y in [26, 40, 54, 68]: content_x = margin + 10
cr.rectangle(6, y, sidebar_width - 12, 8) content_w = width - margin * 2 - 20
cr.fill()
# Draw content lines cr.set_source_rgba(hb.red, hb.green, hb.blue, 0.4)
content_start = sidebar_width + 8 for i, y in enumerate([content_y, content_y + 14, content_y + 28]):
cr.set_source_rgba(accent_rgba.red, accent_rgba.green, w = content_w if i == 0 else content_w * (0.7 if i == 1 else 0.5)
accent_rgba.blue, 0.6) self._rounded_rect(cr, content_x, y, w, 8, 2)
for y in [28, 46, 64]:
cr.rectangle(content_start, y, width - content_start - 8, 6)
cr.fill() cr.fill()
def update_display(self): def update_display(self):
current_value = self.setting._get_backend_value() self._redraw_borders()
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() self._update_reset_visibility()
def on_map_updated(self): def on_map_updated(self):
...@@ -271,9 +308,19 @@ class ThemeChooserWidget(BaseWidget): ...@@ -271,9 +308,19 @@ class ThemeChooserWidget(BaseWidget):
self._build_cards() self._build_cards()
self._update_reset_visibility() self._update_reset_visibility()
def _redraw_borders(self):
"""Redraw all border overlays to update selection state."""
for border_area in self.border_overlays.values():
border_area.queue_draw()
def _on_accent_color_changed(self, style_manager, pspec):
"""Redraw borders when system accent color changes."""
self._redraw_borders()
def _on_theme_selected(self, flowbox, child): def _on_theme_selected(self, flowbox, child):
value = child.value value = child.value
self.setting._set_backend_value(value) self.setting._set_backend_value(value)
self._redraw_borders()
self._update_reset_visibility() self._update_reset_visibility()
def _on_reset_clicked(self, button): def _on_reset_clicked(self, button):
...@@ -282,10 +329,7 @@ class ThemeChooserWidget(BaseWidget): ...@@ -282,10 +329,7 @@ class ThemeChooserWidget(BaseWidget):
if default_value is not None: if default_value is not None:
self.setting._set_backend_value(default_value) self.setting._set_backend_value(default_value)
if default_value in self.theme_cards: self._redraw_borders()
with GObject.signal_handler_block(self.flowbox, self.handler_id):
self.flowbox.select_child(self.theme_cards[default_value])
self._update_reset_visibility() self._update_reset_visibility()
def _update_reset_visibility(self): def _update_reset_visibility(self):
......
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