Spec-Zone.ru › GTK 3.20

Модель рисования GTK+

Модель рисования GTK+ — Подробное описание модели рисования GTK+

Обзор модели рисования

В данном разделе подробно описана модель рисования GTK+. Если вас интересует процедура, которую GTK+ использует для рисования своих виджетов и окон, вам следует прочитать этот раздел; это будет полезно, если вы решите реализовать свои собственные виджеты. Этот раздел также прояснит причины, по которым некоторые вещи делаются определённым образом в GTK+; например, почему вы не можете изменить цвет фона всех виджетов одним и тем же методом.

Окна и события

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

Здесь «окна» означают «прямоугольные области с автоматическим обрезанием», а не «главные окна приложения». Большинство систем окон поддерживают вложенные окна, где содержимое дочерних окон обрезается границами их родительских окон. Хотя GTK+ и GDK, в частности, могут работать в системе окон без такого понятия вложенных окон, GDK создаёт иллюзию работы в такой системе. Главное окно может содержать множество дочерних и внучатых окон, например, одно для строки меню, одно для области документа, одно для каждой полосы прокрутки и одно для строки состояния. Кроме того, элементы управления, которые получают пользовательский ввод, такие как нажимаемые кнопки, скорее всего, также будут иметь свои собственные дочерние окна.

На практике большинство окон в современных приложениях GTK+ являются клиентскими конструкциями. Только несколько окон (в частности, главные окна) являются нативными, что означает, что они представляют окно в базовой системе окон, в которой работает GTK+. Например, в X11 это соответствует Window; в Win32 — HANDLE.

Обычно цикл рисования начинается, когда GTK+ получает событие экспонирования от базовой системы окон: если пользователь перетаскивает окно поверх другого, система окон сообщит базовому окну, что оно нуждается в перерисовке. Цикл рисования также может быть инициирован, когда сам виджет решает, что ему необходимо обновить своё отображение. Например, когда пользователь вводит символ в виджете GtkEntry, поле ввода запрашивает GTK+ поместить в очередь операцию перерисовки для себя.

Система окон генерирует события для нативных окон. Интерфейс GDK к системе окон преобразует такие нативные события в структуры GdkEvent и отправляет их в слой GTK. В свою очередь, слой GTK находит виджет, соответствующий конкретному GdkWindow, и генерирует соответствующие сигналы событий на этом виджете.

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

Кадр часов

Все приложения GTK+ управляются циклом событий, что означает, что большую часть времени приложение простаивает в цикле, просто ожидая, что что-то произойдёт, и затем вызывает соответствующее место, когда это происходит. В дополнение к этому, GTK+ имеет цикл обработки кадров, который даёт «импульс» приложению. Эти часы бьют с постоянной скоростью, которая привязана к частоте кадров вывода (это синхронизировано с монитором через менеджер окон/композитор). Часы имеют несколько фаз:

  • События

  • Обновление

  • Макет

  • Рисование

Фазы происходят в таком порядке, и мы всегда пройдём через каждую фазу, прежде чем вернуться к началу.

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

После завершения фазы событий мы приостанавливаем все внешние события и запускаем цикл перерисовки. Сначала выполняется фаза обновления, где все анимации выполняются для вычисления нового состояния на основе прогнозируемого времени отображения следующего кадра (доступного через цикл обработки кадров). Это часто включает в себя изменения геометрии, которые приводят к следующей фазе — макету. Если есть какие-либо изменения в требованиях к размеру виджета, мы вычисляем новый макет для иерархии виджетов (то есть мы назначаем размеры и положения).

Затем переходим к фазе рисования, где мы перерисовываем области окна, которые требуют перерисовки.

