Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
aaa5e61
- Moved ayon-ui-qt to ayon-core
mahesh-ynput Jun 3, 2026
7a6ffc5
- updated ayon_core.ui with latest ayon-ui-qt develop branch a9dd018a…
mahesh-ynput Jun 6, 2026
fa4862c
port ayon-ui-qt PR #54: restructure module
philippe-ynput Jun 8, 2026
f7001b3
fix(ruff): ruff pass
philippe-ynput Jun 8, 2026
ae33e42
fix(ruff): more fixes
philippe-ynput Jun 8, 2026
ac0c1f0
refactor(ui): update imports and restructure style modules
philippe-ynput Jun 8, 2026
a425594
test(ui): update visual test resource paths
philippe-ynput Jun 10, 2026
a6ddf38
fix(tests): add tmp_path fixture for temporary directory creation
philippe-ynput Jun 10, 2026
4f6fe3c
Merge branch 'develop' into 48-move-ayon-ui-qt-into-ayon-core
philippe-ynput Jun 10, 2026
b47f990
feat(ui): implement custom menu drawer and styling (#1885)
philippe-ynput Jun 11, 2026
bc29190
fix(ruff): fix ruff CI
philippe-ynput Jun 11, 2026
c1630b1
Merge branch 'develop' into 48-move-ayon-ui-qt-into-ayon-core
moonyuet Jun 22, 2026
14e5c3d
Merge branch 'develop' into 48-move-ayon-ui-qt-into-ayon-core
iLLiCiTiT Jun 23, 2026
a3b5458
use 'AYON' over 'Ayon'
iLLiCiTiT Jun 23, 2026
213d773
remove logging basic config
iLLiCiTiT Jun 23, 2026
1ccb6bc
remove unused functions
iLLiCiTiT Jun 23, 2026
c366acb
small code modifications
iLLiCiTiT Jun 23, 2026
f957bca
try to use windows output of tests
iLLiCiTiT Jun 23, 2026
b76adab
don't use normpath
iLLiCiTiT Jun 23, 2026
b73e8e2
correct import order
iLLiCiTiT Jun 23, 2026
66c6b4e
more code style changes
iLLiCiTiT Jun 25, 2026
33a6de3
-Syntax fix
mahesh-ynput Jun 25, 2026
6d04ccc
move tests to preview
iLLiCiTiT Jun 26, 2026
e0a0cd6
remove unused imports
iLLiCiTiT Jun 26, 2026
69c83f6
move variable fonts to root of the repository
iLLiCiTiT Jun 26, 2026
3aa7fc4
keep only one static NunitoSans font
iLLiCiTiT Jun 26, 2026
fd7f1cd
change how fonts are loadad
iLLiCiTiT Jun 26, 2026
62c6d8b
fix tests imports
iLLiCiTiT Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions client/ayon_core/ui/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""AYON Core UI package."""

import sys
import os

# Add the vendor directory to sys.path so that we can import vendored where needed.

Check failure on line 6 in client/ayon_core/ui/__init__.py

View workflow job for this annotation

GitHub Actions / linting

ruff (E501)

client/ayon_core/ui/__init__.py:6:80: E501 Line too long (83 > 79)
_vendor_path = os.path.join(
os.path.dirname(__file__), "..", "vendor", "python"
)
if _vendor_path not in sys.path:
sys.path.insert(0, _vendor_path)


Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Add the vendor directory to sys.path so that we can import vendored where needed.
_vendor_path = os.path.join(
os.path.dirname(__file__), "..", "vendor", "python"
)
if _vendor_path not in sys.path:
sys.path.insert(0, _vendor_path)

def _get_test_data_dir():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad location. Create _test_utils.py and move it there as get_test_data_dir, so it is imported as from ._test_utils import get_test_data_dir.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the end the best solution would be to move the tests to the tests too, but I would NOT do it now.

"""
As we moved all resources
from `tests/client/ayon_core/ui` to `tests/client/ayon_core/ui/test_data`
we need to be able to find the test data directory.
"""
from pathlib import Path

# Relative to repo root path tests/client/ayon_core/ui/test_data
repo_root = Path(__file__).resolve().parents[3]
test_data = repo_root / "tests" / "client" / "ayon_core" / "ui" / "test_data"

Check failure on line 24 in client/ayon_core/ui/__init__.py

View workflow job for this annotation

GitHub Actions / linting

ruff (E501)

client/ayon_core/ui/__init__.py:24:80: E501 Line too long (81 > 79)
if test_data.is_dir():
return test_data
return None


1,026 changes: 1,026 additions & 0 deletions client/ayon_core/ui/ayon_style.json

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions client/ayon_core/ui/color_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from __future__ import annotations

from functools import lru_cache

from qtpy.QtGui import QColor


@lru_cache(maxsize=256)
def relative_luminance(r: int, g: int, b: int, _) -> float:
"""Calculate relative luminance of a color, i.e. sRGB luminance in
linear space.
"""
comp = [r, g, b]
for i in range(3):
comp[i] /= 255
if comp[i] <= 0.03928:
comp[i] /= 12.92
else:
comp[i] = ((comp[i] + 0.055) / 1.055) ** 2.4
return comp[0] * 0.2126 + comp[1] * 0.7152 + comp[2] * 0.0722


def contrast_ratio(lum1: float, lum2: float) -> float:
"""Calculate the contrast ratio between two relative luminance values
per WCAG 2.1.

The contrast ratio ranges from 1:1 (no contrast) to 21:1 (maximum,
black vs white). WCAG recommends:
- 4.5:1 minimum for normal text (AA)
- 7:1 minimum for enhanced contrast (AAA)
- 3:1 minimum for large text or UI components
"""
if lum1 > lum2:
return (lum1 + 0.05) / (lum2 + 0.05)
return (lum2 + 0.05) / (lum1 + 0.05)


@lru_cache(maxsize=256)
def compute_color_for_contrast(
background: tuple, # (r, g, b, a)
foreground: tuple, # (r, g, b, a)
min_contrast_ratio: float = 4.5,
) -> QColor:
"""Adjust foreground color to achieve minimum contrast with background.

Preserves the hue and saturation of the original foreground color,
adjusting only the lightness to achieve the required contrast ratio.

Args:
background: The background color to contrast against.
foreground: The foreground color to adjust.
min_contrast_ratio: Target minimum contrast ratio (default 4.5:1).

Returns:
A QColor with the same hue/saturation but adjusted lightness
to meet the contrast requirement. Returns original if already
sufficient.
"""

bg_lum = relative_luminance(*background)
fg_lum = relative_luminance(*foreground)

# Check if already sufficient
current_ratio = contrast_ratio(bg_lum, fg_lum)
if current_ratio >= min_contrast_ratio:
# print(f"Final ratio: {current_ratio}: {QColor(*foreground).name()}")
return QColor(*foreground)

fg_col = QColor(*foreground)

# Get HSL components (hue, saturation preserved)
h = fg_col.hslHueF()
s = fg_col.hslSaturationF()
original_l = fg_col.lightnessF()

# Determine if we should lighten or darken based on background
# Use luminance midpoint (0.1791 for 4.5:1 contrast with both black/white)
should_lighten = bg_lum < 0.1791

# Binary search for optimal lightness
if should_lighten:
low, high = original_l, 1.0
# return QColor("white")
else:
low, high = 0.0, original_l
# return QColor("black")

result = QColor(
fg_col.red(),
fg_col.green(),
fg_col.blue(),
fg_col.alpha(),
)
original_alpha = result.alpha()

# Binary search (8 iterations gives ~0.4% precision)
for _ in range(8):
mid = (low + high) * 0.5
result.setHslF(h if h >= 0 else 0.0, s, mid)
result.setAlpha(original_alpha) # Preserve alpha

test_lum = relative_luminance(*result.toRgb().toTuple())
test_ratio = contrast_ratio(bg_lum, test_lum)

if test_ratio >= min_contrast_ratio:
if should_lighten:
high = mid # Can use darker shade
else:
low = mid # Can use lighter shade
else:
if should_lighten:
low = mid # Need lighter
else:
high = mid # Need darker

# Final adjustment to ensure we meet the threshold
final_l = high if should_lighten else low
result.setHslF(h if h >= 0 else 0.0, s, max(0.0, min(1.0, final_l)))
result.setAlpha(original_alpha) # Preserve alpha
result = result.toRgb()

# Verify and fallback to black/white if HSL adjustment isn't enough
final_ratio = contrast_ratio(
bg_lum, relative_luminance(*result.toTuple())
)
if final_ratio < min_contrast_ratio:
# HSL adjustment insufficient (saturated colors can't reach target)
# Fall back to black or white
white_ratio = 1.05 / (bg_lum + 0.05)
black_ratio = (bg_lum + 0.05) * 20.0
result = (
QColor(255, 255, 255, original_alpha)
if white_ratio > black_ratio
else QColor(0, 0, 0, original_alpha)
)
# print(f"Final ratio: {final_ratio}: {result.name()}")
return result
29 changes: 29 additions & 0 deletions client/ayon_core/ui/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""AYON UI Qt components package.

This package provides reusable Qt widgets styled according to the AYON
design system.
"""

from .buttons import AYButton
from .check_box import AYCheckBox
from .combo_box import AYComboBox
from .container import AYContainer
from .label import AYLabel
from .layouts import AYHBoxLayout, AYVBoxLayout, AYGridLayout
from .line_edit import AYLineEdit
from .text_edit import AYTextEdit
from .tree_view import AYTreeView

__all__ = (
"AYButton",
"AYCheckBox",
"AYComboBox",
"AYContainer",
"AYLabel",
"AYHBoxLayout",
"AYVBoxLayout",
"AYGridLayout",
"AYLineEdit",
"AYTextEdit",
"AYTreeView",
)
Loading
Loading