Skip to content

Commit

Permalink
Add support for third-party GUI framework plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
rmartin16 committed Nov 10, 2023
1 parent 4862d70 commit e443741
Show file tree
Hide file tree
Showing 12 changed files with 1,619 additions and 67 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ jobs:
verify-apps:
name: Build app
needs: unit-tests
uses: beeware/.github/.github/workflows/app-build-verify.yml@main
# uses: beeware/.github/.github/workflows/app-build-verify.yml@main
uses: rmartin16/.github-beeware/.github/workflows/app-build-verify.yml@gui-plugin-support
with:
# This *must* be the version of Python that is the system Python on the
# Ubuntu version used to run Linux tests. We use a fixed ubuntu-22.04
Expand All @@ -166,6 +167,8 @@ jobs:
python-version: "3.10"
runner-os: ${{ matrix.runner-os }}
framework: ${{ matrix.framework }}
briefcase-template-source: https://github.com/rmartin16/briefcase-template.git
briefcase-template-branch: gui-plugin-support
strategy:
fail-fast: false
matrix:
Expand Down
1 change: 1 addition & 0 deletions changes/1524.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Creating new projects with arbitrary third-party GUI frameworks is now supported via plugins.
6 changes: 6 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ where = src
[options.entry_points]
console_scripts =
briefcase = briefcase.__main__:main
briefcase.bootstraps =
Toga = briefcase.bootstraps.toga
PySide2 = briefcase.bootstraps.pyside2
PySide6 = briefcase.bootstraps.pyside6
PursuedPyBear = briefcase.bootstraps.pursuedpybear
Pygame = briefcase.bootstraps.pygame
briefcase.platforms =
android = briefcase.platforms.android
iOS = briefcase.platforms.iOS
Expand Down
6 changes: 6 additions & 0 deletions src/briefcase/bootstraps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from briefcase.bootstraps.base import BaseGuiPlugin # noqa: F401
from briefcase.bootstraps.pursuedpybear import PursuedPyBearGuiPlugin # noqa: F401
from briefcase.bootstraps.pygame import PygameGuiPlugin # noqa: F401
from briefcase.bootstraps.pyside2 import PySide2GuiPlugin # noqa: F401
from briefcase.bootstraps.pyside6 import PySide6GuiPlugin # noqa: F401
from briefcase.bootstraps.toga import TogaGuiPlugin # noqa: F401
142 changes: 142 additions & 0 deletions src/briefcase/bootstraps/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from __future__ import annotations

from abc import ABC
from typing import Literal, TypedDict


class ContextDict(TypedDict):
formal_name: str
app_name: str
class_name: str
module_name: str
project_name: str
description: str
author: str
author_email: str
bundle: str
url: str
license: str


class BaseGuiPlugin(ABC):
"""Definition for a plugin that defines a new Briefcase app."""

name: str
fields: list[str] = [
"app_source",
"start_app_source",
"requires",
"macos_requires",
"macos_universal_build",
"linux_requires",
"linux_system_debian_system_requires",
"linux_system_debian_system_runtime_requires",
"linux_system_rhel_system_requires",
"linux_system_rhel_system_runtime_requires",
"linux_system_suse_system_requires",
"linux_system_suse_system_runtime_requires",
"linux_system_arch_system_requires",
"linux_system_arch_system_runtime_requires",
"linux_appimage_manylinux",
"linux_appimage_system_requires",
"linux_appimage_linuxdeploy_plugins",
"linux_flatpak_runtime",
"linux_flatpak_runtime_version",
"linux_flatpak_sdk",
"windows_requires",
"ios_requires",
"ios_supported",
"android_requires",
"android_supported",
"web_requires",
"web_supported",
"web_style_framework",
]

def __init__(self, context: ContextDict):
# context contains metadata about the app being created
self.context = context

def app_source(self) -> str | None:
"""The Python source code for the project."""

def start_app_source(self) -> str | None:
"""The Python source code to start the app from __main__.py."""

def requires(self) -> str | None:
"""List of package requirements for all platforms."""

def macos_requires(self) -> str | None:
"""List of package requirements for macOS."""

def macos_universal_build(self) -> Literal["true", "false"] | None:
"""Whether to create a universal build for macOS."""

def linux_requires(self) -> str | None:
"""List of package requirements for Linux."""

def linux_system_debian_system_requires(self) -> str | None:
"""List of system package requirements to build the app."""

def linux_system_debian_system_runtime_requires(self) -> str | None:
"""List of system package requirements to run the app on Debian."""

def linux_system_rhel_system_requires(self) -> str | None:
"""List of system package requirements to build the app on RHEL."""

def linux_system_rhel_system_runtime_requires(self) -> str | None:
"""List of system package requirements to run the app on RHEL."""

def linux_system_suse_system_requires(self) -> str | None:
"""List of system package requirements to build the app on SUSE."""

def linux_system_suse_system_runtime_requires(self) -> str | None:
"""List of system package requirements to run the app on SUSE."""

def linux_system_arch_system_requires(self) -> str | None:
"""List of system package requirements to build the app on Arch."""

