d中声明式gui

原文
几个月前,我用D展示了白噪声应用并用了一点,但后来想添加其他噪声颜色和图界.
我在网上搜索了些代码来复制/粘贴噪音(并找到了以下代码:https://noisehack.com/generate-noise-web-audio-api/,D的一个优点是它很容易从其他语言移植代码.复制/粘贴,js几乎可在D中工作,
并且很容易用minigui.d界面加东西.
但是,当输入代码来实例化类,并附加事件监听器时,我想使它更自动一些.

用户端

所有这些都是预发布,如有更改,恕不通知.
下面是完整程序(依赖于arsd git master)

import arsd.simpleaudio;
import arsd.minigui;
import std.random;

void main() {
    auto window = new MainWindow("Noise App");

    auto ao = AudioOutputThread(true);

    enum Algorithm {
        brown,
        pink,
        white
    }

    @Container!HorizontalLayout(
        Container!VerticalLayout("default"),
        Container!(Style.maxWidth(32))("volume")
    )
    struct Control {
        private bool paused;

        Algorithm algorithm;

        @ControlledBy!Button("Start / Stop")
        void pause() {
            if(paused)
                ao.unpause();
            else
                ao.pause();
            paused = !paused;
        }

        @ControlledBy!VerticalSlider(0, 32000, 800)
        int volume = 3200; // really a short
    }

    Control control;

    window.addDataControllerWidget(&control);

    // 粉红
    float b0 = 0.0, b1 = 0.0, b2 = 0.0, b3 = 0.0, b4 = 0.0, b5 = 0.0, b6 = 0.0;

    // 棕色
    float lastOut = 0.0;

    ao.addChannel = delegate(short[] buffer) {
        const algorithm = control.algorithm;
        const volume = control.volume;
        foreach(ref item; buffer) {
            final switch(algorithm) with(Algorithm) {
            case white:
                item = cast(short) uniform(-volume, volume);
                break;

            case pink:
                float white = uniform(-1.0, 1.0);
                b0 = 0.99886 * b0 + white * 0.0555179;
                b1 = 0.99332 * b1 + white * 0.0750759;
                b2 = 0.96900 * b2 + white * 0.1538520;
                b3 = 0.86650 * b3 + white * 0.3104856;
                b4 = 0.55000 * b4 + white * 0.5329522;
                b5 = -0.7616 * b5 - white * 0.0168980;
                auto output = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
                output *= 0.11;//(粗略)补偿增益
                b6 = white * 0.115926;

                item = cast(short) (output * volume);
                break;

            case brown:
                float white = uniform(-1.0, 1.0);
                float output = (lastOut + (0.02 * white)) / 1.02;
                lastOut = output;
                output *= 3.5; //(粗略)补偿增益

                item = cast(short) (output * volume);
                break;
            }
        }
        return true;
    };

    window.loop();
}

// https://noisehack.com/generate-noise-web-audio-api/

当然,在窗口上,minigui使用本地控件,所以它就像它们一样.
现在,更深入研究代码.

@Container!HorizontalLayout(
    Container!VerticalLayout("default"),
    Container!(Style.maxWidth(32))("volume")
)
struct Control {
    Algorithm algorithm;

    @ControlledBy!Button("Start / Stop")
    void pause() {}

    @ControlledBy!VerticalSlider(0, 32000, 800)
    int volume = 3200;
}

Control control;

window.addDataControllerWidget(&control);

这段代码是用户端.底部,加数据控制器小部件(addDataControllerWidget)是使用UDA创建UI并拼接起来的必要事件处理器.
第一,构上的@Container,UDA允许定义布局.如果没有该布局,它就用给定父控件默认容器(在此窗口中,垂直排列子控件).
工作原理是按模板参数覆盖类名和/或风格,然后把串名和子项列表作为其他参数,可任意改造.
然后,构内部,有普通方法和数据成员,但又有额外的UDA.因为它是枚举,Algorithm有默认的组件(下拉式选取器).
我用@控件由!按钮(@ControlledBy!Button)代码覆盖了方法的默认值,指示按钮小部件应触发此调用.在此,按模板参数再次传递一个类,然后传递其构造器的一些参数.注意,没有传递parent参数,稍后调用添加数据控制器小部件(addDataControllerWidget)会处理.类似,没有设置事件处理器,因为稍后库会设置它.
小部件位置当前与名字关联.叫"volume"Container接收小部件体积变量.由于其他文件没有指定位置,因此位于"默认"容器中(或如果未指定默认值,就使用最后).
未来发展方向是从静态反射提取越来越多的数据,并在自动默认不充分时,使其容易自定义.
小提示:

Container!(Style.maxWidth(32))

是特例.我没有定义类,而是给予列举了我想在模板基类上覆盖的各个方法的一个列表.风格只是opDispatch构,产生稍后用来插件的方法.
不应用虚函数来处理.匿名嵌套类,script.d子类和其他各种技巧就够了,但仍然假设每个类都有些合适的静态值,一般是正确的,但并不总是正确的.而且它限制了像运行时加载css等.
内部,该覆盖列表用来按需生成新类.

内部

至少在早期阶段,实现是相当简单的.
一般想法是UDA分解为简单的运行时构:UI定义表和类工厂函数.复杂工作由处理这些数据普通运行时函数来完成.,也可在运行时数据定义中使用这些工具.(minigui应该既是小库,又允许用少量代码轻松添加gui特性),并且可透明生成并使用附加工厂.
数据绑定也是自动生成的,同时自动注册必要事件.目前按传统工作:它根据识别小部件设置事件,根据识别类型设置值.需要以后扩展.

深入了解一下代码.
加数据控制器小部件(addDataControllerWidget)是个只是数据控制器小部件模板(DataControllerWidget)UFCS函数.它是接受某个数据构父小部件指针的Widget的子类(稍后会添加一些其他接口).它检查指针类型,来提取注释构建数据树.
控制由(ControlledBy)也是造拥有工厂(私有构建(construct)方法)和参数(构成员)的ControlledBy_构的工厂函数.
更复杂的是容器(Container).它是继承给定基类,插件进给定类,然后定义调用操作(opCall)模板类.在UDA中调用opCall来返回包括实例化完整容器类的工厂函数指针的静态数据.
当然可用不同方法来实现该点.开始想法是按UDA,附加类自身,如:

@VerticalLayout
struct Control {
    @Button
    void pause() {}
}

可用调用操作(opCall)重载,但这表明改造所有的旧类.(哦,我希望static opCall(this This)()工作).
所以添加了外部项,容器(Container)和控制由(ControlledBy),但保持了相同的调用操作接口.有趣的是,也可在过程代码中新建容器(new Container!(...))!(...),及潜在地在更多环境中重用它.
静态调用操作(static opCall)返回ContainerMeta.这里没有模板,所有运行时数据都是在编译时创建的.可用简单可变函数语法创建树,并在稍后处理它.不值得创建完整编译时布局器,使用运行时数据表,让我重用现有代码,并可能用(如UI设计器工具)运行时文件和脚本语言链接起来.
一个开放问题是,不用设置数据值,如何改变UI,及反之.目前,UI可改变构,但构不能改变UI.我正在考虑使DataControllerWidget接口,在代码中提供它,及手动属性来额外定制其他勾挂.
D在GUI方面的真实的潜力并不在于实现底层原语和小部件,而是在于利用D的内省功能来创建新的方便的API.

posted @   zjh6  阅读(72)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示