You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GTK4中通过鼠标移动事件旋转Cairo圆形音量按钮?

GTK4 Cairo圆形音量按钮问题修复

问题1:点击旋转跳转指针位置的修复

当前代码直接将鼠标位置的角度赋值给全局变量,导致点击任意位置时指示点直接跳到鼠标坐标对应的角度。解决核心是:

  • 不再旋转整个画布,而是直接根据当前角度计算指示点位置,避免坐标混乱
  • 限制角度在有效区间(对应音量1-100,取-90°到90°,匹配底部绿色指示线的范围)
  • 仅响应圆形区域内的鼠标操作,避免误触

问题2:输出1-100整数数值的实现

通过线性映射将角度转换为目标区间:

  • 角度范围:-90°(对应音量1)→ 90°(对应音量100)
  • 计算公式:volume = (角度 + 90) / 180 * 99 + 1,最终取整得到1-100的整数

修改后的完整代码

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

// 当前角度(范围:-90到90度,对应音量1到100)
float current_angle = -90.0f;

// 绘制圆形和音量指示点
static void draw_function(GtkDrawingArea *darea, cairo_t *cr, int width, int height, gpointer user_data)
{
    int radius = MIN(width, height) / 2;
    int cx = width / 2;
    int cy = height / 2;

    // 绘制外圆
    cairo_arc(cr, cx, cy, radius, 0.0, 2 * M_PI);
    cairo_set_source_rgb(cr, 0.6, 0.8, 1.0);
    cairo_fill_preserve(cr);
    cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
    cairo_stroke(cr);

    // 计算指示点位置
    float rad = current_angle * M_PI / 180.0f;
    int dot_x = cx + (radius * 0.93) * cos(rad);
    int dot_y = cy + (radius * 0.93) * sin(rad);

    // 绘制音量指示点
    cairo_arc(cr, dot_x, dot_y, radius * 0.05, 0.0, 2 * M_PI);
    cairo_set_source_rgb(cr, 0.9, 0.0, 1.0);
    cairo_fill_preserve(cr);
    cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
    cairo_stroke(cr);

    // 绘制起始和结束指示线
    cairo_set_source_rgb(cr, 0, 1.0, 0);
    cairo_set_line_width(cr, 3.5);

    cairo_move_to(cr, 5, height - 5);
    cairo_line_to(cr, width * 13 / 100, height * 87 / 100);

    cairo_move_to(cr, width - 5, height - 5);
    cairo_line_to(cr, width * 87 / 100, height * 87 / 100);

    cairo_stroke(cr);
}

// 计算鼠标位置对应的角度,并限制范围
static float calculate_target_angle(int cx, int cy, double mx, double my)
{
    float angle = atan2f(my - cy, mx - cx) * 180.0f / M_PI;
    // 限制角度在-90到90度之间(对应音量1到100)
    if (angle < -90.0f) angle = -90.0f;
    if (angle > 90.0f) angle = 90.0f;
    return angle;
}

// 判断鼠标是否在圆形区域内
static gboolean is_mouse_in_circle(int cx, int cy, int radius, double mx, double my)
{
    double dx = mx - cx;
    double dy = my - cy;
    return (dx*dx + dy*dy) <= (radius*radius);
}

void pointer_motion_event(GtkEventController *gesture, gdouble x, gdouble y, gpointer user_data)
{
    GdkModifierType state = gtk_event_controller_get_current_event_state(gesture);
    if (!(state & GDK_BUTTON1_MASK)) return;

    GtkWidget *darea = GTK_WIDGET(user_data);
    int width = gtk_widget_get_width(darea);
    int height = gtk_widget_get_height(darea);
    int cx = width / 2;
    int cy = height / 2;
    int radius = MIN(width, height) / 2;

    // 只在圆形区域内响应拖动
    if (!is_mouse_in_circle(cx, cy, radius, x, y)) return;

    // 计算目标角度
    current_angle = calculate_target_angle(cx, cy, x, y);

    // 转换为1-100的整数音量并输出
    int volume = (int)((current_angle + 90.0f) / 180.0f * 99.0f + 1.0f);
    g_print("当前音量:%d\n", volume);

    gtk_widget_queue_draw(darea);
}

static void activate(GtkApplication* app, gpointer user_data)
{
    GtkWidget *window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "圆形音量按钮");
    gtk_window_set_default_size(GTK_WINDOW(window), 350, 350);

    GtkWidget *darea = gtk_drawing_area_new();
    gtk_drawing_area_set_content_width(GTK_DRAWING_AREA(darea), 350);
    gtk_drawing_area_set_content_height(GTK_DRAWING_AREA(darea), 350);
    gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(darea), draw_function, NULL, NULL);

    gtk_window_set_child(GTK_WINDOW(window), darea);

    GtkEventController *motion_controller = gtk_event_controller_motion_new();
    gtk_widget_add_controller(darea, motion_controller);
    g_signal_connect(motion_controller, "motion", G_CALLBACK(pointer_motion_event), darea);

    gtk_widget_show(window);
}

int main(int argc, char **argv)
{
    GtkApplication *app = gtk_application_new("org.gtk.volume knob", G_APPLICATION_FLAGS_NONE);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}

关键修改说明

  • 移除Rotate函数:直接根据current_angle计算指示点坐标,避免画布旋转带来的坐标偏移问题,同时提升绘制性能
  • 角度范围限制:将角度锁定在-90°到90°,完美匹配底部绿色指示线的起止位置,对应音量1到100的区间
  • 圆形区域判断:仅当鼠标在圆形按钮内部时才响应拖动,避免界面空白区域的误操作
  • 音量数值转换:通过线性映射公式,精准将角度转换为1-100的整数,确保数值范围符合需求
  • 拖动逻辑优化:拖动时平滑跟随鼠标位置(在有效范围内),不再出现点击跳转的问题

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 22:57:08