LINUX.ORG.RU

Нарисовать закругленные углы в окне с использованием GTK-3

 , , , ,


0

1

Здравствуйте, форумчане!

Столкнулся со следующей проблемой..
есть код на С++ :

int main (int argc, char *argv[])
{
  auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");

  Gtk::Window window;
  window.set_default_size(200, 200);

  Glib::RefPtr<Gtk::CssProvider> css_provider = Gtk::CssProvider::create();
  Glib::RefPtr<Gtk::StyleContext> styleContext = Gtk::StyleContext::create();

  try
  {
      css_provider->load_from_path("styles.css");
  }
  catch (/*Exeption &e*/...)
  {
      std::cout << "exeption " << std::endl;
  }

  styleContext->add_provider_for_screen(Gdk::Screen::get_default(), css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER);

  return app->run(window);
}

и СSS код (styles.css файл)

GtkWindow
{
  border: 1px solid black;
  background: none;
  background-color : red;
  border-radius: 20px;
}

сборка:

g++ `pkg-config --cflags gtkmm-3.0` -o exampl main.cc `pkg-config --libs gtkmm-3.0`

в результате на выходе получаем окно с закругленными углами, но вместо прозрачности у нас черные углы
https://i.stack.imgur.com/5OvV1.png

После долгих поисков, оказалось, что надо включить фичу, которая называется Client Side Decoration

export GTK_CSD=1

сразу после этого окно запустилось как и должно быть: https://imgur.com/hJxJels

Но! на всех других машинах с убунту это решение не работает! там все еще черные углы! а еще почему-то вместо «GtkWindow» в СSS файле надо писать просто «window»

наверное у меня есть еще какой-то дополнительный пакет, который позволяет CSD работать хорошо. Кто с чем похожим сталкивался? В чем проблема? Как сделать так, чтобы работало для любого дистрибутива линукс? Я новичок в этой теме, поэтому не удивлюсь, если есть другой способ добиться закругленных углов

Перемещено Pinkbyte из general


Ответ на: комментарий от eternal_sorrow

это тот редкий случай, когда надо сделать именно так. можно пожалуйста более подробно о темах и углах? это - маленькая часть продукта, который будет недоступен для редактирования пользователем. все что мне нужно - круглые углы. если знаете, как их сделать без дичи - подскажите :)

yukik ()
Ответ на: комментарий от yukik

Да уже давно такое не приветсвуется нигде. Тема с фигурными окнами так и померла в 90-е во времена всяких кейгенов под Венду с музыкой. Вообще, объясни своему боссу, что это дерьмо и не задрачивай поцанов. И ещё. Это зависит от композитора (Моцарта, например). Т.к. GTK может о себе думать, что хочет, а композитор может думать иначе. Угадай, чьё слово весомее.

kostyarin_ ★★ ()

Но! на всех других машинах с убунту это решение не работает! там все еще черные углы! а еще почему-то вместо «GtkWindow» в СSS файле надо писать просто «window»

Ну так на всех других машинах тоже нужно делать

export GTK_CSD=1

Что одновременно и является решением и не является. Квантовая фигня короче.

kostyarin_ ★★ ()
Ответ на: комментарий от kostyarin_

Это зависит от композитора (Моцарта, например). Т.к. GTK может о себе думать, что хочет, а композитор может думать иначе. Угадай, чьё слово весомее.

Моцарт - великий композитор! (с) Сибирский Цирюльник

bvn13 ★★★★★ ()
Ответ на: комментарий от yukik

