后端开发指南

作为多后端的GUI库,Charmy支持使用多种后端来构建应用程序,为了统一接口并确保每个未被支持的功能都有对应的fallback,我们提供了一个可供继承的模板,通常您的后端应当基于此模板开发。以下是模板的基本结构和使用方法。

后端的基本结构

一个有效的Charmy后端主要应当包含以下几个部分:

  1. Backend类:用于定义后端的基本接口和功能,以及后端的元信息(也就是您的署名处)。

  2. 各种*SupportState类:用于标记当前后端是否支持某些特性。

  3. 各种*Base类:用于定义对应后端的基本接口和功能。

至于细节可以参见template去查阅有哪些类,或者参见抽象层了解详细的介绍。

编写后端接口

我们将会基于Genesis后端的代码为基础,在本文中讲述如何为Charmy开发一个有效可用的后端。

如果你是扩展后端库开发者,建议使用charmy-backend-xxx作为库名。未来将支持自动搜索所有名称以charmy-backend-开头的包,并将其加入备选后端。

编写GUI后端接口

先要注明后端的基本信息,包括简写名称、具体名称、版本号和作者等。然后继承template.Backend类,并实现必要的接口。

编写Backend

class Backend(template.Backend):
    """The Genesis backend."""

    name: typing.ClassVar[str] =            "genesis"
    friendly_name: typing.ClassVar[str] =   "Genesis"
    version: typing.ClassVar[str] =         "0.1.0"
    author: typing.ClassVar[list[str]] =    ...

    def __init__(self):
        """APIs are aliased here."""
        super().__init__()
    
    def backend_init(self, **kwargs) -> None:
        return
  • name: 后端的名称,最好为字母+下划线这样的安全格式,用于标识,需确保唯一。

  • friendly_name: 后端的用户友好名称,用于人类阅读,暂无实际用途。

  • version: 后端的版本号,可能用于检查更新等,暂无实际用途。

  • author: 后端的作者列表,暂无实际用途。

编写WindowSupportState

后端应当注明自己支持哪些后端方法,从而防止开发时出现纰漏。如以下代码,在支持的方法名称前面标注了True,不支持的方法标注了False。如果你不确定是否支持某个方法,可以先标注为False,等后续实现后再改为True

如果Charmy的后续更新加入了新的后端特性,而您却没有跟进的话,也并不需要额外添加更新来标记新特性为不支持,因为它们会在模板中默认标记为False,然后被继承到您的后端里。

综上所述,具体的实现应当为:后端继承template中对应的*SupportState类,然后标记自身支持的特性为True,并(可选)标记自身明确不会支持的特性为False

以下是一个例子,这个后端声明其支持为窗口设定标题与图标,但明确声明其不支持设定窗口在屏幕上的位置。而对于缩放模式等,后端并未明确进行声明,Charmy会将其视为不支持这些特性。

class WindowSupportState(template.WindowSupportState):
    """Flags all supported window features."""
    set_title               : bool = True
    set_icon                : bool = True
    set_pos                 : bool = False
    set_size                : bool = True
    # set_scale_mode          : bool = False

编写WindowBase

这是后端使用的GUI窗口类接口,应当继承自template.WindowBase,并实现必要的功能。

class WindowBase(template.WindowBase):
    """Window APIs implementation."""
    supports = WindowSupportState()
    Backend = Backend

    def __init__(self, backend: template.Backend, charmy_window: _window.WindowEntity):
        """Creates a window.

        :param backend: The backend that this window uses (can be get from CharmyManager)
        """
        super().__init__(backend, charmy_window)

        self.window: typing.Any = ...

        self.charmy_window._pos = ...


    def show(self) -> typing.Self:
        ...

    def update(self, redraw: bool | charmy_stuff.styles.shape.ShapeRange = True) -> typing.Self:
        ...

    def close(self):
        ...

首先继承模板类,并设Backend类属性为你刚刚编写的Backend类,设定supports类属性为你刚刚编写的WindowSupportState实例。

class WindowBase(template.WindowBase):
    """Window APIs in Genesis backend."""
    supports = WindowSupportState()
    Backend = Backend

之后,编写一个__init__函数。这个初始化函数应当接收两个参数,分别为backend用于接收所使用的后端,和window用于接收Charmy控件层的窗口对象。您的初始化函数应当实现包括但不限于以下内容:为super()进行初始化、调用相关第三方包创建一个窗口、向Charmy控件层汇报窗口位置(透过设定window参数中传入的窗口的_pos属性)。

之后你需要实现__init__showupdateclose方法,具体实现方式可以参考其他后端的实现。本文将会持续补充这些内容。

如此,您便完成了窗口相关接口的开发。

编写图形后端接口

等待编写...

选定并使用您的后端

注意:鉴于本节之内容逐渐完整,并不再适合作为后端开发指南的一部分,故可能未来会被移至别处,届时将会提供超链接供跳转查阅。

Charmy提供了一种方便且优雅的途径来加载自定义后端,而并不需要提前设定环境变量(虽然这可能也会作为一种备选方式,并在将来实现)。

在您引入Charmy时,并不会加载任何后端(包括默认后端),若要手动指定后端,您需要在程式创建第一个窗口之前,手动创建CharmyManager实例,并指定要使用的后端。下文将会详细描述。

手动引入并加载后端

本节将会描述如何手动引入后端包并将其加载。

首先,您需要引入您要加载的后端。在文件头的import段,引入您的后端包,然后取得Backend类,这将是整个后端的入口点。注意,您并不需要显式地声明新变量并存储Backend

在此步骤中,我们假设您已经引入了Charmy,如果尚未引入,则请至文件头的import段将其引入。此时请将取得的Backend类传入charmy.CharmyManager(),作为参数来实例化一个CharmyManager(下称“管理器”)。

此管理器所管理的所有窗口将会基于您所给定的后端。

如果您决定在此应用中只使用这一个后端,那么此时可以直接跳入您的GUI逻辑。Charmy在初始化窗口时,会自动检查可用的管理器。如果只有一个可用,则会自动选定它。

如果您决定在此应用中使用多个后端,例如(包括但不限于):同时提供一个本地GUI和一个远程的WebUI界面,那么您需要为每个后端初始化一个管理器,然后为每个窗口指定所使用的管理器。

Charmy的后端和管理器为一对多关系,即每个管理器仅可使用一个后端,但您可以使用一个后端创建多个管理器。这样做可能会允许您更方便地为不同作用的窗口分组,但通常我们不会用到这一特性。

# The order of importing Charmy and backend does not matter
import your_local_backend
import your_web_backend
import charmy

my_local_manager = charmy.CharmyManager(your_local_backend.Backend)
my_web_manager = charmy.CharmyManager(your_web_backend.Backend)

local_gui_window = charmy.Window(my_local_manager)
remote_webui_window = charmy.Window(my_web_manager)

charmy.mainloop()

由Charmy自动搜索并加载,手动或自动选定后端

本节将会描述如何透过Charmy的后端加载器引入用户透过pip安装的后端,或令其自动选择最合适的后端。

注意:本节依据设计所写,部分功能尚未实现。

Charmy将会提供一个后端加载器,在charmy.backend.loader 中提供。后端加载器会提供用于加载后端的 load_backend函数,支持手动指定或自动搜索后端。

此加载器会搜索用户透过pip安装的所有后端库,然后,默认情况下自动选择最合适的后端(此时可能会参考环境变量),或者加载调用者指定的后端。

鉴于此部分的具体实现尚未定型,将不会提供实例代码。您可以透过Charmy提供的完善的类型提示和自动补全来进行基本探索。