# HdyTab як альтернатива віджету вкладок GtkNotebook

Якщо ви тільки починаєте своє знайомство з фреймворком GTK, можливо, як і в мене, виникне схожа дилема з обмеженою функціональністю класичного віджету для створення вкладок GtkNotebook. В мережі віднайшов матеріал 2021 року "Reinventing tabs", в якому розглядається проблема стандартного віджету та пропонується альтернатива - HdyTab.

* даний матеріал є остаточно застарілим, якщо ви досі шукаєте заміну GtkNotebook, зверніть увагу на бібліотеку Libadwaita, зокрема віджет TabBar, який реалізує клас TabView

## Переосмислення вкладок

В робочому столі GNOME 40, браузер Epiphany має нову панель вкладок. Це не просто переробка старої панелі, а повне її переписування з нуля:

=> https://blogs.gnome.org/alicem/files/2021/03/ephy.png Вигляд вкладок в Epiphany

Але для чого це було необхідно?

## Статичні та динамічні вкладки

Додатки GNOME використовують вкладки різними способами, їх можна приблизно поділити на дві категорії.

### Статичні вкладки

=> https://blogs.gnome.org/alicem/files/2021/03/dialog-tabs-768x645.png Вигляд статичних вкладок в різних діалогах

Вони є повністю незмінними, кожна вкладка містить заголовок і зазвичай не прокручуються. Зазвичай їх використовують у діалогових вікнах, але, наприклад, їх також можна використовувати в панелях інструментів. Сучасні додатки часто використовують GtkStackSwitcher або HdyViewSwitcher, але GtkNotebook все ще досить поширений.

### Динамічні вкладки

=> https://blogs.gnome.org/alicem/files/2021/03/browser-tabs.png Вигляд gedit, GNOME Terminal, Nautilus, GNOME Editor, Devhelp, Sysprof, Epiphany

Цей тип вкладок використовується в багатовіконних додатках, таких як веб-браузери, текстові редактори, файлові менеджери або емулятори терміналів. Вони відкриваються та закриваються користувачем, їх можна змінювати за порядком, перетягувати між вікнами або в нові вікна, скидаючи їх на робочий стіл. Вони часто реалізують прокрутку. Завжди мають заголовок і кнопку закриття, іноді іконку та/або індикатор завантаження. Вони ховаються, коли присутня лише одна вкладка. Їх можна закривати середнім кліком або через контекстне меню. Іноді, динамічні вкладки можна закріпити або додати іконку, наприклад, індикатор відтворення аудіо чи діалог підтвердження закриття.

### Чому така різниця?

Хоча статичні вкладки в GNOME досить однорідні, динамічні вкладки в різних додатках виглядають і відчуваються дуже по-різному.

Наприклад:

* в GNOME Text Editor - кнопки закриття є лише в обраних вкладках, або при наведені на вкладку, тоді як в інших додатках - вони присутні на всіх вкладках
* деякі додатки дозволяють вкладкам зменшуватися до малого розміру перед прокруткою, інші починають прокручуватися раніше
* Devhelp - взагалі не прокручує вкладки, а просто виводить їх за межі вікна
* Epiphany та GNOME Terminal - мають спливаючі вікна, що показують усі ці вкладки; GNOME Terminal використовує GtkMenu, тоді як Epiphany - використовує GtkPopover
* хоча це не видно на скріншоті, лише Epiphany та gedit підтримують перетягування вкладок між вікнами
* У Epiphany та Nautilus - можна перетягувати файли на вкладки. У Nautilus, якщо ви наведете курсор на вкладку під час перетягування файлу, вона автоматично переключиться на цю вкладку, але не в Epiphany
* У gedit та GNOME Terminal - неможливо зосередити фокус на активній вкладці, натискаючи на неї знову
* У GNOME Text Editor та Epiphany - при переході на іншу вкладку, коли відбувся фокус на активну вкладку, її фокус буде здійснено на сторінку а не саму вкладку

### GtkNotebook

Усі додатки вище використовують віджет під назвою GtkNotebook. Він реалізує загальний вигляд з вкладками та показує вкладки з одного боку, причому кожна вкладка має заголовок, який можна замінити на будь-який інший віджет.

За замовчуванням GtkNotebook не має засобів прокручування, має простий заголовок на кожній вкладці, не розширює вкладки та не дозволяє жодного виду перетягування.

Іншими словами, він орієнтований виключно на статичні вкладки. Це не означає, що його неможливо використовувати для динамічних вкладок, але вам доведеться вручну реалізувати наступне:

