You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在Gtk-3.0中使用Cairo时,仅在非透明区域触发窗口拖拽

解决Gtk3.0中仅在非透明区域触发窗口拖拽的问题

嘿,这个需求我之前做自定义异形窗口的时候刚好折腾过,给你两个靠谱的思路,比直接抓取屏幕pixmap要准确高效得多:

方法一:提前维护透明区域的Alpha缓冲区(推荐静态场景)

如果你的窗口绘制内容是固定不变的(比如启动后就不再修改),那自己记录每个像素的透明状态是最优解:

  • 初始化一个和窗口尺寸一致的uint8_t数组(比如叫alpha_buffer),每个元素对应一个像素的Alpha值(0=完全透明,255=完全不透明);
  • 每次用Cairo绘制窗口内容时,同步更新这个缓冲区:比如画了一个不透明矩形,就把矩形区域内的alpha_buffer元素设为255;画半透明圆形,就设为对应的半透明值(比如127);
  • 当鼠标按下事件触发时,拿到鼠标在窗口内的坐标(x,y),直接查询alpha_buffer[y * width + x]的值,只要大于你设定的阈值(比如>0表示非完全透明),就调用gtk_window_begin_move_drag

这个方法的优势是性能拉满,不需要每次鼠标事件都去操作Cairo Surface,适合固定布局的窗口。

方法二:从窗口的Cairo Surface读取Alpha值(通用动态场景)

如果窗口内容是动态变化的(比如实时绘制动画),维护缓冲区太麻烦,那可以直接从窗口的绘制Surface里读取对应像素的Alpha值——不用抓屏幕pixmap,因为屏幕内容可能包含其他窗口,不够准确。

核心步骤:

  1. 在鼠标按下事件回调中,获取窗口的Cairo Surface;
  2. 如果Surface不是Image类型(比如启用了硬件加速),就创建临时Image Surface,把窗口内容绘制到这个临时Surface上;
  3. 根据鼠标坐标计算对应像素的位置,读取该像素的Alpha通道值;
  4. 判断Alpha值是否符合要求,符合就触发拖拽。

代码示例:

gboolean on_window_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data) {
    // 只处理左键点击
    if (event->button != GDK_BUTTON_PRIMARY) {
        return FALSE;
    }

    cairo_surface_t *surface = gtk_widget_get_window_surface(widget);
    cairo_surface_t *temp_surface = NULL;

    // 若当前Surface不是Image类型,创建临时Surface捕获窗口内容
    if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE) {
        int width = gtk_widget_get_allocated_width(widget);
        int height = gtk_widget_get_allocated_height(widget);
        temp_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
        cairo_t *cr = cairo_create(temp_surface);
        gtk_widget_draw(widget, cr);
        cairo_destroy(cr);
        surface = temp_surface;
    }

    // 鼠标在窗口内的坐标
    int x = (int)event->x;
    int y = (int)event->y;
    int stride = cairo_image_surface_get_stride(surface);
    unsigned char *pixel_data = cairo_image_surface_get_data(surface);

    // 注意:CAIRO_FORMAT_ARGB32在小端系统(如x86)的字节顺序是BB GG RR AA
    // 因此Alpha通道是每个像素的第4个字节(索引+3)
    unsigned char alpha = pixel_data[y * stride + x * 4 + 3];

    // 可调整阈值,比如alpha > 127表示半透明以上才允许拖拽
    if (alpha > 0) {
        gtk_window_begin_move_drag(GTK_WINDOW(widget),
                                   event->button,
                                   event->x_root,
                                   event->y_root,
                                   event->time);
    }

    // 清理临时Surface
    if (temp_surface != NULL) {
        cairo_surface_destroy(temp_surface);
    }

    return TRUE;
}

注意事项:

  • 坐标转换:event->xevent->y是相对于窗口客户区的坐标,直接使用即可;
  • 字节顺序:大端系统中Alpha通道是每个像素的第1个字节(索引+0),需要根据系统调整;
  • 性能:每次鼠标事件都要读取Surface数据,大尺寸动态窗口可能有轻微卡顿,此时可以结合方法一,只在内容变化时更新缓冲区。

内容的提问来源于stack exchange,提问作者eexpress

火山引擎 最新活动