GDK - kreslení do okna s využitím knihovny Cairo
9.10.2020
V úvodním článku o programování v GDK jsme si vytvořili základní aplikaci s jedním oknem a ukázali jak zachytit požadavek na překreslení okno. Nyní si již do okna (do jeho klientské oblasti) již něco konkrétního vykreslíme. Využijeme interakce GDK s grafickou knihovnou Cairo.
Jako ukázku si vykreslíme úhlopříčky spojující rohy okna, dále text s diakritikou v utf8 a aktuální datum a čas. To vše s ukázkou nastavení barvy pozadí, čar a textu a také volby fontu. Nejprve celý kód obslužné funkce on_expose volané při zachycení události GDK_EXPOSE, jak jsem si připravili v úvodním článku.
void on_expose(GdkEventExpose* ev)
{
if (ev->region == NULL)
return;
GdkDrawingContext* dc = gdk_window_begin_draw_frame(
ev->window, ev->region);
cairo_t* cr = gdk_drawing_context_get_cairo_context(dc);
cairo_set_source_rgba(cr, 1.0, 1.0, 0.8, 1.0);
cairo_paint(cr);
cairo_set_source_rgba(cr, 0.5, 0.0, 0.0, 1.0);
cairo_set_line_width(cr, 5.0);
cairo_move_to(cr, 0, 0);
cairo_line_to(cr, ev->area.width, ev->area.height);
cairo_move_to(cr, ev->area.width, 0);
cairo_line_to(cr, 0, ev->area.height);
cairo_stroke(cr);
cairo_set_source_rgba(cr, 0.0, 0.0, 0.8, 1.0);
cairo_select_font_face(cr, "Sans",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 18);
cairo_move_to(cr, 50, 50);
cairo_show_text(cr, "Nějaký český text v utf8 ...");
time_t timenow;
time(&timenow);
static char buff[64];
strftime(buff, sizeof(buff), "%d.%m.%Y %H:%M:%S",
localtime((const time_t*)&timenow));
cairo_move_to(cr, 50, ev->area.height - 40);
cairo_show_text(cr, buff);
gdk_window_end_draw_frame(ev->window, dc);
}
Klíčem k zahájení kreslení je funkce gdk_drawing_context_get_cairo_context, která nám vrátí pointr na tzv. cairo context, na který se následně budeme odkazovat při volání různých "kreslících" funkcí. Nastavení barvu (pozadí, čar i textu) provedeme funkcí cairo_set_source_rgba, jejíž parametry následujícími po cairo kontextu jsou RGBA složky barvy vyjádřené typem double, kde 1.0 je maximální hodnota. Význam dalších uvedených funkcí a jejích parametrů je jistě již každému zřejmý z jejich názvů.
Vykreslení máme hotové, ale pokud chceme mít obsah okna stále aktuální jak při změně jeho rozměrů, tak z hlediska aktuálnosti časového údaje, musíme dodělat pár drobností.
Pokud jde o výpis aktuálního času, tedy aby program fungoval jako digitální hodiny i při nečinnosti uživatele, spustíme si timer s intervalem 1 sekundy a na něj programově vyvoláme překreslení okna, čímž se zobrazený čas aktualizuje. K tomu vyžijeme timer z knihovny GLib, který vytvoříme před vstupem do smyčky zpráv:
guint timer_clock; // globální proměnná mimo funkci main timer_clock = g_timeout_add(1000, on_timer, NULL);
V nastevené obslužné funkci pak vyvoláme překreslení okna pomocí funkce gdk_window_invalidate_rect následovně
gboolean on_timer(gpointer data)
{
gdk_window_invalidate_rect(window, NULL, TRUE);
return G_SOURCE_CONTINUE;
}
Při změně velikosti okna uživatelem dostaneme v hlavní smyčce událost GDK_CONFIGURE, při jejím zachycení opět zavoláme výše uvedenou funkci gdk_window_invalidate_rect.
Jako pro tentokrát poslední drobné vylepšení si ještě implementujeme možnost zavření okna klávesou Escape. K tomu je potřeba zachytit událost GDK_KEY_PRESS jak vidíme ve výpisu obslužné funkce smyčky zpráv rozšířené o výše zmíněnou funkčnost:
void on_gdk_event(GdkEvent* event, gpointer data)
{
if (event->type == GDK_EXPOSE)
{
on_expose((GdkEventExpose*)event);
}
else if (event->type == GDK_CONFIGURE)
{
gdk_window_invalidate_rect(window, NULL, TRUE);
}
else if (event->type == GDK_KEY_PRESS)
{
if (((GdkEventKey*)event)->keyval == GDK_KEY_Escape)
on_end_app();
}
else if (event->type == GDK_DELETE)
{
on_end_app();
}
}
Vzhledem k tomu že je slušné po sobě uklízet, po zavření zastavíme a zrušíme vytvořený timer funkcí g_source_remove:
void on_end_app()
{
g_source_remove(timer_clock);
gdk_window_destroy(window);
g_main_loop_quit(main_loop);
}
Na závěr opět celý výpis programu, který lze sestavit překladačem GCC následujícím příkazem (pro ladicí sestavení):
gcc gdk-okno.c -Wall -g `pkg-config --cflags --libs gdk-3.0` -ogdk-okno
#include <gdk/gdk.h>
GMainLoop* main_loop;
GdkWindow* window;
guint timer_clock;
void on_end_app()
{
g_source_remove(timer_clock);
gdk_window_destroy(window);
g_main_loop_quit(main_loop);
}
void on_expose(GdkEventExpose* ev)
{
if (ev->region == NULL)
return;
GdkDrawingContext* dc = gdk_window_begin_draw_frame(
ev->window, ev->region);
cairo_t* cr = gdk_drawing_context_get_cairo_context(dc);
cairo_set_source_rgba(cr, 1.0, 1.0, 0.8, 1.0);
cairo_paint(cr);
cairo_set_source_rgba(cr, 0.5, 0.0, 0.0, 1.0);
cairo_set_line_width(cr, 5.0);
cairo_move_to(cr, 0, 0);
cairo_line_to(cr, ev->area.width, ev->area.height);
cairo_move_to(cr, ev->area.width, 0);
cairo_line_to(cr, 0, ev->area.height);
cairo_stroke(cr);
cairo_set_source_rgba(cr, 0.0, 0.0, 0.8, 1.0);
cairo_select_font_face(cr, "Sans",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 18);
cairo_move_to(cr, 50, 50);
cairo_show_text(cr, "Nějaký český text v utf8 ...");
time_t timenow;
time(&timenow);
static char buff[64];
strftime(buff, sizeof(buff), "%d.%m.%Y %H:%M:%S",
localtime((const time_t*)&timenow));
cairo_move_to(cr, 50, ev->area.height - 40);
cairo_show_text(cr, buff);
gdk_window_end_draw_frame(ev->window, dc);
}
void on_gdk_event(GdkEvent* event, gpointer data)
{
if (event->type == GDK_EXPOSE)
{
on_expose((GdkEventExpose*)event);
}
else if (event->type == GDK_CONFIGURE)
{
gdk_window_invalidate_rect(window, NULL, TRUE);
}
else if (event->type == GDK_KEY_PRESS)
{
if (((GdkEventKey*)event)->keyval == GDK_KEY_Escape)
on_end_app();
}
else if (event->type == GDK_DELETE)
{
on_end_app();
}
}
gboolean on_timer(gpointer data)
{
gdk_window_invalidate_rect(window, NULL, TRUE);
return G_SOURCE_CONTINUE;
}
int main(int argc, char** argv)
{
GdkWindowAttr attributes;
gint attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_TITLE | GDK_WA_WMCLASS;
if (!gdk_init_check(&argc, &argv))
return EXIT_FAILURE;
memset(&attributes, 0, sizeof(attributes));
attributes.window_type = GDK_WINDOW_TOPLEVEL;
attributes.x = 100;
attributes.y = 50;
attributes.event_mask = GDK_ALL_EVENTS_MASK;
attributes.width = 440;
attributes.height = 480;
attributes.title = "GDK - Cairo";
attributes.wclass = GDK_INPUT_OUTPUT;
gdk_event_handler_set(on_gdk_event, NULL, NULL);
window = gdk_window_new(NULL, &attributes, attr_mask);
gdk_window_show(window);
timer_clock = g_timeout_add(1000, on_timer, NULL);
main_loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(main_loop);
g_main_loop_unref(main_loop);
return EXIT_SUCCESS;
}