diff --git a/colorcalc/__init__.py b/app/__init__.py similarity index 70% rename from colorcalc/__init__.py rename to app/__init__.py index 5d393c5..bab5a40 100644 --- a/colorcalc/__init__.py +++ b/app/__init__.py @@ -1,4 +1,4 @@ -"""ColorCalc application package.""" +"""Application package.""" from .app import PurpleTunerApp, start_app diff --git a/colorcalc/app.py b/app/app.py similarity index 88% rename from colorcalc/app.py rename to app/app.py index ef341cf..cfacbd8 100644 --- a/colorcalc/app.py +++ b/app/app.py @@ -4,13 +4,8 @@ from __future__ import annotations import tkinter as tk -from .color_picker import ColorPickerMixin -from .constants import DEFAULTS -from .exclusions import ExclusionMixin -from .image_processing import ImageProcessingMixin -from .reset import ResetMixin -from .theme import ThemeMixin -from .ui import UIBuilderMixin +from .gui import ColorPickerMixin, ExclusionMixin, ThemeMixin, UIBuilderMixin +from .logic import DEFAULTS, ImageProcessingMixin, ResetMixin class PurpleTunerApp( diff --git a/app/gui/__init__.py b/app/gui/__init__.py new file mode 100644 index 0000000..e242ef9 --- /dev/null +++ b/app/gui/__init__.py @@ -0,0 +1,14 @@ +"""GUI-related mixins and helpers for the application.""" + +from .color_picker import ColorPickerMixin +from .exclusions import ExclusionMixin +from .theme import ThemeMixin, HAS_TTKBOOTSTRAP +from .ui import UIBuilderMixin + +__all__ = [ + "ColorPickerMixin", + "ExclusionMixin", + "ThemeMixin", + "HAS_TTKBOOTSTRAP", + "UIBuilderMixin", +] diff --git a/colorcalc/color_picker.py b/app/gui/color_picker.py similarity index 100% rename from colorcalc/color_picker.py rename to app/gui/color_picker.py diff --git a/colorcalc/exclusions.py b/app/gui/exclusions.py similarity index 100% rename from colorcalc/exclusions.py rename to app/gui/exclusions.py diff --git a/colorcalc/theme.py b/app/gui/theme.py similarity index 100% rename from colorcalc/theme.py rename to app/gui/theme.py diff --git a/colorcalc/ui.py b/app/gui/ui.py similarity index 100% rename from colorcalc/ui.py rename to app/gui/ui.py diff --git a/app/logic/__init__.py b/app/logic/__init__.py new file mode 100644 index 0000000..ff4a440 --- /dev/null +++ b/app/logic/__init__.py @@ -0,0 +1,14 @@ +"""Logic utilities and mixins for processing and configuration.""" + +from .constants import BASE_DIR, DEFAULTS, IMAGES_DIR, PREVIEW_MAX_SIZE +from .image_processing import ImageProcessingMixin +from .reset import ResetMixin + +__all__ = [ + "BASE_DIR", + "DEFAULTS", + "IMAGES_DIR", + "PREVIEW_MAX_SIZE", + "ImageProcessingMixin", + "ResetMixin", +] diff --git a/app/logic/constants.py b/app/logic/constants.py new file mode 100644 index 0000000..3657339 --- /dev/null +++ b/app/logic/constants.py @@ -0,0 +1,66 @@ +"""Shared configuration constants for the application.""" + +from __future__ import annotations + +import contextlib +from pathlib import Path +from typing import Any, Callable + +try: # Python 3.11 and above + import tomllib # type: ignore[attr-defined] +except ModuleNotFoundError: # pragma: no cover - fallback for <3.11 + with contextlib.suppress(ModuleNotFoundError): + import tomli as tomllib # type: ignore[assignment] + if "tomllib" not in globals(): + tomllib = None # type: ignore[assignment] + +PREVIEW_MAX_SIZE = (1200, 800) + +BASE_DIR = Path(__file__).resolve().parents[2] +IMAGES_DIR = BASE_DIR / "images" +CONFIG_FILE = BASE_DIR / "config.toml" + +_DEFAULTS_BASE = { + "hue_min": 250.0, + "hue_max": 310.0, + "sat_min": 15.0, + "val_min": 15.0, + "val_max": 100.0, + "alpha": 120, +} + +_DEFAULT_TYPES: dict[str, Callable[[Any], Any]] = { + "hue_min": float, + "hue_max": float, + "sat_min": float, + "val_min": float, + "val_max": float, + "alpha": int, +} + + +def _load_default_overrides() -> dict[str, Any]: + """Load default slider overrides from config.toml if available.""" + if tomllib is None or not CONFIG_FILE.exists(): + return {} + decode_error = getattr(tomllib, "TOMLDecodeError", ValueError) # type: ignore[attr-defined] + try: + with CONFIG_FILE.open("rb") as handle: + data = tomllib.load(handle) + except (OSError, AttributeError, decode_error, TypeError): # type: ignore[arg-type] + return {} + settings = data.get("defaults") + if not isinstance(settings, dict): + return {} + overrides: dict[str, Any] = {} + for key, caster in _DEFAULT_TYPES.items(): + if key not in settings: + continue + try: + overrides[key] = caster(settings[key]) + except (TypeError, ValueError): + continue + return overrides + + +DEFAULTS = {**_DEFAULTS_BASE, **_load_default_overrides()} diff --git a/colorcalc/image_processing.py b/app/logic/image_processing.py similarity index 100% rename from colorcalc/image_processing.py rename to app/logic/image_processing.py diff --git a/colorcalc/reset.py b/app/logic/reset.py similarity index 100% rename from colorcalc/reset.py rename to app/logic/reset.py diff --git a/colorcalc/constants.py b/colorcalc/constants.py deleted file mode 100644 index 5177984..0000000 --- a/colorcalc/constants.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Shared configuration constants for ColorCalc.""" - -from __future__ import annotations - -from pathlib import Path - -PREVIEW_MAX_SIZE = (1200, 800) - -DEFAULTS = { - "hue_min": 250.0, - "hue_max": 310.0, - "sat_min": 15.0, - "val_min": 15.0, - "val_max": 100.0, - "alpha": 120, -} - -BASE_DIR = Path(__file__).resolve().parent.parent -IMAGES_DIR = BASE_DIR / "images" diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..3fe8ce4 --- /dev/null +++ b/config.toml @@ -0,0 +1,10 @@ +[defaults] +# Override any of the following keys to tweak the initial slider values: +# hue_min, hue_max, sat_min, val_min, val_max accept floating point numbers. +# alpha accepts an integer between 0 and 255. +hue_min = 250.0 +hue_max = 310.0 +sat_min = 15.0 +val_min = 15.0 +val_max = 100.0 +alpha = 120 diff --git a/main.py b/main.py index f9b65af..ab919c4 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ -"""CLI entry point for the ColorCalc application.""" +"""CLI entry point for the application.""" -from colorcalc import start_app +from app import start_app if __name__ == "__main__":