* макет вкладок з кнопкою закриття, заголовком, іконкою та, можливо, індикатором беззвучного режиму, а також встановити розумну мінімальну ширину для прокрутки
* гарячі клавіші, такі як Ctrl+Tab або Alt+[1–9] для перемикання між вкладками
* обробку контекстного меню, включаючи гарячі клавіші та жест довгого натискання для сенсорних екранів
* закриття середнім кліком
* логіку відкриття/закриття вкладок - наприклад, яку вкладку вибрати, коли активна вкладка закрита
* якщо потрібно скинути дані на вкладки, обробник для цього. Також потрібно бути обережним, щоб випадково не зламати тайм-аут наведення в процесі, як це робить Epiphany у версії 3.38.x
* авто-приховування контейнера, коли в ньому присутня лише одна вкладка

Ці вимоги підкреслюють, що хоча GtkNotebook може бути використаний для динамічних вкладок, його базова реалізація не підтримує багато функцій, які користувачі очікують від сучасних динамічних вкладок, і вимагає значних зусиль для налаштування.

Звісно, кожен додаток реалізує ці функції трохи по-різному. А ще є проблеми, які не можна повністю вирішити з боку додатка, наприклад, відсутня функціональність закріплених вкладок або інтерфейс загалом не дуже відшліфований - не має анімації при відкритті або закритті вкладок.

Також, прокручування вкладок є дискретним, це виглядає заплутано і викликає інші проблеми:
* динамічні вкладки можуть мати велику кількість вкладок, натискання стрілок для прокрутки по одній вкладці за раз не є ефективним у цьому випадку
* пов'язано з цим, неможливо взяти вкладку і перемістити її на великі відстані; вам доведеться перемістити її на кілька вкладок вперед, натиснути кнопки прокрутки кілька разів, знову перемістити тощо
* при відкритті нової вкладки вона може опинитися за межами екрану без очікування такої поведінки
* мінімальна ширина вкладки змінюється в залежності від розміру вікна

Ці проблеми підкреслюють, що, хоча додатки можуть намагатись реалізувати динамічні вкладки засобами статичного віджета GtkNotebook, існують обмеження, які ускладнюють створення зручного та інтуїтивно зрозумілого досвіду для користувачів.

Деякі з таких проблем можна замаскувати:

* Epiphany імітує закріплені вкладки, примусово переміщуючи їх на початок, використовуючи компактний макет, не розширюючи їх і не дозволяючи закривати. Однак ці вкладки все ще можуть бути прокручені, і те, що ширина вкладок змінюється в залежності від розміру вікна, особливо негативно впливає на зовнішній вигляд:

=> https://blogs.gnome.org/alicem/files/2021/03/ephy-pinned1-768x82.png Вигляд компактно закріплених вкладок
=> https://blogs.gnome.org/alicem/files/2021/03/ephy-pinned2-768x78.png Вигляд закріплених вкладок при зміні ширини вікна

