Начинаем использовать GTKD

от автора

Доброго времени суток, хабр!

Сегодня хочу рассказать вам о том, как писать приложения с использованием GTK+ и языка программирования D.

О том, что такое GTK рассказывать не буду. Приступим сразу к делу.

Создадим новый проект, то есть просто папку =)
Добавим туда dub.json с таким содержанием:

{     "name": "hellogtkd",     "targetPath": "bin",     "targetType": "executable",     "dependencies": {         "gtk-d": "~>3.1.3"     } } 

Система сборки dub по умолчанию ищет исходники в папке source.
Создадим её и добавим туда файл main.d:

import gtk.Main; import gtk.MainWindow; import gtk.Label;  class HelloWorld : MainWindow {     this()     {         super( "wintitle" );         setBorderWidth(20); // чтобы не совсем маленькое было         add( new Label( "hello habr!" ) );         showAll();     } }  void main( string[] args ) {     Main.init( args );     new HelloWorld();     Main.run(); } 

Запустим сборку а затем приложение

dub build && bin/hellogtkd 

И вуаля!

Не будем на этом останавливаться и создадим предельно простой ui в программе glade, а затем загрузим в нашу программу.

Обратите внимание на дерево добавленных компонентов (сверху справа), для главного окна, конпки и поля отрисовки присвоены идентификаторы отличные от тех, что даются по умолчанию (mwindow, btn, plot соответственно), мы их будем использовать для удобного получения из билдера.
Сохраним файл в папке проекта под именем ui.glade и отредактируем main.d:

новый main.d

import std.string : format;  import gtk.Main; import gtk.Builder; import gtk.Window; import gtk.Widget;  import std.format;  final class UI {     string glade_file;      Builder builder; // используется для загрузки интерфейса и его хранения      this( string file )     {         builder = new Builder;          if( !builder.addFromFile( file ) )             except( "could no load glade object from file '%s'", file );          glade_file = file;          prepare();     }      void prepare()     {         prepareMainWindow();     }      void prepareMainWindow()     {         auto w = obj!Window( "mwindow" ); // получаем экземпляр Window по идентификатору         w.setTitle( "glade ui" );         w.addOnHide( (Widget aux){ Main.quit(); } ); // при закрытии окна нужно закрывать всю программу         w.showAll(); // показываем главное окно     }      // так мы из загруженного файла получаем ссылку на экземпляр необходимого объекта     auto obj(T)( string name )     {         // метод getObject возвращает ссылку на объект класса ObjectG, являющийся родителем для всех обёрнутых классов         auto ret = cast(T)builder.getObject( name ); // поэтому мы кастуем его к необходимому классу         if( ret is null ) except( "no '%s' element in file '%s'", name, glade_file );         return ret;     }      void except( string file=__FILE__, size_t line=__LINE__, Args...)( Args args )     { throw new Exception( format( args ), file, line ); } }  void main( string[] args ) {     Main.init( args );     new UI( "ui.glade" );     Main.run(); } 

Думаю все догадались, для чего был добавлен элемент GtkDrawingArea — мы будем рисовать.
Добавим действия, выполняемые при нажатии кнопки:

...     void prepare()     {         prepareMainWindow();         prepareButtonAction();     } ...     void prepareButtonAction()     {         // мы просто передаём делегат, который будет вызывается при сигнале Clicked         obj!Button( "btn" ).addOnClicked( (Button aux)         {             obj!DrawingArea( "plot" ).queueDraw(); // заставить переотрисоваться наш plot         });     } 

Подготовим пока пустой класс для отрисовки, создадим файл draw.d в папке source:

module draw;  import std.stdio; import std.datetime : Clock;  import gtk.Widget; import cairo.Context;  class Figure {     bool draw( Scoped!Context cr, Widget aux )     {         writefln( "draw figure %012d", Clock.currAppTick().length );          // необходимо вернуть true, если мы хотим остановить обработку события         // другими обработчиками, иначе возвращаем false, чтобы остальные         // обработчики были вызваны при вызове сигнала         // такое поведение касается всех сигналов, принимающих bool delegate(...)         return false;     } } 

Привяжем фигуру:

...     void prepare()     {         prepareMainWindow();         prepareButtonAction();         prepareDrawing();     } ...     Figure fig;      void prepareDrawing()     {         fig = new Figure;         // для подключения к сигналу мы можем использовать метод класса, это тоже делегат         obj!DrawingArea( "plot" ).addOnDraw( &fig.draw );     } 

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

... draw figure 014855276247 draw figure 014872180248 draw figure 014889286316 ... 

Можно заметить, что переотрисовка вызывается не только при нажатии кнопки, но и при других событиях: перемещение и изменение размеров окна, анимация цвета кнопки и тд.
За подробностями по поводу перерисовки виджетов обратитесь к документации GTK+.
Вызов переотрисовки при нажатии кнопки я поставил для того, чтобы показать как это делается.
По сути просто совпало, что нажатие кнопки само по себе ведёт к переотрисовке (из-за изменения цвета фона кнопки), но могут быть и другие события, не связанные с изменением GUI, тогда такой вызов необходим.

Добавим немного кода рисования:

...     float angle = 0;     bool draw( Scoped!Context cr, Widget aux )     {         writefln( "draw figure %012d", Clock.currAppTick().length );          import std.math;          auto w = aux.getAllocatedWidth();         auto h = aux.getAllocatedHeight();          auto xc = w / 2.0;         auto yc = h / 2.0;          auto radius = fmin(w,h) / 2.0;          auto x1 = cos( angle ) * radius + xc;         auto y1 = sin( angle ) * radius + yc;          auto x2 = cos( angle + PI / 3 * 2 ) * radius + xc;         auto y2 = sin( angle + PI / 3 * 2 ) * radius + yc;          auto x3 = cos( angle + PI / 3 * 4 ) * radius + xc;         auto y3 = sin( angle + PI / 3 * 4 ) * radius + yc;          cr.setSourceRgb( 1.0, 0.0, 0.0 );         cr.moveTo( x1, y1 );         cr.lineTo( x2, y2 );         cr.lineTo( x3, y3 );         cr.closePath();         cr.fill();          // необходимо вернуть true, если мы хотим остановить обработку события         // другими обработчиками, иначе возвращаем false, чтобы остальные         // обработчики были вызваны при вызове сигнала         // такое поведение касается всех сигналов, принимающих bool delegate(...)         return false;     } ... 

И заставим кнопку делать хоть что-нибудь, что не делается автоматически =)

...     void prepareButtonAction()     {         obj!Button( "btn" ).addOnClicked( (Button aux)         {             fig.angle += 0.1; // немного поворачиваем фигуру             obj!DrawingArea( "plot" ).queueDraw();         });     } ... 

На этом всё. Программа для тренировки с таблицами Шульте (первая картинка) лежит здесь. В ней Вы сможете найти больше примеров использования gtkd и отрисовки через cairo (хоть и не самых лучших, с точки зрения качества кода).

Так же в пакете gtk-d есть обёртки для:

  • gtkgl — использование OpenGL в приложениях GTK+
  • sv — SourceView — расширение GTK+ для редактирования текста с разными плюшками вроде подсветки синтаксиса, undo/redo и тд
  • vte — виджет терминала
  • gstreamer — мультимедийный фреймворк (удивило и порадовало, что биндинг создан и включён в gtkd)

Документация по проекту лежит здесь.

ссылка на оригинал статьи http://habrahabr.ru/post/259717/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *