Новая оболочка в Ubuntu Unity состоит из разных компонентов, взаимодействующих между собой. Данная техническая статья описывает такие понятия как линзы (lens) и области (scopes).
Введение.
Одно из главных возможностей Unity - это Dash. Dash позволяет пользователю быстро находить информацию как локально (приложения, последние использованные файлы, закладки и другое), так и онлайн (Твиттер, Google Docs и т.д).
Dash умеет это делать, благодаря линзам (Lens), которые отвечают по раздельности за свою выдачу поисковых результатов в категории. Пользователь может искать информацию либо в домашней линзе Dash (глобальный поиск), либо в конкретной линзе, щёлкнув на соответствующем значке линзы на панели линз.
Сама по себе линза бесполезна. Это просто значок в панели линз и страница в Dash с поисковой строкой для пользователя. Линза не ищет сама. Вместо этого, линза обладает одной или несколькими "областями" (Scope), которые собственно и производят поиск.
Это означает, что можно создать новую область (scope), которая будет дополнять результаты для существующих линз (lens). Например, область Google Docs может дополнить результатами ответ из области Zeitgeist в файловой линзе (Files Lens) и результаты будут выведены рядом с друг другом, как ответ на поисковый запрос пользователя.
Кроме того, это означает, что одна линза может быть связана с несколькими областями, то есть музыкальная линза (Music Lens) обладает Banshee, UPNP, Spotify областями, которые ищут своими движками и предоставляют готовый ответ на запрос.
Смотрите на Unity с позиции дизайна Model-view-controller (MVC - Модель-представление-поведение, Модель-представление-контроллер) — схема использования нескольких шаблонов проектирования, с помощью которых модель данных приложения, пользовательский интерфейс и взаимодействие с пользователем разделены на три отдельных компонента так, что модификация одного из компонентов оказывает минимальное воздействие на остальные.
Концепция MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента:
- Модель (англ. Model). Модель предоставляет знания: данные и методы работы с этими данными, реагирует на запросы, изменяя своё состояние. Не содержит информации, как эти знания можно визуализировать.
- Представление, вид (англ. View). Отвечает за отображение информации (визуализацию). Часто в качестве представления выступает форма (окно) с графическими элементами.
- Контроллер (англ. Controller). Обеспечивает связь между пользователем и системой: контролирует ввод данных пользователем и использует модель и представление для реализации необходимой реакции.
Линза (lens) - это контроллер, область (scope) - модель.
Важно отметить, что как представление, так и контроллер зависят от модели. Однако модель не зависит ни от представления, ни от контроллера. Тем самым достигается назначение такого разделения: оно позволяет строить модель независимо от визуального представления, а также создавать несколько различных представлений для одной модели.
Рекомендации к проектированию.
Назначение Dash.
Dash призван обеспечить легковесный, мгновенный, простой способ просматривать и отыскивать контент. Dash - это первый шаг пользователя, вначале пользователь находит искомое в Dash, а уж затем переходит к соответствующему приложению.
Dash концентрируется на содержимом. Источники контента группируются вокруг типов контента. Dash не место, где пользователь работает, Dash ограничен только поиском содержимого.
Назначение линз (lens).
Каждая линза создаётся для конкретного типа данных. Например, музыка, книги, ноты, новости, фото, комиксы. Если линза для данного типа контента уже существует, то другим разработчикам стоит сосредоточить свои усилия на разработки новых областей (scope) с новыми данными для данной линзы. К примеру есть линза помощи (help lens) с двумя областями (scope) - AksUbuntu и UbuntuHelp. В качестве обучения я попробовал реализовать область ForumUbuntuRu, чтобы она добавляла в линзу "помощи" ответы русскоязычного форума и статьи с ubuntu.ru.
Разработчикам настоятельно рекомендуется ограничить себя в желании создавать излишние линзы, поскольку более полезно для пользователя создание больше источников данных. Так как Dash предоставляет пользователям агрегацию данных, то большое количество линз делает жизнь сложнее, а создание множества областей (scope) наиболее полезно и желательно.
Основное правило: "Если линза существует для этого типа данных, то сосредоточьтесь на написании области для данной линзы. Не создавайте новых линз. Если нужно изменить линзу, то свяжитесь с автором и предоставьте патч."
Назначение областей (scope).
Область - это специализированный поисковый движок для извлечения данных из локальных или удалённых источников: локальная папка, Picasa, Fickr, Shotwell и т.д. Области сделаны с учётом фильтров (filters) и категорий (categories) для своих родительских линз.
Если архитектура линзы требует некоторой модификации под вашу область (есть специфичный фильтр, новая категория или улучшения в категории), то лучше связаться с автором линзы. Помните, что линза для данного типа данных должна быть в состоянии работать с множеством источников данных и каждый фильтр должен быть полезен для всех.
Для каждого элемента при выводе поискового результата область должна определять:
- заголовок - title.
- унифицированный идентификатор ресурса - URI.
- иконку.
- необязательный комментарий.
- необязательный URI для использования в режиме перетащи-и-брось.
Архитектура.
Возьмём для примера линзу с двумя областями - пусть будет музыкальная линза с областями Banshee и Spotify. В данном примере, будет четыре процесса, связанные с поиском контента в данной линзе:
- собственно Dash (то есть Unity).
- демон линзы.
- демон первой области Banshee (Banshee Scope).
- демон второй области Spotify (Spotify Scope).
Задача линзы, чтобы все процессы были синхронизированы друг с другом. Счастью, если вы автор линзы и области, то сложностей не должно быть.
Создание линзы.
Создание линзы требует:
- Наличие файла .lens, чтобы Unity смогла найти и загрузить линзу.
- Демона, который использует уже известное имя в D-Bus
- D-Bus файл .service, который позволит Unity автоматически активировать линзу, когда она будет готова. Этот файл необходим большинству линз, так как в 99% случаев линзы долго стартуют.
Обязанности линз.
Линза предоставляет для Unity следующую информацию:
- какие категории она поддерживает.
- какие фильтры она поддерживает.
- что отображать по умолчанию в качестве поисковой подсказки в поисковой строке.
- какой значок отображать в панели линз.
- должна линза участвовать в глобальном поиске в Dash?
ID для вашей линзы.
Важно подобрать идентификатор для вашей линзы, чтобы Unity экспортировала данные из линз (lens) и находила области (scopes). Данный ID используется в нескольких местах и должен быть однозначно определён, чтобы не возникало конфликтов с другими линзами. Идентификатор может состоять из букв, цифр и тире.
Файл .lens
Файл .lens очень важен для Unity и позволяет загрузить информацию о линзе до её собственной загрузки и объясняет Unity где найти и как загрузить демона линзы через D-Bus.
Вот пример файла .lens:
[Lens] DBusName=net.launchpad.Lens.MyLens DBusPath=/net/launchpad/lens/mylens Name=My Lens Icon=/путь/до/mylens.svg Description=линза ищёт крутые штучки SearchHint=Search your stuff Shortcut=m [Desktop Entry] X-Ubuntu-Gettext-Domain=my-lens
- DBusName: заранее оговорённое имя в D-Bus, чтобы найти данную линзу.
- DBusPath: путь D-Bus куда была экспортирована линза.
- Name: имя вашей линзы.
- Icon: иконка, которая будет отображаться в панели линз и/или на Unity Launcher. Формат SVG предпочтителен.
- Description: Описание вашей линзы в одну строку. Это нигде не отображается, но может использоваться.
- SearchHint: поисковая подсказка, которая отображается, когда на линзу переключились и поисковые запросы ещё не делались.
- Shortcut: предпочтительная клавиша быстрого вызова линзы. Но без гарантии, если данная горячая клавиша уже будет занята.
- X-Ubuntu-Gettext-Domain: объясняет Unity, на каком языке выводить текст, который содержится в данном файле .lens.
Файл .lens должен быть установлен в тот же самый префикс что и Unity в формате:
/там_где_юнити/share/unity/lenses/$ваш_id_линзы/$ваш_id_линзы.lens
Например, если Unity установлена в префикс /usr и ваша линза mylens, тогда файл .lens должен быть расположен в /usr/share/unity/lenses/mylens/mylens.lens
D-Bus файл .service
[D-BUS Service] Name=net.launchpad.Lens.MyLens Exec=/my/daemon/install/prefix/lib/mylens/my-lens-daemon
- Name: заранее оговорённое имя в D-Bus, чтобы найти вашу линзу. Name должен быть таким же как и DBusName.
- Exec: путь к исполняемому файлу вашего демона, который будет загружен и зарегистрирован с этим именем в D-Bus.
Данный файл D-Bus .service должен быть размещён в /usr/share/dbus-1/services/.
Использование объекта "линза".
После того, как был зарегистрирован демон в D-Bus нужно создать и инициализировать объект "линза". Подробнее расписано в статье LibUnity.
Создание линзы и установка значений по умолчанию.
{ // Когда создаётся объект линза, нужно указать DBusPath и идентификатор линзы this.lens = new Unity.Lens("/net/launchpad/lens/mylens", "mylens"); // Перевести на родной язык пользователя поисковую подсказку lens.search_hint = _("Search your stuff"); // Отображать в панели линз? lens.visible = true; // Должна Unity включать линзу в глобальный поиск? lens.search_in_global = true; // Добавить категории по умолчанию (пример ниже) populate_categories(); // Добавить фильтры по умолчанию (пример ниже) populate_filters(); // Теперь настройки по умолчанию заданы и делаем экспорт // чтобы Unity смогла найти её. lens.export(); }
Добавление категорий.
Вы должны добавить по крайней мере одну категорию в вашу линзу. Категории позволяют создавать различные сектора между различными типами данных в вашей линзе. Например, музыкальная линза обладает категориями Композиция (Songs), Альбомы (Albums), Доступные к покупке (Available to Purchase).
Когда области выводят результат, они показывают в каких категориях это было найдено, используя код возврата в виде целых чисел. Для примера, область в музыкальной линзе может выводить найденную песню в Композициях, указывая цифру 0. Для альбомов применить цифру 1. Песни, которые можно купить, пометить цифрой 2.
При добавлении категории, можно выбрать иконку, имя и тип рендера, который будет выводить результаты. Для Ubuntu 11.10 поддерживается 2 рендера: вертикальная мозаика (VERTICAL_TILE) и горизонтальная мозаика (HORIZONTAL_TILE).
private void populate_categories () { GLib.List categories = new GLib.List (); File icon_dir = File.new_for_path (ICON_PATH); var cat = new Unity.Category (_("Songs"), new FileIcon (icon_dir.get_child ("group-mostused.svg")), VERTICAL_TILE); categories.append (cat); cat = new Unity.Category (_("Albums"), new FileIcon (icon_dir.get_child ("group-installed.svg")), HORIZONTAL_TILE); categories.append (cat); cat = new Unity.Category (_("Available to Purchase"), new FileIcon (icon_dir.get_child ("group-available.svg")), VERTICAL_TILE); categories.append (cat); lens.categories = categories; }
Добавление фильтров.
Большинство линз обладает фильтрами, с помощью которых пользователь указывает точнее свои критерии поиска. Многое зависит от линзы, чтобы фильтры для рендера Unity и результаты работы областей изменяли свой поисковый вывод.
В Ubuntu 11.10 было четыре типа фильтров:
-
Галочка. CheckOption.
Фильтр CheckOption разрешает выбрать пользователю один или несколько пунктов из списка. На практике это часто используется для поиска в папках и PDF, как показано в примере.
{ // Первый параметр - это уникальное имя фильтра в данной области. // Не может быть два фильтра с одним и тем же именем! // Второй параметр, то что увидит пользователь в Unity. var filter = new CheckOptionFilter("type", _("Type")); // CheckOption, RadioOption, MultiRange - все подклассы OptionFilter. // Подкласс OptionFilter абстрагирует понятие "дополнительные опции" фильтра. // Например, каждая галочка или "элемент выбора" в CheckOptionFilter добавлены // таким же образом, как и пункты в RadioOptionFilter или MultiOptionFilter. // // В add_option первый параметр - уникальный идентификатор // это позволяет области легко проверять его состояние. // Второй параметр - видимое пользователю имя пункта. // Третий параметр - значок GIcon, но в Ubuntu 11.10 он не используется. filter.add_option ("document", _("Document")); filter.add_option ("folder", _("Folder")); filter.add_option ("pdf", _("PDF")); // другой код ... }
-
Альтернативный выбор RadioOption.
RadioOption фильтр позволяет пользователю выбрать только один из предложенных вариантов. В примере, пользователь может выбрать "Сегодня" или "Прошлая неделя", но не оба вместе.{ var filter = new RadioOptionsFilter ("last-modified", _("Type")); filter.add_option ("today", _("Today")); filter.add_option ("yesterday", _("yesterday")); filter.add_option ("last-week", _("Last Week")); }
-
MultiRange.
MultiRange позволяет пользователю указать диапазон значений из представленных вариантов. В приведённом примере, если пользователь нажмёт на 1 Мб и 100 Мб, то активными станут 1 Мб, 10 Мб, 100 Мб. Если пользователь нажмёт сразу на 10 Мб, то 1 Мб и 10 Мб станут активными.{ var filter = new MultiRangeFilter ("size", _("Size")); filter.add_option ("1MB", _("1MB")); filter.add_option ("10MB", _("10MB")); filter.add_option ("100MB", _("100MB")); filter.add_option ("1GB", _("1GB")); }
-
Рейтинг. Ratings.
Фильтр Рейтинг позволяет пользователю выбрать количество звёзд из 5 представленных, для уточнения поиска. Это отлично подходит к таким случаям, как рейтинг приложения в Центре приложений Ubuntu или рейтинг песни в Banshee.{ var filter = new RatingsFilter("rating", _("Rating")); }
Полный вариант для примера.
{ GLib.List filters = new GLib.List (); var filter = new CheckOptionFilter("type", _("Type")); filter.add_option ("document", _("Document")); filter.add_option ("folder", _("Folder")); filter.add_option ("pdf", _("PDF")); filters.append (filter); filter = new RadioOptionsFilter ("last-modified", _("Type")); filter.add_option ("today", _("Today")); filter.add_option ("yesterday", _("yesterday")); filter.add_option ("last-week", _("Last Week")); filters.append (filter); filter = new MultiRangeFilter ("size", _("Size")); filter.add_option ("1MB", _("1MB")); filter.add_option ("10MB", _("10MB")); filter.add_option ("100MB", _("100MB")); filter.add_option ("1GB", _("1GB")); filters.append (filter); filter = new RatingsFilter("rating", _("Rating")); filters.append (filter); lens.filters = filters; }
Что ещё?
Если честно, то всё. Линза, будучи экпортирована, ожидает своих областей (scopes) и Unity начинает их поиск. Все синхронизации между процессами осуществляются внутри libunity.
В будущем, планируется разрешить линзам оказывать больше влияние на выводимые результаты. Например, убирать повторы (дедупликация) при выводе из различных областей.
Однако пока, ваша работа, как автора линзы, в данном месте заканчивается!
Создание области (scope).
Обязанности областей.
В обязанности областей входит:
- Возвращать поисковые результаты в родительскую линзу, учитывая состояние фильтров во время поиска.
- Возвращать поисковые результаты, если идёт глобальный поиск, игнорируя состояние фильтров.
- Уведомлять родительскую линзу, когда заканчивается нормальный или глобальный поиск.
- Принимать на себя действия пользователя, когда пользователь щёлкает по результатам поиска.
Регистрация.
Есть два типа областей, которые отличаются друг от друга тем, как именно они регистрируются со своей родительской линзой.
Локальная область (Local Scope) - это область, которая создаётся в том же самом процессе что и линза. Обычно так делают, чтобы для линзы не устанавливать дополнительные программные модули. Или линза узкоспециализированная и можно избавиться от создания и запуска демона. Представьте, кто-то создаёт линзу GoogleDocs и там есть область, которая работает с Google Docs API.
Выносная область (Remote Scope) - представляет собой область, которая подключается к существующей линзе в виде отдельного процесса. Этот тип области будет работать в собственном демоне. Для примера, представьте область Last.fm для музыкальной линзы.
Локальная область.
Поскольку данная область была создана и работает в том же процессе, что и родительская линза, то достаточно указать:
{ lens.add_local_scope (the_local_scope); lens.export(); }
Этим самым сообщается линзе, чтобы область работала в процессе самой линзы.
Выносная область (Remote Scope).
Чтобы выносную область нашла линза, вы должны создать и разместить файл .scope, в котором есть указания как запустить демон области.
Пример файла .scope
[Scope] DBusName=net.launchpad.Scope.lastfm DBusPath=/net/launchpad/scope/lastfm
Здесь просто сообщается заранее оговорённое имя в D-Bus, по которому будет найдена область и путь к ней. Файл .scope должен быть размещён в той же папке что и родительская линза. Если область создана, к примеру, для музыкальной линзы, то и должна быть в /usr/share/unity/lenses/music/lastfm.scope.
Стоит отметить, что имя файла .scope не так важно, как имя файла .lens, но крайне полезно иметь уникальное имя, чтобы не конфликтовать с другими областями.
Выносная область должна создать файл .service, так как же как это делает линза. Это позволяет автоматически активировать область, когда линза стартовала, а область ещё не запущена.
Использование объекта "область".
Объект "область" - это ваш "трубопровод" к текущему состоянию Unity. Родительская линза будет держать область в актуальном состоянии и отображать обновлённые результаты нормального и глобального поиска.
Создание и инициализация области.
{ // Для создания объекта области нужен корректный путь в D-Bus. // Данный здесь путь должен совпадать с параметром DBusPath в файле .scope, // если данная область является выносной (RemoteScope) // Иначе, подойдёт любой правильный D-Bus путь. this.scope = new Unity.Scope("/net/launchpad/scope/lastfm"); // Вы можете указать на уровне области // будете ли участвовать в глобальном поиске? // Это означает, что вы можете отказаться, даже если родительская // линза заявила, что будет участвовать в глобальном поиске. scope.search_in_global = true; // Самое главное связать область с сигналами изменения // состояния и реагировать на них. // ЗАМЕТКА: обработчики событий будут описаны ниже. // Хотим получать сигнал об изменении поисковой фразы scope.search_changed.connect (on_search_changed); // Хотим получать сигнал при изменениях в фильтрах scope.filters_changed.connect (on_filters_changed); // Мы хотим получить сигнал, что пользователь нажал // на результатах поиска и получить URI элемента. // Среагировать на сигнал и выдать Unity результат. scope.activate_uri.connect (on_uri_activated); // Если это выносная область (RemoteScope), то экспортируем. // Для локальной области используем lens.add_local_scope scope.export (); }
Магический поисковый запрос "".
В мире Dash, получая поисковый запрос "", следует понимать, что нужно выдать результат по умолчанию. Пользователь ничего не ищет и просто переключается с линзы на линзу. К примеру, линза приложений, когда получает сигнал в виде "", то выдаёт категории "не давно использовавшихся приложений", "установленных приложений" и т.д.
Стремитесь всегда выдавать что-то полезное пользователю, когда поискового запроса нет и вы получаете магическую строку "".
Реакция на сигнал изменения поисковой строки.
Когда срабатывает сигнал об изменениях в поисковой строке, то вы получите экземпляр LensSearch. Вы получаете текущий поисковый запрос и должны обновить результат поиска от предыдущего запроса.
private void on_search_changed (Scope scope, LensSearch search, SearchType search_type, Cancellable cancellable) { debug ("The current search term is: %s", search.search_string); if (search_type == SearchType.DEFAULT) { update_lens_search (search, cancellable); } else { update_home_search (search, cancellable); } // Once the search finishes, you need to inform the lens that your result set is complete, // otherwise it'll assume that you are still going to provide more results in your result // set (and the Dash might indicate to the user that the search is still in progress). search.finished (); }
Добавление результатов в модель.
Область имеет 2 DeeModels, которые можно использовать для хранения ваших результатов.
Unity.Scope.results_model для хранения результатов локального поиска, когда линза была активна и искали непосредственно в ней.
Unity.Scope.global_results_model для хранения результатов глобального поиска.
Экземпляр LensSearch ссылается на модель, которую вы должны обновить с помощью lens_search.results_model. Внутренний выбор между results_model и global_results_model основан на перечислении SearchType.
Схема.
Схема представляет собой изображение модели в виде таблицы. Легко представлять результаты поиска в виде таблицы.
uri | icon-hint | category-index | mimetype | name | comment | dnd-uri |
string | string | uint | string | string | string | string |
application://firefox.desktop | firefox | 0 | application-x-desktop | Firefox | Browse the World Wide Web | file:///usr/share/applications/firefox.desktop |
- Uri: URI для элемента, чтобы в дальнейшем вам же легко его идентифицировать, когда элемент будет активирован пользователем.
- Icon Hint: Путь и имя файла иконки для элемента или в формате GIcon (GLib.Icon.to_string()).
- Category Index: К какой категории принадлежит данный элемент? Поле для индекса категории в виде целочисленного значения.
- MimeType: Тип MIME.
- Name: Отображаемое имя элемента (переведите если нужно).
- Comment: однострочный комментарий, который может быть использован как всплывающая подсказка над элементом.
- DND URI: этот URI должен быть понятен другим приложениям, если результат будет перетащен с Dash с помощью режима перетащи-и-брось.
Использование DeeModel.
Два метода чаще всего используется при работе с моделями: Dee.Model.clear() и Dee.Model.append().
private void update_model(Unity.LensSearch search) { // Create a temp reference to make reading the code easier var model = search.results_model; // First we clear the existing results in the model model.clear(); // Then we'd load the new results string uri = "myface.com/search?q=" + search.search_term.encode(); add_filter_state_to_search_uri(uri); var results = get_results_as_list(uri); // Add the new results to the model for (MyResult result in results) { model.append(result.uri, icon_as_gicon_string(result.icon_uri), result.category_index, result.mimetype, result.name, result.comment, result.link_uri); } }
Реакция на сигнал изменения состояния фильтров.
Когда люди нажимают кнопки фильтров, следует это учитывать и изменять вывод поискового запроса.
СОВЕТ: Бывает проще удалить результаты из уже найденных ответов, при изменении состояния фильтров, чем делать новый запрос с нуля.
Запрос состояния CheckOption.
{ // получить тип CheckOption фильтра из области var filter = scope.get_filter("type") as CheckOptionFilter; // Мы можем получить ноль, один или несколько включённых опций // и нужно пройтись по всем foreach (Unity.FilterOption option in filter.options) { if (option.active) { if (url == "") url="&type=" + option.id; else url+="+" + option.id; } } }
Запрос состояния RadioOption.
{ // Получаем last-modified RadioOption фильтр из области var filter = scope.get_filter("last-modified") as RadioOptionFilter; // RadioOption намного проще, так как нужно просто запросить // активный вариант (если он существует) и выполнить if (filter.get_active_option()) url += "&modified=" + filter.get_active_option().id; }
Запрос состояния MultiRange.
{ // получаем по имени size - MultiRange фильтр из области var filter = scope.get_filter("size") as MultiRangeFilter; // Так как MultiRange работает при наличии начальной и конечной позиции, // нам нужно просто убедиться, что мы попадаем в диапазон. if (filter.get_first_active() && filter.get_last_active()) { url += "&min-size=" + filter.get_first_active().id + "&max-size=" + filter.get_last_active().id; } }
Запрос состояния Ratings.
{ // получаем оценку из фильтра данной области. var filter = scope.get_filter("ratings") as RatingsFilter; url += "&min-rating=" + "%f".printf(filter.rating); }
Обработка действий пользователя.
Область отвечает за обработку действий над всеми результатами, которые она же и выводит. Используя сигнал activate-uri, область может получать уведомления, когда пользователь щёлкает по показанным элементам в Dash.
Обработчик сигнала может указать Unity закрыть Dash или оставить Dash открытым.
private Unity.ActivationResponse on_uri_activated(string uri) { if (uri_should_hide_dash(uri)) { launch_uri(uri); // Мы обрабатываем действие пользователя и закрываем Dash return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH); } else if (uri_should_keep_dash_open(uri)) { launch_uri(uri); // Мы обрабатываем действие пользователя и оставляем Dash открытым return new Unity.ActivationResponse(Unity.HandledType.SHOW_DASH); } else { warning("Cannot handled URI: %s", uri); // Мы не справились с обработкой действия пользователя и Unity должна // попробовать свои запасные обработчики. return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED); } }
Nux.
Nux - виджет и графическая библиотека, созданная в Canonical. Nux - это виджеты, основанные на OpenGL, которые используются для создания пользовательского интерфейса подобно GTK+. Состоит из трёх компонент:
- NuxCore - отвечает за базовые сущности, такие как типы объектов, математические функции, описания цветов. Так же обеспечивает примитивными типами - цвет, прямоугольник, точка.
- NuxGraphics - это тонкий OpenGL абстрактный слой. Он упрощает работу с распространёнными OpenGL шаблонами. NuxGraphics представляет абстракцию для текстур, загрузки или выгрузки данных.
- Nux - это библиотека виджетов, основанная на NuxCore и NuxGraphics. Nux содержит дубликаты большинства известных виджетов GTK, а так же несколько составных виджетов типа "выбор цвета" (color picker), "графики" (graphs), "3D просмотр" (3d views).
Из того же цикла статей:
Unity Ubuntu изнутри. Launcher.
Unity Ubuntu изнутри. Меню сообщений.
Архитектура безопасности Ubuntu.
Немає коментарів:
Дописати коментар