Если ничего не требует фаз обновления/макета/рисования, мы будем навсегда оставаться в фазе событий, так как мы не хотим перерисовывать, если ничего не изменилось. Каждая фаза может запросить дальнейшую обработку в последующих фазах (например, фаза обновления вызовет работу по макету, а изменения макета вызывают перерисовку).

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

  • Если вы выполняете анимацию, вы можете использовать gtk_widget_add_tick_callback(), что вызовет регулярное срабатывание часов с обратной связью в фазе обновления до тех пор, пока вы не остановите отсчёт.

  • Если какие-то изменения состояния приводят к изменению размера вашего виджета, вы вызываете gtk_widget_queue_resize(), что запросит фазу макета и отметит ваш виджет как требующий перемакетирования.

  • Если какие-то изменения состояния требуют перерисовки некоторой области вашего виджета, вы используете обычный набор функций gtk_widget_queue_draw(). Эти функции запросят фазу рисования и пометят область как требующую перерисовки.

Также существует много неявных триггеров этих событий со стороны слоя CSS (который выполняет анимации, изменения размеров и перерисовку по мере необходимости).

Иерархическое рисование

Во время фазы рисования мы отправим одно событие экспонирования в главное окно. Обработчик событий создаст контекст Cairo для окна и сгенерирует сигнал GtkWidget::draw() в нём, который будет распространяться по всей иерархии виджетов в порядке от заднего плана к переднему, используя обрезание и преобразование контекста Cairo. Это позволяет каждому виджету рисовать своё содержимое в нужном месте и в нужное время, правильно обрабатывая такие вещи, как частичная прозрачность и перекрывающие виджеты.

При генерации события GDK также настраивает буферизацию с двойным буфером, чтобы избежать мерцания, которое могло бы возникнуть, если бы каждый виджет рисовал себя по очереди. Раздел, посвящённый «Двойной буферизации» подробно описывает механизм двойной буферизации.

Обычно используется только один контекст Cairo, который используется при всей перерисовке, а не по одному на каждое GdkWindow. Это означает, что вы должны соблюдать (и не сбрасывать) существующие обрезания и преобразования, заданные на нём.

Большинство виджетов, включая те, которые создают свои собственные GdkWindows, имеют прозрачный фон, поэтому они рисуют поверх виджетов, которые находятся под ними. Это не было так в GTK+ 2, где тема устанавливала фон большинства виджетов в стандартный цвет фона. (На самом деле, прозрачные GdkWindows раньше были невозможны.)

Вся иерархия рендеринга захватывается в стеке вызовов, а не в нескольких отдельных выводах draw, поэтому вы можете использовать эффекты, такие как, например, cairo_push/pop_group(), которые повлияют на все виджеты под вами в иерархии. Это позволяет иметь, например, частично прозрачные контейнеры.

Прокрутка

Традиционно GTK+ использовал операции самокопирования для реализации прокрутки с нативными окнами. С прозрачными фонами это больше не работает. Вместо этого мы просто помечаем всю затронутую область для перерисовки при использовании этих операций. Это позволяет (частично) прозрачные фоны, а также более точно моделирует современное оборудование, где операции самокопирования проблематичны (они нарушают конвейер рендеринга).

Поскольку вышеупомянутое приводит к некоторым накладным расходам, мы вводим механизм кэширования. Контейнеры, которые много прокручиваются (GtkViewport, GtkTextView, GtkTreeView и т. д.) выделяют внеэкранное изображение во время прокрутки и рисуют своих потомков на нём (что возможно, поскольку рисование полностью иерархическое). Внеэкранное изображение немного больше, чем видимая область, поэтому в большинстве случаев при прокрутке ему просто нужно нарисовать внеэкранное изображение в другом положении. Это гораздо лучше соответствует современному графическому оборудованию, а также позволяет эффективно использовать прозрачные фоны. Для работы таких контейнеров необходимо обнаруживать, когда виджеты-потомки перерисовываются, чтобы можно было обновить внеэкранное изображение. Это можно сделать с помощью новой функции gdk_window_set_invalidate_handler().

Двойной буферинг