вывел стоит в 1 :( еще идеи?

Тебе нужно что-то типа такого: https://stackoverflow.com/a/3909283/1816872

Смотри там ниже для GTK-3. На Си.

Т.е. там чувак делает окно прозрачным. А рисует уже на нём со скруглениями. Типа того. У меня работает. И GTK_CSD-костыли не нужны.

Однако, клик по окну, даже по прозрачной его части, будет кликом по окну. Может как-то можно пробросить событие на подлежащее окно. Не заню.

kostyarin_ ★★ ()

Разница с CSS связана с изменениями в GTK 3.20.
Client-side decoration можно реализовать самостоятельно.

Вот как я бы сделал:

#include <gtk/gtk.h>
#include <gtkmm.h>

static void
on_composited_changed(Gtk::Widget &widget)
{
    auto set_visual = [](const auto &widget, const auto &visual) {
        gtk_widget_set_visual(GTK_WIDGET(widget.gobj()), visual->gobj());
    };

    const auto screen = widget.get_screen();
    if (screen->is_composited()) {
        set_visual(widget, screen->get_rgba_visual());
    } else {
        set_visual(widget, screen->get_system_visual());
    }
}

int
main(int   argc,
     char *argv[])
{
    const auto app = Gtk::Application::create("", Gio::APPLICATION_NON_UNIQUE);

    Gtk::Window window;
    window.set_default_size(200, 200);

    Gtk::HeaderBar titlebar;
    titlebar.set_has_subtitle(false);
    titlebar.set_show_close_button(true);
    window.set_titlebar(titlebar);
    titlebar.show();

    window.get_screen()->signal_composited_changed().connect([&window] {
        on_composited_changed(window);
    });
    on_composited_changed(window);

    const auto style_provider = Gtk::CssProvider::create();
    style_provider->load_from_data("* { "
                                   "  border: 1px solid black;"
                                   "  border-radius: 15pt;"
                                   "  background-color: red;"
                                   "}");
    auto style = window.get_style_context();
    style->add_provider(style_provider,
                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

    app->signal_activate().connect([&app, &window] {
        app->add_window(window);
        window.present();
    });
    return app->run(argc, argv);
}

При использовании GTK CSD GdkVisual для альфа-канала выставлять самостоятельно не нужно, потому в коде выше можно убрать весь код, связанный с on_composited_changed().

Но, если вы предпочли бы вообще не иметь декораций окна, тогда нужно, наоборот, убрать всё связанное с titlebar и добавить window.set_decorated(false).

Darth_Revan ★★★★★ ()
Ответ на: комментарий от ananas
#include <gtkmm.h>

static bool
on_draw(const Gtk::Window                   &window,
        const Cairo::RefPtr<Cairo::Context> &cr)
{
    int w, h;
    window.get_size(w, h);

    const double radius = 30;
    cr->rectangle(0, 0, w, h - radius);
    cr->set_source_rgb(1.0, 0.0, 0.0);
    cr->fill();

    cr->arc(w - radius, h - radius, radius, 0.0,        M_PI * 0.5);
    cr->arc(radius,     h - radius, radius, M_PI * 0.5, M_PI);
    cr->set_source_rgb(1.0, 0.0, 0.0);
    cr->fill();

    cr->set_source_rgb(0.0, 0.0, 0.0);
    cr->set_line_width(1.0);
    cr->stroke();
    return false;
}

int
main(int   argc,
     char *argv[])
{
    auto app = Gtk::Application::create("", Gio::APPLICATION_NON_UNIQUE);

    Gtk::Window window;
    window.set_default_size(200, 200);
    window.set_app_paintable(true);

    window.signal_draw().connect(
        [&window](const Cairo::RefPtr<Cairo::Context> &cr) {
            return on_draw(window, cr);
        }
    );

    app->signal_activate().connect([&app, &window] {
        app->add_window(window);
        window.present();
    });
    return app->run(argc, argv);
}

Хорошо, вот адаптировал код, что давал выше, под cairomm. И убрал всё, что может помочь с альфа-каналом.
Ожидаемо, чуда не случилось.

Darth_Revan ★★★★★ ()
Ответ на: комментарий от kostyarin_

Однако, клик по окну, даже по прозрачной его части, будет кликом по окну. Может как-то можно пробросить событие на подлежащее окно. Не заню.

для этого надо обрабатывать не clicked, а button-press-event, в котором оценивать координаты указателя, и возвращать FALSE в случае, если указатель на прозрачной части

ananas ★★★★★ ()
Ответ на: комментарий от ananas

Я совершенно забыл, что GdkWindow не обязан быть квадратным.
Реализовал – может, кому-нибудь пригодится:

#include <gtkmm.h>

static bool
on_widget_draw(Gtk::Widget                  &widget,
               Cairo::RefPtr<Cairo::Context> cr)
{
    const int w = widget.get_allocated_width();
    const int h = widget.get_allocated_height();

    auto surface = widget.get_window()->create_similar_surface(
        Cairo::CONTENT_COLOR_ALPHA,
        w,
        h
    );

    auto cr2 = Cairo::Context::create(surface);
    cr2->set_operator(Cairo::OPERATOR_SOURCE);

    cr2->set_source_rgba(0.0, 0.0, 0.0, 0.0);
    cr2->paint();

    const double radius = 30;
    cr2->rectangle(0, 0, w, h - radius);
    cr2->set_source_rgb(1.0, 0.0, 0.0);
    cr2->fill();

    cr2->arc(w - radius, h - radius, radius, 0.0,        M_PI * 0.5);
    cr2->arc(radius,     h - radius, radius, M_PI * 0.5, M_PI);
    cr2->set_source_rgb(1.0, 0.0, 0.0);
    cr2->fill();

    cr2->set_source_rgb(0.0, 0.0, 0.0);
    cr2->set_line_width(1.0);
    cr2->stroke();

    if (cr) {
        cr->set_operator(Cairo::OPERATOR_SOURCE);
        cr->set_source(surface, 0.0, 0.0);
        cr->paint();
    }

    const auto region = Gdk::Cairo::create_region_from_surface(surface);
    widget.shape_combine_region(region);
    widget.input_shape_combine_region(region);

    return false;
}

static void
on_widget_realize(Gtk::Widget &widget)
{
    on_widget_draw(widget, Cairo::RefPtr<Cairo::Context>());
}

int
main(int   argc,
     char *argv[])
{
    auto app = Gtk::Application::create("", Gio::APPLICATION_NON_UNIQUE);

    Gtk::Window window;
    window.set_default_size(200, 200);

    window.signal_draw().connect(
        [&](const Cairo::RefPtr<Cairo::Context> &cr) {
            return on_widget_draw(window, cr);
        }
    );
    window.signal_realize().connect([&] { on_widget_realize(window); });

    app->signal_activate().connect([&] {
        app->add_window(window);
        window.present();
    });
    return app->run(argc, argv);
}
Darth_Revan ★★★★★ ()
Последнее исправление: Darth_Revan (всего исправлений: 1)
Ответ на: комментарий от ananas

убери обработчик realize, и добавь window.show_all() перед app->run

Теперь оно не работает, ага.

и из draw возвращай true, чтобы сигнал дальше не гулял

Да, стоило так сделать. Но не важно.

Darth_Revan ★★★★★ ()
Последнее исправление: Darth_Revan (всего исправлений: 1)
Ответ на: комментарий от ananas

Если напрягает gtkmm, то вот на C:

#include <gtk/gtk.h>

static gboolean
on_widget_draw (GtkWidget *widget,
                cairo_t   *cr)
{
    GdkWindow       *gdk_surface;
    cairo_t         *cr2;
    cairo_surface_t *surface;
    cairo_region_t  *region;

    const int w = gtk_widget_get_allocated_width (widget);
    const int h = gtk_widget_get_allocated_height (widget);
    const double radius = 30;

    gdk_surface  = gtk_widget_get_window (widget);
    surface = gdk_window_create_similar_surface (gdk_surface,
                                                 CAIRO_CONTENT_COLOR_ALPHA,
                                                 w,
                                                 h);

    cr2 = cairo_create (surface);
    cairo_set_operator (cr2, CAIRO_OPERATOR_SOURCE);

    cairo_set_source_rgba (cr2, 0.0, 0.0, 0.0, 0.0);
    cairo_paint (cr2);

    cairo_rectangle (cr2, 0, 0, w, h - radius);
    cairo_set_source_rgb (cr2, 1.0, 0.0, 0.0);
    cairo_fill (cr2);

    cairo_arc (cr2, w - radius, h - radius, radius, 0.0,        G_PI * 0.5);
    cairo_arc (cr2, radius,     h - radius, radius, G_PI * 0.5, G_PI);
    cairo_set_source_rgb (cr2, 1.0, 0.0, 0.0);
    cairo_fill (cr2);

    cairo_set_source_rgb (cr2, 0.0, 0.0, 0.0);
    cairo_set_line_width (cr2, 1.0);
    cairo_stroke (cr2);

    if (cr != NULL) {
        cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
        cairo_set_source_surface (cr, surface, 0.0, 0.0);
        cairo_paint (cr);
    }

    region = gdk_cairo_region_create_from_surface (surface);
    gtk_widget_shape_combine_region (widget, region);
    gtk_widget_input_shape_combine_region (widget, region);

    cairo_region_destroy (region);
    cairo_destroy (cr2);
    cairo_surface_destroy (surface);

    return FALSE;
}

static void
on_widget_realize (GtkWidget *widget)
{
    on_widget_draw (widget, NULL);
}

static void
on_application_activate (GtkApplication *application)
{
    GtkWidget *window = gtk_application_window_new (application);

    g_signal_connect (window, "draw",    G_CALLBACK (on_widget_draw),    NULL);
    g_signal_connect (window, "realize", G_CALLBACK (on_widget_realize), NULL);

    gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
    gtk_widget_set_app_paintable (window, TRUE);
    gtk_window_present (GTK_WINDOW (window));
}

int
main (int   argc,
      char *argv[])
{
    GtkApplication *app = gtk_application_new (NULL, G_APPLICATION_NON_UNIQUE);

    g_signal_connect (app,
                      "activate",
                      G_CALLBACK (on_application_activate),
                      NULL);

    return g_application_run (G_APPLICATION (app), argc, argv);
}
Darth_Revan ★★★★★ ()
Последнее исправление: Darth_Revan (всего исправлений: 1)
Ответ на: комментарий от ananas

У меня оно тогда применяется не сразу.
И мне всё равно не нравится, что идёт вызов draw из draw.
В какой-то из недавних версий GTK, как мне помнится, добавляли хитрую логику для разрешения подобных циклов, но это всё равно такое себе.
К тому же, @yukik вообще с чем-то древним собрался запускать, потому, чисто гипотетически, эта рекурсия может таки проявиться.

Darth_Revan ★★★★★ ()
Ответ на: комментарий от Darth_Revan

в принципе, если так смущает, можно и из realize ее дергать.

а задержки в применении - это могут быть финты двойной буферизации. можешь попытаться ее отключить и посмотреть, как оно будет

ananas ★★★★★ ()
Последнее исправление: ananas (всего исправлений: 1)