Библиотека Granite от elementary OS

92dfdae1e00502cb23dc578e417da7af.jpg

На данный момент существует множество различных операционных систем на базе ядра Linux. Буквально на любой вкус. Некоторые операционные системы пользуются уже готовыми графическими оболочками, а другие создают свои. 

В дистрибутиве elementary OS применяется свое окружение рабочего стола под названием Pantheon. Имеется небольшой набор своих приложений. Есть файловый менеджер, текстовый редактор, почтовый клиент, эмулятор терминала и прочее.

Также для elementary OS была создана своя библиотека виджетов под названием Granite. Она как раз и применяется при создании приложений для этого дистрибутива. Данная библиотека похожа на Libadwaita от проекта GNOME. Эта библиотека так же, как и Libadwaita, предоставляет некоторое количество компонентов для конструирования пользовательского интерфейса.

В этой статье я хочу рассмотреть некоторые компоненты из библиотеки Granite. Приложения для elementary OS пишутся преимущественно на языке программирования Vala. Далее примеры кода будут приводиться именно на этом языке. Репозиторий библиотеки можно найти здесь.

Dialog

Диалоговое окно для предоставления пользователю каких-либо действий. Можно создавать самые разные окна, например, окно для ввода логина и пароля и тому подобное. Пример:

  var header = new Granite.HeaderLabel ("Header");
  var entry = new Gtk.Entry ();
  var gtk_switch = new Gtk.Switch () {
      halign = Gtk.Align.START
  };

  var layout = new Gtk.Grid () {
      row_spacing = 12
  };
  layout.attach (header, 0, 1);
  layout.attach (entry, 0, 2);
  layout.attach (gtk_switch, 0, 3);

  var dialog = new Granite.Dialog () {
      transient_for = window
  };
  dialog.content_area.add (layout);
  dialog.add_button ("Cancel", Gtk.ResponseType.CANCEL);

  var suggested_button = dialog.add_button ("Suggested Action", Gtk.ResponseType.ACCEPT);
  suggested_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);

  dialog.show_all ();
  dialog.response.connect ((response_id) => {
      if (response_id == Gtk.ResponseType.ACCEPT) {
          // Do Something
      }

      dialog.destroy ();
  });

А выглядит это вот так:

1dacb6d62ef180e68a4c5dacac3d62e7.png

OverlayBar

Представляет собой плавающую строку состояния, которая располагается в нижней части окна приложения и в которой может отображаться одна строка текста. Внешний вид со спиннером:

9b14654e1872b42a446c25ca56904f18.png

Подойдет для отображения протекания какого-либо процесса. Такая штука применяется, например, в центре приложений AppCenter для отображения процессов поиска обновлений и установки приложений. Также она замечена и в файловом менеджере, который поставляется в дистрибутиве elementary OS из коробки. Пример кода:

public class OverlayBarView : Gtk.Box {
    construct {
        var button = new Gtk.ToggleButton.with_label ("Show Spinner");

        /* This is necessary to workaround an issue in the stylesheet with buttons packed directly into overlays */
        var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) {
            halign = Gtk.Align.CENTER,
            valign = Gtk.Align.CENTER
        };

        box.append (button);

        var overlay = new Gtk.Overlay () {
            child = box,
            hexpand = true
        };

        var overlaybar = new Granite.OverlayBar (overlay) {
            label = "Hover the OverlayBar to change its position"
        };

        append (overlay);

        button.toggled.connect (() => {
            overlaybar.active = button.active;
        });
    }
}

Placeholder

Прекрасно подходит для заполнения пустого представления в случае, если отсутствует какой-либо контент или нет доступа к определенным сервисам или функциям. Например, если приложению требуется доступ к сервисам геолокации, то можно сообщить пользователю об этом и отправить его в соответствующий пункт настроек. Вот как это сделано в приложении Nimbus:

var placeholder = new Granite.Placeholder (_("Unable to Get Location")) {
            icon = new ThemedIcon ("location-disabled-symbolic"),
            description = _("Make sure location access is turned on in System Settings → Security & Privacy")
        };

Выглядит это так:

830320b7090284507ff8e03e3d4a26fa.png

MessageDialog

Используется для вывода сообщений. Пример кода для создания простейшего диалогового окна:

private void alert (string str){
        var dialog = new Granite.MessageDialog.with_image_from_icon_name (_("Message"), str, "dialog-warning");
        dialog.show_all ();
        dialog.run ();
        dialog.destroy ();
       }

Вот так это диалоговое окно выглядит в моем приложении Raddiola:

9f36797875c9aa6d859ce335d05e94c1.png

Такие диалоги дополнительно поддерживают добавление кастомных виджетов и показ детализаций ошибок. Пример кода:

 private void show_message_dialog () {
   var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (
            "Basic information and a suggestion",
            "Further details, including information that explains any unobvious consequences of actions.",
            "phone",
            Gtk.ButtonsType.CANCEL
        );
        message_dialog.badge_icon = new ThemedIcon ("dialog-information");
        message_dialog.transient_for = window;

        var suggested_button = new Gtk.Button.with_label ("Suggested Action");
        suggested_button.get_style_context ().add_class (Granite.STYLE_CLASS_SUGGESTED_ACTION);
        message_dialog.add_action_widget (suggested_button, Gtk.ResponseType.ACCEPT);

        var custom_widget = new Gtk.CheckButton.with_label ("Custom widget");

        message_dialog.show_error_details ("The details of a possible error.");
        message_dialog.custom_bin.append (custom_widget);

        message_dialog.response.connect ((response_id) => {
           if (response_id == Gtk.ResponseType.ACCEPT) {
               toast.send_notification ();
           }

           message_dialog.destroy ();
        });

        message_dialog.show ();
    }

Внешний вид такого диалогового окна:

685a3049cf51b611e3742bdc2062682a.png

Toast

Применяется для создания всплывающих сообщений, которые появляются в верхней части окна приложения. Можно добавлять какие-либо действия, которые будут отображаться в виде кнопок. Выглядит этот компонент следующим образом:

6f15664b14b317fd99780df1d2f0cd47.png

Код:

public class ToastView : Gtk.Box {
    construct {
        halign = Gtk.Align.CENTER;

        var overlay = new Gtk.Overlay ();

        var toast = new Granite.Toast (_("Button was pressed!"));
        toast.set_default_action (_("Do Things"));

        var button = new Gtk.Button.with_label (_("Press Me"));

        var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6) {
            margin_start = 24,
            margin_end = 24,
            margin_top = 24,
            margin_bottom = 24,
            halign = Gtk.Align.CENTER,
            valign = Gtk.Align.CENTER
        };
        box.append (button);

        overlay.add_overlay (box);
        overlay.add_overlay (toast);
        overlay.set_measure_overlay (toast, true);

        button.clicked.connect (() => {
            toast.send_notification ();
        });

        toast.default_action.connect (() => {
            var label = new Gtk.Label (_("Did The Thing"));
            toast.title = _("Already did the thing");
            toast.set_default_action (null);
            box.append (label);
        });

        append (overlay);
    }
}

ModeSwitch и SwitchModelButton

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

28c8c330f9421c2566026b8fa28205fe.png

Обычно эта штука располагается в верхней панели приложения, в правой ее части.

SwitchModelButton содержит текстовую метку и переключатель. Может показывать небольшой текст описания при активации. Внешний вид:

ea08aaff727bd5bbdf9a54583de40a65.png

Отличная вещь, когда нужно скрыть некоторые переключатели в одном месте, чтобы не загромождать интерфейс. Пример кода для обоих элементов:

public class ModeButtonView : Gtk.Box {
    construct {
        var mode_switch_label = new Gtk.Label ("ModeSwitch");
        mode_switch_label.halign = Gtk.Align.START;
        mode_switch_label.margin_top = 12;
        mode_switch_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);

        var mode_switch = new Granite.ModeSwitch.from_icon_name (
            "display-brightness-symbolic",
            "weather-clear-night-symbolic"
        );
        mode_switch.primary_icon_tooltip_text = ("Light background");
        mode_switch.secondary_icon_tooltip_text = ("Dark background");
        mode_switch.valign = Gtk.Align.CENTER;