* Epiphany вручну підсвічує стрілки, коли вкладка була відкрита за межами екрана, також надає спливаюче вікно для швидкого перемикання між вкладками, коли вони не вміщаються на екрані, однак це не допомагає з перестановкою вкладок
* динамічний віджет DynamicNotebook (https://valadoc.org/granite/Granite.Widgets.DynamicNotebook.html) в Elementary реалізує затримку зміни розміру після закриття вкладки, але це не завжди надійно; наприклад, він стрибає, коли стрілки прокрутки зникають, крім того, він може працювати лише тоді, коли вкладки не розширені
* ці особливості підкреслюють, що, хоча обидва додатки намагаються покращити управління вкладками, існують обмеження, які можуть призвести до непередбачуваного або незручного досвіду для користувачів; підсвічування стрілок може бути корисним, але не вирішує основну проблему з доступом до вкладок, які відкриваються за межами екрана; затримка зміни розміру може бути корисною, але її ненадійність може викликати додаткові труднощі

Крім того, GtkNotebook містить як вміст, так і вкладки, і вони є невід'ємними, тому неможливо мати вкладки у повноекранному режимі, автоматично приховуючи їх з заголовної панелі, або розміщувати їх у панелі заголовку, відокремлено від вмісту.

Нарешті, GtkNotebook не є адаптивним, і Epiphany наразі має вбудовану реалізацію мобільного перемикача вкладок, яку кожен додаток, що прагне адаптивного інтерфейсу з вкладками - повинен буде копіювати.

Ці обмеження підкреслюють, що GtkNotebook не забезпечує гнучкості, необхідної для сучасних інтерфейсів, які можуть адаптуватися до різних розмірів екранів і режимів використання. Відсутність можливості розміщення вкладок окремо від вмісту обмежує можливості дизайну, а необхідність копіювати реалізацію мобільного перемикача вкладок може призвести до дублювання зусиль і несумісності між додатками.

## HdyTab як рішення

=> https://blogs.gnome.org/alicem/files/2021/03/tab-view-demo-768x128.png Як виглядають вкладки HdyTab

Отже, з урахуванням усіх цих обмежень, я вважаю, що найчистішим шляхом вперед є створення абсолютно нового віджета, який реалізує специфічно динамічні вкладки. Таким чином, також зможемо відокремити перегляд вкладок і панель вкладок в окремі віджети, так само як GtkStack і GtkStackSwitcher - є окремими.

Реалізацію цього віджета було розпочато незадовго до GUADEC 2020 року, і до кінця вересня, на момент долучення до Purism (https://puri.sm) - її було майже завершено. У січні ми з Адрієном нарешті знайшли час, щоб закінчити, переглянути та впровадити його.

Результатом цього стали два віджети під назвою HdyTabView і HdyTabBar, доступні в бібліотеці libhandy, починаючи з версії 1.2:

=> https://gnome.pages.gitlab.gnome.org/libhandy

HdyTabView містить GtkStack і надає API, більш придатний для динамічних вкладок: наприклад, сторінки відтепер строго впорядковані, є API для перестановки вкладок тощо. Крім того, кожна сторінка має велику кількість метаданих, вона використовує об'єкти сторінок, схожі на GTK 4 (HdyTabPage), навіть у GTK 3, замість властивостей дочірніх елементів, які було б досить незручно використовувати без прив'язок властивостей.

HdyTabBar є панеллю вкладок, яка підключається до HdyTabView і відображає його сторінки, реалізуючи цей дизайн:

=> https://gitlab.gnome.org/Teams/Design/os-mockups/-/raw/master/tabs/tabs.png Вигляд різних реалізацій вкладок

### Що він забезпечує

* вкладки мають іконку, заголовок, підказку, кнопку закриття, контекстне меню, і вони можуть показувати індикатор завантаження замість іконки
* закріплені вкладки: вони компактні, розташовані на початку панелі вкладок і не прокручуються разом з іншими вкладками; вони також впливають на поведінку гарячих клавіш, таких як Ctrl+Home/End
* індикатори непрочитаних повідомлень, включаючи випадки, коли непрочитана вкладка прокручується за межі екрану:

=> https://blogs.gnome.org/alicem/files/2021/03/tabs-unread-1024x156.png Вигляд непрочитаних вкладок

* піктограми-індикатори: кожна сторінка може мати одну піктограму-індикатор на вкладці, опціонально доступну для натискання; Epiphany використовує його як індикатор відтворення аудіо/кнопку вимкнення звуку, а індикатори - розташовані зліва від вкладки, щоб не конфліктувати з кнопками закриття:

=> https://blogs.gnome.org/alicem/files/2021/03/tabs-indicators-768x109.png Вигляд індикаторів вкладок

Коли вкладка закріплена, індикатор відображається замість іконки, а не поруч з нею, як це було раніше в Epiphany. Щоб забезпечити легкий вибір вкладки, вона є клікабельною лише тоді, коли вкладка вже вибрана, тому перший клік вибирає вкладку, а другий клік - активує індикатор.

* автоматичне приховування, коли присутня лише одна вкладка
* при закритті вкладок за допомогою вказівника, вони не заповнюють порожній простір одразу, натомість вони змінюють розмір так, щоб кнопка закриття наступної вкладки перемістилася точно туди, де була остання, що дозволяє закривати кілька вкладок поспіль, не переміщуючи вказівник
* автоматичне позиціонування для нових вкладок, хоча також є API для вставки вкладок у довільні позиції, якщо це потрібно
* повноцінна підтримка перетягування: вкладки можна переставляти, переміщувати між вікнами, скидувати на робочий стіл для створення нового вікна, вкладки можуть приймати довільні дані, які скидаються; якщо панель вкладок має лише одну вкладку, вона автоматично відображається, коли починається перетягування, і приховується, коли воно закінчується, тому все ще можливо скинути іншу вкладку в неї
* вкладки також можна скинути на перегляд вкладок; в цьому випадку вони додаються в кінець панелі вкладок
* підтримка сенсорних екранів: перетягніть панель вкладок, щоб прокрутити її, довге натискання і перетягування для перестановки вкладки, довше натискання для відкриття контекстного меню
* API підтвердження закриття
* відкриття, закриття, перестановка вкладок, перетягування, показування та приховування панелі вкладок - анімовані
* гарячі клавіші:

    - Ctrl+Tab і Shift+Ctrl+Tab - перемикання між вкладками з обгортанням
    - Ctrl+PageUp/PageDown/Home/End - те ж саме, але без обгортання
    - Ctrl+Shift+PageUp/PageDown/Home/End - перестановка вкладок
    - Alt+[1–9] - перемикання на одну з перших 9 вкладок

Той факт, що вони є окремими, дозволяє Epiphany зберігати свою панель вкладок у повноекранному режимі в 40 версії:

=> https://blogs.gnome.org/alicem/files/2021/03/ephy-fullscreen-768x51.png Вигляд вкладок у повноекранному режимі Epiphany

### Чого він не забезпечує

* фіксований макет вкладок: HdyTabView не дозволяє встановлювати довільний віджет як макет вкладок; макет є фіксованим і керується виключно через властивості HdyTabPage
* відсутність окремого управління перестановкою та від'єднанням: не можна окремо включати або вимикати перестановку та від'єднання для різних вкладок; вкладки завжди можна переставляти та від'єднувати, і немає можливості це вимкнути
* відсутність контролю над розширенням вкладок: аналогічно, немає можливості зробити так, щоб певні вкладки розширювалися або не розширювалися, усі вкладки (за винятком закріплених) за замовчуванням - розширені
* кнопки закриття: у всіх вкладках (за винятком закріплених) є кнопки закриття, їх не можна видалити, хоча можливо затримати та/або відхилити запит на закриття, наприклад, якщо додаток хоче показати діалог підтвердження при закритті вкладки
* видимість кнопок закриття: кнопки закриття видимі лише на вибраних та/або наведені вкладки, немає можливості показувати їх на всіх вкладках
* вертикальні вкладки: HdyTabBar є строго горизонтальним, і його макет не має сенсу у вертикальному вигляді, однак він має API для спостереження за своїми сторінками через GListModel (GtkSelectionModel у GTK 4), тому дуже легко створити вертикальний список замість цього

Оскільки Epiphany також використовується в Elementary OS, HdyTabBar надає API для вимкнення автоматичного приховування та розширення вкладок, а також для зміни місцями кнопки закриття та індикатора. Усі ці параметри все ще працюють так, як і раніше.

=> https://blogs.gnome.org/alicem/files/2021/03/tabs-elementary-768x84.png Вигляд Epiphany в "елементарному режимі"

### Адаптивність

HdyTabBar поки що не є адаптивним. Epiphany все ще постачає той самий мобільний інтерфейс у версії 40. Однак це значно полегшує реалізацію альтернативних комутаторів, наприклад, нижній аркуш на основі HdyFlap:

=> https://blogs.gnome.org/alicem/files/2021/03/tabs-adaptive-gtk3-603x1024.png Вигляд перемикача вкладок для мобільних пристроїв у стадії розробки

Однак це дуже базово. Мобільні перемикачі вкладок часто є дуже складними, наприклад, використовують 3D-стеки карток, сітки, каруселі або списки з попередніми переглядами. На жаль, усе це недоступно в GTK 3. Однак у GTK 4 це можливо, і ми можемо робити такі речі:

=> https://blogs.gnome.org/alicem/files/2021/03/tabs-overview-carousel-603x1024.png Вигляд вкладок у режимі каруселі
=> https://blogs.gnome.org/alicem/files/2021/03/tabs-overview-grid-1024x904.png Вигляд вкладок у режимі сітки

Маючи належний огляд, можна буде нарешті видалити спливаюче вікно вкладок з Epiphany, яке все ще є в 40 версії, але показується лише тоді, коли панель вкладок починає прокручуватися.

Порт HdyTabView і HdyTabBar на GTK 4 завершено і повністю працює, і йому просто потрібен код-рев'ю, хоча огляд ще не є повторно використовуваним віджетом.

## Джерело

=> https://blogs.gnome.org/alicem/2021/03/13/reinventing-tabs/ Reinventing tabs, Alice Mikhaylenko's GNOME blog