Если каждый вызов рисования, сделанный обработчиком draw каждого подвиджета, отправлялся бы непосредственно в систему окон, могла возникнуть мерцание. Это происходит потому, что области могут быть перерисованы повторно: фон, затем декоративные рамки, затем текстовые метки и т. д. Чтобы избежать мерцания, GTK+ использует систему двойного буферирования на уровне GDK. Виджеты обычно не знают, что они рисуют в буфер вне экрана; они просто выполняют свои обычные команды рисования, а буфер отправляется в систему окон, когда все операции рисования завершены.

Две основные функции в GDK составляют основу механизма двойного буферирования: gdk_window_begin_paint_region() и gdk_window_end_paint(). Первая функция сообщает GdkWindow, чтобы создать временный буфер вне экрана для рисования. Все последующие операции рисования в этом окне автоматически перенаправляются в этот буфер. Вторая функция фактически отрисовывает буфер на экране окна и освобождает буфер.

Автоматическое двойное буферирование

Было бы неудобно для всех виджетов вызывать gdk_window_begin_paint_region() и gdk_window_end_paint() в начале и в конце их обработчиков рисования.

Чтобы упростить задачу, GTK+ обычно вызывает gdk_window_begin_paint_region() перед выводом сигнала #GtkWidget::draw, а затем вызывает gdk_window_end_paint() после того, как сигнал был выведен. Это удобно для большинства виджетов, так как им не нужно беспокоиться о создании своих временных буферов рисования или о вызове этих функций.

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

Пример 5. Отключение автоматического двойного буферирования

staticvoid
my_widget_init(MyWidget*widget)
{
...

gtk_widget_set_double_buffered(widget, FALSE);

...
}

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

Даже если вы отключите двойное буферирование для виджета, вы по-прежнему можете вручную вызывать gdk_window_begin_paint_region() и gdk_window_end_paint(), чтобы использовать временные буферы рисования.

Виджеты, рисуемые приложением

В общем случае приложения используют предварительно определенные виджеты в GTK+ и не рисуют дополнительные элементы поверх них (исключение составляют GtkDrawingArea). Однако приложениям иногда может быть удобно рисовать напрямую на определенных виджетах, таких как окна верхнего уровня или ящики событий. В этом случае GTK+ необходимо указать, чтобы не перезаписывать ваше рисование впоследствии, когда окно приступает к отрисовке своего стандартного содержимого.

GtkWindow и GtkEventBox — это два виджета, которые позволяют отключить отрисовку стандартного содержимого, вызвав gtk_widget_set_app_paintable(). Если вы вызовете эту функцию, они не будут отрисовывать своё содержимое и позволят вам сделать это вместо них.

Так как сигнал #GtkWidget::draw выполняется обработчиками пользователя перед стандартным обработчиком виджета, обычно происходит следующее:

  1. Выполняется ваш собственный обработчик рисования. Он рисует что-то на окне или в ящике событий.

  2. Выполняется стандартный обработчик рисования виджета. Если gtk_widget_set_app_paintable() не был вызван для отключения рисования виджета (это значение по умолчанию), ваше рисование будет перезаписано. Однако виджет, рисуемый приложением, не будет отрисовывать своего стандартного содержимого и вместо этого сохранит ваше рисование.

  3. Выполняется обработчик рисования для родительского класса. Поскольку и GtkWindow, и GtkEventBox являются потомками GtkContainer, их дочерние элементы без окна будут попрошены нарисовать себя рекурсивно, как описано в разделе «Иерархическое рисование».

Резюме виджетов, рисуемых приложением. Вызовите gtk_widget_set_app_paintable(), если вы планируете рисовать своё собственное содержимое напрямую на GtkWindow и GtkEventBox. Вам редко нужно рисовать поверх других виджетов, и GtkDrawingArea игнорирует этот флаг, так как он предназначен для отрисовки.

© 2005–2020 The GNOME Project
Licensed under the GNU Lesser General Public License version 2.1 or later.
https://developer.gnome.org/gtk3/3.20/chap-drawing-model.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API