Bindingi do GTK4 dla języka H# (H-Sharp). W 100% kod H# — ani
jednej linii C. Cała biblioteka to deklaracje extern static [c, "..."]
wskazujące bezpośrednio na prawdziwe, systemowe libgtk-4, libglib-2.0
i libgobject-2.0, plus wygodna warstwa wysokopoziomowa (struktury,
impl, obsługa zdarzeń) napisana czystym H# na wierzchu tych deklaracji.
use "bytes -> gtk" from "gtk"
fn main() is
gtk::init()
let win: gtk::Window = gtk::new_window("Witaj, H#!", 400, 200)
let btn: gtk::Button = gtk::new_button("Kliknij mnie")
win.set_child(btn.handle)
let app: gtk::App = gtk::new_app()
app.on_click(btn, || is write("Klik!") end)
win.present()
app.run()
end
Wymagane w systemie (dowolna dystrybucja Linuksa z GTK4):
# Debian/Ubuntu
sudo apt install libgtk-4-dev libglib2.0-dev pkg-config
# Fedora
sudo dnf install gtk4-devel glib2-devel pkgconf-pkg-config
# Arch
sudo pacman -S gtk4 glib2 pkgconfNastępnie w swoim projekcie H#:
bytes add gtklub ręcznie, dopisując do deps w swoim <projekt>.hk:
[deps]
-> gtk => bytes
git clone <adres-repo> gtk
cd gtk
h# check src/gtk.h# # weryfikacja składni/typów
h# compile examples/hello_window.h# --emit bin -o hello_window
./hello_windowKompilator H# (ffi_linker.rs) dla każdego bloku extern static [c, "<lib>"]
najpierw próbuje pkg-config --static --libs <lib>; jeśli dana biblioteka
nie ma statycznego wariantu w systemie (typowe dla GTK4 na większości
dystrybucji — pakiety -dev/-devel zwykle dają tylko .so), linker i tak
poprawnie dowiąże się dynamicznie do .so, bo pkg-config bez --static
i tak zwraca poprawne -lgtk-4 -lglib-2.0 -lgobject-2.0. Blok jest
zadeklarowany jako static, zgodnie z życzeniem — jeśli w Twoim systemie
są dostępne statyczne .a dla GTK4/GLib, bytes/h# użyje ich automatycznie.
gtk/
gtk.hk manifest pakietu (format bytes)
README.md ten plik
LICENSE MIT
src/
gtk_ffi.h# surowe `extern static [c, "..."]` do GTK4/GLib/GObject
gtk.h# warstwa wysokopoziomowa: struktury widgetów, `impl`, `App`
examples/
hello_window.h# minimalne okno + przycisk "Wyjdź"
counter_and_form.h# licznik kliknięć, checkbox, pole tekstowe
widgets_showcase.h# DropDown, SpinButton, ListBox, Stack, CSS, hover, AlertDialog
| Kategoria | Widgety |
|---|---|
| Okna/kontenery | Window, Box, Grid, Frame, ScrolledWindow, CenterBox, Overlay |
| Podstawowe | Button, Label, Entry, CheckButton, Image, Separator, TextView, ProgressBar |
| Wybór | DropDown, SpinButton, ListBox |
| Nawigacja | Stack + StackSwitcher, MenuButton + Popover |
| Dialogi | show_alert (GtkAlertDialog, fire-and-forget) |
Każdy widget to struktura H# z polem handle: int (surowy wskaźnik GTK —
tak samo jak malloc/free w rdzeniu H# reprezentują pamięć jako int)
oraz metodami impl wołającymi prawdziwe funkcje gtk_*/g_* z
gtk_ffi.h#. Wspólne dla każdego widgetu: gtk::show, gtk::hide,
gtk::set_visible, gtk::set_sensitive, gtk::set_size, gtk::set_margin,
gtk::set_align, gtk::set_expand (hexpand/vexpand), gtk::add_css_class /
gtk::remove_css_class, gtk::grab_focus.
gtk::load_css("
.naglowek { font-weight: bold; color: #3b82f6; }
")
gtk::add_css_class(label.handle, "naglowek")
albo z pliku: gtk::load_css_file("styl.css"). Obie funkcje owijają
prawdziwe GtkCssProvider + gtk_style_context_add_provider_for_display
z priorytetem GTK_STYLE_PROVIDER_PRIORITY_APPLICATION.
GTK normalnie łączy interakcje użytkownika (klik, zmianę checkboksa, ...)
przez sygnały GObject (g_signal_connect), które wymagają C-owego
wskaźnika funkcji jako callbacku. Dzisiejszy kompilator H# nie generuje
takiej trampoliny z domknięcia H# (ffi.rs::type_to_c mapuje typ funkcyjny
tylko na nieprzezroczysty void*, bez żadnego wywołania zwrotnego C→H#).
Ponieważ ta biblioteka ma być w 100% H#, App w gtk.h# zamiast
sygnałów odpytuje (polling) prawdziwy stan widgetu w swojej pętli głównej:
Metoda App |
Widget | Co odpytuje | Wiarygodność |
|---|---|---|---|
on_click |
Button |
flaga GTK_STATE_FLAG_ACTIVE (zbocze) |
przybliżona* |
on_toggle |
CheckButton |
gtk_check_button_get_active |
100% pewna |
on_change |
Entry |
gtk_editable_get_text |
100% pewna |
on_hover |
dowolny widget | flaga GTK_STATE_FLAG_PRELIGHT |
100% pewna |
on_submit |
Entry |
flaga GTK_STATE_FLAG_FOCUS_WITHIN (zbocze, utrata fokusu) |
przybliżona** |
on_selection_change |
ListBox |
gtk_list_box_get_selected_row + row_get_index |
100% pewna |
on_dropdown_change |
DropDown |
gtk_drop_down_get_selected |
100% pewna |
on_value_change |
SpinButton |
gtk_spin_button_get_value |
100% pewna |
on_view_change |
Stack |
gtk_stack_get_visible_child_name |
100% pewna |
on_window_close |
Window |
gtk_widget_get_mapped (po g_object_ref, zbocze) |
przybliżona*** |
* App::run() odpytuje w każdej iteracji prawdziwej pętli GLib
(g_main_context_iteration), więc w praktyce nie da się kliknąć szybciej
niż biblioteka to zauważy — ale formalnie jest to detekcja zbocza stanu, a
nie natywny sygnał clicked.
** To NIE jest sygnał activate (Enter) — ten wymaga
GtkEventControllerKey + sygnału, czyli C-owego callbacku. on_submit
odpala się przy utracie fokusu (użytkownik kliknął gdzie indziej /
wcisnął Tab), co w praktyce często pokrywa się z "skończyłem wpisywać",
ale nie jest identyczne z wciśnięciem Enter.
*** Realny sygnał close-request wymaga C-owego callbacku. Zamiast
tego trzymamy dodatkową referencję (g_object_ref) na oknie, żeby było
bezpiecznie odpytywać jego stan mapped nawet po tym, jak domyślny
handler GTK zniszczy widget po kliknięciu w „X”. To działa poprawnie w
typowym przypadku (okno bez własnego close-request), ale jeśli chcesz
100% gwarancji, najprościej dodać własny przycisk "Zamknij"/"Wyjdź"
wołający app.quit() (patrz examples/hello_window.h#).
- Brak natywnych sygnałów GTK. Wszystko powyżej to polling, nie
g_signal_connect. Dla zdecydowanej większości aplikacji desktopowych różnicy praktycznie nie widać, ale formalnie nie jest to 1:1 z tym, jak zachowuje się "prawdziwa" aplikacja GTK. GtkFileDialog/GtkFileChoosernie są obsługiwane. Współczesne GTK4 usunęłogtk_dialog_run()(synchroniczne okna dialogowe) — jedyny sposób odebrania wyboru pliku to asynchronicznyGAsyncReadyCallback, czyli znów C-owy wskaźnik funkcji. To ta sama bariera co przy sygnałach, więc świadomie zostawiamy to poza zakresem biblioteki zamiast dawać bindingi, które i tak nie zwróciłyby wyniku.gtk_drop_down_new_from_strings(wariant przyjmujący gotową tablicęchar**) nie jest używany — zamiast tego budujemyGtkStringListprzez pojedyncze wywołaniagtk_string_list_append, żeby nie polegać na marshalingu tablic NULL-terminated z H# do C.
MIT — patrz LICENSE.