Графични (desktop) интерфейси

17 януари 2023

Административни неща

GTK

Как да направим ООП-style наследяване?

Как да направим ООП-style наследяване?

Как да направим ООП-style наследяване?

Как да направим ООП-style наследяване?

Наследяване в GTK-rs

Ext-traits

Типа gtk::MessageDialog има само асоциирани функции new и builder. Повечето типове са така, имат само конструиращи функции.

Оттам нататък, всички собствени методи на този "клас" се намират в trait-а gtk::prelude::MessageDialogExt.

Всички наследени методи се намират в trait-овете: DialogExt, GtkWindowExt, BinExt, ContainerExt, WidgetExt, glib::object::ObjectExt, BuildableExt. Това са всички типове, за които имаме IsA имплементация за MessageDialog.

Това работи, когато повечето код е автоматично-генерирани binding-и, но би било доста тегаво да се поддържа ръчно.

ООП-style наследяване

IsA<T>

Примерно, имаме

1
MessageDialog::new<T: IsA<Window>>(parent: Option<&T>, /* ... */)

Това IsA<T> ни позволява да cast-ваме неща напред-назад:

1 2 3
let button = gtk::Button::new();
let widget = button.upcast::<gtk::Widget>();
assert!(widget.downcast::<gtk::Button>().is_ok());

Забележете, че upcast не връща резултат, а връща директно структура от правилния тип. Това се проверява compile-time, така че upcast няма да се компилира, ако cast-а е несъвместим.

Downcast, от друга страна, няма как да се провери at compile-time, затова връща Result.

Native rust-ки аналог (kind of): Any

Вижте и https://gtk-rs.org/gtk4-rs/stable/latest/book/g_object_subclassing.html за правене на ваши типове, макар че аз не бих се занимавал.

Инсталация на GTK3

Външните библиотеки вероятно ще са най-досадната част, особено под Windows: https://www.gtk.org/docs/installations/

1 2
[dependencies]
gtk = { version = "0.16", features = ["v3_24"] }

В main файла:

1 2 3 4
// За да може всички trait-ове да се include-нат,
// иначе ще трябва да се изброяват *доста*:
use gtk::prelude::*;
use gio::prelude::*;

Инсталация на GTK4

Горе-долу същата, но на теория може да има различни dependencies: https://gtk-rs.org/gtk4-rs/stable/latest/book/installation.html

1 2 3 4
[dependencies]
gtk = { version = "0.5.5", package = "gtk4", features = ["v4_4"]}
# или:
gtk4 = { version = "0.5.5", features = ["v4_4"]}

В main файла:

1 2
use gtk::prelude::*;
use gio::prelude::*;

Версии

Ако намерите tutorial online, който не се компилира, добра идея е да пробвате да пуснете cargo update -- това ще опита да инсталира по-нови версии на пакетите, които продължават да са съвместими с изискванията.

Примерно, проекта може да е фиксирал libc версия "0.2.33", но пакетите просто да търсят версия "0.2.x". Един cargo update може да вдигне до версия "0.2.82", която е API-compatible, но просто оправя някакви вътрешни проблеми.

Размери на пакетите

Досадно големи. Моя "quickmd" пакет има 2.6GB "target" директория. Проекти, които сте пробвали да компилирате веднъж и сте ги изоставили, може да ги зачистите с cargo clean.

Glade

При твърде сложен дизайн на интерфейса, може да се пробва да минем на Glade: https://glade.gnome.org/. Може да заредите UI-а от gtk::Builder и достъпвате компоненти с .object("<name>").

That said, builder-а генерира XML за GTK3, и нямам добри примери за употреба.

Отвъд това, "опаковането" на gui компоненти в наши си типове може да се окаже по-сложно… Но пък glade може и да ви даде идея какво имате като типове widget-и и какво може да използвате, дори да минете на ръка.

Demo

Quickmd

Source: https://github.com/AndrewRadev/rust-quickmd

Комуникиране между нишки

В GTK, widget-ите трябва да им се викат методи в главния thread. Това означава, че ако искате да предавате ownership напред-назад, вероятно е добре да ги опаковате в клонируеми smart pointer-и, но дори тогава може да не сработят нещата.

Проблема е добре описан в този blog post: https://coaxion.net/blog/2019/02/mpsc-channel-api-for-painless-usage-of-threads-with-gtk-in-rust/

