Skip to content

feat(themes): Implement categorized theme menus and debug tools #12905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

kacperpaczos
Copy link
Contributor

  • Organizes themes into distinct categories (Mint-X, Mint-Y, etc.) with labeled separators
  • Groups theme variants (Light/Dark/Darker) within each category
  • Improves menu readability by fixing gray separator background issues
  • Enhances theme selection UX with logical grouping and clear visual hierarchy

- Organizes themes into distinct categories (Mint-X, Mint-Y, etc.) with labeled separators
- Groups theme variants (Light/Dark/Darker) within each category
- Improves menu readability by fixing gray separator background issues
- Enhances theme selection UX with logical grouping and clear visual hierarchy
@kacperpaczos
Copy link
Contributor Author

Before:
image

After:
image

@kacperpaczos
Copy link
Contributor Author

Before:
image

After:
image

@fredcw
Copy link
Contributor

fredcw commented May 26, 2025

I like it, but you can also end up with lots of themes on separate lines:

Screenshot from 2025-05-26 15-01-53

Maybe themes in categories that contain only 1 theme can all be grouped together at the top without a category title.

…d grouping

   - Implemented grouping of single-item categories under 'Other Themes' for improved UI clarity
   - Added translation support for 'Other Themes' label
   - Ensured themes are sorted by variant order: Light, Darker, Dark
   - Removed unnecessary separators between variants of the same category
@kacperpaczos
Copy link
Contributor Author

I believe I have fixed that. @fredcw

@mtwebster
Copy link
Member

Hi, the single themes being bunched together is good, but since they're listed first, I don't think we need an actual label for them.

Just:

theme_a theme_b theme_c
theme_d theme_e ...
...
Mint-Y
...
Mint-X
...

@fredcw
Copy link
Contributor

fredcw commented Jun 7, 2025

@kacperpaczos

I would write the icons bit like this as it avoids the duplication of code and simplifies it a bit. You could probably do something similar to the gtk/cinnamon section though that's not as long:

if path_suffix == 'icons':
            cache_folder = GLib.get_user_cache_dir() + '/cs_themes/'
            icon_cache_path = os.path.join(cache_folder, 'icons')

            # Retrieve list of known themes/locations for faster loading (icon theme loading and lookup are very slow)
            if os.path.exists(icon_cache_path):
                read_path = icon_cache_path
            else:
                read_path = '/usr/share/cinnamon/cinnamon-settings/icons'
            icon_paths = {}
            with open(read_path, 'r') as cache_file:
                for line in cache_file:
                    theme_name, icon_path = line.strip().split(':')
                    icon_paths[theme_name] = icon_path

            # Collect all themes with their categories and variants
            categorized_themes = []
            for theme in themes:
                category, variant = self.get_theme_category(theme, 'icon')
                categorized_themes.append({'name': theme, 'category': category, 'variant': variant})

            categorized_themes.sort(key=lambda x: (x['category'], variant_sort_order.get(x['variant'], 3), x['name']))

            grouped_themes = {"ungrouped": []}
            for theme_info in categorized_themes:
                if theme_info['category'] not in grouped_themes:
                    grouped_themes[theme_info['category']] = []
                grouped_themes[theme_info['category']].append(theme_info)
            
            for key, group in grouped_themes.items():
                if len(group) == 1:
                    grouped_themes['ungrouped'].append(group[0])
                    grouped_themes[key] = None

            # Reset column position at the start
            chooser.col = 0
            chooser.row = 0
            dump = False

            for key, group in grouped_themes.items():
                if group is None:
                    continue
                if key != 'ungrouped':
                    chooser.add_separator()
                    chooser.add_separator(key)
                    chooser.add_separator()

                for theme_info in group:
                    theme = theme_info['name']

                    theme_path = None
                    if theme in icon_paths:
                        # loop through all possible locations until we find a match
                        # (user folders should override system ones)
                        for theme_folder in ICON_FOLDERS:
                            possible_path = os.path.join(theme_folder, icon_paths[theme])
                            if os.path.exists(possible_path):
                                theme_path = possible_path
                                break
                    if theme_path is None:
                        icon_theme = Gtk.IconTheme()
                        icon_theme.set_custom_theme(theme)
                        folder = icon_theme.lookup_icon('folder', ICON_SIZE, Gtk.IconLookupFlags.FORCE_SVG)
                        if folder:
                            theme_path = folder.get_filename()
                            # we need to get the relative path for storage
                            for theme_folder in ICON_FOLDERS:
                                if os.path.commonpath([theme_folder, theme_path]) == theme_folder:
                                    icon_paths[theme] = os.path.relpath(theme_path, start=theme_folder)
                                    break
                            dump = True

                    if theme_path is None:
                        continue
                    if os.path.exists(theme_path):
                        chooser.add_picture(theme_path, callback, title=theme, id=theme)
                    GLib.timeout_add(5, self.increment_progress, (chooser, inc))
            if dump:
                if not os.path.exists(cache_folder):
                    os.mkdir(cache_folder)

                with open(icon_cache_path, 'w') as cache_file:
                    for theme_name, icon_path_val in icon_paths.items(): # Renamed icon_path to avoid conflict
                        cache_file.write('%s:%s\\n' % (theme_name, icon_path_val))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants