mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-07-03 05:58:25 +08:00
chore(deps): make Qt backend optional (#350)
* chore(deps): make Qt backend optional TODO: - [ ] Add relevant entry in CHANGELOG - [ ] Update install documentation - [ ] Make sure `manim-slides convert` can run without any Qt backend - [ ] Make sure test suite works (partially) without any Qt backend - [ ] Make sure we can import `manim_slides` without any Qt backend * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(deps): some fixes but wip * chore(docs): update * chore(deps): support PyQt6 * chore(deps): make Qt backend optional TODO: - [ ] Add relevant entry in CHANGELOG - [ ] Update install documentation - [ ] Make sure `manim-slides convert` can run without any Qt backend - [ ] Make sure test suite works (partially) without any Qt backend - [ ] Make sure we can import `manim_slides` without any Qt backend * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(deps): some fixes but wip * chore(docs): update * chore(deps): support PyQt6 * fix(deps): ci and docs * fix(lib): missing package * chore(ci): does it work? * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(test): skip failing * chore(docs): update * chore(docs): update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(docs): typo * fix(test): quit instead of shutdown --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
2
.github/workflows/pages.yml
vendored
2
.github/workflows/pages.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
||||
uses: nikeee/setup-pandoc@v1
|
||||
|
||||
- name: Install local Python package
|
||||
run: pdm install -dGdocs -dGgithub-action
|
||||
run: pdm sync -Gdocs -Ggithub-action
|
||||
|
||||
- name: Install IPython kernel
|
||||
run: pdm run ipython kernel install --name "manim-slides" --user
|
||||
|
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
pyversion: ['3.8', '3.9', '3.10', '3.11']
|
||||
pyversion: ['3.9', '3.10', '3.11', '3.12']
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Install Manim Slides
|
||||
run: |
|
||||
pdm install -dGgithub-action -dGtest
|
||||
pdm sync -Ggithub-action -Gtest
|
||||
|
||||
- name: Run pytest
|
||||
if: matrix.os != 'ubuntu-latest' || matrix.pyversion != '3.11'
|
||||
|
@ -52,6 +52,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
[#335](https://github.com/jeertmans/manim-slides/pull/335)
|
||||
- Changed build backend to PDM and reflected on docs.
|
||||
[#354](https://github.com/jeertmans/manim-slides/pull/354)
|
||||
- Dropped Python 3.8 support.
|
||||
[#350](https://github.com/jeertmans/manim-slides/pull/350)
|
||||
- Made Qt backend optional and support PyQt6 too.
|
||||
[#350](https://github.com/jeertmans/manim-slides/pull/350)
|
||||
- Documentated how to create and use a custom HTML template.
|
||||
[#357](https://github.com/jeertmans/manim-slides/pull/357)
|
||||
|
||||
|
12
README.md
12
README.md
@ -88,11 +88,15 @@ manim-slides render example.py BasicExample
|
||||
# or use ManimGL
|
||||
manim-slides render --GL example.py BasicExample
|
||||
```
|
||||
<!-- end usage -->
|
||||
|
||||
> [!NOTE]
|
||||
> Using `manim-slides render` makes sure the use the `manim`
|
||||
> (or `manimlib`) library that was installed in the Python same environment.
|
||||
> Put simply, this is a wrapper of `manim render [ARGS]...` (or `manimgl [ARGS]...`).
|
||||
> Using `manim-slides render` makes sure to use the `manim`
|
||||
> (or `manimlib`) library that was installed in the same Python environment.
|
||||
> Put simply, this is a wrapper around
|
||||
> `manim render [ARGS]...` (or `manimgl [ARGS]...`).
|
||||
|
||||
<!-- start more-usage -->
|
||||
|
||||
To start the presentation using `Scene1`, `Scene2` and so on, run:
|
||||
|
||||
@ -106,7 +110,7 @@ In our example:
|
||||
manim-slides BasicExample
|
||||
```
|
||||
|
||||
<!-- end usage -->
|
||||
<!-- end more-usage -->
|
||||
|
||||
<p align="center">
|
||||
<img alt="Example GIF" src="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/example.gif">
|
||||
|
@ -48,11 +48,11 @@ pdm install -Gmanimgl # For ManimGL
|
||||
Additionnally, Manim Slides comes with groups of dependencies for development purposes:
|
||||
|
||||
```bash
|
||||
pdm install -dGdev # For linters and formatters
|
||||
pdm install -Gdev # For linters and formatters
|
||||
# or
|
||||
pdm install --dGdocs # To build the documentation locally
|
||||
pdm install -Gdocs # To build the documentation locally
|
||||
# or
|
||||
pdm install --dGtests # To run tests
|
||||
pdm install -Gtest # To run tests
|
||||
```
|
||||
|
||||
:::{note}
|
||||
|
@ -34,16 +34,23 @@ If you install Manim from its git repository, as suggested by ManimGL,
|
||||
make sure to first check out a supported version (e.g., `git checkout tags/v1.6.1`
|
||||
for ManimGL), otherwise it might install an unsupported version of Manim!
|
||||
See [#314](https://github.com/jeertmans/manim-slides/issues/314).
|
||||
|
||||
Also, note that ManimGL uses outdated dependencies, and may
|
||||
not work out-of-the-box. One example is NumPy: ManimGL
|
||||
does not specify any restriction on this package, but
|
||||
only `numpy<1.25` will work, see
|
||||
[#2053](https://github.com/3b1b/manim/issues/2053).
|
||||
:::
|
||||
|
||||
<!-- end deps -->
|
||||
|
||||
## Pip Install
|
||||
|
||||
The recommended way to install the latest release is to use pip:
|
||||
The recommended way to install the latest release
|
||||
with all features is to use pipx:
|
||||
|
||||
```bash
|
||||
pipx install -U manim-slides
|
||||
pipx install -U "manim-slides[pyside6-full]"
|
||||
```
|
||||
|
||||
:::{tip}
|
||||
@ -52,6 +59,28 @@ like to upgrade to the latest version available,
|
||||
if Manim Slides is already installed.
|
||||
:::
|
||||
|
||||
:::{note}
|
||||
The quotes `"` are added because not all shell support unquoted
|
||||
brackets (e.g., zsh) or commas (e.g., Windows).
|
||||
:::
|
||||
|
||||
You can check that Manim Slides was correctly installed with:
|
||||
|
||||
```bash
|
||||
manim-slides --version
|
||||
```
|
||||
|
||||
## Custom install
|
||||
|
||||
If you want more control on what dependencies are installed,
|
||||
you can always install the bare minimal dependencies with:
|
||||
|
||||
```bash
|
||||
pipx install -U manim-slides
|
||||
```
|
||||
|
||||
And install additional dependencies later.
|
||||
|
||||
Optionally, you can also install Manim or ManimGL using extras[^1]:
|
||||
|
||||
```bash
|
||||
@ -60,11 +89,8 @@ pipx install -U "manim-slides[manim]" # For Manim
|
||||
pipx install -U "manim-slides[manimgl]" # For ManimGL
|
||||
```
|
||||
|
||||
You can check that Manim Slides was correctly installed with:
|
||||
|
||||
```bash
|
||||
manim-slides --version
|
||||
```
|
||||
For optional dependencies documentation, see
|
||||
[next section](#optional-dependencies).
|
||||
|
||||
:::{warning}
|
||||
If you are installing with pipx, this is mandatory to at least include
|
||||
@ -74,20 +100,30 @@ either `manim` or `manimgl`.
|
||||
[^1]: You still need to have Manim or ManimGL platform-specific dependencies
|
||||
installed on your computer.
|
||||
|
||||
## Optional Dependencies
|
||||
## Optional dependencies
|
||||
|
||||
Along with the optional dependencies for Manim and ManimGL,
|
||||
Manim Slides offers additional *extras*, that can be activated
|
||||
using optional dependencies:
|
||||
|
||||
- `full`, to include `magic`, `manim`, `manimgl`, and
|
||||
`sphinx-directive` extras (see below);
|
||||
- `magic`, to include a Jupyter magic to render
|
||||
animations inside notebooks. This automatically installs `manim`,
|
||||
and does not work with ManimGL;
|
||||
- `manim` and `manimgl`, for installing the corresponding
|
||||
dependencies;
|
||||
- `pyqt6` to include PyQt6 Qt bindings. Those bindings are available
|
||||
on most platforms and Python version, but produce a weird black
|
||||
screen between slide with `manim-slides present`,
|
||||
see [#QTBUG-118501](https://bugreports.qt.io/browse/QTBUG-118501);
|
||||
- `pyqt6-full` to include `full` and `pyqt6`;
|
||||
- `pyside6` to include PySide6 Qt bindings. Those bindings are available
|
||||
on most platforms and Python version, except on Python 3.12[^2];
|
||||
- `pyside6-full` to include `full` and `pyside6`;
|
||||
- `sphinx-directive`, to generate presentation inside your Sphinx
|
||||
documentation. This automatically installs `manim`,
|
||||
and does not work with ManimGL;
|
||||
and does not work with ManimGL.
|
||||
|
||||
Installing those extras can be done with the following syntax:
|
||||
|
||||
@ -95,14 +131,27 @@ Installing those extras can be done with the following syntax:
|
||||
pipx install -U "manim-slides[extra1,extra2]"
|
||||
```
|
||||
|
||||
:::{note}
|
||||
The quotes `"` are added because not all shell support unquoted
|
||||
brackets (e.g., zsh) or commas (e.g., Windows).
|
||||
:::
|
||||
[^2]: Actually, PySide6 can be installed on Python 3.12, but you will then
|
||||
observe the same visual bug as with PyQt6.
|
||||
|
||||
## Install From Repository
|
||||
## When you need a Qt backend
|
||||
|
||||
Before `v5.1`, Manim Slides automatically included PySide6 as
|
||||
a Qt backend. As only `manim-slides present` and `manim-slides wizard`
|
||||
command need a graphical library, and installing PySide6 on all platforms
|
||||
and Python version can be sometimes complicated, Manim Slides chooses
|
||||
**not to include** any Qt backend.
|
||||
|
||||
The use can choose between PySide6 (best) and PyQt6, depending on their
|
||||
availability and licensing rules.
|
||||
|
||||
As of `v5.1`, you **need** to have Qt bindings installed to use
|
||||
`manim-slides present` or `manim-slides wizard`. The recommended way to
|
||||
install those are via optional dependencies, as explained above.
|
||||
|
||||
## Install from source
|
||||
|
||||
An alternative way to install Manim Slides is to clone the git repository,
|
||||
and install from there: read the
|
||||
and build the package from source. Read the
|
||||
[contributing guide](./contributing/workflow)
|
||||
to know how to process.
|
||||
|
@ -10,6 +10,19 @@ see [installation](./installation).
|
||||
:end-before: <!-- end usage -->
|
||||
```
|
||||
|
||||
:::{note}
|
||||
Using `manim-slides render` makes sure to use the `manim`
|
||||
(or `manimlib`) library that was installed in the same Python environment.
|
||||
Put simply, this is a wrapper around
|
||||
`manim render [ARGS]...` (or `manimgl [ARGS]...`).
|
||||
:::
|
||||
|
||||
|
||||
```{include} ../../README.md
|
||||
:start-after: <!-- start more-usage -->
|
||||
:end-before: <!-- end more-usage -->
|
||||
```
|
||||
|
||||
The output slides should look this this:
|
||||
|
||||
```{eval-rst}
|
||||
|
@ -17,7 +17,6 @@ from pydantic import (
|
||||
model_validator,
|
||||
)
|
||||
from pydantic_extra_types.color import Color
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
from .logger import logger
|
||||
|
||||
@ -38,6 +37,13 @@ class Signal(BaseModel): # type: ignore[misc]
|
||||
receiver(*args)
|
||||
|
||||
|
||||
def key_id(name: str) -> PositiveInt:
|
||||
"""Avoid importing Qt too early."""
|
||||
from qtpy.QtCore import Qt
|
||||
|
||||
return getattr(Qt, f"Key_{name}")
|
||||
|
||||
|
||||
class Key(BaseModel): # type: ignore[misc]
|
||||
"""Represents a list of key codes, with optionally a name."""
|
||||
|
||||
@ -73,14 +79,22 @@ class Key(BaseModel): # type: ignore[misc]
|
||||
|
||||
|
||||
class Keys(BaseModel): # type: ignore[misc]
|
||||
QUIT: Key = Key(ids=[Qt.Key_Q], name="QUIT")
|
||||
PLAY_PAUSE: Key = Key(ids=[Qt.Key_Space], name="PLAY / PAUSE")
|
||||
NEXT: Key = Key(ids=[Qt.Key_Right], name="NEXT")
|
||||
PREVIOUS: Key = Key(ids=[Qt.Key_Left], name="PREVIOUS")
|
||||
REVERSE: Key = Key(ids=[Qt.Key_V], name="REVERSE")
|
||||
REPLAY: Key = Key(ids=[Qt.Key_R], name="REPLAY")
|
||||
FULL_SCREEN: Key = Key(ids=[Qt.Key_F], name="TOGGLE FULL SCREEN")
|
||||
HIDE_MOUSE: Key = Key(ids=[Qt.Key_H], name="HIDE / SHOW MOUSE")
|
||||
QUIT: Key = Field(default_factory=lambda: Key(ids=[key_id("Q")], name="QUIT"))
|
||||
PLAY_PAUSE: Key = Field(
|
||||
default_factory=lambda: Key(ids=[key_id("Space")], name="PLAY / PAUSE")
|
||||
)
|
||||
NEXT: Key = Field(default_factory=lambda: Key(ids=[key_id("Right")], name="NEXT"))
|
||||
PREVIOUS: Key = Field(
|
||||
default_factory=lambda: Key(ids=[key_id("Left")], name="PREVIOUS")
|
||||
)
|
||||
REVERSE: Key = Field(default_factory=lambda: Key(ids=[key_id("V")], name="REVERSE"))
|
||||
REPLAY: Key = Field(default_factory=lambda: Key(ids=[key_id("R")], name="REPLAY"))
|
||||
FULL_SCREEN: Key = Field(
|
||||
default_factory=lambda: Key(ids=[key_id("F")], name="TOGGLE FULL SCREEN")
|
||||
)
|
||||
HIDE_MOUSE: Key = Field(
|
||||
default_factory=lambda: Key(ids=[key_id("H")], name="HIDE / SHOW MOUSE")
|
||||
)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@ -121,7 +135,7 @@ class Keys(BaseModel): # type: ignore[misc]
|
||||
class Config(BaseModel): # type: ignore[misc]
|
||||
"""General Manim Slides config."""
|
||||
|
||||
keys: Keys = Keys()
|
||||
keys: Keys = Field(default_factory=Keys)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path: Path) -> "Config":
|
||||
@ -326,6 +340,3 @@ class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
shutil.copy(rev_file, rev_dest)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
DEFAULT_CONFIG = Config()
|
||||
|
@ -648,14 +648,16 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
"config_options",
|
||||
multiple=True,
|
||||
callback=validate_config_option,
|
||||
help="Configuration options passed to the converter. E.g., pass `-cslide_number=true` to display slide numbers.",
|
||||
help="Configuration options passed to the converter. "
|
||||
"E.g., pass ``-cslide_number=true`` to display slide numbers.",
|
||||
)
|
||||
@click.option(
|
||||
"--use-template",
|
||||
"template",
|
||||
metavar="FILE",
|
||||
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
||||
help="Use the template given by FILE instead of default one. To echo the default template, use `--show-template`.",
|
||||
help="Use the template given by FILE instead of default one. "
|
||||
"To echo the default template, use ``--show-template``.",
|
||||
)
|
||||
@show_template_option
|
||||
@show_config_options
|
||||
|
@ -6,18 +6,10 @@ from typing import List, Optional, Tuple
|
||||
import click
|
||||
from click import Context, Parameter
|
||||
from pydantic import ValidationError
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
from ..commons import config_path_option, folder_path_option, verbosity_option
|
||||
from ..config import Config, PresentationConfig
|
||||
from ..logger import logger
|
||||
from ..qt_utils import qapp
|
||||
from .player import Player
|
||||
|
||||
ASPECT_RATIO_MODES = {
|
||||
"keep": Qt.KeepAspectRatio,
|
||||
"ignore": Qt.IgnoreAspectRatio,
|
||||
}
|
||||
|
||||
|
||||
@click.command()
|
||||
@ -130,7 +122,8 @@ def start_at_callback(
|
||||
return tuple(map(str_to_int_or_none, values_tuple))
|
||||
|
||||
raise click.BadParameter(
|
||||
f"exactly 2 arguments are expected but you gave {n_values}, please use commas to separate them",
|
||||
f"exactly 2 arguments are expected but you gave {n_values}, "
|
||||
"please use commas to separate them",
|
||||
ctx=ctx,
|
||||
param=param,
|
||||
)
|
||||
@ -283,6 +276,8 @@ def present(
|
||||
if start_at[1]:
|
||||
start_at_slide_number = start_at[1]
|
||||
|
||||
from ..qt_utils import qapp
|
||||
|
||||
app = qapp()
|
||||
app.setApplicationName("Manim Slides")
|
||||
|
||||
@ -298,6 +293,15 @@ def present(
|
||||
else:
|
||||
screen = None
|
||||
|
||||
from qtpy.QtCore import Qt
|
||||
|
||||
aspect_ratio_modes = {
|
||||
"keep": Qt.KeepAspectRatio,
|
||||
"ignore": Qt.IgnoreAspectRatio,
|
||||
}
|
||||
|
||||
from .player import Player
|
||||
|
||||
player = Player(
|
||||
config,
|
||||
presentation_configs,
|
||||
@ -306,7 +310,7 @@ def present(
|
||||
skip_all=skip_all,
|
||||
exit_after_last_slide=exit_after_last_slide,
|
||||
hide_mouse=hide_mouse,
|
||||
aspect_ratio_mode=ASPECT_RATIO_MODES[aspect_ratio],
|
||||
aspect_ratio_mode=aspect_ratio_modes[aspect_ratio],
|
||||
presentation_index=start_at_scene_number,
|
||||
slide_index=start_at_slide_number,
|
||||
screen=screen,
|
||||
|
@ -2,11 +2,11 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from PySide6.QtCore import Qt, QTimer, QUrl, Signal, Slot
|
||||
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
|
||||
from PySide6.QtMultimedia import QMediaPlayer
|
||||
from PySide6.QtMultimediaWidgets import QVideoWidget
|
||||
from PySide6.QtWidgets import (
|
||||
from qtpy.QtCore import Qt, QTimer, QUrl, Signal, Slot
|
||||
from qtpy.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
|
||||
from qtpy.QtMultimedia import QMediaPlayer
|
||||
from qtpy.QtMultimediaWidgets import QVideoWidget
|
||||
from qtpy.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
@ -271,7 +271,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
|
||||
def media_status_changed(status: QMediaPlayer.MediaStatus) -> None:
|
||||
self.media_player.setLoops(1) # Otherwise looping slides never end
|
||||
if status == QMediaPlayer.EndOfMedia:
|
||||
if status == QMediaPlayer.MediaStatus.EndOfMedia:
|
||||
self.load_next_slide()
|
||||
|
||||
self.media_player.mediaStatusChanged.connect(media_status_changed)
|
||||
@ -280,7 +280,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
|
||||
def media_status_changed(status: QMediaPlayer.MediaStatus) -> None:
|
||||
if (
|
||||
status == QMediaPlayer.EndOfMedia
|
||||
status == QMediaPlayer.MediaStatus.EndOfMedia
|
||||
and self.current_slide_config.auto_next
|
||||
):
|
||||
self.load_next_slide()
|
||||
@ -390,7 +390,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
"""
|
||||
|
||||
def load_current_media(self, start_paused: bool = False) -> None:
|
||||
url = QUrl.fromLocalFile(self.current_file)
|
||||
url = QUrl.fromLocalFile(str(self.current_file))
|
||||
self.media_player.setSource(url)
|
||||
|
||||
if self.playing_reversed_slide:
|
||||
@ -475,7 +475,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
|
||||
def preview_next_slide(self) -> None:
|
||||
if slide_config := self.next_slide_config:
|
||||
url = QUrl.fromLocalFile(slide_config.file)
|
||||
url = QUrl.fromLocalFile(str(slide_config.file))
|
||||
self.info.next_media_player.setSource(url)
|
||||
self.info.next_media_player.play()
|
||||
|
||||
@ -493,7 +493,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
|
||||
@Slot()
|
||||
def next(self) -> None:
|
||||
if self.media_player.playbackState() == QMediaPlayer.PausedState:
|
||||
if self.media_player.playbackState() == QMediaPlayer.PlaybackState.PausedState:
|
||||
self.media_player.play()
|
||||
elif self.next_terminates_loop and self.media_player.loops() != 1:
|
||||
position = self.media_player.position()
|
||||
@ -521,9 +521,9 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
@Slot()
|
||||
def play_pause(self) -> None:
|
||||
state = self.media_player.playbackState()
|
||||
if state == QMediaPlayer.PausedState:
|
||||
if state == QMediaPlayer.PlaybackState.PausedState:
|
||||
self.media_player.play()
|
||||
elif state == QMediaPlayer.PlayingState:
|
||||
elif state == QMediaPlayer.PlaybackState.PlayingState:
|
||||
self.media_player.pause()
|
||||
|
||||
@Slot()
|
||||
@ -540,11 +540,9 @@ class Player(QMainWindow): # type: ignore[misc]
|
||||
else:
|
||||
self.setCursor(Qt.BlankCursor)
|
||||
|
||||
@Slot()
|
||||
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
||||
self.close()
|
||||
|
||||
@Slot()
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
||||
key = event.key()
|
||||
self.dispatch(key)
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Qt utils."""
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
|
||||
def qapp() -> QApplication:
|
||||
|
@ -4,7 +4,7 @@
|
||||
# Created by: The Resource Compiler for Qt version 6.4.0
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide6 import QtCore
|
||||
from qtpy import QtCore
|
||||
|
||||
qt_resource_data = b"\
|
||||
\x00\x00\x08\x1c\
|
||||
|
85
manim_slides/wizard/__init__.py
Normal file
85
manim_slides/wizard/__init__.py
Normal file
@ -0,0 +1,85 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from ..commons import config_options, verbosity_option
|
||||
from ..config import Config
|
||||
from ..defaults import CONFIG_PATH
|
||||
from ..logger import logger
|
||||
|
||||
|
||||
@click.command()
|
||||
@config_options
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
def wizard(config_path: Path, force: bool, merge: bool) -> None:
|
||||
"""Launch configuration wizard."""
|
||||
return _init(config_path, force, merge, skip_interactive=False)
|
||||
|
||||
|
||||
@click.command()
|
||||
@config_options
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
def init(
|
||||
config_path: Path, force: bool, merge: bool, skip_interactive: bool = False
|
||||
) -> None:
|
||||
"""Initialize a new default configuration file."""
|
||||
return _init(config_path, force, merge, skip_interactive=True)
|
||||
|
||||
|
||||
def _init(
|
||||
config_path: Path, force: bool, merge: bool, skip_interactive: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Actual initialization code for configuration file, with optional interactive
|
||||
mode.
|
||||
"""
|
||||
if config_path.exists():
|
||||
click.secho(f"The `{CONFIG_PATH}` configuration file exists")
|
||||
|
||||
if not force and not merge:
|
||||
choice = click.prompt(
|
||||
"Do you want to continue and (o)verwrite / (m)erge it, or (q)uit?",
|
||||
type=click.Choice(["o", "m", "q"], case_sensitive=False),
|
||||
)
|
||||
|
||||
force = choice == "o"
|
||||
merge = choice == "m"
|
||||
|
||||
if not force and not merge:
|
||||
logger.debug("Exiting without doing anything")
|
||||
sys.exit(0)
|
||||
|
||||
config = Config()
|
||||
|
||||
if force:
|
||||
logger.debug(f"Overwriting `{config_path}` if exists")
|
||||
elif merge:
|
||||
logger.debug("Merging new config into `{config_path}`")
|
||||
|
||||
if not skip_interactive:
|
||||
if config_path.exists():
|
||||
config = Config.from_file(config_path)
|
||||
|
||||
from ..qt_utils import qapp
|
||||
from .wizard import Wizard
|
||||
|
||||
app = qapp()
|
||||
app.setApplicationName("Manim Slides Wizard")
|
||||
window = Wizard(config)
|
||||
window.show()
|
||||
app.exec()
|
||||
|
||||
if window.closed_without_saving:
|
||||
sys.exit(0)
|
||||
|
||||
config = window.config
|
||||
|
||||
if merge:
|
||||
config = Config.from_file(config_path).merge_with(config)
|
||||
|
||||
config.to_file(config_path)
|
||||
|
||||
click.secho(f"Configuration file successfully saved to `{config_path}`")
|
@ -1,12 +1,9 @@
|
||||
import sys
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QIcon, QKeyEvent
|
||||
from PySide6.QtWidgets import (
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtGui import QIcon, QKeyEvent
|
||||
from qtpy.QtWidgets import (
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QGridLayout,
|
||||
@ -17,12 +14,9 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from .commons import config_options, verbosity_option
|
||||
from .config import Config, Key
|
||||
from .defaults import CONFIG_PATH
|
||||
from .logger import logger
|
||||
from .qt_utils import qapp
|
||||
from .resources import * # noqa: F403
|
||||
from ..config import Config, Key
|
||||
from ..logger import logger
|
||||
from ..resources import * # noqa: F403
|
||||
|
||||
WINDOW_NAME: str = "Configuration Wizard"
|
||||
|
||||
@ -125,76 +119,3 @@ class Wizard(QWidget): # type: ignore
|
||||
key_name = keymap[dialog.key]
|
||||
key.set_ids(dialog.key)
|
||||
button.setText(key_name)
|
||||
|
||||
|
||||
@click.command()
|
||||
@config_options
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
def wizard(config_path: Path, force: bool, merge: bool) -> None:
|
||||
"""Launch configuration wizard."""
|
||||
return _init(config_path, force, merge, skip_interactive=False)
|
||||
|
||||
|
||||
@click.command()
|
||||
@config_options
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
def init(
|
||||
config_path: Path, force: bool, merge: bool, skip_interactive: bool = False
|
||||
) -> None:
|
||||
"""Initialize a new default configuration file."""
|
||||
return _init(config_path, force, merge, skip_interactive=True)
|
||||
|
||||
|
||||
def _init(
|
||||
config_path: Path, force: bool, merge: bool, skip_interactive: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Actual initialization code for configuration file, with optional interactive
|
||||
mode.
|
||||
"""
|
||||
if config_path.exists():
|
||||
click.secho(f"The `{CONFIG_PATH}` configuration file exists")
|
||||
|
||||
if not force and not merge:
|
||||
choice = click.prompt(
|
||||
"Do you want to continue and (o)verwrite / (m)erge it, or (q)uit?",
|
||||
type=click.Choice(["o", "m", "q"], case_sensitive=False),
|
||||
)
|
||||
|
||||
force = choice == "o"
|
||||
merge = choice == "m"
|
||||
|
||||
if not force and not merge:
|
||||
logger.debug("Exiting without doing anything")
|
||||
sys.exit(0)
|
||||
|
||||
config = Config()
|
||||
|
||||
if force:
|
||||
logger.debug(f"Overwriting `{config_path}` if exists")
|
||||
elif merge:
|
||||
logger.debug("Merging new config into `{config_path}`")
|
||||
|
||||
if not skip_interactive:
|
||||
if config_path.exists():
|
||||
config = Config.from_file(config_path)
|
||||
|
||||
app = qapp()
|
||||
app.setApplicationName("Manim Slides Wizard")
|
||||
window = Wizard(config)
|
||||
window.show()
|
||||
app.exec()
|
||||
|
||||
if window.closed_without_saving:
|
||||
sys.exit(0)
|
||||
|
||||
config = window.config
|
||||
|
||||
if merge:
|
||||
config = Config.from_file(config_path).merge_with(config)
|
||||
|
||||
config.to_file(config_path)
|
||||
|
||||
click.secho(f"Configuration file successfully saved to `{config_path}`")
|
@ -6,10 +6,10 @@ requires = ["pdm-backend", "setuptools"]
|
||||
authors = [{name = "Jérome Eertmans", email = "jeertmans@icloud.com"}]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Topic :: Multimedia :: Video",
|
||||
@ -27,8 +27,8 @@ dependencies = [
|
||||
"pillow>=9.5.0",
|
||||
"pydantic>=2.0.1",
|
||||
"pydantic-extra-types>=2.0.0",
|
||||
"pyside6==6.5.2",
|
||||
"python-pptx>=0.6.21",
|
||||
"qtpy>=2.4.1",
|
||||
"requests>=2.28.1",
|
||||
"rich>=13.3.2",
|
||||
"rtoml>=0.9.0",
|
||||
@ -40,15 +40,19 @@ keywords = ["manim", "slides", "plugin", "manimgl"]
|
||||
license = {text = "MIT"}
|
||||
name = "manim-slides"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8,<3.12"
|
||||
requires-python = ">=3.9,<3.13"
|
||||
|
||||
[project.optional-dependencies]
|
||||
all = [
|
||||
full = [
|
||||
"manim-slides[magic,manim,manimgl,sphinx-directive]",
|
||||
]
|
||||
magic = ["manim-slides[manim]", "ipython>=8.12.2"]
|
||||
manim = ["manim>=0.17.3"]
|
||||
manimgl = ["manimgl>=1.6.1"]
|
||||
pyqt6 = ["pyqt6>=6.6.1"]
|
||||
pyqt6-full = ["manim-slides[full,pyqt6]"]
|
||||
pyside6 = ["pyside6>=6.5.1,<6.5.3;python_version<'3.12'"]
|
||||
pyside6-full = ["manim-slides[full,pyside6]"]
|
||||
sphinx-directive = ["docutils>=0.20.1", "manim-slides[manim]"]
|
||||
|
||||
[project.scripts]
|
||||
@ -63,10 +67,10 @@ Repository = "https://github.com/jeertmans/manim-slides"
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = [
|
||||
'pragma: no cover',
|
||||
'raise NotImplementedError',
|
||||
'if TYPE_CHECKING:',
|
||||
'if typing.TYPE_CHECKING:',
|
||||
"pragma: no cover",
|
||||
"raise NotImplementedError",
|
||||
"if TYPE_CHECKING:",
|
||||
"if typing.TYPE_CHECKING:",
|
||||
]
|
||||
precision = 2
|
||||
|
||||
@ -95,7 +99,7 @@ docs = [
|
||||
]
|
||||
github-action = ["setuptools"]
|
||||
test = [
|
||||
"manim-slides[manim,manimgl]",
|
||||
"manim-slides[manim,manimgl,pyqt6]",
|
||||
"pytest>=7.4.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-env>=0.8.2",
|
||||
@ -103,6 +107,10 @@ test = [
|
||||
"pytest-xdist>=3.3.1",
|
||||
]
|
||||
|
||||
[tool.pdm.resolution.overrides]
|
||||
manimpango = "<1.0.0,>=0.5.0" # This conflicts with ManimGL, hopefully not an issue
|
||||
skia-pathops = "0.8.0.post1" # From manim 0.18.0 (Python 3.12 support)
|
||||
|
||||
[tool.pdm.version]
|
||||
path = "manim_slides/__version__.py"
|
||||
source = "file"
|
||||
@ -134,6 +142,6 @@ extend-ignore = [
|
||||
]
|
||||
extend-include = ["*.ipynb"]
|
||||
extend-select = ["B", "C90", "D", "I", "N", "RUF", "UP", "T"]
|
||||
isort = {known-first-party = ['manim_slides', 'tests']}
|
||||
isort = {known-first-party = ["manim_slides", "tests"]}
|
||||
line-length = 88
|
||||
target-version = "py38"
|
||||
|
@ -3,7 +3,7 @@ from typing import Iterator, Tuple
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
from manim_slides.present import present
|
||||
|
||||
@ -11,12 +11,12 @@ from manim_slides.present import present
|
||||
@pytest.fixture(autouse=True)
|
||||
def auto_shutdown_qapp() -> Iterator[None]:
|
||||
if app := QApplication.instance():
|
||||
app.shutdown()
|
||||
app.quit()
|
||||
|
||||
yield
|
||||
|
||||
if app := QApplication.instance():
|
||||
app.shutdown()
|
||||
app.quit()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
@ -1,4 +1,4 @@
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
from manim_slides.qt_utils import qapp
|
||||
|
||||
|
@ -2,6 +2,7 @@ import random
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from manim import (
|
||||
@ -17,6 +18,7 @@ from manim import (
|
||||
GrowFromCenter,
|
||||
Text,
|
||||
)
|
||||
from packaging import version
|
||||
from pydantic import ValidationError
|
||||
|
||||
from manim_slides.config import PresentationConfig
|
||||
@ -29,7 +31,13 @@ from manim_slides.slide.manim import Slide
|
||||
"renderer",
|
||||
[
|
||||
"--CE",
|
||||
"--GL",
|
||||
pytest.param(
|
||||
"--GL",
|
||||
marks=pytest.mark.skipif(
|
||||
version.parse(np.__version__) >= version.parse("1.25"),
|
||||
reason="ManimGL requires numpy<1.25, which is outdate",
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_render_basic_slide(
|
||||
|
@ -1,17 +1,18 @@
|
||||
from pathlib import Path
|
||||
|
||||
from click.testing import CliRunner
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
from pytest import MonkeyPatch
|
||||
from pytestqt.qtbot import QtBot
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QMessageBox,
|
||||
)
|
||||
from pytest import MonkeyPatch
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from manim_slides.config import Config, Key
|
||||
from manim_slides.defaults import CONFIG_PATH
|
||||
from manim_slides.wizard import KeyInput, Wizard, init, wizard
|
||||
from manim_slides.wizard import init, wizard
|
||||
from manim_slides.wizard.wizard import KeyInput, Wizard
|
||||
|
||||
|
||||
class TestKeyInput:
|
||||
|
Reference in New Issue
Block a user