Решението на статията е доста добро -- използвайте канали! В quickmd, има две нишки, които си комуникират със съобщения с канали:

Когато watcher-а, докато си цикли безкрайно, намери промяна във файл, изпраща съобщение по канал, и това съобщение стига до UI нишката и предизвиква update.

В някои отношения, това усложнява нещата. В други, ги опростява значително. Няма нужда да си мислите как ще споделите някакво парче данни -- дръжте му ownership-а на едно-единствено място и просто изпращайте съобщения по канал. Това улеснява много и тестването на неща в изолация.

Demo

GTK4

Source: https://github.com/AndrewRadev/gtk4-example

Дебъгване

gtk::Application vs gtk::main

"Стария" стил на писане на GTK приложения (GTK 2.0):

gtk::Application vs gtk::main

"Стария" стил на писане на GTK приложения (GTK 2.0):

gtk::Application vs gtk::main

"Стария" стил на писане на GTK приложения (GTK 2.0):

gtk::Application vs gtk::main

"Стария" стил на писане на GTK приложения (GTK 2.0):

gtk::Application vs gtk::main

"Стария" стил на писане на GTK приложения (GTK 2.0):

gtk::Application vs gtk::main

"Стария" стил на писане на GTK приложения (GTK 2.0):

gtk::Application vs gtk::main

"Новия" стил на писане на GTK приложения (GTK 3.0+):

gtk::Application vs gtk::main

"Новия" стил на писане на GTK приложения (GTK 3.0+):

gtk::Application vs gtk::main

"Новия" стил на писане на GTK приложения (GTK 3.0+):

gtk::Application vs gtk::main

"Новия" стил на писане на GTK приложения (GTK 3.0+):

gtk::Application vs gtk::main

"Новия" стил на писане на GTK приложения (GTK 3.0+):

gtk::Application vs gtk::main

"Новия" стил на писане на GTK приложения (GTK 3.0+):

gtk::Application vs gtk::main

"Новия" стил на писане на GTK приложения (GTK 3.0+):

gtk::Application vs gtk::main

Двата модела не са съвсем еквивалентни -- стария стил означава, че приложението е self-contained -- стартира, прави нещо, приключва. Ако пуснете второ такова приложение, то ще е отделно.

С новия модел, ако пуснете второ приложение, то просто ще "активира" първото. Примерно, ако в командния ред пуснете firefox http://google.com, това ще ви отвори firefox и ще чака в терминала. Ако след това в друг терминал пуснете firefox http://duckduckgo.com, това веднага ще приключи, и ще отвори DuckDuckGo във вече отворения firefox.

Това усложнява малко логиката, но е нещо, което е oчаквано донякъде в повечето модерни GUI приложения. Един application, множество прозорци. Има и бонуси, като интеграция с DBUS и разни други неща.

Command-line handling

В gtk4-example:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use gio::prelude::*;
use gio::ApplicationFlags;

fn main() {
    let application = Application::builder().
        application_id("com.andrewradev.ClickExample").
        flags(ApplicationFlags::HANDLES_OPEN | ApplicationFlags::HANDLES_COMMAND_LINE).
        build();

    application.connect_command_line(move |app, cmdline| {
        build_ui(&app, cmdline.arguments());
        0
    });

    application.run_with_args(&env::args().collect::<Vec<_>>());
}

fn build_ui(app: &Application, arguments: Vec<OsString>) {
    // ...
}

Тоест, вместо да чакаме connect_startup, чакаме connect_command_line, защото очакваме някакъв потенциално различен вход при всяко изпълнение.

EGUI

EGUI

EGUI

EGUI

EGUI

Demo

EGUI -- basic template

Source: https://github.com/emilk/eframe_template/

EGUI template -- хитри неща

Demo

EGUI -- mp3 filter

Source: https://github.com/andrewradev/egui-mp3s

Зареждане на картинки

Трябва ни egui_extras пакета: https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html

Кое е по-харно?

Кое е по-харно?

GTK

Кое е по-харно?

GTK

Кое е по-харно?

GTK

Egui

Кое е по-харно?

GTK

Egui

Други интересни ресурси

Други интересни ресурси

Други интересни ресурси

Други интересни ресурси

Други интересни ресурси

Други интересни ресурси

Въпроси