6个版本

0.2.2 2020年8月17日
0.2.1 2020年8月5日
0.2.0 2020年7月23日
0.1.0 2020年6月19日
0.0.2 2020年2月20日

#405 in GUI

MIT许可协议

115KB
1.5K SLoC

Rust 1K SLoC // 0.1% comments JavaScript 163 SLoC // 0.1% comments

Myxine:一条帮助您快速获得GUI的滑溜海友

woodcut sketch of myxine glutinosa, the hagfish

盲鳗,又称Myxine glutinosa,是一种类似鳗鱼的海生生物,以其产生大量粘液的能力而闻名。

通过类比,myxine快速降低创建动态图形界面的摩擦,帮助您在任何海洋语言下快速获得GUI。

Myxine是一个本地Web服务器,使您能够从您喜欢的编程语言舒适地创建交互式应用程序。它旨在满足三个明确的目标

  1. 使非UI设计专业程序员能够在不学习复杂的UI框架的情况下构建吸引人的交互式应用程序。
  2. 使从几乎任何编程语言中编写正确的、高效的、符合语法的Myxine绑定变得容易。
  3. 尽可能快,同时尽可能少消耗资源。

这是它的工作方式

  1. 您启动Myxine并打开浏览器到它所提供的服务器页面。
  2. 从您选择的编程语言中,您向Myxine发送一些HTML,它立即出现,替换了该页面的<body>内容。
  3. 然后,您可以请求订阅您感兴趣的任何浏览器事件,当每个事件发生时,Myxine会通知您,跳过您不关心的那些。
  4. 然后,您可以对这些事件做出反应,再次更新页面,以反映您现在希望它看起来像什么。重复此过程!

Myxine处理优化此过程以最小化延迟和计算负载的繁重工作:它可以处理每秒数千个请求,并将它们转换为60帧每秒的平滑、无闪烁的动画。

安装

要安装 Myxine 服务器,您需要一个最新版本的 Rust 编程语言及其构建工具 cargo。如果您没有它,请访问这里了解 Rust 的快速安装方法。设置完成后,请安装 Myxine 服务器。

$ cargo install myxine

安装 Myxine 的客户端库需要遵循该库特定的步骤:请查阅相应库的文档,了解如何安装它。

构建交互式应用程序

您可以通过 HTTP 请求直接与服务器交互(有关详细信息,请参阅API 文档),但您很可能会选择使用您选择的语言的轻量级绑定库。目前,Myxine 项目官方维护的两个客户端库是针对 PythonHaskell 的。

如果您有兴趣为新的语言编写 Myxine 绑定,请阅读API 文档,并可能参考一个或多个现有客户端库。不要害怕通过创建一个问题来寻求帮助,并请通过提交拉取请求来贡献您的作品!

Python 示例

如果一张图片胜过千言万语,那么一次视觉交互值多少图片呢?

以下是 Python 中的一个简单、完整的 Myxine 应用程序。要使此示例正常工作,您需要安装 Python 客户端库

$ pip3 install myxine-client

要运行此示例,首先确保您的计算机上正在运行 myxine

$ myxine
Running at: http://127.0.0.1:1123

然后,在另一个终端窗口中运行脚本

$ ./examples/python/follow.py

最后,在您的网页浏览器中导航到 https://127.0.0.1:1123,并进行操作!您可以在终端中按 Ctrl+C 停止应用程序。

您可以在examples 目录中找到此示例和其他示例,按实现语言分类的子目录。

无需多言,这里是 follow.py

#!/usr/bin/env python3

import random
import myxine

class Page:
    # The model of the page
    def __init__(self):
        self.x, self.y = 150, 150
        self.hue = random.uniform(0, 360)
        self.radius = 75

    # Draw the page's model as a fragment of HTML
    def draw(self):
        circle_style = f'''
        position: absolute;
        height: {round(self.radius*2)}px;
        width: {round(self.radius*2)}px;
        top: {self.y}px;
        left: {self.x}px;
        transform: translate(-50%, -50%);
        border-radius: 50%;
        border: {round(self.radius/2)}px solid hsl({round(self.hue)}, 80%, 80%);
        background: hsl({round(self.hue+120)}, 80%, 75%)
        '''
        background_style = f'''
        position: absolute;
        overflow: hidden;
        width: 100vw;
        height: 100vh;
        background: hsl({round(self.hue-120)}, 80%, 90%);
        '''
        instructions_style = f'''
        position: absolute;
        bottom: 30px;
        left: 30px;
        font-family: sans-serif;
        font-size: 22pt;
        user-select: none;
        '''
        return f'''
        <div style="{background_style}">
            <div style="{instructions_style}">
                <b>Move the mouse</b> to move the circle<br/>
                <b>Scroll</b> to change the circle's size<br/>
                <b>Ctrl + Scroll</b> to change the color scheme<br/>
                <b>Click</b> to randomize the color scheme<br/>
            </div>
            <div style="{circle_style}"></div>
        </div>
        '''

    # Change the page's model in response to a browser event
    def react(self, event):
        if event.event() == 'mousemove':
            self.x = event.clientX
            self.y = event.clientY
        elif event.event() == 'mousedown':
            self.hue = (self.hue + random.uniform(30, 330)) % 360
        elif event.event() == 'wheel':
            if event.ctrlKey:
                self.hue = (self.hue + event.deltaY * -0.1) % 360
            else:
                self.radius += event.deltaY * -0.2
                self.radius = min(max(self.radius, 12), 1000)

    # The page's event loop
    def run(self, path):
        myxine.update(path, self.draw())          # Draw the page in the browser.
        try:
            for event in myxine.events(path):     # For each browser event,
                self.react(event)                 # update our model of the page,
                myxine.update(path, self.draw())  # then re-draw it in the browser.
        except KeyboardInterrupt:
            pass                                  # Press Ctrl-C to quit.

if __name__ == '__main__':
    Page().run('/')  # Run the page on the root path.

在上文中,您可以看到 Myxine 交互模型的每个步骤都表示为独立的 Python 构造

  • Page 类具有跟踪应用程序状态的字段。
  • draw 函数是从当前应用程序状态到其作为 HTML 的表示的纯函数。
  • react 函数在浏览器中发生某些事件后更新 Page 的状态。
  • run 函数通过在浏览器中循环所有事件,针对每个事件更新应用程序状态,并将新的 HTML 主体发送到浏览器,立即显示给您来将所有这些组合在一起!

尽管不同的客户端库可能使用不同的语言特定习惯用法来表示此模式,但基本结构是相同的。尽管它很简单,但它运行得很快!

依赖项

~16–27MB
~415K SLoC