Start · 10 minut
Szybki start
Od pustego projektu do okna, które reaguje na kliknięcie i rysuje prawdziwy widget. Wszystko w jednym pliku i bez kompozytora Selene — wystarczy dowolny Wayland.
Co będzie potrzebne
| Składnik | Wymaganie | Po co |
|---|---|---|
| System | Linux | Wayland, evdev, wl_shm. |
| Rust | 1.80+ (stable) | Toolchain z rustup wystarczy. |
| Kompozytor | Wayland | Selene, GNOME, KDE, Sway — każdy z xdg-shell. |
| Czcionki | Adwaita Sans / DejaVu | Silnik tekstu szuka fontu w systemie. |
Nie potrzebujesz GTK, Qt, GObject ani pkg-config. libLuna linkuje się jak zwykła biblioteka Rusta.
Dodaj zależność
W typowej aplikacji wystarczy fasada libluna — ona re-eksportuje całą resztę. W repozytorium monorepo użyj ścieżki:
[dependencies]
libluna = { path = "../libluna/crates/libluna" }
# albo z repozytorium:
# libluna = { git = "https://github.com/mijsys/libLuna.git" }
# docelowo, z rejestru:
# libluna = "0.0.4"
W kodzie wystarczy jedno use:
use libluna::prelude::*;
Scaffolder
Najszybciej utworzysz projekt gotowym szablonem:
./libluna/tools/libluna-new.sh ~/projekty/moja-apka
cd ~/projekty/moja-apka
cargo run
Pierwsze okno
Poniższy program otwiera okno 800×600, ustawia język z systemu, rysuje tło i napis. To cały kontrakt: platforma woła domknięcie przy każdej klatce, a Ty wypełniasz scenę.
use libluna::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
set_locale(Locale::from_env());
let config = AppConfig::new("Moja apka")
.initial_size(800, 600)
.app_id("eu.luna.MojaApka");
run(config, |frame: &mut Frame| {
let theme = frame.theme;
let (w, h) = (frame.size.width, frame.size.height);
frame.scene.fill_rect(
Rect::from_xywh(0.0, 0.0, w, h),
theme.colors.window_bg,
);
frame.scene.text(
TextRun::new(Point::new(24.0, 64.0), "Hello, libLuna", 24.0)
.with_weight(700)
.with_color(theme.colors.text),
);
})
}
frame.scene jest czyszczona przed każdą klatką. Aplikacja rysuje całą klatkę od zera — nie trzeba wołać clear(), ale nie można liczyć na ślady z poprzedniej klatki.
Uruchom i obejrzyj
# Twoja aplikacja
cargo run
# Przykład z repozytorium libLuny
cargo run --manifest-path libluna/Cargo.toml -p libluna --example minimal
# Galeria widgetów bez otwierania okna (PNG)
cargo run --manifest-path apps/luna-demo/Cargo.toml --bin gallery
# Zrzut dowolnego ekranu demo
cargo run --manifest-path apps/luna-demo/Cargo.toml --bin render -- docs/images/out.png
Jeśli okno się nie pojawia, sprawdź WAYLAND_DISPLAY i to, czy sesja naprawdę działa pod Waylandem. libLuna nie ma backendu X11 — XWayland obsługuje tylko okna innych aplikacji w Selene.
Co dostaje aplikacja: Frame
Frame to cały kontekst jednej klatki. Najważniejsze pola:
| Pole | Typ | Znaczenie |
|---|---|---|
scene | &mut Scene | Graf sceny do wypełnienia; piksele logiczne. |
theme | Theme | Rozwiązany motyw: paleta, spacing, radius, typografia, metryki. |
text | &TextEngine | Pomiar i rasteryzacja tekstu (systemowy lub mono). |
size | Size | Bieżący rozmiar powierzchni w pikselach logicznych. |
scale | f64 | Skala fizyczna/logiczna (np. 2.0 na HiDPI). |
events | &[Event] | Zdarzenia od poprzedniej klatki. |
dt | f64 | Czas od poprzedniej klatki w sekundach. |
Najczęstsze metody
| Metoda | Do czego |
|---|---|
request_frame() | Poproś o kolejną klatkę, dopóki trwa animacja. |
request_frame_after(delay) | Zaplanuj klatkę np. dla zegara lub timeoutu. |
measure_text(text, size, weight) | Zmierz napis bez rysowania. |
set_title(...), close() | Tytuł i zamknięcie okna xdg-toplevel. |
begin_move(), begin_resize(edges) | Przeciąganie i zmiana rozmiaru z własnego CSD. |
copy_to_clipboard(...), paste_from_clipboard() | Schowek systemowy; wklejenie wraca jako Event::Paste. |
set_preference(...), set_accent(...) | Zmiana motywu i akcentu, zapisywana w konfiguracji. |
waker() | Bezpieczne wybudzenie pętli z innego wątku. |
Pierwsze zdarzenie
Zdarzenia są w frame.events. Ten przykład przełącza kolor po kliknięciu lewym przyciskiem:
use libluna::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = AppConfig::new("Klik").initial_size(640, 400);
let mut active = false;
run(config, move |frame: &mut Frame| {
for event in frame.events {
if let Event::PointerButton(pe) = event {
if pe.button == PointerButton::Left
&& pe.state == ButtonState::Pressed
{
active = !active;
}
}
}
let theme = frame.theme;
let (w, h) = (frame.size.width, frame.size.height);
let color = if active { theme.colors.accent } else { theme.colors.card_bg };
frame.scene.fill_rect(Rect::from_xywh(0.0, 0.0, w, h), theme.colors.window_bg);
frame.scene.fill_rounded_rect(
Rect::from_xywh(24.0, 24.0, w - 48.0, h - 48.0),
theme.radius.large,
color,
);
})
}
Rodzaje zdarzeń
| Wariant | Dane |
|---|---|
PointerButton | Pozycja, przycisk, stan, modyfikatory. |
PointerMotion | Pozycja i modyfikatory (dla hoveru). |
PointerLeave | Kursor opuścił powierzchnię. |
Scroll | dx/dy w krokach. |
Key | Keysym, keycode, tekst, stan, modyfikatory, repeat. |
Text, Paste | Złożony tekst oraz wklejenie ze schowka. |
Window | Resize, ScaleChanged, Focused, CloseRequested, Redraw. |
Rysowanie sceny
Scene to lista poleceń, nie stos stanu. Każda metoda zwraca &mut Self, więc można je łączyć.
| Metoda | Efekt |
|---|---|
fill_rect(rect, color) | Wypełniony prostokąt. |
fill_rounded_rect(rect, radius, color) | Prostokąt z zaokrągleniami. |
stroke_rounded_rect(...) | Obramowanie zaokrąglonego prostokąta. |
stroke_line(from, to, width, color) | Odcinek. |
fill_circle(center, radius, color) | Wypełniony okrąg. |
stroke_circle(...), stroke_polyline(...), fill_polygon(...) | Obrysy i wielokąty. |
text(TextRun) | Tekst z linią bazową origin.y. |
image(rect, data) | Obraz RGBA w prostokącie. |
push_clip(rect) / pop_clip() | Ograniczenie rysowania (także zaokrąglone). |
frame.scene.text(
TextRun::new(Point::new(24.0, 48.0), "Tytuł", theme.typography.title.size)
.with_weight(theme.typography.title.weight)
.with_color(theme.colors.text),
);
Pamiętaj: origin.y to linia bazowa pierwszego wiersza, nie górna krawędź tekstu.
Pierwszy widget
Widgety są immediate: trzymają stan w zwykłej strukturze, a Ty wołasz paint i click_at. Nie ma drzewa obiektów ani ręcznego refcountingu.
use libluna::prelude::*;
use libluna::widgets::{Button, ButtonVariant, PaintCtx};
fn draw_ui(scene: &mut Scene, theme: &Theme, text: &TextEngine) {
let rect = Rect::from_xywh(24.0, 24.0, 120.0, 36.0);
let mut button = Button::new("Zapisz")
.variant(ButtonVariant::Suggested);
let mut ctx = PaintCtx::new(scene, theme, text);
button.paint(&mut ctx, rect);
}
fn handle(event: &Event) -> bool {
let rect = Rect::from_xywh(24.0, 24.0, 120.0, 36.0);
let button = Button::new("Zapisz");
match event {
Event::PointerButton(pe) => button.click_at(rect, pe.position),
_ => false,
}
}
W praktyce trzymasz widget w stanie aplikacji (żeby pamiętał hover/pressed i wartość), a nie tworzysz go od nowa przy każdym kliknięciu. Pełne przykłady: Biblioteka widgetów.
Układ bez ręcznego liczenia
Nad widgetami jest retencyjny layout: budujesz drzewo Node, a silnik mierzy je i przydziela prostokąty liściom.
use libluna::widgets::layout::{Layout, LayoutCtx, Node};
let tree = Node::padding(
Insets::splat(16.0),
Node::vbox(vec![
Node::label("Ustawienia", theme.typography.heading),
Node::spacer(),
Node::hbox(vec![
Node::fixed(120.0, 34.0),
Node::spacer(),
Node::fixed(80.0, 34.0),
])
.gap(8.0),
])
.gap(10.0),
);
let layout_ctx = LayoutCtx { theme: &theme, text: frame.text };
let rects = Layout::new(tree).fit(
Rect::from_xywh(0.0, 0.0, frame.size.width, frame.size.height),
&layout_ctx,
);
rects zawiera prostokąty liści w kolejności DFS — mapujesz je na swoje widgety. Szczegóły: API → layout.
Tłumaczenia od pierwszego dnia
Kluczem jest angielski napis. Brak tłumaczenia degraduje się do angielskiego, a każdy język to osobny plik.
use libluna::t;
use libluna::prelude::*;
set_locale(Locale::from_env());
let title = tr("Settings");
let hello = tr_args("Hello, {name}", &[("name", "Ania")]);
let shortcut = t!("Settings");
| Plik | Zawartość |
|---|---|
i18n/pl.lang | __name__ = Polski oraz Settings = Ustawienia. |
~/.config/luna/i18n/*.lang | Dodatkowe języki ładowane przy starcie, nadpisują wbudowane. |
Wbudowane są pl, en i de. Więcej: Koncepcje → i18n.
Co dalej
Zrozum model
Klatka, scena, immediate widgety, layout i zdarzenia — bez zgadywania.
Koncepcje →Wybierz widget
77 komponentów z podglądami i kodem. Zobacz, co już masz.
Biblioteka →Dopasuj wygląd
Tokeny, akcenty i metryki — bez przepisywania komponentów.
Motywy →Sprawdź API
Pełny, przeszukiwalny indeks typów, funkcji i metod.
API →