DlangUI — кросплатформенный GUI для D (Часть 1)

    Мне нравится язык D. Давно слежу за его развитием. Для D есть несколько GUI библиотек и биндингов, но я решил изобрести свой велосипед.
    Хочу рассказать о своём проекте DlangUI. Надеюсь, что он кому-нибудь будет полезен.



    На КДПВ скриншот DlangIDE — приложения, написанного на DlangUI.

    Особенности:

    • Кроссплатформенность — поддерживаются Windows, Linux, Mac OSX; легкость портирования на другие платформы
    • Написан на D — легкорасширяемый
    • Использование Layouts для позиционирования элементов интерфейса
    • Масштабирование шрифтов и иконок в приложении в зависимости от разрешения экрана
    • Поддержка Unicode
    • Интернационализация — поддержка перевода UI на несколько языков
    • Аппаратное ускорение с помощью OpenGL (опционально)
    • Возможность отрисовки виджетов поверх OpenGL сцены (например, для UI в игре)
    • Небольшой размер исполняемого файла
    • Внешний вид интерфейса настраивается с помощью тем (две стандартные темы — светлая и темная)
    • Встраивание ресурсов в исполняемый файл
    • Открытый исходный код, под лицензией Boost License 1.0


    Еще пара скриншотов


    Демо DlangUI — example1



    Демо DlangUI — Tetris



    Зачем нужна еще одна GUI библиотека?


    Для D имеется немало GUI библиотек. Полный список можно найти на wiki.dlang.org
    Если биндинги к GTK, Qt, wxWidgets, FLTK, и даже порт SWT с Java на D (DWT).Но они тянут с собой много зависимостей, сложно расширять набор виджетов, менять их внешний вид.
    Нативные, написанные на D, DFL и DGUI — работают только под Windows.
    Поэтому написание своего GUI велосипеда не такая уж и глупая затея.

    Начнем знакомство с Hello, World


    Чтобы собрать и запустить приложение на DlangUI, нам понадобится компилятор D (например, dmd) и DUB (build tool и менеджер зависимостей). Скачайте и установите их, если их еще нет.
    Создайте директорию для проекта, в ней создайте файл проекта для DUB — dub.json
    {
        "name": "helloworld",
        "targetPath": "bin",
        "targetName": "helloworld",
        "targetType": "executable",
    
        "dependencies": {
            "dlangui": "~master",
        }
    }
    


    Также, в поддиректории src создайте файл src/helloworld.d с таким содержимым:
    module app;
    // импортируем библиотеку dlangui
    import dlangui;
    
    // поместить объявление main или WinMain в этот файл
    mixin APP_ENTRY_POINT;
    // точка входа в приложение DlangUI - вызывается из main после инициализации библиотеки
    extern (C) int UIAppMain(string[] args) {
        // создаем окно
        Window window = Platform.instance.createWindow("DlangUI example - HelloWorld", null);
        // создаем кнопку и устанавливаем ее как основной виджет окна
        window.mainWidget = (new Button()).text("Hello, world!"d).margins(Rect(20,20,20,20));
        // показываем окно
        window.show();
        // цикл сообщений
        return Platform.instance.enterMessageLoop();
    }
    

    Более простой способ создания нового проекта DUB - с помощью dub init

    Есть более удобный способ создания нового проекта DUB. Воспользуйтесь командой
    dub init helloworld
    

    DUB создаст директорию с указанным именем, файл dub.json, .gitignore и source/app.d
    Начальное содержимое dub.json:
    {
    	"name": "helloworld",
    	"description": "A minimal D application.",
    	"copyright": "Copyright © 2015, username",
    	"authors": ["username"],
    	"dependencies": {
    	}
    }
    

    Просто добавьте сюда зависимость dlangui
        "dependencies": {
            "dlangui": "~master",
        }
    

    Содержимое файла app.d замените на код helloworld


    Теперь мы можем запустить приложение. В командной строке, в директории проекта с dub.json выполните команду:
    dub run
    

    При успешной компиляции приложение сразу запустится. Окно с единственной кнопкой:


    Также можете посмотреть примеры из dlangui (демо почти всех виджетов example1 и игра tetris):
    dub fetch dlangui
    dub run dlangui:example1
    dub run dlangui:tetris
    


    Еще одно приложение — DlangIDE:
    dub fetch dlangide
    dub run dlangide
    


    Widgets & Layouts


    Усложним наше приложение. Добавим несколько виджетов.

    Будем использовать простые виджеты:
    • TextWidget — текст
    • Button — кнопка с текстом
    • ImageButton — кнопка с картинкой
    • ImageTextButton — кнопка с картинкой и текстом
    • CheckBox — понятно из названия
    • RadioButton — понятно из названия
    • ImageWidget — картинка
    • EditLine — однострочный редактор
    • ComboBox — комбобокс — для выбора элемента из выпадающего списка


    Пример создания простой текстовой кнопки:
    auto btn = new Button("btn1", "Button 1"d);
    


    Здесь «btn1» — это идентификатор виджета, обычно использоваться для его поиска в родительском виджете или для того, чтобы отличать один виджет от другого в общем обработчике событий.

    «Button 1»d — текст кнопки. Обратите внимание на суффикс d — это utf32 — dstring. Обычно в конструкторах виджетов DlangUI в качестве текста может передаваться сам текст — как utf32 dstring, или идентификатор строкового ресурса как обычный string — для поддержки перевода интерфейса на несколько языков.

    Виджеты могут иметь вложенные виджеты.
    Layouts — виджеты-контейнеры для выравнивания других виджетов. Похожи на используемые в Android UI:
    • VerticalLayout — расположить вложенные виджеты по вертикали
    • HorizontalLayout — расположить вложенные виджеты по вертикали
    • TableLayout — расположить вложенные виджеты в несколько столбцов, как в таблице


    Создание VerticalLayout и добавление в него пары кнопок:
        auto vlayout = new VerticalLayout(); // расположить элементы по вертикали
        vlayout.addChild(new RadioButton("radio1", "Radio Button 1"d));
        vlayout.addChild(new RadioButton("radio2", "Radio Button 2"d));
    

    Исправим наш пример — сделаем форму со сложной структурой.
    module app;
    // импортируем библиотеку dlangui
    import dlangui;
    
    // поместить объявление main или WinMain в этот файл
    mixin APP_ENTRY_POINT;
    // точка входа в приложение DlangUI - вызывается из main после инициализации библиотеки
    extern (C) int UIAppMain(string[] args) {
        // создаем окно
        Window window = Platform.instance.createWindow("DlangUI example - HelloWorld", null);
    
        // основной виджет окна - располагаем все, что внутри него по вертикали
        auto mainWidget = new VerticalLayout();
    
        mainWidget.addChild(new TextWidget(null, "пример HorizontalLayout:"d)); // заголовок
    
        auto hlayout = new HorizontalLayout(); // расположить элементы по вертикали
        hlayout.addChild(new Button("btn1", "Кнопка 1"d));
        hlayout.addChild(new Button("btn2", "Кнопка 2"d));
        hlayout.addChild(new Button("btn3", "Кнопка 3"d));
        hlayout.addChild(new CheckBox("btn4", "Пример CheckBox"d));
        mainWidget.addChild(hlayout);
    
        mainWidget.addChild(new TextWidget(null, "пример VerticalLayout:"d)); // заголовок
    
        auto vlayout = new VerticalLayout(); // расположить элементы по вертикали
        vlayout.addChild(new RadioButton("radio1", "Radio Button 1"d));
        vlayout.addChild(new RadioButton("radio2", "Radio Button 2"d));
        vlayout.addChild(new RadioButton("radio3", "Radio Button 3"d));
        mainWidget.addChild(vlayout);
    
        mainWidget.addChild(new TextWidget(null, "пример TableLayout - форма с 2 столбцами:"d)); // заголовок
    
        auto tlayout = new TableLayout(); // таблица / форма
        tlayout.colCount = 2;
        tlayout.addChild(new TextWidget(null, "Строка ввода"d));
        tlayout.addChild(new EditLine("edit1", "Какой-то текст для редактирования"d));
        tlayout.addChild(new TextWidget(null, "ComboBox"d));
        tlayout.addChild((new ComboBox("combo1", ["Значение 1"d, "Значение 2"d, "Значение 3"d])).selectedItemIndex(0));
        tlayout.addChild(new TextWidget(null, "Группа RadioButton"d));
        // внутри Layout может быть другой Layout:
        auto radiogroup = new VerticalLayout();
        radiogroup.addChild(new RadioButton("rb1", "Значение 1"d));
        radiogroup.addChild(new RadioButton("rb2", "Значение 2"d));
        radiogroup.addChild(new RadioButton("rb3", "Значение 3"d));
        tlayout.addChild(radiogroup);
        tlayout.addChild(new TextWidget(null, "Кнопка ImageTextButton"d));
        tlayout.addChild(new ImageTextButton("btn_ok", "dialog-ok-apply", "Текст кнопки"d));
    
        mainWidget.addChild(tlayout);
    
        // создаем кнопку и устанавливаем ее как основной виджет окна
        window.mainWidget = mainWidget;
        // показываем окно
        window.show();
        // цикл сообщений
        return Platform.instance.enterMessageLoop();
    }
    


    Вот что у нас получилось:


    Обработка сигналов (событий) от виджетов



    Рассмотрим обработку сигналов на примере сигнала onClick

    Добавим обработчики нажатия на кнопки — пусть переключают тему интерфейса.

    В нашем примере меняем кусок кода с RadioButton в VerticalLayout.

        mainWidget.addChild(new TextWidget(null, "Выбор темы интерфейса:"d));
    
        auto vlayout = new VerticalLayout();
        // addChild() возвращает добавленный вижет, и большинство методов установки свойств виджета возвращают сам виджет, 
        // поэтому можно вызывать несколько методов по цепочке.
        vlayout.addChild(new RadioButton("radio1", "Обычная"d)).checked(true).onClickListener = delegate(Widget src) {
            platform.instance.uiTheme = "theme_default";
            return true;
        };
        vlayout.addChild(new RadioButton("radio2", "Тёмная"d)).onClickListener = delegate(Widget src) {
            platform.instance.uiTheme = "theme_dark";
            return true;
        };
        mainWidget.addChild(vlayout);
    


    Вот что получилось:


    Пояснения:

    onClickListener — сигнал, доступный в любом виджете.Вот как он описан:
    /// interface - slot for onClick
    interface OnClickHandler {
        bool onClick(Widget source);
    }
    //.....
    class Widget {
    //...
        Signal!OnClickHandler onClickListener;
    //...
    }
    

    Обработчиком может служить делегат подходящего типа.

    Подключать обработчик событий можно по разному.
    Пример обрабочика onClick — обычный делегат
        auto button1 = new Button("btn1", "Кнопка 1"d);
        button1.onClickListener = delegate(Widget src) {
            window.showMessageBox(UIString("Обработчик onClick"d), UIString("Вызван\ndelegate"d));
            return true;
        };
    


    Пример обрабочика onClick — метод класса
        class MyOnClickHandler1 {
            bool onButtonClick(Widget src) {
                src.window.showMessageBox(UIString("Обработчик onClick"d), 
                    UIString("Вызван MyOnClickHandler1.onClick\nиз виджета с id="d ~ to!dstring(src.id)));
                return true;
            }
        }
        auto memberFunctionHandler = new MyOnClickHandler1();
        auto button2 = new Button("btn2", "Кнопка 2"d);
        button2.onClickListener = &memberFunctionHandler.onButtonClick;
        hlayout.addChild(button2);
    


    Пример обрабочика onClick — класс, определяющий интерфейс, использованный при определении сигнала
        // пример обрабочика onClick - класс, определяющий интерфейс сигнала
        class MyOnClickHandler2 : OnClickHandler {
            override bool onClick(Widget src) {
                src.window.showMessageBox(UIString("Обработчик onClick"d), 
                          UIString("Вызван MyOnClickHandler2.onClick\nиз виджета с id="d ~ to!dstring(src.id)));
                return true;
            }
        }
        auto interfaceHandler = new MyOnClickHandler2();
        auto button4 = new Button("btn4", "Показать сообщение 4"d);
        button2.onClickListener = interfaceHandler; // нужный метод onClick будет взят из интерфейса OnClickHandler
    


    Другие полезные сигналы из класса Widget:
    • onCheckChangeListener — состояние checked изменено (например, для CheckBox, RadioButton)
    • onFocusChangeListener — изменено состояние фокуса этого виджета
    • onKeyListener — перехват событий от клавиатуры
    • onMouseListener — перехват событий мыши



    Часто используемые свойства виджета


    • margins — отступ от соседних виджетов или границ контейнера (фон виджета рисуется с отступом на margins)
    • padding — отступ от границ виджета до его внутренних элементов
    • backgroundColor — цвет фона (32 bit uint, 0xAARRGGBB)
    • backgroundImageId — фоновое изображение — id ресурса
    • textColor — цвет текста (32 bit uint, 0xAARRGGBB)
    • fontSize — размер шрифта

    Эти и многие другие свойства можно задавать как напрямую, так и в виде стилей (определяются в файле — теме).
    Назначить стиль можно с помощью свойства styleId. Например, поменяем стиль заголовка у выбора темы интерфейса.
        mainWidget.addChild(new TextWidget(null, "Выбор темы интерфейса:"d)).styleId("POPUP_MENU");
    


    Пример: добавим отступов к основному виджету и сделаем ему полупрозрачный желтый фон.
        // отступ от границ окна на 10 пикселей, вложенные виджеты будут располагаться с отступом 15 пикселей
        mainWidget.margins(Rect(10, 10, 10, 10)).padding(Rect(15, 15, 15, 15));
        mainWidget.backgroundColor(0xC0FFFF00); // полупрозрачный желтый фон
    


    У TableLayout назначим фоновой картинкой «btn_default.png». Id ресурса — это имя файла без расширения. Расширения .9.png обозначают nine-patch — масштабируемое излбражение, как в Android.
    Добавим также padding — отступ для вложенных виджетов.
        tlayout.backgroundImageId("btn_default"); // фон от кнопки - btn_default.9.png из стандартных ресурсов
        tlayout.padding(Rect(5, 5, 5, 5)); // отступ для вложенных виджетов - 5 пикселей
    

    В TextWidget заголовка для TableLayout поменяем размер и цвет шрифта.
        tlayout.backgroundImageId("btn_default"); // фон от кнопки - btn_default.9.png из стандартных ресурсов
        tlayout.padding(Rect(5, 5, 5, 5)); // отступ для вложенных виджетов - 5 пикселей
    

    Вот, что получилось:

    Текущий код helloworld.d:
    module app;
    // импортируем библиотеку dlangui
    import dlangui;
    
    // поместить объявление main или WinMain в этот файл
    mixin APP_ENTRY_POINT;
    // точка входа в приложение DlangUI - вызывается из main после инициализации библиотеки
    extern (C) int UIAppMain(string[] args) {
        // создаем окно с изменяемым размером, начальный размер - 800x600
        Window window = Platform.instance.createWindow("DlangUI example - HelloWorld", null, WindowFlag.Resizable, 600, 400);
    
        // основной виджет окна - располагаем все, что внутри него по вертикали
        auto mainWidget = new VerticalLayout();
        // отступ от границ окна на 10 пикселей, вложенные виджеты будут располагаться с отступом 15 пикселей
        mainWidget.margins(Rect(10, 10, 10, 10)).padding(Rect(15, 15, 15, 15));
        mainWidget.backgroundColor(0xC0FFFF00); // полупрозрачный желтый фон
    
        mainWidget.addChild(new TextWidget(null, "пример HorizontalLayout:"d)); // заголовок
    
        auto hlayout = new HorizontalLayout(); // расположить элементы по вертикали
        // пример обрабочика onClick - делегат
        auto button1 = new Button("btn1", "Кнопка 1"d);
        button1.onClickListener = delegate(Widget src) {
            window.showMessageBox(UIString("Обработчик onClick"d), UIString("Вызван\ndelegate"d));
            return true;
        };
        hlayout.addChild(button1);
       
        // пример обрабочика onClick - метод класса
        class MyOnClickHandler1 {
            bool onButtonClick(Widget src) {
                src.window.showMessageBox(UIString("Обработчик onClick"d), 
                    UIString("Вызван MyOnClickHandler1.onClick\nиз виджета с id="d ~ to!dstring(src.id)));
                return true;
            }
        }
        auto memberFunctionHandler = new MyOnClickHandler1();
        auto button2 = new Button("btn2", "Кнопка 2"d);
        button2.onClickListener = &memberFunctionHandler.onButtonClick;
        hlayout.addChild(button2);
    
        // можно использовать один и тот же обработчик сигнала для нескольких источников
        hlayout.addChild(new Button("btn3", "Кнопка 3"d)).onClickListener = &memberFunctionHandler.onButtonClick;
    
        // пример обрабочика onClick - класс, определяющий интерфейс сигнала
        class MyOnClickHandler2 : OnClickHandler {
            override bool onClick(Widget src) {
                src.window.showMessageBox(UIString("Обработчик onClick"d), 
                    UIString("Вызван MyOnClickHandler2.onClick\nиз виджета с id="d ~ to!dstring(src.id)));
                return true;
            }
        }
        auto interfaceHandler = new MyOnClickHandler2();
        auto button4 = new Button("btn4", "Показать сообщение 4"d);
        button2.onClickListener = interfaceHandler; // нужный метод onClick будет взят из интерфейса OnClickHandler
        hlayout.addChild(button4);
        
        mainWidget.addChild(hlayout);
    
        mainWidget.addChild(new TextWidget(null, "Выбор темы интерфейса:"d)).styleId("POPUP_MENU");
    
        auto vlayout = new VerticalLayout();
        vlayout.addChild(new RadioButton("radio1", "Обычная"d)).checked(true).onClickListener = delegate(Widget src) {
            platform.instance.uiTheme = "theme_default";
            return true;
        };
        vlayout.addChild(new RadioButton("radio2", "Тёмная"d)).onClickListener = delegate(Widget src) {
            platform.instance.uiTheme = "theme_dark";
            return true;
        };
        mainWidget.addChild(vlayout);
    
        // в этом заголовке поменяем цвет и размер шрифта, и выровняем его по горизонтали вправо
        mainWidget.addChild(new TextWidget(null, "пример TableLayout - форма с 2 столбцами:"d)).textColor(0xC00000).fontSize(26).alignment(Align.Right);
    
        auto tlayout = new TableLayout(); // таблица / форма
        tlayout.backgroundImageId("btn_default"); // фон от кнопки - btn_default.9.png из стандартных ресурсов
        tlayout.padding(Rect(5, 5, 5, 5)); // отступ для вложенных виджетов - 5 пикселей
        tlayout.colCount = 2;
        tlayout.addChild(new TextWidget(null, "Строка ввода"d));
        tlayout.addChild(new EditLine("edit1", "Какой-то текст для редактирования"d));
        tlayout.addChild(new TextWidget(null, "ComboBox"d));
        tlayout.addChild((new ComboBox("combo1", ["Значение 1"d, "Значение 2"d, "Значение 3"d])).selectedItemIndex(0));
        tlayout.addChild(new TextWidget(null, "Группа RadioButton"d));
        // внутри Layout может быть другой Layout:
        auto radiogroup = new VerticalLayout();
        radiogroup.addChild(new RadioButton("rb1", "Значение 1"d));
        radiogroup.addChild(new RadioButton("rb2", "Значение 2"d));
        radiogroup.addChild(new RadioButton("rb3", "Значение 3"d));
        tlayout.addChild(radiogroup);
        tlayout.addChild(new TextWidget(null, "Кнопка ImageTextButton"d));
        tlayout.addChild(new ImageTextButton("btn_ok", "dialog-ok-apply", "Текст кнопки"d));
    
        mainWidget.addChild(tlayout);
    
        // создаем кнопку и устанавливаем ее как основной виджет окна
        window.mainWidget = mainWidget;
        // показываем окно
        window.show();
        // цикл сообщений
        return Platform.instance.enterMessageLoop();
    }
    

    Эта же программа, запущенная на Ubuntu: отличаются только рамка окна и шрифты.


    Размер helloworld.exe, построенного dmd2 под windows (dub build --build=release) — 1.4Mb, из них 200K занимают ресурсы.
    При наличии libfreetype-6.dll (700K) и zlib1.dll (84K) — автоматически копируются DUB в директорию bin — использует FreeType для рендеринга шрифтов, иначе — win32 API.

    Бинарный файл, построенный в Ubuntu x64 c помощью dmd — 4Mb.

    Другие полезные виджеты


    • EditBox — многострочный редактор
    • TreeWidget — дерево
    • StringGrid — таблица, похожая на Excel
    • TabWidget — табы — для выбора страниц
    • AppFrame — базовый класс основного виджета окна с меню, тулбарами и стстус-строкой, для удобства написания таких приложений.
    • ScrollBar
    • ToolBar
    • StatusLine
    • ScrollWidget — реализует скроллинг вложенного в него виджета, если размеры превышают доступное место
    • DockHost, DockWidget — для приложений с док-виджетами по краям от основного виджета окна (как в IDE)


    Ссылки




    Продолжение следует...



    Нужна ли следующая часть? О чем еще рассказать? Пишите в комментах…

    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 48
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        Могу добавить. Выглядит точно так же, только рамка окна другая и шрифты.
        • 0
          Добавил скриншот для Ubuntu.
          На Маке проверить не могу — нет его у меня.
          • 0
            Жаль, что не мимикрирует под системные темы.
            • 0
              Можно сделать темы под каждую систему похожие на нативные.
              Еще проблемы — меню на маке и Unity. Должно быть в системном меню, вместо этого — в верхней части окна. Смотрится чужеродно.

            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                Под windows 32bit — 1.4Mb, linux 64bit — 4Mb
                Почему под Linux больше — непонятно.
                • 0
                  Как я писал ниже — компиляторы весьма плохие. Я бы посоветовал LDC2, т.к. на данный момент он генерирует самый компактный код.
                  • 0
                    Windows — dmd2.0.66 helloworld (подсчет средней длины строки с stdin) занимает 189K
                  • 0
                    Вообще, именно с компиляторами было наибольшее разочарование. В учебнике Андрей Александреску делал особый акцент на том, что именно компилятор должен делать всю «грязную» работу. На деле, они (компиляторы) вообще мало что могут.
                    З.ы. Если кому нужен русский учебник по D — обращайтесь, с удовольствием предоставлю сразу в PDF. Не знаю как сейчас, но раньше было очень проблемно её достать.
                    • 0
                      Обращаюсь. Давно хотел освоить D. Моя почта у Гугла совпадает с ником.
                      • НЛО прилетело и опубликовало эту надпись здесь
                    • 0
                      Для Qt 4 есть пара биндингов.

                      Не знаю, насколько эти проекты живые.
                • 0
                  image
                  на маке — как-то так
                  • 0
                    Это нормально, окно только маленькое. Надо вручную растянуть.
              • 0
                > Возможность отрисовки виджетов поверх OpenGL сцены (например, для UI в игре)

                Хотелось бы туториала именно про этому пункту. Мы давно приглядываемся к DlangUI, чтобы не городить очередных велосипедов.

                У нас используется DSFML для создания контекста, а внутрях репозитория находили только адаптеры для SDL. Насколько сложно реализовать обертку для DSFML?
                • 0
                  Примера/туториала нет, но можно сделать.
                  Есть какой-нибудь DSFML проект, на котором можно поэкспериментировать?
                  Надо реализовать Platform и Window под DSFML.
                  • 0
                    На данный момент открытого проекта нет, основной, к сожалению, проприетарный и очень тяжеловесный.
                • +1
                  Отличный повод начать изучать язык.
                  Возможно стоит продолжить по ходу появления вопросов у читателей и накопления критической массы материала для новой статьи.
                  Спасибо.
                  • +1
                    Да, следующая часть нужна, хотелось бы объяснения принципа работы, как определяете что надо перерисовывать а что нет, используются ли регионы отсечения.
                    • +1
                      Подумаю, что интересного можно рассказать.

                      А если коротко, используется двухфазная модель measure / layout как в Android.
                      Перерисовка всегда идет полностью.
                      При включенном OpenGL все равно только полностью можно перерисовывать.

                      Есть разделение на основную часть и backend — код для поддержки конкретной платформы (создание окон, отрисовка bitmap или создание OpenGL контекста, трансляция событий мыши и клавиатуры, ...).

                      Графика может рисоваться в bitmap буфер c помощью software renderer, или в OpenGL контекст. Общий интерфейс прячет реализацию.
                    • +1
                      Действительно классный язык. Год назад сыграл большую роль в расширении моего сознания. Правда, тогда всё было очень плохо реализовано. Так обычный «Hello, world!», после компиляции GDC в GNU/Linux, превращался в бинарник весом 10Мб. Я проверял и с последней, пятой версией, — ничего не изменилось. LDC2 просто вешал систему при компиляции библиотек, вроде viber.d.
                      Нужно отметить, что по большей мере именно этот язык катализировал рост C++, заменой которого он пытался быть. Сейчас же, области для применения вообще не очевидны, т.к. практически всё множество киллер-фич перекочевало в C++11/14.
                      • +1
                        Пока еще не догнал.
                        CTFE и метапрограммирование в D все еще на порядок лучше.
                        Чистый синтаксис.
                        Многое без garbage collector реализовать сложно или невозможно.
                        mixins — навряд ли когда-нибудь будут в C++.
                        • 0
                          LDC я попробовал — он тормозной. Один файл компилирует столько же времени, сколько DMD — проект из сотни файлов.
                          Для разработки не очень подходит из-за тормознутости.
                          Скорее для финального билда.
                          • 0
                            Да, это частая практика — использовать DMD для разработки, но собирать финальную версию с помощью GDC/LDC.
                            Для идиоматического кода в стиле Phobos LDC сейчас создаёт самые компактные бинарники с большим отрывом, т.к. по умолчанию включено --gc-sections для линкера.
                        • 0
                          По-моему, у проекта немалый потенциал. Развивайте!

                          Скажите, а декларативность описания UI, будет, как в Sciter? CSS или аналог обязателен. Хороший UI в наше время должны делать профессионалы с помощью профессиональных инструментов, а не кодом.
                          • +1
                            Скоро будет возможность загрузки layout из xml (как в Android) или чего-то похожего на QML. Выбираю формат. (Issue #58 в трекере)

                            Планируется автоматическая привязка загруженных виджетов к полям класса при совпадении ID виджета и названия переменной (например, в ресурсе есть Button с id=«button1», а в классе — переменная Button _button1 — после загрузки ссылка на созданный виджет будет сохранена в эту переменную), а так же автоматическое назначение обработчиков сигналов — по id виджета и названию (например, если есть виджет с id=«button1» а в классе — метод bool onButton1Click(Widget src) — этот метод будет назначен на onClickListener загруженного виджета.
                            • 0
                              А, понятно, VB-style. Иногда бывает нужно назначить один обработчик для многих однотипных контролов, предусмотрите это.

                              И подумайте об отделении стилей от layout-а, это очень важно.
                              • 0
                                Стили есть в теме.
                                Можно назначить Id стиля виджету. По умолчанию у стандартных виджетов уже выставлен id родного стиля.
                                Можно переопределить часть свойств стиля у конкретного виджета — остальные будут браться из стиля темы.
                                Если не переопределять никаких свойств стиля у виджета — будет напрямую использоваться стиль из темы.
                                При изменении таких свойств создается новый экземпляр стиля (copy-on-write) с указанием базового стиля — из темы.
                          • 0
                            Крайне не хватает реактивности.

                            Как реализована кроссплатформенность? Через какие-то кроссплатформенные графические библиотеки или кучей прагм под разные платформы? Интересуюсь, потому-что тоже хочу что-то такое замутить.
                            • 0
                              Есть слой абстракции от платформы.
                              Для конкретной платформы надо определить backend — наследников классов Platform и Window.
                              Для win32 это занимает 72Kb, для sdl — 54K.
                              Если есть возможность (и dlangui построен с USE_OPENGL), отрисовка идет через OpenGL.
                              Иначе платформа должна определить способ отрисовки ARGB битмапа в окно.
                              Отрисовка графики идет с использованием интерфейса DrawBuf — в котором реализованы примитивы рисования прямоугольника, битмапа, символа текста, и т.д.
                              Также backend должен обеспечить трансляцию событий от мыши и клавиатуры в события dlangui.
                              • 0
                                Ну, то есть эти бекенды — это ваш велосипед и нет уже чего-то готового?
                                • 0
                                  win32 backend — это велосипед на базе win32 api
                                  sld2 backend — адаптер между sdl2 и dlangui
                                  • 0
                                    Зачем тогда win32 если SDL «officially supports Windows, Mac OS X, Linux, iOS, and Android»?
                                    • 0
                                      чтобы необязательна DLL от SDL2 была.
                                      и можно было более легковесное приложение сделать, вообще без внешних зависимостей.
                                      около 900К
                                      а так, под win32 dlangui тоже можно собрать с поддержкой SDL — работает нормально
                            • 0
                              Обработка сигналов (событий) от виджетов
                              На один «Listener» можно навесить только один обработчик? А если захочется парадигмы сигналов-слотов как в Qt «многие ко многим» и с передачей каких-либо аргументов? Придётся делать обработчик-прокси и пихать в него всю логику?

                              Наверное вопрос вкуса и может в D так заведено с именованием в API, но для чего все эти сложные Java-like имена, вроде button.onClickListener, разве не было бы проще и лаконичнее что-то вроде button.clicked?
                              • НЛО прилетело и опубликовало эту надпись здесь
                                • 0
                                  Когда я говорил «Java-like», я имел в виду не стиль написания objectName/object_name/..., а именование атрибутов в принципе. В Qt тоже используется objectName конвенция, но имена там обычно проще и менее громоздкие.

                                  Логическая переменная у меня ассоциируется с isClicked. :)
                                  • 0
                                    Никогда не работали с JavaScript?
                                • +1
                                  Если сигнал объявлен как
                                  Signal!OnClickHandler — то можно вешать несколько обработчиков, с помощью connect и отключать из с помощью disconnect. Присваивание отсоединяет все подключенные слоты и заменяет их одним новым. С помощью ~= новый слот добавляется к имеющимся (аналог connect), -= отключает (то же, что disconnect).
                                  Если объявлен как
                                  Listener!OnClickHandler — то возможено подключение только одного слота
                                  Listener используется вместо Signal там, где хочется сэкономить память ценой урезания функциональности (Listener в два раза меньше занимает памяти, чем Signal).

                                  import dlangui.core.signals;
                                  
                                  interface SomeInterface {
                                      bool someMethod(string s, int n);
                                  }
                                  class Foo : SomeInterface {
                                  	override bool someMethod(string s, int n) {
                                  		writeln("someMethod called ", s, ", ", n);
                                  		return n > 10; // can return value
                                  	}
                                  }
                                  
                                  // Listener! can hold arbitrary number of connected slots
                                  
                                  // declare using list of return value and parameter types
                                  Listener!(bool, string, n) signal1;
                                  
                                  Foo f = new Foo();
                                  // when signal is defined as type list, you can use delegate
                                  signal1 = bool delegate(string s, int n) { writeln("inside delegate - ", s, n); return false; }
                                  // or method reference
                                  signal1 = &f.someMethod;
                                  
                                  // declare using interface with single method
                                  Listener!SomeInterface signal2;
                                  // you still can use any delegate
                                  signal2 = bool delegate(string s, int n) { writeln("inside delegate - ", s, n); return false; }
                                  // but for class method which overrides interface method, you can use simple syntax
                                  signal2 = f; // it will automatically take &f.someMethod
                                  
                                  
                                  // call listener(s) either by opcall or explicit emit
                                  signal1("text", 1);
                                  signal1.emit("text", 2);
                                  signal2.emit("text", 3);
                                  
                                  // check if any slit is connected
                                  if (signal1.assigned)
                                  	writeln("has listeners");
                                  
                                  // Signal! can hold arbitrary number of connected slots
                                  
                                  // declare using list of return value and parameter types
                                  Signal!(bool, string, n) signal3;
                                  
                                  // add listeners via connect call
                                  signal3.connect(bool delegate(string, int) { return false; });
                                  // or via ~= operator
                                  signal3 ~= bool delegate(string, int) { return false; };
                                  
                                  // declare using interface with single method
                                  Signal!SomeInterface signal4;
                                  
                                  // you can connect several slots to signal
                                  signal4 ~= f;
                                  signal4 ~= bool delegate(string, int) { return true; }
                                  
                                  // calling of listeners of Signal! is similar to Listener!
                                  // using opCall
                                  bool res = signal4("blah", 5);
                                  // call listeners using emit
                                  bool res = signal4.emit("blah", 5);
                                  
                                  // you can disconnect individual slots
                                  // using disconnect()
                                  signal4.disconnect(f);
                                  // or -= operator
                                  signal4 -= f;
                                  
                                  


                                  • 0
                                    Экономия памяти настолько критична и существенна, что потребовалось вводить две сущности? Мне это кажется немного «накрученным». Как же принцип «Бритвы Оккама»? Возможно, это имеет смысл при разработке GUI для встраиваемых систем, например, под QNX.

                                    Например, если стандартный виджет кнопки из коробки имеет только сигнал типа «Listener», а пользователю нужен полноценный сигнал для этой кнопки, то ему придётся создавать собственный класс виджета, расширяющий стандартный. В итоге, по памяти выигрыша не будет, а сущностей добавится, что никак не упростит программу и нарушит принцип KISS.

                                    Это только моё мнение, я не утверждаю, что оно верное. Я не критикую библиотеку, здорово, что вы это делаете. Просто мне интересны некоторые архитектурные моменты, и я сам пытаюсь понять как лучше проектировать подобные вещи.
                                    • 0
                                      Я подумаю, может быть и выкину лишнее, оставлю Signal

                                  • 0
                                    Надо подумать.
                                    Пока еще не поздно поменять.
                                    • 0
                                      Из вашего ответного комментария стало понятно, зачем в имени сигнала нужно слово «Listener», так как у вас есть сигналы двух типов.
                                  • 0
                                    Экспериментирую с добавлением новой платформы — отображение DlangUI виджетов поверх OpenGL сцены, отрисованной другим движком — SFML.

                                    <img src="" alt=«image»/>

                                    Рожица отрисована SFML, а виджеты — DlangUI.

                                    Исходники файла, добавленного в dlangui для поддержки новой платформы.

                                    Для SFML пока сделал только рисование, надо еще добавить трансляцию событий мыши и клавиатуры.

                                    GitHub репозиторий с примером
                                    • 0
                                      UPD: уже работает мышь и клавиатура
                                    • 0
                                      Выглядит очень интересно. Кажется пришло время реанимировать один мой проект на D.

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.