diff --git a/.editorconfig b/.editorconfig index d0d5825..68cec78 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,4 +12,4 @@ indent_size = 4 indent_size = 2 [*.{md,rst}] -trim_trailing_whitespace = false \ No newline at end of file +trim_trailing_whitespace = false diff --git a/.flake8 b/.flake8 index e0ea542..8dd399a 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,3 @@ [flake8] max-line-length = 88 -extend-ignore = E203 \ No newline at end of file +extend-ignore = E203 diff --git a/.gitattribute b/.gitattribute index 71027c2..478b946 100644 --- a/.gitattribute +++ b/.gitattribute @@ -18,4 +18,4 @@ LICENSE text eol=lf .gitignore text eol=lf .gitattribute text eol=lf .flake8 text eol=lf -.pylintrc text eol=lf \ No newline at end of file +.pylintrc text eol=lf diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..680f85b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,33 @@ +version: 2 +updates: + # Enable version updates for Python dependencies + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "04:00" + open-pull-requests-limit: 10 + reviewers: + - "PermutaTriangle" + assignees: + - "PermutaTriangle" + commit-message: + prefix: "deps" + include: "scope" + + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "04:00" + open-pull-requests-limit: 5 + reviewers: + - "PermutaTriangle" + assignees: + - "PermutaTriangle" + commit-message: + prefix: "ci" + include: "scope" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e476994 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,40 @@ +name: "CodeQL" + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master ] + schedule: + - cron: '30 2 * * 1' # Weekly on Mondays at 02:30 UTC + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 691a48b..c93e1eb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,22 +9,21 @@ jobs: fail-fast: false matrix: include: - - python: 3.8 + # Linting and type checking (run on Python 3.11) + - python: "3.11" toxenv: flake8 os: ubuntu-latest - - python: 3.8 + - python: "3.11" toxenv: mypy os: ubuntu-latest - - python: 3.8 + - python: "3.11" toxenv: pylint os: ubuntu-latest - - python: 3.8 + - python: "3.11" toxenv: black os: ubuntu-latest - - python: 3.7 - toxenv: py37 - os: ubuntu-latest + # Python version testing (Linux) - python: 3.8 toxenv: py38 os: ubuntu-latest @@ -34,28 +33,47 @@ jobs: - python: "3.10" toxenv: py310 os: ubuntu-latest - - python: pypy-3.7 - toxenv: pypy37 + - python: "3.11" + toxenv: py311 + os: ubuntu-latest + - python: "3.12" + toxenv: py312 + os: ubuntu-latest + - python: "3.13" + toxenv: py313 + os: ubuntu-latest + - python: pypy-3.8 + toxenv: pypy38 + os: ubuntu-latest + - python: pypy-3.9 + toxenv: pypy39 + os: ubuntu-latest + - python: pypy-3.10 + toxenv: pypy310 os: ubuntu-latest - - python: 3.8 - toxenv: py38 + # Cross-platform testing (Python 3.11) + - python: "3.11" + toxenv: py311 os: macos-latest - - python: 3.8 - toxenv: py38 + - python: "3.11" + toxenv: py311 os: windows-latest runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python }} + python-version: ${{ matrix.python }} + allow-prereleases: true - name: install dependencies - run: python -m pip install --upgrade pip tox + run: | + python -m pip install --upgrade pip setuptools + python -m pip install tox - name: run env: TOXENV: ${{ matrix.toxenv }} run: tox - name: setup - run: python setup.py install \ No newline at end of file + run: python setup.py install diff --git a/.gitignore b/.gitignore index 20229bc..4eac1e6 100644 --- a/.gitignore +++ b/.gitignore @@ -86,8 +86,8 @@ target/ profile_default/ ipython_config.py -# pyenv -.python-version +# pyenv (but we want to track our .python-version file) +# .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -142,4 +142,63 @@ dmypy.json .vscode/ # Program's output -tilingsgui/exports/ \ No newline at end of file +tilingsgui/exports/ +exports/ + +# macOS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Windows +*.stackdump + +# Linux +*~ + +# Temporary/backup files +*.tmp +*.temp +*.bak +*.swp +*.swo +*~ + +# IDE specific files (additional) +.idea/ +*.iml +*.ipr +*.iws +.vscode/settings.json +.vscode/launch.json +.vscode/tasks.json + +# Pre-commit +.pre-commit-config.yaml.bak + +# Ruff cache (modern Python linter) +.ruff_cache/ + +# Modern type checkers +.pyright/ + +# Coverage.py +.coverage.* +coverage.xml +htmlcov/ + +# Duplicate files (with version numbers or " 2" suffix) +*" 2".* +* 2.* + +# Temporary export files +*.json.tmp +tilings_export.json + +# Development files that shouldn't be committed +hamstur.py +2025-MODERNIZATION-PLAN.md diff --git a/.isort.cfg b/.isort.cfg index 3884dca..92c8e2d 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -5,4 +5,4 @@ multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 use_parentheses=True -line_length=88 \ No newline at end of file +line_length=88 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee9075b..1df3071 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,21 @@ repos: - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 24.8.0 hooks: - - id: black \ No newline at end of file + - id: black + language_version: python3.11 + + - repo: https://github.com/pycqa/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + additional_dependencies: [flake8-isort] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict diff --git a/.pylintrc b/.pylintrc index e1de93b..2f899e8 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,8 +1,8 @@ [MASTER] ignore-patterns=test_.*?py -init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc())+'/tilingsgui')" +init-hook="import os, sys; sys.path.append(os.path.join(os.path.dirname(__file__), 'tilingsgui'))" max-args=10 max-module-lines=1200 -disable=too-few-public-methods,too-many-lines -good-names=r,c,x,y,h,w,i,j,k,n,x1,x2,y1,y2,g,b,dx,dy \ No newline at end of file +disable=too-few-public-methods,too-many-lines,too-many-positional-arguments +good-names=r,c,x,y,h,w,i,j,k,n,x1,x2,y1,y2,g,b,dx,dy diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6210168..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: python -matrix: - include: - - python: 3.8 - env: TOXENV=flake8 -install: -- pip install tox -script: -- tox -deploy: - skip_cleanup: true - provider: pypi - user: __token__ - password: - secure: ZEsM76obFP9ZIGdHkKIgMFc/7qN33ZCO/2Tl0E8noUOHuL/dPkKeG1VWD7XiXupljULgRkxKInrz5aw/423+qwy1kQw2rDxdxbs4ITAbp3xEd4X7N2WwvYp7biuw5z4I2lzY7TI6HvHxFeN25Wvo1qIc7SIMMcYmSAY7pOs7qW+ljfrg9PhBJZBk0zZ3ISAu8npkIVa9FKaYTbx0lq9GWdy3bovkGER4DwcjGB6Cx0OCSrLxwYqNi34xlSy7hBQugQp85SqSlwOpgxLGgxr0/xyW73iQdoQhcFdl3Aoupudu/suy0w7JShANGeD2cWtXs5a028w79tKwKmlb11gSFbLT9fdhxdSPU87qm5EOye0loCg5pGQtZvZ2Zf+WyIFaPWl7UewcBDHNSx2APUHokpI9N6qoaI94CcY5UHCmlXGc2PMN00WmhTlHGYsFEADoFa5brqBNEvP5XpHa8eT/w63tGG/v/a9NdfLMzktYemUIGdNZrOj8V1YGjd28HsxE2/zF/kG9Qbq1878tYmocpxro8gQXdbzc1DyiXZsyDWPRSv7WCIXl07Y2nWIqKuuQmmrFlxTw+SmRiIXpzl2YZ2c5UqPJFNcaJ5fZ2U+MwadbkhuANIYn5vc4AkZyz2L/jeyKz7wNB7Ar8DgAKxKkZ0gJv7AICZTGEXXEtIeMsdk= - skip_existing: true - on: - branch: master - condition: "$TRAVIS_OS_NAME = linux" diff --git a/README.rst b/README.rst index ac5c932..e33a6d6 100644 --- a/README.rst +++ b/README.rst @@ -21,11 +21,24 @@ Pyperclip requires clipboard tools that might not come pre-installed. sudo apt-get install xclip -Without them the app still works but pasting won’t. +Without them the app still works but pasting won't. + +Note for macOS +~~~~~~~~~~~~~~ + +PyObjC is automatically installed on macOS systems for proper Cocoa/OpenGL integration with pyglet 2.0+. If you encounter OpenGL surface errors, ensure PyObjC is installed: + +.. code:: sh + + pip install pyobjc-core pyobjc-framework-Cocoa + +This is handled automatically during normal installation. + +**PyPy Users**: PyObjC doesn't support PyPy. TilingsGUI automatically configures pyglet to use shadow context disabled mode for PyPy compatibility on macOS. Known issues ------------ -* +* Report a bug ~~~~~~~~~~~~ @@ -79,9 +92,9 @@ The following two inputs are two ways of producing the same initial tiling. .. code:: 1432_12345 - + {"class_module": "tilings.tiling", "comb_class": "Tiling", "obstructions": [{"patt": [0, 3, 2, 1], "pos": [[0, 0], [0, 0], [0, 0], [0, 0]]}, {"patt": [0, 1, 2, 3, 4], "pos": [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}], "requirements": [], "assumptions": []} - + The initial tiling in question would be the following. .. code:: sh @@ -90,7 +103,7 @@ The initial tiling in question would be the following. |1| +-+ 1: Av(0321, 01234) - + Cell insertion ~~~~~~~~~~~~~~ @@ -131,7 +144,7 @@ By clicking a point of a requirement, we pass its gridded permutation along with Fusion ~~~~~~ -Let ``c_r`` and ``c_c`` be the row and column respectively of the clicked cell. There are 4 types of fusions available. Fusion with ``row=c_r``, |fusion_r|, fusion with ``col=c_c``, |fusion_c|, component fusion with ``row=c_r``, |fusion_comp_r|, and component fusion with ``col=c_c``, |fusion_comp_c|. If the fusion are invalid, then exceptions are caught and nothing happens. +Let ``c_r`` and ``c_c`` be the row and column respectively of the clicked cell. There are 4 types of fusions available. Fusion with ``row=c_r``, |fusion_r|, fusion with ``col=c_c``, |fusion_c|, component fusion with ``row=c_r``, |fusion_comp_r|, and component fusion with ``col=c_c``, |fusion_comp_c|. If the fusion are invalid, then exceptions are caught and nothing happens. Fusion: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e5b7775 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,47 @@ +# Security Policy + +## Supported Versions + +We actively support the following versions of TilingsGUI with security updates: + +| Version | Supported | +| ------- | ------------------ | +| 0.2.x | :white_check_mark: | +| < 0.2 | :x: | + +## Reporting a Vulnerability + +If you discover a security vulnerability in TilingsGUI, please report it to us privately. We take security seriously and will respond promptly to legitimate security concerns. + +### How to Report + +1. **Email**: Send an email to permutatriangle@gmail.com with the subject line "Security Vulnerability in TilingsGUI" +2. **Include**: + - A detailed description of the vulnerability + - Steps to reproduce the issue + - Potential impact assessment + - Any suggested fixes (if available) + +### What to Expect + +- **Acknowledgment**: We will acknowledge receipt of your report within 48 hours +- **Initial Assessment**: We will provide an initial assessment within 5 business days +- **Resolution Timeline**: We aim to resolve critical security issues within 30 days +- **Disclosure**: We will coordinate with you on appropriate disclosure timing + +### Security Best Practices + +When using TilingsGUI: +- Keep your installation up to date with the latest version +- Only load tilings from trusted sources +- Be cautious when running TilingsGUI with elevated privileges +- Report any suspicious behavior or unexpected security prompts + +## Scope + +This security policy covers: +- The TilingsGUI application itself +- Dependencies and third-party libraries +- Build and deployment processes + +Thank you for helping keep TilingsGUI secure! diff --git a/pyproject.toml b/pyproject.toml index f2d5bca..25e376c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,9 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + [tool.black] -target-version = ['py37'] +target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313'] include = '\.pyi?$' exclude = ''' ( @@ -18,4 +22,4 @@ exclude = ''' | foo.py # also separately exclude a file named foo.py in # the root of the project ) -''' \ No newline at end of file +''' diff --git a/setup.py b/setup.py index 522586d..4d06e32 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,11 @@ def get_version(): raise ValueError("Version not found in tilingsgui/__init__.py") +def get_install_requires(): + """Get install requirements.""" + return ["pyperclip>=1.9.0", "pyglet>=2.0.0", "tilings>=2.5.0"] + + setup( name="tilingsgui", version=get_version(), @@ -31,8 +36,8 @@ def get_version(): }, packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), long_description=read("README.rst"), - install_requires=["pyperclip==1.8.1", "pyglet==1.5.15", "tilings==2.5.0"], - python_requires=">=3.6", + install_requires=get_install_requires(), + python_requires=">=3.8", include_package_data=True, classifiers=[ "Topic :: Education", @@ -41,10 +46,12 @@ def get_version(): "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], diff --git a/tilingsgui/__init__.py b/tilingsgui/__init__.py index af7642d..bb16f6e 100644 --- a/tilingsgui/__init__.py +++ b/tilingsgui/__init__.py @@ -1,4 +1,3 @@ -"""As the tilingsgui is not intened to be imported, this only holds the version. -""" +"""As the tilingsgui is not intened to be imported, this only holds the version.""" __version__ = "0.2.3" diff --git a/tilingsgui/app.py b/tilingsgui/app.py index d425915..2702dd1 100644 --- a/tilingsgui/app.py +++ b/tilingsgui/app.py @@ -5,10 +5,49 @@ # pylint: disable=abstract-method -from typing import ClassVar, Tuple +import sys +from typing import ClassVar, Literal, Tuple import pyglet +# PyPy compatibility on macOS +# +# PyPy is not supported on macOS due to fundamental incompatibilities between PyPy's +# ctypes implementation and macOS GUI frameworks. This issue affects ALL major Python +# GUI libraries, not just pyglet. +# +# Investigation conducted (Oct 2025) found the following: +# +# 1. pyglet 2.0: +# - Fails with IndexError/AttributeError in PyPy's ctypes when interfacing with +# Cocoa/Objective-C bridge +# - pyglet.options like shadow_window=False and osx_alt_loop=True do not help +# +# 2. Tkinter: +# - Imports successfully but hangs when creating windows +# - PyPy's bundled Tk requires manual mainloop() calls, breaking event-driven design +# - Tested with PyPy 7.3.20 (Python 3.11.13) - same issues +# +# 3. PySide6 (Qt): +# - Officially supports PyPy 3.8+ but no pre-built PyPy wheels available for macOS +# - Would require building from source +# +# 4. wxPython: +# - Community reports indicate it does not work with PyPy +# +# Root cause: PyPy's ctypes has subtle differences from CPython in callback and object +# reference handling, which breaks all macOS GUI frameworks that use ctypes to interface +# with Cocoa/Objective-C. +# +# Note: PyPy may work on Linux (X11) or Windows (Win32), as the issue is specific to +# macOS's Cocoa backend. +if sys.platform == "darwin" and sys.implementation.name == "pypy": + print("Error: PyPy is not supported on macOS.") + print("Reason: PyPy's ctypes is incompatible with macOS GUI frameworks (Cocoa).") + print("Please use CPython on macOS, or try PyPy on Linux/Windows.") + sys.exit(1) + +# pylint: disable=wrong-import-position from .files import History, PathManager from .graphics import Color from .menu import RightMenu, TopMenu @@ -22,13 +61,13 @@ class TilingGui(pyglet.window.Window): _TITLE: ClassVar[str] = "Tilings GUI" _MIN_WIDTH: ClassVar[int] = 500 _MIN_HEIGHT: ClassVar[int] = 400 - _INITIAL_WIDTH: ClassVar[int] = 800 - _INITIAL_HEIGHT: ClassVar[int] = 650 - _RIGHT_BAR_WIDTH: ClassVar[int] = 200 - _TOP_BAR_HEIGHT: ClassVar[int] = 24 - _CLEAR_COLOR: ClassVar[ - Tuple[float, float, float, float] - ] = Color.alpha_extend_and_scale_to_01(Color.WHITE) + _INITIAL_WIDTH: ClassVar[int] = 1600 + _INITIAL_HEIGHT: ClassVar[int] = 1200 + _RIGHT_BAR_WIDTH: ClassVar[int] = 400 + _TOP_BAR_HEIGHT: ClassVar[int] = 50 + _CLEAR_COLOR: ClassVar[Tuple[float, float, float, float]] = ( + Color.alpha_extend_and_scale_to_01(Color.WHITE) + ) def __init__(self, init_tiling: str, *args, **kargs) -> None: """Instantiate the parent window class and create all @@ -90,7 +129,7 @@ def _initial_config(self) -> None: """Configuration done before starting.""" # Center the window within the os. - screen = pyglet.canvas.Display().get_default_screen() + screen = pyglet.display.Display().get_default_screen() # type: ignore self.set_location( (screen.width - self.width) // 2, (screen.height - self.height) // 2 ) @@ -100,9 +139,9 @@ def _initial_config(self) -> None: # Handle clearing the canvas on each draw. pyglet.gl.glClearColor(*TilingGui._CLEAR_COLOR) - self.push_handlers(on_draw=self.clear) + self.push_handlers(on_draw=self.clear) # pylint: disable=unreachable - def on_resize(self, width: int, height: int) -> bool: + def on_resize(self, width: int, height: int) -> Literal[True]: """Event handler for the window resize event. Args: diff --git a/tilingsgui/events.py b/tilingsgui/events.py index fcb3aec..b70ade1 100644 --- a/tilingsgui/events.py +++ b/tilingsgui/events.py @@ -1,5 +1,4 @@ -"""Event related module. -""" +"""Event related module.""" from typing import Iterable diff --git a/tilingsgui/files.py b/tilingsgui/files.py index 5f73c4d..dd5590b 100644 --- a/tilingsgui/files.py +++ b/tilingsgui/files.py @@ -1,5 +1,4 @@ -"""A collection of file and path related functionality. -""" +"""A collection of file and path related functionality.""" import json import pathlib diff --git a/tilingsgui/geometry.py b/tilingsgui/geometry.py index af33417..4c013ec 100644 --- a/tilingsgui/geometry.py +++ b/tilingsgui/geometry.py @@ -1,5 +1,4 @@ -"""Mathematical geometric objects. -""" +"""Mathematical geometric objects.""" from typing import Iterator diff --git a/tilingsgui/graphics.py b/tilingsgui/graphics.py index 499b501..c1d23a5 100644 --- a/tilingsgui/graphics.py +++ b/tilingsgui/graphics.py @@ -1,11 +1,9 @@ -"""Drawable objects -""" +"""Drawable objects""" - -from math import cos, pi, sin from typing import ClassVar, List, Tuple import pyglet +import pyglet.shapes from .geometry import Point @@ -18,9 +16,6 @@ class GeoDrawer: """A static class container of drawing methods.""" - _VERTEX_MODE: ClassVar[str] = "v2f" - _COLOR_MODE: ClassVar[str] = "c3B" - @staticmethod def draw_line_segment( x1: float, y1: float, x2: float, y2: float, color: C3F @@ -34,12 +29,9 @@ def draw_line_segment( y2 (float): End y coordinate. color (Tuple[float, float, float]): RGB valued color. """ - pyglet.graphics.draw( - 2, - pyglet.gl.GL_LINE_STRIP, - (GeoDrawer._VERTEX_MODE, [x1, y1, x2, y2]), - (GeoDrawer._COLOR_MODE, color * 2), - ) + color_255 = Color.scale_to_255(color) + line = pyglet.shapes.Line(x1, y1, x2, y2, color=color_255) + line.draw() @staticmethod def draw_circle(x: float, y: float, r: float, color: C3F, splits: int = 30) -> None: @@ -53,17 +45,9 @@ def draw_circle(x: float, y: float, r: float, color: C3F, splits: int = 30) -> N splits (int, optional): How detailed the polygon emulating a circle should be. Higher values increase detail. """ - vertices = [x, y] - for i in range(splits + 1): - ang = 2 * pi * i / splits - vertices.append(x + cos(ang) * r) - vertices.append(y + sin(ang) * r) - pyglet.graphics.draw( - splits + 2, - pyglet.gl.GL_TRIANGLE_FAN, - (GeoDrawer._VERTEX_MODE, vertices), - (GeoDrawer._COLOR_MODE, color * (splits + 2)), - ) + color_255 = Color.scale_to_255(color) + circle = pyglet.shapes.Circle(x, y, r, color=color_255, segments=splits) + circle.draw() @staticmethod def draw_point(point: Point, size: float, color: C3F) -> None: @@ -87,12 +71,9 @@ def draw_rectangle(x: float, y: float, w: float, h: float, color: C3F) -> None: h (float): Vertical length. color (Tuple[float, float, float]): Fill color. """ - pyglet.graphics.draw( - 4, - pyglet.gl.GL_TRIANGLE_STRIP, - (GeoDrawer._VERTEX_MODE, [x, y, x, y + h, x + w, y, x + w, y + h]), - (GeoDrawer._COLOR_MODE, color * 4), - ) + color_255 = Color.scale_to_255(color) + rectangle = pyglet.shapes.Rectangle(x, y, w, h, color=color_255) + rectangle.draw() @staticmethod def draw_point_path(pnt_path: List[Point], color: C3F, point_size: float) -> None: @@ -106,13 +87,13 @@ def draw_point_path(pnt_path: List[Point], color: C3F, point_size: float) -> Non n = len(pnt_path) if n > 0: if n > 1: - vertices = [coord for pnt in pnt_path for coord in pnt.coords()] - pyglet.graphics.draw( - n, - pyglet.gl.GL_LINE_STRIP, - (GeoDrawer._VERTEX_MODE, vertices), - (GeoDrawer._COLOR_MODE, color * n), - ) + # Draw line segments between adjacent points + color_255 = Color.scale_to_255(color) + for i in range(n - 1): + p1, p2 = pnt_path[i], pnt_path[i + 1] + line = pyglet.shapes.Line(p1.x, p1.y, p2.x, p2.y, color=color_255) + line.draw() + # Draw points for pnt in pnt_path: GeoDrawer.draw_point(pnt, point_size, color) @@ -264,13 +245,13 @@ class Color: @staticmethod def scale_to_01(color: C3I) -> C3F: - """Scale color values from 0-255 to 0-1. Color can include alpha value. + """Scale color values from 0-255 to 0-1. Args: - color (Tuple[int, int, int]): A tuple of length 3. + color (Tuple[int, int, int]): RGB color in 0-255 range. Returns: - Tuple[float, float, float]: A scaled tuple. + Tuple[float, float, float]: RGB color in 0-1 range. """ r, g, b = color return r / 255, g / 255, b / 255 @@ -291,15 +272,27 @@ def alpha_extend(color: C3I, alpha: int = 255) -> C4I: @staticmethod def alpha_extend_and_scale_to_01(color: C3I, alpha: int = 255) -> C4F: """Performs both alpha extension and 0-1 scaling of a color. - Use this method if you intent to do both to guarantee they + Use this method if you intend to do both to guarantee they are done in correct order. Args: - color (Tuple[int, int, int]): A rgb color value. - alpha (int, optional): The alpha value, 0-255. Defaults to 255. + color (Tuple[int, int, int]): RGB color in 0-255 range. + alpha (int, optional): Alpha value in 0-255 range. Defaults to 255. Returns: - Tuple[int, int, int, int]: A rgba color value, 0-1. + Tuple[float, float, float, float]: RGBA color in 0-1 range. """ r, g, b = color return r / 255, g / 255, b / 255, alpha / 255 + + @staticmethod + def scale_to_255(color: C3F) -> C4I: + """Convert RGB (0-1) to RGBA (0-255) for pyglet.shapes. + + Args: + color (Tuple[float, float, float]): RGB color in 0-1 range. + + Returns: + Tuple[int, int, int, int]: RGBA color in 0-255 range. + """ + return (int(color[0] * 255), int(color[1] * 255), int(color[2] * 255), 255) diff --git a/tilingsgui/main.py b/tilingsgui/main.py index 39168ff..361c749 100644 --- a/tilingsgui/main.py +++ b/tilingsgui/main.py @@ -1,5 +1,4 @@ -"""Entrypoint. -""" +"""Entrypoint.""" import argparse @@ -17,7 +16,7 @@ def get_args() -> str: def main() -> None: """The application's starting point.""" - app = TilingGui(get_args(), resizable=True) + app = TilingGui(get_args(), resizable=True) # type: ignore app.start() diff --git a/tilingsgui/menu.py b/tilingsgui/menu.py index 59186c6..0d73723 100644 --- a/tilingsgui/menu.py +++ b/tilingsgui/menu.py @@ -1,6 +1,4 @@ -"""Control stations. -""" - +"""Control stations.""" from typing import Iterable @@ -22,9 +20,9 @@ class TopMenu(pyglet.event.EventDispatcher, Observer): _PADDING = 1 _INITIAL_MESSAGE = " -- Basis here -- e.g. 1234_1324" - _FONT_SIZE = 12 + _FONT_SIZE = 20 _TEXT_COLOR = Color.alpha_extend(Color.BLACK) - _TEXT_BOX_COLOR = Color.DARK_GRAY + _TEXT_BOX_COLOR = Color.scale_to_01(Color.WHITE) _BACKGROUND_COLOR = Color.BLACK _V_BTN = 118 _CTRL_MODIFIER = 18 @@ -194,11 +192,11 @@ class RightMenu(pyglet.event.EventDispatcher, Observer): """A menu that sits to the right of the tiling plot.""" _PADDING = 1 - _INITIAL_MESSAGE = "12" - _FONT_SIZE = 12 + _INITIAL_MESSAGE = "Req: 12" + _FONT_SIZE = 20 _TEXT_COLOR = Color.alpha_extend(Color.BLACK) - _TEXT_BOX_COLOR = Color.DARK_GRAY - _BACKGROUND_COLOR = Color.BLACK + _TEXT_BOX_COLOR = Color.scale_to_01(Color.WHITE) + _BACKGROUND_COLOR = Color.scale_to_01(Color.LIGHT_GRAY) def __init__( self, diff --git a/tilingsgui/resources/img/svg/add_custom.svg b/tilingsgui/resources/img/svg/add_custom.svg index 06edd7e..9d145e8 100644 --- a/tilingsgui/resources/img/svg/add_custom.svg +++ b/tilingsgui/resources/img/svg/add_custom.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/add_point.svg b/tilingsgui/resources/img/svg/add_point.svg index c58fd13..1851242 100644 --- a/tilingsgui/resources/img/svg/add_point.svg +++ b/tilingsgui/resources/img/svg/add_point.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/export.svg b/tilingsgui/resources/img/svg/export.svg index 45ea3c4..1226846 100644 --- a/tilingsgui/resources/img/svg/export.svg +++ b/tilingsgui/resources/img/svg/export.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/factor.svg b/tilingsgui/resources/img/svg/factor.svg index 8aa063e..6fca35c 100644 --- a/tilingsgui/resources/img/svg/factor.svg +++ b/tilingsgui/resources/img/svg/factor.svg @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/factor_int.svg b/tilingsgui/resources/img/svg/factor_int.svg index 9aa7c3f..280667c 100644 --- a/tilingsgui/resources/img/svg/factor_int.svg +++ b/tilingsgui/resources/img/svg/factor_int.svg @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/fusion_c.svg b/tilingsgui/resources/img/svg/fusion_c.svg index 6fe2189..673e3e0 100644 --- a/tilingsgui/resources/img/svg/fusion_c.svg +++ b/tilingsgui/resources/img/svg/fusion_c.svg @@ -7,5 +7,5 @@ - - \ No newline at end of file + + diff --git a/tilingsgui/resources/img/svg/fusion_comp_c.svg b/tilingsgui/resources/img/svg/fusion_comp_c.svg index e946d01..ddb27cb 100644 --- a/tilingsgui/resources/img/svg/fusion_comp_c.svg +++ b/tilingsgui/resources/img/svg/fusion_comp_c.svg @@ -8,5 +8,5 @@ - - \ No newline at end of file + + diff --git a/tilingsgui/resources/img/svg/fusion_comp_r.svg b/tilingsgui/resources/img/svg/fusion_comp_r.svg index 6bc84f1..915046c 100644 --- a/tilingsgui/resources/img/svg/fusion_comp_r.svg +++ b/tilingsgui/resources/img/svg/fusion_comp_r.svg @@ -8,5 +8,5 @@ - - \ No newline at end of file + + diff --git a/tilingsgui/resources/img/svg/fusion_r.svg b/tilingsgui/resources/img/svg/fusion_r.svg index 68a7f16..301d89f 100644 --- a/tilingsgui/resources/img/svg/fusion_r.svg +++ b/tilingsgui/resources/img/svg/fusion_r.svg @@ -7,5 +7,5 @@ - - \ No newline at end of file + + diff --git a/tilingsgui/resources/img/svg/htc.svg b/tilingsgui/resources/img/svg/htc.svg index 470225b..f930712 100644 --- a/tilingsgui/resources/img/svg/htc.svg +++ b/tilingsgui/resources/img/svg/htc.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/move.svg b/tilingsgui/resources/img/svg/move.svg index e20cd3a..20fbed8 100644 --- a/tilingsgui/resources/img/svg/move.svg +++ b/tilingsgui/resources/img/svg/move.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/obs_inf.svg b/tilingsgui/resources/img/svg/obs_inf.svg index 62b8dba..ed3c951 100644 --- a/tilingsgui/resources/img/svg/obs_inf.svg +++ b/tilingsgui/resources/img/svg/obs_inf.svg @@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/obstr_trans.svg b/tilingsgui/resources/img/svg/obstr_trans.svg index fa7c688..38f9f57 100644 --- a/tilingsgui/resources/img/svg/obstr_trans.svg +++ b/tilingsgui/resources/img/svg/obstr_trans.svg @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/place_east.svg b/tilingsgui/resources/img/svg/place_east.svg index fa98b92..f0d32e7 100644 --- a/tilingsgui/resources/img/svg/place_east.svg +++ b/tilingsgui/resources/img/svg/place_east.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/place_north.svg b/tilingsgui/resources/img/svg/place_north.svg index fe334d3..fec0bd3 100644 --- a/tilingsgui/resources/img/svg/place_north.svg +++ b/tilingsgui/resources/img/svg/place_north.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/place_south.svg b/tilingsgui/resources/img/svg/place_south.svg index cbd743c..331805c 100644 --- a/tilingsgui/resources/img/svg/place_south.svg +++ b/tilingsgui/resources/img/svg/place_south.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/place_west.svg b/tilingsgui/resources/img/svg/place_west.svg index 819e221..32112c4 100644 --- a/tilingsgui/resources/img/svg/place_west.svg +++ b/tilingsgui/resources/img/svg/place_west.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/pplace_east.svg b/tilingsgui/resources/img/svg/pplace_east.svg index 0e90391..52a538b 100644 --- a/tilingsgui/resources/img/svg/pplace_east.svg +++ b/tilingsgui/resources/img/svg/pplace_east.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/pplace_north.svg b/tilingsgui/resources/img/svg/pplace_north.svg index 136ceeb..dabbe08 100644 --- a/tilingsgui/resources/img/svg/pplace_north.svg +++ b/tilingsgui/resources/img/svg/pplace_north.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/pplace_south.svg b/tilingsgui/resources/img/svg/pplace_south.svg index 3d32f44..a09dbc6 100644 --- a/tilingsgui/resources/img/svg/pplace_south.svg +++ b/tilingsgui/resources/img/svg/pplace_south.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/pplace_west.svg b/tilingsgui/resources/img/svg/pplace_west.svg index 59582dc..22998e7 100644 --- a/tilingsgui/resources/img/svg/pplace_west.svg +++ b/tilingsgui/resources/img/svg/pplace_west.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/pretty.svg b/tilingsgui/resources/img/svg/pretty.svg index 4d27ce1..b97f2bd 100644 --- a/tilingsgui/resources/img/svg/pretty.svg +++ b/tilingsgui/resources/img/svg/pretty.svg @@ -4,4 +4,4 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/redo.svg b/tilingsgui/resources/img/svg/redo.svg index d905272..c33f9ff 100644 --- a/tilingsgui/resources/img/svg/redo.svg +++ b/tilingsgui/resources/img/svg/redo.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/rowcolsep.svg b/tilingsgui/resources/img/svg/rowcolsep.svg index a91f57f..afbaa59 100644 --- a/tilingsgui/resources/img/svg/rowcolsep.svg +++ b/tilingsgui/resources/img/svg/rowcolsep.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/shading.svg b/tilingsgui/resources/img/svg/shading.svg index 9eda0f9..1156ff6 100644 --- a/tilingsgui/resources/img/svg/shading.svg +++ b/tilingsgui/resources/img/svg/shading.svg @@ -1,6 +1,6 @@ - + Sunglass Created with Sketch. @@ -50,8 +50,8 @@ - - + + diff --git a/tilingsgui/resources/img/svg/show_cross.svg b/tilingsgui/resources/img/svg/show_cross.svg index 6ac6d06..c4f8cbb 100644 --- a/tilingsgui/resources/img/svg/show_cross.svg +++ b/tilingsgui/resources/img/svg/show_cross.svg @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/show_local.svg b/tilingsgui/resources/img/svg/show_local.svg index d95aaa5..32d8cc6 100644 --- a/tilingsgui/resources/img/svg/show_local.svg +++ b/tilingsgui/resources/img/svg/show_local.svg @@ -20,4 +20,4 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/str.svg b/tilingsgui/resources/img/svg/str.svg index 3f191c0..4a24983 100644 --- a/tilingsgui/resources/img/svg/str.svg +++ b/tilingsgui/resources/img/svg/str.svg @@ -1,4 +1,4 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/tikz.svg b/tilingsgui/resources/img/svg/tikz.svg index 2284e29..8a31f30 100644 --- a/tilingsgui/resources/img/svg/tikz.svg +++ b/tilingsgui/resources/img/svg/tikz.svg @@ -1,4 +1,4 @@ TIKZ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/undo.svg b/tilingsgui/resources/img/svg/undo.svg index 59d5ef9..77000a4 100644 --- a/tilingsgui/resources/img/svg/undo.svg +++ b/tilingsgui/resources/img/svg/undo.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/resources/img/svg/verification.svg b/tilingsgui/resources/img/svg/verification.svg index 774b447..229ea80 100644 --- a/tilingsgui/resources/img/svg/verification.svg +++ b/tilingsgui/resources/img/svg/verification.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/tilingsgui/state.py b/tilingsgui/state.py index 5ba5aed..dd33f5c 100644 --- a/tilingsgui/state.py +++ b/tilingsgui/state.py @@ -1,5 +1,4 @@ -"""A global state for the app, in a seperate module. -""" +"""A global state for the app, in a seperate module.""" from typing import Tuple diff --git a/tilingsgui/tplot.py b/tilingsgui/tplot.py index 34612f8..7750e5a 100644 --- a/tilingsgui/tplot.py +++ b/tilingsgui/tplot.py @@ -1,5 +1,4 @@ -"""The tiling drawing tools. -""" +"""The tiling drawing tools.""" import json from collections import Counter, deque @@ -13,9 +12,8 @@ from tilings import GriddedPerm, Tiling from tilings.algorithms import Factor, FactorWithInterleaving from tilings.exception import InvalidOperationError -from tilings.strategies import ( +from tilings.strategies import ( # DatabaseVerificationStrategy removed v4.0.0 BasicVerificationStrategy, - DatabaseVerificationStrategy, ElementaryVerificationStrategy, InsertionEncodingVerificationStrategy, LocallyFactorableVerificationStrategy, @@ -36,11 +34,19 @@ class TPlot: REQ_NOT_FOUND: ClassVar[Tuple[int, int, int]] = (-1, -1, -1) OBS_NOT_FOUND: ClassVar[Tuple[int, int]] = (-1, -1) - _OBSTRUCTION_COLOR: ClassVar[Tuple[float, float, float]] = Color.RED - _REQUIREMENT_COLOR: ClassVar[Tuple[float, float, float]] = Color.BLUE - _HIGHLIGHT_COLOR: ClassVar[Tuple[float, float, float]] = Color.ORANGE - _EMPTY_COLOR: ClassVar[Tuple[float, float, float]] = Color.GRAY - _SHADED_CELL_COLOR: ClassVar[Tuple[float, float, float]] = Color.GRAY + _OBSTRUCTION_COLOR: ClassVar[Tuple[float, float, float]] = Color.scale_to_01( + Color.RED + ) + _REQUIREMENT_COLOR: ClassVar[Tuple[float, float, float]] = Color.scale_to_01( + Color.BLUE + ) + _HIGHLIGHT_COLOR: ClassVar[Tuple[float, float, float]] = Color.scale_to_01( + Color.ORANGE + ) + _EMPTY_COLOR: ClassVar[Tuple[float, float, float]] = Color.scale_to_01(Color.GRAY) + _SHADED_CELL_COLOR: ClassVar[Tuple[float, float, float]] = Color.scale_to_01( + Color.GRAY + ) _FUZZYNESS = 0.25 # Should be in [0,0.5) _CLICK_PRECISION_SQUARED: int = 100 _POINT_SIZE = 5 @@ -469,7 +475,7 @@ class TPlotManager(pyglet.event.EventDispatcher, Observer): _MAX_SEQUENCE_SIZE: ClassVar[int] = 7 _VERIFICATION_STRATS: ClassVar[List[str]] = [ "BasicVerificationStrategy", - "DatabaseVerificationStrategy", + # "DatabaseVerificationStrategy", # Removed in tilings 4.0.0 "ElementaryVerificationStrategy", "InsertionEncodingVerificationStrategy", "LocallyFactorableVerificationStrategy", @@ -492,7 +498,7 @@ def _verify(tiling: Tiling) -> List[str]: """ return [ str(BasicVerificationStrategy().verified(tiling)), - str(DatabaseVerificationStrategy().verified(tiling)), + # str(DatabaseVerificationStrategy().verified(tiling)), # Removed str(ElementaryVerificationStrategy().verified(tiling)), str(InsertionEncodingVerificationStrategy().verified(tiling)), str(LocallyFactorableVerificationStrategy().verified(tiling)), diff --git a/tilingsgui/utils.py b/tilingsgui/utils.py index f2b4c3c..8624262 100644 --- a/tilingsgui/utils.py +++ b/tilingsgui/utils.py @@ -1,5 +1,4 @@ -"""A collection of various utility functionality. -""" +"""A collection of various utility functionality.""" import datetime diff --git a/tilingsgui/widgets.py b/tilingsgui/widgets.py index 808b00a..72945cd 100644 --- a/tilingsgui/widgets.py +++ b/tilingsgui/widgets.py @@ -1,5 +1,4 @@ -"""Graphical UI components. -""" +"""Graphical UI components.""" from typing import Callable, ClassVar, Dict, List, Optional, Tuple @@ -30,10 +29,16 @@ def __init__(self, init_text: str, font_size: int, color: RGBA) -> None: self._document: pyglet.text.document.UnformattedDocument = ( pyglet.text.document.UnformattedDocument(init_text) ) - self._document.set_style(0, 0, dict(font_size=font_size, color=color)) + self._document.set_style(0, 0, {"font_size": font_size, "color": color}) self._layout: pyglet.text.layout.IncrementalTextLayout = ( pyglet.text.layout.IncrementalTextLayout( - self._document, 0, 0, multiline=False, batch=self._batch + self._document, + x=0, + y=0, + width=100, + height=20, # Will be updated in position() + multiline=False, + batch=self._batch, ) ) self._caret: pyglet.text.caret.Caret = pyglet.text.caret.Caret(self._layout) @@ -48,10 +53,10 @@ def position(self, x: float, y: float, w: float, h: float) -> None: w (float): The horizontal length of the component. h (float): The vertical length of the component. """ - self._layout.x = x + Text._LEFT_PAD - self._layout.y = y - self._layout.width = w - Text._LEFT_PAD - self._layout.height = h + self._layout.x = int(x + Text._LEFT_PAD) + self._layout.y = int(y - 15) + self._layout.width = int(w - Text._LEFT_PAD) + self._layout.height = int(h) def set_focus(self) -> None: """Set focus on the input text. This is needed to write to it.""" @@ -119,12 +124,14 @@ def __init__( box_color (Tuple[float, float, float]): The rgb color of the box. """ super().__init__(init_text, font_size, text_color) - self._vertex_list: pyglet.graphics.vertexdomain.VertexList = self._batch.add( - 4, - pyglet.gl.GL_QUADS, - None, - ("v2f", [0] * 8), - ("c3B", box_color * 4), + box_color_255 = Color.scale_to_255(box_color) + self._rectangle = pyglet.shapes.Rectangle( + x=0, + y=0, + width=100, + height=20, # Will be updated in position() + color=box_color_255, + batch=self._batch, ) def position(self, x: float, y: float, w: float, h: float) -> None: @@ -137,8 +144,10 @@ def position(self, x: float, y: float, w: float, h: float) -> None: h (float): The vertical length of the component. """ super().position(x, y, w, h) - for i, vertex in enumerate((x, y, x + w, y, x + w, y + h, x, y + h)): - self._vertex_list.vertices[i] = vertex + self._rectangle.x = x + self._rectangle.y = y + self._rectangle.width = w + self._rectangle.height = h def hit_test(self, x: float, y: float) -> bool: """Is the point (x,y) inside the rectangle that the text box forms. @@ -151,8 +160,8 @@ def hit_test(self, x: float, y: float) -> bool: [type]: True iff inside. """ return ( - self._vertex_list.vertices[0] < x < self._vertex_list.vertices[2] - and self._vertex_list.vertices[1] < y < self._vertex_list.vertices[5] + self._rectangle.x < x < self._rectangle.x + self._rectangle.width + and self._rectangle.y < y < self._rectangle.y + self._rectangle.height ) @@ -214,9 +223,25 @@ def position(self, x: float, y: float, w: float, h: float) -> None: h (float): The vertical length of the button. """ self._x, self._y, self._w, self._h = x, y, w, h - dim = min(w, h) - if dim > 0: - self._sprite.scale *= dim / self._sprite.width + if w > 0 and h > 0: + # Scale to fit within the button bounds while maintaining aspect ratio + # Handle both AbstractImage and Animation types + image = self._sprite.image + if hasattr(image, "width") and hasattr(image, "height"): + img_width = float(image.width) + img_height = float(image.height) + else: + # Fallback for Animation or other types - use sprite dimensions + img_width = float(self._sprite.width) + img_height = float(self._sprite.height) + + scale_x = float(w) / img_width + scale_y = float(h) / img_height + # Use the smaller scale to ensure image fits within bounds + scale = ( + min(scale_x, scale_y) * 0.95 + ) # 0.95 for slightly smaller, well-proportioned images + self._sprite.scale = scale self._sprite.x = x + self._w / 2 - self._sprite.width / 2 self._sprite.y = y + self._h / 2 - self._sprite.height / 2 @@ -373,7 +398,7 @@ def __contains__(self, key: Tuple[int, int]) -> bool: class ButtonGrid: """A positional object to place and group buttons together.""" - _PADDING: ClassVar[int] = 1 + _PADDING: ClassVar[int] = 2 def __init__(self, r: int, c: int) -> None: """Create a button grid with r rows and c columns. @@ -413,7 +438,7 @@ def position(self, x: float, y: float, w: float, h: float) -> None: w (float): The horizontal length of the grid. h (float): The vertical length of the grid. """ - self.rect.x, self.rect.y, self.rect.h, self.rect.h = x, y, w, h + self.rect.x, self.rect.y, self.rect.w, self.rect.h = x, y, w, h self.button_w, self.button_h = w / len(self.buttons[0]), h / len(self.buttons) self._position_btns() diff --git a/tox.ini b/tox.ini index 85c2a40..d2fb7a9 100644 --- a/tox.ini +++ b/tox.ini @@ -6,23 +6,28 @@ [tox] envlist = flake8, mypy, pylint, black - py{37,38,39,310}, - pypy37 + py{38,39,310,311,312,313}, + pypy{38,39,310} [default] -basepython=python3.8 +basepython=python3.11 [testenv] description = run test basepython = - py37: python3.7 py38: python3.8 py39: python3.9 py310: python3.10 - pypy37: pypy3 + py311: python3.11 + py312: python3.12 + py313: python3.13 + pypy38: pypy3.8 + pypy39: pypy3.9 + pypy310: pypy3.10 deps = - pytest==7.2.0 - pytest-timeout==2.1.0 + setuptools + pytest>=8.0.0 + pytest-timeout>=2.3.0 commands = pytest [pytest] @@ -34,8 +39,8 @@ description = run flake8 (linter) basepython = {[default]basepython} skip_install = True deps = - flake8==5.0.4 - flake8-isort==5.0.0 + flake8>=7.0.0 + flake8-isort>=6.0.0 commands = flake8 --isort-show-traceback tilingsgui tests setup.py @@ -43,19 +48,19 @@ commands = description = run pylint (static code analysis) basepython = {[default]basepython} deps = - pylint==2.15.5 + pylint>=3.0.0 commands = pylint tilingsgui [testenv:mypy] description = run mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.990 + mypy>=1.13.0 commands = mypy [testenv:black] description = check that comply with autoformating basepython = {[default]basepython} deps = - black==22.10.0 + black>=24.0.0 commands = black --check --diff .