Графични (desktop) интерфейси
17 януари 2023
Административни неща
- Предизвикателство 3 за малко бонус точки: https://fmi.rust-lang.bg/challenges/3
- Предложения за проекти -- до петък (https://fmi.rust-lang.bg/guides/projects)
- Сложете си име и факултетен номер в предложението
GTK
- Главния сайт: https://gtk-rs.org/
- Добър guide за GTK4: https://gtk-rs.org/gtk4-rs/stable/latest/book/
- API документация за GTK4: https://docs.rs/gtk4/latest/gtk4/
- API документация за GTK3 (по-стабилна засега): https://docs.rs/gtk/latest/gtk/
Как да направим ООП-style наследяване?
Как да направим ООП-style наследяване?
Deref
: Ограничен, само за един тип
Как да направим ООП-style наследяване?
Deref
: Ограничен, само за един тип- Делегация, в някоя хипотетична бъдеща версия на Rust (RFC)
Как да направим ООП-style наследяване?
Deref
: Ограничен, само за един тип- Делегация, в някоя хипотетична бъдеща версия на Rust (RFC)
- Trait-ове
Наследяване в 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>
Примерно, имаме
MessageDialog::new<T: IsA<Window>>(parent: Option<&T>, /* ... */)
Това IsA<T>
ни позволява да cast-ваме неща напред-назад:
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/
[dependencies]
gtk = { version = "0.16", features = ["v3_24"] }
В main файла:
// За да може всички trait-ове да се include-нат,
// иначе ще трябва да се изброяват *доста*:
use gtk::prelude::*;
use gio::prelude::*;
Инсталация на GTK4
Горе-долу същата, но на теория може да има различни dependencies: https://gtk-rs.org/gtk4-rs/stable/latest/book/installation.html
[dependencies]
gtk = { version = "0.5.5", package = "gtk4", features = ["v4_4"]}
# или:
gtk4 = { version = "0.5.5", features = ["v4_4"]}
В main файла:
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-и и какво може да използвате, дори да минете на ръка.
Комуникиране между нишки
В 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, има две нишки, които си комуникират със съобщения с канали:
- UI thread
- Watcher loop
Когато watcher-а, докато си цикли безкрайно, намери промяна във файл, изпраща съобщение по канал, и това съобщение стига до UI нишката и предизвиква update.
В някои отношения, това усложнява нещата. В други, ги опростява значително. Няма нужда да си мислите как ще споделите някакво парче данни -- дръжте му ownership-а на едно-единствено място и просто изпращайте съобщения по канал. Това улеснява много и тестването на неща в изолация.
Дебъгване
GTK_DEBUG=help cargo run
за помощ,GTK_DEBUG=interactive cargo run
за да видите дървото от widget-и live и да го ръчкате
gtk::Application vs gtk::main
"Стария" стил на писане на GTK приложения (GTK 2.0):
gtk::Application vs gtk::main
"Стария" стил на писане на GTK приложения (GTK 2.0):
gtk::init()
gtk::Application vs gtk::main
"Стария" стил на писане на GTK приложения (GTK 2.0):
gtk::init()
- Правим каквото правим, създаваме си дървото от GUI елементи
gtk::Application vs gtk::main
"Стария" стил на писане на GTK приложения (GTK 2.0):
gtk::init()
- Правим каквото правим, създаваме си дървото от GUI елементи
- Викаме на най-главния прозорец
window.show_all()
gtk::Application vs gtk::main
"Стария" стил на писане на GTK приложения (GTK 2.0):
gtk::init()
- Правим каквото правим, създаваме си дървото от GUI елементи
- Викаме на най-главния прозорец
window.show_all()
gtk::main()
gtk::Application vs gtk::main
"Стария" стил на писане на GTK приложения (GTK 2.0):
gtk::init()
- Правим каквото правим, създаваме си дървото от GUI елементи
- Викаме на най-главния прозорец
window.show_all()
gtk::main()
- За да приключим, някъде викаме
gtk::main_quit()
gtk::Application vs gtk::main
"Новия" стил на писане на GTK приложения (GTK 3.0+):
gtk::Application vs gtk::main
"Новия" стил на писане на GTK приложения (GTK 3.0+):
let application = gtk::Application::new(<уникално име>, <флагове>)?;
gtk::Application vs gtk::main
"Новия" стил на писане на GTK приложения (GTK 3.0+):
let application = gtk::Application::new(<уникално име>, <флагове>)?;
application.connect_startup(|application| <инициализираме си всичко>);
gtk::Application vs gtk::main
"Новия" стил на писане на GTK приложения (GTK 3.0+):
let application = gtk::Application::new(<уникално име>, <флагове>)?;
application.connect_startup(|application| <инициализираме си всичко>);
- Стартираме приложението със
application.run(&args().collect::<Vec<_>>());
gtk::Application vs gtk::main
"Новия" стил на писане на GTK приложения (GTK 3.0+):
let application = gtk::Application::new(<уникално име>, <флагове>)?;
application.connect_startup(|application| <инициализираме си всичко>);
- Стартираме приложението със
application.run(&args().collect::<Vec<_>>());
- (или може би със
application.run(&[]);
)
gtk::Application vs gtk::main
"Новия" стил на писане на GTK приложения (GTK 3.0+):
let application = gtk::Application::new(<уникално име>, <флагове>)?;
application.connect_startup(|application| <инициализираме си всичко>);
- Стартираме приложението със
application.run(&args().collect::<Vec<_>>());
- (или може би със
application.run(&[]);
) - (Вместо
gtk::Window
, използвамеgtk::ApplicationWindow
)
gtk::Application vs gtk::main
"Новия" стил на писане на GTK приложения (GTK 3.0+):
let application = gtk::Application::new(<уникално име>, <флагове>)?;
application.connect_startup(|application| <инициализираме си всичко>);
- Стартираме приложението със
application.run(&args().collect::<Vec<_>>());
- (или може би със
application.run(&[]);
) - (Вместо
gtk::Window
, използвамеgtk::ApplicationWindow
) - За да приключим, някъде викаме на приложението
application.quit()
(или очакваме автоматично да приключи при затваряне на всички прозорци)
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:
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
- По-rusty, предвид че си е създадено за Rust, няма legacy design decisions както GTK.
EGUI
- По-rusty, предвид че си е създадено за Rust, няма legacy design decisions както GTK.
- Immediate-mode, а не "retained" -- няма widget-и, които си държат state, просто на всеки loop се рендерира всичко от нулата.
EGUI
- По-rusty, предвид че си е създадено за Rust, няма legacy design decisions както GTK.
- Immediate-mode, а не "retained" -- няма widget-и, които си държат state, просто на всеки loop се рендерира всичко от нулата.
- Има интересни features, може да се компилира за web, може да си persist-ва state-а на приложението
EGUI
- По-rusty, предвид че си е създадено за Rust, няма legacy design decisions както GTK.
- Immediate-mode, а не "retained" -- няма widget-и, които си държат state, просто на всеки loop се рендерира всичко от нулата.
- Има интересни features, може да се компилира за web, може да си persist-ва state-а на приложението
- НО: няма native look & feel, и си е work in progress. Някои неща стават доста тегаво, защото се мъчи да бъде гъвкаво и минималистично.
EGUI template -- хитри неща
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attributeT.e. ако
feature = "persistence"
, все едно има#[derive(serde::Deserialize, serde::Serialize)]
Опционални пакети:
1 2 3 4 5 6serde = { version = "1", features = ["derive"], optional = true } [features] default = [] # Enable if you want to persist app state on shutdown: persistence = ["eframe/persistence", "serde"]
Зареждане на картинки
Трябва ни egui_extras
пакета: https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html
Кое е по-харно?
Кое е по-харно?
GTK
- ✅ Тонове widget-и за каквото ви душа иска
- ✅
GTK_DEBUG=interactive
, има glade за интерфейс - ✅ Guide и обилна документация
Кое е по-харно?
GTK
- ✅ Тонове widget-и за каквото ви душа иска
- ✅
GTK_DEBUG=interactive
, има glade за интерфейс - ✅ Guide и обилна документация
- ❌ Особени абстракции като
*Ext
trait-овете - ❌ Голям surface area
- ❌ Вероятно малко досадно да се подкара под windows, macOS
Кое е по-харно?
GTK
- ✅ Тонове widget-и за каквото ви душа иска
- ✅
GTK_DEBUG=interactive
, има glade за интерфейс - ✅ Guide и обилна документация
- ❌ Особени абстракции като
*Ext
trait-овете - ❌ Голям surface area
- ❌ Вероятно малко досадно да се подкара под windows, macOS
Egui
- ✅ По-директно API, по-проста употреба
- ✅ Лесна компилация -- нулеви външни зависимости
Кое е по-харно?
GTK
- ✅ Тонове widget-и за каквото ви душа иска
- ✅
GTK_DEBUG=interactive
, има glade за интерфейс - ✅ Guide и обилна документация
- ❌ Особени абстракции като
*Ext
trait-овете - ❌ Голям surface area
- ❌ Вероятно малко досадно да се подкара под windows, macOS
Egui
- ✅ По-директно API, по-проста употреба
- ✅ Лесна компилация -- нулеви външни зависимости
- ❌ Някои неща все пак са досадни заради минимализъм и/или съвместимост между desktop/web
- ❌ Не толкова mature, примерно няма да намерите code editor widget, webview…
Други интересни ресурси
- Добра лекция, която прави един UI с GTK и също с терминален интерфейс: https://www.youtube.com/watch?v=dK9-oXptFcM
Други интересни ресурси
- Добра лекция, която прави един UI с GTK и също с терминален интерфейс: https://www.youtube.com/watch?v=dK9-oXptFcM
- fltk: Малко по-дървен интерфейс, но популярен за базови неща.
Други интересни ресурси
- Добра лекция, която прави един UI с GTK и също с терминален интерфейс: https://www.youtube.com/watch?v=dK9-oXptFcM
- fltk: Малко по-дървен интерфейс, но популярен за базови неща.
- relm: Библиотека, която седи отгоре на GTK и предоставя elm-подобен интерфейс.
- relm4: Същото като горното, ама различно? 😅
- vgtk: Също седи отгоре на GTK, прави интересни неща с макроси. По-скоро експериментално.
Други интересни ресурси
- Добра лекция, която прави един UI с GTK и също с терминален интерфейс: https://www.youtube.com/watch?v=dK9-oXptFcM
- fltk: Малко по-дървен интерфейс, но популярен за базови неща.
- relm: Библиотека, която седи отгоре на GTK и предоставя elm-подобен интерфейс.
- relm4: Същото като горното, ама различно? 😅
- vgtk: Също седи отгоре на GTK, прави интересни неща с макроси. По-скоро експериментално.
- tauri: Използва webview, като electron, но с доста по-малък overhead (твърдят).
Други интересни ресурси
- Добра лекция, която прави един UI с GTK и също с терминален интерфейс: https://www.youtube.com/watch?v=dK9-oXptFcM
- fltk: Малко по-дървен интерфейс, но популярен за базови неща.
- relm: Библиотека, която седи отгоре на GTK и предоставя elm-подобен интерфейс.
- relm4: Същото като горното, ама различно? 😅
- vgtk: Също седи отгоре на GTK, прави интересни неща с макроси. По-скоро експериментално.
- tauri: Използва webview, като electron, но с доста по-малък overhead (твърдят).
- slint: The new hotness, малко (твърде?) особено, цели да е cross-platform и cross-language даже.
Други интересни ресурси
- Добра лекция, която прави един UI с GTK и също с терминален интерфейс: https://www.youtube.com/watch?v=dK9-oXptFcM
- fltk: Малко по-дървен интерфейс, но популярен за базови неща.
- relm: Библиотека, която седи отгоре на GTK и предоставя elm-подобен интерфейс.
- relm4: Същото като горното, ама различно? 😅
- vgtk: Също седи отгоре на GTK, прави интересни неща с макроси. По-скоро експериментално.
- tauri: Използва webview, като electron, но с доста по-малък overhead (твърдят).
- slint: The new hotness, малко (твърде?) особено, цели да е cross-platform и cross-language даже.
- И всичко друго в секция "GUI" в "awesome-rust".
- Също, "Are we GUI Yet?"