def linux_system_arch_system_runtime_requires(self) -> str | None:
"""List of system package requirements to run the app on Arch."""

def linux_appimage_manylinux(self) -> str | None:
"""The manylinux base, e.g. manylinux2014, to use to build the app."""

def linux_appimage_system_requires(self) -> str | None:
"""List of system package requirements to build the app in to an AppImage."""

def linux_appimage_linuxdeploy_plugins(self) -> str | None:
"""List of linuxdeploy plugins to use to build the app in to an AppImage."""

def linux_flatpak_runtime(self) -> str | None:
"""The Flatpak runtime, e.g. org.gnome.Platform, for the app."""

def linux_flatpak_runtime_version(self) -> str | None:
"""The Flatpak runtime version, e.g. 44, for the app."""

def linux_flatpak_sdk(self) -> str | None:
"""The Flatpak SDK, e.g. org.gnome.Sdk, for the app."""

def windows_requires(self) -> str | None:
"""List of package requirements for Windows."""

def ios_requires(self) -> str | None:
"""List of package requirements for iOS."""

def ios_supported(self) -> Literal["true", "false"] | None:
"""Whether the GUI framework supports iOS."""

def android_requires(self) -> str | None:
"""List of package requirements for Android."""

def android_supported(self) -> Literal["true", "false"] | None:
"""Whether the GUI framework supports Android."""

def web_requires(self) -> str | None:
"""List of package requirements for Web."""

def web_supported(self) -> Literal["true", "false"] | None:
"""Whether the GUI framework supports Web."""

def web_style_framework(self) -> str | None:
"""The style framework, e.g. Bootstrap or Shoelace, for web."""
79 changes: 79 additions & 0 deletions src/briefcase/bootstraps/pursuedpybear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from briefcase.bootstraps.base import BaseGuiPlugin


class PursuedPyBearGuiPlugin(BaseGuiPlugin):
name = "PursuedPyBear"

def app_source(self):
return """\
import os
import sys
try:
from importlib import metadata as importlib_metadata
except ImportError:
# Backwards compatibility - importlib.metadata was added in Python 3.8
import importlib_metadata
import ppb
class {{ cookiecutter.class_name }}(ppb.Scene):
def __init__(self, **props):
super().__init__(**props)
self.add(
ppb.Sprite(
image=ppb.Image("{{ cookiecutter.module_name }}/resources/{{ cookiecutter.app_name }}.png"),
)
)
def main():
# Linux desktop environments use app's .desktop file to integrate the app
# to their application menus. The .desktop file of this app will include
# StartupWMClass key, set to app's formal name, which helps associate
# 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 PPB this
# is set using environment variable.
# Find the name of the module that was used to start the app
app_module = sys.modules["__main__"].__package__
# Retrieve the app's metadata
metadata = importlib_metadata.metadata(app_module)
os.environ["SDL_VIDEO_X11_WMCLASS"] = metadata["Formal-Name"]
ppb.run(
starting_scene={{ cookiecutter.class_name }},
title=metadata["Formal-Name"],
)
"""

def requires(self):
return """
"ppb~=1.1",
"""

def macos_requires(self):
return ""

def linux_requires(self):
return ""

def linux_appimage_manylinux(self):
return "manylinux2014"

def linux_flatpak_runtime(self):
return "org.freedesktop.Platform"

def linux_flatpak_runtime_version(self):
return "22.08"

def linux_flatpak_sdk(self):
return "org.freedesktop.Sdk"


plugin = PursuedPyBearGuiPlugin
82 changes: 82 additions & 0 deletions src/briefcase/bootstraps/pygame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from briefcase.bootstraps.base import BaseGuiPlugin


class PygameGuiPlugin(BaseGuiPlugin):
name = "Pygame"

def app_source(self):
return """\
import os
import sys
import pygame
try:
from importlib import metadata as importlib_metadata
except ImportError:
# Backwards compatibility - importlib.metadata was added in Python 3.8
import importlib_metadata
SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
WHITE = (255, 255, 255)
def main():
# Linux desktop environments use app's .desktop file to integrate the app
# to their application menus. The .desktop file of this app will include
# StartupWMClass key, set to app's formal name, which helps associate
# 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 PPB this
# is set using environment variable.
# Find the name of the module that was used to start the app
app_module = sys.modules["__main__"].__package__
# Retrieve the app's metadata
metadata = importlib_metadata.metadata(app_module)
os.environ["SDL_VIDEO_X11_WMCLASS"] = metadata["Formal-Name"]
pygame.init()
pygame.display.set_caption(metadata["Formal-Name"])
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
screen.fill(WHITE)
pygame.display.flip()
pygame.quit()
"""

def requires(self):
return """
"pygame~=2.2",
"""

def macos_requires(self):
return ""

def linux_requires(self):
return ""

def linux_appimage_manylinux(self):
return "manylinux2014"

def linux_flatpak_runtime(self):
return "org.freedesktop.Platform"

def linux_flatpak_runtime_version(self):
return "22.08"

def linux_flatpak_sdk(self):
return "org.freedesktop.Sdk"


plugin = PygameGuiPlugin
Loading

0 comments on commit e443741

Please sign in to comment.