diff --git a/.circleci/config.yml b/.circleci/config.yml index 031c83c0d9..640479ab69 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,14 +28,9 @@ jobs: environment: # these environment variables will be passed to the docker container - - CIBW_ENVIRONMENT: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk - - CIBW_BUILD: "cp3{8,9,10,11,12}-* pp3{8,9,10}-*" - CIBW_ARCHS: aarch64 - - CIBW_SKIP: '*-musllinux_*' - CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014_base_aarch64 - CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux2014_base_aarch64 - - CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,music,timing --time_out 300 - - CIBW_BUILD_VERBOSITY: 2 steps: - checkout @@ -47,7 +42,7 @@ jobs: - run: name: Build the Linux wheels. command: | - pip3 install --user cibuildwheel==2.18.1 + pip3 install --user cibuildwheel==2.21.1 PATH="$HOME/.local/bin:$PATH" cibuildwheel --output-dir wheelhouse - store_artifacts: @@ -57,5 +52,15 @@ jobs: # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: build-arm: + when: + equal: [ "", << pipeline.parameters.GHA_Actor >> ] + jobs: + - linux-arm-wheels + + # run a separate, identical release job only if triggered + build-arm-release: + when: + not: + equal: [ "", << pipeline.parameters.GHA_Actor >> ] jobs: - linux-arm-wheels diff --git a/.github/ISSUE_TEMPLATE/blank_issue.md b/.github/ISSUE_TEMPLATE/blank_issue.md index 3364940bb3..02205dc269 100644 --- a/.github/ISSUE_TEMPLATE/blank_issue.md +++ b/.github/ISSUE_TEMPLATE/blank_issue.md @@ -2,7 +2,7 @@ name: 🗎 Blank Issue about: A blank issue. For those who know what they are doing. title: '' -labels: +labels: assignees: '' ---- \ No newline at end of file +--- diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d49c959f84..795e661cfd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,7 +11,7 @@ assignees: '' **Environment:** -If possible, please include the output of `pygame.print_debug_info()` from your program in your bug report. It looks something +If possible, please include the output of `pygame.print_debug_info()` from your program in your bug report. It looks something like this: ``` @@ -33,7 +33,7 @@ Freetype versions: Linked: 2.11.1 Compiled: 2.11.1 Display Driver: windows Mixer Driver: wasapi ``` -If you can't get the debug output, any of the environment details included in it that you do know would be useful +If you can't get the debug output, any of the environment details included in it that you do know would be useful in diagnosing the issue & helping you. Other environment details, not included in `print_debug_info()`, that might help: diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md index d51828ae3f..8166019d92 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -9,4 +9,4 @@ assignees: '' **Description** -Describe your enhancement, as clearly as possible. \ No newline at end of file +Describe your enhancement, as clearly as possible. diff --git a/.github/workflows/build-debian-multiarch.yml b/.github/workflows/build-debian-multiarch.yml index 20ffc87e74..9676b48101 100644 --- a/.github/workflows/build-debian-multiarch.yml +++ b/.github/workflows/build-debian-multiarch.yml @@ -49,7 +49,7 @@ env: jobs: build-multiarch: name: Debian (Bookworm - 12) [${{ matrix.arch }}] - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false # if a particular matrix build fails, don't skip the rest @@ -65,7 +65,7 @@ jobs: - { arch: armv7, base_image: 'balenalib/raspberrypi3-debian:bookworm' } steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Build sources and run tests uses: uraimo/run-on-arch-action@v2.7.2 @@ -107,7 +107,7 @@ jobs: export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=disk python3 -m pygame.tests -v --exclude opengl,music,timing --time_out 300 - + # Upload the generated files under github actions assets section - name: Upload dist uses: actions/upload-artifact@v4 @@ -120,7 +120,7 @@ jobs: test-armv7-on-armv6: needs: build-multiarch name: Debian (Bookworm - 12) [build - armv7, test - armv6] - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Download all multiarch artifacts uses: actions/download-artifact@v4 diff --git a/.github/workflows/build-emsdk.yml b/.github/workflows/build-emsdk.yml index a90ec6201c..0c9ea22a52 100644 --- a/.github/workflows/build-emsdk.yml +++ b/.github/workflows/build-emsdk.yml @@ -41,7 +41,7 @@ jobs: SDKROOT: /opt/python-wasm-sdk steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Regen with latest cython (using system python3) run: | diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index a6ff2d1999..5da113a4ac 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -1,7 +1,7 @@ name: macOS -# Run CI only when a release is created, on changes to main branch, or any PR -# to main. Do not run CI on any other branch. Also, skip any non-source changes +# Run CI only when a release is created, on changes to main branch, or any PR +# to main. Do not run CI on any other branch. Also, skip any non-source changes # from running on CI on: push: @@ -27,7 +27,7 @@ on: - '.github/workflows/*.yml' # re-include current file to not be excluded - '!.github/workflows/build-macos.yml' - + # the github release drafter can call this workflow workflow_call: @@ -47,14 +47,14 @@ jobs: - { macarch: x86_64, os: macos-13 } steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Test for Mac Deps cache hit id: macdep-cache uses: actions/cache@v4.0.2 with: path: ${{ github.workspace }}/pygame_mac_deps_${{ matrix.macarch }} - # The hash of all files in buildconfig manylinux-build and macdependencies is + # The hash of all files in buildconfig manylinux-build and macdependencies is # the key to the cache. If anything changes here, the deps are built again key: macdep-${{ hashFiles('buildconfig/manylinux-build/**') }}-${{ hashFiles('buildconfig/macdependencies/*.sh') }}-${{ matrix.macarch }}-${{ matrix.os }} lookup-only: true @@ -76,58 +76,18 @@ jobs: # path: ${{ github.workspace }}/pygame_mac_deps_${{ matrix.macarch }} build: - name: ${{ matrix.name }} + name: ${{ matrix.macarch }} needs: deps runs-on: ${{ matrix.os }} strategy: fail-fast: false # if a particular matrix build fails, don't skip the rest matrix: - # Split job into 5 matrix builds, because GH actions provides 5 concurrent - # builds on macOS. This needs to be manually kept updated so that each - # of these builds take roughly the same time include: - - { - name: "x86_64 (CPython 3.9 - 3.12)", - macarch: x86_64, - os: macos-13, - pyversions: "cp3{9,10,11,12}-*", - } - - - { - name: "x86_64 (Python 3.8)", - macarch: x86_64, - os: macos-13, - # CPython/PyPy 3.8 - pyversions: "?p38-*", - } - - - { - name: "x86_64 (PyPy 3.9 and 3.10)", - macarch: x86_64, - os: macos-13, - pyversions: "pp39-* pp310-*", - } - - - { - name: "arm64 (CPython 3.8 - 3.10)", - macarch: arm64, - os: macos-14, - pyversions: "cp3{8,9,10}-*", - } - - - { - name: "arm64 (CPython 3.11 - 3.12)", - macarch: arm64, - os: macos-14, - pyversions: "cp3{11,12}-*", - } + - { macarch: arm64, os: macos-14 } + - { macarch: x86_64, os: macos-13 } env: MAC_ARCH: ${{ matrix.macarch }} - # load pip config from this file. Define this in 'CIBW_ENVIRONMENT' - # because this should not affect cibuildwheel machinery - # also define environment variables needed for testing - CIBW_ENVIRONMENT: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk # Explicitly tell CIBW what the wheel arch deployment target should be # There seems to be no better way to set this than this env @@ -140,8 +100,6 @@ jobs: # should be for 10.11 on x86 MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macarch == 'x86_64' && '10.11' || '11.0' }} - CIBW_BUILD: ${{ matrix.pyversions }} - CIBW_ARCHS: ${{ matrix.macarch }} # Setup macOS dependencies @@ -152,25 +110,19 @@ jobs: bash ./install_mac_deps.sh CIBW_BEFORE_BUILD: | - pip install numpy cp -r ${{ github.workspace }}/pygame_mac_deps_${{ matrix.macarch }} ${{ github.workspace }}/pygame_mac_deps # To remove any speculations about the wheel not being self-contained CIBW_BEFORE_TEST: rm -rf ${{ github.workspace }}/pygame_mac_deps - CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,timing --time_out 300 - - # Increase pip debugging output - CIBW_BUILD_VERBOSITY: 2 - steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: pip cache uses: actions/cache@v4.0.2 with: path: ~/Library/Caches/pip # This cache path is only right on mac - key: pip-cache-${{ matrix.name }} + key: pip-cache-${{ matrix.macarch }}-${{ matrix.os }} - name: Fetch Mac deps id: macdep-cache @@ -180,12 +132,16 @@ jobs: key: macdep-${{ hashFiles('buildconfig/manylinux-build/**') }}-${{ hashFiles('buildconfig/macdependencies/*.sh') }}-${{ matrix.macarch }} fail-on-cache-miss: true + - name: Install uv for speed + uses: yezz123/setup-uv@v4 + with: + uv-version: "0.4.10" + - name: Build and test wheels - uses: pypa/cibuildwheel@v2.18.1 + uses: pypa/cibuildwheel@v2.21.1 - uses: actions/upload-artifact@v4 with: - name: pygame-wheels-macos-${{ matrix.name }} + name: pygame-wheels-macos-${{ matrix.macarch }} path: ./wheelhouse/*.whl compression-level: 0 # wheels are already zip files, no need for more compression - diff --git a/.github/workflows/build-manylinux.yml b/.github/workflows/build-manylinux.yml index 2bd6d84143..0a6ede1155 100644 --- a/.github/workflows/build-manylinux.yml +++ b/.github/workflows/build-manylinux.yml @@ -1,7 +1,7 @@ name: ManyLinux -# Run CI only when a release is created, on changes to main branch, or any PR -# to main. Do not run CI on any other branch. Also, skip any non-source changes +# Run CI only when a release is created, on changes to main branch, or any PR +# to main. Do not run CI on any other branch. Also, skip any non-source changes # from running on CI on: push: @@ -27,7 +27,7 @@ on: - '.github/workflows/*.yml' # re-include current file to not be excluded - '!.github/workflows/build-manylinux.yml' - + # the github release drafter can call this workflow workflow_call: @@ -38,51 +38,29 @@ concurrency: jobs: build: name: ${{ matrix.arch }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 permissions: contents: read packages: write - + strategy: fail-fast: false # if a particular matrix build fails, don't skip the rest matrix: arch: [x86_64, i686] - - env: - # load pip config from this file. Define this in 'CIBW_ENVIRONMENT' - # because this should not affect cibuildwheel machinery - # also define environment variables needed for testing - CIBW_ENVIRONMENT: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk - CIBW_BUILD: "cp3{8,9,10,11,12}-* pp3{8,9,10}-*" + env: CIBW_ARCHS: ${{ matrix.arch }} - # skip musllinux for now - CIBW_SKIP: '*-musllinux_*' - - CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,music,timing --time_out 300 - - # To 'solve' this issue: - # >>> process 338: D-Bus library appears to be incorrectly set up; failed to read - # machine uuid: Failed to open "/var/lib/dbus/machine-id": No such file or directory - CIBW_BEFORE_TEST: | - if [ ! -f /var/lib/dbus/machine-id ]; then - dbus-uuidgen > /var/lib/dbus/machine-id - fi - - # Increase pip debugging output - CIBW_BUILD_VERBOSITY: 2 - steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Log in to the Container registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + - name: Inspect image, skip build if image exists id: inspect continue-on-error: true @@ -97,7 +75,7 @@ jobs: - name: Build and push Docker image if: steps.inspect.outcome == 'failure' - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 + uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 with: context: ${{ github.workspace }}/buildconfig/manylinux-build/docker_base file: ${{ github.workspace }}/buildconfig/manylinux-build/docker_base/Dockerfile-${{ matrix.arch }} @@ -113,7 +91,7 @@ jobs: CIBW_MANYLINUX_I686_IMAGE: ghcr.io/${{ github.repository }}_i686:${{ steps.meta.outputs.version }} CIBW_MANYLINUX_PYPY_I686_IMAGE: ghcr.io/${{ github.repository }}_i686:${{ steps.meta.outputs.version }} - uses: pypa/cibuildwheel@v2.18.1 + uses: pypa/cibuildwheel@v2.21.1 # We upload the generated files under github actions assets - name: Upload dist @@ -122,4 +100,3 @@ jobs: name: pygame-wheels-manylinux-${{ matrix.arch }} path: ./wheelhouse/*.whl compression-level: 0 # wheels are already zip files, no need for more compression - diff --git a/.github/workflows/build-on-msys2.yml b/.github/workflows/build-on-msys2.yml index 4eb03319a5..a4cb6e9ba2 100644 --- a/.github/workflows/build-on-msys2.yml +++ b/.github/workflows/build-on-msys2.yml @@ -44,12 +44,11 @@ jobs: - { sys: mingw64, env: x86_64 } - { sys: mingw32, env: i686 } - { sys: ucrt64, env: ucrt-x86_64 } - - { sys: clang32, env: clang-i686 } - { sys: clang64, env: clang-x86_64 } # - { sys: clangarm64, env: clang-aarch64 } steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.sys }} diff --git a/.github/workflows/build-ubuntu-coverage.yml b/.github/workflows/build-ubuntu-coverage.yml new file mode 100644 index 0000000000..865ae53b1d --- /dev/null +++ b/.github/workflows/build-ubuntu-coverage.yml @@ -0,0 +1,102 @@ +# this workflow generates C code coverage information from the unit test +# suite. Note that for intrinsics, it only runs what gets compiled +# and would naturally run. It also is limited to what can run in +# a CI environment +# IMPORTANT: binaries are not to be uploaded from this workflow! + +name: Ubuntu coverage + +# Run CI only when a release is created, on changes to main branch, or any PR +# to main. Do not run CI on any other branch. Also, skip any non-source changes +# from running on CI +on: + push: + branches: main + paths-ignore: + - 'docs/**' + - 'examples/**' + - '.gitignore' + - '*.rst' + - '*.md' + - '.github/workflows/*.yml' + # gcov/lcov only gets C coverage + - 'src_py/**' + # re-include current file to not be excluded + - '!.github/workflows/build-ubuntu-coverage.yml' + + pull_request: + branches: main + paths-ignore: + - 'docs/**' + - 'examples/**' + - '.gitignore' + - '*.rst' + - '*.md' + - '.github/workflows/*.yml' + # gcov/lcov only gets C coverage + - 'src_py/**' + # re-include current file to not be excluded + - '!.github/workflows/build-ubuntu-coverage.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-ubuntu-coverage + cancel-in-progress: true + +jobs: + gen_coverage: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false # if a particular matrix build fails, don't skip the rest + matrix: + os: [ubuntu-24.04] + + env: + # Pip now forces us to either make a venv or set this flag, so we will do + # this + PIP_BREAK_SYSTEM_PACKAGES: 1 + # We are using dependencies installed from apt + PG_DEPS_FROM_SYSTEM: 1 + + steps: + - uses: actions/checkout@v4.2.0 + + - name: Install deps + # install numpy from pip and not apt because the one from pip is newer, + # and has typestubs + # https://github.com/actions/runner-images/issues/7192 + # https://github.com/orgs/community/discussions/47863 + run: | + sudo apt-get update --fix-missing + sudo apt-get install lcov -y + sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev libfreetype6-dev libportmidi-dev python3-dev -y + pip3 install --upgrade pip + pip3 install meson-python ninja cython "sphinx<=7.2.6" # because we are doing --no-build-isolation + pip3 install numpy>=1.21.0 + + - name: Build with coverage hooks and install + id: build + run: | + pip3 install -e . --no-build-isolation -Cbuild-dir=./.mesonpy-rel -Csetup-args=-Dcoverage=true + + - name: Run tests + env: + SDL_VIDEODRIVER: "dummy" + SDL_AUDIODRIVER: "disk" + run: python3 -m pygame.tests -v --exclude opengl,music,timing --time_out 300 + + - name: Generate coverage + id: gen-coverage + # want to continue regardless of whether a test failed or not as long as the job wasn't cancelled + if: ${{ steps.build.conclusion == 'success' && !cancelled() }} + run: | + lcov --capture --directory . --output-file ./coverage.info + genhtml ./coverage.info --output-directory ./out + + # We upload the generated files under github actions assets + - name: Upload coverage html + # want to continue only if the coverage generation was successful + if: ${{ steps.gen-coverage.conclusion == 'success' && !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: pygame-wheels-coverage + path: ./out diff --git a/.github/workflows/build-ubuntu-sdist.yml b/.github/workflows/build-ubuntu-sdist.yml index 9d4bb2a35a..dbda77773b 100644 --- a/.github/workflows/build-ubuntu-sdist.yml +++ b/.github/workflows/build-ubuntu-sdist.yml @@ -7,8 +7,8 @@ name: Ubuntu sdist -# Run CI only when a release is created, on changes to main branch, or any PR -# to main. Do not run CI on any other branch. Also, skip any non-source changes +# Run CI only when a release is created, on changes to main branch, or any PR +# to main. Do not run CI on any other branch. Also, skip any non-source changes # from running on CI on: push: @@ -34,7 +34,7 @@ on: - '.github/workflows/*.yml' # re-include current file to not be excluded - '!.github/workflows/build-ubuntu-sdist.yml' - + # the github release drafter can call this workflow workflow_call: @@ -44,14 +44,21 @@ concurrency: jobs: build: - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: fail-fast: false # if a particular matrix build fails, don't skip the rest matrix: - os: [ubuntu-20.04, ubuntu-22.04] + os: [ubuntu-24.04, ubuntu-22.04] + + env: + # Pip now forces us to either make a venv or set this flag, so we will do + # this + PIP_BREAK_SYSTEM_PACKAGES: 1 + # We are using dependencies installed from apt + PG_DEPS_FROM_SYSTEM: 1 steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Install deps # install numpy from pip and not apt because the one from pip is newer, @@ -74,19 +81,17 @@ jobs: SDL_VIDEODRIVER: "dummy" SDL_AUDIODRIVER: "disk" run: python3 -m pygame.tests -v --exclude opengl,music,timing --time_out 300 - + - name: Test typestubs - if: matrix.os == 'ubuntu-22.04' # run stubtest only once run: | pip3 install mypy python3 buildconfig/stubs/stubcheck.py # We upload the generated files under github actions assets - name: Upload sdist - if: matrix.os == 'ubuntu-20.04' # upload sdist only once + if: matrix.os == 'ubuntu-24.04' # upload sdist only once uses: actions/upload-artifact@v4 with: name: pygame-wheels-sdist path: dist/*.tar.gz compression-level: 0 # already compressed, no need for more compression - diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 66ff0cd3ae..ead4917dd4 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -27,7 +27,7 @@ on: - '.github/workflows/*.yml' # re-include current file to not be excluded - '!.github/workflows/build-windows.yml' - + # the github release drafter can call this workflow workflow_call: @@ -37,120 +37,35 @@ concurrency: jobs: build: - name: ${{ matrix.name }} + name: ${{ matrix.winarch }} runs-on: windows-latest strategy: fail-fast: false # if a particular matrix build fails, don't skip the rest matrix: include: - - { - name: "CPython 3.12 (64 bit)", - winarch: AMD64, - msvc-dev-arch: x86_amd64, - pyversions: "cp312-*" - } - - { - name: "CPython 3.11 (64 bit)", - winarch: AMD64, - msvc-dev-arch: x86_amd64, - pyversions: "cp311-*" - } - - { - name: "CPython 3.10 (64 bit)", - winarch: AMD64, - msvc-dev-arch: x86_amd64, - pyversions: "cp310-*" - } - - { - name: "CPython 3.9 (64 bit)", - winarch: AMD64, - msvc-dev-arch: x86_amd64, - pyversions: "cp39-*" - } - - { - name: "CPython 3.8 (64 bit)", - winarch: AMD64, - msvc-dev-arch: x86_amd64, - pyversions: "cp38-*" - } - - { - name: "CPython 3.12 (32 bit)", - winarch: x86, - msvc-dev-arch: x86, - pyversions: "cp312-win32*" - } - - { - name: "CPython 3.11 (32 bit)", - winarch: x86, - msvc-dev-arch: x86, - pyversions: "cp311-win32" - } - - { - name: "CPython 3.10 (32 bit)", - winarch: x86, - msvc-dev-arch: x86, - pyversions: "cp310-win32" - } - - { - name: "CPython 3.9 (32 bit)", - winarch: x86, - msvc-dev-arch: x86, - pyversions: "cp39-win32" - } - - { - name: "CPython 3.8 (32 bit)", - winarch: x86, - msvc-dev-arch: x86, - pyversions: "cp38-win32" - } - - { - name: "Pypy 3.8", - winarch: AMD64, - msvc-dev-arch: x86_amd64, - pyversions: "pp38-*" - } - - { - name: "Pypy 3.9", - winarch: AMD64, - msvc-dev-arch: x86_amd64, - pyversions: "pp39-*" - } - - { - name: "Pypy 3.10", - winarch: AMD64, - msvc-dev-arch: x86_amd64, - pyversions: "pp310-*" - } - + - { winarch: AMD64, msvc-dev-arch: x86_amd64 } + - { winarch: x86, msvc-dev-arch: x86 } env: - # load pip config from this file. Define this in 'CIBW_ENVIRONMENT' - # because this should not affect cibuildwheel machinery - # also define environment variables needed for testing - CIBW_ENVIRONMENT: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk - - CIBW_BUILD: ${{ matrix.pyversions }} CIBW_ARCHS: ${{ matrix.winarch }} - CIBW_BEFORE_BUILD: pip install numpy - - CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,timing --time_out 300 - - # Increase pip debugging output - CIBW_BUILD_VERBOSITY: 2 - steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - uses: TheMrMilchmann/setup-msvc-dev@v3 # this lets us use the developer command prompt on windows with: arch: ${{ matrix.msvc-dev-arch }} + - name: Install uv for speed + uses: yezz123/setup-uv@v4 + with: + uv-version: "0.4.10" + - name: Build and test wheels - uses: pypa/cibuildwheel@v2.18.1 + uses: pypa/cibuildwheel@v2.21.1 - uses: actions/upload-artifact@v4 with: - name: pygame-wheels-windows-${{ matrix.name }} + name: pygame-wheels-windows-${{ matrix.winarch }} path: ./wheelhouse/*.whl compression-level: 0 # wheels are already zip files, no need for more compression diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml index adf7455141..101d6fec2a 100644 --- a/.github/workflows/cppcheck.yml +++ b/.github/workflows/cppcheck.yml @@ -19,10 +19,10 @@ concurrency: # TODO: Any more static checkers can be added here jobs: run-cppcheck: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Install deps # https://github.com/actions/runner-images/issues/7192 @@ -32,6 +32,10 @@ jobs: sudo apt install cppcheck - name: Run Static Checker - # skip cppcheck on SDL_gfx and scrap for now - run: cppcheck src_c --force --enable=performance,portability,warning \ - --suppress=*:src_c/SDL_gfx/* --suppress=*:src_c/scrap* + # skip cppcheck on SDL_gfx, scrap, scale_mm* and ft_cache for now + # suppress missingReturn and syntaxError because it gives many false positives + run: cppcheck src_c --enable=performance,portability,warning \ + --suppress=*:src_c/freetype/ft_cache.c --suppress=*:src_c/scrap* \ + --suppress=*:src_c/scale_mmx*.c --suppress=*:src_c/SDL_gfx/* \ + --suppress=missingReturn --suppress=syntaxError -DWITH_THREAD -j $(nproc) \ + -DPG_MAJOR_VERSION -DPG_MINOR_VERSION -DPG_PATCH_VERSION -DPG_VERSION_TAG diff --git a/.github/workflows/format-lint.yml b/.github/workflows/format-lint.yml index b107bb6d40..f9bb78d7ef 100644 --- a/.github/workflows/format-lint.yml +++ b/.github/workflows/format-lint.yml @@ -30,17 +30,22 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - uses: actions/setup-python@v5 with: python-version: 3.x - uses: pre-commit/action@v3.0.1 format-lint-code-check: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 + + env: + # Pip now forces us to either make a venv or set this flag, so we will do + # this + PIP_BREAK_SYSTEM_PACKAGES: 1 steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Install deps run: python3 -m pip install pylint sphinx"<7.2.0" diff --git a/.github/workflows/release-gh-draft.yml b/.github/workflows/release-gh-draft.yml index 52c18f7096..391a9088c2 100644 --- a/.github/workflows/release-gh-draft.yml +++ b/.github/workflows/release-gh-draft.yml @@ -32,8 +32,13 @@ jobs: draft-release: needs: [manylinux-aarch64, manylinux, macos, windows, sdist] runs-on: ubuntu-latest + permissions: + id-token: write + attestations: write + contents: write + steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Download all artifacts uses: actions/download-artifact@v4 @@ -55,6 +60,11 @@ jobs: id: ver run: echo "VER=${GITHUB_REF_NAME#'release/'}" >> $GITHUB_OUTPUT + - name: Generate release attestation + uses: actions/attest-build-provenance@v1.4.3 + with: + subject-path: "pygame-wheels/*" + - name: Draft a release uses: softprops/action-gh-release@v2 with: diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index b0939e9c6e..fd545d5a71 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -12,10 +12,10 @@ jobs: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.2.0 - name: Pull all release assets - uses: robinraju/release-downloader@v1.10 + uses: robinraju/release-downloader@v1.11 with: releaseId: ${{ github.event.release.id }} fileName: "*" @@ -23,5 +23,18 @@ jobs: zipBall: false out-file-path: "dist" + - name: Verify release attestation + env: + GH_TOKEN: ${{ github.token }} + run: | + for fname in dist/*; do + if gh attestation verify $fname -R ${{ github.repository }}; then + echo "[ALLOWED] $fname" + else + rm $fname + echo "[DELETED] $fname" + fi + done + - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index caf7b4a21a..67c03d4c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ dist *.so __pycache__ _headers/* +buildconfig/win_dll_dirs.json # cython generated files src_c/_sdl2/*.c diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7195470ae1..dba4697836 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,26 @@ # Then in the project root directory run `pre-commit install` repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer + exclude: | + (?x)^( + ^docs/licenses/.*$ + | ^.*\.svg$ + | ^.*\.sfd$ + | docs/LGPL.txt + )$ + - id: trailing-whitespace + exclude: | + (?x)^( + ^docs/licenses/.*$ + | ^.*\.svg$ + | ^.*\.sfd$ + | docs/LGPL.txt + )$ + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.4.2 hooks: # See pyproject.toml for configuration options. @@ -22,5 +42,6 @@ repos: | docs/reST/_static/script.js | docs/reST/_templates/header.h | src_c/include/sse2neon.h + | src_c/include/pythoncapi_compat.h | src_c/pypm.c )$ diff --git a/README.rst b/README.rst index 4094a27c57..40ed6441f4 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,5 @@ -.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg +.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg + :width: 800 :alt: pygame :target: https://pyga.me/ @@ -7,7 +8,7 @@ |PyPiVersion| |PyPiLicense| |Python3| |GithubCommits| |BlackFormatBadge| -**English** `简体中文`_ `Français`_ `فارسی`_ `Español`_ +**English** `简体中文`_ `繁體中文`_ `Français`_ `فارسی`_ `Español`_ `日本語`_ --------------------------------------------------------------------------------------------------- Pygame_ is a free and open-source cross-platform library @@ -149,10 +150,22 @@ Dependency versions: | SDL_ttf | >= 2.0.15 | +----------+------------------------+ +How to Contribute +----------------- +First of all, thank you for considering contributing to pygame-ce! It's people like you that make pygame-ce a great library. Please follow these steps to get started: +1. Read the `Contribution Guidelines`_ and the `Many Ways to Contribute`_ wiki pages. +2. Read the documentataion on `Opening A Pull Request`_ and `Opening a Great Pull Request`_. +3. Read how to `label and link reported issues`_. +4. Check the `issue tracker`_ for open issues that interest you or open a new issue to start a discussion about your idea. + +There are many more resources throughout the `wiki pages`_ that can help you get started. + +If you have any questions, please feel free to ask in the `Pygame Community Discord Server`_ or open an issue. License ------- +**License Identifier:** LGPL-2.1-or-later This library is distributed under `GNU LGPL version 2.1`_, which can be found in the file ``docs/LGPL.txt``. We reserve the right to place @@ -192,8 +205,18 @@ See docs/licenses for licenses of dependencies. .. _Compilation wiki page: https://github.com/pygame-community/pygame-ce/wiki#compiling .. _docs page: https://pyga.me/docs .. _GNU LGPL version 2.1: https://www.gnu.org/copyleft/lesser.html +.. _Contribution Guidelines: https://github.com/pygame-community/pygame-ce/wiki/Contribution-guidelines +.. _Many Ways to Contribute: https://github.com/pygame-community/pygame-ce/wiki/Many-ways-to-contribute +.. _Opening A Pull Request: https://github.com/pygame-community/pygame-ce/wiki/Opening-a-pull-request +.. _Opening a Great Pull Request: https://github.com/pygame-community/pygame-ce/wiki/Opening-a-great-pull-request +.. _issue tracker: https://github.com/pygame-community/pygame-ce/issues +.. _label and link reported issues: https://github.com/pygame-community/pygame-ce/wiki/Labelling-&-linking-reported-issues +.. _Pygame Community Discord Server: https://discord.gg/pygame +.. _wiki pages: https://github.com/pygame-community/pygame-ce/wiki .. _简体中文: ./docs/readmes/README.zh-cn.rst +.. _繁體中文: ./docs/readmes/README.zh-tw.rst .. _Français: ./docs/readmes/README.fr.rst .. _فارسی: ./docs/readmes/README.fa.rst .. _Español: ./docs/readmes/README.es.rst +.. _日本語: ./docs/readmes/README.ja.rst diff --git a/buildconfig/Setup.Emscripten.SDL2.in b/buildconfig/Setup.Emscripten.SDL2.in index 23b22f5766..28e86f1e42 100644 --- a/buildconfig/Setup.Emscripten.SDL2.in +++ b/buildconfig/Setup.Emscripten.SDL2.in @@ -43,6 +43,7 @@ image src_c/void.c base src_c/void.c bufferproxy src_c/void.c color src_c/void.c +controller src_c/void.c controller_old src_c/void.c display src_c/void.c draw src_c/void.c @@ -66,6 +67,7 @@ window src_c/void.c geometry src_c/void.c #_sdl2.controller src_c/_sdl2/controller.c $(SDL) $(DEBUG) -Isrc_c +_sdl2.controller src_c/void.c _sdl2.controller_old src_c/void.c #_sdl2.touch src_c/_sdl2/touch.c $(SDL) $(DEBUG) -Isrc_c @@ -73,5 +75,3 @@ _sdl2.touch src_c/void.c #transform src_c/simd_transform_sse2.c src_c/simd_transform_avx2.c src_c/transform.c src_c/rotozoom.c src_c/scale2x.c src_c/scale_mmx.c src_c/simd_surface_fill_avx2.c src_c/simd_surface_fill_sse2.c $(SDL) $(DEBUG) -D_NO_MMX_FOR_X86_64 transform src_c/void.c - - diff --git a/buildconfig/Setup.SDL2.in b/buildconfig/Setup.SDL2.in index 2fd66eeea3..011b8d1404 100644 --- a/buildconfig/Setup.SDL2.in +++ b/buildconfig/Setup.SDL2.in @@ -35,6 +35,7 @@ _sdl2.audio src_c/_sdl2/audio.c $(SDL) $(DEBUG) -Isrc_c _sdl2.video src_c/_sdl2/video.c src_c/pgcompat.c $(SDL) $(DEBUG) -Isrc_c _sdl2.mixer src_c/_sdl2/mixer.c $(SDL) $(MIXER) $(DEBUG) -Isrc_c _sdl2.touch src_c/_sdl2/touch.c $(SDL) $(DEBUG) -Isrc_c +_sdl2.controller src_c/_sdl2/controller.c $(SDL) $(DEBUG) -Isrc_c _sdl2.controller_old src_c/_sdl2/controller_old.c $(SDL) $(DEBUG) -Isrc_c GFX = src_c/SDL_gfx/SDL_gfxPrimitives.c diff --git a/buildconfig/_meson_win_dll.py b/buildconfig/_meson_win_dll.py new file mode 100644 index 0000000000..ab5e8c8de2 --- /dev/null +++ b/buildconfig/_meson_win_dll.py @@ -0,0 +1,14 @@ +""" +A helper file invoked by the meson buildconfig to write DLL paths to a json file +""" + +import json +import sys + +from pathlib import Path + +dll_parents = {str(Path(i).parent) for i in sys.argv[1:]} +win_dll_dirs_file = Path(__file__).parent / "win_dll_dirs.json" + +if __name__ == "__main__": + win_dll_dirs_file.write_text(json.dumps(list(dll_parents)), encoding="utf-8") diff --git a/buildconfig/ci/circleci/pull_circleci_artifacts.py b/buildconfig/ci/circleci/pull_circleci_artifacts.py index dbfc4425c1..43494ae66a 100644 --- a/buildconfig/ci/circleci/pull_circleci_artifacts.py +++ b/buildconfig/ci/circleci/pull_circleci_artifacts.py @@ -2,7 +2,7 @@ A script to automate downloading CircleCI artifacts. Usage: python3 pull_circleci_artifacts.py - TOKEN: + TOKEN: CircleCI "personal access token" of a github (preferably machine) user. This is secret! diff --git a/buildconfig/config.py b/buildconfig/config.py index a84006366c..168baa6c21 100644 --- a/buildconfig/config.py +++ b/buildconfig/config.py @@ -19,6 +19,7 @@ import msysio except ImportError: import buildconfig.msysio as msysio +from buildconfig.make_docs import run as docs_run import sys, os, shutil, logging import sysconfig import re @@ -165,6 +166,11 @@ def main(auto=False): Only SDL2 is supported now.""") kwds = {} + + if len(sys.argv) > 1: + if sys.argv[1] == "docs": + sys.exit(docs_run()) + if sys.platform == 'win32': if sys.version_info >= (3, 8) and is_msys2(): print_('Using WINDOWS MSYS2 configuration...\n') diff --git a/buildconfig/config_emsdk.py b/buildconfig/config_emsdk.py index d5ef458fdc..5063d24520 100644 --- a/buildconfig/config_emsdk.py +++ b/buildconfig/config_emsdk.py @@ -267,4 +267,3 @@ def main(auto_config=False): if __name__ == "__main__": print("This is the configuration subscript for Emscripten.") print('Please run "config.py" for full configuration.') - diff --git a/buildconfig/config_unix.py b/buildconfig/config_unix.py index c5d0b457ec..a9ebc83945 100644 --- a/buildconfig/config_unix.py +++ b/buildconfig/config_unix.py @@ -141,7 +141,7 @@ def main(auto_config=False): origincdirs = ['/include', '/include/SDL2'] origlibdirs = ['/lib', '/lib64', '/X11R6/lib'] - # If we are on a debian based system, we also need to handle + # If we are on a debian based system, we also need to handle # /lib/ # We have a few commands to get the correct , we try those # one by one till we get something that works diff --git a/buildconfig/config_win.py b/buildconfig/config_win.py index 886d860b4e..13dc13b611 100644 --- a/buildconfig/config_win.py +++ b/buildconfig/config_win.py @@ -372,7 +372,7 @@ def _add_sdl2_dll_deps(DEPS): DEPS.add_dll(r'(z|zlib1)\.dll$', 'z', ['zlib-[1-9].*']) DEPS.add_dll(r'(lib)?webp[-0-9]*\.dll$', 'webp', ['*webp-[0-9]*']) DEPS.add_dll(r'(lib)?webpdemux[-0-9]*\.dll$', 'webpdemux', ['*webpdemux-[0-9]*']) - + def setup(): DEPS = DependencyGroup() diff --git a/buildconfig/download_win_prebuilt.py b/buildconfig/download_win_prebuilt.py index aefbce3a8a..221920026a 100644 --- a/buildconfig/download_win_prebuilt.py +++ b/buildconfig/download_win_prebuilt.py @@ -78,8 +78,8 @@ def get_urls(x86=True, x64=True): url_sha1 = [] url_sha1.extend([ [ - 'https://github.com/libsdl-org/SDL/releases/download/release-2.30.3/SDL2-devel-2.30.3-VC.zip', - '2878b4b1fbe9e4b22a317ad52c9d751c70e8df62', + 'https://github.com/libsdl-org/SDL/releases/download/release-2.30.7/SDL2-devel-2.30.7-VC.zip', + '9121a66a4bc45d657d0c15300cee6c7f37ade51d', ], [ 'https://github.com/pygame-community/SDL_image/releases/download/2.8.2-pgce/SDL2_image-devel-2.8.2-VCpgce.zip', @@ -234,12 +234,12 @@ def copy(src, dst): copy( os.path.join( temp_dir, - 'SDL2-devel-2.30.3-VC/SDL2-2.30.3' + 'SDL2-devel-2.30.7-VC/SDL2-2.30.7' ), os.path.join( move_to_dir, prebuilt_dir, - 'SDL2-2.30.3' + 'SDL2-2.30.7' ) ) diff --git a/buildconfig/macdependencies/README.rst b/buildconfig/macdependencies/README.rst index 81b1fbec87..2788a8547f 100644 --- a/buildconfig/macdependencies/README.rst +++ b/buildconfig/macdependencies/README.rst @@ -32,4 +32,3 @@ fi It currently relies on GNU `readlink` to build, which is provided by the coreutils homebrew package. However, this could be fixed to be cross platform, since mac `readlink` does not support `-f`. - diff --git a/buildconfig/manylinux-build/Makefile b/buildconfig/manylinux-build/Makefile index 68d7f766bb..a5532c8281 100644 --- a/buildconfig/manylinux-build/Makefile +++ b/buildconfig/manylinux-build/Makefile @@ -70,7 +70,7 @@ push-manylinux2014-x86: push-manylinux2014-aarch64: docker push pygame/manylinux2014_base_aarch64 - + # push: push-manylinux1-x64 push-manylinux1-x86 push-manylinux2010-x64 push-manylinux2010-x86 push-manylinux2014-x64 push-manylinux2014-x86 push: push-manylinux2010-x64 push-manylinux2010-x86 push-manylinux2014-x64 push-manylinux2014-x86 push-manylinux2014-aarch64 diff --git a/buildconfig/manylinux-build/docker_base/Dockerfile-aarch64 b/buildconfig/manylinux-build/docker_base/Dockerfile-aarch64 index 8eb9c898b1..62f1caf8f6 100644 --- a/buildconfig/manylinux-build/docker_base/Dockerfile-aarch64 +++ b/buildconfig/manylinux-build/docker_base/Dockerfile-aarch64 @@ -1,4 +1,5 @@ -FROM quay.io/pypa/manylinux2014_aarch64 +# pin version on image for CI stability +FROM quay.io/pypa/manylinux2014_aarch64:2024.09.09-0 ENV MAKEFLAGS="-j 2" ENV PG_DEP_PREFIX="/usr/local" @@ -162,4 +163,3 @@ RUN ["bash", "/portmidi_build/build-portmidi.sh"] # run strip on built libraries COPY strip-lib-so-files.sh /tmp/ RUN source /tmp/strip-lib-so-files.sh - diff --git a/buildconfig/manylinux-build/docker_base/Dockerfile-i686 b/buildconfig/manylinux-build/docker_base/Dockerfile-i686 index d516a4581f..9ba1171df2 100644 --- a/buildconfig/manylinux-build/docker_base/Dockerfile-i686 +++ b/buildconfig/manylinux-build/docker_base/Dockerfile-i686 @@ -1,4 +1,5 @@ -FROM quay.io/pypa/manylinux2014_i686 +# pin version on image for CI stability +FROM quay.io/pypa/manylinux2014_i686:2024.09.09-0 ENV MAKEFLAGS="-j 2" ENV PG_DEP_PREFIX="/usr/local" diff --git a/buildconfig/manylinux-build/docker_base/Dockerfile-x86_64 b/buildconfig/manylinux-build/docker_base/Dockerfile-x86_64 index e712fe8e3f..11a2c4150f 100644 --- a/buildconfig/manylinux-build/docker_base/Dockerfile-x86_64 +++ b/buildconfig/manylinux-build/docker_base/Dockerfile-x86_64 @@ -1,4 +1,5 @@ -FROM quay.io/pypa/manylinux2014_x86_64 +# pin version on image for CI stability +FROM quay.io/pypa/manylinux2014_x86_64:2024.09.09-0 ENV MAKEFLAGS="-j 2" ENV PG_DEP_PREFIX="/usr/local" diff --git a/buildconfig/manylinux-build/docker_base/alsa/build-alsa.sh b/buildconfig/manylinux-build/docker_base/alsa/build-alsa.sh index 9a9fdcb2b5..11ecb100af 100644 --- a/buildconfig/manylinux-build/docker_base/alsa/build-alsa.sh +++ b/buildconfig/manylinux-build/docker_base/alsa/build-alsa.sh @@ -11,6 +11,6 @@ tar xjf ${ALSA}.tar.bz2 cd ${ALSA} # alsa prefers /usr prefix as a default, so we explicitly override it -./configure $PG_BASE_CONFIGURE_FLAGS --with-configdir=$PG_DEP_PREFIX/share/alsa +./configure $PG_BASE_CONFIGURE_FLAGS --with-configdir=$PG_DEP_PREFIX/share/alsa make make install diff --git a/buildconfig/manylinux-build/docker_base/buildtools/install.sh b/buildconfig/manylinux-build/docker_base/buildtools/install.sh index b3d49e116d..95f50c050a 100644 --- a/buildconfig/manylinux-build/docker_base/buildtools/install.sh +++ b/buildconfig/manylinux-build/docker_base/buildtools/install.sh @@ -4,7 +4,7 @@ set -e -x cd $(dirname `readlink -f "$0"`) # This file installs tools (cmake and meson+ninja) needed to build dependencies -# Also installs setuptools to make sure distutils is available (on newer python +# Also installs setuptools to make sure distutils is available (on newer python # versions) because some builds may need it. # cmake is also installed via pip because it is easier than maintaining a # separate build script for it diff --git a/buildconfig/manylinux-build/docker_base/glib/build-glib.sh b/buildconfig/manylinux-build/docker_base/glib/build-glib.sh index 83543e51ce..0a1b78c895 100644 --- a/buildconfig/manylinux-build/docker_base/glib/build-glib.sh +++ b/buildconfig/manylinux-build/docker_base/glib/build-glib.sh @@ -20,4 +20,3 @@ meson setup _build $PG_BASE_MESON_FLAGS --force-fallback-for libpcre2-8 -Dtests= meson compile -C _build meson install -C _build - diff --git a/buildconfig/manylinux-build/docker_base/libmodplug/build-libmodplug.sh b/buildconfig/manylinux-build/docker_base/libmodplug/build-libmodplug.sh index de3a1211ca..b5d3ed3b72 100644 --- a/buildconfig/manylinux-build/docker_base/libmodplug/build-libmodplug.sh +++ b/buildconfig/manylinux-build/docker_base/libmodplug/build-libmodplug.sh @@ -15,4 +15,3 @@ cd ${MODPLUG_NAME} ./configure $PG_BASE_CONFIGURE_FLAGS make make install - diff --git a/buildconfig/manylinux-build/docker_base/libpng/build-png.sh b/buildconfig/manylinux-build/docker_base/libpng/build-png.sh index 92fdff9592..105b1a427f 100644 --- a/buildconfig/manylinux-build/docker_base/libpng/build-png.sh +++ b/buildconfig/manylinux-build/docker_base/libpng/build-png.sh @@ -14,4 +14,3 @@ cd $PNG ./configure --with-zlib-prefix=$PG_DEP_PREFIX $PG_BASE_CONFIGURE_FLAGS make make install - diff --git a/buildconfig/manylinux-build/docker_base/mesa/mesa/build-mesa.sh b/buildconfig/manylinux-build/docker_base/mesa/mesa/build-mesa.sh index 308f4d459e..db5c5c94bd 100644 --- a/buildconfig/manylinux-build/docker_base/mesa/mesa/build-mesa.sh +++ b/buildconfig/manylinux-build/docker_base/mesa/mesa/build-mesa.sh @@ -3,7 +3,7 @@ set -e -x cd $(dirname `readlink -f "$0"`) -# We need mesa for opengl, gbm (SDL kmsdrm driver needs it), egl (SDL +# We need mesa for opengl, gbm (SDL kmsdrm driver needs it), egl (SDL # wayland driver needs this) and glx (SDL needs it) # we don't support vulkan yet @@ -21,7 +21,7 @@ cd $MESA if [ `uname -m` == "aarch64" ]; then # On aarch64 we allow mesa to use all drivers it wants to pick by default # (because radeonsi is not used on arm platforms) - GALLIUM_DRIVERS="auto" + GALLIUM_DRIVERS="auto" else # all default except radeonsi GALLIUM_DRIVERS="r300,r600,nouveau,virgl,svga,swrast,iris,crocus,i915" diff --git a/buildconfig/manylinux-build/docker_base/ogg/build-ogg.sh b/buildconfig/manylinux-build/docker_base/ogg/build-ogg.sh index 4029ff4521..ef670d6ea8 100644 --- a/buildconfig/manylinux-build/docker_base/ogg/build-ogg.sh +++ b/buildconfig/manylinux-build/docker_base/ogg/build-ogg.sh @@ -36,4 +36,3 @@ elif [[ "$OSTYPE" == "darwin"* ]]; then fi make make install - diff --git a/buildconfig/manylinux-build/docker_base/pkg-config/build-pkg-config.sh b/buildconfig/manylinux-build/docker_base/pkg-config/build-pkg-config.sh index c2764c1ba9..fd6e0a2683 100644 --- a/buildconfig/manylinux-build/docker_base/pkg-config/build-pkg-config.sh +++ b/buildconfig/manylinux-build/docker_base/pkg-config/build-pkg-config.sh @@ -1,6 +1,6 @@ #!/bin/bash -# This file exists because pkg-config is too old on manylinux docker centos +# This file exists because pkg-config is too old on manylinux docker centos # images (the older version segfaults if it gets a cyclic dependency, like # freetype2+harfbuzz) diff --git a/buildconfig/manylinux-build/docker_base/sdl_libs/build-sdl2-libs.sh b/buildconfig/manylinux-build/docker_base/sdl_libs/build-sdl2-libs.sh index 746ac2aa42..26a6ec525f 100644 --- a/buildconfig/manylinux-build/docker_base/sdl_libs/build-sdl2-libs.sh +++ b/buildconfig/manylinux-build/docker_base/sdl_libs/build-sdl2-libs.sh @@ -3,7 +3,7 @@ set -e -x cd $(dirname `readlink -f "$0"`) -SDL2_VER="2.30.3" +SDL2_VER="2.30.7" SDL2="SDL2-$SDL2_VER" IMG2_VER="2.8.2" IMG2="SDL2_image-$IMG2_VER" @@ -114,4 +114,3 @@ cd $MIX2 make make install - diff --git a/buildconfig/manylinux-build/docker_base/sdl_libs/sdl2.sha512 b/buildconfig/manylinux-build/docker_base/sdl_libs/sdl2.sha512 index 5a2aa027f9..ba5afae51c 100644 --- a/buildconfig/manylinux-build/docker_base/sdl_libs/sdl2.sha512 +++ b/buildconfig/manylinux-build/docker_base/sdl_libs/sdl2.sha512 @@ -1,4 +1,4 @@ -75ddea9ac3c2130e9f6036b4718dbe3d9d1b40dab3cd48d3d488d5a397dd0c226c0573bf18d82eb41498800accf045e259d2ae305f069fad7b34e47a017f2372 SDL2-2.30.3.tar.gz +1a16c10f500dea97bd4e4ca5f560fe4ab8e746c975d30751b7cf567868743d105ce84055d480e4c18b290adac485e67d1bb14ae1719644d6e42223d96f299a16 SDL2-2.30.7.tar.gz 0ff345824f95158dfa72f83f9d4a540601c178cd759334bf849c14a2920b5330d0763413b58c08b3deba8d3a4ccb6ea2a8159f87efe4cbb0e8ea850f63d09454 SDL2_image-2.8.2.tar.gz 5ddbc4b0b5fad2e0844a503daa79564b912654192599ef8fa7698531f08323ce01801f6bb17b2b3905020a3df362a967b7566ae725eb085da991578cc0807aad SDL2_mixer-2.8.0.tar.gz 34a1d210d8f1b1e802139d65ba47e36033bb7881e75a8862c1b1c515565bef85e3d81ee42e952aa664de043debef387ba60088a9cf3ba3297413db39a13af912 SDL2_ttf-2.22.0.tar.gz diff --git a/buildconfig/manylinux-build/docker_base/sndfile/build-sndfile.sh b/buildconfig/manylinux-build/docker_base/sndfile/build-sndfile.sh index 265ea9af15..660b932219 100644 --- a/buildconfig/manylinux-build/docker_base/sndfile/build-sndfile.sh +++ b/buildconfig/manylinux-build/docker_base/sndfile/build-sndfile.sh @@ -17,4 +17,3 @@ cd $SNDNAME ./configure $PG_BASE_CONFIGURE_FLAGS --disable-mpeg --disable-alsa make make install - diff --git a/buildconfig/manylinux-build/docker_base/zlib-ng/build-zlib-ng.sh b/buildconfig/manylinux-build/docker_base/zlib-ng/build-zlib-ng.sh index d00587d5a5..29033209ab 100644 --- a/buildconfig/manylinux-build/docker_base/zlib-ng/build-zlib-ng.sh +++ b/buildconfig/manylinux-build/docker_base/zlib-ng/build-zlib-ng.sh @@ -14,4 +14,3 @@ cd ${ZLIB_NG_NAME} cmake . $PG_BASE_CMAKE_FLAGS -DZLIB_COMPAT=1 make make install - diff --git a/buildconfig/manylinux-build/docker_base/zlib/build-zlib.sh b/buildconfig/manylinux-build/docker_base/zlib/build-zlib.sh index 6cbaae0551..9f99471caf 100644 --- a/buildconfig/manylinux-build/docker_base/zlib/build-zlib.sh +++ b/buildconfig/manylinux-build/docker_base/zlib/build-zlib.sh @@ -14,4 +14,3 @@ cd ${ZLIB_NAME} ./configure $PG_BASE_CONFIGURE_FLAGS make make install - diff --git a/buildconfig/stubs/gen_stubs.py b/buildconfig/stubs/gen_stubs.py index 670db2f951..488ba2cfc7 100644 --- a/buildconfig/stubs/gen_stubs.py +++ b/buildconfig/stubs/gen_stubs.py @@ -4,6 +4,7 @@ """ import pathlib +import shutil from typing import Any import pygame.constants @@ -51,6 +52,7 @@ "system", "geometry", "window", + "typing", ] # pygame classes that are autoimported into main namespace are kept in this dict @@ -66,13 +68,13 @@ "_debug": ["print_debug_info"], "event": ["Event"], "font": ["Font"], - "mixer": ["Channel"], + "mixer": ["Sound", "Channel"], "time": ["Clock"], "joystick": ["Joystick"], "window": ["Window"], "base": ["__version__"], # need an explicit import # uncomment below line if Circle is added to the base namespace later - # "geometry": ["Circle"], + # "geometry": ["Circle"], } # pygame modules from which __init__.py does the equivalent of @@ -151,3 +153,8 @@ def get_all(mod: Any): for element in get_all(pygame.locals): constant_type = getattr(pygame.locals, element).__class__.__name__ f.write(f"{element}: {constant_type}\n") + +# copy typing.py to typing.pyi for type checkers +typing_py_file = pathlib.Path(__file__).parent.parent.parent / "src_py" / "typing.py" +typing_stub_file = pathlib.Path(__file__).parent / "pygame" / "typing.pyi" +shutil.copyfile(typing_py_file, typing_stub_file) diff --git a/buildconfig/stubs/mypy_allow_list.txt b/buildconfig/stubs/mypy_allow_list.txt index c6f3ffa15b..1e7c532d72 100644 --- a/buildconfig/stubs/mypy_allow_list.txt +++ b/buildconfig/stubs/mypy_allow_list.txt @@ -2,14 +2,10 @@ # listed here are not checked by the mypy stubtest program # This allowlist supports regex -# This is not a real typestub file, it is used only in the typestubs to export -# a few utility typestub definitions -pygame\._common - # cython files have this top level dunder pygame\._sdl2\..*\.__test__ -# cython classes have some special dunders for internal use, ignore that in +# cython classes have some special dunders for internal use, ignore that in # stubtest pygame\._sdl2\..*\.__pyx_.*__ @@ -31,7 +27,3 @@ pygame\.pypm pygame\._sdl2\.mixer pygame\.sysfont.* pygame\.docs.* - -# temporarily ignore the controller module while it is being converted -# to C -pygame\._sdl2\.controller diff --git a/buildconfig/stubs/pygame/__init__.pyi b/buildconfig/stubs/pygame/__init__.pyi index c05a428fb4..a8ecdd7f25 100644 --- a/buildconfig/stubs/pygame/__init__.pyi +++ b/buildconfig/stubs/pygame/__init__.pyi @@ -38,6 +38,7 @@ from pygame import ( system as system, geometry as geometry, window as window, + typing as typing, ) from .rect import Rect as Rect, FRect as FRect @@ -51,7 +52,7 @@ from .mask import Mask as Mask from ._debug import print_debug_info as print_debug_info from .event import Event as Event from .font import Font as Font -from .mixer import Channel as Channel +from .mixer import Sound as Sound, Channel as Channel from .time import Clock as Clock from .joystick import Joystick as Joystick from .window import Window as Window @@ -183,6 +184,9 @@ from .constants import ( FINGERDOWN as FINGERDOWN, FINGERMOTION as FINGERMOTION, FINGERUP as FINGERUP, + FLASH_BRIEFLY as FLASH_BRIEFLY, + FLASH_CANCEL as FLASH_CANCEL, + FLASH_UNTIL_FOCUSED as FLASH_UNTIL_FOCUSED, FONT_CENTER as FONT_CENTER, FONT_LEFT as FONT_LEFT, FONT_RIGHT as FONT_RIGHT, diff --git a/buildconfig/stubs/pygame/_common.pyi b/buildconfig/stubs/pygame/_common.pyi deleted file mode 100644 index 19e958a724..0000000000 --- a/buildconfig/stubs/pygame/_common.pyi +++ /dev/null @@ -1,104 +0,0 @@ -from os import PathLike -from typing import IO, Callable, Tuple, Union, TypeVar - -from typing_extensions import Literal as Literal, SupportsIndex as SupportsIndex -from typing_extensions import Protocol - -# For functions that take a file name -AnyPath = Union[str, bytes, PathLike[str], PathLike[bytes]] - -# Most pygame functions that take a file argument should be able to handle -# a FileArg type -FileArg = Union[AnyPath, IO[bytes], IO[str]] - -_T = TypeVar("_T", covariant=True) - -class Sequence(Protocol[_T]): - """ - This is different from the standard python 'Sequence' abc. This is used in places - where only __getitem__ and __len__ is actually needed (basically almost all places - where a sequence is used). The standard 'Sequence' abc has some extra methods. - """ - def __getitem__(self, __i: SupportsIndex) -> _T: ... - def __len__(self) -> int: ... - -# Right now, it isn't possible to annotate sizes (popular tools don't support it) but -# when it is, the below types should be appropriately annotated - -# Yes, float. The reason being, pygame handles float without erroring in a lot of places -# where a coordinate is expected (usually by rounding to int). -# Also, 'Union[int, float] == float' -Coordinate = Sequence[float] - -# This is used in places where ints are strictly required -IntCoordinate = Sequence[int] - -# This typehint is used when a function would return an RGBA tuple -RGBAOutput = Tuple[int, int, int, int] -ColorValue = Union[int, str, Sequence[int]] - -_CanBeRect = Sequence[Union[float, Coordinate]] - -class _HasRectAttribute(Protocol): - # An object that has a rect attribute that is either a rect, or a function - # that returns a rect confirms to the rect protocol - rect: Union[RectValue, Callable[[], RectValue]] - -RectValue = Union[_CanBeRect, _HasRectAttribute] - -""" -# testing code -def a(b: Coordinate): - b[0] - b[1] - len(b) - e1, e2 = b - for i in b: - i -= 1 - - -import numpy -from pygame import Vector2 - -class MyAmoger: - def __init__(self): - pass - - def __getitem__(self, index): - if index not in (0, 1): - raise IndexError() - - return 42 if index else 69 - - def __len__(self): - return 2 - - -# should pass -a([1, 2]) -a([4.2, 5.2]) -a((1, 2)) -a((1.4, 2.8)) -a(MyAmoger()) -a(range(2, 4)) # yes, range object is a 'Sequence' -a(numpy.array([1.3, 2.1])) -a(b"ab") # weird but this actually works in code (this represents (97, 98) btw) -a(bytearray([1, 2])) -a(Vector2()) - -print("Done testing the passes!") - -# should technically error, but right now we can't annotate sizes so they pass on -# type testing -a([1, 2, 3]) -a([4.2, 5.2, 2, 4]) -a((1,)) -a(numpy.array([1.3, 2.1, 4.2])) - -# all of the below should always error -a({}) -a({1: 2}) -a("abc") -a({1, 2}) - -""" diff --git a/buildconfig/stubs/pygame/_sdl2/controller.pyi b/buildconfig/stubs/pygame/_sdl2/controller.pyi new file mode 100644 index 0000000000..1ff2ac9188 --- /dev/null +++ b/buildconfig/stubs/pygame/_sdl2/controller.pyi @@ -0,0 +1,31 @@ +from typing import final +from pygame.joystick import JoystickType + +def init() -> None: ... +def get_init() -> bool: ... +def quit() -> None: ... +def is_controller(device_index: int) -> bool: ... +def get_count() -> int: ... +@final +class Controller: + def __new__(cls, device_index: int) -> Controller: ... + def __init__(self, device_index: int) -> None: ... + @classmethod + def from_joystick(cls, joystick: JoystickType) -> Controller: ... + def get_init(self) -> bool: ... + def init(self) -> None: ... + def quit(self) -> None: ... + @property + def id(self) -> int: ... + @property + def name(self) -> str: ... + def attached(self) -> bool: ... + def as_joystick(self) -> JoystickType: ... + def get_axis(self, axis: int) -> int: ... + def get_button(self, button: int) -> bool: ... + def get_mapping(self) -> dict: ... + def set_mapping(self, mapping: dict) -> int: ... + def rumble(self, ___) -> bool: ... + def stop_rumble( + self, low_frequency: float, high_frequency: float, duration: int + ) -> bool: ... diff --git a/buildconfig/stubs/pygame/_sdl2/video.pyi b/buildconfig/stubs/pygame/_sdl2/video.pyi index dd12ae5f80..b34970d4eb 100644 --- a/buildconfig/stubs/pygame/_sdl2/video.pyi +++ b/buildconfig/stubs/pygame/_sdl2/video.pyi @@ -5,7 +5,7 @@ from pygame.rect import Rect from pygame.surface import Surface from pygame.window import Window as Window -from .._common import ColorValue, RectValue, Coordinate +from pygame.typing import ColorLike, RectLike, Coordinate WINDOWPOS_UNDEFINED: int WINDOWPOS_CENTERED: int @@ -60,13 +60,13 @@ class Texture: @property def color(self) -> Color: ... @color.setter - def color(self, value: ColorValue) -> None: ... + def color(self, value: ColorLike) -> None: ... def get_rect(self, **kwargs: Any) -> Rect: ... def draw( self, - srcrect: Optional[RectValue] = None, - dstrect: Optional[RectValue] = None, + srcrect: Optional[RectLike] = None, + dstrect: Optional[RectLike] = None, angle: float = 0.0, origin: Optional[Iterable[int]] = None, flip_x: bool = False, @@ -99,17 +99,17 @@ class Texture: p3_mod: Iterable[int] = (255, 255, 255, 255), p4_mod: Iterable[int] = (255, 255, 255, 255), ) -> None: ... - def update(self, surface: Surface, area: Optional[RectValue] = None) -> None: ... + def update(self, surface: Surface, area: Optional[RectLike] = None) -> None: ... class Image: def __init__( self, texture_or_image: Union[Texture, Image], - srcrect: Optional[RectValue] = None, + srcrect: Optional[RectLike] = None, ) -> None: ... def get_rect(self) -> Rect: ... def draw( - self, srcrect: Optional[RectValue] = None, dstrect: Optional[RectValue] = None + self, srcrect: Optional[RectLike] = None, dstrect: Optional[RectLike] = None ) -> None: ... angle: float origin: Optional[Iterable[float]] @@ -123,7 +123,7 @@ class Image: @property def color(self) -> Color: ... @color.setter - def color(self, value: ColorValue) -> None: ... + def color(self, value: ColorLike) -> None: ... class Renderer: def __init__( @@ -140,25 +140,25 @@ class Renderer: @property def draw_color(self) -> Color: ... @draw_color.setter - def draw_color(self, value: ColorValue) -> None: ... + def draw_color(self, value: ColorLike) -> None: ... def clear(self) -> None: ... def present(self) -> None: ... def get_viewport(self) -> Rect: ... - def set_viewport(self, area: Optional[RectValue]) -> None: ... + def set_viewport(self, area: Optional[RectLike]) -> None: ... logical_size: Iterable[int] scale: Iterable[float] target: Optional[Texture] def blit( self, source: Union[Texture, Image], - dest: Optional[RectValue] = None, - area: Optional[RectValue] = None, + dest: Optional[RectLike] = None, + area: Optional[RectLike] = None, special_flags: int = 0, ) -> Rect: ... def draw_line(self, p1: Coordinate, p2: Coordinate) -> None: ... def draw_point(self, point: Coordinate) -> None: ... - def draw_rect(self, rect: RectValue) -> None: ... - def fill_rect(self, rect: RectValue) -> None: ... + def draw_rect(self, rect: RectLike) -> None: ... + def fill_rect(self, rect: RectLike) -> None: ... def draw_triangle( self, p1: Coordinate, p2: Coordinate, p3: Coordinate ) -> None: ... @@ -172,7 +172,7 @@ class Renderer: self, p1: Coordinate, p2: Coordinate, p3: Coordinate, p4: Coordinate ) -> None: ... def to_surface( - self, surface: Optional[Surface] = None, area: Optional[RectValue] = None + self, surface: Optional[Surface] = None, area: Optional[RectLike] = None ) -> Surface: ... @staticmethod def compose_custom_blend_mode( diff --git a/buildconfig/stubs/pygame/camera.pyi b/buildconfig/stubs/pygame/camera.pyi index 5de3a41194..de5eac1767 100644 --- a/buildconfig/stubs/pygame/camera.pyi +++ b/buildconfig/stubs/pygame/camera.pyi @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import List, Optional, Tuple, Union, Literal -from ._common import IntCoordinate +from pygame.typing import IntCoordinate from pygame.surface import Surface diff --git a/buildconfig/stubs/pygame/color.pyi b/buildconfig/stubs/pygame/color.pyi index d53344de1d..635b05d4ea 100644 --- a/buildconfig/stubs/pygame/color.pyi +++ b/buildconfig/stubs/pygame/color.pyi @@ -1,7 +1,8 @@ import sys -from typing import Any, Dict, Iterator, Tuple, Union, overload +from typing import Any, Dict, Iterator, SupportsIndex, Tuple, Union, overload +from typing_extensions import deprecated # added in 3.13 -from ._common import ColorValue, SupportsIndex +from pygame.typing import ColorLike if sys.version_info >= (3, 9): from collections.abc import Collection @@ -27,7 +28,7 @@ class Color(Collection[int]): @overload def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ... @overload - def __init__(self, rgbvalue: ColorValue) -> None: ... + def __init__(self, rgbvalue: ColorLike) -> None: ... @overload def __getitem__(self, i: SupportsIndex) -> int: ... @overload @@ -79,11 +80,12 @@ class Color(Collection[int]): def from_normalized(cls, r: float, g: float, b: float, a: float, /) -> Color: ... def normalize(self) -> Tuple[float, float, float, float]: ... def correct_gamma(self, gamma: float, /) -> Color: ... + @deprecated("since 2.1.3. Use unpacking instead") def set_length(self, length: int, /) -> None: ... - def lerp(self, color: ColorValue, amount: float) -> Color: ... + def lerp(self, color: ColorLike, amount: float) -> Color: ... def premul_alpha(self) -> Color: ... def grayscale(self) -> Color: ... @overload def update(self, r: int, g: int, b: int, a: int = 255, /) -> None: ... @overload - def update(self, rgbvalue: ColorValue, /) -> None: ... + def update(self, rgbvalue: ColorLike, /) -> None: ... diff --git a/buildconfig/stubs/pygame/constants.pyi b/buildconfig/stubs/pygame/constants.pyi index 9ed59ec44f..3d793e3c50 100644 --- a/buildconfig/stubs/pygame/constants.pyi +++ b/buildconfig/stubs/pygame/constants.pyi @@ -107,6 +107,9 @@ DROPTEXT: int FINGERDOWN: int FINGERMOTION: int FINGERUP: int +FLASH_BRIEFLY: int +FLASH_CANCEL: int +FLASH_UNTIL_FOCUSED: int FONT_CENTER: int FONT_LEFT: int FONT_RIGHT: int diff --git a/buildconfig/stubs/pygame/cursors.pyi b/buildconfig/stubs/pygame/cursors.pyi index d6b610946e..0bd2d77c6d 100644 --- a/buildconfig/stubs/pygame/cursors.pyi +++ b/buildconfig/stubs/pygame/cursors.pyi @@ -1,8 +1,8 @@ -from typing import Any, Iterator, Tuple, Union, overload +from typing import Any, Iterator, Literal, Tuple, Union, overload from pygame.surface import Surface -from ._common import FileArg, Literal, IntCoordinate, Sequence +from pygame.typing import FileLike, IntCoordinate, SequenceLike _Small_string = Tuple[ str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str @@ -47,13 +47,13 @@ sizer_xy_strings: _Small_string textmarker_strings: _Small_string def compile( - strings: Sequence[str], + strings: SequenceLike[str], black: str = "X", white: str = ".", xor: str = "o", ) -> Tuple[Tuple[int, ...], Tuple[int, ...]]: ... def load_xbm( - curs: FileArg, mask: FileArg + curs: FileLike, mask: FileLike ) -> Tuple[Tuple[int, int], Tuple[int, int], Tuple[int, ...], Tuple[int, ...]]: ... class Cursor: @@ -66,8 +66,8 @@ class Cursor: self, size: IntCoordinate, hotspot: IntCoordinate, - xormasks: Sequence[int], - andmasks: Sequence[int], + xormasks: SequenceLike[int], + andmasks: SequenceLike[int], ) -> None: ... @overload def __init__( @@ -82,7 +82,7 @@ class Cursor: def __getitem__( self, index: int ) -> Union[int, IntCoordinate, Surface]: ... - copy = __copy__ + def copy(self) -> Cursor: ... type: Literal["system", "color", "bitmap"] data: Union[ Tuple[int], diff --git a/buildconfig/stubs/pygame/display.pyi b/buildconfig/stubs/pygame/display.pyi index 399f462ffa..0acf086e99 100644 --- a/buildconfig/stubs/pygame/display.pyi +++ b/buildconfig/stubs/pygame/display.pyi @@ -1,17 +1,17 @@ -from typing import Dict, List, Optional, Tuple, Union, overload, Literal +from typing import Dict, List, Optional, Tuple, Union, overload, Literal, Iterable +from typing_extensions import deprecated # added in 3.13 from pygame.constants import FULLSCREEN from pygame.surface import Surface from pygame._sdl2 import Window -from ._common import ( - ColorValue, +from pygame.typing import ( + ColorLike, Coordinate, IntCoordinate, - RectValue, - RGBAOutput, - Sequence, + RectLike, + SequenceLike, ) class _VidInfo: @@ -20,9 +20,9 @@ class _VidInfo: video_mem: int bitsize: int bytesize: int - masks: RGBAOutput - shifts: RGBAOutput - losses: RGBAOutput + masks: Tuple[int, int, int, int] + shifts: Tuple[int, int, int, int] + losses: Tuple[int, int, int, int] blit_hw: int blit_hw_CC: int blit_hw_A: int @@ -47,7 +47,7 @@ def get_surface() -> Surface: ... def flip() -> None: ... @overload def update( - rectangle: Optional[Union[RectValue, Sequence[Optional[RectValue]]]] = None, / + rectangle: Optional[Union[RectLike, Iterable[Optional[RectLike]]]] = None, / ) -> None: ... @overload def update(x: int, y: int, w: int, h: int, /) -> None: ... @@ -72,14 +72,16 @@ def gl_set_attribute(flag: int, value: int, /) -> None: ... def get_active() -> bool: ... def iconify() -> bool: ... def toggle_fullscreen() -> int: ... +@deprecated("since 2.1.4. Removed in SDL3") def set_gamma(red: float, green: float = ..., blue: float = ..., /) -> int: ... +@deprecated("since 2.1.4. Removed in SDL3") def set_gamma_ramp( - red: Sequence[int], green: Sequence[int], blue: Sequence[int], / + red: SequenceLike[int], green: SequenceLike[int], blue: SequenceLike[int], / ) -> int: ... def set_icon(surface: Surface, /) -> None: ... def set_caption(title: str, icontitle: Optional[str] = None, /) -> None: ... def get_caption() -> Tuple[str, str]: ... -def set_palette(palette: Sequence[ColorValue], /) -> None: ... +def set_palette(palette: SequenceLike[ColorLike], /) -> None: ... def get_num_displays() -> int: ... def get_window_size() -> Tuple[int, int]: ... def get_window_position() -> Tuple[int, int]:... @@ -96,7 +98,7 @@ def message_box( message: Optional[str] = None, message_type: Literal["info", "warn", "error"] = "info", parent_window: Optional[Window] = None, - buttons: Sequence[str] = ("OK",), + buttons: SequenceLike[str] = ("OK",), return_button: int = 0, escape_button: Optional[int] = None, ) -> int: ... diff --git a/buildconfig/stubs/pygame/draw.pyi b/buildconfig/stubs/pygame/draw.pyi index db2c345a4d..57bae66712 100644 --- a/buildconfig/stubs/pygame/draw.pyi +++ b/buildconfig/stubs/pygame/draw.pyi @@ -2,12 +2,12 @@ from pygame.rect import Rect from pygame.surface import Surface from typing import overload -from ._common import ColorValue, Coordinate, RectValue, Sequence +from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike def rect( surface: Surface, - color: ColorValue, - rect: RectValue, + color: ColorLike, + rect: RectLike, width: int = 0, border_radius: int = -1, border_top_left_radius: int = -1, @@ -17,13 +17,13 @@ def rect( ) -> Rect: ... def polygon( surface: Surface, - color: ColorValue, - points: Sequence[Coordinate], + color: ColorLike, + points: SequenceLike[Coordinate], width: int = 0, ) -> Rect: ... def circle( surface: Surface, - color: ColorValue, + color: ColorLike, center: Coordinate, radius: float, width: int = 0, @@ -35,7 +35,7 @@ def circle( @overload def aacircle( surface: Surface, - color: ColorValue, + color: ColorLike, center: Coordinate, radius: float, width: int = 0, @@ -43,7 +43,7 @@ def aacircle( @overload def aacircle( surface: Surface, - color: ColorValue, + color: ColorLike, center: Coordinate, radius: float, width: int = 0, @@ -53,39 +53,39 @@ def aacircle( draw_bottom_right: bool = False, ) -> Rect: ... def ellipse( - surface: Surface, color: ColorValue, rect: RectValue, width: int = 0 + surface: Surface, color: ColorLike, rect: RectLike, width: int = 0 ) -> Rect: ... def arc( surface: Surface, - color: ColorValue, - rect: RectValue, + color: ColorLike, + rect: RectLike, start_angle: float, stop_angle: float, width: int = 1, ) -> Rect: ... def line( surface: Surface, - color: ColorValue, + color: ColorLike, start_pos: Coordinate, end_pos: Coordinate, width: int = 1, ) -> Rect: ... def lines( surface: Surface, - color: ColorValue, + color: ColorLike, closed: bool, - points: Sequence[Coordinate], + points: SequenceLike[Coordinate], width: int = 1, ) -> Rect: ... def aaline( surface: Surface, - color: ColorValue, + color: ColorLike, start_pos: Coordinate, end_pos: Coordinate, ) -> Rect: ... def aalines( surface: Surface, - color: ColorValue, + color: ColorLike, closed: bool, - points: Sequence[Coordinate], + points: SequenceLike[Coordinate], ) -> Rect: ... diff --git a/buildconfig/stubs/pygame/event.pyi b/buildconfig/stubs/pygame/event.pyi index dc9a1c852f..1987f96de9 100644 --- a/buildconfig/stubs/pygame/event.pyi +++ b/buildconfig/stubs/pygame/event.pyi @@ -7,7 +7,7 @@ from typing import ( final, ) -from ._common import Sequence +from pygame.typing import SequenceLike @final class Event: @@ -23,7 +23,7 @@ class Event: def __delattr__(self, name: str) -> None: ... def __bool__(self) -> bool: ... -_EventTypes = Union[int, Sequence[int]] +_EventTypes = Union[int, SequenceLike[int]] def pump() -> None: ... def get( diff --git a/buildconfig/stubs/pygame/font.pyi b/buildconfig/stubs/pygame/font.pyi index 683a28ef2b..ea1074cee7 100644 --- a/buildconfig/stubs/pygame/font.pyi +++ b/buildconfig/stubs/pygame/font.pyi @@ -1,8 +1,8 @@ -from typing import Callable, Hashable, Iterable, List, Optional, Tuple, Union +from typing import Callable, Hashable, Iterable, List, Literal, Optional, Tuple, Union from pygame.surface import Surface -from ._common import ColorValue, FileArg, Literal +from pygame.typing import ColorLike, FileLike # TODO: Figure out a way to type this attribute such that mypy knows it's not # always defined at runtime @@ -56,13 +56,13 @@ class Font: def point_size(self) -> int: ... @point_size.setter def point_size(self, value: int) -> None: ... - def __init__(self, filename: Optional[FileArg] = None, size: int = 20) -> None: ... + def __init__(self, filename: Optional[FileLike] = None, size: int = 20) -> None: ... def render( self, text: Union[str, bytes, None], antialias: bool, - color: ColorValue, - bgcolor: Optional[ColorValue] = None, + color: ColorLike, + bgcolor: Optional[ColorLike] = None, wraplength: int = 0, ) -> Surface: ... def size(self, text: Union[str, bytes], /) -> Tuple[int, int]: ... diff --git a/buildconfig/stubs/pygame/freetype.pyi b/buildconfig/stubs/pygame/freetype.pyi index 54751f8385..8ce24499b8 100644 --- a/buildconfig/stubs/pygame/freetype.pyi +++ b/buildconfig/stubs/pygame/freetype.pyi @@ -1,16 +1,18 @@ from typing import Any, Callable, Iterable, List, Optional, Tuple, Union +from typing_extensions import deprecated # added in 3.13 from pygame.color import Color from pygame.rect import Rect from pygame.surface import Surface -from ._common import ColorValue, FileArg, RectValue +from pygame.typing import ColorLike, FileLike, RectLike def get_error() -> str: ... def get_version(linked: bool = True) -> Tuple[int, int, int]: ... def init(cache_size: int = 64, resolution: int = 72) -> None: ... def quit() -> None: ... def get_init() -> bool: ... +@deprecated("Use `pygame.freetype.get_init` instead") def was_init() -> bool: ... def get_cache_size() -> int: ... def get_default_resolution() -> int: ... @@ -121,18 +123,18 @@ class Font: @property def fgcolor(self) -> Color: ... @fgcolor.setter - def fgcolor(self, value: ColorValue) -> None: ... + def fgcolor(self, value: ColorLike) -> None: ... @property def bgcolor(self) -> Color: ... @bgcolor.setter - def bgcolor(self, value: ColorValue) -> None: ... + def bgcolor(self, value: ColorLike) -> None: ... @property def origin(self) -> bool: ... @origin.setter def origin(self, value: bool) -> None: ... def __init__( self, - file: Optional[FileArg], + file: Optional[FileLike], size: float = 0, font_index: int = 0, resolution: int = 0, @@ -156,8 +158,8 @@ class Font: def render( self, text: str, - fgcolor: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, + fgcolor: Optional[ColorLike] = None, + bgcolor: Optional[ColorLike] = None, style: int = STYLE_DEFAULT, rotation: int = 0, size: float = 0, @@ -165,10 +167,10 @@ class Font: def render_to( self, surf: Surface, - dest: RectValue, + dest: RectLike, text: str, - fgcolor: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, + fgcolor: Optional[ColorLike] = None, + bgcolor: Optional[ColorLike] = None, style: int = STYLE_DEFAULT, rotation: int = 0, size: float = 0, @@ -185,7 +187,7 @@ class Font: self, array: Any, text: str, - dest: Optional[RectValue] = None, + dest: Optional[RectLike] = None, style: int = STYLE_DEFAULT, rotation: int = 0, size: float = 0, diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index fb7c8de850..8a3e3fc43c 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -5,14 +5,15 @@ from typing import ( Protocol, Tuple, Sequence, + List, ) from pygame import Rect, FRect -from ._common import Coordinate, RectValue +from pygame.typing import Coordinate, RectLike, SequenceLike from .rect import Rect, FRect from .math import Vector2 -_CanBeCircle = Union[Circle, Tuple[Coordinate, float], Sequence[float]] +_CanBeCircle = Union[Circle, Tuple[Coordinate, float], SequenceLike[float]] class _HasCirclettribute(Protocol): # An object that has a circle attribute that is either a circle, or a function @@ -21,6 +22,7 @@ class _HasCirclettribute(Protocol): _CircleValue = Union[_CanBeCircle, _HasCirclettribute] _CanBeCollided = Union[Circle, Rect, FRect, Coordinate, Vector2] +_CanBeIntersected = Union[Circle] class Circle: @property @@ -63,6 +65,22 @@ class Circle: def center(self) -> Tuple[float, float]: ... @center.setter def center(self, value: Coordinate) -> None: ... + @property + def top(self) -> Tuple[float, float]: ... + @top.setter + def top(self, value: Coordinate) -> None: ... + @property + def left(self) -> Tuple[float, float]: ... + @left.setter + def left(self, value: Coordinate) -> None: ... + @property + def bottom(self) -> Tuple[float, float]: ... + @bottom.setter + def bottom(self, value: Coordinate) -> None: ... + @property + def right(self) -> Tuple[float, float]: ... + @right.setter + def right(self, value: Coordinate) -> None: ... @overload def __init__(self, x: float, y: float, r: float) -> None: ... @overload @@ -88,12 +106,15 @@ class Circle: @overload def collidecircle(self, center: Coordinate, r: float, /) -> bool: ... @overload - def colliderect(self, rect: RectValue, /) -> bool: ... + def colliderect(self, rect: RectLike, /) -> bool: ... @overload def colliderect(self, x: float, y: float, w: float, h: float, /) -> bool: ... @overload def colliderect(self, topleft: Coordinate, size: Coordinate, /) -> bool: ... def collideswith(self, other: _CanBeCollided, /) -> bool: ... + def collidelist(self, colliders: Sequence[_CanBeCollided], /) -> int: ... + def collidelistall(self, colliders: Sequence[_CanBeCollided], /) -> List[int]: ... + def intersect(self, other: _CanBeIntersected, /) -> List[Tuple[float, float]]: ... def contains(self, shape: _CanBeCollided) -> bool: ... @overload def update(self, circle: _CircleValue, /) -> None: ... @@ -111,5 +132,5 @@ class Circle: def rotate_ip(self, angle: float, /) -> None: ... def as_rect(self) -> Rect: ... def as_frect(self) -> FRect: ... + def copy(self) -> Circle: ... def __copy__(self) -> Circle: ... - copy = __copy__ diff --git a/buildconfig/stubs/pygame/gfxdraw.pyi b/buildconfig/stubs/pygame/gfxdraw.pyi index d3592e6b7c..44a02d7d82 100644 --- a/buildconfig/stubs/pygame/gfxdraw.pyi +++ b/buildconfig/stubs/pygame/gfxdraw.pyi @@ -1,28 +1,28 @@ from pygame.surface import Surface -from ._common import ColorValue, Coordinate, RectValue, Sequence +from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike -def pixel(surface: Surface, x: int, y: int, color: ColorValue, /) -> None: ... -def hline(surface: Surface, x1: int, x2: int, y: int, color: ColorValue, /) -> None: ... -def vline(surface: Surface, x: int, y1: int, y2: int, color: ColorValue, /) -> None: ... +def pixel(surface: Surface, x: int, y: int, color: ColorLike, /) -> None: ... +def hline(surface: Surface, x1: int, x2: int, y: int, color: ColorLike, /) -> None: ... +def vline(surface: Surface, x: int, y1: int, y2: int, color: ColorLike, /) -> None: ... def line( - surface: Surface, x1: int, y1: int, x2: int, y2: int, color: ColorValue, / + surface: Surface, x1: int, y1: int, x2: int, y2: int, color: ColorLike, / ) -> None: ... -def rectangle(surface: Surface, rect: RectValue, color: ColorValue, /) -> None: ... -def box(surface: Surface, rect: RectValue, color: ColorValue, /) -> None: ... -def circle(surface: Surface, x: int, y: int, r: int, color: ColorValue, /) -> None: ... -def aacircle(surface: Surface, x: int, y: int, r: int, color: ColorValue, /) -> None: ... +def rectangle(surface: Surface, rect: RectLike, color: ColorLike, /) -> None: ... +def box(surface: Surface, rect: RectLike, color: ColorLike, /) -> None: ... +def circle(surface: Surface, x: int, y: int, r: int, color: ColorLike, /) -> None: ... +def aacircle(surface: Surface, x: int, y: int, r: int, color: ColorLike, /) -> None: ... def filled_circle( - surface: Surface, x: int, y: int, r: int, color: ColorValue, / + surface: Surface, x: int, y: int, r: int, color: ColorLike, / ) -> None: ... def ellipse( - surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, / + surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, / ) -> None: ... def aaellipse( - surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, / + surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, / ) -> None: ... def filled_ellipse( - surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, / + surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, / ) -> None: ... def arc( surface: Surface, @@ -31,7 +31,7 @@ def arc( r: int, start_angle: int, atp_angle: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def pie( surface: Surface, @@ -40,7 +40,7 @@ def pie( r: int, start_angle: int, atp_angle: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def trigon( surface: Surface, @@ -50,7 +50,7 @@ def trigon( y2: int, x3: int, y3: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def aatrigon( surface: Surface, @@ -60,7 +60,7 @@ def aatrigon( y2: int, x3: int, y3: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def filled_trigon( surface: Surface, @@ -70,20 +70,20 @@ def filled_trigon( y2: int, x3: int, y3: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def polygon( - surface: Surface, points: Sequence[Coordinate], color: ColorValue, / + surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, / ) -> None: ... def aapolygon( - surface: Surface, points: Sequence[Coordinate], color: ColorValue, / + surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, / ) -> None: ... def filled_polygon( - surface: Surface, points: Sequence[Coordinate], color: ColorValue, / + surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, / ) -> None: ... def textured_polygon( - surface: Surface, points: Sequence[Coordinate], texture: Surface, tx: int, ty: int, / + surface: Surface, points: SequenceLike[Coordinate], texture: Surface, tx: int, ty: int, / ) -> None: ... def bezier( - surface: Surface, points: Sequence[Coordinate], steps: int, color: ColorValue, / + surface: Surface, points: SequenceLike[Coordinate], steps: int, color: ColorLike, / ) -> None: ... diff --git a/buildconfig/stubs/pygame/image.pyi b/buildconfig/stubs/pygame/image.pyi index a9a00a8504..0e02f02271 100644 --- a/buildconfig/stubs/pygame/image.pyi +++ b/buildconfig/stubs/pygame/image.pyi @@ -1,47 +1,48 @@ -from typing import Optional, Tuple, Union +from typing import Literal, Optional, Tuple, Union +from typing_extensions import deprecated # added in 3.13 from pygame.bufferproxy import BufferProxy from pygame.surface import Surface -from ._common import FileArg, Literal, IntCoordinate, Coordinate +from pygame.typing import FileLike, IntCoordinate, Coordinate _BufferStyle = Union[BufferProxy, bytes, bytearray, memoryview] -_to_string_format = Literal[ - "P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA", "RGBA_PREMULT", "ARGB_PREMULT" +_to_bytes_format = Literal[ + "P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA", "ABGR", "RGBA_PREMULT", "ARGB_PREMULT" ] _from_buffer_format = Literal["P", "RGB", "BGR", "BGRA", "RGBX", "RGBA", "ARGB"] -_from_string_format = Literal["P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA"] +_from_bytes_format = Literal["P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA", "ABGR"] -def load(file: FileArg, namehint: str = "") -> Surface: ... -def load_sized_svg(file: FileArg, size: Coordinate) -> Surface: ... -def save(surface: Surface, file: FileArg, namehint: str = "") -> None: ... +def load(file: FileLike, namehint: str = "") -> Surface: ... +def load_sized_svg(file: FileLike, size: Coordinate) -> Surface: ... +def save(surface: Surface, file: FileLike, namehint: str = "") -> None: ... def get_sdl_image_version(linked: bool = True) -> Optional[Tuple[int, int, int]]: ... def get_extended() -> bool: ... +@deprecated("since 2.3.0. Use `pygame.image.tobytes` instead") def tostring( surface: Surface, - format: _to_string_format, + format: _to_bytes_format, flipped: bool = False, pitch: int = -1, ) -> bytes: ... +@deprecated("since 2.3.0. Use `pygame.image.frombytes` instead") def fromstring( bytes: bytes, size: IntCoordinate, - format: _from_string_format, + format: _from_bytes_format, flipped: bool = False, pitch: int = -1, ) -> Surface: ... - -# the use of tobytes/frombytes is preferred over tostring/fromstring def tobytes( surface: Surface, - format: _to_string_format, + format: _to_bytes_format, flipped: bool = False, pitch: int = -1, ) -> bytes: ... def frombytes( bytes: bytes, size: IntCoordinate, - format: _from_string_format, + format: _from_bytes_format, flipped: bool = False, pitch: int = -1, ) -> Surface: ... @@ -51,6 +52,6 @@ def frombuffer( format: _from_buffer_format, pitch: int = -1, ) -> Surface: ... -def load_basic(file: FileArg, /) -> Surface: ... -def load_extended(file: FileArg, namehint: str = "") -> Surface: ... -def save_extended(surface: Surface, file: FileArg, namehint: str = "") -> None: ... +def load_basic(file: FileLike, /) -> Surface: ... +def load_extended(file: FileLike, namehint: str = "") -> Surface: ... +def save_extended(surface: Surface, file: FileLike, namehint: str = "") -> None: ... diff --git a/buildconfig/stubs/pygame/joystick.pyi b/buildconfig/stubs/pygame/joystick.pyi index c24a7ea88e..2936e5a2b3 100644 --- a/buildconfig/stubs/pygame/joystick.pyi +++ b/buildconfig/stubs/pygame/joystick.pyi @@ -1,4 +1,5 @@ from typing import Tuple, final +from typing_extensions import deprecated # added in 3.13 def init() -> None: ... def quit() -> None: ... @@ -7,9 +8,11 @@ def get_count() -> int: ... @final class JoystickType: def __init__(self, id: int) -> None: ... + @deprecated("since 2.0.0. Multiple initializations are not supported anymore") def init(self) -> None: ... def quit(self) -> None: ... def get_init(self) -> bool: ... + @deprecated("since 2.0.0. Use `pygame.Joystick.get_instance_id` instead") def get_id(self) -> int: ... def get_instance_id(self) -> int: ... def get_guid(self) -> str: ... diff --git a/buildconfig/stubs/pygame/key.pyi b/buildconfig/stubs/pygame/key.pyi index c55f1500d1..09aa380b35 100644 --- a/buildconfig/stubs/pygame/key.pyi +++ b/buildconfig/stubs/pygame/key.pyi @@ -1,6 +1,6 @@ from typing import Tuple -from ._common import RectValue +from pygame.typing import RectLike class ScancodeWrapper(Tuple[bool, ...]): ... @@ -16,4 +16,4 @@ def name(key: int, use_compat: bool = True) -> str: ... def key_code(name: str) -> int: ... def start_text_input() -> None: ... def stop_text_input() -> None: ... -def set_text_input_rect(rect: RectValue, /) -> None: ... +def set_text_input_rect(rect: RectLike, /) -> None: ... diff --git a/buildconfig/stubs/pygame/locals.pyi b/buildconfig/stubs/pygame/locals.pyi index 8aa04e2610..ef822c62d6 100644 --- a/buildconfig/stubs/pygame/locals.pyi +++ b/buildconfig/stubs/pygame/locals.pyi @@ -108,6 +108,9 @@ DROPTEXT: int FINGERDOWN: int FINGERMOTION: int FINGERUP: int +FLASH_BRIEFLY: int +FLASH_CANCEL: int +FLASH_UNTIL_FOCUSED: int FONT_CENTER: int FONT_LEFT: int FONT_RIGHT: int diff --git a/buildconfig/stubs/pygame/mask.pyi b/buildconfig/stubs/pygame/mask.pyi index 43f71110c9..667bc7f7b7 100644 --- a/buildconfig/stubs/pygame/mask.pyi +++ b/buildconfig/stubs/pygame/mask.pyi @@ -3,13 +3,13 @@ from typing import Any, List, Optional, Tuple, Union from pygame.rect import Rect from pygame.surface import Surface -from ._common import ColorValue, Coordinate, RectValue +from pygame.typing import ColorLike, Coordinate, RectLike def from_surface(surface: Surface, threshold: int = 127) -> Mask: ... def from_threshold( surface: Surface, - color: ColorValue, - threshold: ColorValue = (0, 0, 0, 255), + color: ColorLike, + threshold: ColorLike = (0, 0, 0, 255), othersurface: Optional[Surface] = None, palette_colors: int = 1, ) -> Mask: ... @@ -17,7 +17,7 @@ def from_threshold( class Mask: def __init__(self, size: Coordinate, fill: bool = False) -> None: ... def __copy__(self) -> Mask: ... - copy = __copy__ + def copy(self) -> Mask: ... def get_size(self) -> Tuple[int, int]: ... def get_rect(self, **kwargs: Any) -> Rect: ... # Dict type needs to be completed def get_at(self, pos: Coordinate) -> int: ... @@ -43,15 +43,15 @@ class Mask: ) -> Mask: ... def connected_component(self, pos: Coordinate = ...) -> Mask: ... def connected_components(self, minimum: int = 0) -> List[Mask]: ... - def get_bounding_rects(self) -> Rect: ... + def get_bounding_rects(self) -> List[Rect]: ... def to_surface( self, surface: Optional[Surface] = None, setsurface: Optional[Surface] = None, unsetsurface: Optional[Surface] = None, - setcolor: Optional[ColorValue] = (255, 255, 255, 255), - unsetcolor: Optional[ColorValue] = (0, 0, 0, 255), - dest: Union[RectValue, Coordinate] = (0, 0), + setcolor: Optional[ColorLike] = (255, 255, 255, 255), + unsetcolor: Optional[ColorLike] = (0, 0, 0, 255), + dest: Union[RectLike, Coordinate] = (0, 0), ) -> Surface: ... MaskType = Mask diff --git a/buildconfig/stubs/pygame/math.pyi b/buildconfig/stubs/pygame/math.pyi index 963f2bd957..4526960b5a 100644 --- a/buildconfig/stubs/pygame/math.pyi +++ b/buildconfig/stubs/pygame/math.pyi @@ -5,6 +5,7 @@ from typing import ( Iterator, List, Literal, + SupportsIndex, Tuple, Type, TypeVar, @@ -13,13 +14,14 @@ from typing import ( overload, Optional ) +from typing_extensions import deprecated # added in 3.13 if sys.version_info >= (3, 9): from collections.abc import Collection else: from typing import Collection -from ._common import SupportsIndex, Sequence +from pygame.typing import SequenceLike def clamp(value: float, min: float, max: float, /) -> float: ... @@ -35,18 +37,18 @@ class _GenericVector(Collection[float]): @overload def __setitem__(self, key: int, value: float) -> None: ... @overload - def __setitem__(self, key: slice, value: Union[Sequence[float], _TVec]) -> None: ... + def __setitem__(self, key: slice, value: Union[SequenceLike[float], _TVec]) -> None: ... @overload def __getitem__(self, i: SupportsIndex) -> float: ... @overload def __getitem__(self, s: slice) -> List[float]: ... def __iter__(self) -> VectorIterator: ... - def __add__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... - def __radd__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... - def __sub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... - def __rsub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... + def __add__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... + def __radd__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... + def __sub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... + def __rsub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... @overload - def __mul__(self: _TVec, other: Union[Sequence[float], _TVec]) -> float: ... + def __mul__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> float: ... @overload def __mul__(self: _TVec, other: float) -> _TVec: ... def __rmul__(self: _TVec, other: float) -> _TVec: ... @@ -56,17 +58,17 @@ class _GenericVector(Collection[float]): def __neg__(self: _TVec) -> _TVec: ... def __pos__(self: _TVec) -> _TVec: ... def __bool__(self) -> bool: ... - def __iadd__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... - def __isub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... + def __iadd__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... + def __isub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... @overload - def __imul__(self: _TVec, other: Union[Sequence[float], _TVec]) -> float: ... + def __imul__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> float: ... @overload def __imul__(self: _TVec, other: float) -> _TVec: ... def __copy__(self: _TVec) -> _TVec: ... - copy = __copy__ + def copy(self: _TVec) -> _TVec: ... def __safe_for_unpickling__(self) -> Literal[True]: ... def __contains__(self, other: float) -> bool: ... # type: ignore[override] - def dot(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ... + def dot(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ... def magnitude(self) -> float: ... def magnitude_squared(self) -> float: ... def length(self) -> float: ... @@ -75,37 +77,37 @@ class _GenericVector(Collection[float]): def normalize_ip(self) -> None: ... def is_normalized(self) -> bool: ... def scale_to_length(self, value: float, /) -> None: ... - def reflect(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ... - def reflect_ip(self: _TVec, other: Union[Sequence[float], _TVec], /) -> None: ... - def distance_to(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ... + def reflect(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ... + def reflect_ip(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> None: ... + def distance_to(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ... def distance_squared_to( - self: _TVec, other: Union[Sequence[float], _TVec], / + self: _TVec, other: Union[SequenceLike[float], _TVec], / ) -> float: ... def lerp( self: _TVec, - other: Union[Sequence[float], _TVec], + other: Union[SequenceLike[float], _TVec], value: float, / ) -> _TVec: ... def slerp( self: _TVec, - other: Union[Sequence[float], _TVec], + other: Union[SequenceLike[float], _TVec], value: float, / ) -> _TVec: ... def smoothstep( self: _TVec, - other: Union[Sequence[float], _TVec], + other: Union[SequenceLike[float], _TVec], value: float, / ) -> _TVec: ... def elementwise(self: _TVec) -> VectorElementwiseProxy[_TVec]: ... - def angle_to(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ... + def angle_to(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ... def move_towards( self: _TVec, - target: Union[Sequence[float], _TVec], + target: Union[SequenceLike[float], _TVec], max_distance: float, / ) -> _TVec: ... def move_towards_ip( self: _TVec, - target: Union[Sequence[float], _TVec], + target: Union[SequenceLike[float], _TVec], max_distance: float, / ) -> None: ... @overload @@ -118,7 +120,7 @@ class _GenericVector(Collection[float]): def clamp_magnitude_ip(self, max_length: float, /) -> None: ... @overload def clamp_magnitude_ip(self, min_length: float, max_length: float, /) -> None: ... - def project(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ... + def project(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ... def __round__(self: _TVec, ndigits: Optional[int] = None, /) -> _TVec: ... # VectorElementwiseProxy is a generic, it can be an elementwiseproxy object for @@ -222,7 +224,7 @@ class Vector2(_GenericVector): @overload def __init__( self: _TVec, - x: Union[str, float, Sequence[float], _TVec] = 0, + x: Union[str, float, SequenceLike[float], _TVec] = 0, ) -> None: ... @overload def __init__(self, x: float, y: float) -> None: ... @@ -231,14 +233,15 @@ class Vector2(_GenericVector): def rotate_rad(self: _TVec, angle: float, /) -> _TVec: ... def rotate_ip(self, angle: float, /) -> None: ... def rotate_rad_ip(self, angle: float, /) -> None: ... + @deprecated("since 2.1.1. Use `pygame.Vector2.rotate_rad_ip` instead") def rotate_ip_rad(self, angle: float, /) -> None: ... - def cross(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ... + def cross(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ... def as_polar(self) -> Tuple[float, float]: ... - def from_polar(self, polar_value: Sequence[float], /) -> None: ... + def from_polar(self, polar_value: SequenceLike[float], /) -> None: ... @overload def update( self: _TVec, - x: Union[str, float, Sequence[float], _TVec] = 0, + x: Union[str, float, SequenceLike[float], _TVec] = 0, ) -> None: ... @overload def update(self, x: float = 0, y: float = 0) -> None: ... @@ -286,48 +289,52 @@ class Vector3(_GenericVector): @overload def __init__( self: _TVec, - x: Union[str, float, Sequence[float], _TVec] = 0, + x: Union[str, float, SequenceLike[float], _TVec] = 0, ) -> None: ... @overload def __init__(self, x: float, y: float, z: float) -> None: ... def __reduce__(self: _TVec) -> Tuple[Type[_TVec], Tuple[float, float, float]]: ... - def cross(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ... + def cross(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ... def rotate( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> _TVec: ... def rotate_rad( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> _TVec: ... def rotate_ip( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> None: ... def rotate_rad_ip( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> None: ... + @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_rad_ip` instead") def rotate_ip_rad( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> None: ... def rotate_x(self: _TVec, angle: float, /) -> _TVec: ... def rotate_x_rad(self: _TVec, angle: float, /) -> _TVec: ... def rotate_x_ip(self, angle: float, /) -> None: ... def rotate_x_rad_ip(self, angle: float, /) -> None: ... + @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_x_rad_ip` instead") def rotate_x_ip_rad(self, angle: float, /) -> None: ... def rotate_y(self: _TVec, angle: float, /) -> _TVec: ... def rotate_y_rad(self: _TVec, angle: float, /) -> _TVec: ... def rotate_y_ip(self, angle: float, /) -> None: ... def rotate_y_rad_ip(self, angle: float, /) -> None: ... + @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_y_rad_ip` instead") def rotate_y_ip_rad(self, angle: float, /) -> None: ... def rotate_z(self: _TVec, angle: float, /) -> _TVec: ... def rotate_z_rad(self: _TVec, angle: float, /) -> _TVec: ... def rotate_z_ip(self, angle: float, /) -> None: ... def rotate_z_rad_ip(self, angle: float, /) -> None: ... + @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_z_rad_ip` instead") def rotate_z_ip_rad(self, angle: float, /) -> None: ... def as_spherical(self) -> Tuple[float, float, float]: ... def from_spherical(self, spherical: Tuple[float, float, float], /) -> None: ... @overload def update( self: _TVec, - x: Union[str, float, Sequence[float], _TVec] = 0, + x: Union[str, float, SequenceLike[float], _TVec] = 0, ) -> None: ... @overload def update(self, x: int, y: int, z: int) -> None: ... @@ -338,7 +345,7 @@ def invlerp(a: float, b: float, value: float, /) -> float: ... def remap(i_min: float, i_max: float, o_min: float, o_max: float, value: float, /) -> float: ... def smoothstep(a: float, b: float, weight: float, /) -> float: ... - -# typehints for deprecated functions, to be removed in a future version +@deprecated("Functionality is removed") def enable_swizzling() -> None: ... +@deprecated("Functionality is removed") def disable_swizzling() -> None: ... diff --git a/buildconfig/stubs/pygame/midi.pyi b/buildconfig/stubs/pygame/midi.pyi index 028342508c..22fc749f80 100644 --- a/buildconfig/stubs/pygame/midi.pyi +++ b/buildconfig/stubs/pygame/midi.pyi @@ -1,7 +1,7 @@ from typing import List, Tuple, Union from pygame.event import Event -from ._common import Sequence +from pygame.typing import SequenceLike MIDIIN: int MIDIOUT: int @@ -17,7 +17,7 @@ def get_default_input_id() -> int: ... def get_default_output_id() -> int: ... def get_device_info(an_id: int) -> Tuple[str, str, int, int, int]: ... def midis2events( - midis: Sequence[Sequence[Union[Sequence[int], int]]], device_id: int + midis: SequenceLike[SequenceLike[Union[SequenceLike[int], int]]], device_id: int ) -> List[Event]: ... def time() -> int: ... def frequency_to_midi(frequency: float) -> int: ... diff --git a/buildconfig/stubs/pygame/mixer.pyi b/buildconfig/stubs/pygame/mixer.pyi index 4d1cafc830..fc04a0ef02 100644 --- a/buildconfig/stubs/pygame/mixer.pyi +++ b/buildconfig/stubs/pygame/mixer.pyi @@ -5,7 +5,7 @@ import numpy from pygame.event import Event from . import mixer_music -from ._common import FileArg +from pygame.typing import FileLike # export mixer_music as mixer.music music = mixer_music @@ -44,7 +44,7 @@ def get_sdl_mixer_version(linked: bool = True) -> Tuple[int, int, int]: ... class Sound: @overload - def __init__(self, file: FileArg) -> None: ... + def __init__(self, file: FileLike) -> None: ... @overload def __init__( self, buffer: Any @@ -70,6 +70,8 @@ class Sound: def get_num_channels(self) -> int: ... def get_length(self) -> float: ... def get_raw(self) -> bytes: ... + def copy(self) -> Sound: ... + def __copy__(self) -> Sound: ... class Channel: diff --git a/buildconfig/stubs/pygame/mixer_music.pyi b/buildconfig/stubs/pygame/mixer_music.pyi index 08b2e8a11a..40cf46e2a9 100644 --- a/buildconfig/stubs/pygame/mixer_music.pyi +++ b/buildconfig/stubs/pygame/mixer_music.pyi @@ -1,8 +1,8 @@ from typing import Optional, Dict -from ._common import FileArg +from pygame.typing import FileLike -def load(filename: FileArg, namehint: Optional[str] = "") -> None: ... +def load(filename: FileLike, namehint: Optional[str] = "") -> None: ... def unload() -> None: ... def play(loops: int = 0, start: float = 0.0, fade_ms: int = 0) -> None: ... def rewind() -> None: ... @@ -15,7 +15,7 @@ def get_volume() -> float: ... def get_busy() -> bool: ... def set_pos(pos: float, /) -> None: ... def get_pos() -> int: ... -def queue(filename: FileArg, namehint: str = "", loops: int = 0) -> None: ... +def queue(filename: FileLike, namehint: str = "", loops: int = 0) -> None: ... def set_endevent(event_type: int, /) -> None: ... def get_endevent() -> int: ... -def get_metadata(filename: Optional[FileArg] = None, namehint: str = "") -> Dict[str, str]: ... +def get_metadata(filename: Optional[FileLike] = None, namehint: str = "") -> Dict[str, str]: ... diff --git a/buildconfig/stubs/pygame/mouse.pyi b/buildconfig/stubs/pygame/mouse.pyi index bec7b2899c..2d01fe94a2 100644 --- a/buildconfig/stubs/pygame/mouse.pyi +++ b/buildconfig/stubs/pygame/mouse.pyi @@ -1,17 +1,22 @@ -from typing import Tuple, overload -from typing_extensions import Literal +from typing import Literal, Tuple, overload +from typing_extensions import deprecated # added in 3.13 + from pygame.cursors import Cursor from pygame.surface import Surface -from ._common import Coordinate, Sequence, IntCoordinate +from pygame.typing import Coordinate, SequenceLike, IntCoordinate @overload -def get_pressed(num_buttons: Literal[3] = 3) -> Tuple[bool, bool, bool]: ... +def get_pressed( + num_buttons: Literal[3] = 3, + desktop: bool = False) -> Tuple[bool, bool, bool]: ... @overload -def get_pressed(num_buttons: Literal[5]) -> Tuple[bool, bool, bool, bool, bool]: ... +def get_pressed( + num_buttons: Literal[5], + desktop: bool = False) -> Tuple[bool, bool, bool, bool, bool]: ... def get_just_pressed() -> Tuple[bool, bool, bool, bool, bool]: ... def get_just_released() -> Tuple[bool, bool, bool, bool, bool]: ... -def get_pos() -> Tuple[int, int]: ... +def get_pos(desktop: bool = False) -> Tuple[int, int]: ... def get_rel() -> Tuple[int, int]: ... @overload def set_pos(pos: Coordinate, /) -> None: ... @@ -28,12 +33,13 @@ def set_cursor(constant: int) -> None: ... def set_cursor( size: IntCoordinate, hotspot: IntCoordinate, - xormasks: Sequence[int], - andmasks: Sequence[int], + xormasks: SequenceLike[int], + andmasks: SequenceLike[int], ) -> None: ... @overload def set_cursor(hotspot: IntCoordinate, surface: Surface) -> None: ... def get_cursor() -> Cursor: ... +@deprecated("since 2.2.0. Use `pygame.mouse.set_cursor` instead") def set_system_cursor(cursor: int, /) -> None: ... def get_relative_mode() -> bool: ... def set_relative_mode(enable: bool, /) -> None: ... diff --git a/buildconfig/stubs/pygame/pixelarray.pyi b/buildconfig/stubs/pygame/pixelarray.pyi index 935d4c01d6..a10bb66e42 100644 --- a/buildconfig/stubs/pygame/pixelarray.pyi +++ b/buildconfig/stubs/pygame/pixelarray.pyi @@ -2,7 +2,7 @@ from typing import Any, Dict, Tuple, Union, overload from pygame.surface import Surface -from ._common import ColorValue, Sequence +from pygame.typing import ColorLike, SequenceLike class PixelArray: surface: Surface @@ -34,22 +34,22 @@ class PixelArray: def make_surface(self) -> Surface: ... def replace( self, - color: ColorValue, - repcolor: ColorValue, + color: ColorLike, + repcolor: ColorLike, distance: float = 0, - weights: Sequence[float] = (0.299, 0.587, 0.114), + weights: SequenceLike[float] = (0.299, 0.587, 0.114), ) -> None: ... def extract( self, - color: ColorValue, + color: ColorLike, distance: float = 0, - weights: Sequence[float] = (0.299, 0.587, 0.114), + weights: SequenceLike[float] = (0.299, 0.587, 0.114), ) -> PixelArray: ... def compare( self, array: PixelArray, distance: float = 0, - weights: Sequence[float] = (0.299, 0.587, 0.114), + weights: SequenceLike[float] = (0.299, 0.587, 0.114), ) -> PixelArray: ... def transpose(self) -> PixelArray: ... def close(self) -> PixelArray: ... diff --git a/buildconfig/stubs/pygame/pixelcopy.pyi b/buildconfig/stubs/pygame/pixelcopy.pyi index 9954816074..4c2f59ce7e 100644 --- a/buildconfig/stubs/pygame/pixelcopy.pyi +++ b/buildconfig/stubs/pygame/pixelcopy.pyi @@ -1,9 +1,9 @@ +from typing import Literal + import numpy from pygame.surface import Surface -from ._common import Literal - _kind = Literal["P", "p", "R", "r", "G", "g", "B", "b", "A", "a", "C", "c"] def surface_to_array( diff --git a/buildconfig/stubs/pygame/rect.pyi b/buildconfig/stubs/pygame/rect.pyi index b401f9a930..6e55ce45d7 100644 --- a/buildconfig/stubs/pygame/rect.pyi +++ b/buildconfig/stubs/pygame/rect.pyi @@ -2,6 +2,8 @@ import sys from typing import ( Dict, List, + Literal, + SupportsIndex, Tuple, TypeVar, Union, @@ -10,7 +12,7 @@ from typing import ( Optional, ) -from ._common import Coordinate, Literal, RectValue, SupportsIndex, Sequence +from pygame.typing import Coordinate, RectLike, SequenceLike if sys.version_info >= (3, 11): from typing import Self @@ -27,7 +29,7 @@ _K = TypeVar("_K") _V = TypeVar("_V") _T = TypeVar("_T") -_RectTypeCompatible_co = TypeVar("_RectTypeCompatible_co", bound=RectValue, covariant=True) +_RectTypeCompatible_co = TypeVar("_RectTypeCompatible_co", bound=RectLike, covariant=True) class _GenericRect(Collection[_N]): @property @@ -127,7 +129,7 @@ class _GenericRect(Collection[_N]): @overload def __init__(self, left_top: Coordinate, width_height: Coordinate) -> None: ... @overload - def __init__(self, single_arg: RectValue) -> None: ... + def __init__(self, single_arg: RectLike) -> None: ... @overload def __init__(self) -> None: ... def __len__(self) -> Literal[4]: ... @@ -139,9 +141,9 @@ class _GenericRect(Collection[_N]): @overload def __setitem__(self, key: int, value: float) -> None: ... @overload - def __setitem__(self, key: slice, value: Union[float, RectValue]) -> None: ... + def __setitem__(self, key: slice, value: Union[float, RectLike]) -> None: ... def __copy__(self) -> Self: ... - copy = __copy__ + def copy(self) -> Self: ... @overload def move(self, x: float, y: float, /) -> Self: ... @overload @@ -172,15 +174,15 @@ class _GenericRect(Collection[_N]): @overload def update(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ... @overload - def update(self, single_arg: RectValue, /) -> None: ... + def update(self, single_arg: RectLike, /) -> None: ... @overload - def clamp(self, rect: RectValue, /) -> Self: ... + def clamp(self, rect: RectLike, /) -> Self: ... @overload def clamp(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ... @overload def clamp(self, left: float, top: float, width: float, height: float, /) -> Self: ... @overload - def clamp_ip(self, rect: RectValue, /) -> None: ... + def clamp_ip(self, rect: RectLike, /) -> None: ... @overload def clamp_ip(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ... @overload @@ -188,7 +190,7 @@ class _GenericRect(Collection[_N]): self, left: float, top: float, width: float, height: float, / ) -> None: ... @overload - def clip(self, rect: RectValue, /) -> Self: ... + def clip(self, rect: RectLike, /) -> Self: ... @overload def clip(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ... @overload @@ -203,34 +205,34 @@ class _GenericRect(Collection[_N]): ) -> Union[Tuple[Tuple[_N, _N], Tuple[_N, _N]], Tuple[()]]: ... @overload def clipline( - self, rect_arg: RectValue, / + self, rect_arg: RectLike, / ) -> Union[Tuple[Tuple[_N, _N], Tuple[_N, _N]], Tuple[()]]: ... @overload - def union(self, rect: RectValue, /) -> Self: ... + def union(self, rect: RectLike, /) -> Self: ... @overload def union(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ... @overload def union(self, left: float, top: float, width: float, height: float, /) -> Self: ... @overload - def union_ip(self, rect: RectValue, /) -> None: ... + def union_ip(self, rect: RectLike, /) -> None: ... @overload def union_ip(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ... @overload def union_ip( self, left: float, top: float, width: float, height: float, / ) -> None: ... - def unionall(self, rect: Sequence[_RectTypeCompatible_co], /) -> Self: ... - def unionall_ip(self, rect_sequence: Sequence[_RectTypeCompatible_co], /) -> None: ... + def unionall(self, rect: SequenceLike[_RectTypeCompatible_co], /) -> Self: ... + def unionall_ip(self, rect_SequenceLike: SequenceLike[_RectTypeCompatible_co], /) -> None: ... @overload - def fit(self, rect: RectValue, /) -> Self: ... + def fit(self, rect: RectLike, /) -> Self: ... @overload def fit(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ... @overload def fit(self, left: float, top: float, width: float, height: float, /) -> Self: ... def normalize(self) -> None: ... - def __contains__(self, rect: Union[RectValue, _N], /) -> bool: ... # type: ignore[override] + def __contains__(self, rect: Union[RectLike, _N], /) -> bool: ... # type: ignore[override] @overload - def contains(self, rect: RectValue, /) -> bool: ... + def contains(self, rect: RectLike, /) -> bool: ... @overload def contains(self, left_top: Coordinate, width_height: Coordinate, /) -> bool: ... @overload @@ -242,20 +244,20 @@ class _GenericRect(Collection[_N]): @overload def collidepoint(self, x_y: Coordinate, /) -> bool: ... @overload - def colliderect(self, rect: RectValue, /) -> bool: ... + def colliderect(self, rect: RectLike, /) -> bool: ... @overload def colliderect(self, left_top: Coordinate, width_height: Coordinate, /) -> bool: ... @overload def colliderect( self, left: float, top: float, width: float, height: float, / ) -> bool: ... - def collidelist(self, rect_list: Sequence[_RectTypeCompatible_co], /) -> int: ... - def collidelistall(self, rect_list: Sequence[_RectTypeCompatible_co], /) -> List[int]: ... + def collidelist(self, rect_list: SequenceLike[_RectTypeCompatible_co], /) -> int: ... + def collidelistall(self, rect_list: SequenceLike[_RectTypeCompatible_co], /) -> List[int]: ... def collideobjectsall( - self, objects: Sequence[_T], key: Optional[Callable[[_T], RectValue]] = None + self, objects: SequenceLike[_T], key: Optional[Callable[[_T], RectLike]] = None ) -> List[_T]: ... def collideobjects( - self, objects: Sequence[_T], key: Optional[Callable[[_T], RectValue]] = None + self, objects: SequenceLike[_T], key: Optional[Callable[[_T], RectLike]] = None ) -> Optional[_T]: ... @overload def collidedict( @@ -278,7 +280,7 @@ class _GenericRect(Collection[_N]): # Sized, Iterable and Container ABCs class Rect(_GenericRect[int]): ... - + class FRect(_GenericRect[float]): ... diff --git a/buildconfig/stubs/pygame/rwobject.pyi b/buildconfig/stubs/pygame/rwobject.pyi index 3688279b6c..d14c62a804 100644 --- a/buildconfig/stubs/pygame/rwobject.pyi +++ b/buildconfig/stubs/pygame/rwobject.pyi @@ -1,16 +1,16 @@ from typing import Any, Optional, overload, Type -from ._common import AnyPath +from pygame.typing import _PathLike def encode_string( - obj: Optional[AnyPath], + obj: Optional[_PathLike], encoding: Optional[str] = "unicode_escape", errors: Optional[str] = "backslashreplace", etype: Optional[Type[Exception]] = UnicodeEncodeError, ) -> bytes: ... @overload def encode_file_path( - obj: Optional[AnyPath], etype: Optional[Type[Exception]] = UnicodeEncodeError + obj: Optional[_PathLike], etype: Optional[Type[Exception]] = UnicodeEncodeError ) -> bytes: ... @overload def encode_file_path( diff --git a/buildconfig/stubs/pygame/scrap.pyi b/buildconfig/stubs/pygame/scrap.pyi index 5929539a86..edb98e8801 100644 --- a/buildconfig/stubs/pygame/scrap.pyi +++ b/buildconfig/stubs/pygame/scrap.pyi @@ -1,13 +1,22 @@ from typing import List, Optional +from typing_extensions import deprecated # added in 3.13 from collections.abc import ByteString +@deprecated("since 2.2.0. Use the new API instead, which only requires display init") def init() -> None: ... +@deprecated("since 2.2.0. Use the new API instead, which doesn't require scrap init") def get_init() -> bool: ... +@deprecated("since 2.2.0. Use the new API instead: `pygame.scrap.get_text`") def get(data_type: str, /) -> Optional[bytes]: ... +@deprecated("since 2.2.0. Use the new API instead, which only supports strings") def get_types() -> List[str]: ... +@deprecated("since 2.2.0. Use the new API instead: `pygame.scrap.put_text`") def put(data_type: str, data: ByteString, /) -> None: ... +@deprecated("since 2.2.0. Use the new API instead: `pygame.scrap.has_text`") def contains(data_type: str, /) -> bool: ... +@deprecated("since 2.2.0. Use the new API instead, which uses system clipboard") def lost() -> bool: ... +@deprecated("since 2.2.0. Use the new API instead, which only supports strings") def set_mode(mode: int, /) -> None: ... def put_text(text: str, /) -> None: ... def get_text() -> str: ... diff --git a/buildconfig/stubs/pygame/sndarray.pyi b/buildconfig/stubs/pygame/sndarray.pyi index 8b0dd65303..bf03698feb 100644 --- a/buildconfig/stubs/pygame/sndarray.pyi +++ b/buildconfig/stubs/pygame/sndarray.pyi @@ -1,4 +1,5 @@ from typing import Tuple +from typing_extensions import deprecated # added in 3.13 import numpy @@ -7,6 +8,9 @@ from pygame.mixer import Sound def array(sound: Sound) -> numpy.ndarray: ... def samples(sound: Sound) -> numpy.ndarray: ... def make_sound(array: numpy.ndarray) -> Sound: ... +@deprecated("Only numpy is supported") def use_arraytype(arraytype: str) -> Sound: ... +@deprecated("Only numpy is supported") def get_arraytype() -> str: ... +@deprecated("Only numpy is supported") def get_arraytypes() -> Tuple[str]: ... diff --git a/buildconfig/stubs/pygame/sprite.pyi b/buildconfig/stubs/pygame/sprite.pyi index fc9c022ac6..8c129ff936 100644 --- a/buildconfig/stubs/pygame/sprite.pyi +++ b/buildconfig/stubs/pygame/sprite.pyi @@ -7,20 +7,19 @@ from typing import ( Iterator, List, Optional, + Protocol, SupportsFloat, Tuple, TypeVar, Union, ) - -# Protocol added in python 3.8 -from typing_extensions import Protocol +from typing_extensions import deprecated # added in 3.13 from pygame.rect import FRect, Rect from pygame.surface import Surface from pygame.mask import Mask -from ._common import RectValue, Coordinate +from pygame.typing import RectLike, Coordinate # non-generic Group, used in Sprite _Group = AbstractGroup[_SpriteSupportsGroup] @@ -184,10 +183,13 @@ class Group(AbstractGroup[_TSprite]): ) -> None: ... # these are aliased in the code too -RenderPlain = Group -RenderClear = Group +@deprecated("Use `pygame.sprite.Group` instead") +class RenderPlain(Group): ... +@deprecated("Use `pygame.sprite.Group` instead") +class RenderClear(Group): ... class RenderUpdates(Group[_TSprite]): ... +@deprecated("Use `pygame.sprite.RenderUpdates` instead") class OrderedUpdates(RenderUpdates[_TSprite]): ... class LayeredUpdates(AbstractGroup[_TSprite]): @@ -230,14 +232,16 @@ class LayeredDirty(LayeredUpdates[_TDirtySprite]): ) -> List[Union[FRect, Rect]]: ... # clear breaks Liskov substitution principle in code def clear(self, surface: Surface, bgd: Surface) -> None: ... # type: ignore[override] - def repaint_rect(self, screen_rect: RectValue) -> None: ... - def set_clip(self, screen_rect: Optional[RectValue] = None) -> None: ... + def repaint_rect(self, screen_rect: RectLike) -> None: ... + def set_clip(self, screen_rect: Optional[RectLike] = None) -> None: ... def get_clip(self) -> Union[FRect, Rect]: ... def set_timing_threshold( self, time_ms: SupportsFloat ) -> None: ... # This actually accept any value - # deprecated alias - set_timing_treshold = set_timing_threshold + @deprecated("since 2.1.1. Use `pygame.sprite.LayeredDirty.set_timing_threshold` instead") + def set_timing_treshold( + self, time_ms: SupportsFloat + ) -> None: ... class GroupSingle(AbstractGroup[_TSprite]): sprite: _TSprite diff --git a/buildconfig/stubs/pygame/surface.pyi b/buildconfig/stubs/pygame/surface.pyi index b2c8f6f5fd..697944313c 100644 --- a/buildconfig/stubs/pygame/surface.pyi +++ b/buildconfig/stubs/pygame/surface.pyi @@ -1,16 +1,15 @@ -from typing import Any, Iterable, List, Optional, Tuple, Union, overload +from typing import Any, Iterable, List, Literal, Optional, Tuple, Union, overload +from typing_extensions import deprecated # added in 3.13 from pygame.bufferproxy import BufferProxy from pygame.color import Color from pygame.rect import FRect, Rect -from ._common import ( - ColorValue, +from pygame.typing import ( + ColorLike, Coordinate, - Literal, - RectValue, - RGBAOutput, - Sequence, + RectLike, + SequenceLike, ) _ViewKind = Literal[ @@ -54,7 +53,7 @@ class Surface: size: Coordinate, flags: int = 0, depth: int = 0, - masks: Optional[ColorValue] = None, + masks: Optional[ColorLike] = None, ) -> None: ... @overload def __init__( @@ -65,28 +64,28 @@ class Surface: ) -> None: ... def __copy__(self) -> Surface: ... def __deepcopy__(self, memo) -> Surface: ... - copy = __copy__ + def copy(self) -> Surface: ... def blit( self, source: Surface, - dest: Union[Coordinate, RectValue], - area: Optional[RectValue] = None, + dest: Union[Coordinate, RectLike] = (0, 0), + area: Optional[RectLike] = None, special_flags: int = 0, ) -> Rect: ... def blits( self, - blit_sequence: Iterable[ + blit_SequenceLike: Iterable[ Union[ - Tuple[Surface, Union[Coordinate, RectValue]], - Tuple[Surface, Union[Coordinate, RectValue], Union[RectValue, int]], - Tuple[Surface, Union[Coordinate, RectValue], RectValue, int], + Tuple[Surface, Union[Coordinate, RectLike]], + Tuple[Surface, Union[Coordinate, RectLike], Union[RectLike, int]], + Tuple[Surface, Union[Coordinate, RectLike], RectLike, int], ] ], doreturn: Union[int, bool] = 1, ) -> Union[List[Rect], None]: ... def fblits( self, - blit_sequence: Iterable[Tuple[Surface, Union[Coordinate, RectValue]]], + blit_SequenceLike: Iterable[Tuple[Surface, Union[Coordinate, RectLike]]], special_flags: int = 0, / ) -> None: ... @overload @@ -94,22 +93,22 @@ class Surface: @overload def convert(self, depth: int, flags: int = 0, /) -> Surface: ... @overload - def convert(self, masks: ColorValue, flags: int = 0, /) -> Surface: ... + def convert(self, masks: ColorLike, flags: int = 0, /) -> Surface: ... @overload def convert(self) -> Surface: ... def convert_alpha(self) -> Surface: ... def fill( self, - color: ColorValue, - rect: Optional[RectValue] = None, + color: ColorLike, + rect: Optional[RectLike] = None, special_flags: int = 0, ) -> Rect: ... def scroll(self, dx: int = 0, dy: int = 0, /) -> None: ... @overload - def set_colorkey(self, color: ColorValue, flags: int = 0, /) -> None: ... + def set_colorkey(self, color: ColorLike, flags: int = 0, /) -> None: ... @overload def set_colorkey(self, color: None, /) -> None: ... - def get_colorkey(self) -> Optional[RGBAOutput]: ... + def get_colorkey(self) -> Optional[Tuple[int, int, int, int]]: ... @overload def set_alpha(self, value: int, flags: int = 0, /) -> None: ... @overload @@ -121,18 +120,18 @@ class Surface: def get_locked(self) -> bool: ... def get_locks(self) -> Tuple[Any, ...]: ... def get_at(self, x_y: Coordinate, /) -> Color: ... - def set_at(self, x_y: Coordinate, color: ColorValue, /) -> None: ... + def set_at(self, x_y: Coordinate, color: ColorLike, /) -> None: ... def get_at_mapped(self, x_y: Coordinate, /) -> int: ... def get_palette(self) -> List[Color]: ... def get_palette_at(self, index: int, /) -> Color: ... - def set_palette(self, palette: Sequence[ColorValue], /) -> None: ... - def set_palette_at(self, index: int, color: ColorValue, /) -> None: ... - def map_rgb(self, color: ColorValue, /) -> int: ... + def set_palette(self, palette: SequenceLike[ColorLike], /) -> None: ... + def set_palette_at(self, index: int, color: ColorLike, /) -> None: ... + def map_rgb(self, color: ColorLike, /) -> int: ... def unmap_rgb(self, mapped_int: int, /) -> Color: ... - def set_clip(self, rect: Optional[RectValue], /) -> None: ... + def set_clip(self, rect: Optional[RectLike], /) -> None: ... def get_clip(self) -> Rect: ... @overload - def subsurface(self, rect: RectValue, /) -> Surface: ... + def subsurface(self, rect: RectLike, /) -> Surface: ... @overload def subsurface(self, left_top: Coordinate, width_height: Coordinate, /) -> Surface: ... @overload @@ -152,15 +151,18 @@ class Surface: def get_bytesize(self) -> int: ... def get_flags(self) -> int: ... def get_pitch(self) -> int: ... - def get_masks(self) -> RGBAOutput: ... - def set_masks(self, color: ColorValue, /) -> None: ... - def get_shifts(self) -> RGBAOutput: ... - def set_shifts(self, color: ColorValue, /) -> None: ... - def get_losses(self) -> RGBAOutput: ... + def get_masks(self) -> Tuple[int, int, int, int]: ... + @deprecated("since 2.0.0. Immutable in SDL2") + def set_masks(self, color: ColorLike, /) -> None: ... + def get_shifts(self) -> Tuple[int, int, int, int]: ... + @deprecated("since 2.0.0. Immutable in SDL2") + def set_shifts(self, color: ColorLike, /) -> None: ... + def get_losses(self) -> Tuple[int, int, int, int]: ... def get_bounding_rect(self, min_alpha: int = 1) -> Rect: ... def get_view(self, kind: _ViewKind = "2", /) -> BufferProxy: ... def get_buffer(self) -> BufferProxy: ... def get_blendmode(self) -> int: ... def premul_alpha(self) -> Surface: ... + def premul_alpha_ip(self) -> Surface: ... SurfaceType = Surface diff --git a/buildconfig/stubs/pygame/surfarray.pyi b/buildconfig/stubs/pygame/surfarray.pyi index bf15758713..c15f089518 100644 --- a/buildconfig/stubs/pygame/surfarray.pyi +++ b/buildconfig/stubs/pygame/surfarray.pyi @@ -1,4 +1,5 @@ from typing import Tuple +from typing_extensions import deprecated # added in 3.13 import numpy @@ -26,6 +27,9 @@ def array_colorkey(surface: Surface) -> numpy.ndarray: ... def make_surface(array: numpy.ndarray) -> Surface: ... def blit_array(surface: Surface, array: numpy.ndarray) -> None: ... def map_array(surface: Surface, array: numpy.ndarray) -> numpy.ndarray: ... +@deprecated("Only numpy is supported") def use_arraytype(arraytype: str) -> None: ... +@deprecated("Only numpy is supported") def get_arraytype() -> str: ... +@deprecated("Only numpy is supported") def get_arraytypes() -> Tuple[str]: ... diff --git a/buildconfig/stubs/pygame/system.pyi b/buildconfig/stubs/pygame/system.pyi index f9f50452ca..f2e367473f 100644 --- a/buildconfig/stubs/pygame/system.pyi +++ b/buildconfig/stubs/pygame/system.pyi @@ -1,6 +1,4 @@ -from typing import List, Optional, final - -from typing_extensions import TypedDict +from typing import List, Optional, TypedDict from pygame._data_classes import PowerState diff --git a/buildconfig/stubs/pygame/transform.pyi b/buildconfig/stubs/pygame/transform.pyi index fd412ec948..fa0709e394 100644 --- a/buildconfig/stubs/pygame/transform.pyi +++ b/buildconfig/stubs/pygame/transform.pyi @@ -1,9 +1,8 @@ -from typing import Optional, Union, Literal +from typing import Optional, Union, Literal, Tuple -from pygame.color import Color from pygame.surface import Surface -from ._common import ColorValue, Coordinate, RectValue, Sequence +from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike def flip(surface: Surface, flip_x: bool, flip_y: bool) -> Surface: ... def scale( @@ -13,7 +12,7 @@ def scale( ) -> Surface: ... def scale_by( surface: Surface, - factor: Union[float, Sequence[float]], + factor: Union[float, SequenceLike[float]], dest_surface: Optional[Surface] = None, ) -> Surface: ... def rotate(surface: Surface, angle: float) -> Surface: ... @@ -27,28 +26,28 @@ def smoothscale( ) -> Surface: ... def smoothscale_by( surface: Surface, - factor: Union[float, Sequence[float]], + factor: Union[float, SequenceLike[float]], dest_surface: Optional[Surface] = None, ) -> Surface: ... def get_smoothscale_backend() -> Literal["GENERIC", "SSE2", "NEON"]: ... def set_smoothscale_backend(backend: Literal["GENERIC", "SSE2", "NEON"]) -> None: ... -def chop(surface: Surface, rect: RectValue) -> Surface: ... +def chop(surface: Surface, rect: RectLike) -> Surface: ... def laplacian(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ... def invert(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ... def average_surfaces( - surfaces: Sequence[Surface], + surfaces: SequenceLike[Surface], dest_surface: Optional[Surface] = None, palette_colors: Union[bool, int] = 1, ) -> Surface: ... def average_color( - surface: Surface, rect: Optional[RectValue] = None, consider_alpha: bool = False -) -> Color: ... + surface: Surface, rect: Optional[RectLike] = None, consider_alpha: bool = False +) -> Tuple[int, int, int, int]: ... def threshold( dest_surface: Optional[Surface], surface: Surface, - search_color: Optional[ColorValue], - threshold: ColorValue = (0, 0, 0, 0), - set_color: Optional[ColorValue] = (0, 0, 0, 0), + search_color: Optional[ColorLike], + threshold: ColorLike = (0, 0, 0, 0), + set_color: Optional[ColorLike] = (0, 0, 0, 0), set_behavior: int = 1, search_surf: Optional[Surface] = None, inverse_set: bool = False, diff --git a/buildconfig/stubs/pygame/typing.pyi b/buildconfig/stubs/pygame/typing.pyi new file mode 100644 index 0000000000..852c5192fb --- /dev/null +++ b/buildconfig/stubs/pygame/typing.pyi @@ -0,0 +1,70 @@ +"""Set of common pygame type aliases for proper typehint annotations""" + +# NOTE: `src_py/typing.py` and `buildconfig/stubs/pygame/typing.pyi` must be duplicates. +# Use the command `python buildconfig/stubs/gen_stubs.py` to copy typing.py to typing.pyi + +__all__ = [ + "RectLike", + "SequenceLike", + "FileLike", + "ColorLike", + "Coordinate", + "IntCoordinate", +] + +import sys +from abc import abstractmethod +from typing import IO, Callable, Tuple, Union, TypeVar, Protocol + +if sys.version_info >= (3, 9): + from os import PathLike as _PathProtocol +else: + _AnyStr_co = TypeVar("_AnyStr_co", str, bytes, covariant=True) + + class _PathProtocol(Protocol[_AnyStr_co]): + @abstractmethod + def __fspath__(self) -> _AnyStr_co: ... + + +# For functions that take a file name +_PathLike = Union[str, bytes, _PathProtocol[str], _PathProtocol[bytes]] +# Most pygame functions that take a file argument should be able to handle a FileLike type +FileLike = Union[_PathLike, IO[bytes], IO[str]] + +_T_co = TypeVar("_T_co", covariant=True) + + +class SequenceLike(Protocol[_T_co]): + """ + Variant of the standard `Sequence` ABC that only requires `__getitem__` and `__len__`. + """ + + @abstractmethod + def __getitem__(self, index: int, /) -> _T_co: ... + @abstractmethod + def __len__(self) -> int: ... + + +# Modify typehints when it is possible to annotate sizes + +# Pygame handles float without errors in most cases where a coordinate is expected, +# usually rounding to int. Also, 'Union[int, float] == float' +Coordinate = SequenceLike[float] +# This is used where ints are strictly required +IntCoordinate = SequenceLike[int] + +ColorLike = Union[int, str, SequenceLike[int]] + + +class _HasRectAttribute(Protocol): + # An object that has a rect attribute that is either a rect, or a function + # that returns a rect conforms to the rect protocol + @property + def rect(self) -> Union["RectLike", Callable[[], "RectLike"]]: ... + + +RectLike = Union[SequenceLike[float], SequenceLike[Coordinate], _HasRectAttribute] + + +# cleanup namespace +del sys, abstractmethod, IO, Callable, Tuple, Union, TypeVar, Protocol diff --git a/buildconfig/stubs/pygame/version.pyi b/buildconfig/stubs/pygame/version.pyi index 69a3b6fe71..3838d68274 100644 --- a/buildconfig/stubs/pygame/version.pyi +++ b/buildconfig/stubs/pygame/version.pyi @@ -1,6 +1,4 @@ -from typing import Tuple - -from ._common import Literal +from typing import Literal, Tuple class SoftwareVersion(Tuple[int, int, int]): def __new__(cls, major: int, minor: int, patch: int) -> SoftwareVersion: ... diff --git a/buildconfig/stubs/pygame/window.pyi b/buildconfig/stubs/pygame/window.pyi index 9b09e76d2d..a2a759c500 100644 --- a/buildconfig/stubs/pygame/window.pyi +++ b/buildconfig/stubs/pygame/window.pyi @@ -1,12 +1,13 @@ -from typing import Optional, Tuple, Union, final +from typing import Optional, Tuple, Union +from typing_extensions import deprecated # added in 3.13 -from pygame._common import Coordinate, RectValue +from pygame.typing import Coordinate, RectLike from pygame.locals import WINDOWPOS_UNDEFINED from pygame.rect import Rect from pygame.surface import Surface def get_grabbed_window() -> Optional[Window]: ... -@final + class Window: def __init__( self, @@ -28,6 +29,7 @@ class Window: def set_icon(self, icon: Surface, /) -> None: ... def get_surface(self) -> Surface: ... def flip(self) -> None: ... + def flash(self, operation: int, /) -> None: ... grab_mouse: bool grab_keyboard: bool @@ -43,11 +45,13 @@ class Window: @property def keyboard_grabbed(self) -> bool: ... @property + def focused(self) -> bool: ... + @property def id(self) -> int: ... @property def mouse_rect(self) -> Optional[Rect]: ... @mouse_rect.setter - def mouse_rect(self, value: Optional[RectValue]) -> None: ... + def mouse_rect(self, value: Optional[RectLike]) -> None: ... @property def size(self) -> Tuple[int, int]: ... @size.setter @@ -67,4 +71,5 @@ class Window: @property def opengl(self) -> bool: ... @classmethod + @deprecated("since 2.4.0. Use either the display module or the Window class with get_surface and flip. Try not to mix display and Window") def from_display_module(cls) -> Window: ... diff --git a/buildconfig/stubs/stubcheck.py b/buildconfig/stubs/stubcheck.py index 14477a31d5..d0c544a4c8 100644 --- a/buildconfig/stubs/stubcheck.py +++ b/buildconfig/stubs/stubcheck.py @@ -11,9 +11,34 @@ STUBS_BASE_DIR = Path(__file__).parent -def main(): +def typing_check(): """ - Main entrypoint + Ensure type aliases in typing.py work as expected with type checkers + """ + mypy_version_args = [sys.executable, "-m", "mypy"] + try: + version = subprocess.run( + [*mypy_version_args, "--version"], capture_output=True, check=True, text=True + ).stdout.strip() + except subprocess.CalledProcessError: + print("ERROR: could not validate typing.py, make sure you have mypy installed") + return + + mypy_args = [*mypy_version_args, "typing_sample_app.py"] + cmd = " ".join(mypy_args) + print(f"Using mypy invocation: `{cmd}` (version: {version})") + prev_dir = os.getcwd() + try: + os.chdir(STUBS_BASE_DIR) + returncode = subprocess.run([*mypy_args]).returncode + if returncode != 0: + raise RuntimeError(f"mypy process finished with unsuccessful return code {returncode}") + finally: + os.chdir(prev_dir) + +def stubs_check(): + """ + Validate the stubs files """ for stubtest in ([sys.executable, "-m", "mypy.stubtest"], ["stubtest"]): try: @@ -41,5 +66,13 @@ def main(): sys.exit(1) +def main(): + """ + Main entrypoint + """ + typing_check() + stubs_check() + + if __name__ == "__main__": main() diff --git a/buildconfig/stubs/typing_sample_app.py b/buildconfig/stubs/typing_sample_app.py new file mode 100644 index 0000000000..454f7702af --- /dev/null +++ b/buildconfig/stubs/typing_sample_app.py @@ -0,0 +1,113 @@ +"""Sample app run by mypy to ensure typing.py aliases work as expected""" + +from pygame import typing +import pygame +import pathlib + + +# validate SequenceLike +class MySequence: + def __getitem__(self, index: int) -> str: + if index > 20: + raise IndexError() + if index % 2 == 0: + return "a" + return "bc" + + def __len__(self) -> int: + return 20 + +def validator_SequenceLike(sequence: typing.SequenceLike) -> int: + return 0 + +def validator_SequenceLikeTypes( + sequence_float: typing.SequenceLike[float], + sequence_int: typing.SequenceLike[int], + sequence_str: typing.SequenceLike[str], + sequence_sequence: typing.SequenceLike[typing.SequenceLike], +) -> int: + return 0 + +# must pass +validator_SequenceLike(MySequence()) +validator_SequenceLike([0, 1, 2, 3]) +validator_SequenceLike((0, 1, 2, 3)) +validator_SequenceLike(pygame.Rect(-10, 10, 40, 40)) +validator_SequenceLike(pygame.Vector2()) +validator_SequenceLike("1234567890") + +validator_SequenceLikeTypes( + (-1.5, -0.5, 0, 0.5, 2.5, 10), + (-2, -1, 0, 1, 2, 3), + "abcdefghijklmnopqrstuvwxyz", + [(0.5, 1.5), (-1, 1), "123", [(), (), ()]], +) + + +# validate _PathLike +class MyPath: + def __fspath__(self) -> str: + return "file.py" + +def validator_PathLike(path: typing._PathLike) -> int: + return 0 + +# must pass +validator_PathLike("file.py") +validator_PathLike(b"file.py") +validator_PathLike(pathlib.Path("file.py")) +validator_PathLike(MyPath()) + + +# validate Coordinate, IntCoordinate +def validator_Coordinate(coordinate: typing.Coordinate) -> int: + return 0 + +def validator_IntCoordinate(coordinate: typing.IntCoordinate) -> int: + return 0 + +# must pass +validator_Coordinate((1, 2)) +validator_Coordinate([3, -4]) +validator_Coordinate((5, -6.5)) +validator_Coordinate((-6.7, 8.9)) +validator_Coordinate(pygame.Vector2()) + +validator_IntCoordinate((3, 4)) +validator_IntCoordinate([-4, -3]) + + +def validator_ColorLike(color: typing.ColorLike) -> int: + return 0 + +# must pass +validator_ColorLike("green") +validator_ColorLike(1) +validator_ColorLike((255, 255, 255, 30)) +validator_ColorLike(pygame.Color(100, 100, 100, 100)) + + +# validate RectLike +class MyObject1: + def __init__(self): + self.rect = pygame.Rect(10, 10, 20, 20) + +class MyObject2: + def __init__(self): + self.rect = lambda: pygame.Rect(5, 5, 10, 10) + +class MyObject3: + def rect(self) -> pygame.Rect: + return pygame.Rect(15, 15, 30, 30) + +def validator_RectLike(rect: typing.RectLike) -> int: + return 0 + +# must pass +validator_RectLike((10, 10, 10, 10)) +validator_RectLike(((5, 5), (30, 30))) +validator_RectLike(([3, 3.2], pygame.Vector2())) +validator_RectLike(pygame.Rect(1, 2, 3, 4)) +validator_RectLike(MyObject1()) +validator_RectLike(MyObject2()) +validator_RectLike(MyObject3()) diff --git a/docs/README.md b/docs/README.md index bf49f76d51..349849b658 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,19 +2,20 @@ ### Accessing Documentation -Obviously you can visit pyga.me/docs/ to see the documentation, +Obviously you can visit pyga.me/docs/ to see the documentation, but the documentation can also be launched with `python -m pygame.docs` ### Generating the Documentation Steps: + - Install Sphinx (`pip install Sphinx`) - Fork the pygame-ce repository, download and navigate to it in the terminal -- Run `python buildconfig/make_docs.py` +- Run `python -m buildconfig docs` - If you are using the legacy `python setup.py docs` (which is now deprecated): - - (Run `python -m pip install -U pip setuptools` first if `ModuleNotFoundError: No module named setuptools` occurs) + - (Run `python -m pip install -U pip setuptools` first if `ModuleNotFoundError: No module named setuptools` occurs) -This will create a new folder under the `docs` folder. +This will create a new folder under the `docs` folder. In `docs/generated`, you will find a local copy of the pygame documentation. You can launch this by clicking on index.html or by running the command @@ -22,11 +23,18 @@ You can launch this by clicking on index.html or by running the command __main__.py in `docs/`). The docs launch command will direct you to the pygame website if there aren't any locally generated docs. +--- **DEPRECATED** --- There is also a `docs --fullgeneration` or `docs --f` command for regenerating everything regardless of whether Sphinx thinks it should be regenerated. This is useful when editing the theme CSS. -### Contributing + +--- **INSTEAD USE** --- +There is also `python -m buildconfig docs full_generation` for regenerating +everything regardless of whether Sphinx thinks it should be regenerated. This +is useful when editing the theme CSS. + +### Contributing If you see any grammatical mistakes or errors in the documentation, contributing to the docs is a great way to help out. @@ -43,16 +51,16 @@ Sphinx has a good ReStructed Text primer to learn the basics: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html Contributing steps: -- Have an idea to improve the docs, perhaps create an issue on Github -- Find the file you want to edit: it will most likely be in `docs/reST/ref`. -OR -- Pygame docs pages have an "Edit on Github" button, which will show you the file -- Download the pygame source from Github locally. - ^ One way to do this is to fork and use a Git client to make that a local repository + +- Have an idea to improve the docs, perhaps create an issue on Github. +- Find the file you want to edit. It will most likely be in `docs/reST/ref`. + OR +- Pygame docs pages have an "Edit on Github" button, which will show you the file. +- Download the pygame source from Github locally.^ One way to do this is to fork and use a Git client to make that a local repository. - Implement your idea. -- Follow the steps in "Generating the Documentation" - ^ This is important to test your changes work well -- Commit your changes, create a pull request +- Follow the steps in "Generating the Documentation" above. + ^ This is important to test that your changes work well. +- Commit your changes, create a pull request. ## Documentation Style diff --git a/docs/reST/_static/pygame_ce_lofi.png b/docs/reST/_static/pygame_ce_lofi.png new file mode 100644 index 0000000000..2db62e4cc6 Binary files /dev/null and b/docs/reST/_static/pygame_ce_lofi.png differ diff --git a/docs/reST/_static/pygame_ce_lofi.svg b/docs/reST/_static/pygame_ce_lofi.svg new file mode 100644 index 0000000000..5169aad6b5 --- /dev/null +++ b/docs/reST/_static/pygame_ce_lofi.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/reST/_static/pygame_ce_logo.png b/docs/reST/_static/pygame_ce_logo.png new file mode 100644 index 0000000000..8e377919ea Binary files /dev/null and b/docs/reST/_static/pygame_ce_logo.png differ diff --git a/docs/reST/_static/pygame_ce_logo.svg b/docs/reST/_static/pygame_ce_logo.svg new file mode 100644 index 0000000000..609bc917b6 --- /dev/null +++ b/docs/reST/_static/pygame_ce_logo.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/reST/_static/pygame_ce_powered.png b/docs/reST/_static/pygame_ce_powered.png new file mode 100644 index 0000000000..2e6ba0cf80 Binary files /dev/null and b/docs/reST/_static/pygame_ce_powered.png differ diff --git a/docs/reST/_static/pygame_ce_powered.svg b/docs/reST/_static/pygame_ce_powered.svg new file mode 100644 index 0000000000..57d906c704 --- /dev/null +++ b/docs/reST/_static/pygame_ce_powered.svg @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/reST/_static/pygame_ce_powered_lowres.png b/docs/reST/_static/pygame_ce_powered_lowres.png new file mode 100644 index 0000000000..6b4590a331 Binary files /dev/null and b/docs/reST/_static/pygame_ce_powered_lowres.png differ diff --git a/docs/reST/_static/pygame_ce_tiny.png b/docs/reST/_static/pygame_ce_tiny.png new file mode 100644 index 0000000000..648b2d8018 Binary files /dev/null and b/docs/reST/_static/pygame_ce_tiny.png differ diff --git a/docs/reST/_static/pygame_lofi.png b/docs/reST/_static/pygame_lofi.png deleted file mode 100644 index b9e9f56921..0000000000 Binary files a/docs/reST/_static/pygame_lofi.png and /dev/null differ diff --git a/docs/reST/_static/pygame_lofi.svg b/docs/reST/_static/pygame_lofi.svg deleted file mode 100644 index 9747a6d5dc..0000000000 --- a/docs/reST/_static/pygame_lofi.svg +++ /dev/null @@ -1,234 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/reST/_static/pygame_logo.png b/docs/reST/_static/pygame_logo.png deleted file mode 100644 index 0de28c4c56..0000000000 Binary files a/docs/reST/_static/pygame_logo.png and /dev/null differ diff --git a/docs/reST/_static/pygame_logo.svg b/docs/reST/_static/pygame_logo.svg deleted file mode 100644 index 38fa4ec5cd..0000000000 --- a/docs/reST/_static/pygame_logo.svg +++ /dev/null @@ -1,234 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/reST/_static/pygame_powered.png b/docs/reST/_static/pygame_powered.png deleted file mode 100644 index 4d5bf4bb9d..0000000000 Binary files a/docs/reST/_static/pygame_powered.png and /dev/null differ diff --git a/docs/reST/_static/pygame_powered.svg b/docs/reST/_static/pygame_powered.svg deleted file mode 100644 index 2bb4a42a2f..0000000000 --- a/docs/reST/_static/pygame_powered.svg +++ /dev/null @@ -1,326 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/reST/_static/pygame_powered_lowres.png b/docs/reST/_static/pygame_powered_lowres.png deleted file mode 100644 index 642ae7a6e8..0000000000 Binary files a/docs/reST/_static/pygame_powered_lowres.png and /dev/null differ diff --git a/docs/reST/_static/pygame_tiny.png b/docs/reST/_static/pygame_tiny.png deleted file mode 100644 index 5ee063a7d9..0000000000 Binary files a/docs/reST/_static/pygame_tiny.png and /dev/null differ diff --git a/docs/reST/_static/upstream_logos.zip b/docs/reST/_static/upstream_logos.zip new file mode 100644 index 0000000000..80ffee46ed Binary files /dev/null and b/docs/reST/_static/upstream_logos.zip differ diff --git a/docs/reST/c_api/base.rst b/docs/reST/c_api/base.rst index c2117460ac..9c20197f5f 100644 --- a/docs/reST/c_api/base.rst +++ b/docs/reST/c_api/base.rst @@ -92,7 +92,7 @@ C header: src_c/include/pygame.h .. c:function:: int pg_RGBAFromObj(PyObject *obj, Uint8 *RGBA) You probably want to use the :c:func:`pg_RGBAFromObjEx` function instead of this. - + Convert the color represented by object *obj* into a red, green, blue, alpha length 4 C array *RGBA*. The object must be a length 3 or 4 sequence of numbers having values diff --git a/docs/reST/c_api/event.rst b/docs/reST/c_api/event.rst index 7f053faf3d..650dccd25b 100644 --- a/docs/reST/c_api/event.rst +++ b/docs/reST/c_api/event.rst @@ -40,7 +40,7 @@ Header file: src_c/include/pygame.h On failure raise a Python exception and return ``NULL``. .. c:function:: char* pgEvent_GetKeyDownInfo(void) - + Return an array of bools (using char) of length SDL_NUM_SCANCODES with the most recent key presses. diff --git a/docs/reST/c_api/mixer.rst b/docs/reST/c_api/mixer.rst index e9d947c42d..6cd281a2df 100644 --- a/docs/reST/c_api/mixer.rst +++ b/docs/reST/c_api/mixer.rst @@ -64,4 +64,3 @@ Header file: src_c/include/pygame_mixer.h Return the SDL mixer music channel number associated with :c:type:`pgChannel_Type` instance *x*. A macro that does no ``NULL`` or Python type check on *x*. - diff --git a/docs/reST/c_api/rwobject.rst b/docs/reST/c_api/rwobject.rst index c734442def..3507118451 100644 --- a/docs/reST/c_api/rwobject.rst +++ b/docs/reST/c_api/rwobject.rst @@ -56,4 +56,4 @@ Header file: src_c/include/pygame.h :c:func:`PyUnicode_AsEncodedString`. On error raise a Python exception and return ``NULL``, using *eclass* as the exception type if it is not ``NULL``. - If *obj* is ``NULL`` assume an exception was already raised and pass it on. \ No newline at end of file + If *obj* is ``NULL`` assume an exception was already raised and pass it on. diff --git a/docs/reST/c_api/surflock.rst b/docs/reST/c_api/surflock.rst index 6d806e89a5..9e08034dea 100644 --- a/docs/reST/c_api/surflock.rst +++ b/docs/reST/c_api/surflock.rst @@ -15,33 +15,6 @@ This extension module implements SDL surface locking for the Header file: src_c/include/pygame.h -.. c:type:: pgLifetimeLockObject - - .. c:member:: PyObject *surface - - An SDL locked pygame surface. - - .. c:member:: PyObject *lockobj - - The Python object which owns the lock on the surface. - This field does not own a reference to the object. - - The lifetime lock type instance. - A lifetime lock pairs a locked pygame surface with - the Python object that locked the surface for modification. - The lock is removed automatically when the lifetime lock instance - is garbage collected. - -.. c:var:: PyTypeObject *pgLifetimeLock_Type - - The pygame internal surflock lifetime lock object type. - -.. c:function:: int pgLifetimeLock_Check(PyObject *x) - - Return true if Python object *x* is a :c:data:`pgLifetimeLock_Type` instance, - false otherwise. - This will return false on :c:data:`pgLifetimeLock_Type` subclass instances as well. - .. c:function:: void pgSurface_Prep(pgSurfaceObject *surfobj) If *surfobj* is a subsurface, then lock the parent surface with *surfobj* @@ -72,11 +45,3 @@ Header file: src_c/include/pygame.h .. c:function:: int pgSurface_UnLockBy(pgSurfaceObject *surfobj, PyObject *lockobj) Remove the lock on pygame surface *surfobj* owned by Python object *lockobj*. - -.. c:function:: PyObject *pgSurface_LockLifetime(PyObject *surfobj, PyObject *lockobj) - - Lock pygame surface *surfobj* for Python object *lockobj* and return a - new :c:data:`pgLifetimeLock_Type` instance for the lock. - - This function is not called anywhere within pygame. - It and pgLifetimeLock_Type are candidates for removal. diff --git a/docs/reST/conf.py b/docs/reST/conf.py index cfd4138170..136a723ae3 100644 --- a/docs/reST/conf.py +++ b/docs/reST/conf.py @@ -122,7 +122,7 @@ # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = '_static/pygame_tiny.png' +html_logo = '_static/pygame_ce_tiny.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 diff --git a/docs/reST/index.rst b/docs/reST/index.rst index 53121de17a..229acc023b 100644 --- a/docs/reST/index.rst +++ b/docs/reST/index.rst @@ -38,6 +38,41 @@ section below, check out a video tutorial (`I'm a fan of this one `_), or reference the API documentation by module. +Experimental modules +-------------------- + +Experimental modules are work in progress, this is why you should refrain from relying on any features +provided by these modules, as they are subject to change or removal without prior notice. +If you want to test these experimental modules, you might want to understand how you import +them, this is how you can do it: + +.. code-block:: python + + from pygame import experimental_module + # Or for specific modules like _sdl2.controller + from pygame._sdl2 import controller + # Or + import pygame.experimental_module + +Don't forget to report us any problem with the experimental features on `github`_ so we can easily +turn them to stable API in the future ^^. + +**Below is currently the list of experimental modules :** + +:doc:`ref/geometry` + Pygame module for the Circle, Line, and Polygon objects. + +:doc:`ref/window` + Pygame object that handles a window. + +:doc:`ref/sdl2_controller` + Pygame module to work with controllers. + +:doc:`ref/sdl2_video` + Pygame module for porting new SDL video systems. + +.. _github: https://github.com/pygame-community/pygame-ce + Documents --------- @@ -206,6 +241,9 @@ Reference :doc:`ref/transform` Resize and move images. +:doc:`ref/typing` + Provide common typehints + :doc:`pygame C API ` The C api shared amongst pygame extension modules. diff --git a/docs/reST/logos.rst b/docs/reST/logos.rst index a7ee49311c..5a6d83b20a 100644 --- a/docs/reST/logos.rst +++ b/docs/reST/logos.rst @@ -1,47 +1,58 @@ ************************************************* - Pygame Logos Page + Pygame CE Logos Page ************************************************* -Pygame Logos -============ +Pygame CE Logos +=============== These logos are available for use in your own game projects. -Please put them up wherever you see fit. The logo was created -by TheCorruptor on July 29, 2001 and upscaled by Mega_JC on -August 29, 2021. +Please put them up wherever you see fit. + +The pygame-ce logo and its variants were created by Mega-JC and kadir014 on June 30, 2024. .. container:: fullwidth - .. image:: _static/pygame_logo.png + .. image:: _static/pygame_ce_logo.png - | `pygame_logo.svg <_static/pygame_logo.svg>`_ - | `pygame_logo.png <_static/pygame_logo.png>`_ - 1561 x 438 + | `pygame_ce_logo.svg <_static/pygame_ce_logo.svg>`_ + | `pygame_ce_logo.png <_static/pygame_ce_logo.png>`_ - 1560 x 800 - .. image:: _static/pygame_lofi.png + .. image:: _static/pygame_ce_lofi.png - | `pygame_lofi.svg <_static/pygame_lofi.svg>`_ - | `pygame_lofi.png <_static/pygame_lofi.png>`_ - 1561 x 438 + | `pygame_ce_lofi.svg <_static/pygame_ce_lofi.svg>`_ + | `pygame_ce_lofi.png <_static/pygame_ce_lofi.png>`_ - 1560 x 800 - .. image:: _static/pygame_powered.png + .. image:: _static/pygame_ce_powered.png - | `pygame_powered.svg <_static/pygame_powered.svg>`_ - | `pygame_powered.png <_static/pygame_powered.png>`_ - 1617 x 640 + | `pygame_ce_powered.svg <_static/pygame_ce_powered.svg>`_ + | `pygame_ce_powered.png <_static/pygame_ce_powered.png>`_ - 1560 x 824 - .. image:: _static/pygame_tiny.png + .. image:: _static/pygame_ce_tiny.png - | `pygame_tiny.png <_static/pygame_tiny.png>`_ - 214 x 60 + | `pygame_ce_tiny.png <_static/pygame_ce_tiny.png>`_ - 214 x 110 - .. image:: _static/pygame_powered_lowres.png + .. image:: _static/pygame_ce_powered_lowres.png - | `pygame_powered_lowres.png <_static/pygame_powered_lowres.png>`_ - 101 x 40 + | `pygame_ce_powered_lowres.png <_static/pygame_ce_powered_lowres.png>`_ - 101 x 53 -There is a higher resolution layered photoshop image -available `here `_. *(1.3 MB)* -Legacy logos ------------- +Upstream & legacy logos +----------------------- + +The pygame logo was originally created +by TheCorruptor on July 29, 2001. + +It was upscaled alongside its "LoFi" and "Powered" variants by Mega-JC on +August 29, 2021. + +.. container:: fullwidth + + | `upstream_logos.zip <_static/upstream_logos.zip>`_ - 699 KB + +There is a higher resolution layered photoshop image +available `here `_. *(1.3 MB)* .. container:: fullwidth - `legacy_logos.zip <_static/legacy_logos.zip>`_ - 50.1 KB \ No newline at end of file + | `legacy_logos.zip <_static/legacy_logos.zip>`_ - 50.1 KB diff --git a/docs/reST/ref/bufferproxy.rst b/docs/reST/ref/bufferproxy.rst index 4936a4102d..6b3e04089c 100644 --- a/docs/reST/ref/bufferproxy.rst +++ b/docs/reST/ref/bufferproxy.rst @@ -65,7 +65,7 @@ The callback is passed on argument, the ``"parent"`` object if given, otherwise None. The callback is useful for releasing a lock on the parent. - + The BufferProxy class supports subclassing, instance variables, and weak references. diff --git a/docs/reST/ref/code_examples/cursors_module_example.py b/docs/reST/ref/code_examples/cursors_module_example.py index 71f67b723f..517ca1fd11 100644 --- a/docs/reST/ref/code_examples/cursors_module_example.py +++ b/docs/reST/ref/code_examples/cursors_module_example.py @@ -11,11 +11,11 @@ # create bitmap cursors bitmap_1 = pygame.cursors.Cursor(*pygame.cursors.arrow) bitmap_2 = pygame.cursors.Cursor( - (24, 24), (0, 0), *pygame.cursors.compile(pygame.cursors.thickarrow_strings) + (24, 24), (0, 0), *pygame.cursors.compile(pygame.cursors.thickarrow_strings) ) # create a color cursor -surf = pygame.Surface((40, 40)) # you could also load an image +surf = pygame.Surface((40, 40)) # you could also load an image surf.fill((120, 50, 50)) # and use that as your surface color = pygame.cursors.Cursor((20, 20), surf) diff --git a/docs/reST/ref/code_examples/draw_module_example.py b/docs/reST/ref/code_examples/draw_module_example.py index 99c2c89f9d..ce13298477 100644 --- a/docs/reST/ref/code_examples/draw_module_example.py +++ b/docs/reST/ref/code_examples/draw_module_example.py @@ -77,13 +77,13 @@ # Draw a circle pygame.draw.circle(screen, "blue", [60, 250], 40) - + # Draw an antialiased circle with 3 pixels wide line pygame.draw.aacircle(screen, "green", [340, 250], 40, 3) - + # Draw an antialiased top right circle quadrant with 4 pixels wide line pygame.draw.aacircle(screen, "red", [340, 250], 20, 4, draw_top_right=True) - + # Draw an antialiased bottom left filled circle quadrant pygame.draw.aacircle(screen, "blue", [340, 250], 20, draw_bottom_left=True) diff --git a/docs/reST/ref/color.rst b/docs/reST/ref/color.rst index b257aaea37..4d36c23fcc 100644 --- a/docs/reST/ref/color.rst +++ b/docs/reST/ref/color.rst @@ -196,7 +196,7 @@ | :sl:`Gets or sets the normalized representation of the Color.` | :sg:`normalized -> tuple` - + The ``Normalized``` representation of the Color. The components of the ``Normalized`` representation represent the basic ``RGBA`` values but normalized the ranges of the values are ``r`` = [0, 1], ``g`` = [0, 1], ``b`` = [0, 1] @@ -209,7 +209,7 @@ .. versionadded:: 2.5.0 .. ## Color.normalized ## - + .. classmethod:: from_cmy | :sl:`Returns a Color object from a CMY representation` @@ -222,7 +222,7 @@ .. versionadded:: 2.3.1 .. ## Color.from_cmy ## - + .. classmethod:: from_hsva | :sl:`Returns a Color object from an HSVA representation` @@ -235,7 +235,7 @@ .. versionadded:: 2.3.1 .. ## Color.from_hsva ## - + .. classmethod:: from_hsla | :sl:`Returns a Color object from an HSLA representation` @@ -300,11 +300,11 @@ | :sl:`Set the number of elements in the Color to 1,2,3, or 4.` | :sg:`set_length(len, /) -> None` - DEPRECATED: You may unpack the values you need like so, + DEPRECATED: You may unpack the values you need like so, ``r, g, b, _ = pygame.Color(100, 100, 100)`` If you only want r, g and b - Or - ``r, g, *_ = pygame.Color(100, 100, 100)`` + Or + ``r, g, *_ = pygame.Color(100, 100, 100)`` if you only want r and g The default Color length is 4. Colors can have lengths 1,2,3 or 4. This @@ -320,8 +320,8 @@ | :sl:`returns the grayscale of a Color` | :sg:`grayscale() -> Color` - - Returns a new Color object which represents the grayscaled version of self, using the luminosity formula, + + Returns a new Color object which represents the grayscaled version of self, using the luminosity formula, which weights red, green, and blue according to their relative contribution to perceived brightness. .. versionadded:: 2.1.4 diff --git a/docs/reST/ref/common.txt b/docs/reST/ref/common.txt index 05685a1108..9373ee3e6b 100644 --- a/docs/reST/ref/common.txt +++ b/docs/reST/ref/common.txt @@ -1,2 +1 @@ .. include:: ../common.txt - diff --git a/docs/reST/ref/cursors.rst b/docs/reST/ref/cursors.rst index 1a1e8f97db..009bfa23f8 100644 --- a/docs/reST/ref/cursors.rst +++ b/docs/reST/ref/cursors.rst @@ -23,7 +23,7 @@ single tuple you can call like this: :: >>> pygame.mouse.set_cursor(*pygame.cursors.arrow) - + The following variables can be passed to ``pygame.mouse.set_cursor`` function: * ``pygame.cursors.arrow`` @@ -55,7 +55,7 @@ The following strings can be converted into cursor bitmaps with * ``pygame.cursors.sizer_y_strings`` * ``pygame.cursors.sizer_xy_strings`` - + * ``pygame.cursor.textmarker_strings`` .. function:: compile @@ -65,15 +65,15 @@ The following strings can be converted into cursor bitmaps with A sequence of strings can be used to create binary cursor data for the system cursor. This returns the binary data in the form of two tuples. - Those can be passed as the third and fourth arguments respectively of the + Those can be passed as the third and fourth arguments respectively of the :func:`pygame.mouse.set_cursor()` function. If you are creating your own cursor strings, you can use any value represent the black and white pixels. Some system allow you to set a special toggle color for the system color, this is also called the xor color. If the system does not support xor cursors, that color will simply be black. - - The height must be divisible by 8. The width of the strings must all be equal + + The height must be divisible by 8. The width of the strings must all be equal and be divisible by 8. If these two conditions are not met, ``ValueError`` is raised. An example set of cursor strings looks like this @@ -144,17 +144,17 @@ The following strings can be converted into cursor bitmaps with In pygame 2, there are 3 types of cursors you can create to give your game that little bit of extra polish. There's **bitmap** type cursors, which existed in pygame 1.x, and are compiled from a string or load from an xbm file. - Then there are **system** type cursors, where you choose a preset that will - convey the same meaning but look native across different operating systems. + Then there are **system** type cursors, where you choose a preset that will + convey the same meaning but look native across different operating systems. Finally you can create a **color** cursor, which displays a pygame surface as the cursor. **Creating a system cursor** - Choose a constant from this list, pass it into ``pygame.cursors.Cursor(constant)``, + Choose a constant from this list, pass it into ``pygame.cursors.Cursor(constant)``, and you're good to go. Be advised that not all systems support every system cursor, and you may get a substitution instead. For example, on macOS, WAIT/WAITARROW should show up as an arrow, and SIZENWSE/SIZENESW/SIZEALL - should show up as a closed hand. And on Wayland, every SIZE cursor should + should show up as a closed hand. And on Wayland, every SIZE cursor should show up as a hand. :: @@ -165,15 +165,15 @@ The following strings can be converted into cursor bitmaps with pygame.SYSTEM_CURSOR_IBEAM i-beam pygame.SYSTEM_CURSOR_WAIT wait pygame.SYSTEM_CURSOR_CROSSHAIR crosshair - pygame.SYSTEM_CURSOR_WAITARROW small wait cursor + pygame.SYSTEM_CURSOR_WAITARROW small wait cursor (or wait if not available) - pygame.SYSTEM_CURSOR_SIZENWSE double arrow pointing + pygame.SYSTEM_CURSOR_SIZENWSE double arrow pointing northwest and southeast pygame.SYSTEM_CURSOR_SIZENESW double arrow pointing northeast and southwest pygame.SYSTEM_CURSOR_SIZEWE double arrow pointing west and east - pygame.SYSTEM_CURSOR_SIZENS double arrow pointing + pygame.SYSTEM_CURSOR_SIZENS double arrow pointing north and south pygame.SYSTEM_CURSOR_SIZEALL four pointed arrow pointing north, south, east, and west @@ -181,7 +181,7 @@ The following strings can be converted into cursor bitmaps with pygame.SYSTEM_CURSOR_HAND hand **Creating a cursor without passing arguments** - + In addition to the cursor constants available and described above, you can also call ``pygame.cursors.Cursor()``, and your cursor is ready (doing that is the same as calling ``pygame.cursors.Cursor(pygame.SYSTEM_CURSOR_ARROW)``. @@ -196,34 +196,34 @@ The following strings can be converted into cursor bitmaps with **Creating a bitmap cursor** When the mouse cursor is visible, it will be displayed as a black and white - bitmap using the given bitmask arrays. The ``size`` is a sequence containing - the cursor width and height. ``hotspot`` is a sequence containing the cursor - hotspot position. - - A cursor has a width and height, but a mouse position is represented by a - set of point coordinates. So the value passed into the cursor ``hotspot`` - variable helps pygame to actually determine at what exact point the cursor + bitmap using the given bitmask arrays. The ``size`` is a sequence containing + the cursor width and height. ``hotspot`` is a sequence containing the cursor + hotspot position. + + A cursor has a width and height, but a mouse position is represented by a + set of point coordinates. So the value passed into the cursor ``hotspot`` + variable helps pygame to actually determine at what exact point the cursor is at. - - ``xormasks`` is a sequence of bytes containing the cursor xor data masks. + + ``xormasks`` is a sequence of bytes containing the cursor xor data masks. Lastly ``andmasks``, a sequence of bytes containing the cursor bitmask data. - To create these variables, we can make use of the + To create these variables, we can make use of the :func:`pygame.cursors.compile()` function. - Width and height must be a multiple of 8, and the mask arrays must be the + Width and height must be a multiple of 8, and the mask arrays must be the correct size for the given width and height. Otherwise an exception is raised. - + .. method:: copy | :sl:`copy the current cursor` | :sg:`copy() -> Cursor` - + Returns a new Cursor object with the same data and hotspot as the original. .. ## pygame.cursors.Cursor.copy ## - + .. attribute:: type - + | :sl:`get the cursor type` | :sg:`type -> string` @@ -243,7 +243,7 @@ The following strings can be converted into cursor bitmaps with .. versionaddedold:: 2.0.1 .. ## pygame.cursors.Cursor ## - + .. ## pygame.cursors ## Example code for creating and settings cursors. (Click the mouse to switch cursor) diff --git a/docs/reST/ref/display.rst b/docs/reST/ref/display.rst index 6122e34d23..310ebb38fa 100644 --- a/docs/reST/ref/display.rst +++ b/docs/reST/ref/display.rst @@ -175,9 +175,9 @@ required). pygame.HIDDEN window is opened in hidden mode - .. versionadded:: 2.0.0 ``SCALED``, ``SHOWN`` and ``HIDDEN`` + .. versionaddedold:: 2.0.0 ``SCALED``, ``SHOWN`` and ``HIDDEN`` - .. versionadded:: 2.0.0 ``vsync`` parameter + .. versionaddedold:: 2.0.0 ``vsync`` parameter By setting the ``vsync`` parameter to ``1``, it is possible to get a display with vertical sync at a constant frame rate determined by the monitor and @@ -273,7 +273,7 @@ required). | :sl:`Update all, or a portion, of the display. For non-OpenGL displays.` | :sg:`update(rectangle=None, /) -> None` - | :sg:`update(rectangle_list, /) -> None` + | :sg:`update(rectangle_iterable, /) -> None` For non OpenGL display Surfaces, this function is very similar to ``pygame.display.flip()`` with an optional parameter that allows only @@ -285,8 +285,8 @@ required). updated. Whereas ``display.update()`` means the whole window is updated. - You can pass the function a single rectangle, or a sequence of rectangles. - Generally you do not want to pass a sequence of rectangles as there is a + You can pass the function a single rectangle, or an iterable of rectangles. + Generally you do not want to pass an iterable of rectangles as there is a performance cost per rectangle passed to the function. On modern hardware, after a very small number of rectangles passed in, the per-rectangle cost will exceed the saving of updating less pixels. In most applications it is @@ -294,12 +294,14 @@ required). means you do not need to keep track of a list of rectangles for each call to update. - If passing a sequence of rectangles it is safe to include None + If passing an iterable of rectangles it is safe to include None values in the list, which will be skipped. This call cannot be used on ``pygame.OPENGL`` displays and will generate an exception. + .. versionchanged:: 2.5.1 Added support for passing an iterable, previously only sequence was allowed + .. ## pygame.display.update ## .. function:: get_driver @@ -850,13 +852,10 @@ required). :param str title: A title string. :param str message: A message string. If this parameter is set to ``None``, the message will be the title. :param str message_type: Set the type of message_box, could be ``"info"``, ``"warn"`` or ``"error"``. + :param Window parent_window: The parent window of the message box. :param tuple buttons: An optional sequence of button name strings to show to the user. :param int return_button: Button index to use if the return key is hit, ``0`` by default. :param int escape_button: Button index to use if the escape key is hit, ``None`` for no button linked by default. -.. - (Uncomment this after the window API is published) - :param Window parent_window: The parent window of the message_box -.. :return: The index of the button that was pushed. diff --git a/docs/reST/ref/draw.rst b/docs/reST/ref/draw.rst index a6e09d9319..f9300ee153 100644 --- a/docs/reST/ref/draw.rst +++ b/docs/reST/ref/draw.rst @@ -213,7 +213,7 @@ object around the draw calls (see :func:`pygame.Surface.lock` and | :sg:`aacircle(surface, color, center, radius, width=0, draw_top_right=None, draw_top_left=None, draw_bottom_left=None, draw_bottom_right=None) -> Rect` Draws an antialiased circle on the given surface. - Uses Xaolin Wu Circle Algorithm. + Uses Xiaolin Wu Circle Algorithm. adapted from: https://cgg.mff.cuni.cz/~pepca/ref/WU.pdf :param Surface surface: surface to draw on @@ -606,4 +606,3 @@ object around the draw calls (see :func:`pygame.Surface.lock` and Example code for draw module. .. literalinclude:: code_examples/draw_module_example.py - diff --git a/docs/reST/ref/event.rst b/docs/reST/ref/event.rst index 89562572a9..b6c8349948 100644 --- a/docs/reST/ref/event.rst +++ b/docs/reST/ref/event.rst @@ -51,7 +51,7 @@ their own new events with the :func:`pygame.event.Event()` function. The event type identifier is in between the values of ``NOEVENT`` and ``NUMEVENTS``. User defined events should have a value in the inclusive range of ``USEREVENT`` to ``NUMEVENTS - 1``. User defined events can get a custom -event number with :func:`pygame.event.custom_type()`. +event number with :func:`pygame.event.custom_type()`. It is recommended all user events follow this system. Events support equality and inequality comparisons. Two events are equal if @@ -159,7 +159,7 @@ pygame 2 also supports controller hot-plugging Also in this version, ``instance_id`` attributes were added to joystick events, and the ``joy`` attribute was deprecated. -``KEYMAPCHANGED`` is a type of an event sent when keymap changes due to a +``KEYMAPCHANGED`` is a type of an event sent when keymap changes due to a system event such as an input language or keyboard layout change. ``CLIPBOARDUPDATE`` is an event sent when clipboard changes. This can still @@ -170,7 +170,7 @@ not trigger this event. .. versionaddedold:: 2.0.0 -.. versionadded:: 2.1.3 ``KEYMAPCHANGED``, ``CLIPBOARDUPDATE``, +.. versionadded:: 2.1.3 ``KEYMAPCHANGED``, ``CLIPBOARDUPDATE``, ``RENDER_TARGETS_RESET``, ``RENDER_DEVICE_RESET`` and ``LOCALECHANGED`` | @@ -301,7 +301,7 @@ On Android, the following events can be generated Returns a single event from the queue. If the queue is empty this function will wait until one is created. From pygame 2.0.0, if a ``timeout`` argument - is given, the function will return an event of type ``pygame.NOEVENT`` + is given, the function will return an event of type ``pygame.NOEVENT`` if no events enter the queue in ``timeout`` milliseconds. The event is removed from the queue once it has been returned. While the program is waiting it will sleep in an idle state. This is important for programs that want to share the diff --git a/docs/reST/ref/examples.rst b/docs/reST/ref/examples.rst index 201f20da30..88dc4d95e7 100644 --- a/docs/reST/ref/examples.rst +++ b/docs/reST/ref/examples.rst @@ -26,7 +26,7 @@ eg: python -m pygame.examples.scaletest someimage.png -Resources such as images and sounds for the examples are found in the +Resources such as images and sounds for the examples are found in the pygame/examples/data subdirectory. You can find where the example files are installed by using the following @@ -287,7 +287,7 @@ pygame much earlier. Thumbnail generation with scaling is an example of what you can do with pygame. - ``NOTE``: the pygame scale function uses SIMD acceleration if available, + ``NOTE``: the pygame scale function uses SIMD acceleration if available, and can be run in multiple threads. If ``headless_no_windows_needed.py`` is run as a program it takes the diff --git a/docs/reST/ref/font.rst b/docs/reST/ref/font.rst index acfc789212..a538a48aec 100644 --- a/docs/reST/ref/font.rst +++ b/docs/reST/ref/font.rst @@ -24,7 +24,7 @@ accessed by passing ``None`` as the font name. Before pygame 2.0.3, pygame.font accepts any UCS-2 / UTF-16 character ('\\u0001' to '\\uFFFF'). After 2.0.3, pygame.font built with SDL_ttf -2.0.15 accepts any valid UCS-4 / UTF-32 character +2.0.15 accepts any valid UCS-4 / UTF-32 character (like emojis, if the font has them) ('\\U00000001' to '\\U0010FFFF')). More about this in :func:`Font.render`. @@ -88,8 +88,8 @@ solves no longer exists, it will likely be removed in the future. Returns a tuple of integers that identify SDL_ttf's version. SDL_ttf is the underlying font rendering library, written in C, - on which pygame's font module depends. If 'linked' is True (the default), - the function returns the version of the linked TTF library. + on which pygame's font module depends. If 'linked' is True (the default), + the function returns the version of the linked TTF library. Otherwise this function returns the version of TTF pygame was compiled with .. versionadded:: 2.1.3 @@ -216,7 +216,7 @@ solves no longer exists, it will likely be removed in the future. | :sl:`Gets the font's style_name.` | :sg:`style_name -> str` - Read only. Returns the font's style name. Style names are arbitrary, can be an empty string. + Read only. Returns the font's style name. Style names are arbitrary, can be an empty string. Here are some examples: 'Black', 'Bold', 'Bold Italic', 'BoldOblique', 'Book', 'BookOblique', 'Condensed', 'Condensed Oblique', @@ -261,7 +261,7 @@ solves no longer exists, it will likely be removed in the future. .. versionaddedold:: 2.0.0 .. ## Font.underline ## - + .. attribute:: strikethrough | :sl:`Gets or sets whether the font should be rendered with a strikethrough.` @@ -311,7 +311,7 @@ solves no longer exists, it will likely be removed in the future. | :sl:`draw text on a new Surface` | :sg:`render(text, antialias, color, bgcolor=None, wraplength=0) -> Surface` - This creates a new Surface with the specified text rendered on it. + This creates a new Surface with the specified text rendered on it. :mod:`pygame.font` provides no way to directly draw text on an existing Surface: instead you must use :func:`Font.render` to create an image (Surface) of the text, then blit this image onto another Surface. @@ -405,7 +405,7 @@ solves no longer exists, it will likely be removed in the future. .. note:: This is the same as the :attr:`underline` attribute. .. ## Font.get_underline ## - + .. method:: set_strikethrough | :sl:`control if text is rendered with a strikethrough` @@ -416,7 +416,7 @@ solves no longer exists, it will likely be removed in the future. This can be mixed with the bold, italic and underline modes. .. note:: This is the same as the :attr:`strikethrough` attribute. - + .. versionadded:: 2.1.3 .. ## Font.set_strikethrough ## @@ -429,7 +429,7 @@ solves no longer exists, it will likely be removed in the future. Return True when the font strikethrough is enabled. .. note:: This is the same as the :attr:`strikethrough` attribute. - + .. versionadded:: 2.1.3 .. ## Font.get_strikethrough ## @@ -541,7 +541,7 @@ solves no longer exists, it will likely be removed in the future. Returns the point size of the font. Will not be accurate upon initializing the font object when the font name is initialized as ``None``. - + .. versionadded:: 2.3.1 .. ## Font.get_point_size ## @@ -581,7 +581,7 @@ solves no longer exists, it will likely be removed in the future. .. versionadded:: 2.1.4 - .. ## Font.set_script ## + .. ## Font.set_script ## .. method:: set_direction @@ -604,7 +604,7 @@ solves no longer exists, it will likely be removed in the future. or bottom-to-top rendering. .. versionadded:: 2.1.4 - + .. ## font.set_direction ## .. ## pygame.font.Font ## diff --git a/docs/reST/ref/freetype.rst b/docs/reST/ref/freetype.rst index bf6a74609e..04687be9ae 100644 --- a/docs/reST/ref/freetype.rst +++ b/docs/reST/ref/freetype.rst @@ -229,7 +229,7 @@ loaded. This module must be imported explicitly to be used. :: | :sl:`Gets the font's style_name.` | :sg:`style_name -> str` - Read only. Returns the font's style name. Style names are arbitrary, can be an empty string. + Read only. Returns the font's style name. Style names are arbitrary, can be an empty string. Here are some examples: 'Black', 'Bold', 'Bold Italic', 'BoldOblique', 'Book', 'BookOblique', 'Condensed', 'Condensed Oblique', diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index 8d3fa7cfd0..3bcd81f785 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -24,10 +24,10 @@ | :sg:`Circle((x, y), radius) -> Circle` | :sg:`Circle(x, y, radius) -> Circle` - The `Circle` class provides many useful methods for collision / transform and intersection. + The `Circle` class provides many useful methods for collision testing, transformation and intersection. A `Circle` can be created from a combination of a pair of coordinates that represent the center of the circle and a radius. Circles can also be created from python objects that - are already a `Circle` or have an attribute named "circle". + are already a `Circle` (effectively copying the circle) or have an attribute named "circle". Specifically, to construct a circle you can pass the x, y, and radius values as separate arguments or inside a sequence(list or tuple). @@ -38,17 +38,10 @@ ((x, y), radius) (x, y, radius) + (vector2, radius) - It is important to note that you cannot create degenerate circles, which are circles with - a radius of 0 or less. If you try to create such a circle, the `Circle` object will not be - created and an error will be raised. This is because a circle with a radius of 0 or - less is not a valid geometric object. - - The `Circle` class has both virtual and non-virtual attributes. Non-virtual attributes - are attributes that are stored in the `Circle` object itself. Virtual attributes are the - result of calculations that utilize the Circle's non-virtual attributes. - - Here is the list of all the attributes and methods of the Circle class: + The `Circle` class only stores the x, y and r attributes, everything else is calculated + on the fly based on them. **Circle Attributes** @@ -59,9 +52,7 @@ | :sl:`center x coordinate of the circle` | :sg:`x -> float` - The `x` coordinate of the center of the circle. It can be reassigned to move the circle. - Reassigning the `x` attribute will move the circle to the new `x` coordinate. - The `y` and `r` attributes will not be affected. + The horizontal coordinate of the center of the circle. Reassigning it moves the circle. .. versionadded:: 2.4.0 @@ -72,9 +63,7 @@ | :sl:`center y coordinate of the circle` | :sg:`y -> float` - The `y` coordinate of the center of the circle. It can be reassigned to move the circle. - Reassigning the `y` attribute will move the circle to the new `y` coordinate. - The `x` and `r` attributes will not be affected. + The vertical coordinate of the center of the circle. Reassigning it moves the circle. .. versionadded:: 2.4.0 @@ -85,12 +74,12 @@ | :sl:`radius of the circle` | :sg:`r -> float` - It is not possible to set the radius to a negative value. It can be reassigned. - If reassigned it will only change the radius of the circle. - The circle will not be moved from its original position. + Represents the size of the circle. It can't be negative. Reassigning it scales the circle. .. versionadded:: 2.4.0 + .. versionchanged:: 2.5.1 It is now allowed to create degenerate circles with :math:`r = 0`. + .. ## Circle.r ## .. attribute:: r_sqr @@ -98,9 +87,7 @@ | :sl:`radius of the circle squared` | :sg:`r_sqr -> float` - It's equivalent to `r*r`. It can be reassigned. If reassigned, the radius - of the circle will be changed to the square root of the new value. - The circle will not be moved from its original position. + It's equivalent to :math:`r^2`. It can't be negative. Reassigning it changes the radius to :math:`r = \sqrt{r_{sqr}}`. .. versionadded:: 2.4.0 @@ -111,9 +98,8 @@ | :sl:`x and y coordinates of the center of the circle` | :sg:`center -> (float, float)` - It's a tuple containing the `x` and `y` coordinates that represent the center - of the circle. It can be reassigned. If reassigned, the circle will be moved - to the new position. The radius will not be affected. + It's a tuple containing the circle's `x` and `y` coordinates representing its center. + Reassigning it moves the circle to the new position. .. versionadded:: 2.4.0 @@ -124,9 +110,8 @@ | :sl:`diameter of the circle` | :sg:`diameter -> float` - It's calculated using the `d=2*r` formula. It can be reassigned. If reassigned - the radius will be changed to half the diameter. - The circle will not be moved from its original position. + It's equivalent to :math:`2 \cdot r`. It can't be negative. Reassigning it + changes the radius to :math:`r = \frac{d}{2}`. .. versionadded:: 2.4.0 @@ -137,9 +122,8 @@ | :sl:`area of the circle` | :sg:`area -> float` - It's calculated using the `area=pi*r*r` formula. It can be reassigned. - If reassigned the circle radius will be changed to produce a circle with matching - area. The circle will not be moved from its original position. + It's equivalent to :math:`\pi \cdot r^2`. It can't be negative. Reassigning it + changes the radius to :math:`r = \sqrt{\frac{area}{\pi}}` producing a circle with matching area. .. versionadded:: 2.4.0 @@ -150,129 +134,131 @@ | :sl:`circumference of the circle` | :sg:`circumference -> float` - It's calculated using the `circumference=2*pi*r` formula. It can be reassigned. - If reassigned the circle radius will be changed to produce a circle with matching - circumference. The circle will not be moved from its original position. + It's equivalent to :math:`2 \cdot \pi \cdot r`. It can't be negative. Reassigning it + changes the radius to :math:`r = \frac{circumference}{2\pi}` producing a circle with + matching circumference. .. versionadded:: 2.4.0 .. ## Circle.circumference ## - **Circle Methods** + .. attribute:: top - ---- + | :sl:`top coordinate of the circle` + | :sg:`top -> (float, float)` - .. method:: collidepoint + It's a tuple containing the `x` and `y` coordinates that represent the top + of the circle. + Reassigning it moves the circle to the new position. The radius will not be affected. - | :sl:`test if a point is inside the circle` - | :sg:`collidepoint((x, y), /) -> bool` - | :sg:`collidepoint(x, y, /) -> bool` - | :sg:`collidepoint(vector2, /) -> bool` + .. versionadded:: 2.5.2 - The `collidepoint` method tests whether a given point is inside the `Circle` - (including the edge of the `Circle`). It takes a tuple of (x, y) coordinates, two - separate x and y coordinates, or a `Vector2` object as its argument, and returns - `True` if the point is inside the `Circle`, `False` otherwise. + .. ## Circle.top ## - .. versionadded:: 2.4.0 + .. attribute:: bottom - .. ## Circle.collidepoint ## + | :sl:`bottom coordinate of the circle` + | :sg:`bottom -> (float, float)` - .. method:: collidecircle + It's a tuple containing the `x` and `y` coordinates that represent the bottom + of the circle. + Reassigning it moves the circle to the new position. The radius will not be affected. - | :sl:`test if two circles collide` - | :sg:`collidecircle(circle, /) -> bool` - | :sg:`collidecircle(x, y, radius, /) -> bool` - | :sg:`collidecircle((x, y), radius, /) -> bool` + .. versionadded:: 2.5.2 - The `collidecircle` method tests whether two `Circle` objects overlap. It takes either - a `Circle` object, a tuple of (x, y) coordinates and a radius, or separate x and y - coordinates and a radius as its argument, and returns `True` if any portion of the two - `Circle` objects overlap, `False` otherwise. + .. ## Circle.bottom ## - .. note:: - If this method is called with a `Circle` object that is the same as the `Circle` - it is called on, it will always return `True`. + .. attribute:: left - .. versionadded:: 2.4.0 + | :sl:`left coordinate of the circle` + | :sg:`left -> (float, float)` - .. ## Circle.collidecircle ## + It's a tuple containing the `x` and `y` coordinates that represent the left + of the circle. + Reassigning it moves the circle to the new position. The radius will not be affected. - .. method:: move + .. versionadded:: 2.5.2 - | :sl:`moves the circle by a given amount` - | :sg:`move((x, y), /) -> Circle` - | :sg:`move(x, y, /) -> Circle` - | :sg:`move(vector2, /) -> Circle` + .. ## Circle.left ## - The `move` method allows you to create a new `Circle` object that is moved by a given - offset from the original `Circle`. This is useful if you want to move a `Circle` without - modifying the original. The move method takes either a tuple of (x, y) coordinates, - two separate x and y coordinates, or a `Vector2` object as its argument, and returns - a new `Circle` object with the updated position. + .. attribute:: right - .. note:: - This method is equivalent(behaviour wise) to the following code: + | :sl:`right coordinate of the circle` + | :sg:`right -> (float, float)` - .. code-block:: python + It's a tuple containing the `x` and `y` coordinates that represent the right + of the circle. + Reassigning it moves the circle to the new position. The radius will not be affected. - Circle((circle.x + x, circle.y + y), circle.r) + .. versionadded:: 2.5.2 - .. versionadded:: 2.5.0 + .. ## Circle.right ## - .. ## Circle.move ## + **Circle Methods** - .. method:: move_ip + ---- - | :sl:`moves the circle by a given amount, in place` - | :sg:`move_ip((x, y), /) -> None` - | :sg:`move_ip(x, y, /) -> None` - | :sg:`move_ip(vector2, /) -> None` + .. method:: collidepoint - The `move_ip` method is similar to the move method, but it moves the `Circle` in place, - modifying the original `Circle` object. This method takes the same types of arguments - as move, and it always returns None. + | :sl:`tests if a point is inside the circle` + | :sg:`collidepoint((x, y), /) -> bool` + | :sg:`collidepoint(x, y, /) -> bool` + | :sg:`collidepoint(vector2, /) -> bool` - .. note:: - This method is equivalent(behaviour wise) to the following code: + Returns `True` if the given point is inside this `Circle` (edge included), `False` otherwise. + It takes a tuple of (x, y) coordinates, two separate x and y coordinates, or a `Vector2` + object as its argument. - .. code-block:: python + .. versionadded:: 2.4.0 - circle.x += x - circle.y += y + .. ## Circle.collidepoint ## - .. versionadded:: 2.5.0 + .. method:: collidecircle - .. ## Circle.move_ip ## + | :sl:`tests if a circle collides with this circle` + | :sg:`collidecircle(circle, /) -> bool` + | :sg:`collidecircle(x, y, radius, /) -> bool` + | :sg:`collidecircle((x, y), radius, /) -> bool` + | :sg:`collidecircle(vector2, radius, /) -> bool` + + Returns `True` if the given circle intersects with this `Circle`, `False` otherwise. + It takes either a `Circle` object, a tuple of (x, y) coordinates and a radius, or separate x and y + coordinates and a radius as its argument. + + .. note:: + Calling this method with this circle as the argument will always return `True`. + + .. versionadded:: 2.4.0 + + .. ## Circle.collidecircle ## .. method:: colliderect - | :sl:`checks if a rectangle intersects the circle` + | :sl:`tests if a rectangle collides with this circle` | :sg:`colliderect(rect, /) -> bool` | :sg:`colliderect((x, y, width, height), /) -> bool` | :sg:`colliderect(x, y, width, height, /) -> bool` | :sg:`colliderect((x, y), (width, height), /) -> bool` + | :sg:`colliderect(vector2, (width, height), /) -> bool` - The `colliderect` method tests whether a given rectangle intersects the `Circle`. It - takes either a `Rect` object, a tuple of (x, y, width, height) coordinates, or separate - x, y coordinates and width, height as its argument. Returns `True` if any portion - of the rectangle overlaps with the `Circle`, `False` otherwise. + Returns `True` if the given rectangle intersects with this `Circle`, `False` otherwise. + Takes either a `Rect` object, a tuple of (x, y, width, height) coordinates, or separate + x, y coordinates and width, height as its argument. .. versionadded:: 2.4.0 - + .. ## Circle.colliderect ## .. method:: collideswith - | :sl:`check if a shape or point collides with the circle` + | :sl:`tests if a shape or point collides with this circle` | :sg:`collideswith(circle, /) -> bool` | :sg:`collideswith(rect, /) -> bool` | :sg:`collideswith((x, y), /) -> bool` | :sg:`collideswith(vector2, /) -> bool` - The `collideswith` method checks if a shape or point overlaps with a `Circle` object. - It takes a single argument which can be a `Circle`, `Rect`, `FRect`, or a point. - It returns `True` if there's an overlap, and `False` otherwise. + Returns `True` if the given shape or point intersects with this `Circle`, `False` otherwise. + The shape can be a `Circle`, `Rect`, `FRect`. .. note:: The shape argument must be an actual shape object (`Circle`, `Rect`, or `FRect`). @@ -283,17 +269,58 @@ .. ## Circle.collideswith ## + .. method:: collidelist + + | :sl:`test if a list of objects collide with the circle` + | :sg:`collidelist(colliders) -> int` + + The `collidelist` method tests whether a given list of shapes or points collides + (overlaps) with this `Circle` object. The function takes in a single argument, which + must be a list of `Circle`, `Rect`, `FRect`, or a point. The function returns the index + of the first shape or point in the list that collides with the `Circle` object, or + -1 if there is no collision. + + .. note:: + The shapes must be actual shape objects, such as `Circle`, `Rect` or `FRect` + instances. It is not possible to pass a tuple or list of coordinates representing + the shape as an argument (except for a point), because the shape type can't be + determined from the coordinates alone. + + .. versionadded:: 2.5.2 + + .. ## Circle.collidelist ## + + .. method:: collidelistall + + | :sl:`test if all objects in a list collide with the circle` + | :sg:`collidelistall(colliders) -> list` + + The `collidelistall` method tests whether a given list of shapes or points collides + (overlaps) with this `Circle` object. The function takes in a single argument, which + must be a list of `Circle`, `Rect`, `FRect`, or a point. The function returns a list + containing the indices of all the shapes or points in the list that collide with + the `Circle` object, or an empty list if there is no collision. + + .. note:: + The shapes must be actual shape objects, such as `Circle`, `Rect` or `FRect` + instances. It is not possible to pass a tuple or list of coordinates representing + the shape as an argument (except for a point), because the shape type can't be + determined from the coordinates alone. + + .. versionadded:: 2.5.2 + + .. ## Circle.collidelistall ## + .. method:: contains - | :sl:`check if a shape or point is inside the circle` + | :sl:`tests if a shape or point is inside the circle` | :sg:`contains(circle, /) -> bool` | :sg:`contains(rect, /) -> bool` | :sg:`contains((x, y), /) -> bool` | :sg:`contains(vector2, /) -> bool` - Checks whether a given shape or point is completely contained within the `Circle`. - Takes a single argument which can be a `Circle`, `Rect`, `FRect`, or a point. - Returns `True` if the shape or point is completely contained, and `False` otherwise. + Returns `True` if the shape or point is completely contained within this `Circle`, `False` otherwise. + The shape can be a `Circle`, `Rect`, `FRect`. .. note:: The shape argument must be an actual shape object (`Circle`, `Rect`, or `FRect`). @@ -304,24 +331,83 @@ .. ## Circle.contains ## + .. method:: move + + | :sl:`moves the circle by a given amount` + | :sg:`move((x, y), /) -> Circle` + | :sg:`move(x, y, /) -> Circle` + | :sg:`move(vector2, /) -> Circle` + + Returns a copy of this `Circle` moved by the given amounts. + Takes either a tuple of (x, y) coordinates, two separate x and y coordinates, + or a `Vector2` object as its argument. + + This method is equivalent to the following code: + + .. code-block:: python + + Circle((circle.x + x, circle.y + y), circle.r) + + .. versionadded:: 2.5.0 + + .. ## Circle.move ## + + .. method:: move_ip + + | :sl:`moves the circle by a given amount, in place` + | :sg:`move_ip((x, y), /) -> None` + | :sg:`move_ip(x, y, /) -> None` + | :sg:`move_ip(vector2, /) -> None` + + Moves this `Circle` in place by the given amounts. + Takes the same types of arguments as :meth:`move` and it always returns `None`. + + This method is equivalent to the following code: + + .. code-block:: python + + circle.x += x + circle.y += y + + .. versionadded:: 2.5.0 + + .. ## Circle.move_ip ## + + .. method:: intersect + + | :sl:`finds intersections between the circle and a shape` + | :sg:`intersect(circle, /) -> list` + + Finds and returns a list of intersection points between the circle and another shape. + The other shape must be a `Circle` object. + If the circle does not intersect or has infinite intersections, an empty list is returned. + + .. note:: + The shape argument must be an instance of the `Circle` class. + Passing a tuple or list of coordinates representing the shape is not supported, + as the type of shape cannot be determined from coordinates alone. + + .. versionadded:: 2.5.2 + + .. ## Circle.intersect ## + .. method:: update | :sl:`updates the circle position and radius` | :sg:`update((x, y), radius, /) -> None` | :sg:`update(x, y, radius, /) -> None` + | :sg:`update(vector2, radius, /) -> None` - The `update` method allows you to set the position and radius of a `Circle` object in - place. This method takes either a tuple of (x, y) coordinates, two separate x and - y coordinates, and a radius as its arguments, and it always returns `None`. + Sets the position and radius of this `Circle` to the provided values. + It always returns `None`. - .. note:: - This method is equivalent(behaviour wise) to the following code: + This method is equivalent to the following code: - .. code-block:: python + .. code-block:: python - circle.x = x - circle.y = y - circle.r = radius + circle.x = x + circle.y = y + circle.r = radius .. versionadded:: 2.4.0 @@ -333,10 +419,9 @@ | :sg:`rotate(angle, rotation_point=Circle.center, /) -> Circle` | :sg:`rotate(angle, /) -> Circle` - Returns a new `Circle` that is rotated by the specified angle around a point. - A positive angle rotates the circle clockwise, while a negative angle rotates it counter-clockwise. Angles should be specified in degrees. - The rotation point can be a `tuple`, `list`, or `Vector2`. - If no rotation point is given, the circle will be rotated around its center. + Returns a copy of this `Circle` rotated by the specified angle (in degrees) around a point. + Positive angles rotate the circle clockwise, counter-clockwise otherwise. + The rotation point is optional and defaults to the circle's center. .. versionadded:: 2.5.0 @@ -348,11 +433,9 @@ | :sg:`rotate_ip(angle, rotation_point=Circle.center, /) -> None` | :sg:`rotate_ip(angle, /) -> None` - - This method rotates the circle by a specified angle around a point. - A positive angle rotates the circle clockwise, while a negative angle rotates it counter-clockwise. Angles should be specified in degrees. - The rotation point can be a `tuple`, `list`, or `Vector2`. - If no rotation point is given, the circle will be rotated around its center. + Rotates the circle by a specified angle (in degrees) around a point. + Positive angles rotate the circle clockwise, counter-clockwise otherwise. + The rotation point is optional and defaults to the circle's center. .. versionadded:: 2.5.0 @@ -360,20 +443,16 @@ .. method:: as_rect - | :sl:`returns the smallest pygame.Rect object that contains the circle` + | :sl:`returns the smallest Rect containing the circle` | :sg:`as_rect() -> Rect` - The `as_rect` method returns a `pygame.Rect` object that represents the smallest - rectangle that completely contains the `Circle` object. This means that the `Rect` - object returned by as_rect will have dimensions such that it completely encloses - the `Circle`, with no part of the `Circle` extending outside of the `Rect`. + Returns the smallest `pygame.Rect` object containing this `Circle`. - .. note:: - This method is equivalent(behaviour wise) to the following code: + This method is equivalent to the following code: - .. code-block:: python + .. code-block:: python - Rect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2) + Rect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2) .. versionadded:: 2.5.0 @@ -381,20 +460,16 @@ .. method:: as_frect - | :sl:`returns the smallest pygame.FRect object that contains the circle` + | :sl:`returns the smallest FRect containing the circle` | :sg:`as_frect() -> FRect` - The `as_frect` method returns a `pygame.FRect` object that represents the smallest - rectangle that completely contains the `Circle` object. This means that the `FRect` - object returned by as_rect will have dimensions such that it completely encloses - the `Circle`, with no part of the `Circle` extending outside of the `FRect`. + Returns the smallest `pygame.FRect` object containing this `Circle`. - .. note:: - This method is equivalent(behaviour wise) to the following code: + This method is equivalent to the following code: - .. code-block:: python + .. code-block:: python - FRect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2) + FRect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2) .. versionadded:: 2.5.0 @@ -402,15 +477,13 @@ .. method:: copy - | :sl:`returns a copy of the circle` + | :sl:`copies the circle` | :sg:`copy() -> Circle` - The `copy` method returns a new `Circle` object having the same position and radius - as the original `Circle` object. The function takes no arguments and returns the - new `Circle` object. + Returns a copy of this `Circle`. .. versionadded:: 2.4.0 .. ## Circle.copy ## - .. ## pygame.Circle ## \ No newline at end of file + .. ## pygame.Circle ## diff --git a/docs/reST/ref/image.rst b/docs/reST/ref/image.rst index f964f86642..65438a9e82 100644 --- a/docs/reST/ref/image.rst +++ b/docs/reST/ref/image.rst @@ -63,7 +63,7 @@ following formats. * ``PNG`` * ``TGA`` - + ``JPEG`` and ``JPG``, as well as ``TIF`` and ``TIFF`` refer to the same file format @@ -228,13 +228,15 @@ following formats. * ``RGBA``, 32-bit image with an alpha channel * ``ARGB``, 32-bit image with alpha channel first - - * ``BGRA``, 32-bit image with alpha channel, red and blue channels swapped + + * ``BGRA``, 32-bit image with alpha channel, red and blue channels swapped + + * ``ABGR``, 32-bit image with alpha channel, reverse order * ``RGBA_PREMULT``, 32-bit image with colors scaled by alpha channel * ``ARGB_PREMULT``, 32-bit image with colors scaled by alpha channel, alpha channel first - + The 'pitch' argument can be used to specify the pitch/stride per horizontal line of the image in bytes. It must be equal to or greater than how many bytes the pixel data of each horizontal line in the image bytes occupies without any @@ -242,12 +244,13 @@ following formats. the same size as how many bytes the pure pixel data of each horizontal line takes. .. note:: The use of this function is recommended over :func:`tostring` as of pygame 2.1.3. - This function was introduced so it matches nicely with other + This function was introduced so it matches nicely with other libraries (PIL, numpy, etc), and with people's expectations. - .. versionadded:: 2.1.3 + .. versionadded:: 2.1.3 .. versionchanged:: 2.2.0 Now supports keyword arguments. .. versionchanged:: 2.5.0 Added a 'pitch' argument. + .. versionchanged:: 2.5.1 Added support for ABGR image format .. ## pygame.image.tobytes ## @@ -279,14 +282,14 @@ following formats. The 'pitch' argument can be used specify the pitch/stride per horizontal line of the image bytes in bytes. It must be equal to or greater than how many bytes the pixel data of each horizontal line in the image bytes occupies without any - extra padding. By default, it is ``-1``, which means that the pitch/stride is + extra padding. By default, it is ``-1``, which means that the pitch/stride is the same size as how many bytes the pure pixel data of each horizontal line takes. See the :func:`pygame.image.frombuffer()` method for a potentially faster way to transfer images into pygame. .. note:: The use of this function is recommended over :func:`fromstring` as of pygame 2.1.3. - This function was introduced so it matches nicely with other + This function was introduced so it matches nicely with other libraries (PIL, numpy, etc), and with people's expectations. .. versionadded:: 2.1.3 @@ -327,7 +330,7 @@ following formats. The 'pitch' argument can be used specify the pitch/stride per horizontal line of the image buffer in bytes. It must be equal to or greater than how many bytes the pixel data of each horizontal line in the image buffer occupies without any - extra padding. By default, it is ``-1``, which means that the pitch/stride is + extra padding. By default, it is ``-1``, which means that the pitch/stride is the same size as how many bytes the pure pixel data of each horizontal line takes. .. versionadded:: 2.1.3 BGRA format diff --git a/docs/reST/ref/joystick.rst b/docs/reST/ref/joystick.rst index bef725240d..0e89c13dcf 100644 --- a/docs/reST/ref/joystick.rst +++ b/docs/reST/ref/joystick.rst @@ -85,7 +85,7 @@ variable. See :ref:`environment variables ` for more deta | :sl:`Returns the number of joysticks.` | :sg:`get_count() -> count` - Return the number of joystick devices on the system. The count will be ``0`` + Return the number of joystick devices on the system. The count will be ``0`` if there are no joysticks on the system. When you create Joystick objects using ``Joystick(id)``, you pass an integer @@ -107,7 +107,7 @@ variable. See :ref:`environment variables ` for more deta .. versionchangedold:: 2.0.0 Joystick objects are now opened immediately on creation. - .. versionchanged:: 2.1.4 This class is also available through the ``pygame.Joystick`` + .. versionchanged:: 2.1.4 This class is also available through the ``pygame.Joystick`` alias. .. method:: init @@ -158,7 +158,7 @@ variable. See :ref:`environment variables ` for more deta .. deprecatedold:: 2.0.0 The original device index is not useful in pygame 2. Use - :meth:`.get_instance_id` instead. + :meth:`.get_instance_id` instead. .. method:: get_instance_id() -> int @@ -215,13 +215,13 @@ variable. See :ref:`environment variables ` for more deta two for the position. Controls like rudders and throttles are treated as additional axes. - The ``pygame.JOYAXISMOTION`` events will be in the range from ``-1.0`` - to ``1.0``. A value of ``0.0`` means the axis is centered. Gamepad devices - will usually be ``-1``, ``0``, or ``1`` with no values in between. Older - analog joystick axes will not always use the full ``-1`` to ``1`` range, - and the centered value will be some area around ``0``. - - Analog joysticks usually have a bit of noise in their axis, which will + The ``pygame.JOYAXISMOTION`` events will be in the range from ``-1.0`` + to ``1.0``. A value of ``0.0`` means the axis is centered. Gamepad devices + will usually be ``-1``, ``0``, or ``1`` with no values in between. Older + analog joystick axes will not always use the full ``-1`` to ``1`` range, + and the centered value will be some area around ``0``. + + Analog joysticks usually have a bit of noise in their axis, which will generate a lot of rapid small motion events. .. ## Joystick.get_numaxes ## @@ -232,9 +232,9 @@ variable. See :ref:`environment variables ` for more deta | :sg:`get_axis(axis_number, /) -> float` Returns the current position of a joystick axis. The value will range - from ``-1`` to ``1`` with a value of ``0`` being centered. You may want - to take into account some tolerance to handle jitter, and joystick drift - may keep the joystick from centering at ``0`` or using the full range of + from ``-1`` to ``1`` with a value of ``0`` being centered. You may want + to take into account some tolerance to handle jitter, and joystick drift + may keep the joystick from centering at ``0`` or using the full range of position values. The axis number must be an integer from ``0`` to ``get_numaxes() - 1``. @@ -302,8 +302,8 @@ variable. See :ref:`environment variables ` for more deta input. The ``pygame.JOYHATMOTION`` event is generated when the hat changes - position. The ``position`` attribute for the event contains a pair of - values that are either ``-1``, ``0``, or ``1``. A position of ``(0, 0)`` + position. The ``position`` attribute for the event contains a pair of + values that are either ``-1``, ``0``, or ``1``. A position of ``(0, 0)`` means the hat is centered. .. ## Joystick.get_numhats ## @@ -316,10 +316,10 @@ variable. See :ref:`environment variables ` for more deta Returns the current position of a position hat. The position is given as two values representing the ``x`` and ``y`` position for the hat. ``(0, 0)`` means centered. A value of ``-1`` means left/down and a value of ``1`` means - right/up: so ``(-1, 0)`` means left; ``(1, 0)`` means right; ``(0, 1)`` means + right/up: so ``(-1, 0)`` means left; ``(1, 0)`` means right; ``(0, 1)`` means up; ``(1, 1)`` means upper-right; etc. - This value is digital, ``i.e.``, each coordinate can be ``-1``, ``0`` or ``1`` + This value is digital, ``i.e.``, each coordinate can be ``-1``, ``0`` or ``1`` but never in-between. The hat number must be between ``0`` and ``get_numhats() - 1``. @@ -403,7 +403,7 @@ After SDL 2.24.0, The controller is recognized as "Nintendo Switch Joy-Con (L)". L Button 17 Button 14 ZL Button 19 Button 15 -Reference : D-pad Up points toward SL and SR buttons. +Reference : D-pad Up points toward SL and SR buttons. * **Hat/JoyStick**:: diff --git a/docs/reST/ref/key.rst b/docs/reST/ref/key.rst index d58adf034c..8feba790fb 100644 --- a/docs/reST/ref/key.rst +++ b/docs/reST/ref/key.rst @@ -266,10 +266,10 @@ for ``KMOD_NONE``, which should be compared using equals ``==``). For example: translate these pushed keys into a fully translated character value. See the ``pygame.KEYDOWN`` events on the :mod:`pygame.event` queue for this functionality. - + .. versionchanged:: 2.1.4 The collection of bools returned by ``get_pressed`` can not be iterated - over because the indexes of the internal tuple does not correspond to the + over because the indexes of the internal tuple does not correspond to the keycodes. .. ## pygame.key.get_pressed ## @@ -298,7 +298,7 @@ for ``KMOD_NONE``, which should be compared using equals ``==``). For example: ``KEYDOWN`` events :: - + if pygame.key.get_just_pressed()[pygame.K_b]: print("B key just pressed") @@ -310,7 +310,7 @@ for ``KMOD_NONE``, which should be compared using equals ``==``). For example: | :sl:`returns a pygame.key.ScancodeWrapper containing the most recent key releases` | :sg:`get_just_pressed() -> bools` - + Returns a mapping from key codes to booleans indicating which keys were newly released as of the last time events were processed. This can be used as a convenience function to detect keys that were released "this frame." @@ -325,7 +325,7 @@ for ``KMOD_NONE``, which should be compared using equals ``==``). For example: ``KEYUP`` events. :: - + if pygame.key.get_just_released()[pygame.K_b]: print("B key just released") diff --git a/docs/reST/ref/math.rst b/docs/reST/ref/math.rst index 4d93a46667..e359a4b899 100644 --- a/docs/reST/ref/math.rst +++ b/docs/reST/ref/math.rst @@ -11,13 +11,13 @@ The pygame math module currently provides Vector classes in two and three dimensions, ``Vector2`` and ``Vector3`` respectively. -They support the following numerical operations: ``vec + vec``, ``vec - vec``, -``vec * number``, ``number * vec``, ``vec / number``, ``vec // number``, ``vec += vec``, -``vec -= vec``, ``vec *= number``, ``vec /= number``, ``vec //= number``, ``round(vec, ndigits=0)``. +They support the following numerical operations: ``vec + vec``, ``vec - vec``, +``vec * number``, ``number * vec``, ``vec / number``, ``vec // number``, ``vec += vec``, +``vec -= vec``, ``vec *= number``, ``vec /= number``, ``vec //= number``, ``round(vec, ndigits=0)``. All these operations will be performed elementwise. -In addition ``vec * vec`` will perform a scalar-product (a.k.a. dot-product). -If you want to multiply every element from vector v with every element from +In addition ``vec * vec`` will perform a scalar-product (a.k.a. dot-product). +If you want to multiply every element from vector v with every element from vector w you can use the elementwise method: ``v.elementwise() * w`` The coordinates of a vector can be retrieved or set using attributes or @@ -145,11 +145,11 @@ Multiple coordinates can be set using slices or swizzling Example: .. code-block:: python - + > value = 50 > pygame.math.remap(0, 100, 0, 200, value) > 100.0 - + .. versionadded:: 2.5.0 @@ -167,8 +167,8 @@ Multiple coordinates can be set using slices or swizzling Some general information about the ``Vector2`` class. - .. versionchanged:: 2.1.3 - Inherited methods of vector subclasses now correctly return an instance of the + .. versionchanged:: 2.1.3 + Inherited methods of vector subclasses now correctly return an instance of the subclass instead of the superclass .. method:: dot @@ -224,7 +224,7 @@ Multiple coordinates can be set using slices or swizzling | :sg:`length_squared() -> float` calculates the Euclidean length of the vector which follows from the - Pythagorean theorem: ``vec.length_squared() == vec.x**2 + vec.y**2``. + Pythagorean theorem: ``vec.length_squared() == vec.x**2 + vec.y**2``. This is faster than ``vec.length()`` because it avoids the square root. .. ## Vector2.length_squared ## @@ -234,7 +234,7 @@ Multiple coordinates can be set using slices or swizzling | :sl:`returns a vector with the same direction but length 1.` | :sg:`normalize() -> Vector2` - Returns a new vector that has ``length`` equal to ``1`` and the same + Returns a new vector that has ``length`` equal to ``1`` and the same direction as self. .. ## Vector2.normalize ## @@ -244,7 +244,7 @@ Multiple coordinates can be set using slices or swizzling | :sl:`normalizes the vector in place so that its length is 1.` | :sg:`normalize_ip() -> None` - Normalizes the vector so that it has ``length`` equal to ``1``. + Normalizes the vector so that it has ``length`` equal to ``1``. The direction of the vector is not changed. .. ## Vector2.normalize_ip ## @@ -254,7 +254,7 @@ Multiple coordinates can be set using slices or swizzling | :sl:`tests if the vector is normalized i.e. has length == 1.` | :sg:`is_normalized() -> Bool` - Returns True if the vector has ``length`` equal to ``1``. Otherwise + Returns True if the vector has ``length`` equal to ``1``. Otherwise it returns ``False``. .. ## Vector2.is_normalized ## @@ -265,7 +265,7 @@ Multiple coordinates can be set using slices or swizzling | :sg:`scale_to_length(float, /) -> None` Scales the vector so that it has the given length. The direction of the - vector is not changed. You can also scale to length ``0``. If the vector + vector is not changed. You can also scale to length ``0``. If the vector is the zero vector (i.e. has length ``0`` thus no direction) a ``ValueError`` is raised. @@ -343,7 +343,7 @@ Multiple coordinates can be set using slices or swizzling Returns a Vector which is a linear interpolation between self and the given Vector. The second parameter determines how far between self and - other the result is going to be. It must be a value between ``0`` and ``1`` + other the result is going to be. It must be a value between ``0`` and ``1`` where ``0`` means self and ``1`` means other will be returned. .. ## Vector2.lerp ## @@ -462,7 +462,7 @@ Multiple coordinates can be set using slices or swizzling | :sg:`angle_to(Vector2, /) -> float` Returns the angle from self to the passed ``Vector2`` that would rotate self - to be aligned with the passed ``Vector2`` without crossing over the negative + to be aligned with the passed ``Vector2`` without crossing over the negative x-axis. .. figure:: code_examples/angle_to.png @@ -477,7 +477,7 @@ Multiple coordinates can be set using slices or swizzling | :sl:`returns a tuple with radial distance and azimuthal angle.` | :sg:`as_polar() -> (r, phi)` - Returns a tuple ``(r, phi)`` where r is the radial distance, and phi + Returns a tuple ``(r, phi)`` where r is the radial distance, and phi is the azimuthal angle. .. ## Vector2.as_polar ## @@ -497,14 +497,14 @@ Multiple coordinates can be set using slices or swizzling | :sl:`projects a vector onto another.` | :sg:`project(Vector2, /) -> Vector2` - Returns the projected vector. This is useful for collision detection in finding the components in a certain direction (e.g. in direction of the wall). + Returns the projected vector. This is useful for collision detection in finding the components in a certain direction (e.g. in direction of the wall). For a more detailed explanation see `Wikipedia `_. .. versionaddedold:: 2.0.2 .. ## Vector2.project ## - + .. method:: copy | :sl:`Returns a copy of itself.` @@ -515,7 +515,7 @@ Multiple coordinates can be set using slices or swizzling .. versionaddedold:: 2.1.1 .. ## Vector2.copy ## - + .. method:: clamp_magnitude @@ -526,8 +526,8 @@ Multiple coordinates can be set using slices or swizzling **Experimental:** feature still in development available for testing and feedback. It may change. `Please leave clamp_magnitude feedback with authors `_ - Returns a new copy of a vector with the magnitude clamped between - ``max_length`` and ``min_length``. If only one argument is passed, it is + Returns a new copy of a vector with the magnitude clamped between + ``max_length`` and ``min_length``. If only one argument is passed, it is taken to be the ``max_length`` This function raises ``ValueError`` if ``min_length`` is greater than @@ -537,12 +537,12 @@ Multiple coordinates can be set using slices or swizzling .. versionchanged:: 2.4.0 It is now possible to use ``clamp_magnitude`` on a zero-vector as long as ``min_length`` is unspecified or 0. - + .. note:: Before pygame-ce 2.4.0, attempting to clamp a zero vector would always raise a ``ValueError`` .. ## Vector2.clamp_magnitude ## - + .. method:: clamp_magnitude_ip @@ -560,7 +560,7 @@ Multiple coordinates can be set using slices or swizzling .. versionchanged:: 2.4.0 It is now possible to use ``clamp_magnitude`` on a zero-vector as long as ``min_length`` is unspecified or 0. - + .. note:: Before pygame-ce 2.4.0, attempting to clamp a zero vector would always raise a ``ValueError`` @@ -583,11 +583,11 @@ Multiple coordinates can be set using slices or swizzling .. ## Vector2.update ## - + .. attribute:: epsilon - + | :sl:`Determines the tolerance of vector calculations.` - + Both Vector classes have a value named ``epsilon`` that defaults to ``1e-6``. This value acts as a numerical margin in various methods to account for floating point arithmetic errors. Specifically, ``epsilon`` is used in the following places: @@ -613,7 +613,7 @@ Multiple coordinates can be set using slices or swizzling print(v == u) # >> False You'll probably never have to change ``epsilon`` from the default value, but in rare situations you might - find that either the margin is too large or too small, in which case changing ``epsilon`` slightly + find that either the margin is too large or too small, in which case changing ``epsilon`` slightly might help you out. @@ -631,8 +631,8 @@ Multiple coordinates can be set using slices or swizzling Some general information about the Vector3 class. - .. versionchanged:: 2.1.3 - Inherited methods of vector subclasses now correctly return an instance of the + .. versionchanged:: 2.1.3 + Inherited methods of vector subclasses now correctly return an instance of the subclass instead of the superclass .. method:: dot @@ -667,7 +667,7 @@ Multiple coordinates can be set using slices or swizzling | :sg:`magnitude_squared() -> float` calculates the magnitude of the vector which follows from the - theorem: + theorem: ``vec.magnitude_squared() == vec.x**2 + vec.y**2 + vec.z**2``. This is faster than ``vec.magnitude()`` because it avoids the square root. @@ -680,7 +680,7 @@ Multiple coordinates can be set using slices or swizzling | :sg:`length() -> float` calculates the Euclidean length of the vector which follows from the - Pythagorean theorem: + Pythagorean theorem: ``vec.length() == math.sqrt(vec.x**2 + vec.y**2 + vec.z**2)`` .. ## Vector3.length ## @@ -691,8 +691,8 @@ Multiple coordinates can be set using slices or swizzling | :sg:`length_squared() -> float` calculates the Euclidean length of the vector which follows from the - Pythagorean theorem: - ``vec.length_squared() == vec.x**2 + vec.y**2 + vec.z**2``. + Pythagorean theorem: + ``vec.length_squared() == vec.x**2 + vec.y**2 + vec.z**2``. This is faster than ``vec.length()`` because it avoids the square root. .. ## Vector3.length_squared ## @@ -702,7 +702,7 @@ Multiple coordinates can be set using slices or swizzling | :sl:`returns a vector with the same direction but length 1.` | :sg:`normalize() -> Vector3` - Returns a new vector that has ``length`` equal to ``1`` and the same + Returns a new vector that has ``length`` equal to ``1`` and the same direction as self. .. ## Vector3.normalize ## @@ -712,7 +712,7 @@ Multiple coordinates can be set using slices or swizzling | :sl:`normalizes the vector in place so that its length is 1.` | :sg:`normalize_ip() -> None` - Normalizes the vector so that it has ``length`` equal to ``1``. The + Normalizes the vector so that it has ``length`` equal to ``1``. The direction of the vector is not changed. .. ## Vector3.normalize_ip ## @@ -722,7 +722,7 @@ Multiple coordinates can be set using slices or swizzling | :sl:`tests if the vector is normalized i.e. has length == 1.` | :sg:`is_normalized() -> Bool` - Returns True if the vector has ``length`` equal to ``1``. Otherwise it + Returns True if the vector has ``length`` equal to ``1``. Otherwise it returns ``False``. .. ## Vector3.is_normalized ## @@ -733,7 +733,7 @@ Multiple coordinates can be set using slices or swizzling | :sg:`scale_to_length(float, /) -> None` Scales the vector so that it has the given length. The direction of the - vector is not changed. You can also scale to length ``0``. If the vector + vector is not changed. You can also scale to length ``0``. If the vector is the zero vector (i.e. has length ``0`` thus no direction) a ``ValueError`` is raised. @@ -811,7 +811,7 @@ Multiple coordinates can be set using slices or swizzling Returns a Vector which is a linear interpolation between self and the given Vector. The second parameter determines how far between self an - other the result is going to be. It must be a value between ``0`` and + other the result is going to be. It must be a value between ``0`` and ``1``, where ``0`` means self and ``1`` means other will be returned. .. ## Vector3.lerp ## @@ -1096,7 +1096,7 @@ Multiple coordinates can be set using slices or swizzling | :sg:`rotate_z_ip_rad(angle, /) -> None` DEPRECATED: Use rotate_z_rad_ip() instead. - + .. deprecatedold:: 2.1.1 .. ## Vector3.rotate_z_ip_rad ## @@ -1149,13 +1149,13 @@ Multiple coordinates can be set using slices or swizzling | :sl:`projects a vector onto another.` | :sg:`project(Vector3, /) -> Vector3` - Returns the projected vector. This is useful for collision detection in finding the components in a certain direction (e.g. in direction of the wall). + Returns the projected vector. This is useful for collision detection in finding the components in a certain direction (e.g. in direction of the wall). For a more detailed explanation see `Wikipedia `_. .. versionaddedold:: 2.0.2 .. ## Vector3.project ## - + .. method:: copy | :sl:`Returns a copy of itself.` @@ -1174,8 +1174,8 @@ Multiple coordinates can be set using slices or swizzling | :sg:`clamp_magnitude(max_length, /) -> Vector3` | :sg:`clamp_magnitude(min_length, max_length, /) -> Vector3` - Returns a new copy of a vector with the magnitude clamped between - ``max_length`` and ``min_length``. If only one argument is passed, it is + Returns a new copy of a vector with the magnitude clamped between + ``max_length`` and ``min_length``. If only one argument is passed, it is taken to be the ``max_length`` This function raises ``ValueError`` if ``min_length`` is greater than @@ -1185,12 +1185,12 @@ Multiple coordinates can be set using slices or swizzling .. versionchanged:: 2.4.0 It is now possible to use ``clamp_magnitude`` on a zero-vector as long as ``min_length`` is unspecified or 0. - + .. note:: Before pygame-ce 2.4.0, attempting to clamp a zero vector would always raise a ``ValueError`` .. ## Vector3.clamp_magnitude ## - + .. method:: clamp_magnitude_ip @@ -1208,7 +1208,7 @@ Multiple coordinates can be set using slices or swizzling .. versionchanged:: 2.4.0 It is now possible to use ``clamp_magnitude`` on a zero-vector as long as ``min_length`` is unspecified or 0. - + .. note:: Before pygame-ce 2.4.0, attempting to clamp a zero vector would always raise a ``ValueError`` @@ -1233,9 +1233,9 @@ Multiple coordinates can be set using slices or swizzling .. attribute:: epsilon | :sl:`Determines the tolerance of vector calculations.` - + With lengths within this number, vectors are considered equal. For more information see :attr:`pygame.math.Vector2.epsilon` - + .. ## ## .. ## pygame.math.Vector3 ## diff --git a/docs/reST/ref/mixer.rst b/docs/reST/ref/mixer.rst index 3de038983d..c5041c8eb3 100644 --- a/docs/reST/ref/mixer.rst +++ b/docs/reST/ref/mixer.rst @@ -68,7 +68,7 @@ The following file formats are supported Initialize the mixer module for Sound loading and playback. The default arguments can be overridden to provide specific audio mixing. Keyword - arguments are accepted. For backwards compatibility, argument values of + arguments are accepted. For backwards compatibility, argument values of 0 are replaced with the startup defaults, except for ``allowedchanges``, where -1 is used. (startup defaults may be changed by a :func:`pre_init` call). @@ -241,7 +241,7 @@ The following file formats are supported | :sg:`set_reserved(count, /) -> count` The mixer can reserve any number of channels that will not be automatically - selected for playback by Sounds. This means that whenever you play a Sound + selected for playback by Sounds. This means that whenever you play a Sound without specifying a channel, a reserved channel will never be used. If sounds are currently playing on the reserved channels they will not be stopped. @@ -292,7 +292,7 @@ The following file formats are supported | :sl:`get the soundfont for playing midi music` | :sg:`get_soundfont() -> paths` - This gets the soundfont filepaths as a string (each path is separated by a semi-colon) + This gets the soundfont filepaths as a string (each path is separated by a semi-colon) to be used in the playback of ``MID``, ``MIDI``, and ``KAR`` music file formats. If no soundfont is specified, the return type is ``None``. @@ -351,7 +351,7 @@ The following file formats are supported the initialize arguments for the mixer. A Unicode string can only be a file pathname. A bytes object can be either a pathname or a buffer object. Use the 'file' or 'buffer' keywords to avoid ambiguity; otherwise Sound may - guess wrong. If the array keyword is used, the object is expected to export + guess wrong. If the array keyword is used, the object is expected to export a new buffer interface (The object is checked for a buffer interface first.) The Sound object represents actual sound sample data. Methods that change @@ -375,6 +375,9 @@ The following file formats are supported :class:`pygame.mixer.Sound` keyword arguments and array interface support .. versionaddedold:: 2.0.1 pathlib.Path support on Python 3. + .. versionchanged:: 2.5.2 This class is also available through the ``pygame.Sound`` + alias. + .. method:: play | :sl:`begin sound playback` @@ -476,6 +479,18 @@ The following file formats are supported .. ## Sound.get_raw ## + .. method:: copy + + | :sl:`return a new Sound object that is a deep copy of this one` + | :sg:`copy() -> Sound` + + Return a new Sound object that is a deep copy of this one. The new Sound will + be playable just like the original. If the copy fails, a ``MemoryError`` or a + :meth:`pygame.error` exception will be raised. + + .. ## Sound.copy ## + + .. ## pygame.mixer.Sound ## .. class:: Channel @@ -574,11 +589,11 @@ The following file formats are supported Set the position (angle, distance) of a playing channel. `angle`: Angle is in degrees. - + `distance`: Range from 0 to 255. .. warning:: This function currently fails and raises a - :exc:`pygame.error` when using 7.1 surround sound. + :exc:`pygame.error` when using 7.1 surround sound. By default, the mixer module will use what the hardware is best suited for, so this leads to hardware specific exceptions when using this function. @@ -593,9 +608,9 @@ The following file formats are supported allowedchanges=pygame.AUDIO_ALLOW_FREQUENCY_CHANGE, ) pygame.init() - + .. versionadded:: 2.3.0 - + .. ## Channel.set_source_location ## .. method:: set_volume @@ -632,7 +647,7 @@ The following file formats are supported | :sl:`get the volume of the playing channel` | :sg:`get_volume() -> value` - Return the volume of the channel for the current playing sound + Return the volume of the channel for the current playing sound in the range of 0.0 to 1.0 (inclusive). This does not take into account stereo separation used by :meth:`Channel.set_volume`. The Sound object also has its own volume diff --git a/docs/reST/ref/mouse.rst b/docs/reST/ref/mouse.rst index c01a70997c..9a99fd22c6 100644 --- a/docs/reST/ref/mouse.rst +++ b/docs/reST/ref/mouse.rst @@ -15,13 +15,13 @@ When the display mode is set, the event queue will start receiving mouse events. The mouse buttons generate ``pygame.MOUSEBUTTONDOWN`` and ``pygame.MOUSEBUTTONUP`` events when they are pressed and released. These events contain a button attribute representing which button was pressed. The -mouse wheel will generate ``pygame.MOUSEBUTTONDOWN`` and -``pygame.MOUSEBUTTONUP`` events when rolled. The button will be set to 4 -when the wheel is rolled up, and to button 5 when the wheel is rolled down. -Whenever the mouse is moved it generates a ``pygame.MOUSEMOTION`` event. The -mouse movement is broken into small and accurate motion events. As the mouse -is moving many motion events will be placed on the queue. Mouse motion events -that are not properly cleaned from the event queue are the primary reason the +mouse wheel will generate ``pygame.MOUSEBUTTONDOWN`` and +``pygame.MOUSEBUTTONUP`` events when rolled. The button will be set to 4 +when the wheel is rolled up, and to button 5 when the wheel is rolled down. +Whenever the mouse is moved it generates a ``pygame.MOUSEMOTION`` event. The +mouse movement is broken into small and accurate motion events. As the mouse +is moving many motion events will be placed on the queue. Mouse motion events +that are not properly cleaned from the event queue are the primary reason the event queue fills up. If the mouse cursor is hidden, and input is grabbed to the current display the @@ -34,17 +34,17 @@ configured. **Mouse Wheel Behavior in pygame 2** There is proper functionality for mouse wheel behaviour with pygame 2 supporting -``pygame.MOUSEWHEEL`` events. The new events support horizontal and vertical -scroll movements, with signed integer values representing the amount scrolled -(``x`` and ``y``), as well as ``flipped`` direction (the set positive and -negative values for each axis is flipped). Read more about SDL2 +``pygame.MOUSEWHEEL`` events. The new events support horizontal and vertical +scroll movements, with signed integer values representing the amount scrolled +(``x`` and ``y``), as well as ``flipped`` direction (the set positive and +negative values for each axis is flipped). Read more about SDL2 input-related changes here ``_ -In pygame 2, the mouse wheel functionality can be used by listening for the -``pygame.MOUSEWHEEL`` type of an event (Bear in mind they still emit +In pygame 2, the mouse wheel functionality can be used by listening for the +``pygame.MOUSEWHEEL`` type of an event (Bear in mind they still emit ``pygame.MOUSEBUTTONDOWN`` events like in pygame 1.x, as well). -When this event is triggered, a developer can access the appropriate ``Event`` object -with ``pygame.event.get()``. The object can be used to access data about the mouse +When this event is triggered, a developer can access the appropriate ``Event`` object +with ``pygame.event.get()``. The object can be used to access data about the mouse scroll, such as ``which`` (it will tell you what exact mouse device trigger the event). .. code-block:: python @@ -57,7 +57,7 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the pygame.init() screen = pygame.display.set_mode((640, 480)) clock = pygame.time.Clock() - + def main(): while True: for event in pygame.event.get(): @@ -65,11 +65,11 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the pygame.quit() return elif event.type == MOUSEWHEEL: - print(event) + print(event) print(event.x, event.y) print(event.flipped) print(event.which) - # can access properties with + # can access properties with # proper notation(ex: event.y) clock.tick(60) @@ -79,32 +79,39 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the .. function:: get_pressed | :sl:`get the state of the mouse buttons` - | :sg:`get_pressed(num_buttons=3) -> (left_button, middle_button, right_button)` - | :sg:`get_pressed(num_buttons=5) -> (left_button, middle_button, right_button, x1_button, x2_button)` + | :sg:`get_pressed(num_buttons=3, desktop=False) -> (left_button, middle_button, right_button)` + | :sg:`get_pressed(num_buttons=5, desktop=False) -> (left_button, middle_button, right_button, x1_button, x2_button)` Returns a sequence of booleans representing the state of all the mouse buttons. A true value means the mouse is currently being pressed at the time of the call. - Note, to get all of the mouse events it is better to use either - ``pygame.event.wait()`` or ``pygame.event.get()`` and check all of those + To get all of the mouse events it is better to use either + ``pygame.event.wait()`` or ``pygame.event.get()`` and check all of those events to see if they are ``MOUSEBUTTONDOWN``, ``MOUSEBUTTONUP``, or - ``MOUSEMOTION``. + ``MOUSEMOTION``. Remember to call ``pygame.event.get()`` or ``pygame.event.pump()`` + before this function, otherwise it will not work as expected. - Note, that on ``X11`` some X servers use middle button emulation. When you - click both buttons ``1`` and ``3`` at the same time a ``2`` button event - can be emitted. + To support five button mice, an optional parameter ``num_buttons`` has been + added in pygame 2. When this is set to ``5``, ``button4`` and ``button5`` + are added to the returned tuple. Only ``3`` and ``5`` are valid values + for this parameter. - Note, remember to call ``pygame.event.get()`` before this function. - Otherwise it will not work as expected. + If the ``desktop`` argument is ``True`` the mouse state will be correct even + if the window has no focus. In addition since it queries the OS it does not depend + on the last event pump while being slightly slower. + + .. note:: On ``X11`` some X servers use middle button emulation. When you + click both buttons ``1`` and ``3`` at the same time a ``2`` button event + can be emitted. + + .. warning:: Due to design constraints it is impossible to retrieve the desktop + mouse state on Wayland. The normal mouse state is returned instead. - To support five button mice, an optional parameter ``num_buttons`` has been - added in pygame 2. When this is set to ``5``, ``button4`` and ``button5`` - are added to the returned tuple. Only ``3`` and ``5`` are valid values - for this parameter. - .. versionchangedold:: 2.0.0 ``num_buttons`` argument added + .. versionchanged:: 2.5.2 Added the ``desktop`` argument + .. ## pygame.mouse.get_pressed ## .. function:: get_just_pressed @@ -113,10 +120,10 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the | :sg:`get_just_pressed() -> (left_button, middle_button, right_button, x1_button, x2_button)` Very similar to :func:`pygame.mouse.get_pressed()`, returning a tuple - of length 5 with the important difference that the buttons are + of length 5 with the important difference that the buttons are True only in the frame they start being pressed. This can be convenient for checking the buttons pressed "this frame", but for more precise results - and correct ordering prefer using the pygame.MOUSEBUTTONDOWN event. + and correct ordering prefer using the ``pygame.MOUSEBUTTONDOWN`` event. The result of this function is updated when new events are processed, e.g. in :func:`pygame.event.get()` or :func:`pygame.event.pump()`. @@ -124,7 +131,7 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the .. seealso:: :func:`pygame.mouse.get_just_released()` :: - + if pygame.mouse.get_just_pressed()[0]: print("LMB just pressed") @@ -138,10 +145,10 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the | :sg:`get_just_released() -> (left_button, middle_button, right_button, x1_button, x2_button)` Similar to :func:`pygame.mouse.get_pressed()`, returning a tuple - of length 5 with the important difference that the buttons are + of length 5 with the important difference that the buttons are True only in the frame they stop being pressed. This can be convenient for checking the buttons released "this frame", but for more precise results - and correct ordering prefer using the pygame.MOUSEBUTTONUP event. + and correct ordering prefer using the ``pygame.MOUSEBUTTONUP`` event. The result of this function is updated when new events are processed, e.g. in :func:`pygame.event.get()` or :func:`pygame.event.pump()`. @@ -149,7 +156,7 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the .. seealso:: :func:`pygame.mouse.get_just_pressed()` :: - + if pygame.mouse.get_just_released()[0]: print("LMB just released") @@ -160,12 +167,20 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the .. function:: get_pos | :sl:`get the mouse cursor position` - | :sg:`get_pos() -> (x, y)` + | :sg:`get_pos(desktop=False) -> (x, y)` + + By default returns the ``x`` and ``y`` position of the mouse cursor. The position + is relative to the top-left corner of the display. The cursor position can be + located outside of the display window, but is always constrained to the screen. + + If the ``desktop`` argument is ``True``, the position will be instead relative to the + top-left corner of the primary monitor. The position might be negative or exceed + the desktop bounds if multiple monitors are present. + + .. warning:: Due to design constraints it is impossible to retrieve the desktop + mouse state on Wayland. The relative mouse position is returned instead. - Returns the ``x`` and ``y`` position of the mouse cursor. The position is - relative to the top-left corner of the display. The cursor position can be - located outside of the display window, but is always constrained to the - screen. + .. versionchanged:: 2.5.2 Added the ``desktop`` argument .. ## pygame.mouse.get_pos ## @@ -258,7 +273,7 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the Get the information about the mouse system cursor. The return value contains the same data as the arguments passed into :func:`pygame.mouse.set_cursor()`. - .. note:: Code that unpacked a get_cursor() call into + .. note:: Code that unpacked a get_cursor() call into ``size, hotspot, xormasks, andmasks`` will still work, assuming the call returns an old school type cursor. @@ -285,7 +300,7 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the Sets the relative mouse mode state. While the mouse is in relative mode, the cursor is hidden, the mouse position is constrained to the window, and pygame - will report continuous relative mouse motion even if the + will report continuous relative mouse motion even if the mouse is at the edge of the window. *This function will flush any pending mouse motion."* diff --git a/docs/reST/ref/music.rst b/docs/reST/ref/music.rst index be4682cc7f..e49177387b 100644 --- a/docs/reST/ref/music.rst +++ b/docs/reST/ref/music.rst @@ -59,18 +59,18 @@ For a complete list of supported file formats, see the :mod:`pygame.mixer` doc p This will play the loaded music stream. If the music is already playing it will be restarted. - - ``loops`` is an optional integer argument, which is ``0`` by default, which - indicates how many times to repeat the music. The music repeats indefinitely if - this argument is set to ``-1``. - - ``start`` is an optional float argument, which is ``0.0`` by default, which - denotes the position in time from which the music starts playing. The starting - position depends on the format of the music played. ``MP3`` and ``OGG`` use + + ``loops`` is an optional integer argument, which is ``0`` by default, which + indicates how many times to repeat the music. The music repeats indefinitely if + this argument is set to ``-1``. + + ``start`` is an optional float argument, which is ``0.0`` by default, which + denotes the position in time from which the music starts playing. The starting + position depends on the format of the music played. ``MP3`` and ``OGG`` use the position as time in seconds. For ``MP3`` files the start time position selected may not be accurate as things like variable bit rate encoding and ID3 - tags can throw off the timing calculations. For ``MOD`` music it is the pattern - order number. Passing a start position will raise a NotImplementedError if + tags can throw off the timing calculations. For ``MOD`` music it is the pattern + order number. Passing a start position will raise a NotImplementedError if the start position cannot be set. ``fade_ms`` is an optional integer argument, which is ``0`` by default, @@ -78,7 +78,7 @@ For a complete list of supported file formats, see the :mod:`pygame.mixer` doc p will fade up from volume level ``0.0`` to full volume (or the volume level previously set by :func:`set_volume`). The sample may end before the fade-in is complete. If the music is already streaming ``fade_ms`` is ignored. - + .. versionchangedold:: 2.0.0 Added optional ``fade_ms`` argument .. ## pygame.mixer.music.play ## @@ -90,7 +90,7 @@ For a complete list of supported file formats, see the :mod:`pygame.mixer` doc p Resets playback of the current music to the beginning. If :func:`pause` has previously been used to pause the music, the music will remain paused. - + .. note:: :func:`rewind` supports a limited number of file types and notably ``WAV`` files are NOT supported. For unsupported file types use :func:`play` which will restart the music that's already playing (note that this @@ -135,12 +135,12 @@ For a complete list of supported file formats, see the :mod:`pygame.mixer` doc p Fade out and stop the currently playing music. - The ``time`` argument denotes the integer milliseconds for which the + The ``time`` argument denotes the integer milliseconds for which the fading effect is generated. - Note, that this function blocks until the music has faded out. Calls - to :func:`fadeout` and :func:`set_volume` will have no effect during - this time. If an event was set using :func:`set_endevent` it will be + Note, that this function blocks until the music has faded out. Calls + to :func:`fadeout` and :func:`set_volume` will have no effect during + this time. If an event was set using :func:`set_endevent` it will be called after the music has faded. .. ## pygame.mixer.music.fadeout ## @@ -151,8 +151,8 @@ For a complete list of supported file formats, see the :mod:`pygame.mixer` doc p | :sg:`set_volume(volume, /) -> None` Set the volume of the music playback. - - The ``volume`` argument is a float between ``0.0`` and ``1.0`` that sets + + The ``volume`` argument is a float between ``0.0`` and ``1.0`` that sets the volume level. When new music is loaded the volume is reset to full volume. If ``volume`` is a negative value it will be ignored and the volume will remain set at the current level. If the ``volume`` argument @@ -165,7 +165,7 @@ For a complete list of supported file formats, see the :mod:`pygame.mixer` doc p | :sl:`get the music volume` | :sg:`get_volume() -> value` - Returns the current volume for the mixer. The value will be between ``0.0`` + Returns the current volume for the mixer. The value will be between ``0.0`` and ``1.0``. .. ## pygame.mixer.music.get_volume ## @@ -192,7 +192,7 @@ For a complete list of supported file formats, see the :mod:`pygame.mixer` doc p This sets the position in the music file where playback will start. The meaning of "pos", a float (or a number that can be converted to a float), depends on the music format. - + For ``MOD`` files, pos is the integer pattern number in the module. For ``OGG`` it is the absolute position, in seconds, from the beginning of the sound. For ``MP3`` files, it is the relative position, @@ -285,24 +285,24 @@ For a complete list of supported file formats, see the :mod:`pygame.mixer` doc p | :sg:`get_metadata() -> dict` | :sg:`get_metadata(filename) -> dict` | :sg:`get_metadata(fileobj, namehint="") -> dict` - - If no arguments are passed returns a dictionary containing metadata - of the currently loaded music stream, raises an exception if a music stream is not loaded. - Available keys are ``"title"``, ``"album"``, ``"artist"``, ``"copyright"``. - Values are strings containing corresponding retrieved metadata. + + If no arguments are passed returns a dictionary containing metadata + of the currently loaded music stream, raises an exception if a music stream is not loaded. + Available keys are ``"title"``, ``"album"``, ``"artist"``, ``"copyright"``. + Values are strings containing corresponding retrieved metadata. If particular metadata was not found the value is an empty string. Here is an example: ``{'title': 'Small Tone', 'album': 'Tones', 'artist': 'Audacity Generator', 'copyright': ''}`` - - Refer to the :func:`pygame.mixer.music.load` function for arguments regarding specifying a file or a file-like object - whose metadata you want to retrieve. For this function all arguments are optional, + + Refer to the :func:`pygame.mixer.music.load` function for arguments regarding specifying a file or a file-like object + whose metadata you want to retrieve. For this function all arguments are optional, however, specifying only the ``namehint`` will raise an exception. - + Since the underlying functionality was introduced in version 2.6.0 of SDL_mixer, calling this function with an older version of SDL_mixer will return a dictionary with all values being set to empty strings. You can find your version of SDL_mixer by using :func:`pygame.mixer.get_sdl_mixer_version`. .. versionadded:: 2.1.4 - - .. ## pygame.mixer.music.get_metadata ## \ No newline at end of file + + .. ## pygame.mixer.music.get_metadata ## diff --git a/docs/reST/ref/pygame.rst b/docs/reST/ref/pygame.rst index d73a2fb99c..b6266f687d 100644 --- a/docs/reST/ref/pygame.rst +++ b/docs/reST/ref/pygame.rst @@ -206,7 +206,7 @@ object instead of the module, which can be used to test for availability. .. ## pygame.encode_file_path ## .. function:: print_debug_info - + | :sl:`retrieves useful information for debugging and issue-reporting purposes` | :sg:`print_debug_info(filename=None) -> None` @@ -218,7 +218,7 @@ object instead of the module, which can be used to test for availability. .. note:: If ``pygame.freetype`` has not been initialized with :func:`pygame.init` or :func:`pygame.freetype.init`, then the linked and compiled versions of FreeType will be "Unk" since this information is not - available before initialization. + available before initialization. .. versionadded:: 2.1.4 @@ -288,12 +288,12 @@ check which version of pygame has been imported. package was built. If the identifier ends with a plus sign '+' then the package contains uncommitted changes. Please include this revision number in bug reports, especially for non-release pygame builds. - - Important note: pygame development has moved to github, this variable is + + Important note: pygame development has moved to github, this variable is obsolete now. As soon as development shifted to github, this variable started - returning an empty string ``""``. + returning an empty string ``""``. It has always been returning an empty string since ``v1.9.5``. - + .. versionchangedold:: 1.9.5 Always returns an empty string ``""``. @@ -559,6 +559,3 @@ where this is set to 0 by default. This hint only affects the windows platform, other platforms can control DPI awareness via a Window creation keyword parameter called "allow_high_dpi". - - - diff --git a/docs/reST/ref/rect.rst b/docs/reST/ref/rect.rst index a936683a10..a759f3c67e 100644 --- a/docs/reST/ref/rect.rst +++ b/docs/reST/ref/rect.rst @@ -122,7 +122,7 @@ Same as the ``Rect.move()`` method, but operates in place. .. ## Rect.move_ip ## - + .. method:: move_to | :sl:`moves the rectangle to the specified position` @@ -168,7 +168,7 @@ | :sg:`scale_by(x, y) -> Rect` Returns a new rectangle with the size scaled by the given multipliers. - The rectangle remains centered around its current center. A single + The rectangle remains centered around its current center. A single scalar or separate width and height scalars are allowed. Values above one will increase the size of the rectangle, whereas values between zero and one will decrease the size of the rectangle. @@ -415,39 +415,39 @@ .. code-block:: python :linenos: - + Rect = pygame.Rect r = Rect(0, 0, 10, 10) - + list_of_rects = [Rect(1, 1, 1, 1), Rect(2, 2, 2, 2)] indices0 = r.collidelistall(list_of_rects) - + list_of_lists = [[1, 1, 1, 1], [2, 2, 2, 2]] indices1 = r.collidelistall(list_of_lists) - + list_of_tuples = [(1, 1, 1, 1), (2, 2, 2, 2)] indices2 = r.collidelistall(list_of_tuples) - + list_of_double_tuples = [((1, 1), (1, 1)), ((2, 2), (2, 2))] indices3 = r.collidelistall(list_of_double_tuples) - + class ObjectWithRectAttribute(object): def __init__(self, r): self.rect = r - + list_of_object_with_rect_attribute = [ ObjectWithRectAttribute(Rect(1, 1, 1, 1)), ObjectWithRectAttribute(Rect(2, 2, 2, 2)), ] indices4 = r.collidelistall(list_of_object_with_rect_attribute) - + class ObjectWithCallableRectAttribute(object): def __init__(self, r): self._rect = r - + def rect(self): return self._rect - + list_of_object_with_callable_rect = [ ObjectWithCallableRectAttribute(Rect(1, 1, 1, 1)), ObjectWithCallableRectAttribute(Rect(2, 2, 2, 2)), @@ -595,7 +595,7 @@ .. versionchanged:: 2.4.0 ``values`` is now accepted as a keyword argument. Type Stub updated - to use boolean ``True`` or ``False``, but any truthy or falsy value + to use boolean ``True`` or ``False``, but any truthy or falsy value will be valid. .. ## Rect.collidedict ## @@ -618,9 +618,9 @@ .. versionchanged:: 2.4.0 ``values`` is now accepted as a keyword argument. Type Stub updated - to use boolean ``True`` or ``False``, but any truthy or falsy value + to use boolean ``True`` or ``False``, but any truthy or falsy value will be valid. .. ## Rect.collidedictall ## - .. ## pygame.Rect ## \ No newline at end of file + .. ## pygame.Rect ## diff --git a/docs/reST/ref/scrap.rst b/docs/reST/ref/scrap.rst index c1d8117c1a..e44f6e28f3 100644 --- a/docs/reST/ref/scrap.rst +++ b/docs/reST/ref/scrap.rst @@ -18,7 +18,7 @@ functions. All other functions are deprecated as of pygame 2.2.0 and will be rem in a future release of pygame. .. note:: ``scrap.put_text``, ``scrap.get_text``, and ``scrap.has_text`` use the same - clipboard as the rest of the current API, but only strings are compatible with the + clipboard as the rest of the current API, but only strings are compatible with the new API as of right now. .. function:: put_text @@ -41,7 +41,7 @@ in a future release of pygame. .. ## pygame.scrap.put_text .. function:: get_text - + | :sl:`Gets text from the clipboard.` | :sg:`get_text() -> str` @@ -56,7 +56,7 @@ in a future release of pygame. .. ## pygame.scrap.get_text .. function:: has_text - + | :sl:`Checks if text is in the clipboard.` | :sg:`has_text() -> bool` @@ -140,7 +140,7 @@ For an example of how the scrap module works refer to the examples page .. note:: The scrap module requires :func:`pygame.display.set_mode()` be called before being initialized. - + .. deprecated:: 2.2.0 .. ## pygame.scrap.init ## @@ -311,4 +311,4 @@ For an example of how the scrap module works refer to the examples page .. deprecated:: 2.2.0 .. ## pygame.scrap.set_mode ## -.. ## pygame.scrap ## \ No newline at end of file +.. ## pygame.scrap ## diff --git a/docs/reST/ref/sdl2_controller.rst b/docs/reST/ref/sdl2_controller.rst index 0f46afb361..c5e61e4d9f 100644 --- a/docs/reST/ref/sdl2_controller.rst +++ b/docs/reST/ref/sdl2_controller.rst @@ -127,6 +127,14 @@ events related to controllers. ``pygame._sdl2.controller.from_joystick``. Controllers are initialized on creation. + .. method:: init + + | :sl:`Initialize the Controller` + | :sg:`init() -> None` + + Initialize a controller object. This should not be used much, since + Controllers are initialised on creation. + .. method:: quit | :sl:`uninitialize the Controller` @@ -229,7 +237,7 @@ events related to controllers. | :sl:`Assign a mapping to the controller` | :sg:`set_mapping(mapping) -> int` - Rebind buttons, axes, triggers and dpads. The mapping should be a + Rebind buttons, axes, triggers and dpads. The mapping should be a dict containing all buttons, hats and axes. The easiest way to get this is to use the dict returned by :meth:`Controller.get_mapping`. To edit this mapping assign a value to the original button. The value of the diff --git a/docs/reST/ref/sdl2_video.rst b/docs/reST/ref/sdl2_video.rst index bb499ef441..47602a5d40 100644 --- a/docs/reST/ref/sdl2_video.rst +++ b/docs/reST/ref/sdl2_video.rst @@ -34,9 +34,9 @@ | :sl:`pygame object encapsulating Renderer driver information` Attributes: - + :: - + name flags num_texture_formats @@ -63,7 +63,7 @@ See :class:`pygame.Window` -.. class:: Texture +.. class:: Texture | :sl:`pygame object that represents a texture` | :sg:`Texture(renderer, size, depth=0, static=False, streaming=False, target=False, scale_quality=None) -> Texture` @@ -95,18 +95,18 @@ Since textures are stored in GPU video memory, they aren't as easy to modify as the image data of :class:`pygame.Surface` objects, which reside in RAM. - + Textures can be modified in 2 ways: - + * By drawing other textures onto them, achieved by marking them as "target" textures and setting them as the rendering target of their Renderer object (if properly configured and supported). - + * By updating them with a Surface. .. note:: A :class:`pygame.Surface`-to-:class:`Texture` update is generally considered a slow operation, as it requires image data to be uploaded from RAM to VRAM, which can have a notable overhead cost. - + .. attribute:: renderer | :sl:`Get the renderer associated with the texture (**read-only**)` @@ -133,7 +133,7 @@ | :sg:`blend_mode -> int` Gets or sets the blend mode for the texture's drawing operations. - Valid blend modes are any of the ``BLENDMODE_*`` constants or a custom one. + Valid blend modes are any of the ``BLENDMODE_*`` constants or a custom one. .. attribute:: color @@ -294,7 +294,7 @@ An origin of ``None`` means no origin was set and the Image will be rotated around its center. - + .. method:: get_rect | :sl:`Get the rectangular area of the Image` @@ -336,7 +336,7 @@ the refresh rate. :param bool target_texture: Whether the renderer should support setting :class:`Texture` objects as target textures, to - enable drawing onto them. + enable drawing onto them. :class:`Renderer` objects provide a cross-platform API for rendering 2D @@ -350,7 +350,7 @@ If configured correctly and supported by an underlying rendering driver, Renderer objects can have a :class:`Texture` object temporarily set as a target texture (the Texture object must have been created with target texture usage support), - which allows those textures to be drawn onto. + which allows those textures to be drawn onto. To present drawn content onto the window, :meth:`Renderer.present` should be called. :meth:`Renderer.clear` should be called to clear any drawn content @@ -366,7 +366,7 @@ .. attribute:: draw_blend_mode | :sl:`Get or set the blend mode used for primitive drawing operations` - | :sg:`draw_blend_mode -> int` + | :sg:`draw_blend_mode -> int` .. attribute:: draw_color @@ -422,7 +422,7 @@ :param area: A :class:`pygame.Rect` or tuple representing the drawing area on the target, or ``None`` to use the - entire area of the current rendering target. + entire area of the current rendering target. .. method:: blit @@ -472,7 +472,7 @@ | :sl:`Draw a triangle outline` | :sg:`draw_triangle(p1, p2, p3) -> None` - + :param p1: The first triangle point. :param p2: The second triangle point. :param p2: The third triangle point. @@ -528,7 +528,7 @@ :class:`pygame.Surface`. It should not be used frequently. .. method:: compose_custom_blend_mode - + | :sl:`Compose a custom blend mode` | :sg:`compose_custom_blend_mode(color_mode, alpha_mode) -> int` @@ -537,5 +537,5 @@ :param color_mode: A tuple ``(srcColorFactor, dstColorFactor, colorOperation)`` :param alpha_mode: A tuple ``(srcAlphaFactor, dstAlphaFactor, alphaOperation)`` - - :return: A blend mode to be used with :meth:`Renderer.set_draw_blend_mode` and :meth:`Texture.set_blend_mode`. \ No newline at end of file + + :return: A blend mode to be used with :meth:`Renderer.set_draw_blend_mode` and :meth:`Texture.set_blend_mode`. diff --git a/docs/reST/ref/special_flags_list.rst b/docs/reST/ref/special_flags_list.rst index 057675257e..25c495fb88 100644 --- a/docs/reST/ref/special_flags_list.rst +++ b/docs/reST/ref/special_flags_list.rst @@ -46,15 +46,15 @@ Special Flags List - ``BLEND_ADD`` / ``BLEND_RGB_ADD`` Adds the source color channels to the destination color channels, clamped to a maximum of 255. - The result color is always a lighter color. + The result color is always a lighter color or the same color. - ``BLEND_SUB`` / ``BLEND_RGB_SUB`` Subtracts the source color channels from the destination color channels, clamped to a minimum of 0. - The result color is always a darker color. + The result color is always a darker color or the same color. - ``BLEND_MULT`` / ``BLEND_RGB_MULT`` Multiplies the destination color channels by the source color channels, divided by 256 (or >> 8). - The result color is always a darker color. + The result color is always a darker color or the same color. - ``BLEND_MIN`` / ``BLEND_RGB_MIN`` Takes the minimum value between the source and destination color channels. @@ -94,9 +94,9 @@ Special Flags List accurate blending results when the color channels are already multiplied by the surface alpha channel. You should only use this blend mode if you previously premultiplied the Surface with - :meth:`pygame.Surface.premul_alpha()`, or if you know that the Surface was already - created or loaded in with premultiplied alpha colors. You can read more about the - advantages of `premultiplied alpha blending + :meth:`pygame.Surface.premul_alpha()`, or if you know that the Surface was already + created or loaded in with premultiplied alpha colors. You can read more about the + advantages of `premultiplied alpha blending here `_. .. versionaddedold:: 2.0.0 diff --git a/docs/reST/ref/sprite.rst b/docs/reST/ref/sprite.rst index a9d9d5e8ed..268727a1d0 100644 --- a/docs/reST/ref/sprite.rst +++ b/docs/reST/ref/sprite.rst @@ -64,24 +64,24 @@ Sprites are not thread safe. So lock them yourself if using threads. adding the Sprite to Groups. For example: .. code-block:: python - + class Block(pygame.sprite.Sprite): - - # Constructor. Pass in the color of the block, + + # Constructor. Pass in the color of the block, # and its x and y position def __init__(self, color, width, height): # Call the parent class (Sprite) constructor - pygame.sprite.Sprite.__init__(self) - + pygame.sprite.Sprite.__init__(self) + # Create an image of the block, and fill it with a color. # This could also be an image loaded from the disk. self.image = pygame.Surface([width, height]) self.image.fill(color) - + # Fetch the rectangle object that has the dimensions of the image # Update the position of this object by setting the values of rect.x and rect.y - self.rect = self.image.get_rect() - + self.rect = self.image.get_rect() + .. method:: update | :sl:`method to control sprite behavior` @@ -667,17 +667,17 @@ Sprites are not thread safe. So lock them yourself if using threads. collide_circle_ratio, collide_mask Example: - + .. code-block:: python # See if the Sprite block has collided with anything in the Group block_list # The True flag will remove the sprite in block_list - blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True) - + blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True) + # Check the list of colliding sprites, and add one to the score for each one for block in blocks_hit_list: score +=1 - + .. ## pygame.sprite.spritecollide ## .. function:: collide_rect diff --git a/docs/reST/ref/surface.rst b/docs/reST/ref/surface.rst index 8132ad0ace..4d0e3be9da 100644 --- a/docs/reST/ref/surface.rst +++ b/docs/reST/ref/surface.rst @@ -90,7 +90,7 @@ .. method:: blit | :sl:`draw another surface onto this one` - | :sg:`blit(source, dest, area=None, special_flags=0) -> Rect` + | :sg:`blit(source, dest=(0, 0), area=None, special_flags=0) -> Rect` Draws another Surface onto this Surface. @@ -98,8 +98,8 @@ - ``source`` The ``Surface`` object to draw onto this ``Surface``. If it has transparency, transparent pixels will be ignored when blittting to an 8-bit ``Surface``. - - ``dest`` - The ``source`` draw position onto this ``Surface``. + - ``dest`` *(optional)* + The ``source`` draw position onto this ``Surface``, defaults to (0, 0). It can be a coordinate ``(x, y)`` or a ``Rect`` (using its top-left corner). If a ``Rect`` is passed, its size will not affect the blit. - ``area`` *(optional)* @@ -137,11 +137,13 @@ - The blit is ignored if the ``source`` is positioned completely outside this ``Surface``'s clipping area. Otherwise only the overlapping area will be drawn. + .. versionchanged:: 2.5.1 The dest argument is optional and defaults to (0, 0) + .. ## Surface.blit ## .. method:: blits - | :sl:`draw many images onto another` + | :sl:`draw many surfaces onto this surface at their corresponding location` | :sg:`blits(blit_sequence=((source, dest), ...), doreturn=True) -> [Rect, ...] or None` | :sg:`blits(((source, dest, area), ...)) -> [Rect, ...]` | :sg:`blits(((source, dest, area, special_flags), ...)) -> [Rect, ...]` @@ -184,8 +186,8 @@ - To draw a ``Surface`` with a special flag, you must specify an area as well, e.g., ``(source, dest, None, special_flags)``. - - Prefer using :meth:`blits` over :meth:`blit` when drawing a multiple images - for better performance. Use :meth:`blit` if you need to draw a single image. + - Prefer using :meth:`blits` over :meth:`blit` when drawing multiple surfaces + for better performance. Use :meth:`blit` if you need to draw a single surface. - For drawing a sequence of (source, dest) pairs with whole source Surface and a singular special_flag, use the :meth:`fblits()` method. @@ -196,7 +198,7 @@ .. method:: fblits - | :sl:`draw many surfaces onto the calling surface at their corresponding location and the same special_flags` + | :sl:`draw many surfaces onto this surface at their corresponding location and with the same special_flags` | :sg:`fblits(blit_sequence=((source, dest), ...), special_flags=0, /) -> None` This method takes a sequence of tuples (source, dest) as input, where source is a Surface @@ -223,7 +225,7 @@ .. method:: convert - | :sl:`change the pixel format of an image` + | :sl:`change the pixel format of a surface` | :sg:`convert(surface, /) -> Surface` | :sg:`convert(depth, flags=0, /) -> Surface` | :sg:`convert(masks, flags=0, /) -> Surface` @@ -244,7 +246,7 @@ creating per-pixel alphas. The new copy will have the same class as the copied surface. This lets - as Surface subclass inherit this method without the need to override, + a Surface subclass inherit this method without the need to override, unless subclass specific instance attributes also need copying. .. versionchanged:: 2.5.0 converting to a known format will succeed without a window/display surface. @@ -253,7 +255,7 @@ .. method:: convert_alpha - | :sl:`change the pixel format of an image including per pixel alphas` + | :sl:`change the pixel format of a surface including per pixel alphas` | :sg:`convert_alpha() -> Surface` Creates a new copy of the surface with the desired pixel format. The new @@ -261,7 +263,7 @@ with per pixel alpha. Unlike the :meth:`convert()` method, the pixel format for the new - image will not be exactly the same as the display surface, but it will + surface will not be exactly the same as the display surface, but it will be optimized for fast alpha blitting to it. As with :meth:`convert()` the returned surface has the same class as @@ -306,11 +308,16 @@ This will return the affected Surface area. + .. note:: As of pygame-ce version 2.5.1, a long-standing bug has been fixed! + Now when passing in a ``Rect`` with negative ``x`` or negative ``y`` (or both), + the ``Rect`` filled will no longer be shifted to ``(0, 0)``, but instead only the + part of the ``Rect`` overlapping the window's ``Rect`` will be filled. + .. ## Surface.fill ## .. method:: scroll - | :sl:`Shift the surface image in place` + | :sl:`shift the surface image in place` | :sg:`scroll(dx=0, dy=0, /) -> None` Move the image by dx pixels right and dy pixels down. dx and dy may be @@ -325,7 +332,7 @@ .. method:: set_colorkey - | :sl:`Set the transparent colorkey` + | :sl:`set the transparent colorkey` | :sg:`set_colorkey(color, flags=0, /) -> None` | :sg:`set_colorkey(None) -> None` @@ -347,7 +354,7 @@ .. method:: get_colorkey - | :sl:`Get the current transparent colorkey` + | :sl:`get the current transparent colorkey` | :sg:`get_colorkey() -> RGB or None` Return the current colorkey value for the Surface. If the colorkey is not @@ -357,7 +364,7 @@ .. method:: set_alpha - | :sl:`set the alpha value for the full Surface image` + | :sl:`set the alpha value for the full Surface` | :sg:`set_alpha(value, flags=0, /) -> None` | :sg:`set_alpha(None) -> None` @@ -464,7 +471,7 @@ .. method:: get_locks - | :sl:`Gets the locks for the Surface` + | :sl:`gets the locks for the Surface` | :sg:`get_locks() -> tuple` Returns the currently existing locks for the Surface. @@ -646,7 +653,7 @@ Return a rectangle of the current clipping area. The Surface will always return a valid rectangle that will never be outside the bounds of the - image. If the Surface has had ``None`` set for the clipping area, the + surface. If the Surface has had ``None`` set for the clipping area, the Surface will return a rectangle with the full area of the Surface. .. ## Surface.get_clip ## @@ -749,7 +756,7 @@ | :sg:`get_rect(\**kwargs) -> Rect` Returns a new rectangle covering the entire surface. This rectangle will - always start at (0, 0) with a width and height the same size as the image. + always start at (0, 0) with a width and height the same size as the surface. You can pass keyword argument values to this function. These named values will be applied to the attributes of the Rect before it is returned. An @@ -1034,6 +1041,23 @@ .. ## Surface.premul_alpha ## + .. method:: premul_alpha_ip + + | :sl:`multiplies the RGB channels by the surface alpha channel.` + | :sg:`premul_alpha_ip() -> Surface` + + Multiplies the RGB channels of the surface by the alpha channel in place and returns the surface. + + Surfaces without an alpha channel cannot use this method and will return an error if you use + it on them. It is best used on 32 bit surfaces (the default on most platforms) as the blitting + on these surfaces can be accelerated by SIMD versions of the pre-multiplied blitter. + + Refer to the :meth:`premul_alpha` method for more information. + + .. versionadded:: 2.5.1 + + .. ## Surface.premul_alpha_ip ## + .. attribute:: width | :sl:`Surface width in pixels (read-only)` @@ -1062,5 +1086,3 @@ .. versionadded:: 2.5.0 .. ## pygame.Surface ## - - diff --git a/docs/reST/ref/system.rst b/docs/reST/ref/system.rst index 1d522b49ee..eb1450f9e3 100644 --- a/docs/reST/ref/system.rst +++ b/docs/reST/ref/system.rst @@ -16,11 +16,11 @@ | :sg:`get_cpu_instruction_sets() -> instruction_sets` Returns a dict of the information of CPU instruction sets. The keys of - the dict are the names of instruction sets and the values determine + the dict are the names of instruction sets and the values determine whether the instruction set is available. - Some of functions like ``Surface.blit`` can be accelerated by SIMD - instruction sets like SSE2 or AVX2. By checking the availability of + Some of functions like ``Surface.blit`` can be accelerated by SIMD + instruction sets like SSE2 or AVX2. By checking the availability of instruction sets, you can check if these accelerations are available. Here is an example of the returned dict @@ -35,23 +35,23 @@ 'SSE41': True, 'SSE42': True, 'AVX': True, - 'AVX2': True, + 'AVX2': True, 'AVX512F': False, - 'NEON': False, + 'NEON': False, 'ARMSIMD': False, - 'LSX': False, + 'LSX': False, 'LASX': False } .. Note:: The value of ``ARMSIMD`` will be always False if SDL version < 2.0.12. - + The values of ``LSX`` and ``LASX`` will be always False if SDL version < 2.24.0. - + .. versionadded:: 2.3.1 - .. versionchanged:: 2.4.0 removed ``RDTSC`` key, + .. versionchanged:: 2.4.0 removed ``RDTSC`` key, as it has been removed in pre-release SDL3 .. function:: get_total_ram @@ -77,7 +77,7 @@ per app name. It takes two strings, ``org`` and ``app``, referring to the "organization" - and "application name." For example, the organization could be "Valve," + and "application name." For example, the organization could be "Valve," and the application name could be "Half Life 2." It then will figure out the preferred path, **creating the folders referenced by the path if necessary**, and return a string containing the absolute path. @@ -101,7 +101,7 @@ .. note:: The ``appdirs`` library has similar functionality for this use case, but has more "folder types" to choose from. - + .. versionadded:: 2.2.0 .. function:: get_pref_locales @@ -114,11 +114,11 @@ also be an empty list if pygame could not find this information. Each "locale" dict contains the language code which can be accessed by the - key ``"language"``. This language code is an ISO-639 language specifier + key ``"language"``. This language code is an ISO-639 language specifier (such as "en" for English, "de" for German, etc). A "locale" dict may also optionally contain a ``"country"`` field, whose - value is an ISO-3166 country code (such as "US" for the United States, - "CA" for Canada, etc). If this field is not set or undetermined, it is + value is an ISO-3166 country code (such as "US" for the United States, + "CA" for Canada, etc). If this field is not set or undetermined, it is ``None``. A "locale" dict which looks like ``{'language': 'en', 'country': 'US'}`` indicates the user prefers American English, while @@ -126,7 +126,7 @@ English, generically. This might be a bit of an expensive call because it has to query the OS. So - this function must not be called in a game loop, instead it's best to ask + this function must not be called in a game loop, instead it's best to ask for this once and save the results. However, this list can change when the user changes a system preference outside of your program. pygame will send a ``LOCALECHANGED`` event in this case, if possible, and you can call this @@ -141,11 +141,11 @@ **Experimental:** feature available for testing and feedback. We don't anticipate it changing, but it might if something important - is brought up. `Please leave get_power_state feedback with + is brought up. `Please leave get_power_state feedback with authors `_ Returns a ``PowerState`` object representing the power supply state. - + Returns ``None`` if the power state is unknown. The PowerState object has several attributes: @@ -159,7 +159,7 @@ battery_seconds: An integer, representing the seconds of battery life left. Could be None if the value is unknown. - + on_battery: True if the device is running on the battery (not plugged in). diff --git a/docs/reST/ref/transform.rst b/docs/reST/ref/transform.rst index 9c6f0111d0..3ee5f05200 100644 --- a/docs/reST/ref/transform.rst +++ b/docs/reST/ref/transform.rst @@ -39,7 +39,7 @@ Instead, always begin with the original image and scale to the desired size.) | :sl:`resize to new resolution` | :sg:`scale(surface, size, dest_surface=None) -> Surface` - Resizes the Surface to a new size, given as (width, height). + Resizes the Surface to a new size, given as (width, height). This is a fast scale operation that does not sample the results. An optional destination surface can be passed which is faster than creating a new @@ -113,7 +113,7 @@ Instead, always begin with the original image and scale to the desired size.) This really only has an effect on simple images with solid colors. On photographic and antialiased images it will look like a regular unfiltered scale. - + An optional destination surface can be passed which is faster than creating a new Surface. This destination surface must have double the dimensions (width * 2, height * 2) and same depth and format as the source Surface. @@ -127,7 +127,7 @@ Instead, always begin with the original image and scale to the desired size.) Uses one of two different algorithms for scaling each dimension of the input surface as required. For shrinkage, the output pixels are area averages of - the colors they cover. The size is a 2 number sequence for (width, height). + the colors they cover. The size is a 2 number sequence for (width, height). This function only works for 24-bit or 32-bit surfaces. A ``ValueError`` will be thrown if the input surface bit depth is less than 24. @@ -138,7 +138,7 @@ Instead, always begin with the original image and scale to the desired size.) .. versionaddedold:: 1.8 .. versionchanged:: 2.4.0 now uses SSE2/NEON SIMD for acceleration on x86 - and ARM machines, a performance improvement over previous MMX/SSE only + and ARM machines, a performance improvement over previous MMX/SSE only supported on x86. .. ## pygame.transform.smoothscale ## @@ -269,9 +269,9 @@ Instead, always begin with the original image and scale to the desired size.) .. versionchanged:: 2.3.0 Passing the calling surface as destination surface raises a ``ValueError`` - + .. versionchanged:: 2.3.1 - Now the standard deviation of the Gaussian kernel is equal to the radius. + Now the standard deviation of the Gaussian kernel is equal to the radius. Blur results will be slightly different. .. versionchanged:: 2.5.0 @@ -295,7 +295,7 @@ Instead, always begin with the original image and scale to the desired size.) correctly. An optional destination surface can be passed which is faster than creating a new - Surface. This destination surface must have the same dimensions (width, height) and + Surface. This destination surface must have the same dimensions (width, height) and depth as the first passed source Surface. .. versionaddedold:: 1.8 @@ -306,11 +306,12 @@ Instead, always begin with the original image and scale to the desired size.) .. function:: average_color | :sl:`finds the average color of a surface` - | :sg:`average_color(surface, rect=None, consider_alpha=False) -> Color` + | :sg:`average_color(surface, rect=None, consider_alpha=False) -> tuple` Finds the average color of a Surface or a region of a surface specified by a - Rect, and returns it as a Color. If consider_alpha is set to True, then alpha is - taken into account (removing the black artifacts). + Rect, and returns it as a tuple of integers red, green, blue, and alpha. + If consider_alpha is set to True, then alpha is taken into account + (removing the black artifacts). .. versionaddedold:: 2.1.2 ``consider_alpha`` argument @@ -341,7 +342,7 @@ Instead, always begin with the original image and scale to the desired size.) An optional destination surface can be passed which is faster than creating a new Surface. This destination surface must have the same dimensions (width, height) and depth as the source Surface. - + .. versionadded:: 2.1.4 .. versionchanged:: 2.4.0 Adjusted formula slightly to support performance optimisation. It may return very slightly diff --git a/docs/reST/ref/typing.rst b/docs/reST/ref/typing.rst new file mode 100644 index 0000000000..27d3bb7ecd --- /dev/null +++ b/docs/reST/ref/typing.rst @@ -0,0 +1,78 @@ +.. include:: common.txt + +:mod:`pygame.typing` +==================== + +.. module:: pygame.typing + :synopsis: pygame module providing common typehints + +| :sl:`pygame module providing common typehints` + +.. versionadded:: 2.5.2 + +A lot of pygame functions and methods allow the user to provide different types +for the same value like colors or coordinates. This module exports the most common +type aliases for proper typehint annotations. + + .. data:: FileLike + + An object representing a file. This includes both path-like + objects and file-like objects, i.e.: + + * ``"my/string/path.txt"`` + * ``open("my/file/path.txt")`` + * ``pathlib.Path("my/pathlib/path.txt")`` + * ``io.BytesIO(b"my data: \x00\x01")`` + * ``b"my/bytes/path.txt"`` + * Any object implementing the path protocol or file protocol. + + .. data:: SequenceLike + + A variant of the standard ``Sequence`` ABC only requiring ``__getitem__`` + and ``__len__``. This includes custom sequences or builtin ones, i.e.: + + * ``"abcdefg"`` + * ``[a, b, c, d, ...]`` + * ``(a, b, c, d, ...)`` + + Being a generic, subscribing it will signal further precision such as + ``SequenceLike[str]`` or ``SequenceLike[float]``. + + .. data:: Coordinate + + A sequence of two numbers (floats or ints), i.e: + + * ``pygame.Vector2(a, b)`` + * ``[a, b]`` + * ``(a, b)`` + + .. data:: IntCoordinate + + A sequence of strictly two integers such as ``[a, b]`` or ``(a, b)``. + + .. data:: ColorLike + + An object representing a color such as a mapped integer, a string or + a sequence of three or four integers in range 0-255, types supported by + every function accepting a color argument. i.e.: + + * ``pygame.Color(ColorLike)`` + * ``(r, g, b)`` + * ``(r, g, b, a)`` + * ``[r, g, b, a]`` + * ``"green"`` + * ``0`` (mapped color) + + .. data:: RectLike + + An object representing a rect such as a sequence of numbers or coordinates + or an object with a rect attribute or a method returning a rect. These types + are supported by every function accepting a rect as argument. i.e.: + + * ``(x, y, w, h)`` + * ``(Coordinate, Coordinate)`` + * ``pygame.Rect(RectLike)`` + * Any object with a ``.rect`` attribute which is a ``RectLike`` or a function + returning a ``RectLike`` + +.. ## pygame.typing ## diff --git a/docs/reST/ref/window.rst b/docs/reST/ref/window.rst index 1365b636aa..db07af986d 100644 --- a/docs/reST/ref/window.rst +++ b/docs/reST/ref/window.rst @@ -44,8 +44,48 @@ :param bool always_on_top: Create a window that is always presented above others. + Event behavior if one Window is created: When the close button is pressed, + the ``QUIT`` event will be sent to the event queue. + + .. code-block:: python + + import pygame + + window = pygame.Window() + + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + raise SystemExit + + Event behavior if multiple Windows are created: When the close button is + pressed, a ``WINDOWCLOSE`` event is sent. You need to explicitly destroy + the window. Note that the event ``QUIT`` will only be sent if all Window + has been destroyed. + + .. code-block:: python + + import pygame + + window1 = pygame.Window(position=(0,100)) + window2 = pygame.Window(position=(700,100)) + + while True: + for event in pygame.event.get(): + if event.type == pygame.WINDOWCLOSE: + id = event.window.id + print(f"WINDOWCLOSE event sent to Window #{id}.") + event.window.destroy() + + if event.type == pygame.QUIT: + print(f"Last window is destroyed. QUIT event was sent.") + pygame.quit() + raise SystemExit + .. versionadded:: 2.4.0 .. versionchanged:: 2.5.0 when ``opengl`` is ``True``, the ``Window`` has an OpenGL context created by pygame + .. versionchanged:: 2.5.1 Window is now a base class, allowing subclassing .. attribute:: grab_mouse @@ -127,6 +167,18 @@ .. versionadded:: 2.4.0 + .. attribute:: focused + + | :sl:`Get if the window is focused (**read-only**)` + | :sg:`focused -> bool` + + Get if the window is currently focused. The same result can be achieved using + the ``WINDOWFOCUSGAINED`` and ``WINDOWFOCUSLOST`` events. + + Use :meth:`focus` to focus and raise the window. + + .. versionadded:: 2.5.2 + .. attribute:: title | :sl:`Get or set the window title` @@ -278,8 +330,8 @@ Update pixel data from memory to be displayed in the window. This is the Window class equivalent of :func:`pygame.display.flip`. - With ``get_surface()`` this method allows software rendering (classic pygame rendering) flipping pixel data - from an associated surface in memory to be displayed in the window. Alternatively, when this window has an + With ``get_surface()`` this method allows software rendering (classic pygame rendering) flipping pixel data + from an associated surface in memory to be displayed in the window. Alternatively, when this window has an associated OpenGL context, this method will instead perform a GL buffer swap to the window. Here is a runnable example of using ``get_surface`` and ``flip``: @@ -387,4 +439,35 @@ .. note:: This function is only supported on X11. + .. method:: flash + + | :sl:`Flash a window to demand attention from the user` + | :sg:`flash(operation, /) -> None` + + :param int operation: The flash operation. + + Supported flash operations are: + * ``pygame.FLASH_CANCEL``: Cancel the current flash state if present + * ``pygame.FLASH_BRIEFLY``: Flash for a short amount of time to get attention + * ``pygame.FLASH_UNTIL_FOCUSED``: Keep flashing until the window is focused + + Window flashing requires SDL 2.0.16+. A :mod:`pygame.error` exception will be raised + otherwise. + + .. note:: This function is only supported on Windows, X11, Wayland and Cocoa (MacOS). + A :mod:`pygame.error` exception will be raised if it's not supported therefore it's + advised to wrap it in a try block. + + .. code-block:: python + + import pygame + window = pygame.Window() + + try: + window.flash(pygame.FLASH_BRIEFLY) + except pygame.error: + print("Window flashing not supported") + + .. versionadded:: 2.5.2 + .. ## pygame.Window ## diff --git a/docs/reST/themes/classic/elements.html b/docs/reST/themes/classic/elements.html index be82e27801..7b093a89fb 100644 --- a/docs/reST/themes/classic/elements.html +++ b/docs/reST/themes/classic/elements.html @@ -39,8 +39,9 @@
pygame-ce documentation
#} {%- set basic = ['Color', 'display', 'draw', 'event', 'font', 'image', 'key', 'locals', 'mixer', 'mouse', 'music', 'pygame', 'Rect', 'Surface', 'time'] %} +{%- set experimental = ['sdl2_video', 'controller', 'geometry', 'Window'] %} {%- set advanced = ['BufferProxy', 'freetype', 'gfxdraw', 'midi', 'PixelArray', 'pixelcopy', 'sndarray', 'surfarray', 'cursors', 'joystick', 'mask', 'math', 'sprite', 'transform'] %} -{%- set hidden = ['sdl2_video', 'sdl2_controller', 'geometry', 'Window'] %} +{%- set hidden = ['sdl2_video', 'geometry', 'Window'] %} {%- if pyg_sections %}

Most useful stuff: {% set sep = joiner(" | \n") %} @@ -104,8 +105,3 @@

pygame-ce documentation

{{ _('Edit on GitHub') }} {%- endblock %} - - -{%- block relbaritems %} - -{% endblock %} diff --git a/docs/reST/themes/classic/static/pygame.css_t b/docs/reST/themes/classic/static/pygame.css_t index cc71502270..7b84148903 100644 --- a/docs/reST/themes/classic/static/pygame.css_t +++ b/docs/reST/themes/classic/static/pygame.css_t @@ -120,15 +120,17 @@ div.header .logo { align-items: center; justify-content: center; flex-direction: column; + min-width: 214px; } .dark-theme div.header .logo { background-color: {{ theme_dark_logobgcolor }}; border: none; + min-width: 214px; } div.header .logo img { - min-width: 200px; - min-height: 60px; border-style: none; + height: 80px; + min-width: 156px; } div.header .pagelinks { @@ -601,6 +603,15 @@ dt code { color: {{ theme_dark_codecomment }}; } +.dark-theme .highlight .nf, +.dark-theme .highlight .fm { + color: {{ theme_dark_functiondefinecolor }}; +} + +.dark-theme .highlight .nc { + color: {{ theme_dark_classdefinecolor }}; font-weight: bold +} + code.download span.pre { font-family: inherit; font-weight: normal; @@ -637,17 +648,17 @@ div.body h6 { line-height: 130%; } -div.body h1 { +div.body h1 { font-size: 1.5em; color: {{ theme_h1color }}; } -.dark-theme div.body h1 { +.dark-theme div.body h1 { color: {{ theme_dark_h1color }}; } -div.body h2 { +div.body h2 { font-size: 1.4em; color: {{ theme_h2color }}; } -.dark-theme div.body h2 { +.dark-theme div.body h2 { color: {{ theme_dark_h2color }}; } div.body h3 { font-size: 1.3em; } diff --git a/docs/reST/themes/classic/theme.conf b/docs/reST/themes/classic/theme.conf index dc06790366..c0bd7ad019 100644 --- a/docs/reST/themes/classic/theme.conf +++ b/docs/reST/themes/classic/theme.conf @@ -56,6 +56,8 @@ dark_notebgcolor = #555349 dark_notebordercolor = #6e6b5e dark_cautionbgcolor = rgb(255 25 25 / 30%) dark_cautionbordercolor = rgb(255 60 60) +dark_functiondefinecolor = #fee32d +dark_classdefinecolor = #50b0fa relbarbgcolor = #6aee28 relbartextcolor = #000000 diff --git a/docs/reST/tutorials/en/Red_or_Black/2.Print_text/Basic TEMPLATE and OUTPUT.rst b/docs/reST/tutorials/en/Red_or_Black/2.Print_text/Basic TEMPLATE and OUTPUT.rst index 7f6036725c..46ea700b9c 100644 --- a/docs/reST/tutorials/en/Red_or_Black/2.Print_text/Basic TEMPLATE and OUTPUT.rst +++ b/docs/reST/tutorials/en/Red_or_Black/2.Print_text/Basic TEMPLATE and OUTPUT.rst @@ -114,7 +114,7 @@ That was the explanation of the entire source code, which has 20 lines. It seems pygame.display.set_caption("Hello World Project") #7 myScreen = pygame.display.set_mode((640, 480)) #8 myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) #9 - myText = myTextFont.render("Hello World!", True, red, green) #10 + myText = myTextFont.render("Hello World!", True, red, green) #10 myTextArea = myText.get_rect() #11 myTextArea.center = (320, 240) #12 @@ -128,4 +128,3 @@ That was the explanation of the entire source code, which has 20 lines. It seems sys.exit() #19 pygame.display.update() #20 - diff --git a/docs/reST/tutorials/en/Red_or_Black/3.Move_text/Basic PROCESS.rst b/docs/reST/tutorials/en/Red_or_Black/3.Move_text/Basic PROCESS.rst index efb45cc3f2..b95dac1226 100644 --- a/docs/reST/tutorials/en/Red_or_Black/3.Move_text/Basic PROCESS.rst +++ b/docs/reST/tutorials/en/Red_or_Black/3.Move_text/Basic PROCESS.rst @@ -105,10 +105,10 @@ Okay, we learn that “Fixing time” is needed when screen is updated. Every sc red = (255,0,0) green = (0,255,0) pygame.init() - pygame.display.set_caption("Moving World Project") + pygame.display.set_caption("Moving World Project") myScreen = pygame.display.set_mode((640, 480)) myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) - myText = myTextFont.render("Moving World!", True, red, green) + myText = myTextFont.render("Moving World!", True, red, green) myTextArea = myText.get_rect() myTextArea.center = (320, 240) fpsClock = pygame.time.Clock() #1 @@ -139,7 +139,7 @@ Okay, we learn that “Fixing time” is needed when screen is updated. Every sc myTextArea.center = (320 + x, 240 + y) #10 - + myScreen.fill(white) myScreen.blit(myText, myTextArea) @@ -150,4 +150,3 @@ Okay, we learn that “Fixing time” is needed when screen is updated. Every sc pygame.display.update() fpsClock.tick(60) #11 - diff --git a/docs/reST/tutorials/en/Red_or_Black/4.Control_text/Basic INPUT.rst b/docs/reST/tutorials/en/Red_or_Black/4.Control_text/Basic INPUT.rst index 5afa30c37c..c6e32f7a68 100644 --- a/docs/reST/tutorials/en/Red_or_Black/4.Control_text/Basic INPUT.rst +++ b/docs/reST/tutorials/en/Red_or_Black/4.Control_text/Basic INPUT.rst @@ -89,7 +89,7 @@ Usually, we learn how to output something first (Think about Hello World!), lear (Controlling World! moves when player press one of four direction arrow of keyboard) -There are 2 big difference in comparison to before project. First big difference is line #5, which adds checking ``KEYDOWN`` **event** is triggered or not. Other lines are just changing previous algorithm to act differently. We know that same command can make big difference in entire program when it is executed before Event statement of after Event statement. Pay attention that process about changing location appear after Event statement. (**Update after set**. That is second big difference). Variable ``event.key`` means latest pressed key on keyboard. Look at the specific key name. K_UP, K_LEFT, K_DOWN, K_RIGHT. Very intuitive **K_ series**. (Given by pygame.locals which we added at the Header) Furthermore, there are other key named K_8, K_a, K_L, K_LCTRL, K_DELETE, or K_F4. We can understand meaning of these keys without extra explanation. Full key list can be found in +There are 2 big difference in comparison to before project. First big difference is line #5, which adds checking ``KEYDOWN`` **event** is triggered or not. Other lines are just changing previous algorithm to act differently. We know that same command can make big difference in entire program when it is executed before Event statement of after Event statement. Pay attention that process about changing location appear after Event statement. (**Update after set**. That is second big difference). Variable ``event.key`` means latest pressed key on keyboard. Look at the specific key name. K_UP, K_LEFT, K_DOWN, K_RIGHT. Very intuitive **K_ series**. (Given by pygame.locals which we added at the Header) Furthermore, there are other key named K_8, K_a, K_L, K_LCTRL, K_DELETE, or K_F4. We can understand meaning of these keys without extra explanation. Full key list can be found in `https://pyga.me/docs/ref/key.html#pygame.key.name`. Notice that KEYDOWN means “this key was not pressed before, but **now is pressed**” and meaning of **“hold” is not included** here. In the case of hold, new event-handling about checking ``KEYUP`` (it means “this key was pressed before, but now is not pressed”) is needed with some processing (which needs extra variable and algorithm). This will be mentioned at advanced part. @@ -107,10 +107,10 @@ Adding input was easy because it’s just adding if phase with certain event par red = (255,0,0) green = (0,255,0) pygame.init() - pygame.display.set_caption("Controlling World Project") + pygame.display.set_caption("Controlling World Project") myScreen = pygame.display.set_mode((640, 480)) myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) - myText = myTextFont.render("Controlling World!", True, red, green) + myText = myTextFont.render("Controlling World!", True, red, green) myTextArea = myText.get_rect() myTextArea.center = (320, 240) fpsClock = pygame.time.Clock() @@ -143,7 +143,7 @@ Adding input was easy because it’s just adding if phase with certain event par elif event.key == K_RIGHT: moveDown = 0 moveRight = 1 - + if(moveRight == 1): #6 x = x + 10 elif(moveRight == -1): #7 @@ -154,4 +154,3 @@ Adding input was easy because it’s just adding if phase with certain event par y = y - 10 pygame.display.update() - diff --git a/docs/reST/tutorials/en/Red_or_Black/5.HP_bar/Advanced OUTPUT with Advanced PROCESS.rst b/docs/reST/tutorials/en/Red_or_Black/5.HP_bar/Advanced OUTPUT with Advanced PROCESS.rst index fc97bb2ea4..9192a2b861 100644 --- a/docs/reST/tutorials/en/Red_or_Black/5.HP_bar/Advanced OUTPUT with Advanced PROCESS.rst +++ b/docs/reST/tutorials/en/Red_or_Black/5.HP_bar/Advanced OUTPUT with Advanced PROCESS.rst @@ -215,8 +215,8 @@ Furthermore, now it’s time to functionalize specifically. I push Always statem import pygame, sys from pygame.locals import* - - maxHP = 10 + + maxHP = 10 white = (255,255,255) gray = (127,127,127) black = (0,0,0) @@ -233,18 +233,18 @@ Furthermore, now it’s time to functionalize specifically. I push Always statem myTextArea = myText.get_rect() myTextArea.center = (width/2, height/2) #3 fpsClock = pygame.time.Clock() - + def main(): #4 HP = 5 - + while True: myText = myTextFont.render((str(HP) + "/" + str(maxHP)), True, red, gray) - + myScreen.fill(gray) - + myScreen.blit(myText, myTextArea) drawHP(HP) #5 - + for event in pygame.event.get(): if event.type == QUIT: pygame.quit() @@ -256,21 +256,21 @@ Furthermore, now it’s time to functionalize specifically. I push Always statem elif event.key == K_DOWN: if HP != 0: HP = HP - 1 - + pygame.display.update() fpsClock.tick(60) - + def drawHP(HP): #6 r = int((height - 40) / maxHP) - + pygame.draw.rect(myScreen, black, (20, 20, 20, 20 + ((maxHP - 0.5) * r))) - + for i in range(maxHP): if HP >= (maxHP - i): pygame.draw.rect(myScreen, red, (20, 20 + (i * r), 20, r)) pygame.draw.rect(myScreen, white, (20, 20 + (i * r), 20, r), 1) - + return - + if __name__ == '__main__': #7 main() diff --git a/docs/reST/tutorials/en/Red_or_Black/6.Buttons/Advanced INPUT with Advanced OUTPUT.rst b/docs/reST/tutorials/en/Red_or_Black/6.Buttons/Advanced INPUT with Advanced OUTPUT.rst index c3e2bd7b65..6be9b13967 100644 --- a/docs/reST/tutorials/en/Red_or_Black/6.Buttons/Advanced INPUT with Advanced OUTPUT.rst +++ b/docs/reST/tutorials/en/Red_or_Black/6.Buttons/Advanced INPUT with Advanced OUTPUT.rst @@ -185,8 +185,8 @@ In the case of button, input and output area for button must be **identical**. ( import pygame, sys from pygame.locals import* - - maxHP = 10 + + maxHP = 10 white = (255,255,255) gray = (127,127,127) black = (0,0,0) @@ -195,7 +195,7 @@ In the case of button, input and output area for button must be **identical**. ( blue = (0,0,255) pygame.init() pygame.display.set_caption("Array buttons Project") - width = 640 + width = 640 height = 480 myScreen = pygame.display.set_mode((width, height)) myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) @@ -203,19 +203,19 @@ In the case of button, input and output area for button must be **identical**. ( myTextArea = myText.get_rect() myTextArea.center = (width/2, height/2) fpsClock = pygame.time.Clock() - + def main(): HP = 5 - + while True: myText = myTextFont.render((str(HP) + "/" + str(maxHP)), True, red, gray) - + myScreen.fill(gray) - + myScreen.blit(myText, myTextArea) drawHP(HP) drawButtons() - + for event in pygame.event.get(): if event.type == QUIT: pygame.quit() @@ -234,28 +234,28 @@ In the case of button, input and output area for button must be **identical**. ( HP = HP + 1 elif pygame.Rect(325, 425, 45, 45).collidepoint(x, y): if HP != 0: - HP = HP - 1 - + HP = HP - 1 + pygame.display.update() fpsClock.tick(60) - + def drawHP(HP): r = int((height - 40) / maxHP) - + pygame.draw.rect(myScreen, black, (20, 20, 20, 20 + ((maxHP - 0.5) * r))) - + for i in range(maxHP): if HP >= (maxHP - i): pygame.draw.rect(myScreen, red, (20, 20 + (i * r), 20, r)) pygame.draw.rect(myScreen, white, (20, 20 + (i * r), 20, r), 1) - + return - + def drawButtons(): r = 45 r_margin = 10 colors = [red, black] - + num = 2 margin = int((width - ((r * num) + (r_margin * (num - 1)))) / 2) for i in range(0, num): @@ -263,6 +263,6 @@ In the case of button, input and output area for button must be **identical**. ( up = height - r - 10 pygame.draw.rect(myScreen, colors[i], (left, up, r, r)) pygame.draw.rect(myScreen, gray, (left + 2, up + 2, r - 4, r - 4), 2) - + if __name__ == '__main__': main() diff --git a/docs/reST/tutorials/en/Red_or_Black/7.Game_board/Advanced OUTPUT and plus alpha.rst b/docs/reST/tutorials/en/Red_or_Black/7.Game_board/Advanced OUTPUT and plus alpha.rst index bd2a1fca7b..f59617b9f3 100644 --- a/docs/reST/tutorials/en/Red_or_Black/7.Game_board/Advanced OUTPUT and plus alpha.rst +++ b/docs/reST/tutorials/en/Red_or_Black/7.Game_board/Advanced OUTPUT and plus alpha.rst @@ -116,8 +116,8 @@ Actually, there are a lot of idea for improving this game. How about changing bu import pygame, sys, random from pygame.locals import* - - maxHP = 10 + + maxHP = 10 white = (255,255,255) gray = (127,127,127) black = (0,0,0) @@ -126,7 +126,7 @@ Actually, there are a lot of idea for improving this game. How about changing bu blue = (0,0,255) pygame.init() pygame.display.set_caption("Red or Black Project") - width = 640 + width = 640 height = 480 myScreen = pygame.display.set_mode((width, height)) myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) @@ -134,26 +134,26 @@ Actually, there are a lot of idea for improving this game. How about changing bu myTextArea = myText.get_rect() myTextArea.center = (width/2, height/2) fpsClock = pygame.time.Clock() - + def main(): HP = 5 board, b_red, b_black = generateBoard(5,5) #1 - + while True: myText = myTextFont.render((str(HP) + "/" + str(maxHP)), True, red, gray) - + myScreen.fill(gray) - + myScreen.blit(myText, myTextArea) drawHP(HP) drawButtons() drawBoard(board) #2 - + for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() - + elif event.type == KEYDOWN: if event.key == K_UP: if HP != 10: @@ -163,7 +163,7 @@ Actually, there are a lot of idea for improving this game. How about changing bu HP = HP - 1 elif event.type == MOUSEBUTTONUP: x, y = event.pos - + if pygame.Rect(270, 425, 45, 45).collidepoint(x, y): #3 if b_red >= b_black: if HP != 10: @@ -173,7 +173,7 @@ Actually, there are a lot of idea for improving this game. How about changing bu if HP != 0: HP = HP - 1 board, b_red, b_black = generateBoard(5,5) - + elif pygame.Rect(325, 425, 45, 45).collidepoint(x, y): #4 if b_red <= b_black: if HP != 10: @@ -183,63 +183,63 @@ Actually, there are a lot of idea for improving this game. How about changing bu if HP != 0: HP = HP - 1 board, b_red, b_black = generateBoard(5,5) - + pygame.display.update() fpsClock.tick(60) - + def drawHP(HP): r = int((height - 40) / maxHP) - + pygame.draw.rect(myScreen, gray, (20, 20, 20, 20 + ((maxHP - 0.5) * r))) - + for i in range(maxHP): if HP >= (maxHP - i): pygame.draw.rect(myScreen, blue, (20, 20 + (i * r), 20, r)) pygame.draw.rect(myScreen, white, (20, 20 + (i * r), 20, r), 1) - + return - + def drawButtons(): r = 45 r_margin = 10 colors = [red, black] - + num = 2 margin = int((width - ((r * num) + (r_margin * (num - 1)))) / 2) - + for i in range(0, num): left = margin + (i * r) + (i * r_margin) up = height - r - 10 pygame.draw.rect(myScreen, colors[i], (left, up, r, r)) - pygame.draw.rect(myScreen, gray, (left + 2, up + 2, r - 4, r - 4), 2) - + pygame.draw.rect(myScreen, gray, (left + 2, up + 2, r - 4, r - 4), 2) + def generateBoard(width, height): #5 board = [] b_red = 0 b_black = 0 - + for x in range(width): column = [] for y in range(height): column.append(random.randint(0, 1)) board.append(column) - + for x in range(width): for y in range(height): if(board[x][y] == 1): b_red = b_red + 1 elif(board[x][y] == 0): b_black = b_black + 1 - + return board, b_red, b_black - + def drawBoard(board): #6 r = 50 b_width = 5 b_height = 5 l_margin = int((width - (b_width * r)) / 2) u_margin = int((height - (b_height * r)) / 2) - + for x in range(5): for y in range(5): left = x * r + l_margin @@ -249,10 +249,10 @@ Actually, there are a lot of idea for improving this game. How about changing bu elif board[x][y] == 0: color = black pygame.draw.rect(myScreen, color, (left, up, r, r)) - + left = l_margin up = u_margin pygame.draw.rect(myScreen, white, (left-1, up-1, r * 5 + 1, r * b_height + 1), 1) - + if __name__ == '__main__': main() diff --git a/docs/reST/tutorials/en/Red_or_Black/8.Epilog/Epilog.rst b/docs/reST/tutorials/en/Red_or_Black/8.Epilog/Epilog.rst index d365479384..b257ee4588 100644 --- a/docs/reST/tutorials/en/Red_or_Black/8.Epilog/Epilog.rst +++ b/docs/reST/tutorials/en/Red_or_Black/8.Epilog/Epilog.rst @@ -15,4 +15,3 @@ However, this is end of tutorial. This tutorial covers only a few of Pygame. But What is conclusion? **Output is greater than input**. We can implement much more program within our knowledge. Or, we can learn new knowledge easily by connecting it to old knowledge. That’s trait of programming. And so is game. “Radom” is the key concept for every game. (including simple game made on this tutorial!) number of cases is much greater when random variable is concerned. If random variable starts to affect another random variable and so on, output will be greater like Avalanche. That’s why game is interesting. Concept of “Random” is the only unique characteristic for game in comparison to novel, music or movie. Think about Tetris. How much effort Alexey Leonidovich Pajitnov spent? Do you think it is greater than sum of Tetris player’s playing time above the world, along 35 years? That’s ultimate example of both power of programming and game. So, game makers are Avalanche makers. Now it’s time to create any game! Learn! Utilize! Go through trial and errors! - diff --git a/docs/reST/tutorials/en/chimp-explanation.rst b/docs/reST/tutorials/en/chimp-explanation.rst index a5bd19397a..a18c83e0e2 100644 --- a/docs/reST/tutorials/en/chimp-explanation.rst +++ b/docs/reST/tutorials/en/chimp-explanation.rst @@ -65,11 +65,6 @@ It also checks for the availability of some of the optional pygame modules. :: import os import pygame - if not pygame.font: - print("Warning, fonts disabled") - if not pygame.mixer: - print("Warning, sound disabled") - main_dir = os.path.split(os.path.abspath(__file__))[0] data_dir = os.path.join(main_dir, "data") @@ -77,13 +72,7 @@ It also checks for the availability of some of the optional pygame modules. :: First, we import the standard "os" python module. This allow us to do things like create platform independent file paths. -In the next line, we import the pygame package. - -Some pygame modules are optional, and if they aren't found, -they evaluate to ``False``. Because of that, we decide to print -a nice warning message if the :mod:`font` or -:mod:`mixer ` modules in pygame are not available. -(Although they will only be unavailable in very uncommon situations). +In the next line, we import the pygame package. Lastly, we prepare two paths for the rest of the code to use. ``main_dir`` uses the `os.path` module and the `__file__` variable provided @@ -101,12 +90,10 @@ look at each function individually in this section. :: def load_image(name, colorkey=None, scale=1): fullname = os.path.join(data_dir, name) image = pygame.image.load(fullname) + image = image.convert() - size = image.get_size() - size = (size[0] * scale, size[1] * scale) - image = pygame.transform.scale(image, size) + image = pygame.transform.scale_by(image, scale) - image = image.convert() if colorkey is not None: if colorkey == -1: colorkey = image.get_at((0, 0)) @@ -130,9 +117,8 @@ call to the `convert()` function. This makes a new copy of a Surface and convert its color format and depth to match the display. This means blitting the image to the screen will happen as quickly as possible. -We then scale the image, using the :func:`pygame.transform.scale` function. -This function takes a Surface and the size it should be scaled to. To scale -by a scalar, we can get the size and scale the x and y by the scalar. +We then scale the image, using the :func:`pygame.transform.scale_by` function. +This function takes a Surface and the scale factor it should be scaled of. Last, we set the colorkey for the image. If the user supplied an argument for the colorkey argument we use that value as the colorkey for the image. @@ -146,7 +132,7 @@ that color for the colorkey. :: def play(self): pass - if not pygame.mixer or not pygame.mixer.get_init(): + if not pygame.mixer.get_init(): return NoneSound() fullname = os.path.join(data_dir, name) @@ -156,7 +142,7 @@ that color for the colorkey. :: Next is the function to load a sound file. The first thing this function -does is check to see if the :mod:`pygame.mixer` module was imported correctly. +does is check to see if the :mod:`pygame.mixer` module was initialized. If not, it returns a small class instance that has a placeholder play method. This will act enough like a normal Sound object for this game to run without any extra error checking. @@ -335,7 +321,7 @@ Next we set up the display graphics mode. Note that the :mod:`pygame.display` module is used to control all the display settings. In this case we are asking for a 1280 by 480 window, with the ``SCALED`` display flag. This automatically scales up the window for displays much larger than the -window. +window. Last we set the window title and turn off the mouse cursor for our window. Very basic to do, and now we have a small black window ready to @@ -367,15 +353,12 @@ input formats. See the :mod:`pygame.Color` for all the color formats. Put Text On The Background, Centered ------------------------------------ -Now that we have a background surface, lets get the text rendered to it. We -only do this if we see the :mod:`pygame.font` module has imported properly. -If not, we just skip this section. :: +Now that we have a background surface, lets get the text rendered to it. :: - if pygame.font: - font = pygame.font.Font(None, 64) - text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10)) - textpos = text.get_rect(centerx=background.get_width() / 2, y=10) - background.blit(text, textpos) + font = pygame.font.Font(None, 64) + text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10)) + textpos = text.get_rect(centerx=background.get_width() / 2, y=10) + background.blit(text, textpos) As you see, there are a couple steps to getting this done. First we must create the font object and render it into a new surface. We then find @@ -423,24 +406,20 @@ Prepare Game Object Here we create all the objects that the game is going to need. :: - + whiff_sound = load_sound("whiff.wav") punch_sound = load_sound("punch.wav") chimp = Chimp() fist = Fist() - allsprites = pygame.sprite.RenderPlain((chimp, fist)) - clock = pygame.time.Clock() + all_sprites = pygame.sprite.Group((chimp, fist)) + clock = pygame.Clock() First we load two sound effects using the `load_sound` function we defined above. Then we create an instance of each of our sprite classes. And lastly we create a sprite :class:`Group ` which will contain all our sprites. -We actually use a special sprite group named :class:`RenderPlain -`. This sprite group can draw all the sprites it -contains to the screen. It is called `RenderPlain` because there are actually -more advanced Render groups. But for our game, we just need simple drawing. We -create the group named "allsprites" by passing a list with all the sprites that +We create the group named "all_sprites" by passing a list with all the sprites that should belong in the group. We could later on add or remove sprites from this group, but in this game we won't need to. @@ -501,7 +480,7 @@ Update the Sprites :: - allsprites.update() + all_sprites.update() Sprite groups have an `update()` method, which simply calls the update method for all the sprites it contains. Each of the objects will move around, depending @@ -515,16 +494,14 @@ Draw The Entire Scene Now that all the objects are in the right place, time to draw them. :: screen.blit(background, (0, 0)) - allsprites.draw(screen) + all_sprites.draw(screen) pygame.display.flip() The first blit call will draw the background onto the entire screen. This erases everything we saw from the previous frame (slightly inefficient, but good enough for this game). Next we call the `draw()` method of the sprite -container. Since this sprite container is really an instance of the "RenderPlain" -sprite group, it knows how to draw our sprites. Lastly, we `flip()` the contents -of pygame's software double buffer to the screen. This makes everything we've -drawn visible all at once. +container. Lastly, we `flip()` the contentsof pygame's software double buffer +to the screen. This makes everything we've drawn visible all at once. Game Over diff --git a/docs/reST/tutorials/en/import-init.rst b/docs/reST/tutorials/en/import-init.rst index ecccc661a0..3219702d15 100644 --- a/docs/reST/tutorials/en/import-init.rst +++ b/docs/reST/tutorials/en/import-init.rst @@ -5,7 +5,7 @@ ******************************************** Pygame Tutorials - Import and Initialize ******************************************** - + Import and Initialize ===================== diff --git a/docs/reST/tutorials/en/intro-to-camera.rst b/docs/reST/tutorials/en/intro-to-camera.rst index 771926990f..291c54d97b 100644 --- a/docs/reST/tutorials/en/intro-to-camera.rst +++ b/docs/reST/tutorials/en/intro-to-camera.rst @@ -28,7 +28,7 @@ for the full API. that use v4l2 on Linux. There is support for other platforms via OpenCV, but this guide will focus on the native module. Most of the code will be valid for other platforms, but certain things like controls will not work. - The module is also marked as **EXPERIMENTAL**, meaning the API could + The module is also marked as **EXPERIMENTAL**, meaning the API could change in subsequent versions. @@ -99,29 +99,29 @@ we will be supplying the camera with the same surface to use each time. :: self.size = (640,480) # create a display surface. standard pygame stuff self.display = pygame.display.set_mode(self.size, 0) - + # this is the same as what we saw before self.clist = pygame.camera.list_cameras() if not self.clist: raise ValueError("Sorry, no cameras detected.") self.cam = pygame.camera.Camera(self.clist[0], self.size) self.cam.start() - + # create a surface to capture to. for performance purposes # bit depth is the same as that of the display surface. self.snapshot = pygame.surface.Surface(self.size, 0, self.display) - + def get_and_flip(self): # if you don't want to tie the framerate to the camera, you can check # if the camera has an image ready. note that while this works # on most cameras, some will never return true. if self.cam.query_image(): self.snapshot = self.cam.get_image(self.snapshot) - + # blit it to the display surface. simple! self.display.blit(self.snapshot, (0,0)) pygame.display.flip() - + def main(self): going = True while going: @@ -131,7 +131,7 @@ we will be supplying the camera with the same surface to use each time. :: # close the camera safely self.cam.stop() going = False - + self.get_and_flip() diff --git a/docs/reST/tutorials/en/intro-to-pygame.rst b/docs/reST/tutorials/en/intro-to-pygame.rst index acb43a56ab..95e6a44764 100644 --- a/docs/reST/tutorials/en/intro-to-pygame.rst +++ b/docs/reST/tutorials/en/intro-to-pygame.rst @@ -118,8 +118,8 @@ returns us a Surface with the ball data. The Surface will keep any colorkey or alpha transparency from the file. After loading the ball image we create a variable named ballrect. Pygame comes with a convenient utility object type named :class:`Rect `, -which represents a rectangular area. Later, in the animation part of -the code, we will see what the *Rect* objects can do. +which represents a rectangular area (which can be used to manage the position of an +object in the screen). At this point, :clr:`line 13`, our program is initialized and ready to run. Inside an infinite loop we @@ -152,7 +152,7 @@ Drawing of images is handled by the :meth:`Surface.blit() ` method. A blit basically means copying pixel colors from one image to another. We pass the blit method a source :class:`Surface ` -to copy from, and a position to place the source onto the destination. +to copy from, and a position (which can be a :class:`Rect ` object or a pair of numbers e.g. (50.0, 25.0)), to place the source onto the destination. The last thing we need to do is actually update the visible display. Pygame manages the display with a double buffer. When we are finished diff --git a/docs/reST/tutorials/en/intro-to-sprites.rst b/docs/reST/tutorials/en/intro-to-sprites.rst index 53d6978293..9ae4e77351 100644 --- a/docs/reST/tutorials/en/intro-to-sprites.rst +++ b/docs/reST/tutorials/en/intro-to-sprites.rst @@ -374,4 +374,3 @@ both look similar, this is the most flexible way to "see" the difference.) You should go through the code for the sprite module. While the code is a bit "tuned", it's got enough comments to help you follow along. There's even a TODO section in the source if you feel like contributing. - diff --git a/docs/reST/tutorials/en/intro-to-surfarray.rst b/docs/reST/tutorials/en/intro-to-surfarray.rst index 120e844d6d..df6756440c 100644 --- a/docs/reST/tutorials/en/intro-to-surfarray.rst +++ b/docs/reST/tutorials/en/intro-to-surfarray.rst @@ -527,7 +527,7 @@ There is one very useful function though. .. function:: surfarray.blit_array(surface, array) :noindex: - + This will transfer any type of 2D or 3D surface array onto a Surface of the same dimensions. This surfarray blit will generally be faster than assigning an array to a diff --git a/docs/reST/tutorials/en/move-it.rst b/docs/reST/tutorials/en/move-it.rst index b489a6599d..f81b561702 100644 --- a/docs/reST/tutorials/en/move-it.rst +++ b/docs/reST/tutorials/en/move-it.rst @@ -383,8 +383,8 @@ user asks us to stop. :: What this code simply does is, first loop forever, then check if there are -any events from the user. We exit the program if the user presses the close -button on the window. After we've checked all the events we move and draw +any events from the user. We exit the program if the user presses the close +button on the window. After we've checked all the events we move and draw our game objects. (We'll also erase them before they move, too) @@ -471,7 +471,7 @@ our move function under our GameObject class. :: ... if down: ... self.pos.top += self.speed ... if up: - ... self.pos.top -= self.speed + ... self.pos.top -= self.speed ... if self.pos.right > WIDTH: ... self.pos.left = 0 ... if self.pos.top > HEIGHT-SPRITE_HEIGHT: @@ -483,7 +483,7 @@ our move function under our GameObject class. :: There's certainly a lot more going on here, so let's take it one step at a time. First, we've added some default values into the move function, declared as up, -down, left, and right. These booleans will allow us to specifically select a +down, left, and right. These booleans will allow us to specifically select a direction that the object is moving in. The first part, where we go through and check True for each variable, is where we will add to the position of the object, much like before. Right controls horizontal, and top controls vertical positions. @@ -505,7 +505,7 @@ We've already seen that pygame has event handling, and we know that KEYDOWN is an event in this loop. We could, under KEYDOWN, assert the key press matches an arrow key, where we would then call move. However, this movement will only occur once every time a key is pressed, and it therefore will be extremely choppy and -unpleasant. +unpleasant. For this, we can use pygame.key.get_pressed(), which returns a list of all keys, and whether or not they are currently pressed. Since we want these key presses @@ -571,7 +571,7 @@ sure we understand everything. :: A few things not mentioned earlier: we load in a second image and call it entity, and we use that for all objects that aren't the player, which uses the player -image defined earlier. +image defined earlier. And that's all there is to it! Now we have a fully functional player object that is controlled using the arrow keys! @@ -597,4 +597,4 @@ Lastly, you can feel free to come to the pygame mailing list or chatroom with any questions on this stuff. There's always folks on hand who can help you out with this sort of business. -Lastly, have fun, that's what games are for! \ No newline at end of file +Lastly, have fun, that's what games are for! diff --git a/docs/reST/tutorials/en/newbie-guide.rst b/docs/reST/tutorials/en/newbie-guide.rst index e1b4504abc..1265859bc7 100644 --- a/docs/reST/tutorials/en/newbie-guide.rst +++ b/docs/reST/tutorials/en/newbie-guide.rst @@ -495,4 +495,4 @@ the author of Twitch, an entirely average pygame arcade game.* .. _Game Programming Patterns: https://gameprogrammingpatterns.com/contents.html .. _Why Pygame is Slow: https://blubberquark.tumblr.com/post/630054903238262784/why-pygame-is-slow .. _cProfile: https://docs.python.org/3/library/profile.html -.. _SnakeViz: https://jiffyclub.github.io/snakeviz/ \ No newline at end of file +.. _SnakeViz: https://jiffyclub.github.io/snakeviz/ diff --git a/docs/reST/tutorials/en/tom-games4.rst b/docs/reST/tutorials/en/tom-games4.rst index 98048b8515..43818884bf 100644 --- a/docs/reST/tutorials/en/tom-games4.rst +++ b/docs/reST/tutorials/en/tom-games4.rst @@ -101,7 +101,7 @@ This is a really nice feature of Python - class inheritance. Now the ``Ball`` class has all of the functions that come with the ``Sprite`` class, and any object instances of the ``Ball`` class will be registered by Pygame as sprites. Whereas with text and the background, which don't move, it's OK to blit the object onto the background, Pygame handles sprite objects in a different manner, which you'll see when we -look at the whole program's code. +look at the whole program's code. Basically, you create both a ball object, and a sprite object for that ball, and you then call the ball's update function on the sprite object, thus updating the sprite. Sprites also give you sophisticated ways of determining if two objects have collided. @@ -137,7 +137,6 @@ Pygame doesn't support vectors itself, and we can only move the ball by moving i trigonometry, and can be done with the formulae shown in the diagram. If you've studied elementary trigonometry before, none of this should be news to you. But just in case you're forgetful, here are some -useful formulae to remember, that will help you visualise the angles (I find it easier to visualise angles in degrees than in radians!) +useful formulae to remember, that will help you visualise the angles (I find it easier to visualise angles in degrees than in radians!) .. image:: ../assets/tom_formulae.png - diff --git a/docs/reST/tutorials/en/tom-games5.rst b/docs/reST/tutorials/en/tom-games5.rst index 31cc531edd..d8353cf565 100644 --- a/docs/reST/tutorials/en/tom-games5.rst +++ b/docs/reST/tutorials/en/tom-games5.rst @@ -85,7 +85,7 @@ will be set back to "still", and the ``movepos`` attribute will be set back to [ 5.1.1. Diversion 3: Pygame events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - + So how do we know when the player is pushing keys down, and then releasing them? With the Pygame event queue system of course! It's a really easy system to use and understand, so this shouldn't take long :) You've already seen the event queue in action in the basic Pygame program, where it was used to check if the user was quitting the application. The code for moving the bat is about as simple diff --git a/docs/reST/tutorials/es/ChimpanceLineaporLinea.rst b/docs/reST/tutorials/es/ChimpanceLineaporLinea.rst index 2d0d70f38c..f4d65642b2 100644 --- a/docs/reST/tutorials/es/ChimpanceLineaporLinea.rst +++ b/docs/reST/tutorials/es/ChimpanceLineaporLinea.rst @@ -23,22 +23,22 @@ Introducción ------------ Entre los ejemplos de *pygame* hay un ejemplo simple llamado "chimp" (chimpancé). -Este ejemplo simula un mono golpeable que se mueve alrededor de la pantalla +Este ejemplo simula un mono golpeable que se mueve alrededor de la pantalla con promesas de riquezas y recomepensas. El ejemplo en sí es muy simple y acarrea -poco código de comprobación de error. Como modelo de programa, Chimp demuestra muchas +poco código de comprobación de error. Como modelo de programa, Chimp demuestra muchas de las bondades de pygame, como por ejemplo crear una ventana, cargar imágenes y sonidos, representar texto, y manejo de eventos básicos y del mouse. El programa y las imagenes se pueden encontrar dentro de la fuente estándar -de distribución de pygame. Se puede ejecutar al correr `python -m pygame.examples.chimp` +de distribución de pygame. Se puede ejecutar al correr `python -m pygame.examples.chimp` en la terminal. -Este tutorial atravesará el código bloque a bloque, explicando cómo -funciona el mismo. Además, se hará mención de cómo se puede +Este tutorial atravesará el código bloque a bloque, explicando cómo +funciona el mismo. Además, se hará mención de cómo se puede mejorar el código y qué errores de comprobación podrían ser de ayuda. Este tutorial es excelente para aquellas personas que están buscando -una primera aproximación a códigos de *pygame*. Una vez que *pygame* +una primera aproximación a códigos de *pygame*. Una vez que *pygame* esté completamente instalado, podrás encontrar y ejecutar la demostración del chimpancé para ti mismo en el directorio de ejemplos. @@ -58,7 +58,7 @@ Importación de Módulos ---------------------- Este es el código que importa todos los módulos necesarios del programa. -Este código también comprueba la disponibilidad de algunos de los módulos opcionales +Este código también comprueba la disponibilidad de algunos de los módulos opcionales de pygame. :: # Import Modules @@ -75,7 +75,7 @@ de pygame. :: Primero, se importa el módulo estándar de python "os" (sistema operativo). -Esto permite hacer cosas como crear rutas de archivos independientes de la +Esto permite hacer cosas como crear rutas de archivos independientes de la platforma. En la sigueinte línea, se importa el paquete de pygame. En nuestro caso, @@ -84,14 +84,14 @@ ser referenciadas desde el espacio de nombres ``pg``. Algunos de los módulos de pygame son opcionales, y si no fueran encontrados, la evaluación será ``False``. Es por eso que decidimos mostrar (print) un agradable -mensaje de advertencia si los módulos :mod:`font` o +mensaje de advertencia si los módulos :mod:`font` o :mod:`mixer ` no están disponibles. (Aunque estos solo podrían no estar disponibles en situaciones poco comunes). Finalmente, se preparan dos rutas que serán usadas para el resto del código. -Una de ellas es ``main_dir``, que usa el módulo `os.path` y la variable `__file__` -asignada por Python para localizar el archivo de juegos de python, y extraer la carpeta -desde esa ruta. Luego, ésta prepara la ruta ``data_dir`` para indicarle a las +Una de ellas es ``main_dir``, que usa el módulo `os.path` y la variable `__file__` +asignada por Python para localizar el archivo de juegos de python, y extraer la carpeta +desde esa ruta. Luego, ésta prepara la ruta ``data_dir`` para indicarle a las funciones de carga exactamente dónde buscar. @@ -118,7 +118,7 @@ En esta sección examinaremos cada función individualmente. :: Esta función toma el nombre de la imagen a cargar. Opcionalmente, también -toma un argumento que puede usar para definir la clave de color (colorkey) de +toma un argumento que puede usar para definir la clave de color (colorkey) de la imagen, y un argumento para determinar la escala de la imagen. La clave de color se usa en la gráfica para representar un color en la imagen que es transparente. @@ -133,20 +133,20 @@ El paso siguiente es cargar la imagen usando la función :func:`pygame.image.loa Luego de que la imagen se cargue, llamamos a la función `convert()`. Al hacer esto se crea una nueva copia del Surface y convierte su formato de color y la profundidad, de tal forma que coincida con el mostrado. -Esto significa que el dibujo (blitting) de la imagen a la pantalla sucederá +Esto significa que el dibujo (blitting) de la imagen a la pantalla sucederá lo más rápido posible. -Luego, usando la función :func:`pygame.transform.scale` se definirá el tamaño de +Luego, usando la función :func:`pygame.transform.scale` se definirá el tamaño de la imagen. Esta función toma una Surface y el tamaño al cual se debería adecuar. Para darle tamaño con números escalares, se puede tomar la medida y determinar las dimensiones *x* e *y* con número escalar. Finalmente, definimos la clave de color para la imagen. Si el usuario suministró un valor para el parametro de la clave de color, usamos ese valor como la clave -de color de la imagen. Usualmente, éste sería un valor de color RGB -(red-green-blue = rojo-verde-azul), como (255, 255, 255) para el color blanco. -También es posible pasar el valor -1 como la clave de color. En este caso, la -función buscará el color en el píxel de arriba a la izquierda de la imagen, +de color de la imagen. Usualmente, éste sería un valor de color RGB +(red-green-blue = rojo-verde-azul), como (255, 255, 255) para el color blanco. +También es posible pasar el valor -1 como la clave de color. En este caso, la +función buscará el color en el píxel de arriba a la izquierda de la imagen, y lo usará para la clave de color. :: def load_sound(name): @@ -163,14 +163,14 @@ y lo usará para la clave de color. :: return sound -La anterior, es la función para cargar un archivo de sonido. Lo primero que hace +La anterior, es la función para cargar un archivo de sonido. Lo primero que hace esta función es verificar si el módulo :mod:`pygame.mixer` se importó correctamente. En caso de no ser así, la función va a devolver una instancia de reproducción de un -sonido de error. Esto obrará como un objeto de Sonido normal para que el juego se +sonido de error. Esto obrará como un objeto de Sonido normal para que el juego se ejecute sin ningún error de comprobación extra. -Esta funcion es similar a la función de carga de imagen, pero maneja diferentes problemas. -Primero, creamos una ruta completa al sonido de la imagen y cargamos el archivo +Esta funcion es similar a la función de carga de imagen, pero maneja diferentes problemas. +Primero, creamos una ruta completa al sonido de la imagen y cargamos el archivo de sonido. Luego, simplemente devolvemos el objeto de Sonido cargado. @@ -179,7 +179,7 @@ Clases de Objetos para Juegos ----------------------------- En este caso creamos dos clases (classes) que representan los objetos en nuestro juego. -Casi toda la logica del juego se organiza en estas dos clases. A continuación +Casi toda la logica del juego se organiza en estas dos clases. A continuación las revisaremos de a una. :: class Fist(pg.sprite.Sprite): @@ -211,25 +211,25 @@ las revisaremos de a una. :: self.punching = False -En este caso, creamos una clase (class) que representa el puño del jugador. Esta se -deriva de la clase `Sprite` incluida en el módulo :mod:`pygame.sprite`. La +En este caso, creamos una clase (class) que representa el puño del jugador. Esta se +deriva de la clase `Sprite` incluida en el módulo :mod:`pygame.sprite`. La función `__init__` es llamada cuando se crean nuevas instancias de este clase. Esto le permite a la función `__init__` del Sprite preparar nuestro objeto para ser usado como una imagen (sprite). Este juego usa uno de los dibujos de sprite de la clase de Grupo. Estas clases pueden dibujar sprites que tienen un atributo "imagen" y uno "rect". Al cambiar -simplemente estos dos atributos, el compilador (renderer) dibujará la imagen actual +simplemente estos dos atributos, el compilador (renderer) dibujará la imagen actual en la posición actual. -Todos los sprites tienen un método `update()`. Esta función es tipicamente -llamada una vez por cuadro. Es en esta función donde se debería colocar el código -que mueva y actualice las variables para el sprite. El método de `update()` para el -movimiento del puño, mueve el puño al lugar donde se encuentre el puntero del mouse. -Asímismo, compensa sutilmente la posición del puño sobre el objeto, si el puño está +Todos los sprites tienen un método `update()`. Esta función es tipicamente +llamada una vez por cuadro. Es en esta función donde se debería colocar el código +que mueva y actualice las variables para el sprite. El método de `update()` para el +movimiento del puño, mueve el puño al lugar donde se encuentre el puntero del mouse. +Asímismo, compensa sutilmente la posición del puño sobre el objeto, si el puño está en condición de golpear. -Las siguientes dos funciones `punch()` y `unpunch()` cambian la condición de +Las siguientes dos funciones `punch()` y `unpunch()` cambian la condición de golpeado del puño. El método `punch()` también devuelve un valor verdadero si el puño está chocando con el sprite objetivo. :: @@ -282,20 +282,20 @@ el puño está chocando con el sprite objetivo. :: self.original = self.image -Si bien la clase (class) `Chimp` está haciendo un poco más de trabajo que el +Si bien la clase (class) `Chimp` está haciendo un poco más de trabajo que el puño, no resulta mucho más complejo. Esta clase moverá al chimpancé hacia adelante -y hacia atrás, por la pantalla. Cuando el mono es golpeado, él girará -con un efecto de emoción. Esta clase también es derivada de la base de clases -:class:`Sprite ` y es iniciada de igual manera que el puño. +y hacia atrás, por la pantalla. Cuando el mono es golpeado, él girará +con un efecto de emoción. Esta clase también es derivada de la base de clases +:class:`Sprite ` y es iniciada de igual manera que el puño. Mientras se inicia, la clase también establece el atributo "area" para que sea del tamaño de la pantalla de visualización. La función `update` para el chimpancé simplemente se fija en el estado actual -del mono. Esta puede ser "dizzy" (mareado), la cual sería verdadera si el mono +del mono. Esta puede ser "dizzy" (mareado), la cual sería verdadera si el mono está girando a causa del golpe. La función llama al método `_spin` o `_walk`. Estas funciones son prefijadas con un guión bajo, lo cual en el idioma estándar de python sugiere que estos métodos deberían ser solo usados por la clase `Chimp`. -Podríamos incluso hasta escribirlas con un doble guión bajo, lo cual indicaría a +Podríamos incluso hasta escribirlas con un doble guión bajo, lo cual indicaría a python que realmente intente hacerlas un método privado, pero no necesitamos tal protección. :) @@ -307,13 +307,13 @@ un efecto crudo que hace que el mono se vea como si estuviera cambiando de dirección. El método `_spin` es llamado cuando el mono está actualmente en estado "dizzy" -(mareado). El atributo 'dizzy' es usado para guardar el monto de rotación. +(mareado). El atributo 'dizzy' es usado para guardar el monto de rotación. Cuando el mono ha rotado por completo en su eje (360 grados) se resetea la imagen a la versión original no rotada. Antes de llamar a la función :func:`pygame.transform.rotate`, verás que el código hace una referencia local -a la función simplemente llamanda "rotate". No hay ncesidad de hacer eso en -este ejemplo, aquí fue realizada para mantener la siguiente línea un poco -más corta. Notese que al llamar a la función `rotate`, se está siempre rotando +a la función simplemente llamanda "rotate". No hay ncesidad de hacer eso en +este ejemplo, aquí fue realizada para mantener la siguiente línea un poco +más corta. Notese que al llamar a la función `rotate`, se está siempre rotando la imagen original del mono. Cuando rotamos, hay un pequeña pérdida de calidad. Rotar repetidamente la misma imagen genera que la calidad se deteriore cada vez más. Esto se debe a que las esquinas de la imagen van a haber sido rotadas de más, @@ -321,8 +321,8 @@ causando que la imagen se haga más grande. Nos aseguramos que la nueva imagen coincida con el centro de la vieja imagen, para que de esta forma se rote sin moverse. -El último método es `punched()` el cual indica al sprite que entre en un estado de -mareo. Esto causará que la imagen empice a girar. Además, también crea una copia de +El último método es `punched()` el cual indica al sprite que entre en un estado de +mareo. Esto causará que la imagen empice a girar. Además, también crea una copia de la actual imagen llamada "original". @@ -344,19 +344,19 @@ La primera línea para inicializar *pygame* realiza algo de trabajo por nosotros Verifica a través del módulo importado *pygame* e intenta inicializar cada uno de ellos. Es posible volver y verificar que los módulos que fallaron al iniciar, pero no vamos a molestarnos acá con eso. También es posible tomar mucho más control -e inicializar cada módulo en especifico, uno a uno. Ese tipo de control no es +e inicializar cada módulo en especifico, uno a uno. Ese tipo de control no es necesario generalmente, pero está disponible en caso de ser deseado. -Luego, se configura el modo de visualización de gráficos. Notse que el módulo +Luego, se configura el modo de visualización de gráficos. Notse que el módulo :mod:`pygame.display` es usado para controlar todas las configuraciones de visualización. En este caso nosotros estamos buscando una ventana 1280x480, con ``SCALED``, que es la señal de visualización (display flag) -Esto aumenta proporcionalmente la ventana de visualización (display) más grande que la +Esto aumenta proporcionalmente la ventana de visualización (display) más grande que la ventana. (window) Por último, establecemos el título de la ventana y apagamos el cursor del mouse para nuestra ventana. Es una acción básica y ahora tenemos una pequeña ventana negra -que está lista para nuestras instrucciones (bidding) u ofertas. Generalmente, el cursor +que está lista para nuestras instrucciones (bidding) u ofertas. Generalmente, el cursor se mantiene visible por default, asi que no hay mucha necesidad de realmente establecer este estado a menos que querramos esconderlo. @@ -373,21 +373,21 @@ El primer paso es crear el Surface. :: background.fill((170, 238, 187)) Esto crea el nuevo surface, que en nuestro caso, es del mismo tamaño que -la ventana de visualización. Notese el llamado extra a `convert()` luego +la ventana de visualización. Notese el llamado extra a `convert()` luego de crear la Surface. La función `convert()` sin argumentos es para asegurarnos que nuestro fondo sea del mismo formato que la ventana de visualización, lo cual nos va a brindar resultados más rápidos. -Lo que nosotros hicimos también, fue rellenar el fondo con un color verduzco. -La función `fill()` suele tomar como argumento tres instancias de colores RGB, -pero soporta muchos formatos de entrada. Para ver todos los formatos de color +Lo que nosotros hicimos también, fue rellenar el fondo con un color verduzco. +La función `fill()` suele tomar como argumento tres instancias de colores RGB, +pero soporta muchos formatos de entrada. Para ver todos los formatos de color veasé :mod:`pygame.Color`. Centrar Texto en el Fondo ------------------------- -Ahora que tenemos el surface del fondo, vamos a representar el texto en él. +Ahora que tenemos el surface del fondo, vamos a representar el texto en él. Nosotros solo haremos esto si vemos que el módulo :mod:`pygame.font` se importó correctamente. De no ser así, hay que saltear esta sección. :: @@ -402,7 +402,7 @@ crear la fuente del objeto y renderizarlo (representarlo) en una nueva Surface. Luego, buscamos el centro de esa nueva surface y lo pegamos (blit) al fondo. La fuente es creada con el constructor `Font()` del módulo `font`. Generalmente, -uno va a poner el nombre de la fuente TrueType en esta función, pero también se +uno va a poner el nombre de la fuente TrueType en esta función, pero también se puede poner `None`, como hicimos en este caso, y entonces se usará la fuente por predeterminada. El constructor `Font` también necesita la información del tamaño de la fuente que se quiere crear. @@ -412,8 +412,8 @@ Luego vamos a represetar (renderizar) la fuente en la nueva surface. La función En este caso, también le estamos pidiendo al render que cree un texto suavizado (para un lindo efecto de suavidad en la apariencia) y que use un color gris oscuro. -Lo siguiente que necesitamos es encontrar la posición la posición central, para -colocar el texto en el centro de la pantalla. Creamos un objeto "Rect" de las +Lo siguiente que necesitamos es encontrar la posición la posición central, para +colocar el texto en el centro de la pantalla. Creamos un objeto "Rect" de las dimensiones del texto, lo cual nos permite asignarlo fácilmente al centro de la pantalla. @@ -434,7 +434,7 @@ El blit se explica por sí mismo, pero ¿qué está haciendo esa rutina flip? En pygame, los cambios en la surface de visualización (display) no se hacen visibles inmediatamente. Normalmente, la pantalla debe ser actualizacada para que el usuario pueda ver los cambios realizados. En este caso la función `flip()` es perfecta para eso -porque se encarga de toda el área de la pantalla. +porque se encarga de toda el área de la pantalla. Preparar Objetos del Juego @@ -443,7 +443,7 @@ Preparar Objetos del Juego En este caso crearemos todos los objetos que el juego va a necesitar. :: - + whiff_sound = load_sound("whiff.wav") punch_sound = load_sound("punch.wav") chimp = Chimp() @@ -452,12 +452,12 @@ En este caso crearemos todos los objetos que el juego va a necesitar. clock = pg.time.Clock() Primero cargamos dos efectos de sonido usando la función `load_sound`, que se -encuentra definida en código arriba. Luego, creamos una instancia para cada -uno de los sprites de la clase. Por último, creamos el sprite +encuentra definida en código arriba. Luego, creamos una instancia para cada +uno de los sprites de la clase. Por último, creamos el sprite :class:`Group ` que va a contener todos nuestros sprites. Nosotros creamos el grupo llamado "allsprites" al pasar una lista con todos los sprites que deberían -pertenecer al grupo. Exise la posibilidad, si más adelante quisieramos, de agregar +pertenecer al grupo. Exise la posibilidad, si más adelante quisieramos, de agregar o sacar sprites de este grupo, pero para este juego no sería necesario. El objeto `clock` que creamos será usado para ayudar a controlar la frequencia de @@ -476,7 +476,7 @@ No hay mucho por acá, solo un loop infinito. :: Todos los juegos se ejecutan sobre una especie de loop. El orden usual de las cosas es verificar el estado de la computadora y la entrada de usuario, mover y actualizar -el estado de todos los objetos, y luego dibujarlos en la pantalla. Verás que este +el estado de todos los objetos, y luego dibujarlos en la pantalla. Verás que este ejemplo no es diferente. También haremos un llamado a nuestro objeto `clock`, que asegurará que nuestro juego @@ -501,14 +501,14 @@ Este es un caso extremandamente simple para trabajar la cola de eventos. :: elif event.type == pg.MOUSEBUTTONUP: fist.unpunch() -Primero obtenemos todos los Eventos (events) disponibles en pygame y los recorremos en loop. -Las primeras dos pruebas es para ver si el usuario dejó nuestro juego, o si +Primero obtenemos todos los Eventos (events) disponibles en pygame y los recorremos en loop. +Las primeras dos pruebas es para ver si el usuario dejó nuestro juego, o si presionó la tecla de escape. En estos casos, configuramos ``going`` en ``False``, permitiendonos salir del loop infinito. -A continuación, verificamos si se presionó o si se soltó el botón del mouse. En el +A continuación, verificamos si se presionó o si se soltó el botón del mouse. En el caso de que el botón se haya presionado, preguntamos al primer objeto si chocó con -el mono. Se reproduce el sonido apropiado, y si el mono fue golpeado, le decimos +el mono. Se reproduce el sonido apropiado, y si el mono fue golpeado, le decimos que empiece a girar (al hacer un llamado a su método `punched()` ) @@ -522,7 +522,7 @@ Actualizar los Sprites Los grupos de Sprite tienen un método `update()`, que simplemente llama al método de actualización para todos los sprites que contiene. Cada uno de los objetos se va a mover, dependiendo de cuál sea el estado en el que estén. Acá -es donde el mono se va a mover de un lado a otro, o va a girar un poco más +es donde el mono se va a mover de un lado a otro, o va a girar un poco más lejos si fue recientemente golpeado. @@ -530,17 +530,17 @@ lejos si fue recientemente golpeado. Dibujar la Escena Completa -------------------------- -Ahora que todos los objetos están en el lugar indicado, es el momento para +Ahora que todos los objetos están en el lugar indicado, es el momento para dibujarlos. :: screen.blit(background, (0, 0)) allsprites.draw(screen) pygame.display.flip() -La primera llamada de blit dibujará el fondo en toda la pantalla. Esto borra -todo lo que vimos en el cuadro anterior (ligeramente ineficiente, pero -suficientemnte bueno para este juego). A continuación, llamamos al método -`draw()` del contenedor de sprites. Ya que este contenedor de sprites es +La primera llamada de blit dibujará el fondo en toda la pantalla. Esto borra +todo lo que vimos en el cuadro anterior (ligeramente ineficiente, pero +suficientemnte bueno para este juego). A continuación, llamamos al método +`draw()` del contenedor de sprites. Ya que este contenedor de sprites es en realidad una instancia del grupo de sprites "DrawPlain", sabe como dibujar nuestros sprites. Por último, usamos el método `flip()` para voltear los contenidos del software de pygame. Se realiza el flip a través del cargado de la imagen en segundo @@ -555,6 +555,6 @@ El usuario ha salido del juego, hora de limpiar (clean up) :: pg.quit() Hacer la limpieza, el cleanup, de la ejecución del juego en *pygame* es extremandamente -simple. Ya que todas las variables son automáticamente destruidas, nosotros no +simple. Ya que todas las variables son automáticamente destruidas, nosotros no tenemos que hacer realmnete nada, únicamente llamar a `pg.quit()` que explicitamente hace la limpieza de las partes internas del pygame. diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/1.\355\224\204\353\241\244\353\241\234\352\267\270/\354\206\214\352\260\234.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/1.\355\224\204\353\241\244\353\241\234\352\267\270/\354\206\214\352\260\234.rst" index f0495254a4..b82626d373 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/1.\355\224\204\353\241\244\353\241\234\352\267\270/\354\206\214\352\260\234.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/1.\355\224\204\353\241\244\353\241\234\352\267\270/\354\206\214\352\260\234.rst" @@ -123,5 +123,3 @@ Contact: rumia0601@gmail.com (파이게임 게임의 예시- 배틀십) 요약하자면, 파이게임은 저급 수준(콘솔 환경은 예시 중 하나)의 게임 제작 프로그램과 고급 수준(게임 엔진은 예시 중 하나)의 게임 제작 프로그램의 장점을 모두 가진다는 것이다. 파이게임은 이 둘 사이의 좋은 연결점이 된다. 이것이 파이게임을 쓸 이유이다. 더 복잡한 게임 엔진을 최대한 활용해 게임을 만드는 1인 개발자가 목표가 아닌 이상 (빨리 그 게임 엔진을 배우는 것이 낫다!), 콘솔 환경용 게임이 아닌 더 발전된 환경에서 게임을 한번쯤은 코딩해 보고 싶다면 (물론, 푹 빠지면 계속 코딩하게 될 것이다!), 한번쯤은 파이게임을 시도해 볼만 하다. - - diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/2.\355\205\215\354\212\244\355\212\270 \354\266\234\353\240\245/\352\270\260\354\264\210 \355\205\234\355\224\214\353\246\277\352\263\274 \354\266\234\353\240\245.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/2.\355\205\215\354\212\244\355\212\270 \354\266\234\353\240\245/\352\270\260\354\264\210 \355\205\234\355\224\214\353\246\277\352\263\274 \354\266\234\353\240\245.rst" index f00dea835e..47173e2361 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/2.\355\205\215\354\212\244\355\212\270 \354\266\234\353\240\245/\352\270\260\354\264\210 \355\205\234\355\224\214\353\246\277\352\263\274 \354\266\234\353\240\245.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/2.\355\205\215\354\212\244\355\212\270 \354\266\234\353\240\245/\352\270\260\354\264\210 \355\205\234\355\224\214\353\246\277\352\263\274 \354\266\234\353\240\245.rst" @@ -89,7 +89,7 @@ Contact: rumia0601@gmail.com 우선, 무언가를 출력하기 위해선 소스코드가 어떻게 작성되어야 하는지 그 형식을 살펴보자. 소스코드는 4개의 부분으로 나눠질 수 있다. Header(#1-#2), Initial문(#3-#12), Always문(#13-#20), Event문(#16-#19)가 그것이다. Header에선, 모듈들을 import하는 작업이 실행된다. 여기에 import pygame, sys는 항상 필요하다. 이 프로젝트가 파이게임 프로젝트이며, 사용자가 프로그램을 종료하고 싶을 때 종료되어야 하기 때문에(실제로 #19에서 sys.exit()가 실행된다) 추가적인 설명이 필요 없는 당연한 문구이다. from pygame.locals import*는 #17에서의 QUIT같은 유용한 상수들을 선언 없이 사용하기 위해 거의 반필수적으로 필요하다. - + Initial문(무한 반복문 이전의 문장들)에선, 전역 변수가 한번만 초기화되거나 몇몇 함수가 한번만 호출된다. 주로 색상과 같은 전역 변수들이 가독성을 높이기 위해 초기화된다. 파이게임은 여러가지 색상을 사용하는 화려한 GUI임을 까먹어선 안된다. (게임이므로) 하나의 색상은 R값, G값, B값 3개의 구성 요소를 가진다. 그래서 색상 변수는 red = (255, 0, 0)와 같이 선언되어야 한다. pygame.init()과 같은 함수는 나중에 사용할 함수를 위해선 가장 앞서서 호출되어야 한다. (이 외의 함수들은 나중에 언급하겠다.) Always문(무한 반복문)에선, 전역 변수가 계속 업데이트되거나 몇몇 함수가 계속 호출된다. (물론, 조건문이 있는 경우 조건이 맞을 때만) pygame.display.update() 라는 함수는 일반적으로 다른 변수/함수의 처리가 끝난 이후에 호출되는데, 이 함수는 처리의 결과물들을 스크린(= 모니터)에 출력하는 함수이기 때문이다. 이 함수가 Always문 마지막에 실행되지 않으면, 출력되는 화면과 게임 내부 데이터가 서로 일치하지 않는 문제가 생길 수 있다. (이 외의 함수들은 나중에 언급하겠다.) @@ -117,7 +117,7 @@ Event문(모든 이벤트를 체크하는 반복문)에선, 특정 이벤트가 pygame.display.set_caption("Hello World Project") #7 myScreen = pygame.display.set_mode((640, 480)) #8 myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) #9 - myText = myTextFont.render("Hello World!", True, red, green) #10 + myText = myTextFont.render("Hello World!", True, red, green) #10 myTextArea = myText.get_rect() #11 myTextArea.center = (320, 240) #12 @@ -131,4 +131,3 @@ Event문(모든 이벤트를 체크하는 반복문)에선, 특정 이벤트가 sys.exit() #19 pygame.display.update() #20 - diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/3.\355\205\215\354\212\244\355\212\270 \354\235\264\353\217\231/\352\270\260\354\264\210 \354\262\230\353\246\254.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/3.\355\205\215\354\212\244\355\212\270 \354\235\264\353\217\231/\352\270\260\354\264\210 \354\262\230\353\246\254.rst" index ef2d2d8cff..398ed8149f 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/3.\355\205\215\354\212\244\355\212\270 \354\235\264\353\217\231/\352\270\260\354\264\210 \354\262\230\353\246\254.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/3.\355\205\215\354\212\244\355\212\270 \354\235\264\353\217\231/\352\270\260\354\264\210 \354\262\230\353\246\254.rst" @@ -104,10 +104,10 @@ Contact: rumia0601@gmail.com red = (255,0,0) green = (0,255,0) pygame.init() - pygame.display.set_caption("Moving World Project") + pygame.display.set_caption("Moving World Project") myScreen = pygame.display.set_mode((640, 480)) myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) - myText = myTextFont.render("Moving World!", True, red, green) + myText = myTextFont.render("Moving World!", True, red, green) myTextArea = myText.get_rect() myTextArea.center = (320, 240) fpsClock = pygame.time.Clock() #1 @@ -138,7 +138,7 @@ Contact: rumia0601@gmail.com myTextArea.center = (320 + x, 240 + y) #10 - + myScreen.fill(white) myScreen.blit(myText, myTextArea) @@ -149,4 +149,3 @@ Contact: rumia0601@gmail.com pygame.display.update() fpsClock.tick(60) #11 - diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/4.\355\205\215\354\212\244\355\212\270 \354\241\260\354\242\205/\352\270\260\354\264\210 \354\236\205\353\240\245.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/4.\355\205\215\354\212\244\355\212\270 \354\241\260\354\242\205/\352\270\260\354\264\210 \354\236\205\353\240\245.rst" index 464e57b0c4..93e581ea70 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/4.\355\205\215\354\212\244\355\212\270 \354\241\260\354\242\205/\352\270\260\354\264\210 \354\236\205\353\240\245.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/4.\355\205\215\354\212\244\355\212\270 \354\241\260\354\242\205/\352\270\260\354\264\210 \354\236\205\353\240\245.rst" @@ -102,10 +102,10 @@ KEYDOWN은 “이 키는 이전에는 눌리지 않았지만, 지금은 눌렸 red = (255,0,0) green = (0,255,0) pygame.init() - pygame.display.set_caption("Controlling World Project") + pygame.display.set_caption("Controlling World Project") myScreen = pygame.display.set_mode((640, 480)) myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) - myText = myTextFont.render("Controlling World!", True, red, green) + myText = myTextFont.render("Controlling World!", True, red, green) myTextArea = myText.get_rect() myTextArea.center = (320, 240) fpsClock = pygame.time.Clock() @@ -138,7 +138,7 @@ KEYDOWN은 “이 키는 이전에는 눌리지 않았지만, 지금은 눌렸 elif event.key == K_RIGHT: moveDown = 0 moveRight = 1 - + if(moveRight == 1): #6 x = x + 10 elif(moveRight == -1): #7 @@ -149,4 +149,3 @@ KEYDOWN은 “이 키는 이전에는 눌리지 않았지만, 지금은 눌렸 y = y - 10 pygame.display.update() - diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/5.HP\353\260\224/\354\213\254\355\231\224 \354\266\234\353\240\245 \352\267\270\353\246\254\352\263\240 \354\213\254\355\231\224 \354\262\230\353\246\254.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/5.HP\353\260\224/\354\213\254\355\231\224 \354\266\234\353\240\245 \352\267\270\353\246\254\352\263\240 \354\213\254\355\231\224 \354\262\230\353\246\254.rst" index 375bbbf199..4111aca87f 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/5.HP\353\260\224/\354\213\254\355\231\224 \354\266\234\353\240\245 \352\267\270\353\246\254\352\263\240 \354\213\254\355\231\224 \354\262\230\353\246\254.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/5.HP\353\260\224/\354\213\254\355\231\224 \354\266\234\353\240\245 \352\267\270\353\246\254\352\263\240 \354\213\254\355\231\224 \354\262\230\353\246\254.rst" @@ -214,8 +214,8 @@ Contact: rumia0601@gmail.com import pygame, sys from pygame.locals import* - - maxHP = 10 + + maxHP = 10 white = (255,255,255) gray = (127,127,127) black = (0,0,0) @@ -232,18 +232,18 @@ Contact: rumia0601@gmail.com myTextArea = myText.get_rect() myTextArea.center = (width/2, height/2) #3 fpsClock = pygame.time.Clock() - + def main(): #4 HP = 5 - + while True: myText = myTextFont.render((str(HP) + "/" + str(maxHP)), True, red, gray) - + myScreen.fill(gray) - + myScreen.blit(myText, myTextArea) drawHP(HP) #5 - + for event in pygame.event.get(): if event.type == QUIT: pygame.quit() @@ -255,21 +255,21 @@ Contact: rumia0601@gmail.com elif event.key == K_DOWN: if HP != 0: HP = HP - 1 - + pygame.display.update() fpsClock.tick(60) - + def drawHP(HP): #6 r = int((height - 40) / maxHP) - + pygame.draw.rect(myScreen, black, (20, 20, 20, 20 + ((maxHP - 0.5) * r))) - + for i in range(maxHP): if HP >= (maxHP - i): pygame.draw.rect(myScreen, red, (20, 20 + (i * r), 20, r)) pygame.draw.rect(myScreen, white, (20, 20 + (i * r), 20, r), 1) - + return - + if __name__ == '__main__': #7 main() diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/6.\353\262\204\355\212\274\353\223\244/\354\213\254\355\231\224 \354\236\205\353\240\245 \352\267\270\353\246\254\352\263\240 \354\213\254\355\231\224 \354\266\234\353\240\245.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/6.\353\262\204\355\212\274\353\223\244/\354\213\254\355\231\224 \354\236\205\353\240\245 \352\267\270\353\246\254\352\263\240 \354\213\254\355\231\224 \354\266\234\353\240\245.rst" index a524c66cdf..907dc206af 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/6.\353\262\204\355\212\274\353\223\244/\354\213\254\355\231\224 \354\236\205\353\240\245 \352\267\270\353\246\254\352\263\240 \354\213\254\355\231\224 \354\266\234\353\240\245.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/6.\353\262\204\355\212\274\353\223\244/\354\213\254\355\231\224 \354\236\205\353\240\245 \352\267\270\353\246\254\352\263\240 \354\213\254\355\231\224 \354\266\234\353\240\245.rst" @@ -185,8 +185,8 @@ KEYDOWN이 사용되었지만, 아직도 이 게임이 완전한 GUI가 아닌 import pygame, sys from pygame.locals import* - - maxHP = 10 + + maxHP = 10 white = (255,255,255) gray = (127,127,127) black = (0,0,0) @@ -195,7 +195,7 @@ KEYDOWN이 사용되었지만, 아직도 이 게임이 완전한 GUI가 아닌 blue = (0,0,255) pygame.init() pygame.display.set_caption("Array buttons Project") - width = 640 + width = 640 height = 480 myScreen = pygame.display.set_mode((width, height)) myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) @@ -203,19 +203,19 @@ KEYDOWN이 사용되었지만, 아직도 이 게임이 완전한 GUI가 아닌 myTextArea = myText.get_rect() myTextArea.center = (width/2, height/2) fpsClock = pygame.time.Clock() - + def main(): HP = 5 - + while True: myText = myTextFont.render((str(HP) + "/" + str(maxHP)), True, red, gray) - + myScreen.fill(gray) - + myScreen.blit(myText, myTextArea) drawHP(HP) drawButtons() - + for event in pygame.event.get(): if event.type == QUIT: pygame.quit() @@ -234,28 +234,28 @@ KEYDOWN이 사용되었지만, 아직도 이 게임이 완전한 GUI가 아닌 HP = HP + 1 elif pygame.Rect(325, 425, 45, 45).collidepoint(x, y): if HP != 0: - HP = HP - 1 - + HP = HP - 1 + pygame.display.update() fpsClock.tick(60) - + def drawHP(HP): r = int((height - 40) / maxHP) - + pygame.draw.rect(myScreen, black, (20, 20, 20, 20 + ((maxHP - 0.5) * r))) - + for i in range(maxHP): if HP >= (maxHP - i): pygame.draw.rect(myScreen, red, (20, 20 + (i * r), 20, r)) pygame.draw.rect(myScreen, white, (20, 20 + (i * r), 20, r), 1) - + return - + def drawButtons(): r = 45 r_margin = 10 colors = [red, black] - + num = 2 margin = int((width - ((r * num) + (r_margin * (num - 1)))) / 2) for i in range(0, num): @@ -263,6 +263,6 @@ KEYDOWN이 사용되었지만, 아직도 이 게임이 완전한 GUI가 아닌 up = height - r - 10 pygame.draw.rect(myScreen, colors[i], (left, up, r, r)) pygame.draw.rect(myScreen, gray, (left + 2, up + 2, r - 4, r - 4), 2) - + if __name__ == '__main__': main() diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/7.\352\262\214\354\236\204\355\214\220/\354\213\254\355\231\224 \354\266\234\353\240\245 \352\267\270\353\246\254\352\263\240 \354\241\260\352\270\210 \353\215\224.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/7.\352\262\214\354\236\204\355\214\220/\354\213\254\355\231\224 \354\266\234\353\240\245 \352\267\270\353\246\254\352\263\240 \354\241\260\352\270\210 \353\215\224.rst" index 594d1ee977..a7ecf1050d 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/7.\352\262\214\354\236\204\355\214\220/\354\213\254\355\231\224 \354\266\234\353\240\245 \352\267\270\353\246\254\352\263\240 \354\241\260\352\270\210 \353\215\224.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/7.\352\262\214\354\236\204\355\214\220/\354\213\254\355\231\224 \354\266\234\353\240\245 \352\267\270\353\246\254\352\263\240 \354\241\260\352\270\210 \353\215\224.rst" @@ -116,8 +116,8 @@ generateboard 함수는 무작위로 만들어진 2차원 배열과 빨간 블 import pygame, sys, random from pygame.locals import* - - maxHP = 10 + + maxHP = 10 white = (255,255,255) gray = (127,127,127) black = (0,0,0) @@ -126,7 +126,7 @@ generateboard 함수는 무작위로 만들어진 2차원 배열과 빨간 블 blue = (0,0,255) pygame.init() pygame.display.set_caption("Red or Black Project") - width = 640 + width = 640 height = 480 myScreen = pygame.display.set_mode((width, height)) myTextFont = pygame.font.Font("HoonWhitecatR.ttf", 32) @@ -134,26 +134,26 @@ generateboard 함수는 무작위로 만들어진 2차원 배열과 빨간 블 myTextArea = myText.get_rect() myTextArea.center = (width/2, height/2) fpsClock = pygame.time.Clock() - + def main(): HP = 5 board, b_red, b_black = generateBoard(5,5) #1 - + while True: myText = myTextFont.render((str(HP) + "/" + str(maxHP)), True, red, gray) - + myScreen.fill(gray) - + myScreen.blit(myText, myTextArea) drawHP(HP) drawButtons() drawBoard(board) #2 - + for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() - + elif event.type == KEYDOWN: if event.key == K_UP: if HP != 10: @@ -163,7 +163,7 @@ generateboard 함수는 무작위로 만들어진 2차원 배열과 빨간 블 HP = HP - 1 elif event.type == MOUSEBUTTONUP: x, y = event.pos - + if pygame.Rect(270, 425, 45, 45).collidepoint(x, y): #3 if b_red >= b_black: if HP != 10: @@ -173,7 +173,7 @@ generateboard 함수는 무작위로 만들어진 2차원 배열과 빨간 블 if HP != 0: HP = HP - 1 board, b_red, b_black = generateBoard(5,5) - + elif pygame.Rect(325, 425, 45, 45).collidepoint(x, y): #4 if b_red <= b_black: if HP != 10: @@ -183,63 +183,63 @@ generateboard 함수는 무작위로 만들어진 2차원 배열과 빨간 블 if HP != 0: HP = HP - 1 board, b_red, b_black = generateBoard(5,5) - + pygame.display.update() fpsClock.tick(60) - + def drawHP(HP): r = int((height - 40) / maxHP) - + pygame.draw.rect(myScreen, gray, (20, 20, 20, 20 + ((maxHP - 0.5) * r))) - + for i in range(maxHP): if HP >= (maxHP - i): pygame.draw.rect(myScreen, blue, (20, 20 + (i * r), 20, r)) pygame.draw.rect(myScreen, white, (20, 20 + (i * r), 20, r), 1) - + return - + def drawButtons(): r = 45 r_margin = 10 colors = [red, black] - + num = 2 margin = int((width - ((r * num) + (r_margin * (num - 1)))) / 2) - + for i in range(0, num): left = margin + (i * r) + (i * r_margin) up = height - r - 10 pygame.draw.rect(myScreen, colors[i], (left, up, r, r)) - pygame.draw.rect(myScreen, gray, (left + 2, up + 2, r - 4, r - 4), 2) - + pygame.draw.rect(myScreen, gray, (left + 2, up + 2, r - 4, r - 4), 2) + def generateBoard(width, height): #5 board = [] b_red = 0 b_black = 0 - + for x in range(width): column = [] for y in range(height): column.append(random.randint(0, 1)) board.append(column) - + for x in range(width): for y in range(height): if(board[x][y] == 1): b_red = b_red + 1 elif(board[x][y] == 0): b_black = b_black + 1 - + return board, b_red, b_black - + def drawBoard(board): #6 r = 50 b_width = 5 b_height = 5 l_margin = int((width - (b_width * r)) / 2) u_margin = int((height - (b_height * r)) / 2) - + for x in range(5): for y in range(5): left = x * r + l_margin @@ -249,10 +249,10 @@ generateboard 함수는 무작위로 만들어진 2차원 배열과 빨간 블 elif board[x][y] == 0: color = black pygame.draw.rect(myScreen, color, (left, up, r, r)) - + left = l_margin up = u_margin pygame.draw.rect(myScreen, white, (left-1, up-1, r * 5 + 1, r * b_height + 1), 1) - + if __name__ == '__main__': main() diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/8.\354\227\220\355\225\204\353\241\234\352\267\270/\354\227\220\355\225\204\353\241\234\352\267\270.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/8.\354\227\220\355\225\204\353\241\234\352\267\270/\354\227\220\355\225\204\353\241\234\352\267\270.rst" index 1e56d39fff..20517bb99b 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/8.\354\227\220\355\225\204\353\241\234\352\267\270/\354\227\220\355\225\204\353\241\234\352\267\270.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/8.\354\227\220\355\225\204\353\241\234\352\267\270/\354\227\220\355\225\204\353\241\234\352\267\270.rst" @@ -15,4 +15,3 @@ Contact: rumia0601@gmail.com 결론이 무엇인가? 출력이 입력보다 크다는 것이다. 우리는 우리의 지식만으로 지식보다 더 폭넓은 프로그램을 구현할 수 있다. 또는 우리는 새로운 지식을 기존의 지식에 연결시키면서 습득할 수도 있다. 그것이 프로그래밍의 특성이다. 게임도 마찬가지이다. “난수”라는 개념은 모든 게임(이미 구현한 게임도 포함!)에서 대단히 중요한 개념이다. 난수까지 고려되었을 때 경우의 수는 매우 커지게 된다. 만약 하나의 난수가 다른 난수까지 영향을 미치게 된다면, “눈사태”와 같은 효과가 나게 된다. 그것이 게임이 흥미로운 이유이다. “난수”라는 개념은 소설, 음악, 영화 등은 가질 수 없는 게임만의 특성이다. 테트리스를 생각해 보아라. 알렉세이 파지트노프가 테트리스를 위해 얼만큼의 시간을 투자했을까? 이 시간이 35년 넘게 전세계 사람들이 플레이 한 시간보다 클까? 이것이 바로 프로그래밍과 게임이 갖는 두 특성이 완벽히 발휘된 예시이다. 그러므로, 게임을 만드는 것은 눈사태를 일으키는 것과 같다. 이제 아무 게임이나 만들 시간이다! 배우고, 활용하고, 시행 착오를 겪어 보자! - diff --git "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/\352\260\234\354\232\224.rst" "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/\352\260\234\354\232\224.rst" index e1c2b76460..6dfe791961 100644 --- "a/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/\352\260\234\354\232\224.rst" +++ "b/docs/reST/tutorials/ko/\353\271\250\352\260\204\353\270\224\353\241\235 \352\262\200\354\235\200\353\270\224\353\241\235/\352\260\234\354\232\224.rst" @@ -28,4 +28,4 @@ 게임판 :doc:`8 부 <8.에필로그/에필로그>` - 에필로그 \ No newline at end of file + 에필로그 diff --git a/docs/readmes/README.es.rst b/docs/readmes/README.es.rst index 2d2753d427..e8f3da48b4 100644 --- a/docs/readmes/README.es.rst +++ b/docs/readmes/README.es.rst @@ -1,4 +1,4 @@ -.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg +.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg :alt: pygame :target: https://pyga.me/ @@ -7,7 +7,7 @@ |PyPiVersion| |PyPiLicense| |Python3| |GithubCommits| |BlackFormatBadge| -`English`_ `简体中文`_ `Français`_ `فارسی`_ **Español** +`English`_ `简体中文`_ `繁體中文`_ `Français`_ `فارسی`_ **Español** `日本語`_ --------------------------------------------------------------------------------------------------------------------------------------------------- `Pygame`_ es una biblioteca multiplataforma, gratuita y de código abierto @@ -129,6 +129,7 @@ Versiones de dependencia: Licencia -------- +**Identificador de licencia:** LGPL-2.1-or-later La biblioteca se distribuye bajo la licencia `GNU LGPL version 2.1`_, que se puede encontrar en el archivo ``docs/LGPL.txt``. Nos reservamos el derecho de licenciar versiones futuras de esta biblioteca bajo una licencia diferente. @@ -164,6 +165,8 @@ Consulta docs/licenses para ver las licencias de dependencia. .. _GNU LGPL version 2.1: https://www.gnu.org/copyleft/lesser.html .. _简体中文: README.zh-cn.rst +.. _繁體中文: README.zh-tw.rst .. _English: ./../../README.rst .. _فارسی: README.fa.rst .. _Français: README.fr.rst +.. _日本語: README.ja.rst diff --git a/docs/readmes/README.fa.rst b/docs/readmes/README.fa.rst index 3c06c9db01..db704eeab0 100644 --- a/docs/readmes/README.fa.rst +++ b/docs/readmes/README.fa.rst @@ -1,4 +1,4 @@ -.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg +.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg :alt: pygame :target: https://pyga.me/ @@ -7,7 +7,7 @@ |PyPiVersion| |PyPiLicense| |Python3| |GithubCommits| |BlackFormatBadge| -`English`_ `简体中文`_ `Français`_ **فارسی** `Español`_ +`English`_ `简体中文`_ `繁體中文`_ `Français`_ **فارسی** `Español`_ `日本語`_ --------------------------------------------------------------------------------------------------- کتابخانه Pygame_ @@ -203,6 +203,8 @@ Dependencies (وابستگی ها) License ------- +LGPL-2.1-or-later **شناسه مجوز:** + این کتابخانه با استفاده از `GNU LGPL version 2.1`_ لایسنس شده است که در فایل @@ -250,5 +252,7 @@ License .. _English: ./../../README.rst .. _简体中文: README.zh-cn.rst +.. _繁體中文: README.zh-tw.rst .. _Français: README.fr.rst .. _Español: README.es.rst +.. _日本語: README.ja.rst diff --git a/docs/readmes/README.fr.rst b/docs/readmes/README.fr.rst index 419a4d4b50..6d3f7c055b 100644 --- a/docs/readmes/README.fr.rst +++ b/docs/readmes/README.fr.rst @@ -1,4 +1,4 @@ -.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg +.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg :alt: pygame :target: https://pyga.me/ @@ -7,7 +7,7 @@ |PyPiVersion| |PyPiLicense| |Python3| |GithubCommits| |BlackFormatBadge| -`English`_ `简体中文`_ **Français** `فارسی`_ `Español`_ +`English`_ `简体中文`_ `繁體中文`_ **Français** `فارسی`_ `Español`_ `日本語`_ --------------------------------------------------------------------------------------------------------------------------------------------------- `Pygame`_ est une bibliothèque multi-plateforme, libre et open-source @@ -158,6 +158,7 @@ Versions des dépendances: Licence ------- +**Identifiant de licence:** LGPL-2.1-or-later La bibliothèque est distribuée sous la licence `GNU LGPL version 2.1`_, qui peut être retrouvée dans le fichier ``docs/LGPL.txt``. Nous nous réservons @@ -202,6 +203,8 @@ Voir les docs/licences pour les licences des dépendances. .. _GNU LGPL version 2.1: https://www.gnu.org/copyleft/lesser.html .. _简体中文: README.zh-cn.rst +.. _繁體中文: README.zh-tw.rst .. _English: ./../../README.rst .. _فارسی: README.fa.rst .. _Español: README.es.rst +.. _日本語: README.ja.rst diff --git a/docs/readmes/README.ja.rst b/docs/readmes/README.ja.rst new file mode 100644 index 0000000000..674c28399c --- /dev/null +++ b/docs/readmes/README.ja.rst @@ -0,0 +1,239 @@ +.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg + :width: 800 + :alt: pygame + :target: https://pyga.me/ + + +|DocsStatus| +|PyPiVersion| |PyPiLicense| +|Python3| |GithubCommits| |BlackFormatBadge| + +`English` `简体中文`_ `繁體中文`_ `Français`_ `فارسی`_ `Español`_ **日本語** +--------------------------------------------------------------------------------------------------- + +Pygame_ は、Pythonを使ってビデオゲームのようなマルチメディアアプリケーションを +開発するための、フリーでオープンソースなクロスプラットフォームライブラリです。 +`Simple DirectMedia Layer library`_ や他のいくつかの有名なライブラリを使って、 +最も一般的な機能を抽象化して、これらのプログラムの作成をより直感的な作業にします。 + +このディストリビューションは **'pygame - Community Edition'** (略して 'pygame-ce')と呼ばれます。 + + +これは、以前のコア開発者たちによって作られた pygame プロジェクトのフォークであり、 +アップストリームの開発を継続できなくなるという困難な状況の後に作成されました。 +この新しいディストリビューションは、より頻繁なリリース、継続的なバグ修正や機能強化、 +そしてより民主的なガバナンスモデルを提供することを目指しています。 + +新しいコントリビュータは歓迎されています! + + +インストール +------------ + +:: + + pip install pygame-ce + + +ヘルプ +------ + +pygame を初めて使用する場合、すぐに開始できるはずです。 +Pygame には多くのチュートリアルや入門資料が付属しています。 +また、ライブラリ全体の完全なリファレンスドキュメントもあります。 +`docs page`_ でドキュメントを参照できます。 + +また、端末で ``python -m pygame.docs`` を実行することで、 +ドキュメントを参照することもできます。もしローカルのドキュメントが +見つからない場合は、オンラインのウェブサイトが代わりに表示されます。 + +オンラインドキュメントは、github の pygame の開発版に最新の状態で +追従しています。これは、あなたが使用している pygame のバージョンよりも +少し新しいかもしれません。最新の完全リリースにアップグレードするには、 +端末で ``pip install pygame-ce --upgrade`` を実行してください。 + + +最も重要なことは、examples ディレクトリに多くのプレイ可能な +小さなプログラムがあり、そのコードによって今すぐ使い始められることです。 + +すぐにコードを使い始めることができることです。 + + +ソースからのビルド +------------------ + +現在開発中の機能を使用したい場合や、pygame-ce に貢献したい場合は、 +pip インストールするのではなく、ソースコードからローカルで +pygame-ce をビルドする必要があります。 + +ソースからのインストールはかなり自動化されています。 +作業のほとんどは、pygame の依存関係をすべてコンパイルして +インストールすることです。それが終われば、 ``setup.py`` スクリプトを +実行してください。自動設定、ビルド、および pygame のインストールが +試みられます。 + +インストールとコンパイルについてのより多くの情報は、 +`Compilation wiki page`_ にあります。 + + +謝辞 +---- + +このライブラリの開発に貢献して下さったすべての人に感謝します。 + +Special thanks: + +* Marcus Von Appen: many changes, and fixes, 1.7.1+ freebsd maintainer +* Lenard Lindstrom: the 1.8+ windows maintainer, many changes, and fixes +* Brian Fisher for svn auto builder, bug tracker and many contributions +* Rene Dudfield: many changes, and fixes, 1.7+ release manager/maintainer +* Phil Hassey for his work on the pygame.org website +* DR0ID for his work on the sprite module +* Richard Goedeken for his smoothscale function +* Ulf Ekström for his pixel perfect collision detection code +* Pete Shinners: original author +* David Clark for filling the right-hand-man position +* Ed Boraas and Francis Irving: Debian packages +* Maxim Sobolev: FreeBSD packaging +* Bob Ippolito: macOS and OS X porting (much work!) +* Jan Ekhol, Ray Kelm, and Peter Nicolai: putting up with early design ideas +* Nat Pryce for starting our unit tests +* Dan Richter for documentation work +* TheCorruptor for his incredible logos and graphics +* Nicholas Dudfield: many test improvements +* Alex Folkner for pygame-ctypes + +パッチやフィックスを送って下さった皆様: Niki Spahiev, Gordon +Tyler, Nathaniel Pryce, Dave Wallace, John Popplewell, Michael Urman, +Andrew Straw, Michael Hudson, Ole Martin Bjoerndalen, Herve Cauwelier, +James Mazer, Lalo Martins, Timothy Stranex, Chad Lester, Matthias +Spiller, Bo Jangeborg, Dmitry Borisov, Campbell Barton, Diego Essaya, +Eyal Lotem, Regis Desgroppes, Emmanuel Hainry, Randy Kaelber, +Matthew L Daniel, Nirav Patel, Forrest Voight, Charlie Nolan, +Frankie Robertson, John Krukoff, Lorenz Quack, Nick Irvine, +Michael George, Saul Spatz, Thomas Ibbotson, Tom Rothamel, Evan Kroske, +Cambell Barton. + +そして卓越した我々のバグハンター: Angus, Guillaume Proux, Frank +Raiser, Austin Henry, Kaweh Kazemi, Arturo Aldama, Mike Mulcheck, +Michael Benfield, David Lau + +他にも、多くのアイデアを提案したり、プロジェクトの進行を支えたり、 +我々の生活をより楽にして下さった方々がいます。ありがとう! + + +ドキュメントにコメントを寄せて下さったり、 `pygame documentation`_ と +`pygame-ce documentation`_ に追記して下さった方々にも感謝します。 + +また、ゲームを作成して、他の人が学んだり楽しんだりできるように +pygame.org ウェブサイトに投稿して下さった方々にもとても感謝します。 + + +James Paige には、pygame の bugzilla をホスティングして下さったことを +感謝します。 + +Roger Dingledine と SEUL.ORG のクルーにも、優れたホスティングを +提供して下さったことを感謝します。 + + +依存関係 +-------- + +Pygame は、当然ながら SDL と Python に強く依存します。 +また、他のいくつかの小さなライブラリにもリンクしたり、埋め込まれています。 +フォントモジュールは SDL_ttf に依存しており、これは freetype に依存しています。 +ミキサー(および mixer.music)モジュールは SDL_mixer に依存しています。 +イメージモジュールは SDL_image に依存しています。 +Transform.rotozoom には SDL_rotozoom の埋め込みバージョンが含まれており、 +gfxdraw には SDL_gfx の埋め込みバージョンが含まれています。 + +依存バージョン: + ++----------+------------------------+ +| CPython | >= 3.8 (Or use PyPy3) | ++----------+------------------------+ +| SDL | >= 2.0.10 | ++----------+------------------------+ +| SDL_mixer| >= 2.0.4 | ++----------+------------------------+ +| SDL_image| >= 2.0.4 | ++----------+------------------------+ +| SDL_ttf | >= 2.0.15 | ++----------+------------------------+ + + +コントリビュータになるには +-------------------------- + +最初に、pygame-ce のコントリビュータになることを考慮してくれたことに感謝します。 +あなたのような方のおかげで pygame-ce を素晴らしいライブラリにすることができます。 +まずは、以下のステップに従ってください: + +1. `Contribution Guidelines`_ と `Many Ways to Contribute`_ wiki ページをお読みください +2. `Opening A Pull Request`_ と `Opening a Great Pull Request`_ のドキュメントをお読みください +3. `label and link reported issues`_ の How-to をお読みください +4. `issue tracker`_ で興味のある issue があるかどうかをチェックするか、 + あなたのアイデアについての議論を開始するため、新しい issue を開いてください + +`wiki pages`_ にもあなたのスタートを手助けするたくさんの資料があります。 +もし何か疑問があれば、issue を開くか、 `Pygame Community Discord Server`_ でお気軽にご質問ください。 + + +ライセンス +---------- + +**License Identifier:** LGPL-2.1-or-later + + +このライブラリは `GNU LGPL version 2.1`_ ライセンスで配布されています。 +ライセンスは ``docs/LGPL.txt`` にあります。我々はこのライブラリの +将来のバージョンに異なるライセンスを適用する権利を保留しています。 + +これは、基本的にあなたは pygame をあらゆるプロジェクトで使用できることを +意味しますが、あなたが pygame 自体に変更や追加を加えた場合、それらは +互換性のあるライセンスでリリースされることが必要です。 +(できれば pygame-ce プロジェクトに送ってください) +クローズドソースや商用ゲームで使用することができます。 + +``examples`` サブディレクトリに含まれるプログラムはパブリックドメインです。 + +ライセンスの依存関係については、docs/licenses を参照してください。 + +.. |PyPiVersion| image:: https://img.shields.io/pypi/v/pygame-ce.svg?v=1 + :target: https://pypi.python.org/pypi/pygame-ce + +.. |PyPiLicense| image:: https://img.shields.io/pypi/l/pygame-ce.svg?v=1 + :target: https://pypi.python.org/pypi/pygame-ce + +.. |Python3| image:: https://img.shields.io/badge/python-3-blue.svg?v=1 + +.. |GithubCommits| image:: https://img.shields.io/github/commits-since/pygame-community/pygame-ce/2.4.1.svg + :target: https://github.com/pygame-community/pygame-ce/compare/2.4.1...main + +.. |DocsStatus| image:: https://img.shields.io/website?down_message=offline&label=docs&up_message=online&url=https%3A%2F%2Fpyga.me%2Fdocs%2F + :target: https://pyga.me/docs/ + +.. |BlackFormatBadge| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + +.. _Pygame: https://pyga.me +.. _pygame-ce documentation: https://pyga.me/docs/ +.. _pygame documentation: https://www.pygame.org/docs/ +.. _Simple DirectMedia Layer library: https://www.libsdl.org +.. _Compilation wiki page: https://github.com/pygame-community/pygame-ce/wiki#compiling +.. _docs page: https://pyga.me/docs +.. _GNU LGPL version 2.1: https://www.gnu.org/copyleft/lesser.html +.. _Contribution Guidelines: https://github.com/pygame-community/pygame-ce/wiki/Contribution-guidelines +.. _Many Ways to Contribute: https://github.com/pygame-community/pygame-ce/wiki/Many-ways-to-contribute +.. _Opening A Pull Request: https://github.com/pygame-community/pygame-ce/wiki/Opening-a-pull-request +.. _Opening a Great Pull Request: https://github.com/pygame-community/pygame-ce/wiki/Opening-a-great-pull-request +.. _issue tracker: https://github.com/pygame-community/pygame-ce/issues +.. _label and link reported issues: https://github.com/pygame-community/pygame-ce/wiki/Labelling-&-linking-reported-issues +.. _Pygame Community Discord Server: https://discord.gg/pygame +.. _wiki pages: https://github.com/pygame-community/pygame-ce/wiki + +.. _简体中文: ./docs/readmes/README.zh-cn.rst +.. _繁體中文: README.zh-tw.rst +.. _Français: ./docs/readmes/README.fr.rst +.. _فارسی: ./docs/readmes/README.fa.rst +.. _Español: ./docs/readmes/README.es.rst diff --git a/docs/readmes/README.zh-cn.rst b/docs/readmes/README.zh-cn.rst index f5aed2f18f..378167b43f 100644 --- a/docs/readmes/README.zh-cn.rst +++ b/docs/readmes/README.zh-cn.rst @@ -1,4 +1,4 @@ -.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg +.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg :alt: pygame :target: https://pyga.me/ @@ -7,7 +7,7 @@ |PyPiVersion| |PyPiLicense| |Python3| |GithubCommits| |BlackFormatBadge| -`English`_ **简体中文** `Français`_ `فارسی`_ `Español`_ +`English`_ **简体中文** `繁體中文`_ `Français`_ `فارسی`_ `Español`_ `日本語`_ ---- Pygame_ 是一款自由且开源的跨平台库,用于开发电子游戏等多媒体应用。Pygame基于 `Simple DirectMedia Layer library`_ 以及其他几个广受欢迎的库,汲取其中最常见的函数,让编写游戏成为更加符合直觉的事情。 @@ -98,10 +98,7 @@ Michael Benfield, David Lau 依赖 ------------ - -**note:** This section translation is out of date (September 2023) - -pygame显然依赖于SDL和Python。此外pygame还嵌入了几个较小的库:font模块依赖于SDL_ttf(SDL_ttf依赖于freetype);mixer模块(以及mixer.music模块)依赖于SDL_mixer;image模块依赖于SDL_image(SDL_image使用到libjpeg与libpng);transform模块内嵌了一个SDL_rotozoom来实现它的rotozoom函数;surfarray模块用到了Numpy中的多维数组。 +pygame显然依赖于SDL和Python。此外pygame还嵌入了几个较小的库:font模块依赖于SDL_ttf(SDL_ttf依赖于freetype);mixer模块(以及mixer.music模块)依赖于SDL_mixer;image模块依赖于SDL_image;transform模块内嵌了一个SDL_rotozoom来实现rotozoom函数;gfxdraw模块内嵌了一个SDL_gfx。 依赖的版本要求如下: @@ -122,6 +119,7 @@ pygame显然依赖于SDL和Python。此外pygame还嵌入了几个较小的库 许可证 ------- +**许可证标识符:** LGPL-2.1-or-later 本库在 `GNU LGPL version 2.1`_ 下发布,许可文件: ``docs/LGPL.txt`` 。我们保留将此库的未来版本置于其他许可证下的权利。 @@ -157,6 +155,8 @@ pygame显然依赖于SDL和Python。此外pygame还嵌入了几个较小的库 .. _GNU LGPL version 2.1: https://www.gnu.org/copyleft/lesser.html .. _English: ./../../README.rst +.. _繁體中文: README.zh-tw.rst .. _Français: README.fr.rst .. _فارسی: README.fa.rst .. _Español: README.es.rst +.. _日本語: README.ja.rst diff --git a/examples/aacircle.py b/examples/aacircle.py index 651df18b50..24fc2aad47 100644 --- a/examples/aacircle.py +++ b/examples/aacircle.py @@ -10,7 +10,7 @@ def main(): pygame.init() screen = pygame.display.set_mode((500, 500)) screen.fill((255, 0, 0)) - s = pygame.Surface(screen.get_size(), pygame.SRCALPHA, 32) + s = pygame.Surface(screen.get_size(), pygame.SRCALPHA) pygame.draw.line(s, (0, 0, 0), (250, 250), (250 + 200, 250)) width = 1 @@ -23,18 +23,23 @@ def main(): pygame.draw.circle(screen, "green", (50, 100), 10) pygame.draw.circle(screen, "black", (50, 100), 10, 1) - pygame.display.flip() - try: - while True: - event = pygame.event.wait() + running = True + + clock = pygame.Clock() + + while running: + clock.tick(10) + + for event in pygame.event.get(): if event.type == pygame.QUIT: - break - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_ESCAPE or event.unicode == "q": - break - pygame.display.flip() - finally: - pygame.quit() + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_q: + running = False + + pygame.display.flip() + + pygame.quit() if __name__ == "__main__": diff --git a/examples/aliens.py b/examples/aliens.py index f5da030b05..5d1fc65e08 100644 --- a/examples/aliens.py +++ b/examples/aliens.py @@ -83,9 +83,10 @@ class Player(pygame.sprite.Sprite): bounce = 24 gun_offset = -11 images = [] + containers = None def __init__(self): - pygame.sprite.Sprite.__init__(self, self.containers) + super().__init__(self.containers) self.image = self.images[0] self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom) self.reloading = False @@ -114,6 +115,7 @@ class Alien(pygame.sprite.Sprite): speed = 13 animcycle = 12 images = [] + containers = None def __init__(self): pygame.sprite.Sprite.__init__(self, self.containers) @@ -140,6 +142,7 @@ class Explosion(pygame.sprite.Sprite): defaultlife = 12 animcycle = 3 images = [] + containers = None def __init__(self, actor): pygame.sprite.Sprite.__init__(self, self.containers) @@ -166,6 +169,7 @@ class Shot(pygame.sprite.Sprite): speed = -11 images = [] + containers = None def __init__(self, pos): pygame.sprite.Sprite.__init__(self, self.containers) @@ -187,6 +191,7 @@ class Bomb(pygame.sprite.Sprite): speed = 9 images = [] + containers = None def __init__(self, alien): pygame.sprite.Sprite.__init__(self, self.containers) @@ -214,7 +219,7 @@ class Score(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.font = pygame.Font(None, 20) - self.font.set_italic(1) + self.font.set_italic(True) self.color = "white" self.lastscore = -1 self.update() @@ -225,10 +230,10 @@ def update(self): if SCORE != self.lastscore: self.lastscore = SCORE msg = "Score: %d" % SCORE - self.image = self.font.render(msg, 0, self.color) + self.image = self.font.render(msg, False, self.color) -def main(winstyle=0): +def main(): # Initialize pygame pygame.mixer.pre_init(44100, 32, 2, 1024) pygame.init() @@ -244,9 +249,9 @@ def main(winstyle=0): # Load images, assign to sprite classes # (do this before the classes are used, after screen setup) img = load_image("player1.gif") - Player.images = [img, pygame.transform.flip(img, 1, 0)] + Player.images = [img, pygame.transform.flip(img, True, False)] img = load_image("explosion1.gif") - Explosion.images = [img, pygame.transform.flip(img, 1, 1)] + Explosion.images = [img, pygame.transform.flip(img, True, True)] Alien.images = [load_image(im) for im in ("alien1.gif", "alien2.gif", "alien3.gif")] Bomb.images = [load_image("bomb.gif")] Shot.images = [load_image("shot.gif")] @@ -255,14 +260,13 @@ def main(winstyle=0): icon = pygame.transform.scale(Alien.images[0], (32, 32)) pygame.display.set_icon(icon) pygame.display.set_caption("Pygame Aliens") - pygame.mouse.set_visible(0) + pygame.mouse.set_visible(False) # create the background, tile the bgd image bgdtile = load_image("background.gif") background = pygame.Surface(SCREENRECT.size) for x in range(0, SCREENRECT.width, bgdtile.get_width()): background.blit(bgdtile, (x, 0)) - screen.blit(background, (0, 0)) pygame.display.flip() # load the sound effects @@ -277,19 +281,18 @@ def main(winstyle=0): aliens = pygame.sprite.Group() shots = pygame.sprite.Group() bombs = pygame.sprite.Group() - all = pygame.sprite.RenderUpdates() + all_sprites = pygame.sprite.Group() lastalien = pygame.sprite.GroupSingle() # assign default groups to each sprite class - Player.containers = all - Alien.containers = aliens, all, lastalien - Shot.containers = shots, all - Bomb.containers = bombs, all - Explosion.containers = all - Score.containers = all + Player.containers = all_sprites + Alien.containers = aliens, all_sprites, lastalien + Shot.containers = shots, all_sprites + Bomb.containers = bombs, all_sprites + Explosion.containers = all_sprites + Score.containers = all_sprites # Create Some Starting Values - global score alienreload = ALIEN_RELOAD clock = pygame.Clock() @@ -298,10 +301,13 @@ def main(winstyle=0): player = Player() Alien() # note, this 'lives' because it goes into a sprite group if pygame.font: - all.add(Score()) + all_sprites.add(Score()) # Run our main loop whilst the player is alive. while player.alive(): + # Erase last frame by blitting the background to the screen + screen.blit(background, (0, 0)) + # get input for event in pygame.event.get(): if event.type == pygame.QUIT: @@ -314,7 +320,8 @@ def main(winstyle=0): print("Changing to FULLSCREEN") screen_backup = screen.copy() screen = pygame.display.set_mode( - SCREENRECT.size, winstyle | pygame.FULLSCREEN + SCREENRECT.size, + winstyle | pygame.FULLSCREEN | pygame.SCALED, ) screen.blit(screen_backup, (0, 0)) else: @@ -327,11 +334,8 @@ def main(winstyle=0): keystate = pygame.key.get_pressed() - # clear/erase the last drawn sprites - all.clear(screen, background) - # update all the sprites - all.update() + all_sprites.update() # handle player input direction = keystate[pygame.K_RIGHT] - keystate[pygame.K_LEFT] @@ -355,7 +359,7 @@ def main(winstyle=0): Bomb(lastalien.sprite) # Detect collisions between aliens and players. - for alien in pygame.sprite.spritecollide(player, aliens, 1): + for alien in pygame.sprite.spritecollide(player, aliens, True): if pygame.mixer: boom_sound.play() Explosion(alien) @@ -364,14 +368,14 @@ def main(winstyle=0): player.kill() # See if shots hit the aliens. - for alien in pygame.sprite.groupcollide(aliens, shots, 1, 1).keys(): + for alien in pygame.sprite.groupcollide(aliens, shots, True, True).keys(): if pygame.mixer: boom_sound.play() Explosion(alien) SCORE = SCORE + 1 # See if alien bombs hit the player. - for bomb in pygame.sprite.spritecollide(player, bombs, 1): + for bomb in pygame.sprite.spritecollide(player, bombs, True): if pygame.mixer: boom_sound.play() Explosion(player) @@ -379,8 +383,8 @@ def main(winstyle=0): player.kill() # draw the scene - dirty = all.draw(screen) - pygame.display.update(dirty) + all_sprites.draw(screen) + pygame.display.flip() # cap the framerate at 40fps. Also called 40HZ or 40 times per second. clock.tick(40) diff --git a/examples/chimp.py b/examples/chimp.py index d1d63e1d86..1f0fecddfe 100644 --- a/examples/chimp.py +++ b/examples/chimp.py @@ -26,9 +26,7 @@ def load_image(name, colorkey=None, scale=1): image = pygame.image.load(fullname) image = image.convert() - size = image.get_size() - size = (size[0] * scale, size[1] * scale) - image = pygame.transform.scale(image, size) + image = pygame.transform.scale_by(image, scale) if colorkey is not None: if colorkey == -1: @@ -42,7 +40,7 @@ class NoneSound: def play(self): pass - if not pygame.mixer or not pygame.mixer.get_init(): + if not pygame.mixer.get_init(): return NoneSound() fullname = os.path.join(data_dir, name) @@ -146,11 +144,10 @@ def main(): background.fill((170, 238, 187)) # Put Text On The Background, Centered - if pygame.font: - font = pygame.Font(None, 64) - text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10)) - textpos = text.get_rect(centerx=background.get_width() / 2, y=10) - background.blit(text, textpos) + font = pygame.Font(None, 64) + text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10)) + textpos = text.get_rect(centerx=background.get_width() / 2, y=10) + background.blit(text, textpos) # Display The Background screen.blit(background, (0, 0)) @@ -161,7 +158,7 @@ def main(): punch_sound = load_sound("punch.wav") chimp = Chimp() fist = Fist() - allsprites = pygame.sprite.RenderPlain((chimp, fist)) + all_sprites = pygame.sprite.Group(chimp, fist) clock = pygame.Clock() # Main Loop @@ -184,11 +181,11 @@ def main(): elif event.type == pygame.MOUSEBUTTONUP: fist.unpunch() - allsprites.update() + all_sprites.update() # Draw Everything screen.blit(background, (0, 0)) - allsprites.draw(screen) + all_sprites.draw(screen) pygame.display.flip() pygame.quit() diff --git a/examples/dropevent.py b/examples/dropevent.py index 20a47d7ede..055d767dc7 100644 --- a/examples/dropevent.py +++ b/examples/dropevent.py @@ -17,49 +17,49 @@ def main(): - Running = True + running = True screen_size = (640, 480) surf = pygame.display.set_mode(screen_size) font = pygame.font.SysFont("Arial", 24) font.align = pygame.FONT_CENTER clock = pygame.Clock() - spr_file_text = font.render("Feed me some file or image!", 1, (255, 255, 255)) + spr_file_text = font.render("Feed me some file or image!", True, (255, 255, 255)) spr_file_text_rect = spr_file_text.get_rect() spr_file_text_rect.center = surf.get_rect().center spr_file_image = None spr_file_image_rect = None - while Running: - for ev in pygame.event.get(): - if ev.type == pygame.QUIT: - Running = False - elif ev.type == pygame.DROPBEGIN: - print(ev) + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.DROPBEGIN: + print(event) print("File drop begin!") - elif ev.type == pygame.DROPCOMPLETE: - print(ev) + elif event.type == pygame.DROPCOMPLETE: + print(event) print("File drop complete!") - elif ev.type == pygame.DROPTEXT: - print(ev) + elif event.type == pygame.DROPTEXT: + print(event) spr_file_text = font.render( - ev.text, 1, (255, 255, 255), wraplength=screen_size[0] - 10 + event.text, True, (255, 255, 255), wraplength=screen_size[0] - 10 ) spr_file_text_rect = spr_file_text.get_rect() spr_file_text_rect.center = surf.get_rect().center - elif ev.type == pygame.DROPFILE: - print(ev) + elif event.type == pygame.DROPFILE: + print(event) spr_file_text = font.render( - ev.file, 1, (255, 255, 255), None, screen_size[0] - 10 + event.file, True, (255, 255, 255), None, screen_size[0] - 10 ) spr_file_text_rect = spr_file_text.get_rect() spr_file_text_rect.center = surf.get_rect().center # Try to open the file if it's an image - filetype = ev.file[-3:] + filetype = event.file[-3:] if filetype in ["png", "bmp", "jpg"]: - spr_file_image = pygame.image.load(ev.file).convert() + spr_file_image = pygame.image.load(event.file).convert() spr_file_image.set_alpha(127) spr_file_image_rect = spr_file_image.get_rect() spr_file_image_rect.center = surf.get_rect().center diff --git a/examples/eventlist.py b/examples/eventlist.py index 319eebbeec..66c387134c 100644 --- a/examples/eventlist.py +++ b/examples/eventlist.py @@ -24,7 +24,6 @@ - press keys up an down to see events. - you can see joystick events if any are plugged in. -- press "c" to toggle events generated by controllers. """ import pygame @@ -152,9 +151,6 @@ def main(): last_key = e.key if e.key == pygame.K_h: draw_usage_in_history(history, usage) - if SDL2 and e.key == pygame.K_c: - current_state = pygame._sdl2.controller.get_eventstate() - pygame._sdl2.controller.set_eventstate(not current_state) if e.type == pygame.MOUSEBUTTONDOWN and e.button == 1: pygame.event.set_grab(not pygame.event.get_grab()) diff --git a/examples/liquid.py b/examples/liquid.py index 0b2bd7f3e3..516690a335 100644 --- a/examples/liquid.py +++ b/examples/liquid.py @@ -16,7 +16,6 @@ import pygame import os from math import sin -import time main_dir = os.path.split(os.path.abspath(__file__))[0] @@ -44,10 +43,15 @@ def main(): # mainloop xblocks = range(0, 640, 20) yblocks = range(0, 480, 20) - stopevents = pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN + stop_events = pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN + + clock = pygame.Clock() + while True: - for e in pygame.event.get(): - if e.type in stopevents: + clock.tick(60) + + for event in pygame.event.get(): + if event.type in stop_events: return anim = anim + 0.02 @@ -58,7 +62,6 @@ def main(): screen.blit(bitmap, (x, y), (xpos, ypos, 20, 20)) pygame.display.flip() - time.sleep(0.01) if __name__ == "__main__": diff --git a/examples/music_drop_fade.py b/examples/music_drop_fade.py index 8091bde09c..2fd9d8aa33 100644 --- a/examples/music_drop_fade.py +++ b/examples/music_drop_fade.py @@ -16,7 +16,8 @@ """ import pygame -import os, sys +import os +import sys VOLUME_CHANGE_AMOUNT = 0.02 # how fast should up and down arrows change the volume? SCREEN_SIZE = (640, 480) diff --git a/examples/playmus.py b/examples/playmus.py index 8a8daf378d..5358e38edd 100644 --- a/examples/playmus.py +++ b/examples/playmus.py @@ -21,142 +21,105 @@ import sys import pygame -import pygame.freetype +pygame.init() -class Window: - """The application's Pygame window - A Window instance manages the creation of and drawing to a - window. It is a singleton class. Only one instance can exist. +def write_lines( + dest: pygame.Surface, + text: str, + font=pygame.Font(None, 30), + color=(254, 231, 21, 255), + starting_line=0, +): + text_surface = font.render(text, True, color) - """ + y_offset = 0 + if starting_line >= 0: + y_offset = font.get_height() * starting_line + else: + y_offset = dest.get_height() + starting_line * font.get_height() - instance = None + dest.fill( + (16, 24, 32, 255), + pygame.Rect((0, y_offset), (text_surface.get_width(), dest.get_width())), + ) + dest.blit(text_surface, (16, y_offset)) - def __new__(cls, *args, **kwds): - """Return an open Pygame window""" - if Window.instance is not None: - return Window.instance - self = object.__new__(cls) - pygame.display.init() - self.screen = pygame.display.set_mode((600, 400)) - Window.instance = self - return self +def show_usage_message(): + print( + "Usage: python playmus.py \n" + " python -m pygame.examples.playmus " + ) - def __init__(self, title): - pygame.display.set_caption(title) - self.text_color = (254, 231, 21, 255) - self.background_color = (16, 24, 32, 255) - self.screen.fill(self.background_color) - pygame.display.flip() - pygame.freetype.init() - self.font = pygame.freetype.Font(None, 20) - self.font.origin = True - self.ascender = int(self.font.get_sized_ascender() * 1.5) - self.descender = int(self.font.get_sized_descender() * 1.5) - self.line_height = self.ascender - self.descender - - self.write_lines( - "\nPress 'q' or 'ESCAPE' or close this window to quit\n" - "Press 'SPACE' to play / pause\n" - "Press 'r' to rewind to the beginning (restart)\n" - "Press 'f' to fade music out over 5 seconds\n\n" - "Window will quit automatically when music ends\n", - 0, - ) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - return False - - def close(self): - pygame.display.quit() - Window.instance = None - - def write_lines(self, text, line=0): - w, h = self.screen.get_size() - line_height = self.line_height - nlines = h // line_height - if line < 0: - line = nlines + line - for i, text_line in enumerate(text.split("\n"), line): - y = i * line_height + self.ascender - # Clear the line first. - self.screen.fill( - self.background_color, (0, i * line_height, w, line_height) - ) - # Write new text. - self.font.render_to(self.screen, (15, y), text_line, self.text_color) - pygame.display.flip() +def main(file_path): + """Play an audio file with pygame.mixer.music""" + screen = pygame.display.set_mode((600, 400)) + screen.fill((16, 24, 32, 255)) -def show_usage_message(): - print("Usage: python playmus.py ") - print(" python -m pygame.examples.playmus ") + write_lines(screen, "Loading ...", starting_line=-1) + pygame.mixer.init(frequency=44100) + pygame.mixer.music.load(file_path) + pygame.display.set_caption(f"File : {file_path}") -def main(file_path): - """Play an audio file with pygame.mixer.music""" + write_lines( + screen, + "Press 'q' or 'ESCAPE' or close this window to quit\n" + "Press 'SPACE' to play / pause\n" + "Press 'r' to rewind to the beginning (restart)\n" + "Press 'f' to fade music out over 5 seconds", + starting_line=1, + ) + + pygame.mixer.music.play() + write_lines(screen, "Playing ...\n", starting_line=-1) - EVENT_LOOP_TICK = pygame.event.custom_type() - - with Window(file_path) as win: - win.write_lines("Loading ...", -1) - pygame.mixer.init(frequency=44100) - try: - paused = False - pygame.mixer.music.load(file_path) - - # Make sure the event loop ticks over at least every 0.5 seconds. - pygame.time.set_timer(EVENT_LOOP_TICK, 500) - - pygame.mixer.music.play() - win.write_lines("Playing ...\n", -1) - - while pygame.mixer.music.get_busy() or paused: - e = pygame.event.wait() - if e.type == pygame.KEYDOWN: - key = e.key - if key == pygame.K_SPACE: - if paused: - pygame.mixer.music.unpause() - paused = False - win.write_lines("Playing ...\n", -1) - else: - pygame.mixer.music.pause() - paused = True - win.write_lines("Paused ...\n", -1) - elif key == pygame.K_r: - if file_path[-3:].lower() in ("ogg", "mp3", "mod"): - status = "Rewound." - pygame.mixer.music.rewind() - else: - status = "Restarted." - pygame.mixer.music.play() - if paused: - pygame.mixer.music.pause() - win.write_lines(status, -1) - elif key == pygame.K_f: - win.write_lines("Fading out ...\n", -1) - pygame.mixer.music.fadeout(5000) - # when finished get_busy() will return False. - elif key in [pygame.K_q, pygame.K_ESCAPE]: + running = True + paused = False + + while running: + pygame.display.flip() + + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + key = event.key + if key == pygame.K_SPACE: + if paused: + pygame.mixer.music.unpause() paused = False - pygame.mixer.music.stop() - # get_busy() will now return False. - elif e.type == pygame.QUIT: + write_lines(screen, "Playing ...", starting_line=-1) + else: + pygame.mixer.music.pause() + paused = True + write_lines(screen, "Paused ...", starting_line=-1) + elif key == pygame.K_r: + if file_path[-3:].lower() in ("ogg", "mp3", "mod"): + status = "Rewound." + pygame.mixer.music.rewind() + else: + status = "Restarted." + pygame.mixer.music.play() + if paused: + pygame.mixer.music.pause() + write_lines(screen, status, starting_line=-1) + elif key == pygame.K_f: + write_lines(screen, "Fading out ...", starting_line=-1) + pygame.mixer.music.fadeout(5000) + # when finished get_busy() will return False. + elif key in [pygame.K_q, pygame.K_ESCAPE]: paused = False pygame.mixer.music.stop() # get_busy() will now return False. - pygame.time.set_timer(EVENT_LOOP_TICK, 0) - finally: - pygame.mixer.quit() + elif event.type == pygame.QUIT: + running = False + + if not pygame.mixer.music.get_busy() and not paused: + running = False + pygame.quit() diff --git a/examples/resizing_new.py b/examples/resizing_new.py index 132c464183..cccde5badd 100644 --- a/examples/resizing_new.py +++ b/examples/resizing_new.py @@ -2,12 +2,11 @@ import pygame pygame.init() - -RES = (160, 120) +RESOLUTION = (160, 120) FPS = 30 clock = pygame.Clock() -screen = pygame.display.set_mode(RES, pygame.RESIZABLE) +screen = pygame.display.set_mode(RESOLUTION, pygame.RESIZABLE) pygame.display._set_autoresize(False) # MAIN LOOP @@ -21,12 +20,10 @@ for event in pygame.event.get(): if event.type == pygame.KEYDOWN and event.key == pygame.K_q: done = True - if event.type == pygame.QUIT: - done = True - # if event.type==pygame.WINDOWRESIZED: - # screen=pygame.display.get_surface() - if event.type == pygame.VIDEORESIZE: + elif event.type == pygame.VIDEORESIZE: screen = pygame.display.get_surface() + elif event.type == pygame.QUIT: + done = True i += 1 i = i % screen.get_width() j += i % 2 diff --git a/examples/scaletest.py b/examples/scaletest.py index e98e6becbb..5f83a69844 100644 --- a/examples/scaletest.py +++ b/examples/scaletest.py @@ -7,7 +7,7 @@ import sys import time -import pygame as pygame +import pygame def main(imagefile, convert_alpha=False, run_speed_test=False): @@ -30,68 +30,59 @@ def main(imagefile, convert_alpha=False, run_speed_test=False): pygame.display.set_mode((1, 1)) background = background.convert_alpha() - SpeedTest(background) + speed_test(background) return - # start fullscreen mode - screen = pygame.display.set_mode((1024, 768), pygame.FULLSCREEN) + # start FULLSCREEN mode + # On Windows, the fullscreen mode doesn't work properly, to fix the problem, + # add with it pygame.SCALED flag + screen = pygame.display.set_mode((1024, 768), pygame.FULLSCREEN | pygame.SCALED) if convert_alpha: background = background.convert_alpha() # turn off the mouse pointer - pygame.mouse.set_visible(0) - # main loop - bRunning = True - bUp = False - bDown = False - bLeft = False - bRight = False + pygame.mouse.set_visible(False) + + running = True cursize = [background.get_width(), background.get_height()] - while bRunning: + + clock = pygame.Clock() + + # main loop + while running: + clock.tick(60) + image = pygame.transform.smoothscale(background, cursize) imgpos = image.get_rect(centerx=512, centery=384) screen.fill((255, 255, 255)) screen.blit(image, imgpos) + pygame.display.flip() + for event in pygame.event.get(): if event.type == pygame.QUIT or ( event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE ): - bRunning = False - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_UP: - bUp = True - if event.key == pygame.K_DOWN: - bDown = True - if event.key == pygame.K_LEFT: - bLeft = True - if event.key == pygame.K_RIGHT: - bRight = True - if event.type == pygame.KEYUP: - if event.key == pygame.K_UP: - bUp = False - if event.key == pygame.K_DOWN: - bDown = False - if event.key == pygame.K_LEFT: - bLeft = False - if event.key == pygame.K_RIGHT: - bRight = False - if bUp: + running = False + + pressed_keys = pygame.key.get_pressed() + + if pressed_keys[pygame.K_UP]: cursize[1] -= 2 if cursize[1] < 1: cursize[1] = 1 - if bDown: + if pressed_keys[pygame.K_DOWN]: cursize[1] += 2 - if bLeft: + if pressed_keys[pygame.K_LEFT]: cursize[0] -= 2 if cursize[0] < 1: cursize[0] = 1 - if bRight: + if pressed_keys[pygame.K_RIGHT]: cursize[0] += 2 pygame.quit() -def SpeedTest(image): +def speed_test(image): print(f"\nImage Scaling Speed Test - Image Size {str(image.get_size())}\n") imgsize = [image.get_width(), image.get_height()] diff --git a/meson.build b/meson.build index a590403cb9..d002aaf55b 100644 --- a/meson.build +++ b/meson.build @@ -63,6 +63,19 @@ if not cc.has_header('Python.h', dependencies: py_dep) ) endif +if get_option('coverage') + if cc.has_argument('--coverage') + add_global_arguments('--coverage', language: 'c') + else + error('Requested coverage but compiler doesn\'t seem to support it') + endif + if cc.has_link_argument('--coverage') + add_global_link_arguments('--coverage', language: 'c') + else + error('Requested coverage but compiler doesn\'t seem to support it') + endif +endif + pg_dir = py.get_install_dir() / pg pg_inc_dirs = [] @@ -86,7 +99,7 @@ if plat == 'win' and host_machine.cpu_family().startswith('x86') ) endif - sdl_ver = '2.30.3' + sdl_ver = '2.30.7' sdl_image_ver = '2.8.2' sdl_mixer_ver = '2.8.0' sdl_ttf_ver = '2.22.0' @@ -154,6 +167,15 @@ if plat == 'win' and host_machine.cpu_family().startswith('x86') ) endif + run_command( + [ + find_program('python3', 'python'), + base_dir / 'buildconfig' / '_meson_win_dll.py', + dlls, + ], + check: true, + ) + # put dlls in root of install install_data(dlls, install_dir: pg_dir, install_tag: 'pg-tag') else diff --git a/meson_options.txt b/meson_options.txt index c34007090a..8d409fba9c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -17,7 +17,7 @@ option('font', type: 'feature', value: 'enabled') option('freetype', type: 'feature', value: 'enabled') # Controls whether pygame.midi is built. -# Enabled by default, disable explicitly if you don't want to compile with +# Enabled by default, disable explicitly if you don't want to compile with # portmidi/porttime. option('midi', type: 'feature', value: 'enabled') @@ -33,3 +33,7 @@ option('error_on_warns', type: 'boolean', value: 'false') # Controls whether to error on build if generated docs are missing. Defaults to # false. option('error_docs_missing', type: 'boolean', value: 'false') + +# Controls whether to do a coverage build. +# This argument must be used together with the editable install. +option('coverage', type: 'boolean', value: false) diff --git a/pyproject.toml b/pyproject.toml index c33803b64c..76c546710b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] name = "pygame-ce" -version = "2.5.1.dev1" +version = "2.5.2.dev1" description = "Python Game Development" readme = "README.rst" # for long description requires-python = ">=3.8" -license = {file = "docs/LGPL.txt"} # path to LGPL license +license = {text = "LGPL v2.1"} authors = [{name = "A community project"}] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -19,6 +19,7 @@ classifiers = [ "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", "Topic :: Games/Entertainment", @@ -39,10 +40,11 @@ classifiers = [ ] [project.urls] -"Homepage" = "https://github.com/pygame-community/pygame-ce" +homepage = "https://pyga.me" "Bug Reports" = "https://github.com/pygame-community/pygame-ce/issues" -"Source" = "https://github.com/pygame-community/pygame-ce" "Documentation" = "https://pyga.me/docs" +"Release Notes" = "https://github.com/pygame-community/pygame-ce/releases" +"Source" = "https://github.com/pygame-community/pygame-ce" [project.entry-points.pyinstaller40] hook-dirs = 'pygame.__pyinstaller:get_hook_dirs' @@ -51,7 +53,13 @@ hook-dirs = 'pygame.__pyinstaller:get_hook_dirs' pygame_ce = 'pygame.__briefcase.pygame_ce:PygameCEGuiBootstrap' [build-system] -requires = ["meson-python", "ninja", "cython", "sphinx<=7.2.6"] +requires = [ + "meson-python<=0.16.0", + "meson<=1.5.1", + "ninja<=1.11.1.1", + "cython<=3.0.11", + "sphinx<=7.2.6", +] build-backend = 'mesonpy' [tool.meson-python.args] @@ -59,6 +67,22 @@ install = ['--tags=runtime,python-runtime,pg-tag'] # uncomment to get werror locally # setup = ["-Derror_on_warns=true"] +[tool.cibuildwheel] +# The default build-frontend is "pip", but we use the recommended "build" frontend. +# build (AKA pypa/build) is a simple tool that just focusses on one thing: building +# wheels. build still needs to interact with a pip-like tool to handle build-time +# dependencies. Here is where uv comes into the picture. It is an "installer" like pip, +# but faster. It has been observed to save a couple of minutes of CI time. +build-frontend = "build[uv]" +build = "cp3{8,9,10,11,12,13}-* pp3{8,9,10}-*" +skip = "*-musllinux_*" +# build[uv] is verbose by default, so below flag is not needed here +# build-verbosity = 3 + +environment = { SDL_VIDEODRIVER="dummy", SDL_AUDIODRIVER="disk" } +test-command = "python -m pygame.tests -v --exclude opengl,music,timing --time_out 300" +test-requires = ["numpy"] + [tool.cibuildwheel.config-settings] setup-args = [ "-Derror_on_warns=true", @@ -75,3 +99,16 @@ exclude = [ [tool.ruff.format] quote-style = "preserve" + +# numpy is a test dependency, but we build for systems that numpy doesn't have +# binary wheels for. In such cases, we do not want to waste CI time building +# numpy from source. So, we are gonna force numpy to be "only-binary" and skip +# numpy on platforms that it doesn't have wheels for +[tool.uv.pip] +only-binary = ["numpy"] + +# 1. skip all 32-bit manylinux (i686) +# 2. skip all pypy+arm combinations +[[tool.cibuildwheel.overrides]] +select = "{*-manylinux_i686,pp*-*{arm64,aarch64}}" +test-requires = [] diff --git a/setup.cfg b/setup.cfg index f7d0bed4e4..1c71cfb797 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [tox:tox] -envlist = py{38,39,310,311,312} +envlist = py{38,39,310,311,312,313} skip_missing_interpreters = True skipsdist = True diff --git a/setup.py b/setup.py index 9aa83f0b96..47648be978 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ METADATA = { "name": "pygame-ce", "version": pg_ver.version, - "license": "LGPL", + "license": "LGPL v2.1", "url": "https://pyga.me", "author": "A community project.", "description": "Python Game Development", @@ -29,6 +29,7 @@ "Documentation": "https://pyga.me/docs", "Bug Tracker": "https://github.com/pygame-community/pygame-ce/issues", "Source": "https://github.com/pygame-community/pygame-ce", + "Release Notes": "https://github.com/pygame-community/pygame-ce/releases", }, "classifiers": [ "Development Status :: 5 - Production/Stable", @@ -43,6 +44,7 @@ "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", "Topic :: Games/Entertainment", @@ -124,7 +126,7 @@ def spawn(self, cmd, **kwargs): distutils.ccompiler.CCompiler.spawn = spawn # A (bit hacky) fix for https://github.com/pygame-community/pygame-ce/issues/1346 -# This is due to the fact that distutils uses command line args to +# This is due to the fact that distutils uses command line args to # export PyInit_* functions on windows, but those functions are already exported # and that is why compiler gives warnings from distutils.command.build_ext import build_ext @@ -264,7 +266,7 @@ def consume_arg(name): for i, kwargs in enumerate(queue): kwargs['progress'] = f'[{i + 1}/{count}] ' cythonize_one(**kwargs) - + if cython_only: sys.exit(0) @@ -420,7 +422,7 @@ def run_install_headers(self): if "freetype" in e.name and sys.platform not in ("darwin", "win32"): # TODO: fix freetype issues here - if sysconfig.get_config_var("MAINCC") != "clang": + if sysconfig.get_config_var("MAINCC") != "clang": e.extra_compile_args.append("-Wno-error=unused-but-set-variable") if "mask" in e.name and sys.platform == "win32": @@ -806,13 +808,13 @@ def run(self): ''' import subprocess command_line = [ - sys.executable, os.path.join('buildconfig', 'make_docs.py') + sys.executable, "-m", "buildconfig", "docs" ] if self.fullgeneration: command_line.append('full_generation') print("WARNING: This command is deprecated and will be removed in the future.") - print(f"Please use the following replacement: `{' '.join(command_line)}`\n") + print(f"Please use the following replacement: `{' '.join(command_line)}`\n") if subprocess.call(command_line) != 0: raise SystemExit("Failed to build documentation") @@ -823,7 +825,7 @@ class StubcheckCommand(Command): user_options = [] def initialize_options(self): pass - + def finalize_options(self): pass @@ -836,7 +838,7 @@ def run(self): sys.executable, os.path.join("buildconfig", "stubs", "stubcheck.py") ] print("WARNING: This command is deprecated and will be removed in the future.") - print(f"Please use the following replacement: `{' '.join(command_line)}`\n") + print(f"Please use the following replacement: `{' '.join(command_line)}`\n") result = subprocess.run(command_line) if result.returncode != 0: raise SystemExit("Stubcheck failed.") diff --git a/src_c/SDL_gfx/SDL_gfxPrimitives.h b/src_c/SDL_gfx/SDL_gfxPrimitives.h index 5f532524e3..ec5dc17c3b 100644 --- a/src_c/SDL_gfx/SDL_gfxPrimitives.h +++ b/src_c/SDL_gfx/SDL_gfxPrimitives.h @@ -133,9 +133,9 @@ lineRGBA(SDL_Surface *dst, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2, Uint8 r, Sint16 x2, Sint16 y2, Uint8 r, Uint8 g, Uint8 b, Uint8 a); /* Thick Line */ - SDL_GFXPRIMITIVES_SCOPE int thickLineColor(SDL_Surface * dst, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2, + SDL_GFXPRIMITIVES_SCOPE int thickLineColor(SDL_Surface * dst, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2, Uint8 width, Uint32 color); - SDL_GFXPRIMITIVES_SCOPE int thickLineRGBA(SDL_Surface * dst, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2, + SDL_GFXPRIMITIVES_SCOPE int thickLineRGBA(SDL_Surface * dst, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2, Uint8 width, Uint8 r, Uint8 g, Uint8 b, Uint8 a); #endif /********** CURRENTLY NOT USED BY pygame.gfxdraw **********/ diff --git a/src_c/_camera.c b/src_c/_camera.c index 3897039cb0..8bf528b559 100644 --- a/src_c/_camera.c +++ b/src_c/_camera.c @@ -1951,7 +1951,6 @@ MODINIT_DEFINE(_camera) } /* type preparation */ - // PyType_Init(pgCamera_Type); pgCamera_Type.tp_new = PyType_GenericNew; if (PyType_Ready(&pgCamera_Type) < 0) { return NULL; diff --git a/src_c/_freetype.c b/src_c/_freetype.c index 5ba7c6f1bb..f4da7757fd 100644 --- a/src_c/_freetype.c +++ b/src_c/_freetype.c @@ -51,6 +51,8 @@ _ft_get_error(PyObject *, PyObject *); static PyObject * _ft_get_init(PyObject *, PyObject *); static PyObject * +_ft_was_init(PyObject *, PyObject *); +static PyObject * _ft_autoinit(PyObject *, PyObject *); static PyObject * _ft_get_cache_size(PyObject *, PyObject *); @@ -231,7 +233,6 @@ load_font_res(const char *filename) { PyObject *load_basicfunc = 0; PyObject *pkgdatamodule = 0; - PyObject *resourcefunc = 0; PyObject *result = 0; PyObject *tmp; @@ -240,13 +241,9 @@ load_font_res(const char *filename) goto font_resource_end; } - resourcefunc = PyObject_GetAttrString(pkgdatamodule, RESOURCE_FUNC_NAME); - if (!resourcefunc) { - goto font_resource_end; - } - - result = PyObject_CallFunction(resourcefunc, "s", filename); - if (!result) { + result = + PyObject_CallMethod(pkgdatamodule, RESOURCE_FUNC_NAME, "s", filename); + if (result == NULL) { goto font_resource_end; } @@ -270,7 +267,6 @@ load_font_res(const char *filename) font_resource_end: Py_XDECREF(pkgdatamodule); - Py_XDECREF(resourcefunc); Py_XDECREF(load_basicfunc); return result; } @@ -501,7 +497,7 @@ static PyMethodDef _ft_methods[] = { DOC_FREETYPE_INIT}, {"quit", (PyCFunction)_ft_quit, METH_NOARGS, DOC_FREETYPE_QUIT}, {"get_init", _ft_get_init, METH_NOARGS, DOC_FREETYPE_GETINIT}, - {"was_init", _ft_get_init, METH_NOARGS, + {"was_init", _ft_was_init, METH_NOARGS, DOC_FREETYPE_WASINIT}, // DEPRECATED {"get_error", _ft_get_error, METH_NOARGS, DOC_FREETYPE_GETERROR}, {"get_version", (PyCFunction)_ft_get_version, METH_VARARGS | METH_KEYWORDS, @@ -2236,6 +2232,19 @@ _ft_get_init(PyObject *self, PyObject *_null) return PyBool_FromLong(FREETYPE_MOD_STATE(self)->freetype ? 1 : 0); } +static PyObject * +_ft_was_init(PyObject *self, PyObject *_null) +{ + if (PyErr_WarnEx( + PyExc_DeprecationWarning, + "was_init has been deprecated and may be removed in a future " + "version. Use the equivalent get_init function instead", + 1) == -1) { + return NULL; + } + return _ft_get_init(self, _null); +} + static PyObject * _ft_get_default_font(PyObject *self, PyObject *_null) { diff --git a/src_c/_pygame.h b/src_c/_pygame.h index aa13a70bb0..e87986d776 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -33,6 +33,7 @@ */ #define PY_SSIZE_T_CLEAN #include +#include "include/pythoncapi_compat.h" /* Ensure PyPy-specific code is not in use when running on GraalPython (PR * #2580) */ @@ -101,6 +102,15 @@ PG_UnlockMutex(SDL_mutex *mutex) /* Mask to test if surface flags are in a fullscreen window. */ #define PG_WINDOW_FULLSCREEN_INCLUSIVE SDL_WINDOW_FULLSCREEN +#define PG_SetEventEnabled(type, enabled) SDL_SetEventEnabled(type, enabled) +#define PG_EventEnabled(type) SDL_EventEnabled(type) +#define PG_SetJoystickEventsEnabled(enabled) \ + SDL_SetJoystickEventsEnabled(enabled) + +#define PG_FIND_VNUM_MAJOR(ver) SDL_VERSIONNUM_MAJOR(ver) +#define PG_FIND_VNUM_MINOR(ver) SDL_VERSIONNUM_MINOR(ver) +#define PG_FIND_VNUM_MICRO(ver) SDL_VERSIONNUM_MICRO(ver) + #else /* ~SDL_VERSION_ATLEAST(3, 0, 0)*/ #define PG_ShowCursor() SDL_ShowCursor(SDL_ENABLE) #define PG_HideCursor() SDL_ShowCursor(SDL_DISABLE) @@ -155,6 +165,17 @@ PG_UnlockMutex(SDL_mutex *mutex) * SDL_WINDOW_FULLSCREEN. */ #define PG_WINDOW_FULLSCREEN_INCLUSIVE SDL_WINDOW_FULLSCREEN_DESKTOP +/* SDL_EventState is meant to take SDL_IGNORE or SDL_ENABLE, but it also + * works identically with SDL_FALSE and SDL_TRUE, because they evaluate to + * the same values, respectively. */ +#define PG_SetEventEnabled(type, enabled) SDL_EventState(type, enabled) +#define PG_EventEnabled(type) SDL_EventState(type, SDL_QUERY) +#define PG_SetJoystickEventsEnabled(enabled) SDL_JoystickEventState(enabled) + +#define PG_FIND_VNUM_MAJOR(ver) ver.major +#define PG_FIND_VNUM_MINOR(ver) ver.minor +#define PG_FIND_VNUM_MICRO(ver) ver.patch + #if SDL_VERSION_ATLEAST(2, 0, 14) #define PG_SurfaceHasRLE SDL_HasSurfaceRLE #else @@ -190,8 +211,11 @@ struct SDL_BlitMap { }; #define SDL_COPY_RLE_DESIRED 0x00001000 -SDL_bool -PG_SurfaceHasRLE(SDL_Surface *surface); +#define PG_SurfaceHasRLE(surface) \ + (((surface) == NULL) \ + ? 0 \ + : ((surface)->map->info.flags & SDL_COPY_RLE_DESIRED)) + #endif #endif @@ -443,19 +467,6 @@ typedef enum { (RAISE(PyExc_NotImplementedError, "Python built without thread support")) #endif /* ~WITH_THREAD */ -#define PyType_Init(x) (((x).ob_type) = &PyType_Type) - -/* Python macro for comparing to Py_None - * Py_IsNone is naturally supported by - * Python 3.10 or higher - * so this macro can be removed after the minimum - * supported - * Python version reaches 3.10 - */ -#ifndef Py_IsNone -#define Py_IsNone(x) (x == Py_None) -#endif - /* Update this function if new sequences are added to the fast sequence * type. */ #ifndef pgSequenceFast_Check @@ -470,21 +481,11 @@ struct pgEventObject { PyObject *dict; }; -/* - * surflock module internals - */ -typedef struct { - PyObject_HEAD PyObject *surface; - PyObject *lockobj; - PyObject *weakrefs; -} pgLifetimeLockObject; - /* * surface module internals */ struct pgSubSurface_Data { PyObject *owner; - int pixeloffset; int offsetx, offsety; }; @@ -527,7 +528,7 @@ typedef enum { #define PYGAMEAPI_JOYSTICK_NUMSLOTS 3 #define PYGAMEAPI_DISPLAY_NUMSLOTS 2 #define PYGAMEAPI_SURFACE_NUMSLOTS 4 -#define PYGAMEAPI_SURFLOCK_NUMSLOTS 8 +#define PYGAMEAPI_SURFLOCK_NUMSLOTS 6 #define PYGAMEAPI_RWOBJECT_NUMSLOTS 5 #define PYGAMEAPI_PIXELARRAY_NUMSLOTS 2 #define PYGAMEAPI_COLOR_NUMSLOTS 5 diff --git a/src_c/_sdl2/controller.c b/src_c/_sdl2/controller.c new file mode 100644 index 0000000000..bc62f90347 --- /dev/null +++ b/src_c/_sdl2/controller.c @@ -0,0 +1,592 @@ + +#include "../pgcompat.h" +#include "../pygame.h" +#include "structmember.h" + +#include "../doc/sdl2_controller_doc.h" +#define CONTROLLER_INIT_CHECK() \ + if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER)) \ + return RAISE(pgExc_SDLError, "Controller system not initialized"); + +typedef struct pgControllerObject { + PyObject_HEAD int id; + char *name; + SDL_GameController *controller; + + struct pgControllerObject *next; +} pgControllerObject; + +static pgControllerObject *_first_controller = NULL; + +#if defined(_MSC_VER) +#define strtok_r strtok_s +#endif + +static PyObject * +controller_module_init(PyObject *module, PyObject *_null) +{ + if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + } + SDL_GameControllerEventState(SDL_ENABLE); + + Py_RETURN_NONE; +} + +static PyObject * +controller_module_quit(PyObject *module, PyObject *_null) +{ + pgControllerObject *cur = _first_controller; + + while (cur) { + if (cur->controller) { + SDL_GameControllerClose(cur->controller); + cur->controller = NULL; + } + cur = cur->next; + } + + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + } + Py_RETURN_NONE; +} + +static void +_controller_module_auto_quit(void) +{ + controller_module_quit(NULL, NULL); +} + +static PyObject * +controller_module_get_init(PyObject *module, PyObject *_null) +{ + return PyBool_FromLong(SDL_WasInit(SDL_INIT_GAMECONTROLLER) != 0); +} + +static PyObject * +controller_module_is_controller(PyObject *module, PyObject *args, + PyObject *kwargs) +{ + int device_index; + static char *keywords[] = {"device_index", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", keywords, + &device_index)) { + return NULL; + } + + CONTROLLER_INIT_CHECK(); + + return PyBool_FromLong(SDL_IsGameController(device_index)); +} + +static PyObject * +controller_module_get_count(PyObject *module, PyObject *_null) +{ + CONTROLLER_INIT_CHECK(); + + int count = SDL_NumJoysticks(); + if (count < 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + + return PyLong_FromLong(count); +} + +static PyMethodDef _controller_module_methods[] = { + {"init", (PyCFunction)controller_module_init, METH_NOARGS, + DOC_SDL2_CONTROLLER_INIT}, + {"quit", (PyCFunction)controller_module_quit, METH_NOARGS, + DOC_SDL2_CONTROLLER_QUIT}, + {"get_init", (PyCFunction)controller_module_get_init, METH_NOARGS, + DOC_SDL2_CONTROLLER_GETINIT}, + {"is_controller", (PyCFunction)controller_module_is_controller, + METH_VARARGS | METH_KEYWORDS, DOC_SDL2_CONTROLLER_ISCONTROLLER}, + {"get_count", (PyCFunction)controller_module_get_count, METH_NOARGS, + DOC_SDL2_CONTROLLER_GETCOUNT}, + {NULL, NULL, 0, NULL}}; + +static PyObject * +controller_from_joystick(PyTypeObject *subtype, PyObject *args, + PyObject *kwargs) +{ + pgJoystickObject *joy; + static char *keywords[] = {"joystick", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", keywords, + &pgJoystick_Type, &joy)) { + return NULL; + } + + CONTROLLER_INIT_CHECK(); + return PyObject_CallFunction((PyObject *)subtype, "i", joy->id); +} + +static int +controller_init(pgControllerObject *, PyObject *, PyObject *); + +static PyObject * +controller_init_func(pgControllerObject *self) +{ + if (controller_init(self, NULL, NULL) == -1) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +controller_get_init(pgControllerObject *self, PyObject *_null) +{ + CONTROLLER_INIT_CHECK(); + return PyBool_FromLong(self->controller != NULL); +} + +static PyObject * +controller_quit(pgControllerObject *self, PyObject *_null) +{ + CONTROLLER_INIT_CHECK(); + if (self->controller) { + SDL_GameControllerClose(self->controller); + self->controller = NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +controller_attached(pgControllerObject *self, PyObject *_null) +{ + CONTROLLER_INIT_CHECK(); + if (!self->controller) { + return RAISE(pgExc_SDLError, "Controller is not initalized"); + } + + return PyBool_FromLong(SDL_GameControllerGetAttached(self->controller)); +} + +static PyObject * +controller_as_joystick(pgControllerObject *self, PyObject *_null) +{ + CONTROLLER_INIT_CHECK(); + JOYSTICK_INIT_CHECK(); + if (!self->controller) { + return RAISE(pgExc_SDLError, "Controller is not initalized"); + } + + return pgJoystick_New(self->id); +} + +static PyObject * +controller_get_axis(pgControllerObject *self, PyObject *args, PyObject *kwargs) +{ + int axis; + static char *keywords[] = {"axis", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", keywords, &axis)) { + return NULL; + } + CONTROLLER_INIT_CHECK(); + if (!self->controller) { + return RAISE(pgExc_SDLError, "Controller is not initalized"); + } + + if (axis < 0 || axis > SDL_CONTROLLER_AXIS_MAX - 1) { + return RAISE(pgExc_SDLError, "Invalid axis"); + } + + return PyLong_FromLong(SDL_GameControllerGetAxis(self->controller, axis)); +} + +static PyObject * +controller_get_button(pgControllerObject *self, PyObject *args, + PyObject *kwargs) +{ + int button; + static char *keywords[] = {"button", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", keywords, &button)) { + return NULL; + } + CONTROLLER_INIT_CHECK(); + if (!self->controller) { + return RAISE(pgExc_SDLError, "Controller is not initalized"); + } + + if (button < 0 || button > SDL_CONTROLLER_BUTTON_MAX) { + return RAISE(pgExc_SDLError, "Invalid button"); + } + + return PyBool_FromLong( + SDL_GameControllerGetButton(self->controller, button)); +} + +static PyObject * +controller_get_mapping(pgControllerObject *self, PyObject *_null) +{ + char *mapping, *key, *value; + char *token, *saveptr = NULL; + PyObject *dict, *value_obj = NULL; + + CONTROLLER_INIT_CHECK(); + if (!self->controller) { + return RAISE(pgExc_SDLError, "Controller is not initalized"); + } + + mapping = SDL_GameControllerMapping(self->controller); + if (!mapping) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + + dict = PyDict_New(); + if (!dict) { + goto err; + } + + token = strtok_r(mapping, ",", &saveptr); + while (token != NULL) { + key = strtok_r(token, ":", &value); + + if (value != NULL && value[0] != '\0') { + value_obj = PyUnicode_FromString(value); + if (!value_obj) { + goto err; + } + if (PyDict_SetItemString(dict, key, value_obj)) { + goto err; + } + Py_DECREF(value_obj); + } + token = strtok_r(NULL, ",", &saveptr); + } + + SDL_free(saveptr); + SDL_free(mapping); + return dict; + +err: + Py_XDECREF(value_obj); + Py_XDECREF(dict); + SDL_free(mapping); + SDL_free(saveptr); + return NULL; +} + +static PyObject * +controller_set_mapping(pgControllerObject *self, PyObject *args, + PyObject *kwargs) +{ + PyObject *dict; + static char *keywords[] = {"mapping", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", keywords, + &PyDict_Type, &dict)) { + return NULL; + } + + CONTROLLER_INIT_CHECK(); + if (!self->controller) { + return RAISE(pgExc_SDLError, "Controller is not initalized"); + } + + char guid_str[64]; + SDL_Joystick *joy = SDL_GameControllerGetJoystick(self->controller); + SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joy), guid_str, 63); + + PyObject *key, *value; + const char *key_str, *value_str; + Py_ssize_t dict_index = 0; + int offset = 0, size = 512; + char *mapping = malloc(size * sizeof(char)); + if (mapping == NULL) { + return PyErr_NoMemory(); + } + + while (PyDict_Next(dict, &dict_index, &key, &value)) { + if (!PyUnicode_Check(key) || !PyUnicode_Check(value)) { + free(mapping); + return RAISE(PyExc_TypeError, "Dict items must be strings"); + } + + key_str = PyUnicode_AsUTF8(key); + value_str = PyUnicode_AsUTF8(value); + if (key_str == NULL || value_str == NULL) { + free(mapping); + return NULL; + } + + int res = SDL_snprintf(mapping + offset, size - offset, ",%s:%s", + key_str, value_str); + if (res < 0) { + free(mapping); + return RAISE(PyExc_RuntimeError, "Internal snprintf call failed"); + } + else if (res >= size - offset) { + // Retry the same key value pair with more memory allocated + dict_index -= 1; + size *= 2; + mapping = realloc(mapping, size); + if (mapping == NULL) { + return PyErr_NoMemory(); + } + continue; + } + offset += res; + } + + int res_size = offset + 64 + (int)SDL_strlen(self->name); + char *mapping_string = malloc(res_size * sizeof(char)); + SDL_snprintf(mapping_string, res_size, "%s,%s%s", guid_str, self->name, + mapping); + + int res = SDL_GameControllerAddMapping(mapping_string); + free(mapping); + free(mapping_string); + + if (res < 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + + return PyLong_FromLong(res); +} + +static PyObject * +controller_rumble(pgControllerObject *self, PyObject *args, PyObject *kwargs) +{ + double low_freq, high_freq; + Uint32 duration; + static char *keywords[] = {"low_frequency", "high_frequency", "duration", + NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ddI", keywords, &low_freq, + &high_freq, &duration)) { + return NULL; + } + + CONTROLLER_INIT_CHECK(); + if (!self->controller) { + return RAISE(pgExc_SDLError, "Controller is not initalized"); + } + +#if SDL_VERSION_ATLEAST(2, 0, 9) + // rumble takes values in range 0 to 0xFFFF (65535) + low_freq = MAX(MIN(low_freq, 1.0f), 0.0f) * 65535; + high_freq = MAX(MIN(high_freq, 1.0f), 0.0f) * 65535; + + int success = SDL_GameControllerRumble(self->controller, (Uint16)low_freq, + (Uint16)high_freq, duration); + + return PyBool_FromLong(success == 0); +#else + Py_RETURN_FALSE; +#endif +} + +static PyObject * +controller_stop_rumble(pgControllerObject *self, PyObject *_null) +{ + CONTROLLER_INIT_CHECK(); + if (!self->controller) { + return RAISE(pgExc_SDLError, "Controller is not initalized"); + } +#if SDL_VERSION_ATLEAST(2, 0, 9) + SDL_GameControllerRumble(self->controller, 0, 0, 1); +#endif + Py_RETURN_NONE; +} + +static PyMethodDef controller_methods[] = { + {"from_joystick", (PyCFunction)controller_from_joystick, + METH_CLASS | METH_VARARGS | METH_KEYWORDS, + DOC_SDL2_CONTROLLER_CONTROLLER_FROMJOYSTICK}, + {"get_init", (PyCFunction)controller_get_init, METH_NOARGS, + DOC_SDL2_CONTROLLER_CONTROLLER_GETINIT}, + {"init", (PyCFunction)controller_init_func, METH_NOARGS, + DOC_SDL2_CONTROLLER_CONTROLLER_INIT}, + {"quit", (PyCFunction)controller_quit, METH_NOARGS, + DOC_SDL2_CONTROLLER_CONTROLLER_QUIT}, + {"attached", (PyCFunction)controller_attached, METH_NOARGS, + DOC_SDL2_CONTROLLER_CONTROLLER_ATTACHED}, + {"as_joystick", (PyCFunction)controller_as_joystick, METH_NOARGS, + DOC_SDL2_CONTROLLER_CONTROLLER_ASJOYSTICK}, + {"get_axis", (PyCFunction)controller_get_axis, + METH_VARARGS | METH_KEYWORDS, DOC_SDL2_CONTROLLER_CONTROLLER_ASJOYSTICK}, + {"get_button", (PyCFunction)controller_get_button, + METH_VARARGS | METH_KEYWORDS, DOC_SDL2_CONTROLLER_CONTROLLER_GETBUTTON}, + {"get_mapping", (PyCFunction)controller_get_mapping, METH_NOARGS, + DOC_SDL2_CONTROLLER_CONTROLLER_GETMAPPING}, + {"set_mapping", (PyCFunction)controller_set_mapping, + METH_VARARGS | METH_KEYWORDS, DOC_SDL2_CONTROLLER_CONTROLLER_SETMAPPING}, + {"rumble", (PyCFunction)controller_rumble, METH_VARARGS | METH_KEYWORDS, + DOC_SDL2_CONTROLLER_CONTROLLER_RUMBLE}, + {"stop_rumble", (PyCFunction)controller_stop_rumble, METH_NOARGS, + DOC_SDL2_CONTROLLER_CONTROLLER_STOPRUMBLE}, + {NULL, NULL, 0, NULL}}; + +static PyMemberDef controller_members[] = { + {"id", T_INT, offsetof(pgControllerObject, id), READONLY, + "Gets the id of the controller"}, + {"name", T_STRING, offsetof(pgControllerObject, name), READONLY, + "Gets the name of the controller"}, + {NULL}, +}; + +static PyObject * +controller_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) +{ + int id; + pgControllerObject *self, *cur; + static char *keywords[] = {"device_index", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", keywords, &id)) { + return NULL; + } + + CONTROLLER_INIT_CHECK(); + + if (id >= SDL_NumJoysticks() || !SDL_IsGameController(id)) { + return RAISE(pgExc_SDLError, "Invalid index"); + } + + SDL_GameController *controller = SDL_GameControllerOpen(id); + if (!controller) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + + cur = _first_controller; + while (cur) { + if (cur->controller == controller) { + Py_INCREF(cur); + return (PyObject *)cur; + } + + if (!cur->next) { + break; + } + + cur = cur->next; + } + self = PyObject_New(pgControllerObject, subtype); + if (!self) { + return NULL; + } + + if (!_first_controller) { + _first_controller = self; + } + else { + cur->next = self; + } + self->next = NULL; + self->controller = controller; + self->id = id; + self->name = NULL; + return (PyObject *)self; +} + +static int +controller_init(pgControllerObject *self, PyObject *args, PyObject *kwargs) +{ + SDL_GameController *controller; + + if (self->controller == NULL) { + controller = SDL_GameControllerOpen(self->id); + if (controller == NULL) { + PyErr_SetString(pgExc_SDLError, SDL_GetError()); + return -1; + } + self->controller = controller; + } + + if (self->name) { + free(self->name); + } + self->name = strdup(SDL_GameControllerName(self->controller)); + + return 0; +} + +void +controller_dealloc(pgControllerObject *self) +{ + pgControllerObject *cur, *prev; + if (self->controller && SDL_GameControllerGetAttached(self->controller)) { + SDL_GameControllerClose(self->controller); + } + self->controller = NULL; + free(self->name); + + cur = _first_controller; + prev = NULL; + while (cur) { + if (cur == self) { + if (!prev) { + _first_controller = self->next; + break; + } + prev->next = self->next; + break; + } + + prev = cur; + cur = cur->next; + } + PyObject_DEL(self); +} + +static PyTypeObject pgController_Type = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Controller", + .tp_basicsize = sizeof(pgControllerObject), + .tp_doc = DOC_SDL2_CONTROLLER_CONTROLLER, + .tp_new = controller_new, + .tp_init = (initproc)controller_init, + .tp_dealloc = (destructor)controller_dealloc, + .tp_methods = controller_methods, + .tp_members = controller_members, +}; + +MODINIT_DEFINE(controller) +{ + PyObject *module; + + static struct PyModuleDef _module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "controller", + .m_doc = DOC_SDL2_CONTROLLER, + .m_size = -1, + .m_methods = _controller_module_methods}; + + import_pygame_base(); + if (PyErr_Occurred()) { + return NULL; + } + + import_pygame_joystick(); + if (PyErr_Occurred()) { + return NULL; + } + + module = PyModule_Create(&_module); + + if (!module) { + return NULL; + } + + if (PyType_Ready(&pgController_Type) < 0) { + return NULL; + } + + Py_INCREF(&pgController_Type); + if (PyModule_AddObject(module, "Controller", + (PyObject *)&pgController_Type)) { + Py_DECREF(&pgController_Type); + Py_DECREF(module); + return NULL; + } + + /* note: whenever this module gets released from _sdl2, base.c _pg_quit + * should should quit the controller module automatically, instead of + * doing this */ + pg_RegisterQuit(_controller_module_auto_quit); + + return module; +} diff --git a/src_c/_sdl2/meson.build b/src_c/_sdl2/meson.build index 0478876c8c..2ad443e993 100644 --- a/src_c/_sdl2/meson.build +++ b/src_c/_sdl2/meson.build @@ -56,3 +56,12 @@ _sdl2_touch = py.extension_module( install: true, subdir: pg / '_sdl2', ) + +_sdl2_controller = py.extension_module( + 'controller', + 'controller.c', + dependencies: pg_base_deps, + include_directories: '..', + install: true, + subdir: pg / '_sdl2', +) diff --git a/src_c/base.c b/src_c/base.c index 2a609e6601..573bca3aa2 100644 --- a/src_c/base.c +++ b/src_c/base.c @@ -177,36 +177,45 @@ pg_EnvShouldBlendAlphaSDL2(void); static int pg_CheckSDLVersions(void) { +#if SDL_VERSION_ATLEAST(3, 0, 0) + int compiled = SDL_VERSION; + int linked = SDL_GetVersion(); +#else SDL_version compiled; SDL_version linked; - SDL_VERSION(&compiled); SDL_GetVersion(&linked); +#endif /* only check the major version, in general major version is bumped for ABI * incompatible changes */ - if (compiled.major != linked.major) { + if (PG_FIND_VNUM_MAJOR(compiled) != PG_FIND_VNUM_MAJOR(linked)) { PyErr_Format(PyExc_RuntimeError, "ABI incompatibility detected! SDL compiled with " "%d.%d.%d, linked to %d.%d.%d (major versions should " "have matched)", - compiled.major, compiled.minor, compiled.patch, - linked.major, linked.minor, linked.patch); + PG_FIND_VNUM_MAJOR(compiled), + PG_FIND_VNUM_MINOR(compiled), + PG_FIND_VNUM_MICRO(compiled), PG_FIND_VNUM_MAJOR(linked), + PG_FIND_VNUM_MINOR(linked), PG_FIND_VNUM_MICRO(linked)); return 0; } /* Basically, this is compiled_version > linked_version case, which we * don't allow */ - if ((linked.minor == compiled.minor && linked.patch < compiled.patch) || - linked.minor < compiled.minor) { + if ((PG_FIND_VNUM_MINOR(linked) == PG_FIND_VNUM_MINOR(compiled) && + PG_FIND_VNUM_MICRO(linked) < PG_FIND_VNUM_MICRO(compiled)) || + PG_FIND_VNUM_MINOR(linked) < PG_FIND_VNUM_MINOR(compiled)) { /* We do some ifdefs to support different SDL versions at compile time. We use newer API only when available. Downgrading via dynamic API probably breaks this.*/ PyErr_Format(PyExc_RuntimeError, "Dynamic linking causes SDL downgrade! (compiled with " "version %d.%d.%d, linked to %d.%d.%d)", - compiled.major, compiled.minor, compiled.patch, - linked.major, linked.minor, linked.patch); + PG_FIND_VNUM_MAJOR(compiled), + PG_FIND_VNUM_MINOR(compiled), + PG_FIND_VNUM_MICRO(compiled), PG_FIND_VNUM_MAJOR(linked), + PG_FIND_VNUM_MINOR(linked), PG_FIND_VNUM_MICRO(linked)); return 0; } @@ -270,7 +279,7 @@ pg_mod_autoinit(const char *modname) } if (funcobj) { - temp = PyObject_CallObject(funcobj, NULL); + temp = PyObject_CallNoArgs(funcobj); if (temp) { Py_DECREF(temp); ret = 1; @@ -305,7 +314,7 @@ pg_mod_autoquit(const char *modname) PyErr_Clear(); if (funcobj) { - temp = PyObject_CallObject(funcobj, NULL); + temp = PyObject_CallNoArgs(funcobj); Py_XDECREF(temp); } @@ -373,7 +382,12 @@ static PyObject * pg_get_sdl_version(PyObject *self, PyObject *args, PyObject *kwargs) { int linked = 1; /* Default is linked version. */ - SDL_version v; +#if SDL_VERSION_ATLEAST(3, 0, 0) + int version = SDL_VERSION; +#else + SDL_version version; + SDL_VERSION(&version); +#endif static char *keywords[] = {"linked", NULL}; @@ -382,12 +396,16 @@ pg_get_sdl_version(PyObject *self, PyObject *args, PyObject *kwargs) } if (linked) { - SDL_GetVersion(&v); - } - else { - SDL_VERSION(&v); +#if SDL_VERSION_ATLEAST(3, 0, 0) + version = SDL_GetVersion(); +#else + SDL_GetVersion(&version); +#endif } - return Py_BuildValue("iii", v.major, v.minor, v.patch); + + return Py_BuildValue("iii", PG_FIND_VNUM_MAJOR(version), + PG_FIND_VNUM_MINOR(version), + PG_FIND_VNUM_MICRO(version)); } static PyObject * @@ -429,7 +447,7 @@ _pg_quit(void) } if (PyCallable_Check(quit)) { - temp = PyObject_CallObject(quit, NULL); + temp = PyObject_CallNoArgs(quit); if (temp) Py_DECREF(temp); else @@ -2331,7 +2349,7 @@ MODINIT_DEFINE(base) if (!quit) { /* assertion */ goto error; } - rval = PyObject_CallFunctionObjArgs(atexit_register, quit, NULL); + rval = PyObject_CallOneArg(atexit_register, quit); Py_DECREF(atexit_register); Py_DECREF(quit); atexit_register = NULL; diff --git a/src_c/bufferproxy.c b/src_c/bufferproxy.c index a8c4a436d1..318e0ad0f4 100644 --- a/src_c/bufferproxy.c +++ b/src_c/bufferproxy.c @@ -107,7 +107,7 @@ _get_buffer_from_dict(PyObject *dict, Py_buffer *view_p, int flags) PyObject *py_rval; Py_INCREF(py_callback); - py_rval = PyObject_CallFunctionObjArgs(py_callback, obj, NULL); + py_rval = PyObject_CallOneArg(py_callback, obj); Py_DECREF(py_callback); if (!py_rval) { pgBuffer_Release(pg_dict_view_p); @@ -153,7 +153,7 @@ _release_buffer_from_dict(Py_buffer *view_p) PyObject *py_rval; Py_INCREF(py_callback); - py_rval = PyObject_CallFunctionObjArgs(py_callback, obj, NULL); + py_rval = PyObject_CallOneArg(py_callback, obj); if (py_rval) { Py_DECREF(py_rval); } diff --git a/src_c/circle.c b/src_c/circle.c index 63df177084..1ff72b6737 100644 --- a/src_c/circle.c +++ b/src_c/circle.c @@ -47,7 +47,8 @@ pg_circle_init(pgCircleObject *self, PyObject *args, PyObject *kwds) PyErr_SetString( PyExc_TypeError, "Arguments must be a Circle, a sequence of length 3 or 2, or an " - "object with an attribute called 'circle'"); + "object with an attribute called 'circle', all with corresponding " + "nonnegative radius argument"); return -1; } return 0; @@ -311,11 +312,10 @@ pg_circle_rotate_ip(pgCircleObject *self, PyObject *const *args, Py_RETURN_NONE; } -static PyObject * -pg_circle_collideswith(pgCircleObject *self, PyObject *arg) +static PG_FORCEINLINE int +_pg_circle_collideswith(pgCircleBase *scirc, PyObject *arg) { int result = 0; - pgCircleBase *scirc = &self->circle; if (pgCircle_Check(arg)) { result = pgCollision_CircleCircle(&pgCircle_AsCircle(arg), scirc); } @@ -334,21 +334,168 @@ pg_circle_collideswith(pgCircleObject *self, PyObject *arg) else if (PySequence_Check(arg)) { double x, y; if (!pg_TwoDoublesFromObj(arg, &x, &y)) { - return RAISE( + PyErr_SetString( PyExc_TypeError, "Invalid point argument, must be a sequence of two numbers"); + return -1; } result = pgCollision_CirclePoint(scirc, x, y); } else { - return RAISE(PyExc_TypeError, - "Invalid shape argument, must be a Circle, Rect / FRect, " - "Line, Polygon or a sequence of two numbers"); + PyErr_SetString( + PyExc_TypeError, + "Invalid point argument, must be a sequence of 2 numbers"); + return -1; + } + + return result; +} + +static PyObject * +pg_circle_collideswith(pgCircleObject *self, PyObject *arg) +{ + int result = _pg_circle_collideswith(&self->circle, arg); + if (result == -1) { + return NULL; } return PyBool_FromLong(result); } +static PyObject * +pg_circle_collidelist(pgCircleObject *self, PyObject *arg) +{ + Py_ssize_t i; + pgCircleBase *scirc = &self->circle; + int colliding; + + if (!PySequence_Check(arg)) { + return RAISE(PyExc_TypeError, "colliders argument must be a sequence"); + } + + /* fast path */ + if (pgSequenceFast_Check(arg)) { + PyObject **items = PySequence_Fast_ITEMS(arg); + for (i = 0; i < PySequence_Fast_GET_SIZE(arg); i++) { + if ((colliding = _pg_circle_collideswith(scirc, items[i])) == -1) { + /*invalid shape*/ + return NULL; + } + if (colliding) { + return PyLong_FromSsize_t(i); + } + } + return PyLong_FromLong(-1); + } + + /* general sequence path */ + for (i = 0; i < PySequence_Length(arg); i++) { + PyObject *obj = PySequence_ITEM(arg, i); + if (!obj) { + return NULL; + } + + if ((colliding = _pg_circle_collideswith(scirc, obj)) == -1) { + /*invalid shape*/ + Py_DECREF(obj); + return NULL; + } + Py_DECREF(obj); + + if (colliding) { + return PyLong_FromSsize_t(i); + } + } + + return PyLong_FromLong(-1); +} + +static PyObject * +pg_circle_collidelistall(pgCircleObject *self, PyObject *arg) +{ + PyObject *ret; + Py_ssize_t i; + pgCircleBase *scirc = &self->circle; + int colliding; + + if (!PySequence_Check(arg)) { + return RAISE(PyExc_TypeError, "Argument must be a sequence"); + } + + ret = PyList_New(0); + if (!ret) { + return NULL; + } + + /* fast path */ + if (pgSequenceFast_Check(arg)) { + PyObject **items = PySequence_Fast_ITEMS(arg); + + for (i = 0; i < PySequence_Fast_GET_SIZE(arg); i++) { + if ((colliding = _pg_circle_collideswith(scirc, items[i])) == -1) { + /*invalid shape*/ + Py_DECREF(ret); + return NULL; + } + + if (!colliding) { + continue; + } + + PyObject *num = PyLong_FromSsize_t(i); + if (!num) { + Py_DECREF(ret); + return NULL; + } + + if (PyList_Append(ret, num)) { + Py_DECREF(num); + Py_DECREF(ret); + return NULL; + } + Py_DECREF(num); + } + + return ret; + } + + /* general sequence path */ + for (i = 0; i < PySequence_Length(arg); i++) { + PyObject *obj = PySequence_ITEM(arg, i); + if (!obj) { + Py_DECREF(ret); + return NULL; + } + + if ((colliding = _pg_circle_collideswith(scirc, obj)) == -1) { + /*invalid shape*/ + Py_DECREF(ret); + Py_DECREF(obj); + return NULL; + } + Py_DECREF(obj); + + if (!colliding) { + continue; + } + + PyObject *num = PyLong_FromSsize_t(i); + if (!num) { + Py_DECREF(ret); + return NULL; + } + + if (PyList_Append(ret, num)) { + Py_DECREF(num); + Py_DECREF(ret); + return NULL; + } + Py_DECREF(num); + } + + return ret; +} + static PyObject * pg_circle_as_rect(pgCircleObject *self, PyObject *_null) { @@ -424,6 +571,28 @@ pg_circle_contains(pgCircleObject *self, PyObject *arg) return PyBool_FromLong(result); } +static PyObject * +pg_circle_intersect(pgCircleObject *self, PyObject *arg) +{ + pgCircleBase *scirc = &self->circle; + + /* max number of intersections when supporting: Circle (2), */ + double intersections[4]; + int num = 0; + + if (pgCircle_Check(arg)) { + pgCircleBase *other = &pgCircle_AsCircle(arg); + num = pgIntersection_CircleCircle(scirc, other, intersections); + } + else { + PyErr_Format(PyExc_TypeError, "Argument must be a CircleType, got %s", + Py_TYPE(arg)->tp_name); + return NULL; + } + + return pg_PointList_FromArrayDouble(intersections, num * 2); +} + static struct PyMethodDef pg_circle_methods[] = { {"collidepoint", (PyCFunction)pg_circle_collidepoint, METH_FASTCALL, DOC_CIRCLE_COLLIDEPOINT}, @@ -438,6 +607,10 @@ static struct PyMethodDef pg_circle_methods[] = { DOC_CIRCLE_UPDATE}, {"collideswith", (PyCFunction)pg_circle_collideswith, METH_O, DOC_CIRCLE_COLLIDESWITH}, + {"collidelist", (PyCFunction)pg_circle_collidelist, METH_O, + DOC_CIRCLE_COLLIDELIST}, + {"collidelistall", (PyCFunction)pg_circle_collidelistall, METH_O, + DOC_CIRCLE_COLLIDELISTALL}, {"as_rect", (PyCFunction)pg_circle_as_rect, METH_NOARGS, DOC_CIRCLE_ASRECT}, {"as_frect", (PyCFunction)pg_circle_as_frect, METH_NOARGS, @@ -449,6 +622,8 @@ static struct PyMethodDef pg_circle_methods[] = { {"rotate_ip", (PyCFunction)pg_circle_rotate_ip, METH_FASTCALL, DOC_CIRCLE_ROTATEIP}, {"contains", (PyCFunction)pg_circle_contains, METH_O, DOC_CIRCLE_CONTAINS}, + {"intersect", (PyCFunction)pg_circle_intersect, METH_O, + DOC_CIRCLE_INTERSECT}, {NULL, NULL, 0, NULL}}; #define GETTER_SETTER(name) \ @@ -494,8 +669,8 @@ pg_circle_setr(pgCircleObject *self, PyObject *value, void *closure) return -1; } - if (radius <= 0) { - PyErr_SetString(PyExc_ValueError, "Radius must be positive"); + if (radius < 0) { + PyErr_SetString(PyExc_ValueError, "Radius must be nonnegative"); return -1; } @@ -523,9 +698,9 @@ pg_circle_setr_sqr(pgCircleObject *self, PyObject *value, void *closure) return -1; } - if (radius_squared <= 0) { + if (radius_squared < 0) { PyErr_SetString(PyExc_ValueError, - "Invalid radius squared value, must be > 0"); + "Invalid radius squared value, must be nonnegative"); return -1; } @@ -570,8 +745,9 @@ pg_circle_setarea(pgCircleObject *self, PyObject *value, void *closure) return -1; } - if (area <= 0) { - PyErr_SetString(PyExc_ValueError, "Invalid area value, must be > 0"); + if (area < 0) { + PyErr_SetString(PyExc_ValueError, + "Invalid area value, must be nonnegative"); return -1; } @@ -600,9 +776,9 @@ pg_circle_setcircumference(pgCircleObject *self, PyObject *value, return -1; } - if (circumference <= 0) { + if (circumference < 0) { PyErr_SetString(PyExc_ValueError, - "Invalid circumference value, must be > 0"); + "Invalid circumference value, must be nonnegative"); return -1; } @@ -630,9 +806,9 @@ pg_circle_setdiameter(pgCircleObject *self, PyObject *value, void *closure) return -1; } - if (diameter <= 0) { + if (diameter < 0) { PyErr_SetString(PyExc_ValueError, - "Invalid diameter value, must be > 0"); + "Invalid diameter value, must be nonnegative"); return -1; } @@ -641,12 +817,104 @@ pg_circle_setdiameter(pgCircleObject *self, PyObject *value, void *closure) return 0; } +static PyObject * +pg_circle_gettop(pgCircleObject *self, void *closure) +{ + return pg_tuple_couple_from_values_double(self->circle.x, + self->circle.y - self->circle.r); +} + +static int +pg_circle_settop(pgCircleObject *self, PyObject *value, void *closure) +{ + double x, y; + + DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value); + + if (!pg_TwoDoublesFromObj(value, &x, &y)) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of 2 numbers"); + return -1; + } + + self->circle.y = y + self->circle.r; + self->circle.x = x; + + return 0; +} + +static PyObject * +pg_circle_getleft(pgCircleObject *self, void *closure) +{ + return pg_tuple_couple_from_values_double(self->circle.x - self->circle.r, + self->circle.y); +} + +static int +pg_circle_setleft(pgCircleObject *self, PyObject *value, void *closure) +{ + double x, y; + + DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value); + + if (!pg_TwoDoublesFromObj(value, &x, &y)) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of 2 numbers"); + return -1; + } + + self->circle.x = x + self->circle.r; + self->circle.y = y; + + return 0; +} + +static PyObject * +pg_circle_getbottom(pgCircleObject *self, void *closure) +{ + return pg_tuple_couple_from_values_double(self->circle.x, + self->circle.y + self->circle.r); +} + static int -double_compare(double a, double b) +pg_circle_setbottom(pgCircleObject *self, PyObject *value, void *closure) { - /* Uses both a fixed epsilon and an adaptive epsilon */ - const double e = 1e-6; - return fabs(a - b) < e || fabs(a - b) <= e * MAX(fabs(a), fabs(b)); + double x, y; + + DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value); + + if (!pg_TwoDoublesFromObj(value, &x, &y)) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of 2 numbers"); + return -1; + } + + self->circle.y = y - self->circle.r; + self->circle.x = x; + + return 0; +} + +static PyObject * +pg_circle_getright(pgCircleObject *self, void *closure) +{ + return pg_tuple_couple_from_values_double(self->circle.x + self->circle.r, + self->circle.y); +} + +static int +pg_circle_setright(pgCircleObject *self, PyObject *value, void *closure) +{ + double x, y; + + DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value); + + if (!pg_TwoDoublesFromObj(value, &x, &y)) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of 2 numbers"); + return -1; + } + + self->circle.x = x - self->circle.r; + self->circle.y = y; + + return 0; } static PyObject * @@ -691,6 +959,14 @@ static PyGetSetDef pg_circle_getsets[] = { DOC_CIRCLE_AREA, NULL}, {"circumference", (getter)pg_circle_getcircumference, (setter)pg_circle_setcircumference, DOC_CIRCLE_CIRCUMFERENCE, NULL}, + {"top", (getter)pg_circle_gettop, (setter)pg_circle_settop, DOC_CIRCLE_TOP, + NULL}, + {"left", (getter)pg_circle_getleft, (setter)pg_circle_setleft, + DOC_CIRCLE_LEFT, NULL}, + {"bottom", (getter)pg_circle_getbottom, (setter)pg_circle_setbottom, + DOC_CIRCLE_BOTTOM, NULL}, + {"right", (getter)pg_circle_getright, (setter)pg_circle_setright, + DOC_CIRCLE_RIGHT, NULL}, {NULL, 0, NULL, NULL, NULL}}; static PyTypeObject pgCircle_Type = { diff --git a/src_c/color.c b/src_c/color.c index 80761a4b0d..845a4c0314 100644 --- a/src_c/color.c +++ b/src_c/color.c @@ -47,12 +47,16 @@ #include +static inline double +pg_round(double d) +{ #if (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) && \ !defined(round) -#define pg_round(d) (((d < 0) ? (ceil((d) - 0.5)) : (floor((d) + 0.5)))) + return (((d < 0) ? (ceil((d)-0.5)) : (floor((d) + 0.5)))); #else -#define pg_round(d) round(d) + return round(d); #endif +} typedef enum { TRISTATE_SUCCESS, TRISTATE_FAIL, TRISTATE_ERROR } tristate; @@ -813,10 +817,10 @@ _color_lerp(pgColorObject *self, PyObject *args, PyObject *kw) return RAISE(PyExc_ValueError, "Argument 2 must be in range [0, 1]"); } - new_rgba[0] = (Uint8)pg_round(self->data[0] * (1 - amt) + rgba[0] * amt); - new_rgba[1] = (Uint8)pg_round(self->data[1] * (1 - amt) + rgba[1] * amt); - new_rgba[2] = (Uint8)pg_round(self->data[2] * (1 - amt) + rgba[2] * amt); - new_rgba[3] = (Uint8)pg_round(self->data[3] * (1 - amt) + rgba[3] * amt); + for (int i = 0; i < 4; i++) { + new_rgba[i] = + (Uint8)pg_round(self->data[i] * (1 - amt) + rgba[i] * amt); + } return (PyObject *)_color_new_internal(Py_TYPE(self), new_rgba); } @@ -1008,21 +1012,12 @@ _color_set_hsva(pgColorObject *color, PyObject *value, void *closure) DEL_ATTR_NOT_SUPPORTED_CHECK("hsva", value); - if (!PySequence_Check(value) || PySequence_Size(value) < 3) { + if (!PySequence_Check(value) || PySequence_Size(value) < 3 || + PySequence_Size(value) > 4) { PyErr_SetString(PyExc_ValueError, "invalid HSVA value"); return -1; } - if (PySequence_Size(value) > 4) { - if (PyErr_WarnEx( - PyExc_DeprecationWarning, - "Passing sequences of size larger than 4 is deprecated, doing " - "this will error in a future version", - 1) == -1) { - return -1; - } - } - /* H */ item = PySequence_GetItem(value, 0); if (!item || !_get_double(item, &(hsva[0])) || hsva[0] < 0 || @@ -1183,21 +1178,12 @@ _color_set_hsla(pgColorObject *color, PyObject *value, void *closure) DEL_ATTR_NOT_SUPPORTED_CHECK("hsla", value); - if (!PySequence_Check(value) || PySequence_Size(value) < 3) { + if (!PySequence_Check(value) || PySequence_Size(value) < 3 || + PySequence_Size(value) > 4) { PyErr_SetString(PyExc_ValueError, "invalid HSLA value"); return -1; } - if (PySequence_Size(value) > 4) { - if (PyErr_WarnEx( - PyExc_DeprecationWarning, - "Passing sequences of size larger than 4 is deprecated, doing " - "this will error in a future version", - 1) == -1) { - return -1; - } - } - /* H */ item = PySequence_GetItem(value, 0); if (!item || !_get_double(item, &(hsla[0])) || hsla[0] < 0 || @@ -1358,21 +1344,11 @@ _color_set_i1i2i3(pgColorObject *color, PyObject *value, void *closure) DEL_ATTR_NOT_SUPPORTED_CHECK("i1i2i3", value); - if (!PySequence_Check(value) || PySequence_Size(value) < 3) { + if (!PySequence_Check(value) || PySequence_Size(value) != 3) { PyErr_SetString(PyExc_ValueError, "invalid I1I2I3 value"); return -1; } - if (PySequence_Size(value) > 3) { - if (PyErr_WarnEx( - PyExc_DeprecationWarning, - "Passing sequences of size larger than 3 is deprecated, doing " - "this will error in a future version", - 1) == -1) { - return -1; - } - } - /* I1 */ item = PySequence_GetItem(value, 0); if (!item || !_get_double(item, &(i1i2i3[0])) || i1i2i3[0] < 0 || @@ -1440,21 +1416,11 @@ _color_set_cmy(pgColorObject *color, PyObject *value, void *closure) DEL_ATTR_NOT_SUPPORTED_CHECK("cmy", value); - if (!PySequence_Check(value) || PySequence_Size(value) < 3) { + if (!PySequence_Check(value) || PySequence_Size(value) != 3) { PyErr_SetString(PyExc_ValueError, "invalid CMY value"); return -1; } - if (PySequence_Size(value) > 3) { - if (PyErr_WarnEx( - PyExc_DeprecationWarning, - "Passing sequences of size larger than 3 is deprecated, doing " - "this will error in a future version", - 1) == -1) { - return -1; - } - } - /* I1 */ item = PySequence_GetItem(value, 0); if (!item || !_get_double(item, &(cmy[0])) || cmy[0] < 0 || cmy[0] > 1) { @@ -1510,21 +1476,12 @@ _color_set_normalized(pgColorObject *color, PyObject *value, void *closure) DEL_ATTR_NOT_SUPPORTED_CHECK("normalized", value); - if (!PySequence_Check(value) || PySequence_Size(value) < 3) { + if (!PySequence_Check(value) || PySequence_Size(value) < 3 || + PySequence_Size(value) > 4) { PyErr_SetString(PyExc_ValueError, "invalid normalized value"); return -1; } - if (PySequence_Size(value) > 4) { - if (PyErr_WarnEx( - PyExc_DeprecationWarning, - "Passing sequences of size larger than 4 is deprecated, doing " - "this will error in a future version", - 1) == -1) { - return -1; - } - } - item = PySequence_GetItem(value, 0); if (!item || !_get_double(item, &(frgba[0])) || frgba[0] < 0.0 || frgba[0] > 1.0) { diff --git a/src_c/constants.c b/src_c/constants.c index 7464c93d9f..6856db5568 100644 --- a/src_c/constants.c +++ b/src_c/constants.c @@ -642,6 +642,16 @@ MODINIT_DEFINE(constants) DEC_CONSTS(WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED); DEC_CONSTS(WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); +#if SDL_VERSION_ATLEAST(2, 0, 16) + DEC_CONSTS(FLASH_CANCEL, SDL_FLASH_CANCEL); + DEC_CONSTS(FLASH_BRIEFLY, SDL_FLASH_BRIEFLY); + DEC_CONSTS(FLASH_UNTIL_FOCUSED, SDL_FLASH_UNTIL_FOCUSED); +#else + DEC_CONSTS(FLASH_CANCEL, -1); + DEC_CONSTS(FLASH_BRIEFLY, -1); + DEC_CONSTS(FLASH_UNTIL_FOCUSED, -1); +#endif + if (PyModule_AddObject(module, "__all__", all_list)) { Py_DECREF(all_list); Py_DECREF(module); diff --git a/src_c/cython/pygame/_sdl2/audio.pyx b/src_c/cython/pygame/_sdl2/audio.pyx index 83d15566ef..c3667d5e32 100644 --- a/src_c/cython/pygame/_sdl2/audio.pyx +++ b/src_c/cython/pygame/_sdl2/audio.pyx @@ -57,7 +57,7 @@ def get_audio_device_names(iscapture = False): cdef int count = SDL_GetNumAudioDevices(iscapture) if count == -1: raise error('Audio system not initialised') - + names = [] for i in range(count): name = SDL_GetAudioDeviceName(i, iscapture) @@ -84,8 +84,8 @@ cdef void recording_cb(void* userdata, Uint8* stream, int len) nogil: raise -# disable auto_pickle since it causes stubcheck error -@cython.auto_pickle(False) +# disable auto_pickle since it causes stubcheck error +@cython.auto_pickle(False) cdef class AudioDevice: def __cinit__(self): self._deviceid = 0 diff --git a/src_c/cython/pygame/_sdl2/controller_old.pyx b/src_c/cython/pygame/_sdl2/controller_old.pyx index 6df1dc4407..cb7d5ad307 100644 --- a/src_c/cython/pygame/_sdl2/controller_old.pyx +++ b/src_c/cython/pygame/_sdl2/controller_old.pyx @@ -10,7 +10,7 @@ cdef extern from "../pygame.h" nogil: cdef extern from "SDL.h" nogil: void SDL_free(void *mem) - int SDL_VERSION_ATLEAST(int major, int minor, int patch) + int SDL_VERSION_ATLEAST(int major, int minor, int patch) import_pygame_joystick() @@ -100,8 +100,8 @@ def name_forindex(index): return None -# disable auto_pickle since it causes stubcheck error -@cython.auto_pickle(False) +# disable auto_pickle since it causes stubcheck error +@cython.auto_pickle(False) cdef class Controller: _controllers = [] @@ -250,7 +250,7 @@ cdef class Controller: """ _gamecontroller_init_check() self._CLOSEDCHECK() - + duration = max(duration, 0) low = min(max(low_frequency, 0.0), 1.0) high = min(max(high_frequency, 0.0), 1.0) diff --git a/src_c/cython/pygame/_sdl2/video.pxd b/src_c/cython/pygame/_sdl2/video.pxd index bedfb8b967..8d3d868286 100644 --- a/src_c/cython/pygame/_sdl2/video.pxd +++ b/src_c/cython/pygame/_sdl2/video.pxd @@ -90,7 +90,7 @@ cdef extern from "SDL.h" nogil: SDL_FPoint position SDL_Color color SDL_FPoint tex_coord - + ctypedef enum SDL_ScaleMode "_pgsdlScaleMode": SDL_ScaleModeNearest, SDL_ScaleModeLinear, @@ -151,8 +151,8 @@ cdef extern from "SDL.h" nogil: # https://wiki.libsdl.org/SDL_RenderCopyExF # https://wiki.libsdl.org/SDL_RenderPresent int SDL_GetRenderDrawColor(SDL_Renderer* renderer, - Uint8* r, - Uint8* g, + Uint8* r, + Uint8* g, Uint8* b, Uint8* a) int SDL_SetRenderDrawColor(SDL_Renderer* renderer, @@ -227,7 +227,7 @@ cdef extern from "SDL.h" nogil: SDL_BlendFactor srcAlphaFactor, SDL_BlendFactor dstAlphaFactor, SDL_BlendOperation alphaOperation) - + ctypedef enum SDL_BlendOperation: SDL_BLENDOPERATION_ADD = 0x00000001, SDL_BLENDOPERATION_SUBTRACT = 0x00000002, @@ -370,7 +370,7 @@ cdef extern from "SDL.h" nogil: # https://wiki.libsdl.org/SDL_RenderGetIntegerScale int SDL_RenderSetScale(SDL_Renderer* renderer, float scaleX, - float scaleY) + float scaleY) void SDL_RenderGetScale(SDL_Renderer* renderer, float* scaleX, float* scaleY) @@ -382,7 +382,7 @@ cdef extern from "SDL.h" nogil: int* h) int SDL_RenderGetIntegerScale(SDL_Renderer* renderer) - int SDL_VERSION_ATLEAST(int major, int minor, int patch) + int SDL_VERSION_ATLEAST(int major, int minor, int patch) # https://wiki.libsdl.org/SDL_GetWindowPixelFormat # https://wiki.libsdl.org/SDL_IntersectRect @@ -414,7 +414,7 @@ cdef extern from "pygame.h" nogil: ctypedef class pygame.color.Color [object pgColorObject]: cdef Uint8 data[4] cdef Uint8 len - + ctypedef enum pgColorHandleFlags: PG_COLOR_HANDLE_SIMPLE PG_COLOR_HANDLE_STR @@ -425,7 +425,7 @@ cdef extern from "pygame.h" nogil: ctypedef class pygame.rect.Rect [object pgRectObject]: cdef SDL_Rect r cdef object weakreflist - + ctypedef class pygame.window.Window [object pgWindowObject]: cdef SDL_Window *_win cdef SDL_bool _is_borrowed diff --git a/src_c/cython/pygame/_sdl2/video.pyx b/src_c/cython/pygame/_sdl2/video.pyx index 0dd2308b84..af8ea28e1e 100644 --- a/src_c/cython/pygame/_sdl2/video.pyx +++ b/src_c/cython/pygame/_sdl2/video.pyx @@ -167,7 +167,7 @@ cdef Uint32 format_from_depth(int depth): Rmask, Gmask, Bmask, Amask) -# disable auto_pickle since it causes stubcheck error +# disable auto_pickle since it causes stubcheck error @cython.auto_pickle(False) cdef class Texture: @@ -320,7 +320,7 @@ cdef class Texture: """Get or set the blend mode for texture drawing operations Gets or sets the blend mode for the texture's drawing operations. - Valid blend modes are any of the ``BLENDMODE_*`` constants or a custom one. + Valid blend modes are any of the ``BLENDMODE_*`` constants or a custom one. """ # https://wiki.libsdl.org/SDL_GetTextureBlendMode cdef SDL_BlendMode blendMode @@ -342,13 +342,13 @@ cdef class Texture: """Get or set the additional color value multiplied into texture drawing operations """ cdef Uint8[4] rgba - + # https://wiki.libsdl.org/SDL_GetTextureColorMod cdef int res = SDL_GetTextureColorMod(self._tex, &(rgba[0]), &(rgba[1]), &(rgba[2])) - rgba[3] = 255 + rgba[3] = 255 if res < 0: raise error() @@ -570,7 +570,7 @@ cdef class Texture: if rectptr == NULL and area is not None: raise TypeError('area must be a rectangle or None') - + cdef int dst_width, dst_height if rectptr == NULL: dst_width = self.width @@ -578,7 +578,7 @@ cdef class Texture: else: dst_width = rect.w dst_height = rect.h - + if dst_height > surf.h or dst_width > surf.w: # if the surface is smaller than the destination rect, # clip the rect to prevent segfault @@ -619,8 +619,8 @@ cdef class Texture: if res < 0: raise error() -# disable auto_pickle since it causes stubcheck error -@cython.auto_pickle(False) +# disable auto_pickle since it causes stubcheck error +@cython.auto_pickle(False) cdef class Image: def __cinit__(self): @@ -745,8 +745,8 @@ cdef class Image: self.flip_x, self.flip_y) -# disable auto_pickle since it causes stubcheck error -@cython.auto_pickle(False) +# disable auto_pickle since it causes stubcheck error +@cython.auto_pickle(False) cdef class Renderer: @classmethod @@ -786,7 +786,7 @@ cdef class Renderer: the refresh rate. :param bool target_texture: Whether the renderer should support setting :class:`Texture` objects as target textures, to - enable drawing onto them. + enable drawing onto them. :class:`Renderer` objects provide a cross-platform API for rendering 2D @@ -800,7 +800,7 @@ cdef class Renderer: If configured correctly and supported by an underlying rendering driver, Renderer objects can have a :class:`Texture` object temporarily set as a target texture (the Texture object must have been created with target texture usage support), - which allows those textures to be drawn onto. + which allows those textures to be drawn onto. To present drawn content onto the window, :meth:`Renderer.present` should be called. :meth:`Renderer.clear` should be called to clear any drawn content @@ -920,7 +920,7 @@ cdef class Renderer: :param area: A :class:`pygame.Rect` or tuple representing the drawing area on the target, or ``None`` to use the - entire area of the current rendering target. + entire area of the current rendering target. """ # https://wiki.libsdl.org/SDL_RenderSetViewport if area is None: @@ -1078,7 +1078,7 @@ cdef class Renderer: cdef SDL_FRect *frectptr cdef int res - + frectptr = pgFRect_FromObject(rect, &_frect) if frectptr == NULL: raise TypeError('expected a rectangle') @@ -1105,7 +1105,7 @@ cdef class Renderer: # https://wiki.libsdl.org/SDL_RenderGeometry if not SDL_VERSION_ATLEAST(2, 0, 18): raise error("fill_triangle requires SDL 2.0.18 or newer") - + cdef Uint8[4] rgba cdef int res = SDL_GetRenderDrawColor(self._renderer, @@ -1146,7 +1146,7 @@ cdef class Renderer: # https://wiki.libsdl.org/SDL_RenderGeometry if not SDL_VERSION_ATLEAST(2, 0, 18): raise error("fill_quad requires SDL 2.0.18 or newer") - + cdef Uint8[4] rgba cdef int res = SDL_GetRenderDrawColor(self._renderer, diff --git a/src_c/cython/pygame/pypm.pyx b/src_c/cython/pygame/pypm.pyx index d45e4aa9c0..8b47b89377 100644 --- a/src_c/cython/pygame/pypm.pyx +++ b/src_c/cython/pygame/pypm.pyx @@ -146,6 +146,7 @@ cdef extern from "porttime.h": ctypedef long PtTimestamp ctypedef void (* PtCallback)(PtTimestamp timestamp, void *userData) PtError Pt_Start(int resolution, PtCallback *callback, void *userData) + PtError Pt_Stop() PtTimestamp Pt_Time() cdef long _pypm_initialized @@ -171,6 +172,7 @@ def Terminate(): your system may crash. """ + Pt_Stop() Pm_Terminate() _pypm_initialized = 0 diff --git a/src_c/display.c b/src_c/display.c index f677bce3e3..af7e268cbc 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -126,9 +126,7 @@ static PyObject * pg_display_resource(char *filename) { PyObject *imagemodule = NULL; - PyObject *load_basicfunc = NULL; PyObject *pkgdatamodule = NULL; - PyObject *resourcefunc = NULL; PyObject *fresult = NULL; PyObject *result = NULL; PyObject *name = NULL; @@ -137,19 +135,12 @@ pg_display_resource(char *filename) if (!pkgdatamodule) goto display_resource_end; - resourcefunc = PyObject_GetAttrString(pkgdatamodule, resourcefunc_name); - if (!resourcefunc) - goto display_resource_end; - imagemodule = PyImport_ImportModule(imagemodule_name); if (!imagemodule) goto display_resource_end; - load_basicfunc = PyObject_GetAttrString(imagemodule, load_basicfunc_name); - if (!load_basicfunc) - goto display_resource_end; - - fresult = PyObject_CallFunction(resourcefunc, "s", filename); + fresult = + PyObject_CallMethod(pkgdatamodule, resourcefunc_name, "s", filename); if (!fresult) goto display_resource_end; @@ -166,15 +157,14 @@ pg_display_resource(char *filename) PyErr_Clear(); } - result = PyObject_CallFunction(load_basicfunc, "O", fresult); + result = + PyObject_CallMethod(imagemodule, load_basicfunc_name, "O", fresult); if (!result) goto display_resource_end; display_resource_end: Py_XDECREF(pkgdatamodule); - Py_XDECREF(resourcefunc); Py_XDECREF(imagemodule); - Py_XDECREF(load_basicfunc); Py_XDECREF(fresult); Py_XDECREF(name); return result; @@ -284,18 +274,6 @@ pg_vidinfo_getattr(PyObject *self, char *name) { pg_VideoInfo *info = &((pgVidInfoObject *)self)->info; - int current_w = -1; - int current_h = -1; - - SDL_version versioninfo; - SDL_VERSION(&versioninfo); - - if (versioninfo.major > 1 || - (versioninfo.minor >= 2 && versioninfo.patch >= 10)) { - current_w = info->current_w; - current_h = info->current_h; - } - if (!strcmp(name, "hw")) return PyLong_FromLong(info->hw_available); else if (!strcmp(name, "wm")) @@ -330,9 +308,9 @@ pg_vidinfo_getattr(PyObject *self, char *name) return Py_BuildValue("(iiii)", info->vfmt->Rloss, info->vfmt->Gloss, info->vfmt->Bloss, info->vfmt->Aloss); else if (!strcmp(name, "current_h")) - return PyLong_FromLong(current_h); + return PyLong_FromLong(info->current_h); else if (!strcmp(name, "current_w")) - return PyLong_FromLong(current_w); + return PyLong_FromLong(info->current_w); else if (!strcmp(name, "pixel_format")) { const char *pixel_format_name = SDL_GetPixelFormatName(info->vfmt->format); @@ -348,23 +326,12 @@ pg_vidinfo_getattr(PyObject *self, char *name) PyObject * pg_vidinfo_str(PyObject *self) { - int current_w = -1; - int current_h = -1; pg_VideoInfo *info = &((pgVidInfoObject *)self)->info; const char *pixel_format_name = SDL_GetPixelFormatName(info->vfmt->format); if (!strncmp(pixel_format_name, "SDL_", 4)) { pixel_format_name += 4; } - SDL_version versioninfo; - SDL_VERSION(&versioninfo); - - if (versioninfo.major > 1 || - (versioninfo.minor >= 2 && versioninfo.patch >= 10)) { - current_w = info->current_w; - current_h = info->current_h; - } - return PyUnicode_FromFormat( "vfmt->Gmask, info->vfmt->Bmask, info->vfmt->Amask, info->vfmt->Rshift, info->vfmt->Gshift, info->vfmt->Bshift, info->vfmt->Ashift, info->vfmt->Rloss, info->vfmt->Gloss, - info->vfmt->Bloss, info->vfmt->Aloss, current_w, current_h, + info->vfmt->Bloss, info->vfmt->Aloss, info->current_w, info->current_h, pixel_format_name); } @@ -860,7 +827,7 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) SDL_Surface *newownedsurf = NULL; int depth = 0; int flags = 0; - int w, h; + int w, h, w_actual, h_actual; PyObject *size = NULL; int vsync = SDL_FALSE; intptr_t hwnd = 0; @@ -907,6 +874,9 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) h = 0; } + h_actual = h; + w_actual = w; + if (!SDL_WasInit(SDL_INIT_VIDEO)) { /* note SDL works special like this too */ if (!pg_display_init(NULL, NULL)) @@ -1129,6 +1099,8 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) } else { win = SDL_CreateWindow(title, x, y, w_1, h_1, sdl_flags); + w_actual = w_1; + h_actual = h_1; } if (!win) return RAISE(pgExc_SDLError, SDL_GetError()); @@ -1238,6 +1210,15 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) SDL_SetHintWithPriority(SDL_HINT_RENDER_SCALE_QUALITY, "nearest", SDL_HINT_DEFAULT); +#if SDL_VERSION_ATLEAST(2, 28, 0) + /* If the window has a surface associated with it already, + * we need to destroy it (if possible) because now we are + * associating a renderer with it. */ + if (SDL_HasWindowSurface(win)) { + SDL_DestroyWindowSurface(win); + } +#endif + if (vsync) { pg_renderer = SDL_CreateRenderer( win, -1, SDL_RENDERER_PRESENTVSYNC); @@ -1247,8 +1228,7 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) } if (pg_renderer == NULL) { - return RAISE(pgExc_SDLError, - "failed to create renderer"); + return RAISE(pgExc_SDLError, SDL_GetError()); } if (flags & PGS_SCALED) { @@ -1341,7 +1321,6 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) /* ensure window is always black after a set_mode call */ SDL_FillRect(surf, NULL, SDL_MapRGB(surf->format, 0, 0, 0)); - pg_flip_internal(state); } /*set the window icon*/ @@ -1365,8 +1344,38 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) } } - /*probably won't do much, but can't hurt, and might help*/ + /* + * Can potentially yield a window resize event that forcibly changes + * the window size. This would invalidate the current surface we store, + * which can cause us to segfault in the event that we reference that + * surface later. So we need to flip, which forces us to update that + * surface as needed. + */ SDL_PumpEvents(); + pg_flip_internal(state); + + /* + * Tell user if their requested screen size is ignored + * OpenGL, SCALED, and FULLSCREEN mess with the screensize in + * such a way that it can, at least our internal stuff, can + * be respected enough that we don't need to issue a warning + */ + if (!state->using_gl && ((flags & (PGS_SCALED | PGS_FULLSCREEN)) == 0) && + !vsync) { + if (((surface->surf->w != w_actual) || + (surface->surf->h != h_actual)) && + ((surface->surf->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0)) { + char buffer[150]; + char *format_string = + "Requested window size was smaller than minimum supported " + "window size on platform. Using (%d, %d) instead."; + snprintf(buffer, sizeof(buffer), format_string, surface->surf->w, + surface->surf->h); + if (PyErr_WarnEx(PyExc_RuntimeWarning, buffer, 1) != 0) { + return NULL; + } + } + } /*return the window's surface (screen)*/ Py_INCREF(surface); @@ -1719,31 +1728,48 @@ pg_update(PyObject *self, PyObject *arg) SDL_UpdateWindowSurfaceRects(win, &sdlr, 1); } else { - PyObject *seq; - PyObject *r; - Py_ssize_t loop, num; + PyObject *iterable, *single_arg, *r; + Py_ssize_t num; int count; SDL_Rect *rects; if (PyTuple_Size(arg) != 1) return RAISE( PyExc_ValueError, - "update requires a rectstyle or sequence of rectstyles"); - seq = PyTuple_GET_ITEM(arg, 0); - if (!seq || !PySequence_Check(seq)) + "update requires a rectstyle or an iterable of rectstyles"); + + single_arg = PyTuple_GET_ITEM(arg, 0); + num = PyObject_Size(single_arg); + if (num == -1) { + /* Either __len__ errored, or object doesn't have __len__. + * In this case we can assume a length arbitrarily, and keep + * scaling it as needed. */ + PyErr_Clear(); + num = 8; + } + iterable = PyObject_GetIter(single_arg); + if (!iterable) return RAISE( PyExc_ValueError, - "update requires a rectstyle or sequence of rectstyles"); + "update requires a rectstyle or an iterable of rectstyles"); - num = PySequence_Length(seq); rects = PyMem_New(SDL_Rect, num); - if (!rects) + if (!rects) { + Py_DECREF(iterable); return NULL; + } count = 0; - for (loop = 0; loop < num; ++loop) { - SDL_Rect *cur_rect = (rects + count); - - /*get rect from the sequence*/ - r = PySequence_GetItem(seq, loop); + while (1) { + r = PyIter_Next(iterable); + if (!r) { + if (PyErr_Occurred()) { + /* forward error */ + Py_DECREF(iterable); + PyMem_Free((void *)rects); + return NULL; + } + /* End of sequence, break loop */ + break; + } if (r == Py_None) { Py_DECREF(r); continue; @@ -1751,7 +1777,8 @@ pg_update(PyObject *self, PyObject *arg) gr = pgRect_FromObject(r, &temp); Py_XDECREF(r); if (!gr) { - PyMem_Free((char *)rects); + Py_DECREF(iterable); + PyMem_Free((void *)rects); return RAISE(PyExc_ValueError, "update_rects requires a single list of rects"); } @@ -1759,8 +1786,20 @@ pg_update(PyObject *self, PyObject *arg) if (gr->w < 1 && gr->h < 1) continue; - /*bail out if rect not onscreen*/ - if (!pg_screencroprect(gr, wide, high, cur_rect)) + if (count >= num) { + /* About to overstep boundary, need reallocing */ + num *= 2; + SDL_Rect *new_rects = PyMem_Resize(rects, SDL_Rect, num); + if (!new_rects) { + Py_DECREF(iterable); + PyMem_Free((void *)rects); + return NULL; + } + rects = new_rects; + } + + /* bail out if rect not onscreen */ + if (!pg_screencroprect(gr, wide, high, &rects[count])) continue; ++count; @@ -1772,7 +1811,8 @@ pg_update(PyObject *self, PyObject *arg) Py_END_ALLOW_THREADS; } - PyMem_Free((char *)rects); + Py_DECREF(iterable); + PyMem_Free((void *)rects); } Py_RETURN_NONE; } @@ -2727,10 +2767,10 @@ pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs) "parent_window", "buttons", "return_button", "escape_button", NULL}; - if (!PyArg_ParseTupleAndKeywords( - arg, kwargs, "s|OsO!OiO", keywords, &title, &message, &msgbox_type, - &pgWindow_Type, &parent_window, &buttons, &return_button_index, - &escape_button_index_obj)) { + if (!PyArg_ParseTupleAndKeywords(arg, kwargs, "s|OsOOiO", keywords, &title, + &message, &msgbox_type, &parent_window, + &buttons, &return_button_index, + &escape_button_index_obj)) { return NULL; } @@ -2767,10 +2807,15 @@ pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs) msgbox_data.flags |= SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT; #endif - if (parent_window == Py_None) + if (parent_window == Py_None) { msgbox_data.window = NULL; - else + } + else { + if (!pgWindow_Check(parent_window)) { + return RAISE(PyExc_TypeError, "'parent_window' must be a Window"); + } msgbox_data.window = ((pgWindowObject *)parent_window)->_win; + } msgbox_data.colorScheme = NULL; // use system color scheme settings diff --git a/src_c/doc/display_doc.h b/src_c/doc/display_doc.h index a0d81d902a..17b881bd49 100644 --- a/src_c/doc/display_doc.h +++ b/src_c/doc/display_doc.h @@ -6,7 +6,7 @@ #define DOC_DISPLAY_SETMODE "set_mode(size=(0, 0), flags=0, depth=0, display=0, vsync=0) -> Surface\nInitialize a window or screen for display" #define DOC_DISPLAY_GETSURFACE "get_surface() -> Surface\nGet a reference to the currently set display surface" #define DOC_DISPLAY_FLIP "flip() -> None\nUpdate the full display Surface to the screen" -#define DOC_DISPLAY_UPDATE "update(rectangle=None, /) -> None\nupdate(rectangle_list, /) -> None\nUpdate all, or a portion, of the display. For non-OpenGL displays." +#define DOC_DISPLAY_UPDATE "update(rectangle=None, /) -> None\nupdate(rectangle_iterable, /) -> None\nUpdate all, or a portion, of the display. For non-OpenGL displays." #define DOC_DISPLAY_GETDRIVER "get_driver() -> name\nGet the name of the pygame display backend" #define DOC_DISPLAY_INFO "Info() -> VideoInfo\nCreate a video display information object" #define DOC_DISPLAY_GETWMINFO "get_wm_info() -> dict\nGet information about the current windowing system" diff --git a/src_c/doc/geometry_doc.h b/src_c/doc/geometry_doc.h index 960714ff73..fbcfefaf9f 100644 --- a/src_c/doc/geometry_doc.h +++ b/src_c/doc/geometry_doc.h @@ -9,16 +9,23 @@ #define DOC_CIRCLE_DIAMETER "diameter -> float\ndiameter of the circle" #define DOC_CIRCLE_AREA "area -> float\narea of the circle" #define DOC_CIRCLE_CIRCUMFERENCE "circumference -> float\ncircumference of the circle" -#define DOC_CIRCLE_COLLIDEPOINT "collidepoint((x, y), /) -> bool\ncollidepoint(x, y, /) -> bool\ncollidepoint(vector2, /) -> bool\ntest if a point is inside the circle" -#define DOC_CIRCLE_COLLIDECIRCLE "collidecircle(circle, /) -> bool\ncollidecircle(x, y, radius, /) -> bool\ncollidecircle((x, y), radius, /) -> bool\ntest if two circles collide" +#define DOC_CIRCLE_TOP "top -> (float, float)\ntop coordinate of the circle" +#define DOC_CIRCLE_BOTTOM "bottom -> (float, float)\nbottom coordinate of the circle" +#define DOC_CIRCLE_LEFT "left -> (float, float)\nleft coordinate of the circle" +#define DOC_CIRCLE_RIGHT "right -> (float, float)\nright coordinate of the circle" +#define DOC_CIRCLE_COLLIDEPOINT "collidepoint((x, y), /) -> bool\ncollidepoint(x, y, /) -> bool\ncollidepoint(vector2, /) -> bool\ntests if a point is inside the circle" +#define DOC_CIRCLE_COLLIDECIRCLE "collidecircle(circle, /) -> bool\ncollidecircle(x, y, radius, /) -> bool\ncollidecircle((x, y), radius, /) -> bool\ncollidecircle(vector2, radius, /) -> bool\ntests if a circle collides with this circle" +#define DOC_CIRCLE_COLLIDERECT "colliderect(rect, /) -> bool\ncolliderect((x, y, width, height), /) -> bool\ncolliderect(x, y, width, height, /) -> bool\ncolliderect((x, y), (width, height), /) -> bool\ncolliderect(vector2, (width, height), /) -> bool\ntests if a rectangle collides with this circle" +#define DOC_CIRCLE_COLLIDESWITH "collideswith(circle, /) -> bool\ncollideswith(rect, /) -> bool\ncollideswith((x, y), /) -> bool\ncollideswith(vector2, /) -> bool\ntests if a shape or point collides with this circle" +#define DOC_CIRCLE_COLLIDELIST "collidelist(colliders) -> int\ntest if a list of objects collide with the circle" +#define DOC_CIRCLE_COLLIDELISTALL "collidelistall(colliders) -> list\ntest if all objects in a list collide with the circle" +#define DOC_CIRCLE_CONTAINS "contains(circle, /) -> bool\ncontains(rect, /) -> bool\ncontains((x, y), /) -> bool\ncontains(vector2, /) -> bool\ntests if a shape or point is inside the circle" #define DOC_CIRCLE_MOVE "move((x, y), /) -> Circle\nmove(x, y, /) -> Circle\nmove(vector2, /) -> Circle\nmoves the circle by a given amount" #define DOC_CIRCLE_MOVEIP "move_ip((x, y), /) -> None\nmove_ip(x, y, /) -> None\nmove_ip(vector2, /) -> None\nmoves the circle by a given amount, in place" -#define DOC_CIRCLE_COLLIDERECT "colliderect(rect, /) -> bool\ncolliderect((x, y, width, height), /) -> bool\ncolliderect(x, y, width, height, /) -> bool\ncolliderect((x, y), (width, height), /) -> bool\nchecks if a rectangle intersects the circle" -#define DOC_CIRCLE_COLLIDESWITH "collideswith(circle, /) -> bool\ncollideswith(rect, /) -> bool\ncollideswith((x, y), /) -> bool\ncollideswith(vector2, /) -> bool\ncheck if a shape or point collides with the circle" -#define DOC_CIRCLE_CONTAINS "contains(circle, /) -> bool\ncontains(rect, /) -> bool\ncontains((x, y), /) -> bool\ncontains(vector2, /) -> bool\ncheck if a shape or point is inside the circle" -#define DOC_CIRCLE_UPDATE "update((x, y), radius, /) -> None\nupdate(x, y, radius, /) -> None\nupdates the circle position and radius" +#define DOC_CIRCLE_INTERSECT "intersect(circle, /) -> list\nfinds intersections between the circle and a shape" +#define DOC_CIRCLE_UPDATE "update((x, y), radius, /) -> None\nupdate(x, y, radius, /) -> None\nupdate(vector2, radius, /) -> None\nupdates the circle position and radius" #define DOC_CIRCLE_ROTATE "rotate(angle, rotation_point=Circle.center, /) -> Circle\nrotate(angle, /) -> Circle\nrotates the circle" #define DOC_CIRCLE_ROTATEIP "rotate_ip(angle, rotation_point=Circle.center, /) -> None\nrotate_ip(angle, /) -> None\nrotates the circle in place" -#define DOC_CIRCLE_ASRECT "as_rect() -> Rect\nreturns the smallest pygame.Rect object that contains the circle" -#define DOC_CIRCLE_ASFRECT "as_frect() -> FRect\nreturns the smallest pygame.FRect object that contains the circle" -#define DOC_CIRCLE_COPY "copy() -> Circle\nreturns a copy of the circle" +#define DOC_CIRCLE_ASRECT "as_rect() -> Rect\nreturns the smallest Rect containing the circle" +#define DOC_CIRCLE_ASFRECT "as_frect() -> FRect\nreturns the smallest FRect containing the circle" +#define DOC_CIRCLE_COPY "copy() -> Circle\ncopies the circle" diff --git a/src_c/doc/mixer_doc.h b/src_c/doc/mixer_doc.h index 92da671ab3..e3ed8f9144 100644 --- a/src_c/doc/mixer_doc.h +++ b/src_c/doc/mixer_doc.h @@ -26,6 +26,7 @@ #define DOC_MIXER_SOUND_GETNUMCHANNELS "get_num_channels() -> count\ncount how many times this Sound is playing" #define DOC_MIXER_SOUND_GETLENGTH "get_length() -> seconds\nget the length of the Sound" #define DOC_MIXER_SOUND_GETRAW "get_raw() -> bytes\nreturn a bytestring copy of the Sound samples." +#define DOC_MIXER_SOUND_COPY "copy() -> Sound\nreturn a new Sound object that is a deep copy of this one" #define DOC_MIXER_CHANNEL "Channel(id) -> Channel\nCreate a Channel object for controlling playback" #define DOC_MIXER_CHANNEL_ID "id -> int\nget the channel id for the Channel object" #define DOC_MIXER_CHANNEL_PLAY "play(Sound, loops=0, maxtime=0, fade_ms=0) -> None\nplay a Sound on a specific Channel" diff --git a/src_c/doc/mouse_doc.h b/src_c/doc/mouse_doc.h index 77ab749c39..5b64b36a69 100644 --- a/src_c/doc/mouse_doc.h +++ b/src_c/doc/mouse_doc.h @@ -1,9 +1,9 @@ /* Auto generated file: with make_docs.py . Docs go in docs/reST/ref/ . */ #define DOC_MOUSE "pygame module to work with the mouse" -#define DOC_MOUSE_GETPRESSED "get_pressed(num_buttons=3) -> (left_button, middle_button, right_button)\nget_pressed(num_buttons=5) -> (left_button, middle_button, right_button, x1_button, x2_button)\nget the state of the mouse buttons" +#define DOC_MOUSE_GETPRESSED "get_pressed(num_buttons=3, desktop=False) -> (left_button, middle_button, right_button)\nget_pressed(num_buttons=5, desktop=False) -> (left_button, middle_button, right_button, x1_button, x2_button)\nget the state of the mouse buttons" #define DOC_MOUSE_GETJUSTPRESSED "get_just_pressed() -> (left_button, middle_button, right_button, x1_button, x2_button)\nget the most recently pressed buttons" #define DOC_MOUSE_GETJUSTRELEASED "get_just_released() -> (left_button, middle_button, right_button, x1_button, x2_button)\nget the most recently released buttons" -#define DOC_MOUSE_GETPOS "get_pos() -> (x, y)\nget the mouse cursor position" +#define DOC_MOUSE_GETPOS "get_pos(desktop=False) -> (x, y)\nget the mouse cursor position" #define DOC_MOUSE_GETREL "get_rel() -> (x, y)\nget the amount of mouse movement" #define DOC_MOUSE_SETPOS "set_pos([x, y], /) -> None\nset the mouse cursor position" #define DOC_MOUSE_SETVISIBLE "set_visible(bool, /) -> bool\nhide or show the mouse cursor" diff --git a/src_c/doc/sdl2_controller_doc.h b/src_c/doc/sdl2_controller_doc.h index cfd79b37ed..adcbc429c2 100644 --- a/src_c/doc/sdl2_controller_doc.h +++ b/src_c/doc/sdl2_controller_doc.h @@ -9,6 +9,7 @@ #define DOC_SDL2_CONTROLLER_ISCONTROLLER "is_controller(index) -> bool\nCheck if the given joystick is supported by the game controller interface" #define DOC_SDL2_CONTROLLER_NAMEFORINDEX "name_forindex(index) -> name or None\nGet the name of the controller" #define DOC_SDL2_CONTROLLER_CONTROLLER "Controller(index) -> Controller\nCreate a new Controller object." +#define DOC_SDL2_CONTROLLER_CONTROLLER_INIT "init() -> None\nInitialize the Controller" #define DOC_SDL2_CONTROLLER_CONTROLLER_QUIT "quit() -> None\nuninitialize the Controller" #define DOC_SDL2_CONTROLLER_CONTROLLER_GETINIT "get_init() -> bool\ncheck if the Controller is initialized" #define DOC_SDL2_CONTROLLER_CONTROLLER_FROMJOYSTICK "from_joystick(joystick) -> Controller\nCreate a Controller from a pygame.joystick.Joystick object" diff --git a/src_c/doc/surface_doc.h b/src_c/doc/surface_doc.h index cbfacf7ce9..38a6fb7120 100644 --- a/src_c/doc/surface_doc.h +++ b/src_c/doc/surface_doc.h @@ -1,22 +1,22 @@ /* Auto generated file: with make_docs.py . Docs go in docs/reST/ref/ . */ #define DOC_SURFACE "Surface((width, height), flags=0, depth=0, masks=None) -> Surface\nSurface((width, height), flags=0, Surface) -> Surface\npygame object for representing images" -#define DOC_SURFACE_BLIT "blit(source, dest, area=None, special_flags=0) -> Rect\ndraw another surface onto this one" -#define DOC_SURFACE_BLITS "blits(blit_sequence=((source, dest), ...), doreturn=True) -> [Rect, ...] or None\nblits(((source, dest, area), ...)) -> [Rect, ...]\nblits(((source, dest, area, special_flags), ...)) -> [Rect, ...]\ndraw many images onto another" -#define DOC_SURFACE_FBLITS "fblits(blit_sequence=((source, dest), ...), special_flags=0, /) -> None\ndraw many surfaces onto the calling surface at their corresponding location and the same special_flags" -#define DOC_SURFACE_CONVERT "convert(surface, /) -> Surface\nconvert(depth, flags=0, /) -> Surface\nconvert(masks, flags=0, /) -> Surface\nconvert() -> Surface\nchange the pixel format of an image" -#define DOC_SURFACE_CONVERTALPHA "convert_alpha() -> Surface\nchange the pixel format of an image including per pixel alphas" +#define DOC_SURFACE_BLIT "blit(source, dest=(0, 0), area=None, special_flags=0) -> Rect\ndraw another surface onto this one" +#define DOC_SURFACE_BLITS "blits(blit_sequence=((source, dest), ...), doreturn=True) -> [Rect, ...] or None\nblits(((source, dest, area), ...)) -> [Rect, ...]\nblits(((source, dest, area, special_flags), ...)) -> [Rect, ...]\ndraw many surfaces onto this surface at their corresponding location" +#define DOC_SURFACE_FBLITS "fblits(blit_sequence=((source, dest), ...), special_flags=0, /) -> None\ndraw many surfaces onto this surface at their corresponding location and with the same special_flags" +#define DOC_SURFACE_CONVERT "convert(surface, /) -> Surface\nconvert(depth, flags=0, /) -> Surface\nconvert(masks, flags=0, /) -> Surface\nconvert() -> Surface\nchange the pixel format of a surface" +#define DOC_SURFACE_CONVERTALPHA "convert_alpha() -> Surface\nchange the pixel format of a surface including per pixel alphas" #define DOC_SURFACE_COPY "copy() -> Surface\ncreate a new copy of a Surface" #define DOC_SURFACE_FILL "fill(color, rect=None, special_flags=0) -> Rect\nfill Surface with a solid color" -#define DOC_SURFACE_SCROLL "scroll(dx=0, dy=0, /) -> None\nShift the surface image in place" -#define DOC_SURFACE_SETCOLORKEY "set_colorkey(color, flags=0, /) -> None\nset_colorkey(None) -> None\nSet the transparent colorkey" -#define DOC_SURFACE_GETCOLORKEY "get_colorkey() -> RGB or None\nGet the current transparent colorkey" -#define DOC_SURFACE_SETALPHA "set_alpha(value, flags=0, /) -> None\nset_alpha(None) -> None\nset the alpha value for the full Surface image" +#define DOC_SURFACE_SCROLL "scroll(dx=0, dy=0, /) -> None\nshift the surface image in place" +#define DOC_SURFACE_SETCOLORKEY "set_colorkey(color, flags=0, /) -> None\nset_colorkey(None) -> None\nset the transparent colorkey" +#define DOC_SURFACE_GETCOLORKEY "get_colorkey() -> RGB or None\nget the current transparent colorkey" +#define DOC_SURFACE_SETALPHA "set_alpha(value, flags=0, /) -> None\nset_alpha(None) -> None\nset the alpha value for the full Surface" #define DOC_SURFACE_GETALPHA "get_alpha() -> int_value\nget the current Surface transparency value" #define DOC_SURFACE_LOCK "lock() -> None\nlock the Surface memory for pixel access" #define DOC_SURFACE_UNLOCK "unlock() -> None\nunlock the Surface memory from pixel access" #define DOC_SURFACE_MUSTLOCK "mustlock() -> bool\ntest if the Surface requires locking" #define DOC_SURFACE_GETLOCKED "get_locked() -> bool\ntest if the Surface is current locked" -#define DOC_SURFACE_GETLOCKS "get_locks() -> tuple\nGets the locks for the Surface" +#define DOC_SURFACE_GETLOCKS "get_locks() -> tuple\ngets the locks for the Surface" #define DOC_SURFACE_GETAT "get_at((x, y), /) -> Color\nget the color value at a single pixel" #define DOC_SURFACE_SETAT "set_at((x, y), color, /) -> None\nset the color value for a single pixel" #define DOC_SURFACE_GETATMAPPED "get_at_mapped((x, y), /) -> Color\nget the mapped color value at a single pixel" @@ -52,6 +52,7 @@ #define DOC_SURFACE_GETBUFFER "get_buffer() -> BufferProxy\nacquires a buffer object for the pixels of the Surface." #define DOC_SURFACE_PIXELSADDRESS "_pixels_address -> int\npixel buffer address" #define DOC_SURFACE_PREMULALPHA "premul_alpha() -> Surface\nreturns a copy of the surface with the RGB channels pre-multiplied by the alpha channel." +#define DOC_SURFACE_PREMULALPHAIP "premul_alpha_ip() -> Surface\nmultiplies the RGB channels by the surface alpha channel." #define DOC_SURFACE_WIDTH "width -> int\nSurface width in pixels (read-only)" #define DOC_SURFACE_HEIGHT "height -> int\nSurface height in pixels (read-only)" #define DOC_SURFACE_SIZE "height -> tuple[int, int]\nSurface size in pixels (read-only)" diff --git a/src_c/doc/transform_doc.h b/src_c/doc/transform_doc.h index 7707ef7c8d..cf51b9a57b 100644 --- a/src_c/doc/transform_doc.h +++ b/src_c/doc/transform_doc.h @@ -15,7 +15,7 @@ #define DOC_TRANSFORM_BOXBLUR "box_blur(surface, radius, repeat_edge_pixels=True, dest_surface=None) -> Surface\nblur a surface using box blur" #define DOC_TRANSFORM_GAUSSIANBLUR "gaussian_blur(surface, radius, repeat_edge_pixels=True, dest_surface=None) -> Surface\nblur a surface using gaussian blur" #define DOC_TRANSFORM_AVERAGESURFACES "average_surfaces(surfaces, dest_surface=None, palette_colors=1) -> Surface\nfind the average surface from many surfaces." -#define DOC_TRANSFORM_AVERAGECOLOR "average_color(surface, rect=None, consider_alpha=False) -> Color\nfinds the average color of a surface" +#define DOC_TRANSFORM_AVERAGECOLOR "average_color(surface, rect=None, consider_alpha=False) -> tuple\nfinds the average color of a surface" #define DOC_TRANSFORM_INVERT "invert(surface, dest_surface=None) -> Surface\ninverts the RGB elements of a surface" #define DOC_TRANSFORM_GRAYSCALE "grayscale(surface, dest_surface=None) -> Surface\ngrayscale a surface" #define DOC_TRANSFORM_THRESHOLD "threshold(dest_surface, surface, search_color, threshold=(0,0,0,0), set_color=(0,0,0,0), set_behavior=1, search_surf=None, inverse_set=False) -> num_threshold_pixels\nfinds which, and how many pixels in a surface are within a threshold of a 'search_color' or a 'search_surf'." diff --git a/src_c/doc/typing_doc.h b/src_c/doc/typing_doc.h new file mode 100644 index 0000000000..ce82b28d18 --- /dev/null +++ b/src_c/doc/typing_doc.h @@ -0,0 +1,8 @@ +/* Auto generated file: with make_docs.py . Docs go in docs/reST/ref/ . */ +#define DOC_TYPING "pygame module providing common typehints" +#define DOC_TYPING_FILELIKE "" +#define DOC_TYPING_SEQUENCELIKE "" +#define DOC_TYPING_COORDINATE "" +#define DOC_TYPING_INTCOORDINATE "" +#define DOC_TYPING_COLORLIKE "" +#define DOC_TYPING_RECTLIKE "" diff --git a/src_c/doc/window_doc.h b/src_c/doc/window_doc.h index 323a67f6bc..b7fd796810 100644 --- a/src_c/doc/window_doc.h +++ b/src_c/doc/window_doc.h @@ -4,6 +4,7 @@ #define DOC_WINDOW_GRABKEYBOARD "grab_keyboard -> bool\nGet or set the window's keyboard grab mode" #define DOC_WINDOW_MOUSEGRABBED "mouse_grabbed -> bool\nGet if the mouse cursor is confined to the window (**read-only**)" #define DOC_WINDOW_KEYBOARDGRABBED "keyboard_grabbed -> bool\nGet if the keyboard shortcuts are captured by the window (**read-only**)" +#define DOC_WINDOW_FOCUSED "focused -> bool\nGet if the window is focused (**read-only**)" #define DOC_WINDOW_TITLE "title -> str\nGet or set the window title" #define DOC_WINDOW_RESIZABLE "resizable -> bool\nGet or set whether the window is resizable" #define DOC_WINDOW_BORDERLESS "borderless -> bool\nGet or set whether the window is borderless" @@ -30,3 +31,4 @@ #define DOC_WINDOW_MINIMIZE "maximize() -> None\nMinimize the window" #define DOC_WINDOW_SETICON "set_icon(surface, /) -> None\nSet the window icon" #define DOC_WINDOW_SETMODALFOR "set_modal_for(parent, /) -> None\nSet the window as a modal for a parent window" +#define DOC_WINDOW_FLASH "flash(operation, /) -> None\nFlash a window to demand attention from the user" diff --git a/src_c/draw.c b/src_c/draw.c index 1d5cbd2cba..06a8149ef0 100644 --- a/src_c/draw.c +++ b/src_c/draw.c @@ -58,13 +58,13 @@ static void draw_circle_bresenham_thin(SDL_Surface *surf, int x0, int y0, int radius, Uint32 color, int *drawn_area); static void -draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius, - int thickness, Uint32 color, int top_right, int top_left, - int bottom_left, int bottom_right, int *drawn_area); +draw_circle_xiaolinwu(SDL_Surface *surf, int x0, int y0, int radius, + int thickness, Uint32 color, int top_right, int top_left, + int bottom_left, int bottom_right, int *drawn_area); static void -draw_circle_xaolinwu_thin(SDL_Surface *surf, int x0, int y0, int radius, - Uint32 color, int top_right, int top_left, - int bottom_left, int bottom_right, int *drawn_area); +draw_circle_xiaolinwu_thin(SDL_Surface *surf, int x0, int y0, int radius, + Uint32 color, int top_right, int top_left, + int bottom_left, int bottom_right, int *drawn_area); static void draw_circle_filled(SDL_Surface *surf, int x0, int y0, int radius, Uint32 color, int *drawn_area); @@ -786,10 +786,10 @@ aacircle(PyObject *self, PyObject *args, PyObject *kwargs) surf = pgSurface_AsSurface(surfobj); SURF_INIT_CHECK(surf) - if (surf->format->BytesPerPixel <= 0 || surf->format->BytesPerPixel > 4) { + if (PG_SURF_BytesPerPixel(surf) <= 0 || PG_SURF_BytesPerPixel(surf) > 4) { return PyErr_Format(PyExc_ValueError, "unsupported surface bit depth (%d) for drawing", - surf->format->BytesPerPixel); + PG_SURF_BytesPerPixel(surf)); } CHECK_LOAD_COLOR(colorobj) @@ -820,33 +820,33 @@ aacircle(PyObject *self, PyObject *args, PyObject *kwargs) if (!width || width == radius) { draw_circle_filled(surf, posx, posy, radius - 1, color, drawn_area); - draw_circle_xaolinwu(surf, posx, posy, radius, 2, color, 1, 1, 1, - 1, drawn_area); + draw_circle_xiaolinwu(surf, posx, posy, radius, 2, color, 1, 1, 1, + 1, drawn_area); } else if (width == 1) { - draw_circle_xaolinwu_thin(surf, posx, posy, radius, color, 1, 1, 1, - 1, drawn_area); + draw_circle_xiaolinwu_thin(surf, posx, posy, radius, color, 1, 1, + 1, 1, drawn_area); } else { - draw_circle_xaolinwu(surf, posx, posy, radius, width, color, 1, 1, - 1, 1, drawn_area); + draw_circle_xiaolinwu(surf, posx, posy, radius, width, color, 1, 1, + 1, 1, drawn_area); } } else { if (!width || width == radius) { - draw_circle_xaolinwu(surf, posx, posy, radius, radius, color, - top_right, top_left, bottom_left, - bottom_right, drawn_area); + draw_circle_xiaolinwu(surf, posx, posy, radius, radius, color, + top_right, top_left, bottom_left, + bottom_right, drawn_area); } else if (width == 1) { - draw_circle_xaolinwu_thin(surf, posx, posy, radius, color, - top_right, top_left, bottom_left, - bottom_right, drawn_area); + draw_circle_xiaolinwu_thin(surf, posx, posy, radius, color, + top_right, top_left, bottom_left, + bottom_right, drawn_area); } else { - draw_circle_xaolinwu(surf, posx, posy, radius, width, color, - top_right, top_left, bottom_left, - bottom_right, drawn_area); + draw_circle_xiaolinwu(surf, posx, posy, radius, width, color, + top_right, top_left, bottom_left, + bottom_right, drawn_area); } } @@ -1126,15 +1126,42 @@ get_antialiased_color(SDL_Surface *surf, int x, int y, Uint32 original_color, float brightness) { Uint8 color_part[4], background_color[4]; - Uint32 *pixels = (Uint32 *)surf->pixels; SDL_GetRGBA(original_color, surf->format, &color_part[0], &color_part[1], &color_part[2], &color_part[3]); if (x < surf->clip_rect.x || x >= surf->clip_rect.x + surf->clip_rect.w || y < surf->clip_rect.y || y >= surf->clip_rect.y + surf->clip_rect.h) return original_color; - SDL_GetRGBA(pixels[(y * surf->w) + x], surf->format, &background_color[0], + + Uint32 pixel = 0; + int bpp = PG_SURF_BytesPerPixel(surf); + Uint8 *pixels = (Uint8 *)surf->pixels + y * surf->pitch + x * bpp; + + switch (bpp) { + case 1: + pixel = *pixels; + break; + + case 2: + pixel = *((Uint16 *)pixels); + break; + + case 3: +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + pixel = (pixels[0]) + (pixels[1] << 8) + (pixels[2] << 16); +#else /* SDL_BIG_ENDIAN */ + pixel = (pixels[2]) + (pixels[1] << 8) + (pixels[0] << 16); +#endif /* SDL_BIG_ENDIAN */ + break; + + default: /* case 4: */ + pixel = *((Uint32 *)pixels); + break; + } + + SDL_GetRGBA(pixel, surf->format, &background_color[0], &background_color[1], &background_color[2], &background_color[3]); + color_part[0] = (Uint8)(brightness * color_part[0] + (1 - brightness) * background_color[0]); color_part[1] = (Uint8)(brightness * color_part[1] + @@ -1213,7 +1240,6 @@ set_at(SDL_Surface *surf, int x, int y, Uint32 color) { SDL_PixelFormat *format = surf->format; Uint8 *pixels = (Uint8 *)surf->pixels; - Uint8 *byte_buf, rgb[4]; if (x < surf->clip_rect.x || x >= surf->clip_rect.x + surf->clip_rect.w || y < surf->clip_rect.y || y >= surf->clip_rect.y + surf->clip_rect.h) @@ -1230,17 +1256,11 @@ set_at(SDL_Surface *surf, int x, int y, Uint32 color) *((Uint32 *)(pixels + y * surf->pitch) + x) = color; break; default: /*case 3:*/ - SDL_GetRGB(color, format, rgb, rgb + 1, rgb + 2); - byte_buf = (Uint8 *)(pixels + y * surf->pitch) + x * 3; -#if (SDL_BYTEORDER == SDL_LIL_ENDIAN) - *(byte_buf + (format->Rshift >> 3)) = rgb[0]; - *(byte_buf + (format->Gshift >> 3)) = rgb[1]; - *(byte_buf + (format->Bshift >> 3)) = rgb[2]; -#else - *(byte_buf + 2 - (format->Rshift >> 3)) = rgb[0]; - *(byte_buf + 2 - (format->Gshift >> 3)) = rgb[1]; - *(byte_buf + 2 - (format->Bshift >> 3)) = rgb[2]; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + color <<= 8; #endif + memcpy((pixels + y * surf->pitch) + x * 3, &color, + 3 * sizeof(Uint8)); break; } return 1; @@ -1805,7 +1825,6 @@ unsafe_set_at(SDL_Surface *surf, int x, int y, Uint32 color) { SDL_PixelFormat *format = surf->format; Uint8 *pixels = (Uint8 *)surf->pixels; - Uint8 *byte_buf, rgb[4]; switch (PG_FORMAT_BytesPerPixel(format)) { case 1: @@ -1818,17 +1837,11 @@ unsafe_set_at(SDL_Surface *surf, int x, int y, Uint32 color) *((Uint32 *)(pixels + y * surf->pitch) + x) = color; break; default: /*case 3:*/ - SDL_GetRGB(color, format, rgb, rgb + 1, rgb + 2); - byte_buf = (Uint8 *)(pixels + y * surf->pitch) + x * 3; -#if (SDL_BYTEORDER == SDL_LIL_ENDIAN) - *(byte_buf + (format->Rshift >> 3)) = rgb[0]; - *(byte_buf + (format->Gshift >> 3)) = rgb[1]; - *(byte_buf + (format->Bshift >> 3)) = rgb[2]; -#else - *(byte_buf + 2 - (format->Rshift >> 3)) = rgb[0]; - *(byte_buf + 2 - (format->Gshift >> 3)) = rgb[1]; - *(byte_buf + 2 - (format->Bshift >> 3)) = rgb[2]; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + color <<= 8; #endif + memcpy((pixels + y * surf->pitch) + x * 3, &color, + 3 * sizeof(Uint8)); break; } } @@ -2514,23 +2527,24 @@ draw_eight_symetric_pixels(SDL_Surface *surf, int x0, int y0, Uint32 color, } } -/* Xaolin Wu Circle Algorithm +/* Xiaolin Wu Circle Algorithm * adapted from: https://cgg.mff.cuni.cz/~pepca/ref/WU.pdf * with additional line width parameter and quadrants option */ static void -draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius, - int thickness, Uint32 color, int top_right, int top_left, - int bottom_left, int bottom_right, int *drawn_area) +draw_circle_xiaolinwu(SDL_Surface *surf, int x0, int y0, int radius, + int thickness, Uint32 color, int top_right, int top_left, + int bottom_left, int bottom_right, int *drawn_area) { for (int layer_radius = radius - thickness; layer_radius <= radius; layer_radius++) { int x = 0; int y = layer_radius; + double pow_layer_r = pow(layer_radius, 2); double prev_opacity = 0.0; if (layer_radius == radius - thickness) { while (x < y) { - double height = sqrt(pow(layer_radius, 2) - pow(x, 2)); + double height = sqrt(pow_layer_r - pow(x, 2)); double opacity = 255.0 * (ceil(height) - height); if (opacity < prev_opacity) { --y; @@ -2547,7 +2561,7 @@ draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius, } else if (layer_radius == radius) { while (x < y) { - double height = sqrt(pow(layer_radius, 2) - pow(x, 2)); + double height = sqrt(pow_layer_r - pow(x, 2)); double opacity = 255.0 * (ceil(height) - height); if (opacity < prev_opacity) { --y; @@ -2565,7 +2579,7 @@ draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius, } else { while (x < y) { - double height = sqrt(pow(layer_radius, 2) - pow(x, 2)); + double height = sqrt(pow_layer_r - pow(x, 2)); double opacity = 255.0 * (ceil(height) - height); if (opacity < prev_opacity) { --y; @@ -2584,15 +2598,16 @@ draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius, } static void -draw_circle_xaolinwu_thin(SDL_Surface *surf, int x0, int y0, int radius, - Uint32 color, int top_right, int top_left, - int bottom_left, int bottom_right, int *drawn_area) +draw_circle_xiaolinwu_thin(SDL_Surface *surf, int x0, int y0, int radius, + Uint32 color, int top_right, int top_left, + int bottom_left, int bottom_right, int *drawn_area) { int x = 0; int y = radius; + double pow_r = pow(radius, 2); double prev_opacity = 0.0; while (x < y) { - double height = sqrt(pow(radius, 2) - pow(x, 2)); + double height = sqrt(pow_r - pow(x, 2)); double opacity = 255.0 * (ceil(height) - height); if (opacity < prev_opacity) { --y; diff --git a/src_c/event.c b/src_c/event.c index aa0c95b39e..59132e3330 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -426,7 +426,7 @@ _pg_translate_windowevent(void *_, SDL_Event *event) { if (event->type == SDL_WINDOWEVENT) { event->type = PGE_WINDOWSHOWN + event->window.event - 1; - return SDL_EventState(_pg_pgevent_proxify(event->type), SDL_QUERY); + return PG_EventEnabled(_pg_pgevent_proxify(event->type)); } return 1; } @@ -599,7 +599,7 @@ pg_event_filter(void *_, SDL_Event *event) return RAISE(pgExc_SDLError, SDL_GetError()), 0; */ } - return SDL_EventState(_pg_pgevent_proxify(event->type), SDL_QUERY); + return PG_EventEnabled(_pg_pgevent_proxify(event->type)); } /* The two keyrepeat functions below modify state accessed by the event filter, @@ -1040,8 +1040,9 @@ dict_from_event(SDL_Event *event) _pg_insobj(dict, "instance_id", PyLong_FromLong(event->jaxis.which)); _pg_insobj(dict, "axis", PyLong_FromLong(event->jaxis.axis)); + // sdl report axis values as values between -32768 and 32767 _pg_insobj(dict, "value", - PyFloat_FromDouble(event->jaxis.value / 32767.0)); + PyFloat_FromDouble(event->jaxis.value / 32768.0)); break; case SDL_JOYBALLMOTION: _pg_insobj(dict, "joy", get_joy_device_index(event->jaxis.which)); @@ -2142,7 +2143,7 @@ pg_event_set_allowed(PyObject *self, PyObject *obj) if (obj == Py_None) { int i; for (i = SDL_FIRSTEVENT; i < SDL_LASTEVENT; i++) { - SDL_EventState(i, SDL_ENABLE); + PG_SetEventEnabled(i, SDL_TRUE); } } else { @@ -2156,7 +2157,7 @@ pg_event_set_allowed(PyObject *self, PyObject *obj) Py_DECREF(seq); return NULL; } - SDL_EventState(_pg_pgevent_proxify(type), SDL_ENABLE); + PG_SetEventEnabled(_pg_pgevent_proxify(type), SDL_TRUE); } Py_DECREF(seq); } @@ -2175,7 +2176,7 @@ pg_event_set_blocked(PyObject *self, PyObject *obj) int i; /* Start at PGPOST_EVENTBEGIN */ for (i = PGPOST_EVENTBEGIN; i < SDL_LASTEVENT; i++) { - SDL_EventState(i, SDL_IGNORE); + PG_SetEventEnabled(i, SDL_FALSE); } } else { @@ -2189,14 +2190,14 @@ pg_event_set_blocked(PyObject *self, PyObject *obj) Py_DECREF(seq); return NULL; } - SDL_EventState(_pg_pgevent_proxify(type), SDL_IGNORE); + PG_SetEventEnabled(_pg_pgevent_proxify(type), SDL_FALSE); } Py_DECREF(seq); } /* Never block SDL_WINDOWEVENT, we need them for translation */ - SDL_EventState(SDL_WINDOWEVENT, SDL_ENABLE); + PG_SetEventEnabled(SDL_WINDOWEVENT, SDL_TRUE); /* Never block PGE_KEYREPEAT too, its needed for pygame internal use */ - SDL_EventState(PGE_KEYREPEAT, SDL_ENABLE); + PG_SetEventEnabled(PGE_KEYREPEAT, SDL_TRUE); Py_RETURN_NONE; } @@ -2219,8 +2220,7 @@ pg_event_get_blocked(PyObject *self, PyObject *obj) Py_DECREF(seq); return NULL; } - if (SDL_EventState(_pg_pgevent_proxify(type), SDL_QUERY) == - SDL_IGNORE) { + if (PG_EventEnabled(_pg_pgevent_proxify(type)) == SDL_FALSE) { isblocked = 1; break; } diff --git a/src_c/font.c b/src_c/font.c index 4be95950ae..5817e896f6 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -84,7 +84,6 @@ static PyObject * font_resource(const char *filename) { PyObject *pkgdatamodule = NULL; - PyObject *resourcefunc = NULL; PyObject *result = NULL; PyObject *tmp; @@ -93,14 +92,9 @@ font_resource(const char *filename) return NULL; } - resourcefunc = PyObject_GetAttrString(pkgdatamodule, resourcefunc_name); + result = + PyObject_CallMethod(pkgdatamodule, resourcefunc_name, "s", filename); Py_DECREF(pkgdatamodule); - if (resourcefunc == NULL) { - return NULL; - } - - result = PyObject_CallFunction(resourcefunc, "s", filename); - Py_DECREF(resourcefunc); if (result == NULL) { return NULL; } @@ -925,10 +919,7 @@ font_set_script(PyObject *self, PyObject *arg) return RAISE_FONT_QUIT_ERROR(); } -/*Sadly, SDL_TTF_VERSION_ATLEAST is new in SDL_ttf 2.0.15, still too - * new to use */ -#if SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, \ - SDL_TTF_PATCHLEVEL) >= SDL_VERSIONNUM(2, 20, 0) +#if SDL_TTF_VERSION_ATLEAST(2, 20, 0) TTF_Font *font = PyFont_AsFont(self); Py_ssize_t size; const char *script_code; @@ -962,10 +953,7 @@ font_set_direction(PyObject *self, PyObject *arg, PyObject *kwarg) return RAISE_FONT_QUIT_ERROR(); } -/* Can't use SDL_TTF_VERSION_ATLEAST until SDL_ttf 2.0.15 is minimum supported - */ -#if SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, \ - SDL_TTF_PATCHLEVEL) >= SDL_VERSIONNUM(2, 20, 0) +#if SDL_TTF_VERSION_ATLEAST(2, 20, 0) TTF_Font *font = PyFont_AsFont(self); int direction; char *kwds[] = {"direction", NULL}; @@ -995,8 +983,7 @@ font_set_direction(PyObject *self, PyObject *arg, PyObject *kwarg) writing this) This bug flips the top-to-bottom and bottom-to-top rendering. So, this is a compat patch for that behavior */ -#if SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, \ - SDL_TTF_PATCHLEVEL) < SDL_VERSIONNUM(2, 22, 0) +#if !SDL_TTF_VERSION_ATLEAST(2, 22, 0) case 2: { dir = TTF_DIRECTION_BTT; break; @@ -1233,6 +1220,12 @@ static PyObject * get_ttf_version(PyObject *self, PyObject *args, PyObject *kwargs) { int linked = 1; /* Default is linked version. */ +#if SDL_VERSION_ATLEAST(3, 0, 0) + int version = SDL_TTF_VERSION; +#else + SDL_version version; + TTF_VERSION(&version); +#endif static char *keywords[] = {"linked", NULL}; @@ -1241,15 +1234,16 @@ get_ttf_version(PyObject *self, PyObject *args, PyObject *kwargs) } if (linked) { - const SDL_version *v = TTF_Linked_Version(); - return Py_BuildValue("iii", v->major, v->minor, v->patch); - } - else { - /* compiled version */ - SDL_version v; - TTF_VERSION(&v); - return Py_BuildValue("iii", v.major, v.minor, v.patch); +#if SDL_VERSION_ATLEAST(3, 0, 0) + version = TTF_Version(); +#else + version = *TTF_Linked_Version(); +#endif } + + return Py_BuildValue("iii", PG_FIND_VNUM_MAJOR(version), + PG_FIND_VNUM_MINOR(version), + PG_FIND_VNUM_MICRO(version)); } static PyMethodDef _font_methods[] = { diff --git a/src_c/geometry_common.c b/src_c/geometry_common.c index 5116cc6b0d..07d84f0843 100644 --- a/src_c/geometry_common.c +++ b/src_c/geometry_common.c @@ -4,7 +4,7 @@ int _pg_circle_set_radius(PyObject *value, pgCircleBase *circle) { double radius = 0.0; - if (!pg_DoubleFromObj(value, &radius) || radius <= 0.0) { + if (!pg_DoubleFromObj(value, &radius) || radius < 0.0) { return 0; } circle->r = radius; @@ -108,7 +108,7 @@ pgCircle_FromObject(PyObject *obj, pgCircleBase *out) if (PyCallable_Check(circleattr)) /*call if it's a method*/ { - PyObject *circleresult = PyObject_CallObject(circleattr, NULL); + PyObject *circleresult = PyObject_CallNoArgs(circleattr); Py_DECREF(circleattr); if (!circleresult) { PyErr_Clear(); @@ -147,38 +147,10 @@ pgCircle_FromObjectFastcall(PyObject *const *args, Py_ssize_t nargs, } } -/* === Collision Functions === */ - -inline int -pgCollision_CirclePoint(pgCircleBase *circle, double Cx, double Cy) -{ - double dx = circle->x - Cx; - double dy = circle->y - Cy; - return dx * dx + dy * dy <= circle->r * circle->r; -} - -inline int -pgCollision_CircleCircle(pgCircleBase *A, pgCircleBase *B) +static inline int +double_compare(double a, double b) { - double dx, dy; - double sum_radii; - - dx = A->x - B->x; - dy = A->y - B->y; - sum_radii = A->r + B->r; - - return dx * dx + dy * dy <= sum_radii * sum_radii; -} - -inline int -pgCollision_RectCircle(double rx, double ry, double rw, double rh, - pgCircleBase *circle) -{ - const double cx = circle->x, cy = circle->y; - const double r_bottom = ry + rh, r_right = rx + rw; - - const double test_x = (cx < rx) ? rx : ((cx > r_right) ? r_right : cx); - const double test_y = (cy < ry) ? ry : ((cy > r_bottom) ? r_bottom : cy); - - return pgCollision_CirclePoint(circle, test_x, test_y); + /* Uses both a fixed epsilon and an adaptive epsilon */ + const double e = 1e-6; + return fabs(a - b) < e || fabs(a - b) <= e * MAX(fabs(a), fabs(b)); } diff --git a/src_c/geometry_common.h b/src_c/geometry_common.h index 124abca450..115f248768 100644 --- a/src_c/geometry_common.h +++ b/src_c/geometry_common.h @@ -13,16 +13,88 @@ int pgCircle_FromObjectFastcall(PyObject *const *args, Py_ssize_t nargs, pgCircleBase *out); +static inline int +double_compare(double a, double b); + /* === Collision Functions === */ -inline int -pgCollision_CirclePoint(pgCircleBase *circle, double Cx, double Cy); +static inline int +pgCollision_CirclePoint(pgCircleBase *circle, double Cx, double Cy) +{ + double dx = circle->x - Cx; + double dy = circle->y - Cy; + return dx * dx + dy * dy <= circle->r * circle->r; +} + +static inline int +pgCollision_CircleCircle(pgCircleBase *A, pgCircleBase *B) +{ + double dx, dy; + double sum_radii; -inline int -pgCollision_CircleCircle(pgCircleBase *A, pgCircleBase *B); + dx = A->x - B->x; + dy = A->y - B->y; + sum_radii = A->r + B->r; -inline int + return dx * dx + dy * dy <= sum_radii * sum_radii; +} + +static inline int pgCollision_RectCircle(double rx, double ry, double rw, double rh, - pgCircleBase *circle); + pgCircleBase *circle) +{ + const double cx = circle->x, cy = circle->y; + const double r_bottom = ry + rh, r_right = rx + rw; + + const double test_x = (cx < rx) ? rx : ((cx > r_right) ? r_right : cx); + const double test_y = (cy < ry) ? ry : ((cy > r_bottom) ? r_bottom : cy); + + return pgCollision_CirclePoint(circle, test_x, test_y); +} + +static inline int +pgIntersection_CircleCircle(pgCircleBase *A, pgCircleBase *B, + double *intersections) +{ + double dx = B->x - A->x; + double dy = B->y - A->y; + double d2 = dx * dx + dy * dy; + double r_sum = A->r + B->r; + double r_diff = A->r - B->r; + double r_sum2 = r_sum * r_sum; + double r_diff2 = r_diff * r_diff; + + if (d2 > r_sum2 || d2 < r_diff2) { + return 0; + } + + if (double_compare(d2, 0) && double_compare(A->r, B->r)) { + return 0; + } + + double d = sqrt(d2); + double a = (d2 + A->r * A->r - B->r * B->r) / (2 * d); + double h = sqrt(A->r * A->r - a * a); + + double xm = A->x + a * (dx / d); + double ym = A->y + a * (dy / d); + + double xs1 = xm + h * (dy / d); + double ys1 = ym - h * (dx / d); + double xs2 = xm - h * (dy / d); + double ys2 = ym + h * (dx / d); + + if (double_compare(d2, r_sum2) || double_compare(d2, r_diff2)) { + intersections[0] = xs1; + intersections[1] = ys1; + return 1; + } + + intersections[0] = xs1; + intersections[1] = ys1; + intersections[2] = xs2; + intersections[3] = ys2; + return 2; +} #endif // PYGAME_CE_GEOMETRY_COMMON_H diff --git a/src_c/image.c b/src_c/image.c index 481b6a99fa..90f52f0988 100644 --- a/src_c/image.c +++ b/src_c/image.c @@ -495,6 +495,8 @@ tobytes_surf_32bpp(SDL_Surface *surf, int flipped, int hascolorkey, } } +#define PREMUL_PIXEL_ALPHA(pixel, alpha) (char)((((pixel) + 1) * (alpha)) >> 8) + PyObject * image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) { @@ -554,7 +556,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) byte_width = surf->w * 4; } else if (!strcmp(format, "RGBX") || !strcmp(format, "ARGB") || - !strcmp(format, "BGRA")) { + !strcmp(format, "BGRA") || !strcmp(format, "ABGR")) { byte_width = surf->w * 4; } else if (!strcmp(format, "RGBA_PREMULT") || @@ -878,6 +880,83 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) } pgSurface_Unlock(surfobj); } + else if (!strcmp(format, "ABGR")) { + pgSurface_Lock(surfobj); + switch (PG_SURF_BytesPerPixel(surf)) { + case 1: + for (h = 0; h < surf->h; ++h) { + Uint8 *ptr = (Uint8 *)DATAROW(surf->pixels, h, surf->pitch, + surf->h, flipped); + for (w = 0; w < surf->w; ++w) { + color = *ptr++; + data[3] = (char)surf->format->palette->colors[color].r; + data[2] = (char)surf->format->palette->colors[color].g; + data[1] = (char)surf->format->palette->colors[color].b; + data[0] = (char)255; + data += 4; + } + pad(&data, padding); + } + break; + case 2: + for (h = 0; h < surf->h; ++h) { + Uint16 *ptr = (Uint16 *)DATAROW( + surf->pixels, h, surf->pitch, surf->h, flipped); + for (w = 0; w < surf->w; ++w) { + color = *ptr++; + data[3] = (char)(((color & Rmask) >> Rshift) << Rloss); + data[2] = (char)(((color & Gmask) >> Gshift) << Gloss); + data[1] = (char)(((color & Bmask) >> Bshift) << Bloss); + data[0] = (char)(Amask ? (((color & Amask) >> Ashift) + << Aloss) + : 255); + data += 4; + } + pad(&data, padding); + } + break; + case 3: + for (h = 0; h < surf->h; ++h) { + Uint8 *ptr = (Uint8 *)DATAROW(surf->pixels, h, surf->pitch, + surf->h, flipped); + for (w = 0; w < surf->w; ++w) { +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + color = ptr[0] + (ptr[1] << 8) + (ptr[2] << 16); +#else + color = ptr[2] + (ptr[1] << 8) + (ptr[0] << 16); +#endif + ptr += 3; + data[3] = (char)(((color & Rmask) >> Rshift) << Rloss); + data[2] = (char)(((color & Gmask) >> Gshift) << Gloss); + data[1] = (char)(((color & Bmask) >> Bshift) << Bloss); + data[0] = (char)(Amask ? (((color & Amask) >> Ashift) + << Aloss) + : 255); + data += 4; + } + pad(&data, padding); + } + break; + case 4: + for (h = 0; h < surf->h; ++h) { + Uint32 *ptr = (Uint32 *)DATAROW( + surf->pixels, h, surf->pitch, surf->h, flipped); + for (w = 0; w < surf->w; ++w) { + color = *ptr++; + data[3] = (char)(((color & Rmask) >> Rshift) << Rloss); + data[2] = (char)(((color & Gmask) >> Gshift) << Gloss); + data[1] = (char)(((color & Bmask) >> Bshift) << Bloss); + data[0] = (char)(Amask ? (((color & Amask) >> Ashift) + << Aloss) + : 255); + data += 4; + } + pad(&data, padding); + } + break; + } + pgSurface_Unlock(surfobj); + } else if (!strcmp(format, "RGBA_PREMULT")) { pgSurface_Lock(surfobj); switch (PG_SURF_BytesPerPixel(surf)) { @@ -888,15 +967,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) for (w = 0; w < surf->w; ++w) { color = *ptr++; alpha = ((color & Amask) >> Ashift) << Aloss; - data[0] = - (char)((((color & Rmask) >> Rshift) << Rloss) * - alpha / 255); - data[1] = - (char)((((color & Gmask) >> Gshift) << Gloss) * - alpha / 255); - data[2] = - (char)((((color & Bmask) >> Bshift) << Bloss) * - alpha / 255); + data[0] = PREMUL_PIXEL_ALPHA( + ((color & Rmask) >> Rshift) << Rloss, alpha); + data[1] = PREMUL_PIXEL_ALPHA( + ((color & Gmask) >> Gshift) << Gloss, alpha); + data[2] = PREMUL_PIXEL_ALPHA( + ((color & Bmask) >> Bshift) << Bloss, alpha); data[3] = (char)alpha; data += 4; } @@ -915,15 +991,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) #endif ptr += 3; alpha = ((color & Amask) >> Ashift) << Aloss; - data[0] = - (char)((((color & Rmask) >> Rshift) << Rloss) * - alpha / 255); - data[1] = - (char)((((color & Gmask) >> Gshift) << Gloss) * - alpha / 255); - data[2] = - (char)((((color & Bmask) >> Bshift) << Bloss) * - alpha / 255); + data[0] = PREMUL_PIXEL_ALPHA( + ((color & Rmask) >> Rshift) << Rloss, alpha); + data[1] = PREMUL_PIXEL_ALPHA( + ((color & Gmask) >> Gshift) << Gloss, alpha); + data[2] = PREMUL_PIXEL_ALPHA( + ((color & Bmask) >> Bshift) << Bloss, alpha); data[3] = (char)alpha; data += 4; } @@ -941,15 +1014,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) data[0] = data[1] = data[2] = 0; } else { - data[0] = - (char)((((color & Rmask) >> Rshift) << Rloss) * - alpha / 255); - data[1] = - (char)((((color & Gmask) >> Gshift) << Gloss) * - alpha / 255); - data[2] = - (char)((((color & Bmask) >> Bshift) << Bloss) * - alpha / 255); + data[0] = PREMUL_PIXEL_ALPHA( + ((color & Rmask) >> Rshift) << Rloss, alpha); + data[1] = PREMUL_PIXEL_ALPHA( + ((color & Gmask) >> Gshift) << Gloss, alpha); + data[2] = PREMUL_PIXEL_ALPHA( + ((color & Bmask) >> Bshift) << Bloss, alpha); } data[3] = (char)alpha; data += 4; @@ -970,15 +1040,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) for (w = 0; w < surf->w; ++w) { color = *ptr++; alpha = ((color & Amask) >> Ashift) << Aloss; - data[1] = - (char)((((color & Rmask) >> Rshift) << Rloss) * - alpha / 255); - data[2] = - (char)((((color & Gmask) >> Gshift) << Gloss) * - alpha / 255); - data[3] = - (char)((((color & Bmask) >> Bshift) << Bloss) * - alpha / 255); + data[1] = PREMUL_PIXEL_ALPHA( + ((color & Rmask) >> Rshift) << Rloss, alpha); + data[2] = PREMUL_PIXEL_ALPHA( + ((color & Gmask) >> Gshift) << Gloss, alpha); + data[3] = PREMUL_PIXEL_ALPHA( + ((color & Bmask) >> Bshift) << Bloss, alpha); data[0] = (char)alpha; data += 4; } @@ -997,15 +1064,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) #endif ptr += 3; alpha = ((color & Amask) >> Ashift) << Aloss; - data[1] = - (char)((((color & Rmask) >> Rshift) << Rloss) * - alpha / 255); - data[2] = - (char)((((color & Gmask) >> Gshift) << Gloss) * - alpha / 255); - data[3] = - (char)((((color & Bmask) >> Bshift) << Bloss) * - alpha / 255); + data[1] = PREMUL_PIXEL_ALPHA( + ((color & Rmask) >> Rshift) << Rloss, alpha); + data[2] = PREMUL_PIXEL_ALPHA( + ((color & Gmask) >> Gshift) << Gloss, alpha); + data[3] = PREMUL_PIXEL_ALPHA( + ((color & Bmask) >> Bshift) << Bloss, alpha); data[0] = (char)alpha; data += 4; } @@ -1023,15 +1087,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) data[1] = data[2] = data[3] = 0; } else { - data[1] = - (char)((((color & Rmask) >> Rshift) << Rloss) * - alpha / 255); - data[2] = - (char)((((color & Gmask) >> Gshift) << Gloss) * - alpha / 255); - data[3] = - (char)((((color & Bmask) >> Bshift) << Bloss) * - alpha / 255); + data[1] = PREMUL_PIXEL_ALPHA( + ((color & Rmask) >> Rshift) << Rloss, alpha); + data[2] = PREMUL_PIXEL_ALPHA( + ((color & Gmask) >> Gshift) << Gloss, alpha); + data[3] = PREMUL_PIXEL_ALPHA( + ((color & Bmask) >> Bshift) << Bloss, alpha); } data[0] = (char)alpha; data += 4; @@ -1224,6 +1285,32 @@ image_frombytes(PyObject *self, PyObject *arg, PyObject *kwds) } SDL_UnlockSurface(surf); } + else if (!strcmp(format, "ABGR")) { + if (pitch == -1) { + pitch = w * 4; + } + else if (pitch < w * 4) { + return RAISE(PyExc_ValueError, + "Pitch must be greater than or equal to the width * " + "4 as per the format"); + } + + if (len != (Py_ssize_t)pitch * h) + return RAISE( + PyExc_ValueError, + "Bytes length does not equal format and resolution size"); + surf = PG_CreateSurface(w, h, SDL_PIXELFORMAT_ABGR32); + if (!surf) + return RAISE(pgExc_SDLError, SDL_GetError()); + SDL_LockSurface(surf); + for (looph = 0; looph < h; ++looph) { + Uint32 *pix = (Uint32 *)DATAROW(surf->pixels, looph, surf->pitch, + h, flipped); + memcpy(pix, data, w * sizeof(Uint32)); + data += pitch; + } + SDL_UnlockSurface(surf); + } else return RAISE(PyExc_ValueError, "Unrecognized type of format"); diff --git a/src_c/imageext.c b/src_c/imageext.c index 376d647701..0d68d59070 100644 --- a/src_c/imageext.c +++ b/src_c/imageext.c @@ -272,6 +272,12 @@ imageext_get_sdl_image_version(PyObject *self, PyObject *args, PyObject *kwargs) { int linked = 1; +#if SDL_VERSION_ATLEAST(3, 0, 0) + int version = SDL_IMAGE_VERSION; +#else + SDL_version version; + SDL_IMAGE_VERSION(&version); +#endif static char *keywords[] = {"linked", NULL}; @@ -280,14 +286,16 @@ imageext_get_sdl_image_version(PyObject *self, PyObject *args, } if (linked) { - const SDL_version *v = IMG_Linked_Version(); - return Py_BuildValue("iii", v->major, v->minor, v->patch); - } - else { - SDL_version v; - SDL_IMAGE_VERSION(&v); - return Py_BuildValue("iii", v.major, v.minor, v.patch); +#if SDL_VERSION_ATLEAST(3, 0, 0) + version = IMG_Version(); +#else + version = *IMG_Linked_Version(); +#endif } + + return Py_BuildValue("iii", PG_FIND_VNUM_MAJOR(version), + PG_FIND_VNUM_MINOR(version), + PG_FIND_VNUM_MICRO(version)); } /* diff --git a/src_c/include/_pygame.h b/src_c/include/_pygame.h index 1477fe24cc..5ff4882dfb 100644 --- a/src_c/include/_pygame.h +++ b/src_c/include/_pygame.h @@ -354,32 +354,25 @@ typedef struct { * auto imported/initialized by surface */ #ifndef PYGAMEAPI_SURFLOCK_INTERNAL -#define pgLifetimeLock_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(surflock, 0)) - -#define pgLifetimeLock_Check(x) ((x)->ob_type == &pgLifetimeLock_Type) - #define pgSurface_Prep(x) \ if ((x)->subsurface) \ - (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 1)))(x) + (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 0)))(x) #define pgSurface_Unprep(x) \ if ((x)->subsurface) \ - (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 2)))(x) + (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 1)))(x) #define pgSurface_Lock \ - (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 3)) + (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 2)) #define pgSurface_Unlock \ - (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 4)) + (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 3)) #define pgSurface_LockBy \ - (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 5)) + (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 4)) #define pgSurface_UnlockBy \ - (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 6)) - -#define pgSurface_LockLifetime \ - (*(PyObject * (*)(PyObject *, PyObject *)) PYGAMEAPI_GET_SLOT(surflock, 7)) + (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 5)) #endif /* @@ -666,3 +659,32 @@ pg_tuple_couple_from_values_double(double val1, double val2) return tuple; } + +static PG_INLINE PyObject * +pg_PointList_FromArrayDouble(double const *array, int arr_length) +{ + if (arr_length % 2) { + return RAISE(PyExc_ValueError, "array length must be even"); + } + + int num_points = arr_length / 2; + PyObject *sequence = PyList_New(num_points); + if (!sequence) { + return NULL; + } + + int i; + PyObject *point = NULL; + for (i = 0; i < num_points; i++) { + point = + pg_tuple_couple_from_values_double(array[i * 2], array[i * 2 + 1]); + if (!point) { + Py_DECREF(sequence); + return NULL; + } + PyList_SET_ITEM(sequence, i, point); + point = NULL; + } + + return sequence; +} diff --git a/src_c/include/pythoncapi_compat.h b/src_c/include/pythoncapi_compat.h new file mode 100644 index 0000000000..51e8c0de75 --- /dev/null +++ b/src_c/include/pythoncapi_compat.h @@ -0,0 +1,1360 @@ +// Header file providing new C API functions to old Python versions. +// +// File distributed under the Zero Clause BSD (0BSD) license. +// Copyright Contributors to the pythoncapi_compat project. +// +// Homepage: +// https://github.com/python/pythoncapi_compat +// +// Latest version: +// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h +// +// SPDX-License-Identifier: 0BSD + +#ifndef PYTHONCAPI_COMPAT +#define PYTHONCAPI_COMPAT + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// Python 3.11.0b4 added PyFrame_Back() to Python.h +#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) +# include "frameobject.h" // PyFrameObject, PyFrame_GetBack() +#endif + + +#ifndef _Py_CAST +# define _Py_CAST(type, expr) ((type)(expr)) +#endif + +// Static inline functions should use _Py_NULL rather than using directly NULL +// to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, +// _Py_NULL is defined as nullptr. +#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ + || (defined(__cplusplus) && __cplusplus >= 201103) +# define _Py_NULL nullptr +#else +# define _Py_NULL NULL +#endif + +// Cast argument to PyObject* type. +#ifndef _PyObject_CAST +# define _PyObject_CAST(op) _Py_CAST(PyObject*, op) +#endif + + +// bpo-42262 added Py_NewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) +static inline PyObject* _Py_NewRef(PyObject *obj) +{ + Py_INCREF(obj); + return obj; +} +#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-42262 added Py_XNewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_XNewRef) +static inline PyObject* _Py_XNewRef(PyObject *obj) +{ + Py_XINCREF(obj); + return obj; +} +#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT) +static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) +{ + ob->ob_refcnt = refcnt; +} +#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt) +#endif + + +// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2. +// It is excluded from the limited C API. +#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API) +#define Py_SETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_DECREF(_tmp_dst); \ + } while (0) + +#define Py_XSETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_XDECREF(_tmp_dst); \ + } while (0) +#endif + + +// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse() +// to Python 3.10.0b1. +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is) +# define Py_Is(x, y) ((x) == (y)) +#endif +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone) +# define Py_IsNone(x) Py_Is(x, Py_None) +#endif +#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsTrue) +# define Py_IsTrue(x) Py_Is(x, Py_True) +#endif +#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsFalse) +# define Py_IsFalse(x) Py_Is(x, Py_False) +#endif + + +// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) +static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ + ob->ob_type = type; +} +#define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type) +#endif + + +// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) +static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) +{ + ob->ob_size = size; +} +#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size) +#endif + + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION) +static inline PyCodeObject* PyFrame_GetCode(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + assert(frame->f_code != _Py_NULL); + return _Py_CAST(PyCodeObject*, Py_NewRef(frame->f_code)); +} +#endif + +static inline PyCodeObject* _PyFrame_GetCodeBorrow(PyFrameObject *frame) +{ + PyCodeObject *code = PyFrame_GetCode(frame); + Py_DECREF(code); + return code; +} + + +// bpo-40421 added PyFrame_GetBack() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject* PyFrame_GetBack(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + return _Py_CAST(PyFrameObject*, Py_XNewRef(frame->f_back)); +} +#endif + +#if !defined(PYPY_VERSION) +static inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame) +{ + PyFrameObject *back = PyFrame_GetBack(frame); + Py_XDECREF(back); + return back; +} +#endif + + +// bpo-40421 added PyFrame_GetLocals() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetLocals(PyFrameObject *frame) +{ +#if PY_VERSION_HEX >= 0x030400B1 + if (PyFrame_FastToLocalsWithError(frame) < 0) { + return NULL; + } +#else + PyFrame_FastToLocals(frame); +#endif + return Py_NewRef(frame->f_locals); +} +#endif + + +// bpo-40421 added PyFrame_GetGlobals() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetGlobals(PyFrameObject *frame) +{ + return Py_NewRef(frame->f_globals); +} +#endif + + +// bpo-40421 added PyFrame_GetBuiltins() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetBuiltins(PyFrameObject *frame) +{ + return Py_NewRef(frame->f_builtins); +} +#endif + + +// bpo-40421 added PyFrame_GetLasti() to Python 3.11.0b1 +#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION) +static inline int PyFrame_GetLasti(PyFrameObject *frame) +{ +#if PY_VERSION_HEX >= 0x030A00A7 + // bpo-27129: Since Python 3.10.0a7, f_lasti is an instruction offset, + // not a bytes offset anymore. Python uses 16-bit "wordcode" (2 bytes) + // instructions. + if (frame->f_lasti < 0) { + return -1; + } + return frame->f_lasti * 2; +#else + return frame->f_lasti; +#endif +} +#endif + + +// gh-91248 added PyFrame_GetVar() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name) +{ + PyObject *locals, *value; + + locals = PyFrame_GetLocals(frame); + if (locals == NULL) { + return NULL; + } +#if PY_VERSION_HEX >= 0x03000000 + value = PyDict_GetItemWithError(locals, name); +#else + value = _PyDict_GetItemWithError(locals, name); +#endif + Py_DECREF(locals); + + if (value == NULL) { + if (PyErr_Occurred()) { + return NULL; + } +#if PY_VERSION_HEX >= 0x03000000 + PyErr_Format(PyExc_NameError, "variable %R does not exist", name); +#else + PyErr_SetString(PyExc_NameError, "variable does not exist"); +#endif + return NULL; + } + return Py_NewRef(value); +} +#endif + + +// gh-91248 added PyFrame_GetVarString() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +static inline PyObject* +PyFrame_GetVarString(PyFrameObject *frame, const char *name) +{ + PyObject *name_obj, *value; +#if PY_VERSION_HEX >= 0x03000000 + name_obj = PyUnicode_FromString(name); +#else + name_obj = PyString_FromString(name); +#endif + if (name_obj == NULL) { + return NULL; + } + value = PyFrame_GetVar(frame, name_obj); + Py_DECREF(name_obj); + return value; +} +#endif + + +// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +static inline PyInterpreterState * +PyThreadState_GetInterpreter(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->interp; +} +#endif + + +// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return _Py_CAST(PyFrameObject *, Py_XNewRef(tstate->frame)); +} +#endif + +#if !defined(PYPY_VERSION) +static inline PyFrameObject* +_PyThreadState_GetFrameBorrow(PyThreadState *tstate) +{ + PyFrameObject *frame = PyThreadState_GetFrame(tstate); + Py_XDECREF(frame); + return frame; +} +#endif + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +static inline PyInterpreterState* PyInterpreterState_Get(void) +{ + PyThreadState *tstate; + PyInterpreterState *interp; + + tstate = PyThreadState_GET(); + if (tstate == _Py_NULL) { + Py_FatalError("GIL released (tstate is NULL)"); + } + interp = tstate->interp; + if (interp == _Py_NULL) { + Py_FatalError("no current interpreter"); + } + return interp; +} +#endif + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6 +#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline uint64_t PyThreadState_GetID(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->id; +} +#endif + +// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_EnterTracing(PyThreadState *tstate) +{ + tstate->tracing++; +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = 0; +#else + tstate->use_tracing = 0; +#endif +} +#endif + +// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) +{ + int use_tracing = (tstate->c_tracefunc != _Py_NULL + || tstate->c_profilefunc != _Py_NULL); + tstate->tracing--; +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = use_tracing; +#else + tstate->use_tracing = use_tracing; +#endif +} +#endif + + +// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1 +// PyObject_CallNoArgs() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallNoArgs) && PY_VERSION_HEX < 0x030900A1 +static inline PyObject* PyObject_CallNoArgs(PyObject *func) +{ + return PyObject_CallFunctionObjArgs(func, NULL); +} +#endif + + +// bpo-39245 made PyObject_CallOneArg() public (previously called +// _PyObject_CallOneArg) in Python 3.9.0a4 +// PyObject_CallOneArg() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallOneArg) && PY_VERSION_HEX < 0x030900A4 +static inline PyObject* PyObject_CallOneArg(PyObject *func, PyObject *arg) +{ + return PyObject_CallFunctionObjArgs(func, arg, NULL); +} +#endif + + +// bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 +static inline int +PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value) +{ + int res; + + if (!value && !PyErr_Occurred()) { + // PyModule_AddObject() raises TypeError in this case + PyErr_SetString(PyExc_SystemError, + "PyModule_AddObjectRef() must be called " + "with an exception raised if value is NULL"); + return -1; + } + + Py_XINCREF(value); + res = PyModule_AddObject(module, name, value); + if (res < 0) { + Py_XDECREF(value); + } + return res; +} +#endif + + +// bpo-40024 added PyModule_AddType() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline int PyModule_AddType(PyObject *module, PyTypeObject *type) +{ + const char *name, *dot; + + if (PyType_Ready(type) < 0) { + return -1; + } + + // inline _PyType_Name() + name = type->tp_name; + assert(name != _Py_NULL); + dot = strrchr(name, '.'); + if (dot != _Py_NULL) { + name = dot + 1; + } + + return PyModule_AddObjectRef(module, name, _PyObject_CAST(type)); +} +#endif + + +// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6. +// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2. +#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsTracked(PyObject* obj) +{ + return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)); +} +#endif + +// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6. +// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final. +#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsFinalized(PyObject *obj) +{ + PyGC_Head *gc = _Py_CAST(PyGC_Head*, obj) - 1; + return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc)); +} +#endif + + +// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) +static inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { + return Py_TYPE(ob) == type; +} +#define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST(ob), type) +#endif + + +// bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7. +// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. +// Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal +// C API: Python 3.11a2-3.11a6 versions are not supported. +#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +static inline int PyFloat_Pack2(double x, char *p, int le) +{ return _PyFloat_Pack2(x, (unsigned char*)p, le); } + +static inline double PyFloat_Unpack2(const char *p, int le) +{ return _PyFloat_Unpack2((const unsigned char *)p, le); } +#endif + + +// bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and +// PyFloat_Unpack8() to Python 3.11a7. +// Python 3.11a2 moved _PyFloat_Pack4(), _PyFloat_Pack8(), _PyFloat_Unpack4() +// and _PyFloat_Unpack8() to the internal C API: Python 3.11a2-3.11a6 versions +// are not supported. +#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +static inline int PyFloat_Pack4(double x, char *p, int le) +{ return _PyFloat_Pack4(x, (unsigned char*)p, le); } + +static inline int PyFloat_Pack8(double x, char *p, int le) +{ return _PyFloat_Pack8(x, (unsigned char*)p, le); } + +static inline double PyFloat_Unpack4(const char *p, int le) +{ return _PyFloat_Unpack4((const unsigned char *)p, le); } + +static inline double PyFloat_Unpack8(const char *p, int le) +{ return _PyFloat_Unpack8((const unsigned char *)p, le); } +#endif + + +// gh-92154 added PyCode_GetCode() to Python 3.11.0b1 +#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetCode(PyCodeObject *code) +{ + return Py_NewRef(code->co_code); +} +#endif + + +// gh-95008 added PyCode_GetVarnames() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetVarnames(PyCodeObject *code) +{ + return Py_NewRef(code->co_varnames); +} +#endif + +// gh-95008 added PyCode_GetFreevars() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetFreevars(PyCodeObject *code) +{ + return Py_NewRef(code->co_freevars); +} +#endif + +// gh-95008 added PyCode_GetCellvars() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetCellvars(PyCodeObject *code) +{ + return Py_NewRef(code->co_cellvars); +} +#endif + + +// Py_UNUSED() was added to Python 3.4.0b2. +#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED) +# if defined(__GNUC__) || defined(__clang__) +# define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) +# else +# define Py_UNUSED(name) _unused_ ## name +# endif +#endif + + +// gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A0 +static inline PyObject* PyImport_AddModuleRef(const char *name) +{ + return Py_XNewRef(PyImport_AddModule(name)); +} +#endif + + +// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D0000 +static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) +{ + PyObject *obj; + if (ref != NULL && !PyWeakref_Check(ref)) { + *pobj = NULL; + PyErr_SetString(PyExc_TypeError, "expected a weakref"); + return -1; + } + obj = PyWeakref_GetObject(ref); + if (obj == NULL) { + // SystemError if ref is NULL + *pobj = NULL; + return -1; + } + if (obj == Py_None) { + *pobj = NULL; + return 0; + } + *pobj = Py_NewRef(obj); + return (*pobj != NULL); +} +#endif + + +// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1 +#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET +# define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1)) +#endif + +// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1 +#if PY_VERSION_HEX < 0x030800B1 +static inline Py_ssize_t PyVectorcall_NARGS(size_t n) +{ + return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET; +} +#endif + + +// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 +static inline PyObject* +PyObject_Vectorcall(PyObject *callable, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ +#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION) + // bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1 + return _PyObject_Vectorcall(callable, args, nargsf, kwnames); +#else + PyObject *posargs = NULL, *kwargs = NULL; + PyObject *res; + Py_ssize_t nposargs, nkwargs, i; + + if (nargsf != 0 && args == NULL) { + PyErr_BadInternalCall(); + goto error; + } + if (kwnames != NULL && !PyTuple_Check(kwnames)) { + PyErr_BadInternalCall(); + goto error; + } + + nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf); + if (kwnames) { + nkwargs = PyTuple_GET_SIZE(kwnames); + } + else { + nkwargs = 0; + } + + posargs = PyTuple_New(nposargs); + if (posargs == NULL) { + goto error; + } + if (nposargs) { + for (i=0; i < nposargs; i++) { + PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args)); + args++; + } + } + + if (nkwargs) { + kwargs = PyDict_New(); + if (kwargs == NULL) { + goto error; + } + + for (i = 0; i < nkwargs; i++) { + PyObject *key = PyTuple_GET_ITEM(kwnames, i); + PyObject *value = *args; + args++; + if (PyDict_SetItem(kwargs, key, value) < 0) { + goto error; + } + } + } + else { + kwargs = NULL; + } + + res = PyObject_Call(callable, posargs, kwargs); + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return res; + +error: + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return NULL; +#endif +} +#endif + + +// gh-106521 added PyObject_GetOptionalAttr() and +// PyObject_GetOptionalAttrString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result) +{ + // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1 +#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION) + return _PyObject_LookupAttr(obj, attr_name, result); +#else + *result = PyObject_GetAttr(obj, attr_name); + if (*result != NULL) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + return 0; + } + return -1; +#endif +} + +static inline int +PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result) +{ + PyObject *name_obj; + int rc; +#if PY_VERSION_HEX >= 0x03000000 + name_obj = PyUnicode_FromString(attr_name); +#else + name_obj = PyString_FromString(attr_name); +#endif + if (name_obj == NULL) { + *result = NULL; + return -1; + } + rc = PyObject_GetOptionalAttr(obj, name_obj, result); + Py_DECREF(name_obj); + return rc; +} +#endif + + +// gh-106307 added PyObject_GetOptionalAttr() and +// PyMapping_GetOptionalItemString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result) +{ + *result = PyObject_GetItem(obj, key); + if (*result) { + return 1; + } + if (!PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; +} + +static inline int +PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result) +{ + PyObject *key_obj; + int rc; +#if PY_VERSION_HEX >= 0x03000000 + key_obj = PyUnicode_FromString(key); +#else + key_obj = PyString_FromString(key); +#endif + if (key_obj == NULL) { + *result = NULL; + return -1; + } + rc = PyMapping_GetOptionalItem(obj, key_obj, result); + Py_DECREF(key_obj); + return rc; +} +#endif + +// gh-108511 added PyMapping_HasKeyWithError() and +// PyMapping_HasKeyStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyMapping_HasKeyWithError(PyObject *obj, PyObject *key) +{ + PyObject *res; + int rc = PyMapping_GetOptionalItem(obj, key, &res); + Py_XDECREF(res); + return rc; +} + +static inline int +PyMapping_HasKeyStringWithError(PyObject *obj, const char *key) +{ + PyObject *res; + int rc = PyMapping_GetOptionalItemString(obj, key, &res); + Py_XDECREF(res); + return rc; +} +#endif + + +// gh-108511 added PyObject_HasAttrWithError() and +// PyObject_HasAttrStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_HasAttrWithError(PyObject *obj, PyObject *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttr(obj, attr, &res); + Py_XDECREF(res); + return rc; +} + +static inline int +PyObject_HasAttrStringWithError(PyObject *obj, const char *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttrString(obj, attr, &res); + Py_XDECREF(res); + return rc; +} +#endif + + +// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result) +{ +#if PY_VERSION_HEX >= 0x03000000 + PyObject *item = PyDict_GetItemWithError(mp, key); +#else + PyObject *item = _PyDict_GetItemWithError(mp, key); +#endif + if (item != NULL) { + *result = Py_NewRef(item); + return 1; // found + } + if (!PyErr_Occurred()) { + *result = NULL; + return 0; // not found + } + *result = NULL; + return -1; +} + +static inline int +PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result) +{ + int res; +#if PY_VERSION_HEX >= 0x03000000 + PyObject *key_obj = PyUnicode_FromString(key); +#else + PyObject *key_obj = PyString_FromString(key); +#endif + if (key_obj == NULL) { + *result = NULL; + return -1; + } + res = PyDict_GetItemRef(mp, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + + +// gh-106307 added PyModule_Add() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyModule_Add(PyObject *mod, const char *name, PyObject *value) +{ + int res = PyModule_AddObjectRef(mod, name, value); + Py_XDECREF(value); + return res; +} +#endif + + +// gh-108014 added Py_IsFinalizing() to Python 3.13.0a1 +// bpo-1856 added _Py_Finalizing to Python 3.2.1b1. +// _Py_IsFinalizing() was added to PyPy 7.3.0. +#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \ + && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000) +static inline int Py_IsFinalizing(void) +{ +#if PY_VERSION_HEX >= 0x030700A1 + // _Py_IsFinalizing() was added to Python 3.7.0a1. + return _Py_IsFinalizing(); +#else + return (_Py_Finalizing != NULL); +#endif +} +#endif + + +// gh-108323 added PyDict_ContainsString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyDict_ContainsString(PyObject *op, const char *key) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + return -1; + } + int res = PyDict_Contains(op, key_obj); + Py_DECREF(key_obj); + return res; +} +#endif + + +// gh-108445 added PyLong_AsInt() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyLong_AsInt(PyObject *obj) +{ +#ifdef PYPY_VERSION + long value = PyLong_AsLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + if (value < (long)INT_MIN || (long)INT_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C int"); + return -1; + } + return (int)value; +#else + return _PyLong_AsInt(obj); +#endif +} +#endif + + +// gh-107073 added PyObject_VisitManagedDict() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) +{ + PyObject **dict = _PyObject_GetDictPtr(obj); + if (*dict == NULL) { + return -1; + } + Py_VISIT(*dict); + return 0; +} + +static inline void +PyObject_ClearManagedDict(PyObject *obj) +{ + PyObject **dict = _PyObject_GetDictPtr(obj); + if (*dict == NULL) { + return; + } + Py_CLEAR(*dict); +} +#endif + +// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1 +// Python 3.5.2 added _PyThreadState_UncheckedGet(). +#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1 +static inline PyThreadState* +PyThreadState_GetUnchecked(void) +{ + return _PyThreadState_UncheckedGet(); +} +#endif + +// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_len) +{ + Py_ssize_t len; + const void *utf8; + PyObject *exc_type, *exc_value, *exc_tb; + int res; + + // API cannot report errors so save/restore the exception + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + + // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize() +#if PY_VERSION_HEX >= 0x030300A1 + if (PyUnicode_IS_ASCII(unicode)) { + utf8 = PyUnicode_DATA(unicode); + len = PyUnicode_GET_LENGTH(unicode); + } + else { + utf8 = PyUnicode_AsUTF8AndSize(unicode, &len); + if (utf8 == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + } + + if (len != str_len) { + res = 0; + goto done; + } + res = (memcmp(utf8, str, (size_t)len) == 0); +#else + PyObject *bytes = PyUnicode_AsUTF8String(unicode); + if (bytes == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + +#if PY_VERSION_HEX >= 0x03000000 + len = PyBytes_GET_SIZE(bytes); + utf8 = PyBytes_AS_STRING(bytes); +#else + len = PyString_GET_SIZE(bytes); + utf8 = PyString_AS_STRING(bytes); +#endif + if (len != str_len) { + Py_DECREF(bytes); + res = 0; + goto done; + } + + res = (memcmp(utf8, str, (size_t)len) == 0); + Py_DECREF(bytes); +#endif + +done: + PyErr_Restore(exc_type, exc_value, exc_tb); + return res; +} + +static inline int +PyUnicode_EqualToUTF8(PyObject *unicode, const char *str) +{ + return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str)); +} +#endif + + +// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int +PyList_Extend(PyObject *list, PyObject *iterable) +{ + return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable); +} + +static inline int +PyList_Clear(PyObject *list) +{ + return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL); +} +#endif + +// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int +PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result) +{ + PyObject *value; + + if (!PyDict_Check(dict)) { + PyErr_BadInternalCall(); + if (result) { + *result = NULL; + } + return -1; + } + + // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2. + // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*. + // Python 3.13.0a1 removed _PyDict_Pop(). +#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000 + value = PyObject_CallMethod(dict, "pop", "O", key); +#elif PY_VERSION_HEX < 0x030600b3 + value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL); +#else + value = _PyDict_Pop(dict, key, NULL); +#endif + if (value == NULL) { + if (result) { + *result = NULL; + } + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; + } + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; +} + +static inline int +PyDict_PopString(PyObject *dict, const char *key, PyObject **result) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + if (result != NULL) { + *result = NULL; + } + return -1; + } + + int res = PyDict_Pop(dict, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + + +#if PY_VERSION_HEX < 0x030200A4 +// Python 3.2.0a4 added Py_hash_t type +typedef Py_ssize_t Py_hash_t; +#endif + + +// gh-111545 added Py_HashPointer() to Python 3.13.0a3 +#if PY_VERSION_HEX < 0x030D00A3 +static inline Py_hash_t Py_HashPointer(const void *ptr) +{ +#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION) + return _Py_HashPointer(ptr); +#else + return _Py_HashPointer(_Py_CAST(void*, ptr)); +#endif +} +#endif + + +// Python 3.13a4 added a PyTime API. +// Use the private API added to Python 3.5. +#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000 +typedef _PyTime_t PyTime_t; +#define PyTime_MIN _PyTime_MIN +#define PyTime_MAX _PyTime_MAX + +static inline double PyTime_AsSecondsDouble(PyTime_t t) +{ return _PyTime_AsSecondsDouble(t); } + +static inline int PyTime_Monotonic(PyTime_t *result) +{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); } + +static inline int PyTime_Time(PyTime_t *result) +{ return _PyTime_GetSystemClockWithInfo(result, NULL); } + +static inline int PyTime_PerfCounter(PyTime_t *result) +{ +#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION) + return _PyTime_GetPerfCounterWithInfo(result, NULL); +#elif PY_VERSION_HEX >= 0x03070000 + // Call time.perf_counter_ns() and convert Python int object to PyTime_t. + // Cache time.perf_counter_ns() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter_ns"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + long long value = PyLong_AsLongLong(res); + Py_DECREF(res); + + if (value == -1 && PyErr_Occurred()) { + return -1; + } + + Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t)); + *result = (PyTime_t)value; + return 0; +#else + // Call time.perf_counter() and convert C double to PyTime_t. + // Cache time.perf_counter() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + double d = PyFloat_AsDouble(res); + Py_DECREF(res); + + if (d == -1.0 && PyErr_Occurred()) { + return -1; + } + + // Avoid floor() to avoid having to link to libm + *result = (PyTime_t)(d * 1e9); + return 0; +#endif +} + +#endif + +// gh-111389 added hash constants to Python 3.13.0a5. These constants were +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. +#if (!defined(PyHASH_BITS) \ + && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ + || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ + && PYPY_VERSION_NUM >= 0x07090000))) +# define PyHASH_BITS _PyHASH_BITS +# define PyHASH_MODULUS _PyHASH_MODULUS +# define PyHASH_INF _PyHASH_INF +# define PyHASH_IMAG _PyHASH_IMAG +#endif + + +// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed() +// to Python 3.13.0a6 +#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE) + +#define Py_CONSTANT_NONE 0 +#define Py_CONSTANT_FALSE 1 +#define Py_CONSTANT_TRUE 2 +#define Py_CONSTANT_ELLIPSIS 3 +#define Py_CONSTANT_NOT_IMPLEMENTED 4 +#define Py_CONSTANT_ZERO 5 +#define Py_CONSTANT_ONE 6 +#define Py_CONSTANT_EMPTY_STR 7 +#define Py_CONSTANT_EMPTY_BYTES 8 +#define Py_CONSTANT_EMPTY_TUPLE 9 + +static inline PyObject* Py_GetConstant(unsigned int constant_id) +{ + static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL}; + + if (constants[Py_CONSTANT_NONE] == NULL) { + constants[Py_CONSTANT_NONE] = Py_None; + constants[Py_CONSTANT_FALSE] = Py_False; + constants[Py_CONSTANT_TRUE] = Py_True; + constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis; + constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented; + + constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0); + if (constants[Py_CONSTANT_ZERO] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_ONE] = PyLong_FromLong(1); + if (constants[Py_CONSTANT_ONE] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_STR] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0); + if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) { + goto fatal_error; + } + // goto dance to avoid compiler warnings about Py_FatalError() + goto init_done; + +fatal_error: + // This case should never happen + Py_FatalError("Py_GetConstant() failed to get constants"); + } + +init_done: + if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) { + return Py_NewRef(constants[constant_id]); + } + else { + PyErr_BadInternalCall(); + return NULL; + } +} + +static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id) +{ + PyObject *obj = Py_GetConstant(constant_id); + Py_XDECREF(obj); + return obj; +} +#endif + + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline PyObject * +PyList_GetItemRef(PyObject *op, Py_ssize_t index) +{ + PyObject *item = PyList_GetItem(op, index); + Py_XINCREF(item); + return item; +} +#endif + + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline int +PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result) +{ + PyObject *value; + if (PyDict_GetItemRef(d, key, &value) < 0) { + // get error + if (result) { + *result = NULL; + } + return -1; + } + if (value != NULL) { + // present + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; + } + + // missing: set the item + if (PyDict_SetItem(d, key, default_value) < 0) { + // set error + if (result) { + *result = NULL; + } + return -1; + } + if (result) { + *result = Py_NewRef(default_value); + } + return 0; +} +#endif + + +// gh-116560 added PyLong_GetSign() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyLong_GetSign(PyObject *obj, int *sign) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expect int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + + *sign = _PyLong_Sign(obj); + return 0; +} +#endif + + +#ifdef __cplusplus +} +#endif +#endif // PYTHONCAPI_COMPAT diff --git a/src_c/joystick.c b/src_c/joystick.c index 047e8b7eff..806dfd0b77 100644 --- a/src_c/joystick.c +++ b/src_c/joystick.c @@ -41,7 +41,7 @@ init(PyObject *self, PyObject *_null) if (!SDL_WasInit(SDL_INIT_JOYSTICK)) { if (SDL_InitSubSystem(SDL_INIT_JOYSTICK)) return RAISE(pgExc_SDLError, SDL_GetError()); - SDL_JoystickEventState(SDL_ENABLE); + PG_SetJoystickEventsEnabled(SDL_TRUE); } Py_RETURN_NONE; } @@ -60,7 +60,7 @@ quit(PyObject *self, PyObject *_null) } if (SDL_WasInit(SDL_INIT_JOYSTICK)) { - SDL_JoystickEventState(SDL_ENABLE); + PG_SetJoystickEventsEnabled(SDL_TRUE); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } Py_RETURN_NONE; diff --git a/src_c/key.c b/src_c/key.c index 3a2435d22e..44ea2457c2 100644 --- a/src_c/key.c +++ b/src_c/key.c @@ -188,8 +188,8 @@ key_get_pressed(PyObject *self, PyObject *_null) PyTuple_SET_ITEM(key_tuple, i, key_elem); } - ret_obj = PyObject_CallFunctionObjArgs((PyObject *)&pgScancodeWrapper_Type, - key_tuple, NULL); + ret_obj = + PyObject_CallOneArg((PyObject *)&pgScancodeWrapper_Type, key_tuple); Py_DECREF(key_tuple); return ret_obj; } @@ -216,8 +216,8 @@ get_just_pressed(PyObject *self, PyObject *_null) } PyTuple_SET_ITEM(key_tuple, i, key_elem); } - ret_obj = PyObject_CallFunctionObjArgs((PyObject *)&pgScancodeWrapper_Type, - key_tuple, NULL); + ret_obj = + PyObject_CallOneArg((PyObject *)&pgScancodeWrapper_Type, key_tuple); Py_DECREF(key_tuple); return ret_obj; } @@ -244,8 +244,8 @@ get_just_released(PyObject *self, PyObject *_null) } PyTuple_SET_ITEM(key_tuple, i, key_elem); } - ret_obj = PyObject_CallFunctionObjArgs((PyObject *)&pgScancodeWrapper_Type, - key_tuple, NULL); + ret_obj = + PyObject_CallOneArg((PyObject *)&pgScancodeWrapper_Type, key_tuple); Py_DECREF(key_tuple); return ret_obj; } diff --git a/src_c/mask.c b/src_c/mask.c index 052322a9e1..020291edc2 100644 --- a/src_c/mask.c +++ b/src_c/mask.c @@ -43,6 +43,24 @@ #define M_PI 3.14159265358979323846 #endif +/* Pretty good idea from Tom Duff :-). */ +#ifndef LOOP_UNROLLED4 +#define LOOP_UNROLLED4(code, n, width) \ + n = (width + 3) / 4; \ + switch (width & 3) { \ + case 0: \ + do { \ + code; \ + case 3: \ + code; \ + case 2: \ + code; \ + case 1: \ + code; \ + } while (--n > 0); \ + } +#endif + /* Macro to create mask objects. This will call the type's tp_new and tp_init. * Params: * w: width of mask @@ -770,22 +788,61 @@ set_pixel_color(Uint8 *pixel, Uint8 bpp, Uint32 color) static void set_from_threshold(SDL_Surface *surf, bitmask_t *bitmask, int threshold) { - SDL_PixelFormat *format = surf->format; - Uint8 bpp = PG_FORMAT_BytesPerPixel(format); - Uint8 *pixel = NULL; - Uint8 rgba[4]; - int x, y; - - for (y = 0; y < surf->h; ++y) { - pixel = (Uint8 *)surf->pixels + y * surf->pitch; + /* This function expects surf to be non-zero sized. */ + SDL_PixelFormat *fmt = surf->format; + const Uint8 bpp = PG_FORMAT_BytesPerPixel(fmt); + int x, y, n; + Uint8 *srcp; + const int src_skip = surf->pitch - surf->w * bpp; + + if (threshold >= 255) { + return; + } - for (x = 0; x < surf->w; ++x, pixel += bpp) { - SDL_GetRGBA(get_pixel_color(pixel, bpp), format, rgba, rgba + 1, - rgba + 2, rgba + 3); - if (rgba[3] > threshold) { - bitmask_setbit(bitmask, x, y); + if (threshold < 0 || !SDL_ISPIXELFORMAT_ALPHA(fmt->format)) { + bitmask_fill(bitmask); + return; + } + else if (bpp < 3) { + Uint8 r, g, b, a; + srcp = (Uint8 *)surf->pixels; + for (y = 0; y < surf->h; ++y) { + for (x = 0; x < surf->w; ++x, srcp += bpp) { + SDL_GetRGBA(bpp == 1 ? *srcp : *((Uint16 *)srcp), fmt, &r, &g, + &b, &a); + if (a > threshold) + bitmask_setbit(bitmask, x, y); } + srcp += src_skip; } + return; + } + + /* With this strategy we avoid to get the rgb channels that we don't need + * and instead we just jump from alpha channel to alpha channel, comparing + * it with the threshold. */ + +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + const char _a_off = fmt->Ashift >> 3; +#else + const char _a_off = 3 - (fmt->Ashift >> 3); +#endif + + srcp = (Uint8 *)surf->pixels + _a_off; + const Uint8 u_threshold = (Uint8)threshold; + + for (y = 0; y < surf->h; ++y) { + x = 0; + LOOP_UNROLLED4( + { + if ((*srcp) > u_threshold) + bitmask_setbit(bitmask, x, y); + srcp += bpp; + x++; + }, + n, surf->w); + + srcp += src_skip; } } diff --git a/src_c/math.c b/src_c/math.c index 8ed8ccae5e..24c04365ca 100644 --- a/src_c/math.c +++ b/src_c/math.c @@ -195,20 +195,12 @@ static PyObject * vector_gety(pgVector *self, void *closure); static PyObject * vector_getz(pgVector *self, void *closure); -#ifdef PYGAME_MATH_VECTOR_HAVE_W -static PyObject * -vector_getw(pgVector *self, void *closure); -#endif static int vector_setx(pgVector *self, PyObject *value, void *closure); static int vector_sety(pgVector *self, PyObject *value, void *closure); static int vector_setz(pgVector *self, PyObject *value, void *closure); -#ifdef PYGAME_MATH_VECTOR_HAVE_W -static int -vector_setw(pgVector *self, PyObject *value, void *closure); -#endif static PyObject * vector_richcompare(PyObject *o1, PyObject *o2, int op); static PyObject * @@ -435,13 +427,6 @@ pgVectorCompatible_Check(PyObject *obj, Py_ssize_t dim) return 1; } break; - /* - case 4: - if (pgVector4_Check(obj)) { - return 1; - } - break; - */ default: PyErr_SetString( PyExc_SystemError, @@ -634,10 +619,6 @@ pgVector_NEW(Py_ssize_t dim) return vector2_new(&pgVector2_Type, NULL, NULL); case 3: return vector3_new(&pgVector3_Type, NULL, NULL); - /* - case 4: - return vector4_new(&pgVector4_Type, NULL, NULL); - */ default: return RAISE(PyExc_SystemError, "Wrong internal call to pgVector_NEW.\n"); @@ -1218,14 +1199,33 @@ static PyMappingMethods vector_as_mapping = { static int vector_set_component(pgVector *self, PyObject *value, int component) { - if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete the x attribute"); - return -1; - } if (component >= self->dim) { PyErr_BadInternalCall(); return -1; } + if (value == NULL) { + switch (component) { + case 0: { + PyErr_SetString(PyExc_TypeError, + "Cannot delete the x attribute"); + break; + } + case 1: { + PyErr_SetString(PyExc_TypeError, + "Cannot delete the y attribute"); + break; + } + case 2: { + PyErr_SetString(PyExc_TypeError, + "Cannot delete the z attribute"); + break; + } + default: { + PyErr_BadInternalCall(); + } + } + return -1; + } self->coords[component] = PyFloat_AsDouble(value); if (PyErr_Occurred()) @@ -1269,20 +1269,6 @@ vector_setz(pgVector *self, PyObject *value, void *closure) return vector_set_component(self, value, 2); } -#ifdef PYGAME_MATH_VECTOR_HAVE_W -static PyObject * -vector_getw(pgVector *self, void *closure) -{ - return PyFloat_FromDouble(self->coords[3]); -} - -static int -vector_setw(pgVector *self, PyObject *value, void *closure) -{ - return vector_set_component(self, value, 3); -} -#endif - static PyObject * vector_richcompare(PyObject *o1, PyObject *o2, int op) { @@ -1944,8 +1930,7 @@ vector_getAttr_swizzle(pgVector *self, PyObject *attr_name) if (attr == NULL) goto internal_error; /* If we are not a swizzle, go straight to GenericGetAttr. */ - if ((attr[0] != 'x') && (attr[0] != 'y') && (attr[0] != 'z') && - (attr[0] != 'w')) { + if ((attr[0] != 'x') && (attr[0] != 'y') && (attr[0] != 'z')) { goto swizzle_failed; } @@ -1965,8 +1950,6 @@ vector_getAttr_swizzle(pgVector *self, PyObject *attr_name) case 'z': idx = attr[i] - 'x'; goto swizzle_idx; - case 'w': - idx = 3; swizzle_idx: if (idx >= self->dim) { @@ -2045,9 +2028,6 @@ vector_setAttr_swizzle(pgVector *self, PyObject *attr_name, PyObject *val) case 'z': idx = attr[i] - 'x'; break; - case 'w': - idx = 3; - break; default: /* swizzle failed. attempt generic attribute setting */ Py_DECREF(attr_unicode); @@ -4192,13 +4172,13 @@ vector_elementwise(pgVector *vec, PyObject *_null) return (PyObject *)proxy; } -inline double +static inline double lerp(double a, double b, double v) { return a + (b - a) * v; } -inline double +static inline double invlerp(double a, double b, double v) { return (v - a) / (b - a); @@ -4459,8 +4439,7 @@ MODINIT_DEFINE(math) if ((PyType_Ready(&pgVector2_Type) < 0) || (PyType_Ready(&pgVector3_Type) < 0) || (PyType_Ready(&pgVectorIter_Type) < 0) || - (PyType_Ready(&pgVectorElementwiseProxy_Type) < 0) /*|| - (PyType_Ready(&pgVector4_Type) < 0)*/) { + (PyType_Ready(&pgVectorElementwiseProxy_Type) < 0)) { return NULL; } @@ -4476,9 +4455,6 @@ MODINIT_DEFINE(math) Py_INCREF(&pgVector3_Type); Py_INCREF(&pgVectorIter_Type); Py_INCREF(&pgVectorElementwiseProxy_Type); - /* - Py_INCREF(&pgVector4_Type); - */ if ((PyModule_AddObject(module, "Vector2", (PyObject *)&pgVector2_Type) != 0) || (PyModule_AddObject(module, "Vector3", (PyObject *)&pgVector3_Type) != @@ -4487,9 +4463,7 @@ MODINIT_DEFINE(math) (PyObject *)&pgVectorElementwiseProxy_Type) != 0) || (PyModule_AddObject(module, "VectorIterator", - (PyObject *)&pgVectorIter_Type) != 0) /*|| -(PyModule_AddObject(module, "Vector4", (PyObject *)&pgVector4_Type) != -0)*/) { + (PyObject *)&pgVectorIter_Type) != 0)) { if (!PyObject_HasAttrString(module, "Vector2")) Py_DECREF(&pgVector2_Type); if (!PyObject_HasAttrString(module, "Vector3")) @@ -4498,10 +4472,6 @@ MODINIT_DEFINE(math) Py_DECREF(&pgVectorElementwiseProxy_Type); if (!PyObject_HasAttrString(module, "VectorIterator")) Py_DECREF(&pgVectorIter_Type); - /* - if (!PyObject_HasAttrString(module, "Vector4")) - Py_DECREF(&pgVector4_Type); - */ Py_DECREF(module); return NULL; } @@ -4510,9 +4480,8 @@ MODINIT_DEFINE(math) c_api[0] = &pgVector2_Type; c_api[1] = &pgVector3_Type; /* - c_api[2] = &pgVector4_Type; - c_api[3] = pgVector_NEW; - c_api[4] = pgVectorCompatible_Check; + c_api[2] = pgVector_NEW; + c_api[3] = pgVectorCompatible_Check; */ apiobj = encapsulate_api(c_api, "math"); if (PyModule_AddObject(module, PYGAMEAPI_LOCAL_ENTRY, apiobj)) { diff --git a/src_c/mixer.c b/src_c/mixer.c index f2f4ecb1f7..72d5ba8d7f 100644 --- a/src_c/mixer.c +++ b/src_c/mixer.c @@ -831,6 +831,72 @@ snd_get_samples_address(PyObject *self, PyObject *closure) #endif } +static PyObject * +snd_copy(PyObject *self, PyObject *_null) +{ + Mix_Chunk *chunk = pgSound_AsChunk(self); + pgSoundObject *new_sound; + Mix_Chunk *new_chunk; + + // Validate the input chunk + CHECK_CHUNK_VALID(chunk, NULL); + + // Create a new sound object + new_sound = + (pgSoundObject *)pgSound_Type.tp_new(Py_TYPE(self), NULL, NULL); + if (!new_sound) { + PyErr_SetString(PyExc_MemoryError, + "Failed to allocate memory for new sound object"); + return NULL; + } + + // Handle chunk allocation type + if (chunk->allocated) { + // Create a deep copy of the audio buffer for allocated chunks + Uint8 *buffer_copy = (Uint8 *)malloc(chunk->alen); + if (!buffer_copy) { + Py_DECREF(new_sound); + PyErr_SetString(PyExc_MemoryError, + "Failed to allocate memory for sound buffer"); + return NULL; + } + memcpy(buffer_copy, chunk->abuf, chunk->alen); + + // Create a new Mix_Chunk + new_chunk = Mix_QuickLoad_RAW(buffer_copy, chunk->alen); + if (!new_chunk) { + free(buffer_copy); + Py_DECREF(new_sound); + PyErr_SetString(pgExc_SDLError, + "Failed to create new sound chunk"); + return NULL; + } + new_chunk->volume = chunk->volume; + new_sound->chunk = new_chunk; + } + else { + // For non-allocated chunks (e.g., formats like .xm), create a full + // copy + new_chunk = (Mix_Chunk *)malloc(sizeof(Mix_Chunk)); + if (!new_chunk) { + Py_DECREF(new_sound); + PyErr_SetString(PyExc_MemoryError, + "Failed to allocate memory for sound chunk"); + return NULL; + } + *new_chunk = *chunk; // Copy the entire structure + + // For safety, ensure the copied chunk doesn't share pointers + new_chunk->abuf = + NULL; // Prevent double-free if original gets deallocated + new_chunk->allocated = 0; + + new_sound->chunk = new_chunk; + } + + return (PyObject *)new_sound; +} + PyMethodDef sound_methods[] = { {"play", (PyCFunction)pgSound_Play, METH_VARARGS | METH_KEYWORDS, DOC_MIXER_SOUND_PLAY}, @@ -842,6 +908,8 @@ PyMethodDef sound_methods[] = { {"get_volume", snd_get_volume, METH_NOARGS, DOC_MIXER_SOUND_GETVOLUME}, {"get_length", snd_get_length, METH_NOARGS, DOC_MIXER_SOUND_GETLENGTH}, {"get_raw", snd_get_raw, METH_NOARGS, DOC_MIXER_SOUND_GETRAW}, + {"copy", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY}, + {"__copy__", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY}, {NULL, NULL, 0, NULL}}; static PyGetSetDef sound_getset[] = { @@ -1583,6 +1651,12 @@ static PyObject * mixer_get_sdl_mixer_version(PyObject *self, PyObject *args, PyObject *kwargs) { int linked = 1; /* Default is linked version. */ +#if SDL_VERSION_ATLEAST(3, 0, 0) + int version = SDL_MIXER_VERSION; +#else + SDL_version version; + SDL_MIXER_VERSION(&version); +#endif static char *keywords[] = {"linked", NULL}; @@ -1593,16 +1667,16 @@ mixer_get_sdl_mixer_version(PyObject *self, PyObject *args, PyObject *kwargs) /* MIXER_INIT_CHECK() is not required for these methods. */ if (linked) { - /* linked version */ - const SDL_version *v = Mix_Linked_Version(); - return Py_BuildValue("iii", v->major, v->minor, v->patch); - } - else { - /* compiled version */ - SDL_version v; - SDL_MIXER_VERSION(&v); - return Py_BuildValue("iii", v.major, v.minor, v.patch); +#if SDL_VERSION_ATLEAST(3, 0, 0) + version = Mix_Version(); +#else + version = *Mix_Linked_Version(); +#endif } + + return Py_BuildValue("iii", PG_FIND_VNUM_MAJOR(version), + PG_FIND_VNUM_MINOR(version), + PG_FIND_VNUM_MICRO(version)); } static int diff --git a/src_c/mouse.c b/src_c/mouse.c index c6af99056e..b6a5ae9195 100644 --- a/src_c/mouse.c +++ b/src_c/mouse.c @@ -63,37 +63,48 @@ mouse_set_pos(PyObject *self, PyObject *args) } static PyObject * -mouse_get_pos(PyObject *self, PyObject *_null) +mouse_get_pos(PyObject *self, PyObject *args, PyObject *kwargs) { int x, y; + int desktop = 0; - VIDEO_INIT_CHECK(); - SDL_GetMouseState(&x, &y); + static char *kwids[] = {"desktop", NULL}; - { - SDL_Window *sdlWindow = pg_GetDefaultWindow(); - SDL_Renderer *sdlRenderer = SDL_GetRenderer(sdlWindow); - if (sdlRenderer != NULL) { - SDL_Rect vprect; - float scalex, scaley; - - SDL_RenderGetScale(sdlRenderer, &scalex, &scaley); - SDL_RenderGetViewport(sdlRenderer, &vprect); - - x = (int)(x / scalex); - y = (int)(y / scaley); - - x -= vprect.x; - y -= vprect.y; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|p", kwids, &desktop)) + return NULL; + VIDEO_INIT_CHECK(); - if (x < 0) - x = 0; - if (x >= vprect.w) - x = vprect.w - 1; - if (y < 0) - y = 0; - if (y >= vprect.h) - y = vprect.h - 1; + if (desktop == 1) { + SDL_GetGlobalMouseState(&x, &y); + } + else { + SDL_GetMouseState(&x, &y); + + { + SDL_Window *sdlWindow = pg_GetDefaultWindow(); + SDL_Renderer *sdlRenderer = SDL_GetRenderer(sdlWindow); + if (sdlRenderer != NULL) { + SDL_Rect vprect; + float scalex, scaley; + + SDL_RenderGetScale(sdlRenderer, &scalex, &scaley); + SDL_RenderGetViewport(sdlRenderer, &vprect); + + x = (int)(x / scalex); + y = (int)(y / scaley); + + x -= vprect.x; + y -= vprect.y; + + if (x < 0) + x = 0; + if (x >= vprect.w) + x = vprect.w - 1; + if (y < 0) + y = 0; + if (y >= vprect.h) + y = vprect.h - 1; + } } } @@ -130,10 +141,12 @@ mouse_get_pressed(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *tuple; int state; int num_buttons = 3; + int desktop = 0; - static char *kwids[] = {"num_buttons", NULL}; + static char *kwids[] = {"num_buttons", "desktop", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwids, &num_buttons)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ip", kwids, &num_buttons, + &desktop)) return NULL; VIDEO_INIT_CHECK(); @@ -141,7 +154,9 @@ mouse_get_pressed(PyObject *self, PyObject *args, PyObject *kwargs) return RAISE(PyExc_ValueError, "Number of buttons needs to be 3 or 5."); - state = SDL_GetMouseState(NULL, NULL); + state = desktop ? SDL_GetGlobalMouseState(NULL, NULL) + : SDL_GetMouseState(NULL, NULL); + if (!(tuple = PyTuple_New(num_buttons))) return NULL; @@ -530,7 +545,8 @@ mouse_set_relative_mode(PyObject *self, PyObject *arg) static PyMethodDef _mouse_methods[] = { {"set_pos", mouse_set_pos, METH_VARARGS, DOC_MOUSE_SETPOS}, - {"get_pos", (PyCFunction)mouse_get_pos, METH_NOARGS, DOC_MOUSE_GETPOS}, + {"get_pos", (PyCFunction)mouse_get_pos, METH_VARARGS | METH_KEYWORDS, + DOC_MOUSE_GETPOS}, {"get_rel", (PyCFunction)mouse_get_rel, METH_NOARGS, DOC_MOUSE_GETREL}, {"get_pressed", (PyCFunction)mouse_get_pressed, METH_VARARGS | METH_KEYWORDS, DOC_MOUSE_GETPRESSED}, diff --git a/src_c/pixelarray_methods.c b/src_c/pixelarray_methods.c index dac65170da..235a5b4b64 100644 --- a/src_c/pixelarray_methods.c +++ b/src_c/pixelarray_methods.c @@ -125,17 +125,22 @@ _make_surface(pgPixelArrayObject *array, PyObject *args) surf = pgSurface_AsSurface(array->surface); bpp = PG_SURF_BytesPerPixel(surf); - - /* Create the second surface. */ - - temp_surf = PG_CreateSurface((int)dim0, (int)dim1, surf->format->format); - if (!temp_surf) { - return RAISE(pgExc_SDLError, SDL_GetError()); + temp_surf = surf; + const int same_dims = (dim0 == surf->w && dim1 == surf->h); + + /* If the array dimensions are different from the surface dimensions, + * create a new surface with the array dimensions */ + if (!same_dims) { + if (!(temp_surf = PG_CreateSurface((int)dim0, (int)dim1, + surf->format->format))) + return RAISE(pgExc_SDLError, SDL_GetError()); } - /* Guarantee an identical format. */ + /* Ensure the new surface has the same format as the original */ new_surf = PG_ConvertSurface(temp_surf, surf->format); - SDL_FreeSurface(temp_surf); + if (temp_surf != surf) + SDL_FreeSurface(temp_surf); + if (!new_surf) { return RAISE(pgExc_SDLError, SDL_GetError()); } @@ -146,6 +151,10 @@ _make_surface(pgPixelArrayObject *array, PyObject *args) return 0; } + /* if the surf and array dims match just return a copy */ + if (same_dims) + return (PyObject *)new_surface; + /* Acquire a temporary lock. */ if (SDL_MUSTLOCK(new_surf) == 0) { SDL_LockSurface(new_surf); @@ -158,58 +167,71 @@ _make_surface(pgPixelArrayObject *array, PyObject *args) new_pixelrow = new_pixels; Py_BEGIN_ALLOW_THREADS; - switch (bpp) { - case 1: - for (y = 0; y < dim1; ++y) { - pixel_p = pixelrow; - new_pixel_p = new_pixelrow; - for (x = 0; x < dim0; ++x) { - *new_pixel_p = *pixel_p; - pixel_p += stride0; - new_pixel_p += new_stride0; + + if (stride0 == new_stride0) { + /* if src and dest have the same bpp, so we can copy the whole + * rows at once */ + y = dim1; + while (y--) { + memcpy(new_pixelrow, pixelrow, stride0 * dim0); + pixelrow += stride1; + new_pixelrow += new_stride1; + } + } + else { + switch (bpp) { + case 1: + for (y = 0; y < dim1; ++y) { + pixel_p = pixelrow; + new_pixel_p = new_pixelrow; + for (x = 0; x < dim0; ++x) { + *new_pixel_p = *pixel_p; + pixel_p += stride0; + new_pixel_p += new_stride0; + } + pixelrow += stride1; + new_pixelrow += new_stride1; } - pixelrow += stride1; - new_pixelrow += new_stride1; - } - break; - case 2: - for (y = 0; y < dim1; ++y) { - pixel_p = pixelrow; - new_pixel_p = new_pixelrow; - for (x = 0; x < dim0; ++x) { - *((Uint16 *)new_pixel_p) = *((Uint16 *)pixel_p); - pixel_p += stride0; - new_pixel_p += new_stride0; + break; + case 2: + for (y = 0; y < dim1; ++y) { + pixel_p = pixelrow; + new_pixel_p = new_pixelrow; + for (x = 0; x < dim0; ++x) { + *((Uint16 *)new_pixel_p) = *((Uint16 *)pixel_p); + pixel_p += stride0; + new_pixel_p += new_stride0; + } + pixelrow += stride1; + new_pixelrow += new_stride1; } - pixelrow += stride1; - new_pixelrow += new_stride1; - } - break; - case 3: - for (y = 0; y < dim1; ++y) { - pixel_p = pixelrow; - new_pixel_p = new_pixelrow; - for (x = 0; x < dim0; ++x) { - memcpy(new_pixel_p, pixel_p, 3); - pixel_p += stride0; - new_pixel_p += new_stride0; + break; + case 3: + for (y = 0; y < dim1; ++y) { + pixel_p = pixelrow; + new_pixel_p = new_pixelrow; + for (x = 0; x < dim0; ++x) { + memcpy(new_pixel_p, pixel_p, 3); + pixel_p += stride0; + new_pixel_p += new_stride0; + } + pixelrow += stride1; + new_pixelrow += new_stride1; } - pixelrow += stride1; - new_pixelrow += new_stride1; - } - break; - default: /* case: 4 */ - for (y = 0; y < dim1; ++y) { - pixel_p = pixelrow; - new_pixel_p = new_pixelrow; - for (x = 0; x < dim0; ++x) { - *((Uint32 *)new_pixel_p) = *((Uint32 *)pixel_p); - pixel_p += stride0; - new_pixel_p += new_stride0; + break; + default: /* case: 4 */ + for (y = 0; y < dim1; ++y) { + pixel_p = pixelrow; + new_pixel_p = new_pixelrow; + for (x = 0; x < dim0; ++x) { + *((Uint32 *)new_pixel_p) = *((Uint32 *)pixel_p); + pixel_p += stride0; + new_pixel_p += new_stride0; + } + pixelrow += stride1; + new_pixelrow += new_stride1; } - pixelrow += stride1; - new_pixelrow += new_stride1; - } + } } Py_END_ALLOW_THREADS; diff --git a/src_c/rect.c b/src_c/rect.c index b300c2f97f..f3e22f69d8 100644 --- a/src_c/rect.c +++ b/src_c/rect.c @@ -138,6 +138,7 @@ four_floats_from_obj(PyObject *obj, float *val1, float *val2, float *val3, #define RectImport_primitiveType int #define RectImport_RectCheck pgRect_Check #define RectImport_OtherRectCheck pgFRect_Check +#define RectImport_OtherRectCheckExact pgFRect_CheckExact #define RectImport_RectCheckExact pgRect_CheckExact #define RectImport_innerRectStruct SDL_Rect #define RectImport_otherInnerRectStruct SDL_FRect @@ -151,6 +152,7 @@ four_floats_from_obj(PyObject *obj, float *val1, float *val2, float *val3, #define RectImport_TypeObject pgRect_Type #define RectImport_IntersectRectAndLine SDL_IntersectRectAndLine #define RectImport_PyBuildValueFormat "i" +#define RectImport_TupleFromTwoPrimitives pg_tuple_couple_from_values_int #define RectImport_ObjectName "pygame.rect.Rect" #define RectImport_PythonNumberCheck PyLong_Check #define RectImport_PythonNumberAsPrimitiveType PyLong_AsLong @@ -253,6 +255,7 @@ four_floats_from_obj(PyObject *obj, float *val1, float *val2, float *val3, #define RectImport_primitiveType float #define RectImport_RectCheck pgFRect_Check #define RectImport_OtherRectCheck pgRect_Check +#define RectImport_OtherRectCheckExact pgRect_CheckExact #define RectImport_RectCheckExact pgFRect_CheckExact #define RectImport_innerRectStruct SDL_FRect #define RectImport_otherInnerRectStruct SDL_Rect @@ -266,6 +269,7 @@ four_floats_from_obj(PyObject *obj, float *val1, float *val2, float *val3, #define RectImport_IntersectRectAndLine PG_IntersectFRectAndLine #define RectImport_TypeObject pgFRect_Type #define RectImport_PyBuildValueFormat "f" +#define RectImport_TupleFromTwoPrimitives pg_tuple_couple_from_values_double #define RectImport_ObjectName "pygame.rect.FRect" #define RectImport_PythonNumberCheck PyFloat_Check #define RectImport_PythonNumberAsPrimitiveType PyFloat_AsDouble @@ -611,11 +615,11 @@ pg_rect_repr(pgRectObject *self) static PyObject * pg_frect_repr(pgFRectObject *self) { - char str[64]; + char str[256]; - int ret = PyOS_snprintf(str, 64, "FRect(%f, %f, %f, %f)", self->r.x, + int ret = PyOS_snprintf(str, 256, "FRect(%f, %f, %f, %f)", self->r.x, self->r.y, self->r.w, self->r.h); - if (ret < 0 || ret >= 64) { + if (ret < 0 || ret >= 256) { return RAISE(PyExc_RuntimeError, "Internal PyOS_snprintf call failed!"); } diff --git a/src_c/rect_impl.h b/src_c/rect_impl.h index 0187f07768..c918b0db8c 100644 --- a/src_c/rect_impl.h +++ b/src_c/rect_impl.h @@ -320,6 +320,9 @@ #ifndef RectImport_RectCheckExact #error RectImport_RectCheckExact needs to be Defined #endif +#ifndef RectImport_OtherRectCheckExact +#error RectImport_OtherRectCheckExact needs to be Defined +#endif #ifndef RectImport_primitiveType #error RectImport_primitiveType needs to be defined #endif @@ -356,6 +359,9 @@ #ifndef RectImport_PyBuildValueFormat #error RectImport_PyBuildValueFormat needs to be defined #endif +#ifndef RectImport_TupleFromTwoPrimitives +#error RectImport_TupleFromTwoPrimitives needs to be defined +#endif // #endregion // #region RectOptional @@ -396,6 +402,7 @@ #define fourPrimivitesFromObj RectImport_fourPrimiviteFromObj #define PrimitiveFromObj RectImport_PrimitiveFromObj #define TypeFMT RectImport_PyBuildValueFormat +#define TupleFromTwoPrimitives RectImport_TupleFromTwoPrimitives #define ObjectName RectImport_ObjectName #define PythonNumberCheck RectImport_PythonNumberCheck #define PythonNumberAsPrimitiveType RectImport_PythonNumberAsPrimitiveType @@ -424,7 +431,7 @@ RectExport_do_rects_intresect(InnerRect *A, InnerRect *B) #define _pg_do_rects_intersect RectExport_do_rects_intresect -static InnerRect * +static PG_INLINE InnerRect * RectExport_RectFromObject(PyObject *obj, InnerRect *temp); static InnerRect * RectExport_RectFromFastcallArgs(PyObject *const *args, Py_ssize_t nargs, @@ -611,16 +618,16 @@ static RectObject int RectOptional_Freelist_Num = -1; #endif -static InnerRect * +static PG_INLINE InnerRect * RectExport_RectFromObject(PyObject *obj, InnerRect *temp) { Py_ssize_t length; - if (RectCheck(obj)) { + /* fast path for exact Rect / FRect class */ + if (RectImport_RectCheckExact(obj)) { return &((RectObject *)obj)->r; } - - if (OtherRectCheck(obj)) { + if (RectImport_OtherRectCheckExact(obj)) { OtherInnerRect rect = ((OtherRectObject *)obj)->r; temp->x = (PrimitiveType)rect.x; temp->y = (PrimitiveType)rect.y; @@ -629,6 +636,7 @@ RectExport_RectFromObject(PyObject *obj, InnerRect *temp) return temp; } + /* fast check for sequences */ if (pgSequenceFast_Check(obj)) { length = PySequence_Fast_GET_SIZE(obj); PyObject **items = PySequence_Fast_ITEMS(obj); @@ -721,7 +729,20 @@ RectExport_RectFromObject(PyObject *obj, InnerRect *temp) } } - /* Try to get the rect attribute */ + /* path for possible subclasses (these are very slow checks) */ + if (RectImport_RectCheck(obj)) { + return &((RectObject *)obj)->r; + } + if (RectImport_OtherRectCheck(obj)) { + OtherInnerRect rect = ((OtherRectObject *)obj)->r; + temp->x = (PrimitiveType)rect.x; + temp->y = (PrimitiveType)rect.y; + temp->w = (PrimitiveType)rect.w; + temp->h = (PrimitiveType)rect.h; + return temp; + } + + /* path to get the 'rect' attribute if present */ PyObject *rectattr; if (!(rectattr = PyObject_GetAttrString(obj, "rect"))) { PyErr_Clear(); @@ -731,7 +752,7 @@ RectExport_RectFromObject(PyObject *obj, InnerRect *temp) InnerRect *returnrect; /*call if it's a method*/ if (PyCallable_Check(rectattr)) { - PyObject *rectresult = PyObject_CallObject(rectattr, NULL); + PyObject *rectresult = PyObject_CallNoArgs(rectattr); Py_DECREF(rectattr); if (rectresult == NULL) { PyErr_Clear(); @@ -1508,8 +1529,7 @@ RectExport_RectFromObjectAndKeyFunc(PyObject *obj, PyObject *keyfunc, InnerRect *temp) { if (keyfunc) { - PyObject *obj_with_rect = - PyObject_CallFunctionObjArgs(keyfunc, obj, NULL); + PyObject *obj_with_rect = PyObject_CallOneArg(keyfunc, obj); if (!obj_with_rect) { return NULL; } @@ -1898,8 +1918,29 @@ RectExport_clipline(RectObject *self, PyObject *const *args, Py_ssize_t nargs) } Py_XDECREF(rect_copy); - return Py_BuildValue("((" TypeFMT "" TypeFMT ")(" TypeFMT "" TypeFMT "))", - x1, y1, x2, y2); + + PyObject *subtup1, *subtup2; + subtup1 = TupleFromTwoPrimitives(x1, y1); + if (!subtup1) + return NULL; + + subtup2 = TupleFromTwoPrimitives(x2, y2); + if (!subtup2) { + Py_DECREF(subtup1); + return NULL; + } + + PyObject *tup = PyTuple_New(2); + if (!tup) { + Py_DECREF(subtup1); + Py_DECREF(subtup2); + return NULL; + } + + PyTuple_SET_ITEM(tup, 0, subtup1); + PyTuple_SET_ITEM(tup, 1, subtup2); + + return tup; } static int @@ -2540,7 +2581,7 @@ RectExport_setcentery(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_gettopleft(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x, self->r.y); + return TupleFromTwoPrimitives(self->r.x, self->r.y); } static int @@ -2567,8 +2608,7 @@ RectExport_settopleft(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_gettopright(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x + self->r.w, - self->r.y); + return TupleFromTwoPrimitives(self->r.x + self->r.w, self->r.y); } static int @@ -2595,8 +2635,7 @@ RectExport_settopright(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_getbottomleft(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x, - self->r.y + self->r.h); + return TupleFromTwoPrimitives(self->r.x, self->r.y + self->r.h); } static int @@ -2623,8 +2662,8 @@ RectExport_setbottomleft(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_getbottomright(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x + self->r.w, - self->r.y + self->r.h); + return TupleFromTwoPrimitives(self->r.x + self->r.w, + self->r.y + self->r.h); } static int @@ -2651,8 +2690,7 @@ RectExport_setbottomright(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_getmidtop(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", - self->r.x + (self->r.w / 2), self->r.y); + return TupleFromTwoPrimitives(self->r.x + (self->r.w / 2), self->r.y); } static int @@ -2679,8 +2717,7 @@ RectExport_setmidtop(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_getmidleft(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x, - self->r.y + (self->r.h / 2)); + return TupleFromTwoPrimitives(self->r.x, self->r.y + (self->r.h / 2)); } static int @@ -2707,8 +2744,8 @@ RectExport_setmidleft(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_getmidbottom(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", - self->r.x + (self->r.w / 2), self->r.y + self->r.h); + return TupleFromTwoPrimitives(self->r.x + (self->r.w / 2), + self->r.y + self->r.h); } static int @@ -2735,8 +2772,8 @@ RectExport_setmidbottom(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_getmidright(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x + self->r.w, - self->r.y + (self->r.h / 2)); + return TupleFromTwoPrimitives(self->r.x + self->r.w, + self->r.y + (self->r.h / 2)); } static int @@ -2763,9 +2800,8 @@ RectExport_setmidright(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_getcenter(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", - self->r.x + (self->r.w / 2), - self->r.y + (self->r.h / 2)); + return TupleFromTwoPrimitives(self->r.x + (self->r.w / 2), + self->r.y + (self->r.h / 2)); } static int @@ -2792,7 +2828,7 @@ RectExport_setcenter(RectObject *self, PyObject *value, void *closure) static PyObject * RectExport_getsize(RectObject *self, void *closure) { - return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.w, self->r.h); + return TupleFromTwoPrimitives(self->r.w, self->r.h); } static int @@ -2933,6 +2969,7 @@ RectExport_iterator(RectObject *self) #undef RectImport_RectCheck #undef RectImport_OtherRectCheck #undef RectImport_RectCheckExact +#undef RectImport_OtherRectCheckExact #undef RectImport_innerRectStruct #undef RectImport_otherInnerRectStruct #undef RectImport_innerPointStruct @@ -2946,6 +2983,7 @@ RectExport_iterator(RectObject *self) #undef RectImport_TypeObject #undef RectImport_PrimitiveFromObj #undef RectImport_PyBuildValueFormat +#undef RectImport_TupleFromTwoPrimitives #undef RectImport_ObjectName #undef PrimitiveType diff --git a/src_c/rotozoom.c b/src_c/rotozoom.c index e9d7f32918..61db5f6a02 100644 --- a/src_c/rotozoom.c +++ b/src_c/rotozoom.c @@ -32,23 +32,6 @@ typedef struct tColorRGBA { #define M_PI 3.141592654 #endif -#if !SDL_VERSION_ATLEAST(2, 0, 14) -// Remove this when our minimum version is 2.0.14 or larger -SDL_bool -PG_SurfaceHasRLE(SDL_Surface *surface) -{ - if (surface == NULL) { - return SDL_FALSE; - } - - if (!(surface->map->info.flags & SDL_COPY_RLE_DESIRED)) { - return SDL_FALSE; - } - - return SDL_TRUE; -} -#endif - /* 32bit Zoomer with optional anti-aliasing by bilinear interpolation. diff --git a/src_c/rwobject.c b/src_c/rwobject.c index 19fe3ed745..a88c2495b2 100644 --- a/src_c/rwobject.c +++ b/src_c/rwobject.c @@ -310,7 +310,7 @@ _pg_rw_size(SDL_RWops *context) /* Current file position; need to restore it later. */ - pos = PyObject_CallFunction(helper->tell, NULL); + pos = PyObject_CallNoArgs(helper->tell); if (!pos) { PyErr_Print(); goto end; @@ -327,7 +327,7 @@ _pg_rw_size(SDL_RWops *context) /* Record file size. */ - tmp = PyObject_CallFunction(helper->tell, NULL); + tmp = PyObject_CallNoArgs(helper->tell); if (!tmp) { PyErr_Print(); goto end; @@ -342,7 +342,7 @@ _pg_rw_size(SDL_RWops *context) /* Return to original position. */ - tmp = PyObject_CallFunctionObjArgs(helper->seek, pos, NULL); + tmp = PyObject_CallOneArg(helper->seek, pos); if (!tmp) { PyErr_Print(); goto end; @@ -398,7 +398,7 @@ _pg_rw_close(SDL_RWops *context) PyGILState_STATE state = PyGILState_Ensure(); if (helper->close) { - result = PyObject_CallFunction(helper->close, NULL); + result = PyObject_CallNoArgs(helper->close); if (!result) { PyErr_Print(); retval = -1; @@ -479,7 +479,7 @@ _pg_rw_seek(SDL_RWops *context, Sint64 offset, int whence) Py_DECREF(result); } - result = PyObject_CallFunction(helper->tell, NULL); + result = PyObject_CallNoArgs(helper->tell); if (!result) { PyErr_Print(); retval = -1; diff --git a/src_c/scale2x.c b/src_c/scale2x.c index a60de524f4..61a9beffa5 100644 --- a/src_c/scale2x.c +++ b/src_c/scale2x.c @@ -34,13 +34,19 @@ #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define READINT24(x) ((x)[0] << 16 | (x)[1] << 8 | (x)[2]) -#define WRITEINT24(x, i) \ - { \ - (x)[0] = i >> 16; \ - (x)[1] = (i >> 8) & 0xff; \ - x[2] = i & 0xff; \ - } +static inline int +read_int24(const Uint8 *x) +{ + return (x[0] << 16 | x[1] << 8 | x[2]); +} + +static inline void +store_int24(Uint8 *x, int i) +{ + x[0] = i >> 16; + x[1] = (i >> 8) & 0xff; + x[2] = i & 0xff; +} /* this requires a destination surface already setup to be twice as @@ -62,38 +68,47 @@ scale2x(SDL_Surface *src, SDL_Surface *dst) const int height = src->h; #if SDL_VERSION_ATLEAST(3, 0, 0) - switch (src->format->bytes_per_pixel) { + const Uint8 Bpp = src->format->bytes_per_pixel; #else - switch (src->format->BytesPerPixel) { + const Uint8 Bpp = src->format->BytesPerPixel; #endif + + switch (Bpp) { case 1: { Uint8 E0, E1, E2, E3, B, D, E, F, H; for (looph = 0; looph < height; ++looph) { + Uint8 *src_row = srcpix + looph * srcpitch; + Uint8 *dst_row0 = dstpix + looph * 2 * dstpitch; + Uint8 *dst_row1 = dstpix + (looph * 2 + 1) * dstpitch; + + Uint8 *src_row_prev = srcpix + MAX(0, looph - 1) * srcpitch; + Uint8 *src_row_next = + srcpix + MIN(height - 1, looph + 1) * srcpitch; + for (loopw = 0; loopw < width; ++loopw) { - B = *(Uint8 *)(srcpix + (MAX(0, looph - 1) * srcpitch) + - (1 * loopw)); - D = *(Uint8 *)(srcpix + (looph * srcpitch) + - (1 * MAX(0, loopw - 1))); - E = *(Uint8 *)(srcpix + (looph * srcpitch) + (1 * loopw)); - F = *(Uint8 *)(srcpix + (looph * srcpitch) + - (1 * MIN(width - 1, loopw + 1))); - H = *(Uint8 *)(srcpix + - (MIN(height - 1, looph + 1) * srcpitch) + - (1 * loopw)); - - E0 = D == B && B != F && D != H ? D : E; - E1 = B == F && B != D && F != H ? F : E; - E2 = D == H && D != B && H != F ? D : E; - E3 = H == F && D != H && B != F ? F : E; - - *(Uint8 *)(dstpix + looph * 2 * dstpitch + loopw * 2 * 1) = - E0; - *(Uint8 *)(dstpix + looph * 2 * dstpitch + - (loopw * 2 + 1) * 1) = E1; - *(Uint8 *)(dstpix + (looph * 2 + 1) * dstpitch + - loopw * 2 * 1) = E2; - *(Uint8 *)(dstpix + (looph * 2 + 1) * dstpitch + - (loopw * 2 + 1) * 1) = E3; + B = *(Uint8 *)(src_row_prev + loopw); + D = *(Uint8 *)(src_row + MAX(0, loopw - 1)); + E = *(Uint8 *)(src_row + loopw); + F = *(Uint8 *)(src_row + MIN(width - 1, loopw + 1)); + H = *(Uint8 *)(src_row_next + loopw); + + if (B != H && D != F) { + E0 = (D == B) ? D : E; + E1 = (B == F) ? F : E; + E2 = (D == H) ? D : E; + E3 = (H == F) ? F : E; + } + else { + E0 = E; + E1 = E; + E2 = E; + E3 = E; + } + + *(Uint8 *)(dst_row0 + loopw * 2) = E0; + *(Uint8 *)(dst_row0 + loopw * 2 + 1) = E1; + *(Uint8 *)(dst_row1 + loopw * 2) = E2; + *(Uint8 *)(dst_row1 + loopw * 2 + 1) = E3; } } break; @@ -101,31 +116,38 @@ scale2x(SDL_Surface *src, SDL_Surface *dst) case 2: { Uint16 E0, E1, E2, E3, B, D, E, F, H; for (looph = 0; looph < height; ++looph) { + Uint8 *src_row = srcpix + looph * srcpitch; + Uint8 *dst_row0 = dstpix + looph * 2 * dstpitch; + Uint8 *dst_row1 = dstpix + (looph * 2 + 1) * dstpitch; + + Uint8 *src_row_prev = srcpix + MAX(0, looph - 1) * srcpitch; + Uint8 *src_row_next = + srcpix + MIN(height - 1, looph + 1) * srcpitch; + for (loopw = 0; loopw < width; ++loopw) { - B = *(Uint16 *)(srcpix + (MAX(0, looph - 1) * srcpitch) + - (2 * loopw)); - D = *(Uint16 *)(srcpix + (looph * srcpitch) + - (2 * MAX(0, loopw - 1))); - E = *(Uint16 *)(srcpix + (looph * srcpitch) + (2 * loopw)); - F = *(Uint16 *)(srcpix + (looph * srcpitch) + - (2 * MIN(width - 1, loopw + 1))); - H = *(Uint16 *)(srcpix + - (MIN(height - 1, looph + 1) * srcpitch) + - (2 * loopw)); - - E0 = D == B && B != F && D != H ? D : E; - E1 = B == F && B != D && F != H ? F : E; - E2 = D == H && D != B && H != F ? D : E; - E3 = H == F && D != H && B != F ? F : E; - - *(Uint16 *)(dstpix + looph * 2 * dstpitch + - loopw * 2 * 2) = E0; - *(Uint16 *)(dstpix + looph * 2 * dstpitch + - (loopw * 2 + 1) * 2) = E1; - *(Uint16 *)(dstpix + (looph * 2 + 1) * dstpitch + - loopw * 2 * 2) = E2; - *(Uint16 *)(dstpix + (looph * 2 + 1) * dstpitch + - (loopw * 2 + 1) * 2) = E3; + B = *(Uint16 *)(src_row_prev + 2 * loopw); + D = *(Uint16 *)(src_row + 2 * MAX(0, loopw - 1)); + E = *(Uint16 *)(src_row + 2 * loopw); + F = *(Uint16 *)(src_row + 2 * MIN(width - 1, loopw + 1)); + H = *(Uint16 *)(src_row_next + 2 * loopw); + + if (B != H && D != F) { + E0 = (D == B) ? D : E; + E1 = (B == F) ? F : E; + E2 = (D == H) ? D : E; + E3 = (H == F) ? F : E; + } + else { + E0 = E; + E1 = E; + E2 = E; + E3 = E; + } + + *(Uint16 *)(dst_row0 + loopw * 2 * 2) = E0; + *(Uint16 *)(dst_row0 + (loopw * 2 + 1) * 2) = E1; + *(Uint16 *)(dst_row1 + loopw * 2 * 2) = E2; + *(Uint16 *)(dst_row1 + (loopw * 2 + 1) * 2) = E3; } } break; @@ -133,66 +155,78 @@ scale2x(SDL_Surface *src, SDL_Surface *dst) case 3: { int E0, E1, E2, E3, B, D, E, F, H; for (looph = 0; looph < height; ++looph) { + Uint8 *src_row = srcpix + looph * srcpitch; + Uint8 *dst_row0 = dstpix + looph * 2 * dstpitch; + Uint8 *dst_row1 = dstpix + (looph * 2 + 1) * dstpitch; + + Uint8 *src_row_prev = srcpix + MAX(0, looph - 1) * srcpitch; + Uint8 *src_row_next = + srcpix + MIN(height - 1, looph + 1) * srcpitch; + for (loopw = 0; loopw < width; ++loopw) { - B = READINT24(srcpix + (MAX(0, looph - 1) * srcpitch) + - (3 * loopw)); - D = READINT24(srcpix + (looph * srcpitch) + - (3 * MAX(0, loopw - 1))); - E = READINT24(srcpix + (looph * srcpitch) + (3 * loopw)); - F = READINT24(srcpix + (looph * srcpitch) + - (3 * MIN(width - 1, loopw + 1))); - H = READINT24(srcpix + - (MIN(height - 1, looph + 1) * srcpitch) + - (3 * loopw)); - - E0 = D == B && B != F && D != H ? D : E; - E1 = B == F && B != D && F != H ? F : E; - E2 = D == H && D != B && H != F ? D : E; - E3 = H == F && D != H && B != F ? F : E; - - WRITEINT24((dstpix + looph * 2 * dstpitch + loopw * 2 * 3), - E0); - WRITEINT24( - (dstpix + looph * 2 * dstpitch + (loopw * 2 + 1) * 3), - E1); - WRITEINT24( - (dstpix + (looph * 2 + 1) * dstpitch + loopw * 2 * 3), - E2); - WRITEINT24((dstpix + (looph * 2 + 1) * dstpitch + - (loopw * 2 + 1) * 3), - E3); + B = read_int24(src_row_prev + (3 * loopw)); + D = read_int24(src_row + (3 * MAX(0, loopw - 1))); + E = read_int24(src_row + (3 * loopw)); + F = read_int24(src_row + (3 * MIN(width - 1, loopw + 1))); + H = read_int24(src_row_next + (3 * loopw)); + + if (B != H && D != F) { + E0 = (D == B) ? D : E; + E1 = (B == F) ? F : E; + E2 = (D == H) ? D : E; + E3 = (H == F) ? F : E; + } + else { + E0 = E; + E1 = E; + E2 = E; + E3 = E; + } + + store_int24(dst_row0 + loopw * 2 * 3, E0); + store_int24(dst_row0 + (loopw * 2 + 1) * 3, E1); + store_int24(dst_row1 + loopw * 2 * 3, E2); + store_int24(dst_row1 + (loopw * 2 + 1) * 3, E3); } } break; } - default: { /*case 4:*/ + default: { Uint32 E0, E1, E2, E3, B, D, E, F, H; + for (looph = 0; looph < height; ++looph) { + Uint8 *src_row = srcpix + looph * srcpitch; + Uint8 *dst_row0 = dstpix + looph * 2 * dstpitch; + Uint8 *dst_row1 = dstpix + (looph * 2 + 1) * dstpitch; + + Uint8 *src_row_prev = srcpix + MAX(0, looph - 1) * srcpitch; + Uint8 *src_row_next = + srcpix + MIN(height - 1, looph + 1) * srcpitch; + for (loopw = 0; loopw < width; ++loopw) { - B = *(Uint32 *)(srcpix + (MAX(0, looph - 1) * srcpitch) + - (4 * loopw)); - D = *(Uint32 *)(srcpix + (looph * srcpitch) + - (4 * MAX(0, loopw - 1))); - E = *(Uint32 *)(srcpix + (looph * srcpitch) + (4 * loopw)); - F = *(Uint32 *)(srcpix + (looph * srcpitch) + - (4 * MIN(width - 1, loopw + 1))); - H = *(Uint32 *)(srcpix + - (MIN(height - 1, looph + 1) * srcpitch) + - (4 * loopw)); - - E0 = D == B && B != F && D != H ? D : E; - E1 = B == F && B != D && F != H ? F : E; - E2 = D == H && D != B && H != F ? D : E; - E3 = H == F && D != H && B != F ? F : E; - - *(Uint32 *)(dstpix + looph * 2 * dstpitch + - loopw * 2 * 4) = E0; - *(Uint32 *)(dstpix + looph * 2 * dstpitch + - (loopw * 2 + 1) * 4) = E1; - *(Uint32 *)(dstpix + (looph * 2 + 1) * dstpitch + - loopw * 2 * 4) = E2; - *(Uint32 *)(dstpix + (looph * 2 + 1) * dstpitch + - (loopw * 2 + 1) * 4) = E3; + B = *(Uint32 *)(src_row_prev + 4 * loopw); + D = *(Uint32 *)(src_row + 4 * MAX(0, loopw - 1)); + E = *(Uint32 *)(src_row + 4 * loopw); + F = *(Uint32 *)(src_row + 4 * MIN(width - 1, loopw + 1)); + H = *(Uint32 *)(src_row_next + 4 * loopw); + + if (B != H && D != F) { + E0 = (D == B) ? D : E; + E1 = (B == F) ? F : E; + E2 = (D == H) ? D : E; + E3 = (H == F) ? F : E; + } + else { + E0 = E; + E1 = E; + E2 = E; + E3 = E; + } + + *(Uint32 *)(dst_row0 + loopw * 2 * 4) = E0; + *(Uint32 *)(dst_row0 + (loopw * 2 + 1) * 4) = E1; + *(Uint32 *)(dst_row1 + loopw * 2 * 4) = E2; + *(Uint32 *)(dst_row1 + (loopw * 2 + 1) * 4) = E3; } } break; diff --git a/src_c/scrap.c b/src_c/scrap.c index e83671b845..b34cb6b06e 100644 --- a/src_c/scrap.c +++ b/src_c/scrap.c @@ -349,6 +349,11 @@ _scrap_lost_scrap(PyObject *self, PyObject *_null) { PYGAME_SCRAP_INIT_CHECK(); + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "pygame.scrap.lost deprecated since 2.2.0", 1) == -1) { + return NULL; + } + if (pygame_scrap_lost()) Py_RETURN_TRUE; Py_RETURN_FALSE; diff --git a/src_c/static.c b/src_c/static.c index b7e8d1ec66..6e459e809e 100644 --- a/src_c/static.c +++ b/src_c/static.c @@ -150,6 +150,9 @@ PyInit_mixer(void); PyMODINIT_FUNC PyInit_system(void); +PyMODINIT_FUNC +PyInit_controller(void); + PyMODINIT_FUNC PyInit_controller_old(void); @@ -253,6 +256,8 @@ mod_pygame_import_cython(PyObject *self, PyObject *spec) load_submodule_mphase("pygame._sdl2", PyInit_mixer(), spec, "mixer"); load_submodule_mphase("pygame._sdl2", PyInit_controller_old(), spec, "controller_old"); + load_submodule_mphase("pygame._sdl2", PyInit_controller(), spec, + "controller"); load_submodule_mphase("pygame._sdl2", PyInit_audio(), spec, "audio"); load_submodule_mphase("pygame._sdl2", PyInit_video(), spec, "video"); @@ -334,8 +339,6 @@ PyInit_pygame_static() #undef pgSurface_UnlockBy #undef pgSurface_Prep #undef pgSurface_Unprep -#undef pgLifetimeLock_Type -#undef pgSurface_LockLifetime #include "surflock.c" @@ -427,6 +430,7 @@ PyInit_pygame_static() #include "pixelcopy.c" #include "newbuffer.c" +#include "_sdl2/controller.c" #include "_sdl2/controller_old.c" #include "_sdl2/touch.c" #include "transform.c" diff --git a/src_c/surface.c b/src_c/surface.c index 957e7bcaee..072aa09b5c 100644 --- a/src_c/surface.c +++ b/src_c/surface.c @@ -63,41 +63,6 @@ typedef struct pg_bufferinternal_s { Py_ssize_t mem[6]; /* Enough memory for dim 3 shape and strides */ } pg_bufferinternal; -/* copy of SDL Blit mapping definitions to enable pointer casting hack - for checking state of the SDL_COPY_RLE_DESIRED flag */ -#define PGS_COPY_RLE_DESIRED 0x00001000 - -typedef struct { - Uint8 *src; - int src_w, src_h; - int src_pitch; - int src_skip; - Uint8 *dst; - int dst_w, dst_h; - int dst_pitch; - int dst_skip; - SDL_PixelFormat *src_fmt; - SDL_PixelFormat *dst_fmt; - Uint8 *table; - int flags; - Uint32 colorkey; - Uint8 r, g, b, a; -} pg_BlitInfo; - -typedef struct pg_BlitMap { - SDL_Surface *dst; - int identity; - SDL_blit blit; - void *data; - pg_BlitInfo info; - - /* the version count matches the destination; mismatch indicates - an invalid mapping */ - Uint32 dst_palette_version; - Uint32 src_palette_version; -} pg_BlitMap; -/* end PGS_COPY_RLE_DESIRED hack definitions */ - int pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, SDL_Rect *dstrect, SDL_Rect *srcrect, int blend_flags); @@ -229,6 +194,8 @@ static PyObject * surf_get_pixels_address(PyObject *self, PyObject *closure); static PyObject * surf_premul_alpha(pgSurfaceObject *self, PyObject *args); +static PyObject * +surf_premul_alpha_ip(pgSurfaceObject *self, PyObject *args); static int _view_kind(PyObject *obj, void *view_kind_vptr); static int @@ -353,6 +320,8 @@ static struct PyMethodDef surface_methods[] = { {"get_buffer", surf_get_buffer, METH_NOARGS, DOC_SURFACE_GETBUFFER}, {"premul_alpha", (PyCFunction)surf_premul_alpha, METH_NOARGS, DOC_SURFACE_PREMULALPHA}, + {"premul_alpha_ip", (PyCFunction)surf_premul_alpha_ip, METH_NOARGS, + DOC_SURFACE_PREMULALPHAIP}, {NULL, NULL, 0, NULL}}; @@ -1024,6 +993,7 @@ surf_get_locks(PyObject *self, PyObject *_null) { pgSurfaceObject *surf = (pgSurfaceObject *)self; Py_ssize_t len, i = 0; + int weakref_getref_result; PyObject *tuple, *tmp; SURF_INIT_CHECK(pgSurface_AsSurface(self)) if (!surf->locklist) @@ -1035,8 +1005,16 @@ surf_get_locks(PyObject *self, PyObject *_null) return NULL; for (i = 0; i < len; i++) { - tmp = PyWeakref_GetObject(PyList_GetItem(surf->locklist, i)); - Py_INCREF(tmp); + weakref_getref_result = + PyWeakref_GetRef(PyList_GetItem(surf->locklist, i), &tmp); + if (weakref_getref_result == -1) { // exception already set + Py_DECREF(tuple); + return NULL; + } + if (weakref_getref_result == 0) { + tmp = Py_None; + Py_INCREF(tmp); + } PyTuple_SetItem(tuple, i, tmp); } return tuple; @@ -1790,53 +1768,32 @@ surf_fill(pgSurfaceObject *self, PyObject *args, PyObject *keywds) rect = &temp; } - if (rect->w < 0 || rect->h < 0 || rect->x > surf->w || rect->y > surf->h) { - sdlrect.x = sdlrect.y = 0; - sdlrect.w = sdlrect.h = 0; + // In SDL3, SDL_IntersectRect is renamed to SDL_GetRectIntersection + SDL_Rect surfrect = {0, 0, surf->w, surf->h}; + if (!SDL_IntersectRect(rect, &surfrect, &sdlrect)) { + sdlrect.x = 0; + sdlrect.y = 0; + sdlrect.w = 0; + sdlrect.h = 0; } - else { - sdlrect.x = rect->x; - sdlrect.y = rect->y; - sdlrect.w = rect->w; - sdlrect.h = rect->h; - - // clip the rect to be within the surface. - if (sdlrect.x + sdlrect.w <= 0 || sdlrect.y + sdlrect.h <= 0) { - sdlrect.w = 0; - sdlrect.h = 0; - } - if (sdlrect.x < 0) { - sdlrect.x = 0; - } - if (sdlrect.y < 0) { - sdlrect.y = 0; - } - - if (sdlrect.x + sdlrect.w > surf->w) { - sdlrect.w = sdlrect.w + (surf->w - (sdlrect.x + sdlrect.w)); - } - if (sdlrect.y + sdlrect.h > surf->h) { - sdlrect.h = sdlrect.h + (surf->h - (sdlrect.y + sdlrect.h)); - } - - if (sdlrect.w <= 0 || sdlrect.h <= 0) { - return pgRect_New(&sdlrect); - } + if (sdlrect.w <= 0 || sdlrect.h <= 0) { + return pgRect_New(&sdlrect); + } - if (blendargs != 0) { - result = surface_fill_blend(surf, &sdlrect, color, blendargs); - } - else { - pgSurface_Prep(self); - pgSurface_Lock((pgSurfaceObject *)self); - result = SDL_FillRect(surf, &sdlrect, color); - pgSurface_Unlock((pgSurfaceObject *)self); - pgSurface_Unprep(self); - } - if (result == -1) - return RAISE(pgExc_SDLError, SDL_GetError()); + if (blendargs != 0) { + result = surface_fill_blend(surf, &sdlrect, color, blendargs); } + else { + pgSurface_Prep(self); + pgSurface_Lock((pgSurfaceObject *)self); + result = SDL_FillRect(surf, &sdlrect, color); + pgSurface_Unlock((pgSurfaceObject *)self); + pgSurface_Unprep(self); + } + if (result == -1) + return RAISE(pgExc_SDLError, SDL_GetError()); + return pgRect_New(&sdlrect); } @@ -1845,7 +1802,7 @@ surf_blit(pgSurfaceObject *self, PyObject *args, PyObject *keywds) { SDL_Surface *src, *dest = pgSurface_AsSurface(self); SDL_Rect *src_rect, temp; - PyObject *argpos, *argrect = NULL; + PyObject *argpos = NULL, *argrect = NULL; pgSurfaceObject *srcobject; int dx, dy, result; SDL_Rect dest_rect; @@ -1853,7 +1810,7 @@ surf_blit(pgSurfaceObject *self, PyObject *args, PyObject *keywds) int blend_flags = 0; static char *kwids[] = {"source", "dest", "area", "special_flags", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!O|Oi", kwids, + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|OOi", kwids, &pgSurface_Type, &srcobject, &argpos, &argrect, &blend_flags)) return NULL; @@ -1862,7 +1819,11 @@ surf_blit(pgSurfaceObject *self, PyObject *args, PyObject *keywds) SURF_INIT_CHECK(src) SURF_INIT_CHECK(dest) - if ((src_rect = pgRect_FromObject(argpos, &temp))) { + if (argpos == NULL) { /* dest argument is absent */ + dx = 0; + dy = 0; + } + else if ((src_rect = pgRect_FromObject(argpos, &temp))) { dx = src_rect->x; dy = src_rect->y; } @@ -2155,7 +2116,7 @@ surf_blits(pgSurfaceObject *self, PyObject *args, PyObject *keywds) #define FBLITS_ERR_INCORRECT_ARGS_NUM 12 #define FBLITS_ERR_FLAG_NOT_NUMERIC 13 -int +static int PG_FORCEINLINE _surf_fblits_item_check_and_blit(pgSurfaceObject *self, PyObject *item, int blend_flags) { @@ -2383,25 +2344,6 @@ surf_scroll(PyObject *self, PyObject *args, PyObject *keywds) Py_RETURN_NONE; } -int -pg_HasSurfaceRLE(SDL_Surface *surface) -{ - pg_BlitMap *blit_map; - /* this is part of a hack to allow us to access - the COPY_RLE_DESIRED flag from pygame */ - if (!surface) { - return SDL_FALSE; - } - - blit_map = (pg_BlitMap *)surface->map; - - if (!(blit_map->info.flags & PGS_COPY_RLE_DESIRED)) { - return SDL_FALSE; - } - - return SDL_TRUE; -} - static int _PgSurface_SrcAlpha(SDL_Surface *surf) { @@ -2443,7 +2385,7 @@ surf_get_flags(PyObject *self, PyObject *_null) flags |= PGS_SRCCOLORKEY; if (sdl_flags & SDL_PREALLOC) flags |= PGS_PREALLOC; - if (pg_HasSurfaceRLE(surf)) + if (PG_SurfaceHasRLE(surf)) flags |= PGS_RLEACCELOK; if ((sdl_flags & SDL_RLEACCEL)) flags |= PGS_RLEACCEL; @@ -2615,8 +2557,6 @@ surf_subsurface(PyObject *self, PyObject *args) SDL_Rect *rect, temp; SDL_Surface *sub; PyObject *subobj; - int pixeloffset; - char *startpixel; struct pgSubSurface_Data *data; Uint8 alpha; Uint32 colorkey; @@ -2633,9 +2573,9 @@ surf_subsurface(PyObject *self, PyObject *args) pgSurface_Lock((pgSurfaceObject *)self); - pixeloffset = - rect->x * PG_FORMAT_BytesPerPixel(format) + rect->y * surf->pitch; - startpixel = ((char *)surf->pixels) + pixeloffset; + char *startpixel = ((char *)surf->pixels) + + rect->x * PG_FORMAT_BytesPerPixel(format) + + rect->y * surf->pitch; sub = PG_CreateSurfaceFrom(startpixel, rect->w, rect->h, surf->pitch, format->format); @@ -2703,7 +2643,6 @@ surf_subsurface(PyObject *self, PyObject *args) } Py_INCREF(self); data->owner = self; - data->pixeloffset = pixeloffset; data->offsetx = rect->x; data->offsety = rect->y; ((pgSurfaceObject *)subobj)->subsurface = data; @@ -3195,6 +3134,31 @@ surf_premul_alpha(pgSurfaceObject *self, PyObject *_null) return final; } +static PyObject * +surf_premul_alpha_ip(pgSurfaceObject *self, PyObject *_null) +{ + SDL_Surface *surf = pgSurface_AsSurface(self); + SURF_INIT_CHECK(surf) + + if (!surf->w || !surf->h) { + Py_INCREF(self); + return (PyObject *)self; + } + + pgSurface_Prep(self); + + if (premul_surf_color_by_alpha(surf, surf) != 0) { + return RAISE(PyExc_ValueError, + "source surface to be alpha pre-multiplied must have " + "alpha channel"); + } + + pgSurface_Unprep(self); + + Py_INCREF(self); + return (PyObject *)self; +} + static int _get_buffer_0D(PyObject *obj, Py_buffer *view_p, int flags) { @@ -3531,8 +3495,8 @@ _get_buffer_colorplane(PyObject *obj, Py_buffer *view_p, int flags, char *name, /* Should not be here */ PyErr_Format(PyExc_SystemError, "Pygame bug caught at line %i in file %s: " - "unknown mask value %p. Please report", - (int)__LINE__, __FILE__, (void *)mask); + "unknown mask value %X. Please report", + (int)__LINE__, __FILE__, mask); return -1; #endif } @@ -3613,18 +3577,22 @@ _release_buffer(Py_buffer *view_p) { pg_bufferinternal *internal; PyObject *consumer_ref; - PyObject *consumer; + PyObject *consumer = NULL; assert(view_p && view_p->obj && view_p->internal); internal = (pg_bufferinternal *)view_p->internal; consumer_ref = internal->consumer_ref; assert(consumer_ref && PyWeakref_CheckRef(consumer_ref)); - consumer = PyWeakref_GetObject(consumer_ref); - if (consumer) { - if (!pgSurface_UnlockBy((pgSurfaceObject *)view_p->obj, consumer)) { - PyErr_Clear(); - } + + if (PyWeakref_GetRef(consumer_ref, &consumer) != 1) { + PyErr_Clear(); // ignore any errors here } + + if (!pgSurface_UnlockBy((pgSurfaceObject *)view_p->obj, consumer)) { + PyErr_Clear(); + } + Py_XDECREF(consumer); + Py_DECREF(consumer_ref); PyMem_Free(internal); Py_DECREF(view_p->obj); @@ -3925,7 +3893,7 @@ pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, PG_SURF_BytesPerPixel(dst) == 2) && _PgSurface_SrcAlpha(src) && (SDL_ISPIXELFORMAT_ALPHA(src->format->format)) && - !pg_HasSurfaceRLE(src) && !pg_HasSurfaceRLE(dst) && + !PG_SurfaceHasRLE(src) && !PG_SurfaceHasRLE(dst) && !(src->flags & SDL_RLEACCEL) && !(dst->flags & SDL_RLEACCEL)) { /* If we have a 32bit source surface with per pixel alpha and no RLE we'll use pygame_Blit so we can mimic how SDL1 diff --git a/src_c/surflock.c b/src_c/surflock.c index ad7919ae0c..62cd3acdbd 100644 --- a/src_c/surflock.c +++ b/src_c/surflock.c @@ -39,18 +39,12 @@ pgSurface_LockBy(pgSurfaceObject *, PyObject *); static int pgSurface_UnlockBy(pgSurfaceObject *, PyObject *); -static void -_lifelock_dealloc(PyObject *); - static void pgSurface_Prep(pgSurfaceObject *surfobj) { struct pgSubSurface_Data *data = ((pgSurfaceObject *)surfobj)->subsurface; if (data != NULL) { - SDL_Surface *surf = pgSurface_AsSurface(surfobj); - SDL_Surface *owner = pgSurface_AsSurface(data->owner); pgSurface_LockBy((pgSurfaceObject *)data->owner, (PyObject *)surfobj); - surf->pixels = ((char *)owner->pixels) + data->pixeloffset; } } @@ -118,20 +112,29 @@ pgSurface_UnlockBy(pgSurfaceObject *surfobj, PyObject *lockobj) pgSurfaceObject *surf = (pgSurfaceObject *)surfobj; int found = 0; int noerror = 1; + int weakref_getref_result; if (surf->locklist != NULL) { PyObject *item, *ref; Py_ssize_t len = PyList_Size(surf->locklist); while (--len >= 0 && !found) { item = PyList_GetItem(surf->locklist, len); - ref = PyWeakref_GetObject(item); - if (ref == lockobj) { - if (PySequence_DelItem(surf->locklist, len) == -1) { - return 0; - } - else { - found = 1; + + weakref_getref_result = PyWeakref_GetRef(item, &ref); + if (weakref_getref_result == -1) { + noerror = 0; + } + if (weakref_getref_result == 1) { + if (ref == lockobj) { + if (PySequence_DelItem(surf->locklist, len) == -1) { + Py_DECREF(ref); + return 0; + } + else { + found = 1; + } } + Py_DECREF(ref); } } @@ -139,8 +142,12 @@ pgSurface_UnlockBy(pgSurfaceObject *surfobj, PyObject *lockobj) len = PyList_Size(surf->locklist); while (--len >= 0) { item = PyList_GetItem(surf->locklist, len); - ref = PyWeakref_GetObject(item); - if (ref == Py_None) { + + weakref_getref_result = PyWeakref_GetRef(item, &ref); + if (weakref_getref_result == -1) { + noerror = 0; + } + else if (weakref_getref_result == 0) { if (PySequence_DelItem(surf->locklist, len) == -1) { noerror = 0; } @@ -148,6 +155,9 @@ pgSurface_UnlockBy(pgSurfaceObject *surfobj, PyObject *lockobj) found++; } } + else if (weakref_getref_result == 1) { + Py_DECREF(ref); + } } } @@ -169,51 +179,6 @@ pgSurface_UnlockBy(pgSurfaceObject *surfobj, PyObject *lockobj) return noerror; } -static PyTypeObject pgLifetimeLock_Type = { - PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.surflock.SurfLifeLock", - .tp_basicsize = sizeof(pgLifetimeLockObject), - .tp_dealloc = _lifelock_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_weaklistoffset = offsetof(pgLifetimeLockObject, weakrefs), -}; - -/* lifetimelock object internals */ -static void -_lifelock_dealloc(PyObject *self) -{ - pgLifetimeLockObject *lifelock = (pgLifetimeLockObject *)self; - - if (lifelock->weakrefs != NULL) { - PyObject_ClearWeakRefs(self); - } - - pgSurface_UnlockBy((pgSurfaceObject *)lifelock->surface, - lifelock->lockobj); - Py_DECREF(lifelock->surface); - Py_TYPE(self)->tp_free(self); -} - -static PyObject * -pgSurface_LockLifetime(PyObject *surfobj, PyObject *lockobj) -{ - pgLifetimeLockObject *life; - if (surfobj == NULL) { - return RAISE(pgExc_SDLError, SDL_GetError()); - } - - life = PyObject_New(pgLifetimeLockObject, &pgLifetimeLock_Type); - if (life != NULL) { - life->surface = surfobj; - life->lockobj = lockobj; - life->weakrefs = NULL; - Py_INCREF(surfobj); - if (!pgSurface_LockBy((pgSurfaceObject *)surfobj, lockobj)) { - return NULL; - } - } - return (PyObject *)life; -} - static PyMethodDef _surflock_methods[] = {{NULL, NULL, 0, NULL}}; /*DOC*/ static char _surflock_doc[] = @@ -234,10 +199,6 @@ MODINIT_DEFINE(surflock) NULL, NULL}; - if (PyType_Ready(&pgLifetimeLock_Type) < 0) { - return NULL; - } - /* Create the module and add the functions */ module = PyModule_Create(&_module); if (module == NULL) { @@ -245,14 +206,12 @@ MODINIT_DEFINE(surflock) } /* export the c api */ - c_api[0] = &pgLifetimeLock_Type; - c_api[1] = pgSurface_Prep; - c_api[2] = pgSurface_Unprep; - c_api[3] = pgSurface_Lock; - c_api[4] = pgSurface_Unlock; - c_api[5] = pgSurface_LockBy; - c_api[6] = pgSurface_UnlockBy; - c_api[7] = pgSurface_LockLifetime; + c_api[0] = pgSurface_Prep; + c_api[1] = pgSurface_Unprep; + c_api[2] = pgSurface_Lock; + c_api[3] = pgSurface_Unlock; + c_api[4] = pgSurface_LockBy; + c_api[5] = pgSurface_UnlockBy; apiobj = encapsulate_api(c_api, "surflock"); if (PyModule_AddObject(module, PYGAMEAPI_LOCAL_ENTRY, apiobj)) { Py_XDECREF(apiobj); diff --git a/src_c/system.c b/src_c/system.c index 7ba35a04d4..7d715821ad 100644 --- a/src_c/system.c +++ b/src_c/system.c @@ -207,9 +207,9 @@ pg_system_get_power_state(PyObject *self, PyObject *_null) "battery_seconds", sec_py, "on_battery", PyBool_FromLong(on_battery), "no_battery", PyBool_FromLong(no_battery), - "charging", PyBool_FromLong(charging), + "charging", PyBool_FromLong(charging), "charged", PyBool_FromLong(charged), - "plugged_in", PyBool_FromLong(!on_battery), + "plugged_in", PyBool_FromLong(!on_battery), "has_battery", PyBool_FromLong(on_battery || !no_battery) ); // clang-format on diff --git a/src_c/transform.c b/src_c/transform.c index 59d11f6fc4..94ef27453c 100644 --- a/src_c/transform.c +++ b/src_c/transform.c @@ -2289,13 +2289,6 @@ HSL_to_RGB(float h, float s, float l, Uint8 *r, Uint8 *g, Uint8 *b) static void modify_hsl(SDL_Surface *surf, SDL_Surface *dst, float h, float s, float l) { - int x, y; - Uint8 r, g, b, a; - float s_h = 0, s_s = 0, s_l = 0; - SDL_PixelFormat *fmt = surf->format; - Uint8 *srcp8 = (Uint8 *)surf->pixels; - Uint8 *dstp8 = (Uint8 *)dst->pixels; - int surf_locked = 0; if (SDL_MUSTLOCK(surf)) { if (SDL_LockSurface(surf) == 0) { @@ -2309,18 +2302,30 @@ modify_hsl(SDL_Surface *surf, SDL_Surface *dst, float h, float s, float l) } } - if (fmt->BytesPerPixel == 4 || fmt->BytesPerPixel == 3) { - const int src_skip = surf->pitch - surf->w * fmt->BytesPerPixel; - const int dst_skip = dst->pitch - dst->w * fmt->BytesPerPixel; + int x, y; + Uint8 r, g, b, a; + float s_h = 0, s_s = 0, s_l = 0; + SDL_PixelFormat *fmt = surf->format; + Uint8 *srcp8 = (Uint8 *)surf->pixels; + Uint8 *dstp8 = (Uint8 *)dst->pixels; + + if (PG_FORMAT_BytesPerPixel(fmt) == 4 || + PG_FORMAT_BytesPerPixel(fmt) == 3) { + const int src_skip = + surf->pitch - surf->w * PG_FORMAT_BytesPerPixel(fmt); + const int dst_skip = + dst->pitch - dst->w * PG_FORMAT_BytesPerPixel(fmt); #if SDL_BYTEORDER == SDL_LIL_ENDIAN const int Ridx = fmt->Rshift >> 3; const int Gidx = fmt->Gshift >> 3; const int Bidx = fmt->Bshift >> 3; + const int Aidx = fmt->Ashift >> 3; #else const int Ridx = 3 - (fmt->Rshift >> 3); const int Gidx = 3 - (fmt->Gshift >> 3); const int Bidx = 3 - (fmt->Bshift >> 3); + const int Aidx = 3 - (fmt->Ashift >> 3); #endif int height = surf->h; @@ -2350,9 +2355,11 @@ modify_hsl(SDL_Surface *surf, SDL_Surface *dst, float h, float s, float l) dstp8[Ridx] = r; dstp8[Gidx] = g; dstp8[Bidx] = b; + if (fmt->Amask) + dstp8[Aidx] = srcp8[Aidx]; - srcp8 += fmt->BytesPerPixel; - dstp8 += fmt->BytesPerPixel; + srcp8 += PG_FORMAT_BytesPerPixel(fmt); + dstp8 += PG_FORMAT_BytesPerPixel(fmt); } srcp8 += src_skip; dstp8 += dst_skip; @@ -2459,7 +2466,8 @@ surf_hsl(PyObject *self, PyObject *args, PyObject *kwargs) if (src->format->Rmask != dst->format->Rmask || src->format->Gmask != dst->format->Gmask || src->format->Bmask != dst->format->Bmask || - src->format->BytesPerPixel != dst->format->BytesPerPixel) { + src->format->Amask != dst->format->Amask || + PG_SURF_BytesPerPixel(src) != PG_SURF_BytesPerPixel(dst)) { return RAISE(PyExc_ValueError, "Source and destination surfaces need the same format."); } diff --git a/src_c/window.c b/src_c/window.c index c3f3718386..f2217cd20c 100644 --- a/src_c/window.c +++ b/src_c/window.c @@ -3,6 +3,7 @@ #include "pygame.h" #include "pgcompat.h" +#include "pgopengl.h" #include "doc/sdl2_video_doc.h" #include "doc/window_doc.h" @@ -40,9 +41,7 @@ static PyObject * pg_display_resource(char *filename) { PyObject *imagemodule = NULL; - PyObject *load_basicfunc = NULL; PyObject *pkgdatamodule = NULL; - PyObject *resourcefunc = NULL; PyObject *fresult = NULL; PyObject *result = NULL; PyObject *name = NULL; @@ -51,19 +50,12 @@ pg_display_resource(char *filename) if (!pkgdatamodule) goto display_resource_end; - resourcefunc = PyObject_GetAttrString(pkgdatamodule, resourcefunc_name); - if (!resourcefunc) - goto display_resource_end; - imagemodule = PyImport_ImportModule(imagemodule_name); if (!imagemodule) goto display_resource_end; - load_basicfunc = PyObject_GetAttrString(imagemodule, load_basicfunc_name); - if (!load_basicfunc) - goto display_resource_end; - - fresult = PyObject_CallFunction(resourcefunc, "s", filename); + fresult = + PyObject_CallMethod(pkgdatamodule, resourcefunc_name, "s", filename); if (!fresult) goto display_resource_end; @@ -80,21 +72,21 @@ pg_display_resource(char *filename) PyErr_Clear(); } - result = PyObject_CallFunction(load_basicfunc, "O", fresult); + result = + PyObject_CallMethod(imagemodule, load_basicfunc_name, "O", fresult); if (!result) goto display_resource_end; display_resource_end: Py_XDECREF(pkgdatamodule); - Py_XDECREF(resourcefunc); Py_XDECREF(imagemodule); - Py_XDECREF(load_basicfunc); Py_XDECREF(fresult); Py_XDECREF(name); return result; } static PyTypeObject pgWindow_Type; +static GL_glViewport_Func p_glViewport = NULL; #define pgWindow_Check(x) \ (PyObject_IsInstance((x), (PyObject *)&pgWindow_Type)) @@ -211,7 +203,24 @@ window_flip(pgWindowObject *self, PyObject *_null) Py_RETURN_NONE; } -// Callback function for surface auto resize +/* Exception already set */ +static int +_window_opengl_set_viewport(SDL_Window *window, SDL_GLContext context, + int wnew, int hnew) +{ + if (SDL_GL_MakeCurrent(window, context) < 0) { + PyErr_SetString(pgExc_SDLError, SDL_GetError()); + return -1; + } + if (p_glViewport == NULL) { + PyErr_SetString(pgExc_SDLError, "glViewport function is unavailable"); + return -1; + } + p_glViewport(0, 0, wnew, hnew); + return 0; +} + +// Callback function for surface auto resize or OpenGL viewport update static int SDLCALL _resize_event_watch(void *userdata, SDL_Event *event) { @@ -232,6 +241,15 @@ _resize_event_watch(void *userdata, SDL_Event *event) return 0; } + if (event_window_pg->context != NULL) { + if (_window_opengl_set_viewport(event_window, event_window_pg->context, + event->window.data1, + event->window.data2) < 0) { + return PyErr_WarnEx(PyExc_RuntimeWarning, + "Failed to set OpenGL viewport", 0); + } + } + if (!event_window_pg->surf) return 0; @@ -285,6 +303,13 @@ window_focus(pgWindowObject *self, PyObject *args, PyObject *kwargs) Py_RETURN_NONE; } +static PyObject * +window_get_focused(pgWindowObject *self, void *v) +{ + uint32_t flags = SDL_GetWindowFlags(self->_win); + return PyBool_FromLong((flags & SDL_WINDOW_INPUT_FOCUS) != 0); +} + static PyObject * window_hide(pgWindowObject *self, PyObject *_null) { @@ -588,6 +613,13 @@ window_set_size(pgWindowObject *self, PyObject *arg, void *v) * relying on the event callback */ self->surf->surf = SDL_GetWindowSurface(self->_win); } + if (self->context != NULL) { + /* Update the OpenGL viewport immediately instead of relying on the + * event callback */ + if (_window_opengl_set_viewport(self->_win, self->context, w, h) < 0) { + return -1; + } + } return 0; } @@ -970,12 +1002,17 @@ window_init(pgWindowObject *self, PyObject *args, PyObject *kwargs) self->_is_borrowed = SDL_FALSE; self->surf = NULL; - if (SDL_GetWindowFlags(self->_win) & SDL_WINDOW_OPENGL) { + if (flags & SDL_WINDOW_OPENGL) { SDL_GLContext context = SDL_GL_CreateContext(self->_win); if (context == NULL) { PyErr_SetString(pgExc_SDLError, SDL_GetError()); return -1; } + /* As stated in the 'Remarks' of the docs + * (https://wiki.libsdl.org/SDL2/SDL_GL_GetProcAddress) on Windows + * SDL_GL_GetProcAddress is only valid after an OpenGL context has been + * created */ + p_glViewport = (GL_glViewport_Func)SDL_GL_GetProcAddress("glViewport"); self->context = context; } else { @@ -1036,6 +1073,32 @@ window_from_display_module(PyTypeObject *cls, PyObject *_null) return (PyObject *)self; } +static PyObject * +window_flash(pgWindowObject *self, PyObject *arg) +{ +#if SDL_VERSION_ATLEAST(2, 0, 16) + long operation = PyLong_AsLong(arg); + if (operation == -1 && PyErr_Occurred()) { + return RAISE(PyExc_TypeError, + "'operation' must be an integer. " + "Must correspond with FLASH_CANCEL, FLASH_BRIEFLY, or " + "FLASH_UNTIL_FOCUSED."); + } + + if (operation != SDL_FLASH_CANCEL && operation != SDL_FLASH_BRIEFLY && + operation != SDL_FLASH_UNTIL_FOCUSED) { + return RAISE(PyExc_ValueError, "Unsupported window flash operation."); + } + + if (SDL_FlashWindow(self->_win, operation) < 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + Py_RETURN_NONE; +#else + return RAISE(pgExc_SDLError, "'Window.flash' requires SDL 2.0.16+"); +#endif /* SDL_VERSION_ATLEAST(2, 0, 16) */ +} + PyObject * window_repr(pgWindowObject *self) { @@ -1101,6 +1164,7 @@ static PyMethodDef window_methods[] = { DOC_WINDOW_GETSURFACE}, {"from_display_module", (PyCFunction)window_from_display_module, METH_CLASS | METH_NOARGS, DOC_WINDOW_FROMDISPLAYMODULE}, + {"flash", (PyCFunction)window_flash, METH_O, DOC_WINDOW_FLASH}, {NULL, NULL, 0, NULL}}; static PyGetSetDef _window_getset[] = { @@ -1112,6 +1176,7 @@ static PyGetSetDef _window_getset[] = { DOC_WINDOW_MOUSEGRABBED, NULL}, {"keyboard_grabbed", (getter)window_get_keyboard_grabbed, NULL, DOC_WINDOW_KEYBOARDGRABBED, NULL}, + {"focused", (getter)window_get_focused, NULL, DOC_WINDOW_FOCUSED, NULL}, {"title", (getter)window_get_title, (setter)window_set_title, DOC_WINDOW_TITLE, NULL}, {"resizable", (getter)window_get_resizable, (setter)window_set_resizable, @@ -1141,6 +1206,7 @@ static PyTypeObject pgWindow_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.window.Window", .tp_basicsize = sizeof(pgWindowObject), .tp_dealloc = (destructor)window_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = DOC_WINDOW, .tp_methods = window_methods, .tp_init = (initproc)window_init, diff --git a/src_py/__briefcase/pygame_ce.py b/src_py/__briefcase/pygame_ce.py index ddd26ddb4a..9a58410587 100644 --- a/src_py/__briefcase/pygame_ce.py +++ b/src_py/__briefcase/pygame_ce.py @@ -24,7 +24,7 @@ def main(): # app's windows to its menu item. # # For association to work, any windows of the app must have WMCLASS property - # set to match the value set in app's desktop file. For pygame_ce, this is + # set to match the value set in app's desktop file. For pygame_ce, this is # set using the SDL_VIDEO_X11_WMCLASS environment variable. # Find the name of the module that was used to start the app diff --git a/src_py/__init__.py b/src_py/__init__.py index 9cc924420c..5f7e540547 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -30,16 +30,32 @@ # Choose Windows display driver if os.name == "nt": pygame_dir = os.path.split(__file__)[0] + dll_parents = {pygame_dir} + try: + # For editable support, add some more folders where DLLs are available. + # This block only executes under an editable install. In a "normal" + # install, the json file will not be installed at the supplied path. + with open( + os.path.join( + os.path.dirname(pygame_dir), "buildconfig", "win_dll_dirs.json" + ), + encoding="utf-8", + ) as f: + import json + + dll_parents.update(json.load(f)) + del json + except (FileNotFoundError, ValueError): + pass - # pypy does not find the dlls, so we add package folder to PATH. - os.environ["PATH"] = os.environ["PATH"] + ";" + pygame_dir - - # Windows store python does not find the dlls, so we run this - if sys.version_info > (3, 8): - os.add_dll_directory(pygame_dir) # only available in 3.8+ + for d in dll_parents: + # adding to PATH is the legacy way, os.add_dll_directory is the new + # and recommended method. For extra safety we do both + os.environ["PATH"] = os.environ["PATH"] + ";" + d + os.add_dll_directory(d) # cleanup namespace - del pygame_dir + del pygame_dir, dll_parents # when running under X11, always set the SDL window WM_CLASS to make the # window managers correctly match the pygame window. @@ -273,6 +289,7 @@ def PixelArray(surface): # pylint: disable=unused-argument try: import pygame.mixer + from pygame.mixer import Sound from pygame.mixer import Channel except (ImportError, OSError): mixer = MissingModule("mixer", urgent=0) @@ -315,6 +332,12 @@ def Window(title="pygame window", size=(640, 480), position=None, **kwargs): # _attribute_undefined("pygame.Window") +try: + import pygame.typing +except (ImportError, OSError): + typing = MissingModule("typing", urgent=0) + + # there's also a couple "internal" modules not needed # by users, but putting them here helps "dependency finder" # programs get everything they need (like py2exe) diff --git a/src_py/_sdl2/controller.py b/src_py/_sdl2/controller.py deleted file mode 100644 index 676f9fde27..0000000000 --- a/src_py/_sdl2/controller.py +++ /dev/null @@ -1 +0,0 @@ -from .controller_old import * # pylint: disable=wildcard-import diff --git a/src_py/_sdl2/meson.build b/src_py/_sdl2/meson.build index 261603fdcd..766b1161fe 100644 --- a/src_py/_sdl2/meson.build +++ b/src_py/_sdl2/meson.build @@ -1,7 +1,6 @@ # pure python sources python_sources = files( '__init__.py', - 'controller.py', 'window.py', ) py.install_sources(python_sources, subdir: pg / '_sdl2') diff --git a/src_py/cursors.py b/src_py/cursors.py index 7fa7f99315..342bce0313 100644 --- a/src_py/cursors.py +++ b/src_py/cursors.py @@ -106,12 +106,12 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) - def __copy__(self): + def copy(self): """Clone the current Cursor object. You can do the same thing by doing Cursor(Cursor).""" return self.__class__(self) - copy = __copy__ + __copy__ = copy def __hash__(self): return hash(tuple([self.type] + list(self.data))) diff --git a/src_py/meson.build b/src_py/meson.build index 561aebaadb..541c54cd69 100644 --- a/src_py/meson.build +++ b/src_py/meson.build @@ -18,6 +18,7 @@ python_sources = files( 'sprite.py', 'surfarray.py', 'sysfont.py', + 'typing.py', 'version.py', ) py.install_sources(python_sources, subdir: pg) diff --git a/src_py/pkgdata.py b/src_py/pkgdata.py index 767cbdf76c..8076900e3e 100644 --- a/src_py/pkgdata.py +++ b/src_py/pkgdata.py @@ -22,7 +22,28 @@ def getResource(identifier, pkgname=__name__): import os try: - from pkg_resources import resource_stream, resource_exists + if sys.version_info[:2] > (3, 8): + from importlib.resources import files + + def resource_exists(_package_or_requirement, _resource_name): + _package_or_requirement = _package_or_requirement.split(".")[0] + return files(_package_or_requirement).joinpath(_resource_name).is_file() + + def resource_stream(_package_or_requirement, _resource_name): + _package_or_requirement = _package_or_requirement.split(".")[0] + ref = files(_package_or_requirement).joinpath(_resource_name) + return ref.open('rb') + else: + from importlib import resources + + def resource_exists(_package_or_requirement, _resource_name): + _package_or_requirement = _package_or_requirement.split(".")[0] + return resources.is_resource(_package_or_requirement, _resource_name) # pylint: disable=deprecated-method + + def resource_stream(_package_or_requirement, _resource_name): + _package_or_requirement = _package_or_requirement.split(".")[0] + return resources.open_binary(_package_or_requirement, _resource_name) # pylint: disable=deprecated-method + except ImportError: def resource_exists(_package_or_requirement, _resource_name): @@ -33,7 +54,7 @@ def resource_exists(_package_or_requirement, _resource_name): """ return False - def resource_stream(_package_of_requirement, _resource_name): + def resource_stream(_package_or_requirement, _resource_name): """ A stub for when we fail to import this function. diff --git a/src_py/typing.py b/src_py/typing.py new file mode 100644 index 0000000000..852c5192fb --- /dev/null +++ b/src_py/typing.py @@ -0,0 +1,70 @@ +"""Set of common pygame type aliases for proper typehint annotations""" + +# NOTE: `src_py/typing.py` and `buildconfig/stubs/pygame/typing.pyi` must be duplicates. +# Use the command `python buildconfig/stubs/gen_stubs.py` to copy typing.py to typing.pyi + +__all__ = [ + "RectLike", + "SequenceLike", + "FileLike", + "ColorLike", + "Coordinate", + "IntCoordinate", +] + +import sys +from abc import abstractmethod +from typing import IO, Callable, Tuple, Union, TypeVar, Protocol + +if sys.version_info >= (3, 9): + from os import PathLike as _PathProtocol +else: + _AnyStr_co = TypeVar("_AnyStr_co", str, bytes, covariant=True) + + class _PathProtocol(Protocol[_AnyStr_co]): + @abstractmethod + def __fspath__(self) -> _AnyStr_co: ... + + +# For functions that take a file name +_PathLike = Union[str, bytes, _PathProtocol[str], _PathProtocol[bytes]] +# Most pygame functions that take a file argument should be able to handle a FileLike type +FileLike = Union[_PathLike, IO[bytes], IO[str]] + +_T_co = TypeVar("_T_co", covariant=True) + + +class SequenceLike(Protocol[_T_co]): + """ + Variant of the standard `Sequence` ABC that only requires `__getitem__` and `__len__`. + """ + + @abstractmethod + def __getitem__(self, index: int, /) -> _T_co: ... + @abstractmethod + def __len__(self) -> int: ... + + +# Modify typehints when it is possible to annotate sizes + +# Pygame handles float without errors in most cases where a coordinate is expected, +# usually rounding to int. Also, 'Union[int, float] == float' +Coordinate = SequenceLike[float] +# This is used where ints are strictly required +IntCoordinate = SequenceLike[int] + +ColorLike = Union[int, str, SequenceLike[int]] + + +class _HasRectAttribute(Protocol): + # An object that has a rect attribute that is either a rect, or a function + # that returns a rect conforms to the rect protocol + @property + def rect(self) -> Union["RectLike", Callable[[], "RectLike"]]: ... + + +RectLike = Union[SequenceLike[float], SequenceLike[Coordinate], _HasRectAttribute] + + +# cleanup namespace +del sys, abstractmethod, IO, Callable, Tuple, Union, TypeVar, Protocol diff --git a/test/README.rst b/test/README.rst index d1936616e0..b18872e5e7 100644 --- a/test/README.rst +++ b/test/README.rst @@ -2,7 +2,7 @@ Pygame Unit Tests ***************** The test runner for pygame was developed for these purposes: - + * Per process isolation of test modules * Ability to tag tests for exclusion (interactive tests etc) * Record timings of tests @@ -139,15 +139,14 @@ some convenience functions :: trunk_relative_path(pth) Will return a normalized relative path, relative to the test_module - + eg trunk_relative_path('examples\\data\\alien.jpg') will work on linux - + This is so the test module can be run from anywhere with working paths - eg ../test/color_test.py - + eg ../test/color_test.py + fixture_path(pth) Likewise but paths are relative to trunk\test\fixtures example_path(pth) Likewise but paths are relative to trunk\examples - diff --git a/test/color_test.py b/test/color_test.py index a34424e957..deca70957d 100644 --- a/test/color_test.py +++ b/test/color_test.py @@ -782,13 +782,12 @@ def test_from_cmy(self): self.assertEqual(expected_cmy, cmy) self.assertEqual(expected_cmy, cmy_tuple) - with self.assertWarns(DeprecationWarning): - self.assertEqual( - expected_cmy, pygame.Color.from_cmy(0.5, 0.5, 0.5, "lel", "foo") - ) - - with self.assertWarns(DeprecationWarning): - self.assertEqual(expected_cmy, pygame.Color.from_cmy((0.5, 0.5, 0.5, 0.5))) + self.assertRaises( + ValueError, lambda: pygame.Color.from_cmy(0.5, 0.5, 0.5, "lel", "foo") + ) + self.assertRaises( + ValueError, lambda: pygame.Color.from_cmy((0.5, 0.5, 0.5, 0.5)) + ) def test_from_hsva(self): hsva = pygame.Color.from_hsva(0, 100, 100, 100) @@ -799,15 +798,12 @@ def test_from_hsva(self): self.assertEqual(expected_hsva, hsva) self.assertEqual(expected_hsva, hsva_tuple) - with self.assertWarns(DeprecationWarning): - self.assertEqual( - expected_hsva, pygame.Color.from_hsva(0, 100, 100, 100, "lel", "foo") - ) - - with self.assertWarns(DeprecationWarning): - self.assertEqual( - expected_hsva, pygame.Color.from_hsva((0, 100, 100, 100, "lel")) - ) + self.assertRaises( + ValueError, lambda: pygame.Color.from_hsva(0, 100, 100, 100, "lel", "foo") + ) + self.assertRaises( + ValueError, lambda: pygame.Color.from_hsva((0, 100, 100, 100, "lel")) + ) def test_from_hsla(self): hsla = pygame.Color.from_hsla(0, 100, 100, 100) @@ -818,15 +814,12 @@ def test_from_hsla(self): self.assertEqual(expected_hsla, hsla) self.assertEqual(expected_hsla, hsla_tuple) - with self.assertWarns(DeprecationWarning): - self.assertEqual( - expected_hsla, pygame.Color.from_hsla(0, 100, 100, 100, "lel") - ) - - with self.assertWarns(DeprecationWarning): - self.assertEqual( - expected_hsla, pygame.Color.from_hsla((0, 100, 100, 100, "lel", "foo")) - ) + self.assertRaises( + ValueError, lambda: pygame.Color.from_hsla(0, 100, 100, 100, "lel") + ) + self.assertRaises( + ValueError, lambda: pygame.Color.from_hsla((0, 100, 100, 100, "lel", "foo")) + ) def test_from_i1i2i3(self): i1i2i3 = pygame.Color.from_i1i2i3(0, 0, 0) @@ -837,13 +830,10 @@ def test_from_i1i2i3(self): self.assertEqual(expected_i1i2i3, i1i2i3) self.assertEqual(expected_i1i2i3, i1i2i3_tuple) - with self.assertWarns(DeprecationWarning): - self.assertEqual( - expected_i1i2i3, pygame.Color.from_i1i2i3(0, 0, 0, "lel", "foo") - ) - - with self.assertWarns(DeprecationWarning): - self.assertEqual(expected_i1i2i3, pygame.Color.from_i1i2i3((0, 0, 0, 0))) + self.assertRaises( + ValueError, lambda: pygame.Color.from_i1i2i3(0, 0, 0, "lel", "foo") + ) + self.assertRaises(ValueError, lambda: pygame.Color.from_i1i2i3((0, 0, 0, 0))) def test_from_normalized(self): normal = pygame.Color.from_normalized(1, 1, 1, 1) @@ -854,16 +844,9 @@ def test_from_normalized(self): self.assertEqual(expected_normal, normal) self.assertEqual(expected_normal, normal_tuple) - with self.assertWarns(DeprecationWarning): - self.assertEqual( - expected_normal, pygame.Color.from_normalized(1, 1, 1, 1, "lel") - ) - - with self.assertWarns(DeprecationWarning): - self.assertEqual( - expected_normal, - pygame.Color.from_normalized((1, 1, 1, 1, "lel", "foo")), - ) + self.assertRaises( + ValueError, lambda: pygame.Color.from_normalized(1, 1, 1, 1, "lel") + ) def test_normalize(self): c = pygame.Color(204, 38, 194, 55) @@ -1062,8 +1045,10 @@ def test_normalized__sanity_testing_converted_should_equate_bar_rounding(self): def test_colorspaces_deprecated_large_sequence(self): c = pygame.Color("black") for space in ("hsla", "hsva", "i1i2i3", "cmy", "normalized"): - with self.assertWarns(DeprecationWarning): - setattr(c, space, (0, 0, 0, 0, "hehe 5th ignored member")) + with self.assertRaises(ValueError): + setattr( + c, space, (0, 0, 0, 0, "HAHAHAHAHAHA, don't ignore 5th member :)") + ) ################################################################################ diff --git a/test/controller_test.py b/test/controller_test.py index 5ffd2f69cf..000ee40a6d 100644 --- a/test/controller_test.py +++ b/test/controller_test.py @@ -4,7 +4,6 @@ from pygame.tests.test_utils import prompt, question -@unittest.skip("Module is under construction") class ControllerModuleTest(unittest.TestCase): def setUp(self): controller.init() @@ -34,15 +33,6 @@ def test_quit__multiple(self): def test_get_init(self): self.assertTrue(controller.get_init()) - def test_get_eventstate(self): - controller.set_eventstate(True) - self.assertTrue(controller.get_eventstate()) - - controller.set_eventstate(False) - self.assertFalse(controller.get_eventstate()) - - controller.set_eventstate(True) - def test_get_count(self): self.assertGreaterEqual(controller.get_count(), 0) @@ -53,17 +43,13 @@ def test_is_controller(self): self.assertIsInstance(c, controller.Controller) c.quit() else: - with self.assertRaises(pygame._sdl2.sdl2.error): + with self.assertRaises(pygame.error): c = controller.Controller(i) with self.assertRaises(TypeError): controller.is_controller("Test") - def test_name_forindex(self): - self.assertIsNone(controller.name_forindex(-1)) - -@unittest.skip("Module is under construction") class ControllerTypeTest(unittest.TestCase): def setUp(self): controller.init() @@ -143,7 +129,6 @@ def test_set_mapping(self): self.skipTest("No controller connected") -@unittest.skip("Module is under construction") class ControllerInteractiveTest(unittest.TestCase): __tags__ = ["interactive"] @@ -176,75 +161,6 @@ def test__get_count_interactive(self): self.assertTrue(ans) - def test_set_eventstate_on_interactive(self): - c = self._get_first_controller() - if not c: - self.skipTest("No controller connected") - - pygame.display.init() - pygame.font.init() - - screen = pygame.display.set_mode((400, 400)) - font = pygame.font.Font(None, 20) - running = True - - screen.fill((255, 255, 255)) - screen.blit( - font.render("Press button 'x' (on ps4) or 'a' (on xbox).", True, (0, 0, 0)), - (0, 0), - ) - pygame.display.update() - - controller.set_eventstate(True) - - while running: - for event in pygame.event.get(): - if event.type == pygame.QUIT: - running = False - - if event.type == pygame.CONTROLLERBUTTONDOWN: - running = False - - pygame.display.quit() - pygame.font.quit() - - def test_set_eventstate_off_interactive(self): - c = self._get_first_controller() - if not c: - self.skipTest("No controller connected") - - pygame.display.init() - pygame.font.init() - - screen = pygame.display.set_mode((400, 400)) - font = pygame.font.Font(None, 20) - running = True - - screen.fill((255, 255, 255)) - screen.blit( - font.render("Press button 'x' (on ps4) or 'a' (on xbox).", True, (0, 0, 0)), - (0, 0), - ) - pygame.display.update() - - controller.set_eventstate(False) - - while running: - for event in pygame.event.get(pygame.QUIT): - if event: - running = False - - if c.get_button(pygame.CONTROLLER_BUTTON_A): - if pygame.event.peek(pygame.CONTROLLERBUTTONDOWN): - pygame.display.quit() - pygame.font.quit() - self.fail() - else: - running = False - - pygame.display.quit() - pygame.font.quit() - def test_get_button_interactive(self): c = self._get_first_controller() if not c: diff --git a/test/display_test.py b/test/display_test.py index 5325df817b..5d5150fc91 100644 --- a/test/display_test.py +++ b/test/display_test.py @@ -151,7 +151,11 @@ def test_get_init(self): self.assertTrue(display.get_init()) def test_get_surface(self): - """Ensures get_surface gets the current display surface.""" + """Ensures get_surface gets the current display surface. + We can't guarantee small screen sizes get respected, so the + sizes of the surfaces returned from set_mode must be compared + to the size of the surface from get_surface. + """ lengths = (1, 5, 100) correct_depth = pygame.display.Info().bitsize @@ -169,7 +173,7 @@ def test_get_surface(self): self.assertEqual(surface, expected_surface) self.assertIsInstance(surface, pygame.Surface) - self.assertEqual(surface.get_size(), expected_size) + self.assertEqual(surface.get_size(), expected_surface.get_size()) self.assertEqual(surface.get_bitsize(), correct_depth) def test_get_surface__mode_not_set(self): @@ -714,6 +718,9 @@ def test_update_negative(self): r3 = pygame.Rect(-10, 0, -100, -100) pygame.display.update(r3) + # random point in rect + self.assertEqual(self.screen.get_at((50, 50)), (0, 255, 0)) + self.question("Is the screen green in (0, 0, 100, 100)?") def test_update_sequence(self): @@ -728,6 +735,47 @@ def test_update_sequence(self): pygame.display.update(rects) pygame.event.pump() # so mac updates + # random points in rect + for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)): + self.assertEqual(self.screen.get_at(random_point), (0, 255, 0)) + + self.question(f"Is the screen green in {rects}?") + + def test_update_dict_values(self): + """only updates the part of the display given by the rects.""" + self.screen.fill("green") + rects = { + "foo": pygame.Rect(0, 0, 100, 100), + "foobar": pygame.Rect(100, 0, 100, 100), + "hello": pygame.Rect(200, 0, 100, 100), + "hi": pygame.Rect(300, 300, 100, 100), + } + pygame.display.update(rects.values()) + pygame.event.pump() # so mac updates + + # random points in rect + for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)): + self.assertEqual(self.screen.get_at(random_point), (0, 255, 0)) + + self.question(f"Is the screen green in {' '.join(map(str, rects.values()))}?") + + def test_update_generator(self): + """only updates the part of the display given by the rects.""" + self.screen.fill("green") + rects = [ + pygame.Rect(0, 0, 100, 100), + pygame.Rect(100, 0, 100, 100), + pygame.Rect(200, 0, 100, 100), + pygame.Rect(300, 300, 100, 100), + ] + # make a generator from list, pass list with rect duplicates + pygame.display.update(i for i in (rects * 5)) + pygame.event.pump() # so mac updates + + # random points in rect + for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)): + self.assertEqual(self.screen.get_at(random_point), (0, 255, 0)) + self.question(f"Is the screen green in {rects}?") def test_update_none_skipped(self): @@ -743,6 +791,10 @@ def test_update_none_skipped(self): pygame.display.update(rects) pygame.event.pump() # so mac updates + # random points in rect + for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)): + self.assertEqual(self.screen.get_at(random_point), (0, 255, 0)) + self.question(f"Is the screen green in {rects}?") def test_update_none(self): @@ -757,6 +809,11 @@ def test_update_no_args(self): self.screen.fill("green") pygame.display.update() pygame.event.pump() # so mac updates + + # random points in rect + for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)): + self.assertEqual(self.screen.get_at(random_point), (0, 255, 0)) + self.question(f"Is the WHOLE screen green?") def test_update_args(self): @@ -766,6 +823,9 @@ def test_update_args(self): pygame.event.pump() # so mac updates self.question("Is the screen green in (100, 100, 100, 100)?") + # random points in rect + self.assertEqual(self.screen.get_at((150, 150)), (0, 255, 0)) + def test_update_incorrect_args(self): """raises a ValueError when inputs are wrong.""" @@ -1022,6 +1082,11 @@ def test_message_box_buttons(self): ) self.assertEqual(result, 0) + def test_message_box_parent_window_none(self): + pygame.display.message_box( + "Test", "Just close this messagebox", parent_window=None + ) + if __name__ == "__main__": unittest.main() diff --git a/test/draw_test.py b/test/draw_test.py index 3f00a9319e..a9e493e687 100644 --- a/test/draw_test.py +++ b/test/draw_test.py @@ -7176,6 +7176,51 @@ def test_color_validation(self): with self.assertRaises(TypeError): draw.polygon(surf, col, points, 0) + def test_aafunctions_depth_segfault(self): + """Ensure future commits don't break the segfault fixed by pull request + https://github.com/pygame-community/pygame-ce/pull/3008 + """ + + pixels_to_check = [(160, 102), (499, 300), (320, 258), (192, 252)] + pixel_colors_8 = [ + pygame.Color(0, 182, 0, 255), + pygame.Color(0, 0, 170, 255), + pygame.Color(255, 0, 0, 255), + pygame.Color(255, 0, 0, 255), + ] + + pixel_colors_16 = [ + pygame.Color(0, 178, 0, 255), + pygame.Color(0, 0, 213, 255), + pygame.Color(246, 0, 0, 255), + pygame.Color(222, 0, 0, 255), + ] + + pixel_colors_24_32 = [ + pygame.Color(0, 178, 0, 255), + pygame.Color(0, 0, 209, 255), + pygame.Color(247, 0, 0, 255), + pygame.Color(223, 0, 0, 255), + ] + + for depth in (8, 16, 24, 32): + # all values must stay so to reproduce the segfault + surf = pygame.Surface(size=(512, 512), flags=0, depth=depth) + + draw.aacircle(surf, pygame.Color("red"), (256, 256), 64) + draw.aaline(surf, pygame.Color("blue"), (256, 256), (500, 300)) + draw.aalines( + surf, pygame.Color("green"), False, [(100, 100), (100, 500), (160, 100)] + ) + + for i, pixel in enumerate(pixels_to_check): + if depth == 8: + self.assertEqual(surf.get_at(pixel), pixel_colors_8[i]) + elif depth == 16: + self.assertEqual(surf.get_at(pixel), pixel_colors_16[i]) + else: + self.assertEqual(surf.get_at(pixel), pixel_colors_24_32[i]) + ############################################################################### diff --git a/test/event_test.py b/test/event_test.py index c3a3d5e1c3..43e4783eee 100644 --- a/test/event_test.py +++ b/test/event_test.py @@ -1,4 +1,5 @@ import collections +import os import time import unittest @@ -818,12 +819,10 @@ def test_pump(self): """Ensure pump() functions properly.""" pygame.event.pump() - # @unittest.skipIf( - # os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER, - # 'requires the SDL_VIDEODRIVER to be a non-null value', - # ) - # Fails on SDL 2.0.18 - @unittest.skip("flaky test, and broken on 2.0.18 windows") + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER, + 'requires the SDL_VIDEODRIVER to be a non-null value', + ) def test_set_grab__and_get_symmetric(self): """Ensure event grabbing can be enabled and disabled. @@ -888,12 +887,10 @@ def test_get_blocked__event_sequence(self): self.assertTrue(blocked) - # @unittest.skipIf( - # os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER, - # 'requires the SDL_VIDEODRIVER to be a non-null value', - # ) - # Fails on SDL 2.0.18 - @unittest.skip("flaky test, and broken on 2.0.18 windows") + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER, + 'requires the SDL_VIDEODRIVER to be a non-null value', + ) def test_get_grab(self): """Ensure get_grab() works as expected""" surf = pygame.display.set_mode((10, 10)) diff --git a/test/font_test.py b/test/font_test.py index 20f8aaf153..f008da2e69 100644 --- a/test/font_test.py +++ b/test/font_test.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from re import T import sys import os import io diff --git a/test/freetype_test.py b/test/freetype_test.py index 200a9933bd..dccd7ab780 100644 --- a/test/freetype_test.py +++ b/test/freetype_test.py @@ -1767,6 +1767,10 @@ def test_get_init(self): # Test if get_init() gets the init state. self.assertTrue(ft.get_init()) + def test_was_init_deprecated(self): + with self.assertWarns(DeprecationWarning): + self.assertTrue(ft.was_init()) + def test_cache_size(self): DEFAULT_CACHE_SIZE = 64 self.assertEqual(ft.get_cache_size(), DEFAULT_CACHE_SIZE) diff --git a/test/geometry_test.py b/test/geometry_test.py index b69b316908..c951bb3fac 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -95,6 +95,13 @@ def testConstructionTUP_XYR_int(self): self.assertEqual(2.0, c.y) self.assertEqual(3.0, c.r) + def testConstruction_zero_radius(self): + c = Circle(1, 2, 0) + + self.assertEqual(1.0, c.x) + self.assertEqual(2.0, c.y) + self.assertEqual(0, c.r) + def test_x(self): """Ensures changing the x attribute moves the circle and does not change the circle's radius. @@ -183,7 +190,7 @@ def test_r__invalid_value(self): with self.assertRaises(TypeError): c.radius = value - for value in (-10.3234, -1, 0, 0.0): + for value in (-10.3234, -1): with self.assertRaises(ValueError): c.r = value with self.assertRaises(ValueError): @@ -293,6 +300,210 @@ def test_center_del(self): with self.assertRaises(AttributeError): del c.center + def test_top(self): + """Ensures changing the top attribute moves the circle and does not change the circle's radius.""" + expected_radius = 5.0 + + for pos in [ + (1, 0), + (0, 0), + (-1, 0), + (0, -1), + (1, 1), + (-1, -1), + (-1, 1), + (1, -1), + ]: + c = Circle((0, 0), expected_radius) + + c.top = pos + + self.assertEqual(pos[0], c.x) + self.assertEqual(pos[1], c.y - expected_radius) + + self.assertEqual(expected_radius, c.r) + + def test_top_update(self): + """Ensures changing the x or y value of the circle correctly updates the top.""" + expected_x = 10.3 + expected_y = 2.12 + expected_radius = 5.0 + c = Circle(1, 1, expected_radius) + + c.x = expected_x + self.assertEqual(c.top, (expected_x, c.y - expected_radius)) + + c.y = expected_y + self.assertEqual(c.top, (c.x, expected_y - expected_radius)) + + def test_top_invalid_value(self): + """Ensures the top attribute handles invalid values correctly.""" + c = Circle(0, 0, 1) + + for value in (None, [], "1", (1,), [1, 2, 3], True, False): + with self.assertRaises(TypeError): + c.top = value + + def test_top_del(self): + """Ensures the top attribute can't be deleted.""" + c = Circle(0, 0, 1) + + with self.assertRaises(AttributeError): + del c.top + + def test_left(self): + """Ensures changing the left attribute moves the circle and does not change the circle's radius.""" + expected_radius = 5.0 + + for pos in [ + (1, 0), + (0, 0), + (-1, 0), + (0, -1), + (1, 1), + (-1, -1), + (-1, 1), + (1, -1), + ]: + c = Circle((0, 0), expected_radius) + + c.left = pos + + self.assertEqual(pos[0], c.x - expected_radius) + self.assertEqual(pos[1], c.y) + + self.assertEqual(expected_radius, c.r) + + def test_left_update(self): + """Ensures changing the x or y value of the circle correctly updates the left.""" + expected_x = 10.3 + expected_y = 2.12 + expected_radius = 5.0 + c = Circle(1, 1, expected_radius) + + c.x = expected_x + self.assertEqual(c.left, (expected_x - expected_radius, c.y)) + + c.y = expected_y + self.assertEqual(c.left, (c.x - expected_radius, expected_y)) + + def test_left_invalid_value(self): + """Ensures the left attribute handles invalid values correctly.""" + c = Circle(0, 0, 1) + + for value in (None, [], "1", (1,), [1, 2, 3], True, False): + with self.assertRaises(TypeError): + c.left = value + + def test_left_del(self): + """Ensures the left attribute can't be deleted.""" + c = Circle(0, 0, 1) + + with self.assertRaises(AttributeError): + del c.left + + def test_right(self): + """Ensures changing the right attribute moves the circle and does not change the circle's radius.""" + expected_radius = 5.0 + + for pos in [ + (1, 0), + (0, 0), + (-1, 0), + (0, -1), + (1, 1), + (-1, -1), + (-1, 1), + (1, -1), + ]: + c = Circle((0, 0), expected_radius) + + c.right = pos + + self.assertEqual(pos[0], c.x + expected_radius) + self.assertEqual(pos[1], c.y) + + self.assertEqual(expected_radius, c.r) + + def test_right_update(self): + """Ensures changing the x or y value of the circle correctly updates the right.""" + expected_x = 10.3 + expected_y = 2.12 + expected_radius = 5.0 + c = Circle(1, 1, expected_radius) + + c.x = expected_x + self.assertEqual(c.right, (expected_x + expected_radius, c.y)) + + c.y = expected_y + self.assertEqual(c.right, (c.x + expected_radius, expected_y)) + + def test_right_invalid_value(self): + """Ensures the right attribute handles invalid values correctly.""" + c = Circle(0, 0, 1) + + for value in (None, [], "1", (1,), [1, 2, 3], True, False): + with self.assertRaises(TypeError): + c.right = value + + def test_right_del(self): + """Ensures the right attribute can't be deleted.""" + c = Circle(0, 0, 1) + + with self.assertRaises(AttributeError): + del c.right + + def test_bottom(self): + """Ensures changing the bottom attribute moves the circle and does not change the circle's radius.""" + expected_radius = 5.0 + + for pos in [ + (1, 0), + (0, 0), + (-1, 0), + (0, -1), + (1, 1), + (-1, -1), + (-1, 1), + (1, -1), + ]: + c = Circle((0, 0), expected_radius) + + c.bottom = pos + + self.assertEqual(pos[0], c.x) + self.assertEqual(pos[1], c.y + expected_radius) + + self.assertEqual(expected_radius, c.r) + + def test_bottom_update(self): + """Ensures changing the x or y value of the circle correctly updates the bottom.""" + expected_x = 10.3 + expected_y = 2.12 + expected_radius = 5.0 + c = Circle(1, 1, expected_radius) + + c.x = expected_x + self.assertEqual(c.bottom, (expected_x, c.y + expected_radius)) + + c.y = expected_y + self.assertEqual(c.bottom, (c.x, expected_y + expected_radius)) + + def test_bottom_invalid_value(self): + """Ensures the bottom attribute handles invalid values correctly.""" + c = Circle(0, 0, 1) + + for value in (None, [], "1", (1,), [1, 2, 3], True, False): + with self.assertRaises(TypeError): + c.bottom = value + + def test_bottom_del(self): + """Ensures the bottom attribute can't be deleted.""" + c = Circle(0, 0, 1) + + with self.assertRaises(AttributeError): + del c.bottom + def test_area(self): """Ensures the area is calculated correctly.""" c = Circle(0, 0, 1) @@ -317,7 +528,7 @@ def test_area_invalid_value(self): with self.assertRaises(TypeError): c.area = value - for value in (-10.3234, -1, 0, 0.0): + for value in (-10.3234, -1): with self.assertRaises(ValueError): c.area = value @@ -352,7 +563,7 @@ def test_circumference_invalid_value(self): with self.assertRaises(TypeError): c.circumference = value - for value in (-10.3234, -1, 0, 0.0): + for value in (-10.3234, -1): with self.assertRaises(ValueError): c.circumference = value @@ -390,7 +601,7 @@ def test_diameter_invalid_value(self): with self.assertRaises(TypeError): c.diameter = value - for value in (-10.3234, -1, 0, 0.0): + for value in (-10.3234, -1): with self.assertRaises(ValueError): c.diameter = value with self.assertRaises(ValueError): @@ -675,6 +886,102 @@ def test_collideswith(self): self.assertTrue(c.collideswith(p)) self.assertFalse(c.collideswith(p2)) + def test_collidelist_argtype(self): + """Tests if the function correctly handles incorrect types as parameters""" + + invalid_types = (None, "1", (1,), 1, (1, 2, 3), True, False) + + c = Circle(10, 10, 4) + + for value in invalid_types: + with self.assertRaises(TypeError): + c.collidelist(value) + + def test_collidelist_argnum(self): + """Tests if the function correctly handles incorrect number of parameters""" + c = Circle(10, 10, 4) + + circles = [(Circle(10, 10, 4), Circle(10, 10, 4))] + + with self.assertRaises(TypeError): + c.collidelist() + + with self.assertRaises(TypeError): + c.collidelist(circles, 1) + + def test_collidelist_return_type(self): + """Tests if the function returns the correct type""" + c = Circle(10, 10, 4) + + objects = [ + Circle(10, 10, 4), + Rect(10, 10, 4, 4), + ] + + for object in objects: + self.assertIsInstance(c.collidelist([object]), int) + + def test_collidelist(self): + """Ensures that the collidelist method works correctly""" + c = Circle(10, 10, 4) + + circles = [Circle(1000, 1000, 2), Circle(5, 10, 5), Circle(16, 10, 7)] + rects = [Rect(1000, 1000, 4, 4), Rect(1000, 200, 5, 5), Rect(5, 10, 7, 3)] + points = [(-10, -10), Vector2(1, 1), Vector2(10, -20), (10, 10)] + expected = [1, 2, 3] + + for objects, expected in zip([circles, rects, points], expected): + self.assertEqual(c.collidelist(objects), expected) + + def test_collidelistall_argtype(self): + """Tests if the function correctly handles incorrect types as parameters""" + + invalid_types = (None, "1", (1,), 1, (1, 2, 3), True, False) + + c = Circle(10, 10, 4) + + for value in invalid_types: + with self.assertRaises(TypeError): + c.collidelistall(value) + + def test_collidelistall_argnum(self): + """Tests if the function correctly handles incorrect number of parameters""" + c = Circle(10, 10, 4) + + circles = [(Circle(10, 10, 4), Circle(10, 10, 4))] + + with self.assertRaises(TypeError): + c.collidelistall() + + with self.assertRaises(TypeError): + c.collidelistall(circles, 1) + + def test_collidelistall_return_type(self): + """Tests if the function returns the correct type""" + c = Circle(10, 10, 4) + + objects = [ + Circle(10, 10, 4), + Rect(10, 10, 4, 4), + (10, 10), + Vector2(9, 9), + ] + + for object in objects: + self.assertIsInstance(c.collidelistall([object]), list) + + def test_collidelistall(self): + """Ensures that the collidelistall method works correctly""" + c = Circle(10, 10, 4) + + circles = [Circle(1000, 1000, 2), Circle(5, 10, 5), Circle(16, 10, 7)] + rects = [Rect(1000, 1000, 4, 4), Rect(1000, 200, 5, 5), Rect(5, 10, 7, 3)] + points = [Vector2(-10, -10), (8, 8), (10, -20), Vector2(10, 10)] + expected = [[1, 2], [2], [1, 3]] + + for objects, expected in zip([circles, rects, points], expected): + self.assertEqual(c.collidelistall(objects), expected) + def test_update(self): """Ensures that updating the circle position and dimension correctly updates position and dimension""" @@ -1288,6 +1595,60 @@ def test_contains_rect_frect(self): # on the edge self.assertTrue(c.contains(fr_edge)) + def test_intersect_argtype(self): + """Tests if the function correctly handles incorrect types as parameters""" + + invalid_types = (None, "1", (1,), 1, (1, 2, 3), True, False) + + c = Circle(10, 10, 4) + + for value in invalid_types: + with self.assertRaises(TypeError): + c.intersect(value) + + def test_intersect_argnum(self): + """Tests if the function correctly handles incorrect number of parameters""" + c = Circle(10, 10, 4) + + circles = [(Circle(10, 10, 4) for _ in range(100))] + for size in range(len(circles)): + with self.assertRaises(TypeError): + c.intersect(*circles[:size]) + + def test_intersect_return_type(self): + """Tests if the function returns the correct type""" + c = Circle(10, 10, 4) + + objects = [ + Circle(10, 10, 4), + Circle(10, 10, 400), + Circle(10, 10, 1), + Circle(15, 10, 10), + ] + + for object in objects: + self.assertIsInstance(c.intersect(object), list) + + def test_intersect(self): + # Circle + c = Circle(10, 10, 4) + c2 = Circle(10, 10, 2) + c3 = Circle(100, 100, 1) + c3_1 = Circle(10, 10, 400) + c4 = Circle(16, 10, 7) + c5 = Circle(18, 10, 4) + + for circle in [c, c2, c3, c3_1]: + self.assertEqual(c.intersect(circle), []) + + # intersecting circle + self.assertEqual( + [(10.25, 6.007820144332172), (10.25, 13.992179855667828)], c.intersect(c4) + ) + + # touching + self.assertEqual([(14.0, 10.0)], c.intersect(c5)) + if __name__ == "__main__": unittest.main() diff --git a/test/image_test.py b/test/image_test.py index e412c40508..8bff1dd037 100644 --- a/test/image_test.py +++ b/test/image_test.py @@ -506,9 +506,9 @@ def convertRGBAtoPremultiplied(surface_to_modify): for y in range(surface_to_modify.get_height()): color = surface_to_modify.get_at((x, y)) premult_color = ( - color[0] * color[3] / 255, - color[1] * color[3] / 255, - color[2] * color[3] / 255, + ((color[0] + 1) * color[3]) >> 8, + ((color[1] + 1) * color[3]) >> 8, + ((color[2] + 1) * color[3]) >> 8, color[3], ) surface_to_modify.set_at((x, y), premult_color) @@ -570,7 +570,7 @@ def test_frombytes__and_tobytes(self): import itertools - fmts = ("RGBA", "ARGB", "BGRA") + fmts = ("RGBA", "ARGB", "BGRA", "ABGR") fmt_permutations = itertools.permutations(fmts, 2) fmt_combinations = itertools.combinations(fmts, 2) diff --git a/test/key_test.py b/test/key_test.py index dc7a6276d8..758010b420 100644 --- a/test/key_test.py +++ b/test/key_test.py @@ -1,5 +1,6 @@ import os import time +import platform import unittest import pygame @@ -186,9 +187,10 @@ def test_import(self): """does it import?""" import pygame.key - # fixme: test_get_focused failing systematically in some linux - # fixme: test_get_focused failing on SDL 2.0.18 on Windows - @unittest.skip("flaky test, and broken on 2.0.18 windows") + @unittest.skipIf( + not ("Windows" in platform.system() or "Darwin" in platform.system()), + "Not windows or macOS - we skip.", + ) def test_get_focused(self): # This test fails in SDL2 in some linux # This test was skipped in SDL1. diff --git a/test/math_test.py b/test/math_test.py index bcc2c73830..d8690ff502 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -1347,6 +1347,22 @@ def supermariobrosiscool(self): self.assertEqual(type(other / 3), TestVector) self.assertEqual(type(other.elementwise() ** 3), TestVector) + def test_del_x(self): + """Verify that the correct error message gets spit out when trying to delete x""" + with self.assertRaises(TypeError) as ctx: + del Vector2().x + + exception = ctx.exception + self.assertEqual(str(exception), "Cannot delete the x attribute") + + def test_del_y(self): + """Verify that the correct error message gets spit out when trying to delete y""" + with self.assertRaises(TypeError) as ctx: + del Vector2().y + + exception = ctx.exception + self.assertEqual(str(exception), "Cannot delete the y attribute") + class Vector3TypeTest(unittest.TestCase): def setUp(self): @@ -3072,6 +3088,30 @@ def test_move_towards_errors(self): self.assertRaises(TypeError, origin.move_towards, "c", 3) self.assertRaises(TypeError, origin.move_towards_ip, "d", 3) + def test_del_x(self): + """Verify that the correct error message gets spit out when trying to delete x""" + with self.assertRaises(TypeError) as ctx: + del Vector3().x + + exception = ctx.exception + self.assertEqual(str(exception), "Cannot delete the x attribute") + + def test_del_y(self): + """Verify that the correct error message gets spit out when trying to delete y""" + with self.assertRaises(TypeError) as ctx: + del Vector3().y + + exception = ctx.exception + self.assertEqual(str(exception), "Cannot delete the y attribute") + + def test_del_z(self): + """Verify that the correct error message gets spit out when trying to delete z""" + with self.assertRaises(TypeError) as ctx: + del Vector3().z + + exception = ctx.exception + self.assertEqual(str(exception), "Cannot delete the z attribute") + if __name__ == "__main__": unittest.main() diff --git a/test/mixer_music_test.py b/test/mixer_music_test.py index d777fbab02..f9cb722b4b 100644 --- a/test/mixer_music_test.py +++ b/test/mixer_music_test.py @@ -40,8 +40,10 @@ def test_load_flac(self): "|tags:music|" self.music_load("house_lo.flac") + # system installed SDL_mixer may not support wavpack @unittest.skipIf( - pygame.mixer.get_sdl_mixer_version() < (2, 8, 0), + pygame.mixer.get_sdl_mixer_version() < (2, 8, 0) + or "PG_DEPS_FROM_SYSTEM" in os.environ, "WavPack support added in SDL_mixer 2.8.0", ) def test_load_wv(self): @@ -85,7 +87,10 @@ def test_load_object(self): if pygame.mixer.get_sdl_mixer_version() >= (2, 6, 0): filenames.append("house_lo.mp3") - if pygame.mixer.get_sdl_mixer_version() >= (2, 8, 0): + if ( + pygame.mixer.get_sdl_mixer_version() >= (2, 8, 0) + and "PG_DEPS_FROM_SYSTEM" not in os.environ + ): filenames.append("house_lo.wv") if pygame.mixer.get_soundfont() is not None: @@ -113,7 +118,10 @@ def test_object_namehint(self): if pygame.mixer.get_sdl_mixer_version() >= (2, 6, 0): filenames.append("house_lo.mp3") - if pygame.mixer.get_sdl_mixer_version() >= (2, 8, 0): + if ( + pygame.mixer.get_sdl_mixer_version() >= (2, 8, 0) + and "PG_DEPS_FROM_SYSTEM" not in os.environ + ): filenames.append("house_lo.wv") if pygame.mixer.get_soundfont() is not None: @@ -208,8 +216,10 @@ def test_queue_flac(self): filename = example_path(os.path.join("data", "house_lo.flac")) pygame.mixer.music.queue(filename) + # system installed SDL_mixer may not support wavpack @unittest.skipIf( - pygame.mixer.get_sdl_mixer_version() < (2, 8, 0), + pygame.mixer.get_sdl_mixer_version() < (2, 8, 0) + or "PG_DEPS_FROM_SYSTEM" in os.environ, "WavPack support added in SDL_mixer 2.8.0", ) def test_queue_wv(self): diff --git a/test/mixer_test.py b/test/mixer_test.py index 9b3551b834..7c1c4794b6 100644 --- a/test/mixer_test.py +++ b/test/mixer_test.py @@ -711,6 +711,46 @@ def test_get_sdl_mixer_version__linked_equals_compiled(self): self.assertTupleEqual(linked_version, compiled_version) + def test_snd_copy(self): + mixer.init() + + filenames = [ + "house_lo.ogg", + "house_lo.wav", + "house_lo.flac", + "house_lo.opus", + "surfonasinewave.xm", + ] + if pygame.mixer.get_sdl_mixer_version() >= (2, 6, 0): + filenames.append("house_lo.mp3") + + for f in filenames: + filename = example_path(os.path.join("data", f)) + try: + sound = mixer.Sound(file=filename) + except pygame.error as e: + continue + sound_copy = sound.copy() + self.assertEqual(sound.get_length(), sound_copy.get_length()) + self.assertEqual(sound.get_num_channels(), sound_copy.get_num_channels()) + self.assertEqual(sound.get_volume(), sound_copy.get_volume()) + self.assertEqual(sound.get_raw(), sound_copy.get_raw()) + + sound.set_volume(0.5) + self.assertNotEqual(sound.get_volume(), sound_copy.get_volume()) + + del sound + + # Test on the copy for playable sounds + channel = sound_copy.play() + if channel is None: + continue + self.assertTrue(channel.get_busy()) + sound_copy.stop() + self.assertFalse(channel.get_busy()) + sound_copy.play() + self.assertEqual(sound_copy.get_num_channels(), 1) + ############################## CHANNEL CLASS TESTS ############################# diff --git a/test/mouse_test.py b/test/mouse_test.py index 3373bd2853..019c8a7da6 100644 --- a/test/mouse_test.py +++ b/test/mouse_test.py @@ -285,6 +285,17 @@ def test_get_pressed(self): for value in buttons_pressed: self.assertIsInstance(value, bool) + desktop_pressed1 = pygame.mouse.get_pressed(desktop=True) + desktop_pressed2 = pygame.mouse.get_pressed(5, desktop=True) + desktop_pressed3 = pygame.mouse.get_pressed(3, True) + self.assertEqual(len(desktop_pressed1), 3) + self.assertEqual(len(desktop_pressed2), 5) + self.assertEqual(len(desktop_pressed3), 3) + for desktop_pressed in [desktop_pressed1, desktop_pressed2, desktop_pressed3]: + self.assertIsInstance(desktop_pressed, tuple) + for value in desktop_pressed: + self.assertIsInstance(value, bool) + with self.assertRaises(ValueError): pygame.mouse.get_pressed(4) @@ -315,6 +326,16 @@ def test_get_pos(self): for value in pos: self.assertIsInstance(value, int) + desktop_pos1 = pygame.mouse.get_pos(True) + desktop_pos2 = pygame.mouse.get_pos(desktop=True) + self.assertEqual(desktop_pos1, desktop_pos2) + + for desktop_pos in [desktop_pos1, desktop_pos2]: + self.assertIsInstance(desktop_pos, tuple) + self.assertEqual(len(desktop_pos), expected_length) + for value in desktop_pos: + self.assertIsInstance(value, int) + def test_set_pos__invalid_pos(self): """Ensures set_pos handles invalid positions correctly.""" for invalid_pos in ((1,), [1, 2, 3], 1, "1", (1, "1"), []): diff --git a/test/pixelcopy_test.py b/test/pixelcopy_test.py index b0541952b9..662918d120 100644 --- a/test/pixelcopy_test.py +++ b/test/pixelcopy_test.py @@ -534,7 +534,7 @@ def test_surface_to_array_3d(self): def test_map_array(self): try: - from numpy import array, zeros, uint8, int32, alltrue + from numpy import array, zeros, uint8, int32, all as np_all except ImportError: return @@ -545,7 +545,7 @@ def test_map_array(self): target = zeros((5, 7), int32) map_array(target, color, surf) - self.assertTrue(alltrue(target == surf.map_rgb(color))) + self.assertTrue(np_all(target == surf.map_rgb(color))) # array column stripes stripe = array([[2, 5, 7], [11, 19, 23], [37, 53, 101]], uint8) @@ -553,7 +553,7 @@ def test_map_array(self): map_array(target, stripe, surf) target_stripe = array([surf.map_rgb(c) for c in stripe], int32) - self.assertTrue(alltrue(target == target_stripe)) + self.assertTrue(np_all(target == target_stripe)) # array row stripes stripe = array( @@ -563,7 +563,7 @@ def test_map_array(self): map_array(target, stripe, surf) target_stripe = array([[surf.map_rgb(c)] for c in stripe[:, 0]], int32) - self.assertTrue(alltrue(target == target_stripe)) + self.assertTrue(np_all(target == target_stripe)) # mismatched shape w = 4 diff --git a/test/rect_test.py b/test/rect_test.py index 307feb8b29..d206abd1fa 100644 --- a/test/rect_test.py +++ b/test/rect_test.py @@ -772,7 +772,8 @@ def test_collidepoint(self): ) def test_inflate__larger(self): - """The inflate method inflates around the center of the rectangle""" + """Ensures inflating a rect keeps its center the same + and grows dimensions by correct values.""" r = Rect(2, 4, 6, 8) r2 = r.inflate(4, 6) @@ -785,7 +786,8 @@ def test_inflate__larger(self): self.assertEqual(r.height + 6, r2.height) def test_inflate__smaller(self): - """The inflate method inflates around the center of the rectangle""" + """Ensures deflating a rect keeps its center the same + and shrinks dimensions by correct values.""" r = Rect(2, 4, 6, 8) r2 = r.inflate(-4, -6) @@ -798,21 +800,23 @@ def test_inflate__smaller(self): self.assertEqual(r.height - 6, r2.height) def test_inflate_ip__larger(self): - """The inflate_ip method inflates around the center of the rectangle""" + """Ensures inflating a rect in place keeps its center the same + and grows dimensions by correct values.""" r = Rect(2, 4, 6, 8) r2 = Rect(r) - r2.inflate_ip(-4, -6) + r2.inflate_ip(4, 6) self.assertEqual(r.center, r2.center) - self.assertEqual(r.left + 2, r2.left) - self.assertEqual(r.top + 3, r2.top) - self.assertEqual(r.right - 2, r2.right) - self.assertEqual(r.bottom - 3, r2.bottom) - self.assertEqual(r.width - 4, r2.width) - self.assertEqual(r.height - 6, r2.height) + self.assertEqual(r.left - 2, r2.left) + self.assertEqual(r.top - 3, r2.top) + self.assertEqual(r.right + 2, r2.right) + self.assertEqual(r.bottom + 3, r2.bottom) + self.assertEqual(r.width + 4, r2.width) + self.assertEqual(r.height + 6, r2.height) def test_inflate_ip__smaller(self): - """The inflate method inflates around the center of the rectangle""" + """Ensures deflating a rect in place keeps its center the same + and shrinks dimensions by correct values.""" r = Rect(2, 4, 6, 8) r2 = Rect(r) r2.inflate_ip(-4, -6) @@ -2877,6 +2881,9 @@ def testRepr(self): repr(rect), "FRect(12.345679, 34.000000, 56.000000, 78.000000)" ) + # test that a large rect repr doesn't error + self.assertIsInstance(repr(Rect(-2e38, -2e38, -2e38, -2e38)), str) + def test_clipline__equal_endpoints_no_overlap(self): """Ensures clipline handles lines with both endpoints the same. diff --git a/test/sndarray_test.py b/test/sndarray_test.py index 5b624caf41..a99171fc5a 100644 --- a/test/sndarray_test.py +++ b/test/sndarray_test.py @@ -1,6 +1,6 @@ import unittest -from numpy import int8, int16, uint8, uint16, float32, array, alltrue +from numpy import int8, int16, uint8, uint16, float32, array, all as np_all import pygame import pygame.sndarray @@ -28,7 +28,7 @@ def check_array(size, channels, test_data): arr = pygame.sndarray.array(snd) self._assert_compatible(arr, size) self.assertTrue( - alltrue(arr == srcarr), + np_all(arr == srcarr), "size: %i\n%s\n%s" % (size, arr, test_data), ) finally: @@ -41,7 +41,7 @@ def check_array(size, channels, test_data): 16, 2, [[0, 0xFFFF], [0xFFFF, 0], [0x00FF, 0xFF00], [0x0F0F, 0xF0F0]] ) check_array(-8, 1, [0, -0x80, 0x7F, 0x64]) - check_array(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0xFF, 0]]) + check_array(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0x7F, 0]]) check_array(-16, 1, [0, 0x7FFF, -0x7FFF, -1]) check_array(-16, 2, [[0, -0x7FFF], [-0x7FFF, 0], [0x7FFF, 0], [0, 0x7FFF]]) @@ -71,7 +71,7 @@ def check_sound(size, channels, test_data): snd = pygame.sndarray.make_sound(srcarr) arr = pygame.sndarray.samples(snd) self.assertTrue( - alltrue(arr == srcarr), + np_all(arr == srcarr), "size: %i\n%s\n%s" % (size, arr, test_data), ) finally: @@ -84,7 +84,7 @@ def check_sound(size, channels, test_data): 16, 2, [[0, 0xFFFF], [0xFFFF, 0], [0x00FF, 0xFF00], [0x0F0F, 0xF0F0]] ) check_sound(-8, 1, [0, -0x80, 0x7F, 0x64]) - check_sound(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0xFF, 0]]) + check_sound(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0x7F, 0]]) check_sound(-16, 1, [0, 0x7FFF, -0x7FFF, -1]) check_sound(-16, 2, [[0, -0x7FFF], [-0x7FFF, 0], [0x7FFF, 0], [0, 0x7FFF]]) check_sound(32, 2, [[0.0, -1.0], [-1.0, 0], [1.0, 0], [0, 1.0]]) @@ -110,7 +110,7 @@ def check_sample(size, channels, test_data): samples[...] = test_data arr = pygame.sndarray.array(snd) self.assertTrue( - alltrue(samples == arr), + np_all(samples == arr), "size: %i\n%s\n%s" % (size, arr, test_data), ) finally: @@ -123,7 +123,7 @@ def check_sample(size, channels, test_data): 16, 2, [[0, 0xFFFF], [0xFFFF, 0], [0x00FF, 0xFF00], [0x0F0F, 0xF0F0]] ) check_sample(-8, 1, [0, -0x80, 0x7F, 0x64]) - check_sample(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0xFF, 0]]) + check_sample(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0x7F, 0]]) check_sample(-16, 1, [0, 0x7FFF, -0x7FFF, -1]) check_sample(-16, 2, [[0, -0x7FFF], [-0x7FFF, 0], [0x7FFF, 0], [0, 0x7FFF]]) check_sample(32, 2, [[0.0, -1.0], [-1.0, 0], [1.0, 0], [0, 1.0]]) diff --git a/test/surface_test.py b/test/surface_test.py index d0c01c28a5..ca60ccb8e0 100644 --- a/test/surface_test.py +++ b/test/surface_test.py @@ -1203,6 +1203,15 @@ def test_blit__SCALPHA32_TO_OPAQUE32(self): # +3 gets the "alpha channel" in this pixel format assert surf1_bytes[i * 4 + 3] == 0 + def test_blit_default_dest(self): + surf1 = pygame.Surface((20, 20)) + surf1.fill("black") + surf2 = pygame.Surface((10, 10)) + surf2.fill("red") + surf1.blit(surf2) + self.assertEqual(surf1.get_at((0, 0)), pygame.Color("red")) + self.assertEqual(surf1.get_at((10, 10)), pygame.Color("black")) + class GeneralSurfaceTests(unittest.TestCase): @unittest.skipIf( @@ -4029,6 +4038,81 @@ def test_surface_premul_alpha(self): ), ) + def test_surface_premul_alpha_ip(self): + """Ensure that .premul_alpha_ip() works correctly""" + + # basic functionality at valid bit depths - 32, 16 & 8 + s1 = pygame.Surface((100, 100), pygame.SRCALPHA, 32) + s1.fill(pygame.Color(255, 255, 255, 100)) + s1.premul_alpha_ip() + s1_alpha = s1 + self.assertEqual(s1_alpha.get_at((50, 50)), pygame.Color(100, 100, 100, 100)) + + # 16-bit color has less precision + s2 = pygame.Surface((100, 100), pygame.SRCALPHA, 16) + s2.fill( + pygame.Color( + int(15 / 15 * 255), + int(15 / 15 * 255), + int(15 / 15 * 255), + int(10 / 15 * 255), + ) + ) + s2.premul_alpha_ip() + s2_alpha = s2 + self.assertEqual( + s2_alpha.get_at((50, 50)), + pygame.Color( + int(10 / 15 * 255), + int(10 / 15 * 255), + int(10 / 15 * 255), + int(10 / 15 * 255), + ), + ) + + # invalid surface - we need alpha to pre-multiply + invalid_surf = pygame.Surface((100, 100), 0, 32) + invalid_surf.fill(pygame.Color(255, 255, 255, 100)) + with self.assertRaises(ValueError): + invalid_surf.premul_alpha_ip() + + # churn a bunch of values + test_colors = [ + (200, 30, 74), + (76, 83, 24), + (184, 21, 6), + (74, 4, 74), + (76, 83, 24), + (184, 21, 234), + (160, 30, 74), + (96, 147, 204), + (198, 201, 60), + (132, 89, 74), + (245, 9, 224), + (184, 112, 6), + ] + + for r, g, b in test_colors: + for a in range(255): + with self.subTest(r=r, g=g, b=b, a=a): + surf = pygame.Surface((10, 10), pygame.SRCALPHA, 32) + surf.fill(pygame.Color(r, g, b, a)) + surf.premul_alpha_ip() + self.assertEqual( + surf.get_at((5, 5)), + Color( + ((r + 1) * a) >> 8, + ((g + 1) * a) >> 8, + ((b + 1) * a) >> 8, + a, + ), + ) + + for size in [(0, 0), (1, 0), (0, 1), (10, 10)]: + surf = pygame.Surface(size, pygame.SRCALPHA, 32) + surf.fill((32, 44, 4, 123)) + self.assertIs(surf, surf.premul_alpha_ip()) + class SurfaceSelfBlitTest(unittest.TestCase): """Blit to self tests. @@ -4268,6 +4352,38 @@ def test_fill(self): for y in range(5, 480, 10): self.assertEqual(screen.get_at((10, y)), screen.get_at((330, 480 - y))) + def test_fill_negative_rectpos(self): + """Regression test for https://github.com/pygame-community/pygame-ce/issues/2938""" + screen = pygame.display.set_mode((640, 480)) + other_surface = screen.copy() + + negative_x_rect = pygame.Rect(-10, 10, 20, 20) + negative_x_rect_drawn = pygame.Rect(0, 10, 10, 20) + negative_y_rect = pygame.Rect(100, -10, 20, 20) + negative_y_rect_drawn = pygame.Rect(100, 0, 20, 10) + negative_both = pygame.Rect(-10, -10, 20, 20) + negative_both_drawn = pygame.Rect(0, 0, 10, 10) + + red_rect_1 = screen.fill("red", negative_x_rect) + blue_rect_1 = screen.fill("blue", negative_y_rect) + green_rect_1 = screen.fill("green", negative_both) + + red_rect_2 = other_surface.fill("red", negative_x_rect_drawn) + blue_rect_2 = other_surface.fill("blue", negative_y_rect_drawn) + green_rect_2 = other_surface.fill("green", negative_both_drawn) + + self.assertEqual(red_rect_1, red_rect_2) + self.assertEqual(blue_rect_1, blue_rect_2) + self.assertEqual(green_rect_1, green_rect_2) + + # verify both have the same pixels + width, height = screen.get_size() + for row in range(height): + for col in range(width): + self.assertEqual( + screen.get_at((col, row)), other_surface.get_at((col, row)) + ) + if __name__ == "__main__": unittest.main() diff --git a/test/surfarray_test.py b/test/surfarray_test.py index b27dc0b460..2288f9b862 100644 --- a/test/surfarray_test.py +++ b/test/surfarray_test.py @@ -9,9 +9,10 @@ zeros, float32, float64, - alltrue, + all as np_all, rint, arange, + __version__ as np_version, ) import pygame @@ -235,7 +236,7 @@ def test_array_alpha(self): ), ) else: - self.assertTrue(alltrue(arr == 255)) + self.assertTrue(np_all(arr == 255)) # No per-pixel alpha when blanket alpha is None. for surf in targets: @@ -243,7 +244,7 @@ def test_array_alpha(self): surf.set_alpha(None) arr = pygame.surfarray.array_alpha(surf) self.assertTrue( - alltrue(arr == 255), + np_all(arr == 255), "All alpha values should be 255 when" " surf.set_alpha(None) has been set." " bitsize: %i, flags: %i" % (surf.get_bitsize(), surf.get_flags()), @@ -257,12 +258,12 @@ def test_array_alpha(self): arr = pygame.surfarray.array_alpha(surf) if surf.get_masks()[3]: self.assertFalse( - alltrue(arr == 255), + np_all(arr == 255), "bitsize: %i, flags: %i" % (surf.get_bitsize(), surf.get_flags()), ) else: self.assertTrue( - alltrue(arr == 255), + np_all(arr == 255), "bitsize: %i, flags: %i" % (surf.get_bitsize(), surf.get_flags()), ) surf.set_alpha(blanket_alpha) @@ -290,7 +291,7 @@ def test_array_colorkey(self): p = [surf.unmap_rgb(surf.map_rgb(c)) for c in p] surf.set_colorkey(None) arr = pygame.surfarray.array_colorkey(surf) - self.assertTrue(alltrue(arr == 255)) + self.assertTrue(np_all(arr == 255)) for i in range(1, len(palette)): surf.set_colorkey(p[i]) @@ -560,6 +561,11 @@ def test_map_array(self): self._make_array2d(uint8), ) + @unittest.skipIf( + int(np_version.split(".")[0]) >= 2, + "This test fails due to a change in numpy 2.0.0, and a 'proper fix' " + "requires an API/behaviour change", + ) def test_pixels2d(self): sources = [ self._make_surface(8), diff --git a/test/test_utils/__init__.py b/test/test_utils/__init__.py index 16ffb105b8..040d7b8876 100644 --- a/test/test_utils/__init__.py +++ b/test/test_utils/__init__.py @@ -3,8 +3,6 @@ import sys import tempfile -is_pygame_pkg = __name__.startswith("pygame.tests.") - ############################################################################### @@ -22,11 +20,8 @@ def geterror(): ############################################################################### this_dir = os.path.dirname(os.path.abspath(__file__)) -trunk_dir = os.path.split(os.path.split(this_dir)[0])[0] -if is_pygame_pkg: - test_module = "tests" -else: - test_module = "test" +this_dir_parent = os.path.split(this_dir)[0] +trunk_dir = os.path.split(this_dir_parent)[0] def trunk_relative_path(relative): @@ -34,7 +29,7 @@ def trunk_relative_path(relative): def fixture_path(path): - return trunk_relative_path(os.path.join(test_module, "fixtures", path)) + return os.path.join(this_dir_parent, "fixtures", path) def example_path(path): diff --git a/test/transform_test.py b/test/transform_test.py index 2756487288..5c96ca29fb 100644 --- a/test/transform_test.py +++ b/test/transform_test.py @@ -450,6 +450,50 @@ def test_hsl(self): for v1, v2 in zip(expected_rgb, actual_rgb): self.assertAlmostEqual(v1, v2, delta=1) + surf = pygame.Surface((1, 1), flags=pygame.SRCALPHA) + dest = pygame.Surface((1, 1), flags=pygame.SRCALPHA) + + alpha_colors = [ + (0, 0, 0, 0), + (255, 255, 255, 255), + (255, 0, 0, 255), + (0, 255, 0, 255), + (0, 0, 255, 255), + (127, 127, 127, 127), + (11, 23, 34, 255), + (1, 0, 1, 255), + ] + + for color in alpha_colors: + c_a = color[3] + for h in range(0, 360, 10): + for s in range(0, 100, 10): + for l in range(0, 100, 10): + surf.fill(color) + ns = s / 100.0 + nl = l / 100.0 + + hsl_surf = pygame.transform.hsl(surf, h, ns, nl) + pygame.transform.hsl(surf, h, ns, nl, dest_surface=dest) + + nh = h / 360.0 + modified_hsl = modify_hsl(*rgb_to_hsl(color[:3]), nh, ns, nl) + expected_rgb = hsl_to_rgb(modified_hsl) + + # without destination + actual_color = hsl_surf.get_at((0, 0)) + actual_rgb = actual_color.rgb + for v1, v2 in zip(expected_rgb, actual_rgb): + self.assertAlmostEqual(v1, v2, delta=1) + self.assertEqual(c_a, actual_color.a) + + # with destination + actual_color = dest.get_at((0, 0)) + actual_rgb = actual_color.rgb + for v1, v2 in zip(expected_rgb, actual_rgb): + self.assertAlmostEqual(v1, v2, delta=1) + self.assertEqual(c_a, actual_color.a) + def test_grayscale_simd_assumptions(self): # The grayscale SIMD algorithm relies on the destination surface pitch # being exactly width * 4 (4 bytes per pixel), for maximum speed. diff --git a/test/window_test.py b/test/window_test.py index 5ac7f6aa5b..fd7828393f 100644 --- a/test/window_test.py +++ b/test/window_test.py @@ -1,6 +1,7 @@ import unittest import pygame import os +import platform from pygame import Window from pygame.version import SDL @@ -8,6 +9,9 @@ pygame.init() +IS_PYPY = "PyPy" == platform.python_implementation() + + class WindowTypeTest(unittest.TestCase): DEFAULT_TITLE = "pygame window" @@ -325,6 +329,7 @@ def test_from_display_module(self): pygame.display.quit() pygame.init() + @unittest.skipIf(IS_PYPY, "for some reason this test is flaky on pypy") def test_window_surface(self): win = Window(size=(640, 480)) surf = win.get_surface() @@ -342,6 +347,7 @@ def test_window_surface(self): win.destroy() self.assertRaises(pygame.error, lambda: surf.fill((0, 0, 0))) + @unittest.skipIf(IS_PYPY, "for some reason this test is flaky on pypy") def test_window_surface_with_display_module(self): # get_surface() should raise an error if the set_mode() is not called. pygame.display.set_mode((640, 480)) @@ -397,6 +403,56 @@ def test_window_opengl(self): pygame.display.quit() pygame.init() + @unittest.skipIf(IS_PYPY, "for some reason this test is flaky on pypy") + def test_window_subclassable(self): + class WindowSubclass(Window): + def __init__(self, title="Different title", size=(640, 480), **flags): + super().__init__(title, size, pygame.WINDOWPOS_CENTERED, **flags) + self.attribute = 10 + + window = WindowSubclass() + self.assertTrue(issubclass(WindowSubclass, Window)) + self.assertIsInstance(window, WindowSubclass) + self.assertEqual(window.title, "Different title") + self.assertEqual(window.attribute, 10) + window.destroy() + + pygame.display.set_mode((200, 200)) + window = WindowSubclass.from_display_module() + self.assertIsInstance(window, WindowSubclass) + self.assertEqual(window.size, (200, 200)) + + @unittest.skipIf( + SDL < (2, 0, 16), + "requires SDL 2.0.16+", + ) + def test_window_flash(self): + window = pygame.Window() + + with self.assertRaises(TypeError): + window.flash("string") + window.flash(2.2) + window.flash([0]) + + with self.assertRaises(ValueError): + window.flash(-1) + window.flash(3) + + for operation in [ + pygame.FLASH_CANCEL, + pygame.FLASH_BRIEFLY, + pygame.FLASH_UNTIL_FOCUSED, + ]: + try: + result = window.flash(operation) + self.assertIsNone(result) + except pygame.error: + pass + + def test_window_focused(self): + window = pygame.Window() + self.assertIsInstance(window.focused, bool) + def tearDown(self): self.win.destroy()