Source code for qtframework.themes.tokens

"""Design token system for theming."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from pydantic import BaseModel, Field


[docs] class PrimitiveColors(BaseModel): """Primitive color tokens - raw color values.""" # Grayscale gray_50: str = Field(default="#FAFAFA") gray_100: str = Field(default="#F5F5F5") gray_200: str = Field(default="#EEEEEE") gray_300: str = Field(default="#E0E0E0") gray_400: str = Field(default="#BDBDBD") gray_500: str = Field(default="#9E9E9E") gray_600: str = Field(default="#757575") gray_700: str = Field(default="#616161") gray_800: str = Field(default="#424242") gray_900: str = Field(default="#212121") gray_950: str = Field(default="#121212") # Primary colors (Blue by default) primary_50: str = Field(default="#E3F2FD") primary_100: str = Field(default="#BBDEFB") primary_200: str = Field(default="#90CAF9") primary_300: str = Field(default="#64B5F6") primary_400: str = Field(default="#42A5F5") primary_500: str = Field(default="#2196F3") primary_600: str = Field(default="#1E88E5") primary_700: str = Field(default="#1976D2") primary_800: str = Field(default="#1565C0") primary_900: str = Field(default="#0D47A1") primary_950: str = Field(default="#0A3A7F") # Secondary colors (Amber by default) secondary_50: str = Field(default="#FFF8E1") secondary_100: str = Field(default="#FFECB3") secondary_200: str = Field(default="#FFE082") secondary_300: str = Field(default="#FFD54F") secondary_400: str = Field(default="#FFCA28") secondary_500: str = Field(default="#FFC107") secondary_600: str = Field(default="#FFB300") secondary_700: str = Field(default="#FFA000") secondary_800: str = Field(default="#FF8F00") secondary_900: str = Field(default="#FF6F00") secondary_950: str = Field(default="#E65100") # Success (Green) success_50: str = Field(default="#E8F5E9") success_100: str = Field(default="#C8E6C9") success_200: str = Field(default="#A5D6A7") success_300: str = Field(default="#81C784") success_400: str = Field(default="#66BB6A") success_500: str = Field(default="#4CAF50") success_600: str = Field(default="#43A047") success_700: str = Field(default="#388E3C") success_800: str = Field(default="#2E7D32") success_900: str = Field(default="#1B5E20") success_950: str = Field(default="#0D4018") # Warning (Orange) warning_50: str = Field(default="#FFF3E0") warning_100: str = Field(default="#FFE0B2") warning_200: str = Field(default="#FFCC80") warning_300: str = Field(default="#FFB74D") warning_400: str = Field(default="#FFA726") warning_500: str = Field(default="#FF9800") warning_600: str = Field(default="#FB8C00") warning_700: str = Field(default="#F57C00") warning_800: str = Field(default="#EF6C00") warning_900: str = Field(default="#E65100") warning_950: str = Field(default="#BF360C") # Error (Red) error_50: str = Field(default="#FFEBEE") error_100: str = Field(default="#FFCDD2") error_200: str = Field(default="#EF9A9A") error_300: str = Field(default="#E57373") error_400: str = Field(default="#EF5350") error_500: str = Field(default="#F44336") error_600: str = Field(default="#E53935") error_700: str = Field(default="#D32F2F") error_800: str = Field(default="#C62828") error_900: str = Field(default="#B71C1C") error_950: str = Field(default="#7F0000") # Info (Cyan) info_50: str = Field(default="#E0F7FA") info_100: str = Field(default="#B2EBF2") info_200: str = Field(default="#80DEEA") info_300: str = Field(default="#4DD0E1") info_400: str = Field(default="#26C6DA") info_500: str = Field(default="#00BCD4") info_600: str = Field(default="#00ACC1") info_700: str = Field(default="#0097A7") info_800: str = Field(default="#00838F") info_900: str = Field(default="#006064") info_950: str = Field(default="#004D50") # Pure colors white: str = Field(default="#FFFFFF") black: str = Field(default="#000000") transparent: str = Field(default="transparent")
[docs] class SemanticColors(BaseModel): """Semantic color tokens - meaning-based references.""" # Background bg_primary: str = Field(default="") # Main background bg_secondary: str = Field(default="") # Secondary surfaces bg_tertiary: str = Field(default="") # Tertiary surfaces bg_elevated: str = Field(default="") # Elevated surfaces (cards, dialogs) bg_overlay: str = Field(default="") # Overlay background # Foreground/Text fg_primary: str = Field(default="") # Primary text fg_secondary: str = Field(default="") # Secondary text fg_tertiary: str = Field(default="") # Tertiary/disabled text fg_on_accent: str = Field(default="") # Text on accent colors fg_on_dark: str = Field(default="") # Text on dark backgrounds fg_on_light: str = Field(default="") # Text on light backgrounds # Interactive states action_primary: str = Field(default="") # Primary action color action_primary_hover: str = Field(default="") action_primary_active: str = Field(default="") action_secondary: str = Field(default="") # Secondary action color action_secondary_hover: str = Field(default="") action_secondary_active: str = Field(default="") # Feedback feedback_success: str = Field(default="") feedback_warning: str = Field(default="") feedback_error: str = Field(default="") feedback_info: str = Field(default="") # Borders border_default: str = Field(default="") border_subtle: str = Field(default="") border_strong: str = Field(default="") border_focus: str = Field(default="") # Special states state_hover: str = Field(default="") state_selected: str = Field(default="") state_disabled: str = Field(default="") state_focus: str = Field(default="")
[docs] class ComponentColors(BaseModel): """Component-specific color tokens.""" # Button button_primary_bg: str = Field(default="") button_primary_fg: str = Field(default="") button_primary_border: str = Field(default="") button_secondary_bg: str = Field(default="") button_secondary_fg: str = Field(default="") button_secondary_border: str = Field(default="") # Button textures (for image-based buttons with 9-slice) button_image: str = Field(default="") # Normal state button texture button_hover_image: str = Field(default="") # Hover/highlight state button_pressed_image: str = Field(default="") # Pressed/down state button_disabled_image: str = Field(default="") # Disabled state button_border_slice: str = Field(default="") # 9-slice values (e.g., "8 8 8 8") # Input input_bg: str = Field(default="") input_fg: str = Field(default="") input_border: str = Field(default="") input_placeholder: str = Field(default="") # Dropdown/ComboBox combobox_bg: str = Field(default="") # Background color combobox_fg: str = Field(default="") # Text color combobox_border: str = Field(default="") # Border color combobox_arrow_image: str = Field(default="") # Dropdown arrow button (normal) combobox_arrow_hover_image: str = Field(default="") # Dropdown arrow button (hover) combobox_arrow_pressed_image: str = Field(default="") # Dropdown arrow button (pressed) combobox_arrow_disabled_image: str = Field(default="") # Dropdown arrow button (disabled) combobox_arrow_width: int = Field(default=20) # Width of arrow button combobox_arrow_height: int = Field(default=20) # Height of arrow button # Table table_header_bg: str = Field(default="") table_header_fg: str = Field(default="") table_row_bg: str = Field(default="") table_row_bg_alt: str = Field(default="") table_row_hover: str = Field(default="") table_row_selected: str = Field(default="") table_border: str = Field(default="") # Tree/List branch indicators tree_branch_closed_image: str = Field( default="" ) # Tree branch indicator when closed (collapsed) tree_branch_open_image: str = Field(default="") # Tree branch indicator when open (expanded) tree_branch_closed_hover_image: str = Field( default="" ) # Tree branch indicator when closed and hovered tree_branch_open_hover_image: str = Field( default="" ) # Tree branch indicator when open and hovered tree_branch_closed_pressed_image: str = Field( default="" ) # Tree branch indicator when closed and pressed tree_branch_open_pressed_image: str = Field( default="" ) # Tree branch indicator when open and pressed # Menu menu_bg: str = Field(default="") menu_fg: str = Field(default="") menu_hover_bg: str = Field(default="") menu_hover_fg: str = Field(default="") menu_selected_bg: str = Field(default="") menu_selected_fg: str = Field(default="") # Scrollbar scrollbar_bg: str = Field(default="") scrollbar_thumb: str = Field(default="") scrollbar_thumb_hover: str = Field(default="") scrollbar_thumb_pressed: str = Field(default="") scrollbar_thumb_border: str = Field(default="") scrollbar_width: int | None = Field(default=None) # Custom width for vertical scrollbars scrollbar_height: int | None = Field(default=None) # Custom height for horizontal scrollbars # Scrollbar images (for textured scrollbars like WoW) scrollbar_bg_image: str = Field(default="") scrollbar_thumb_image: str = Field(default="") scrollbar_thumb_hover_image: str = Field(default="") scrollbar_thumb_pressed_image: str = Field(default="") # 9-slice border-image support (most advanced - prevents distortion) # Format: "top right bottom left" (pixels from edge that don't stretch) scrollbar_thumb_border_slice: str = Field(default="") # e.g., "4 4 4 4" scrollbar_thumb_hover_border_slice: str = Field(default="") scrollbar_thumb_pressed_border_slice: str = Field(default="") scrollbar_bg_border_slice: str = Field(default="") # Scrollbar arrow buttons scrollbar_arrow_bg: str = Field(default="") scrollbar_arrow_bg_hover: str = Field(default="") scrollbar_arrow_bg_pressed: str = Field(default="") scrollbar_up_arrow_image: str = Field(default="") scrollbar_down_arrow_image: str = Field(default="") scrollbar_left_arrow_image: str = Field(default="") scrollbar_right_arrow_image: str = Field(default="") scrollbar_arrow_size: int | None = Field(default=None) # Size of arrow buttons (square) scrollbar_arrow_width: int | None = Field( default=None ) # Width of vertical arrow buttons (overrides size) scrollbar_arrow_height: int | None = Field( default=None ) # Height of vertical arrow buttons (overrides size) scrollbar_arrow_width_horizontal: int | None = Field( default=None ) # Width of horizontal arrow buttons scrollbar_arrow_height_horizontal: int | None = Field( default=None ) # Height of horizontal arrow buttons # Tab tab_bg: str = Field(default="") tab_fg: str = Field(default="") tab_active_bg: str = Field(default="") tab_active_fg: str = Field(default="") tab_hover_bg: str = Field(default="") tab_hover_fg: str = Field(default="") # Chart/Graph colors chart_1: str = Field(default="") chart_2: str = Field(default="") chart_3: str = Field(default="") chart_4: str = Field(default="") chart_5: str = Field(default="") chart_6: str = Field(default="") chart_grid: str = Field(default="") chart_axis: str = Field(default="") # Badge colors badge_default_bg: str = Field(default="") badge_default_fg: str = Field(default="") badge_default_border: str = Field(default="") badge_primary_bg: str = Field(default="") badge_primary_fg: str = Field(default="") badge_primary_border: str = Field(default="") badge_secondary_bg: str = Field(default="") badge_secondary_fg: str = Field(default="") badge_secondary_border: str = Field(default="") badge_success_bg: str = Field(default="") badge_success_fg: str = Field(default="") badge_success_border: str = Field(default="") badge_warning_bg: str = Field(default="") badge_warning_fg: str = Field(default="") badge_warning_border: str = Field(default="") badge_danger_bg: str = Field(default="") badge_danger_fg: str = Field(default="") badge_danger_border: str = Field(default="") badge_info_bg: str = Field(default="") badge_info_fg: str = Field(default="") badge_info_border: str = Field(default="") badge_light_bg: str = Field(default="") badge_light_fg: str = Field(default="") badge_light_border: str = Field(default="") badge_dark_bg: str = Field(default="") badge_dark_fg: str = Field(default="") badge_dark_border: str = Field(default="")
[docs] class Typography(BaseModel): """Typography tokens.""" # Font families font_family_default: str = Field( default="'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica', sans-serif" ) font_family_mono: str = Field( default="'Cascadia Code', 'Consolas', 'Monaco', 'Courier New', monospace" ) font_family_code: str = Field( default="Consolas" # Single font name for Qt QFont ) # Font sizes font_size_xs: int = Field(default=11) font_size_sm: int = Field(default=12) font_size_md: int = Field(default=14) font_size_lg: int = Field(default=16) font_size_xl: int = Field(default=18) font_size_2xl: int = Field(default=20) font_size_3xl: int = Field(default=24) font_size_4xl: int = Field(default=28) font_size_5xl: int = Field(default=32) # Font weights font_weight_thin: int = Field(default=100) font_weight_light: int = Field(default=300) font_weight_normal: int = Field(default=400) font_weight_medium: int = Field(default=500) font_weight_semibold: int = Field(default=600) font_weight_bold: int = Field(default=700) font_weight_black: int = Field(default=900) # Line heights line_height_tight: float = Field(default=1.2) line_height_normal: float = Field(default=1.5) line_height_relaxed: float = Field(default=1.75) line_height_loose: float = Field(default=2.0)
[docs] class Spacing(BaseModel): """Spacing tokens.""" space_0: int = Field(default=0) space_1: int = Field(default=2) space_2: int = Field(default=4) space_3: int = Field(default=6) space_4: int = Field(default=8) space_5: int = Field(default=10) space_6: int = Field(default=12) space_8: int = Field(default=16) space_10: int = Field(default=20) space_12: int = Field(default=24) space_16: int = Field(default=32) space_20: int = Field(default=40) space_24: int = Field(default=48) space_32: int = Field(default=64)
[docs] class BorderRadius(BaseModel): """Border radius tokens.""" radius_none: int = Field(default=0) radius_sm: int = Field(default=2) radius_md: int = Field(default=4) radius_lg: int = Field(default=6) radius_xl: int = Field(default=8) radius_2xl: int = Field(default=12) radius_3xl: int = Field(default=16) radius_full: int = Field(default=9999)
[docs] class Shadows(BaseModel): """Shadow tokens.""" shadow_none: str = Field(default="none") shadow_sm: str = Field(default="0 1px 2px 0 rgba(0, 0, 0, 0.05)") shadow_md: str = Field(default="0 4px 6px -1px rgba(0, 0, 0, 0.1)") shadow_lg: str = Field(default="0 10px 15px -3px rgba(0, 0, 0, 0.1)") shadow_xl: str = Field(default="0 20px 25px -5px rgba(0, 0, 0, 0.1)") shadow_2xl: str = Field(default="0 25px 50px -12px rgba(0, 0, 0, 0.25)") shadow_inner: str = Field(default="inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)")
[docs] class Transitions(BaseModel): """Transition/animation tokens.""" transition_fast: str = Field(default="150ms ease-in-out") transition_normal: str = Field(default="250ms ease-in-out") transition_slow: str = Field(default="350ms ease-in-out")
[docs] class SyntaxColors(BaseModel): """Syntax highlighting color tokens.""" # Core syntax elements keyword: str = Field(default="#0000FF") class_name: str = Field(default="#267F99") function: str = Field(default="#795E26") string: str = Field(default="#A31515") comment: str = Field(default="#008000") number: str = Field(default="#098658") operator: str = Field(default="#000000") # Additional elements decorator: str = Field(default="#AA0000") constant: str = Field(default="#0000FF") variable: str = Field(default="#001080") parameter: str = Field(default="#001080") type: str = Field(default="#267F99") namespace: str = Field(default="#267F99") # Special error: str = Field(default="#FF0000") warning: str = Field(default="#FFA500") info: str = Field(default="#0000FF")
[docs] @dataclass class DesignTokens: """Complete design token system. Supports extension with custom token categories for app-specific needs. Custom tokens can be added via the 'custom' field or by adding new top-level categories in YAML that aren't part of the standard token set. Example: Add custom ping colors in your theme YAML: tokens: ping: excellent: "#00FF00" good: "#FFFF00" poor: "#FF0000" Access via: theme.tokens.get_custom("ping.excellent") """ primitive: PrimitiveColors = field(default_factory=PrimitiveColors) semantic: SemanticColors = field(default_factory=SemanticColors) components: ComponentColors = field(default_factory=ComponentColors) typography: Typography = field(default_factory=Typography) spacing: Spacing = field(default_factory=Spacing) borders: BorderRadius = field(default_factory=BorderRadius) shadows: Shadows = field(default_factory=Shadows) transitions: Transitions = field(default_factory=Transitions) syntax: SyntaxColors = field(default_factory=SyntaxColors) custom: dict[str, Any] = field(default_factory=dict) # For app-specific tokens
[docs] def apply_font_scale(self, scale_percent: int = 100) -> None: """Apply font scaling to all typography tokens. Args: scale_percent: Percentage to scale fonts (e.g., 100 = normal, 125 = 125%, 150 = 150%) """ if scale_percent == 100: return # No scaling needed scale_factor = scale_percent / 100.0 # Scale all font sizes self.typography.font_size_xs = int(self.typography.font_size_xs * scale_factor) self.typography.font_size_sm = int(self.typography.font_size_sm * scale_factor) self.typography.font_size_md = int(self.typography.font_size_md * scale_factor) self.typography.font_size_lg = int(self.typography.font_size_lg * scale_factor) self.typography.font_size_xl = int(self.typography.font_size_xl * scale_factor) self.typography.font_size_2xl = int(self.typography.font_size_2xl * scale_factor) self.typography.font_size_3xl = int(self.typography.font_size_3xl * scale_factor) self.typography.font_size_4xl = int(self.typography.font_size_4xl * scale_factor) self.typography.font_size_5xl = int(self.typography.font_size_5xl * scale_factor)
[docs] def resolve_token(self, token_path: str) -> str | None: """Resolve a token path to its value. Supports both standard and custom token paths. Args: token_path: Dot-separated path to token (e.g., "primitive.primary_500" or "ping.excellent") Returns: Token value or None if not found Example: >>> tokens.resolve_token("primitive.primary_500") "#2196F3" >>> tokens.resolve_token("ping.excellent") # custom token "#00FF00" """ parts = token_path.split(".") current: Any = self for i, part in enumerate(parts): if hasattr(current, part): current = getattr(current, part) elif isinstance(current, dict) and part in current: current = current[part] elif i == 0 and part in self.custom: # First part might be a custom token category current = self.custom[part] else: return None return str(current) if current is not None else None
[docs] def get_custom(self, token_path: str, default: Any = None) -> Any: """Get a custom token value by path. Convenience method for accessing custom tokens. Args: token_path: Dot-separated path within custom tokens (e.g., "ping.excellent") default: Default value if token not found Returns: Token value or default Example: >>> tokens.get_custom("ping.excellent", "#00FF00") "#00FF00" """ parts = token_path.split(".") current: Any = self.custom for part in parts: if isinstance(current, dict) and part in current: current = current[part] else: return default return current if current is not None else default
[docs] def resolve_semantic_colors(self) -> None: """Resolve semantic color references to actual values.""" # This method would resolve semantic tokens that reference primitive tokens # For example, if semantic.bg_primary = "{primitive.gray_50}" # It would resolve it to the actual color value for attr_name in dir(self.semantic): if not attr_name.startswith("_"): value = getattr(self.semantic, attr_name) if isinstance(value, str) and value.startswith("{") and value.endswith("}"): token_path = value[1:-1] # Remove braces resolved = self.resolve_token(token_path) if resolved: setattr(self.semantic, attr_name, resolved)
[docs] def to_dict(self) -> dict[str, Any]: """Convert tokens to dictionary format. Includes both standard and custom token categories. """ result = { "primitive": self.primitive.model_dump(), "semantic": self.semantic.model_dump(), "components": self.components.model_dump(), "typography": self.typography.model_dump(), "spacing": self.spacing.model_dump(), "borders": self.borders.model_dump(), "shadows": self.shadows.model_dump(), "transitions": self.transitions.model_dump(), "syntax": self.syntax.model_dump(), } # Merge in custom token categories result.update(self.custom) return result
[docs] @classmethod def from_dict(cls, data: dict[str, Any]) -> DesignTokens: """Create DesignTokens from dictionary. Automatically preserves any custom token categories not in the standard set. """ # Known token categories known_categories = { "primitive", "semantic", "components", "typography", "spacing", "borders", "shadows", "transitions", "syntax", } # Extract custom token categories (anything not in known set) custom_tokens = {key: value for key, value in data.items() if key not in known_categories} return cls( primitive=PrimitiveColors(**data.get("primitive", {})), semantic=SemanticColors(**data.get("semantic", {})), components=ComponentColors(**data.get("components", {})), typography=Typography(**data.get("typography", {})), spacing=Spacing(**data.get("spacing", {})), borders=BorderRadius(**data.get("borders", {})), shadows=Shadows(**data.get("shadows", {})), transitions=Transitions(**data.get("transitions", {})), syntax=SyntaxColors(**data.get("syntax", {})), custom=custom_tokens, )