        var switchbutton_header = new Gtk.Label ("SwitchModelButton") {
            margin_top = 12,
            halign = Gtk.Align.START,
            xalign = 0,
        };
        switchbutton_header.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);

        var header_switchmodelbutton = new Granite.SwitchModelButton ("Header");
        header_switchmodelbutton.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);

        var switchmodelbutton = new Granite.SwitchModelButton ("Default");

        var description_switchmodelbutton = new Granite.SwitchModelButton ("With Description") {
            active = true,
            description = "A description of additional affects related to the activation state of this switch"
        };

        var switchbutton_grid = new Gtk.Grid () {
            margin_top = 3,
            margin_bottom = 3
        };
        switchbutton_grid.attach (header_switchmodelbutton, 0, 0);
        switchbutton_grid.attach (switchmodelbutton, 0, 1);
        switchbutton_grid.attach (description_switchmodelbutton, 0, 2);

        var switchbutton_popover = new Gtk.Popover () {
            child = switchbutton_grid
        };

        var popover_button = new Gtk.MenuButton () {
            direction = Gtk.ArrowType.UP
        };
        popover_button.popover = switchbutton_popover;

        spacing = 6;
        orientation = Gtk.Orientation.VERTICAL;
        halign = Gtk.Align.CENTER;
        valign = Gtk.Align.CENTER;
        append (mode_switch_label);
        append (mode_switch);
        append (switchbutton_header);
        append (popover_button);
    }
}

DatePicker и TimePicker

DatePicker — это виджет для выбора даты, а TimePicker — для выбора времени. Вот пример кода для создания этих виджетов в одном контейнере:

           var date_picker = new Granite.Widgets.DatePicker.with_format(
                Granite.DateTime.get_default_date_format(false, true, true)
            );

            var time_picker = new Granite.Widgets.TimePicker.with_format(
                Granite.DateTime.get_default_time_format(true), 
                Granite.DateTime.get_default_time_format(false)
            );

            var date_time_container = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
            date_time_container.pack_start(date_picker, true, true, 0);
            date_time_container.pack_start(time_picker, true, true, 0);

Применять эти компоненты можно в таких приложениях, как списки задач, календари, различные напоминалки и тому подобное. Выглядят эти виджеты следующим образом:

abb4444b0867ade5448d679f745e3276.png

ValidatedEntry

Поле для ввода, которое может проверять валидность вводимой информации. Может быть полезно в тех случаях, когда нужно проинформировать пользователя, если он ввел какие-то недопустимые символы. Для примера приведу вот такой код из официальной документации:

  Regex? regex = null;
  ValidatedEntry only_lower_case_letters_entry;
  try {
      regex = new Regex ("^[a-z]*$");
      only_lower_case_letters_entry = new ValidatedEntry.from_regex (regex);
  } catch (Error e) {
      critical (e.message);
      // Provide a fallback entry
  }

В данном коде разрешается вводить символы латинского алфавита только из нижнего регистра. Внешний вид виджета:

82235cb7ccf81f98598dae1d31fa2850.png

Welcome

Окно приветствия. Можно использовать для выбора дальнейших действий. Пригодится, если, например, в игре нужно выбрать уровень сложности, а после прерывания игры из того же окна можно продолжить незавершенную игру или начать новую. Вот как это сделано в игре Sudokular:

 var welcome_view = new Welcome (_("Sudokular"), "");
        welcome_view.append ("", _("New game"), _("Choose difficulty and start a new puzzle"));

        var settings = new SudokuSettings ();
        var sudoku_board = new SudokuBoard.from_string (settings.load ());

        if (settings.load () != null && !sudoku_board.isFinshed ()) {
            welcome_view.append ("", _("Resume game"), _("Return to where you left off."));
        }

А вот такой у всего этого внешний вид:

db3a1daf322c25267f5eea6a7b1305fd.png

HyperTextView

Этот класс расширяет обычный GtkTextView, сделав возможным включать в него URL-адреса. Вот так это выглядит:

b6afa1633e306139b8bbd8e4ba67e303.png

Код:

public class HyperTextViewGrid : Gtk.Box {
    construct {
        var hypertext_label = new Gtk.Label ("Hold Ctrl and click to follow the link") {
            halign = Gtk.Align.START,
            xalign = 0
        };
        hypertext_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);

        var hypertext_textview = new Granite.HyperTextView ();
        hypertext_textview.buffer.text = "elementary OS - https://elementary.io/\nThe fast, open and privacy-respecting replacement for Windows and macOS.";

        var hypertext_scrolled_window = new Gtk.ScrolledWindow () {
            height_request = 300,
            width_request = 600,
            child = hypertext_textview
        };

        margin_start = margin_end = margin_top = margin_bottom = 12;
        orientation = Gtk.Orientation.VERTICAL;
        spacing = 3;
        halign = Gtk.Align.CENTER;
        valign = Gtk.Align.CENTER;
        vexpand = true;
        append (hypertext_label);
        append (hypertext_scrolled_window);
    }
}

С этими и некоторыми другими компонентами подробнее можно ознакомиться на этой странице. На ней предоставлена свежая версия библиотеки (granite-7). С более старой версией можно ознакомиться здесь. Несмотря на то, что версия не совсем свежая, она довольно широко применяется. Даже родные приложения дистрибутива не все переведены на новую версию библиотеки Granite.

Что использовать для разработки

Для разработки приложений с использованием библиотеки Granite можно использовать среду GNOME Builder. Нужно выбрать шаблон для создания приложения GTK. Если выбрать шаблон с приложением GNOME, то тогда приложение будет включать в себя Libadwaita, а это нам не надо. Библиотека Libadwaita никогда не используется при создании приложений для elementary OS. 

После создания проекта необходимо прописать библиотеку Granite в сборочном сценарии meson.build:

dependency('granite-7')

Так как приложения для elementary OS распространяются в формате flatpak, то придется немного поработать с манифестом и заменить в нем платформу и Sdk на соответствующие от проекта elementary:

    "runtime": "io.elementary.Platform",
    "runtime-version": "7.2",
    "sdk": "io.elementary.Sdk",

Теперь можно смело приступать к разработке. Библиотека Granite была специально создана для разработки приложений под elementary OS, поэтому она входит в состав elementary.Sdk, и ее не нужно прописывать в манифесте в виде отдельного модуля. Конечно, никто не заставляет разработчиков использовать эту библиотеку. Если разработчик хочет опубликовать свою программу в AppCenter, то обязательное условие заключается в том, чтобы приложение использовало фреймворк GTK и соответствующую платформу. Компоненты из Granite желательны, но необязательны. 

Библиотеку можно использовать и в приложениях GNOME. Так как она не входит в состав gnome.Sdk, то ее придется прописывать в манифесте в виде отдельного модуля. Вот пример для версии 6.2.0:

          { 
            "name": "granite",
            "buildsystem": "meson",
            "sources": [
              {
                "type": "git",
                "url": "https://github.com/elementary/granite.git",
                "tag": "6.2.0",
                "commit": "4ab145c28bb3db6372fe519e8bd79c645edfcda3"
              }
            ]
         }

Этот модуль мне понадобился для моего приложения Sudoku, которое основано на вышеупомянутом Sudokular. Первые версии Sudoku использовали Granite.Widgets.Welcome, но в последующих версиях было решено от него избавиться и применить вместо него конструкцию на основе обычного GtkBox.

На этом сайте собраны все актуальные, а также устаревшие приложения созданные для дистрибутива elementary OS. Приложений не так уж много. Много заброшенных программ. Новых приложений появляется довольно мало. Вообще, дистрибутив в последнее время развивается не очень активно.

В целом библиотека мне понравилась. Она позволяет достаточно легко и быстро создавать самые разные приложения для elementary OS. Надеюсь, что разработчики не забросят этот проект и продолжат развивать его дальше.

Автор статьи @KAlexAl

НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ любого VDS(кроме тарифа Прогрев) — HABRFIRSTVDS.

© Habrahabr.ru