-
Notifications
You must be signed in to change notification settings - Fork 87
Move ayon-ui-qt to ayon-core #1873
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
base: develop
Are you sure you want to change the base?
Changes from 2 commits
aaa5e61
7a6ffc5
fa4862c
f7001b3
ae33e42
ac0c1f0
a425594
a6ddf38
4f6fe3c
b47f990
bc29190
c1630b1
14e5c3d
a3b5458
213d773
1ccb6bc
c366acb
f957bca
b76adab
b73e8e2
66c6b4e
33a6de3
6d04ccc
e0a0cd6
69c83f6
3aa7fc4
fd7f1cd
62c6d8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
| _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(): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bad location. Create
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
| if test_data.is_dir(): | ||
| return test_data | ||
| return None | ||
|
|
||
|
|
||
Large diffs are not rendered by default.
| 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 |
| 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", | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.