diff --git a/src/libsysprof-gtk/style.css b/src/libsysprof-gtk/style.css index 77fd213c..d02d805d 100644 --- a/src/libsysprof-gtk/style.css +++ b/src/libsysprof-gtk/style.css @@ -69,6 +69,8 @@ timeruler { color: alpha(currentColor, .8); } +timescrubber informative, +timescrubber timecode, tracks informative, tracks timecode { border-radius: 7px; diff --git a/src/sysprof/sysprof-time-scrubber.c b/src/sysprof/sysprof-time-scrubber.c index bd14fefe..2e7a2f42 100644 --- a/src/sysprof/sysprof-time-scrubber.c +++ b/src/sysprof/sysprof-time-scrubber.c @@ -22,6 +22,8 @@ #include +#include "sysprof-session-private.h" + #include "sysprof-time-scrubber.h" struct _SysprofTimeScrubber @@ -29,9 +31,24 @@ struct _SysprofTimeScrubber GtkWidget parent_instance; SysprofSession *session; + GSignalGroup *session_signals; - SysprofTimeRuler *ruler; + GtkLabel *informative; + GtkBox *main; + GtkLabel *timecode; GtkBox *vbox; + SysprofTimeRuler *ruler; + GtkButton *zoom; + + double motion_x; + double motion_y; + + double drag_start_x; + double drag_start_y; + double drag_offset_x; + double drag_offset_y; + + guint in_drag_selection : 1; }; enum { @@ -48,6 +65,468 @@ G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofTimeScrubber, sysprof_time_scrubber, GTK_T static GParamSpec *properties[N_PROPS]; +static void +set_motion (SysprofTimeScrubber *self, + double x, + double y) +{ + gboolean timecode_visible = FALSE; + gboolean informative_visible = FALSE; + GtkWidget *pick; + int ruler_start; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + if (self->motion_x == x && self->motion_y == y) + return; + + self->motion_x = x; + self->motion_y = y; + + ruler_start = 0; + timecode_visible = x >= ruler_start; + + if (timecode_visible) + { + g_autofree char *str = sysprof_time_ruler_get_label_at_point (self->ruler, x - ruler_start); + gtk_label_set_label (self->timecode, str); + } + + pick = gtk_widget_pick (GTK_WIDGET (self), x, y, 0); + + if (pick != NULL) + { + SysprofChartLayer *layer = SYSPROF_CHART_LAYER (gtk_widget_get_ancestor (pick, SYSPROF_TYPE_CHART_LAYER)); + + if (layer != NULL) + { + g_autoptr(GObject) item = NULL; + g_autofree char *text = NULL; + double layer_x; + double layer_y; + + gtk_widget_translate_coordinates (GTK_WIDGET (self), + GTK_WIDGET (layer), + x, y, &layer_x, &layer_y); + + if ((item = sysprof_chart_layer_lookup_item (layer, layer_x, layer_y))) + text = _sysprof_session_describe (self->session, NULL, item); + + gtk_label_set_label (self->informative, text); + + informative_visible = text != NULL; + } + } + + gtk_widget_set_visible (GTK_WIDGET (self->timecode), timecode_visible); + gtk_widget_set_visible (GTK_WIDGET (self->informative), informative_visible); + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_time_scrubber_motion_enter_cb (SysprofTimeScrubber *self, + double x, + double y, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + set_motion (self, x, y); +} + +static void +sysprof_time_scrubber_motion_leave_cb (SysprofTimeScrubber *self, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + set_motion (self, -1, -1); +} + +static void +sysprof_time_scrubber_motion_cb (SysprofTimeScrubber *self, + double x, + double y, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + set_motion (self, x, y); +} + +static void +sysprof_time_scrubber_drag_begin_cb (SysprofTimeScrubber *self, + double start_x, + double start_y, + GtkGestureDrag *drag) +{ + graphene_rect_t zoom_area; + double x, y; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); + + gtk_widget_translate_coordinates (GTK_WIDGET (self->zoom), + GTK_WIDGET (self), + 0, 0, &x, &y); + zoom_area = GRAPHENE_RECT_INIT (x, y, + gtk_widget_get_width (GTK_WIDGET (self->zoom)), + gtk_widget_get_height (GTK_WIDGET (self->zoom))); + + if (start_x < 0 || + (gtk_widget_get_visible (GTK_WIDGET (self->zoom)) && + graphene_rect_contains_point (&zoom_area, &GRAPHENE_POINT_INIT (start_x, start_y)))) + { + gtk_gesture_set_state (GTK_GESTURE (drag), GTK_EVENT_SEQUENCE_DENIED); + return; + } + + self->drag_start_x = start_x; + self->drag_start_y = start_y; + self->drag_offset_x = 0; + self->drag_offset_y = 0; + + gtk_widget_set_visible (GTK_WIDGET (self->zoom), FALSE); + + self->in_drag_selection = TRUE; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_time_scrubber_drag_end_cb (SysprofTimeScrubber *self, + double offset_x, + double offset_y, + GtkGestureDrag *drag) +{ + int base_x; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); + + if (self->session == NULL) + goto cleanup; + + base_x = 0; + + if (self->drag_offset_x != .0) + { + graphene_rect_t selection; + graphene_rect_t area; + int width; + int height; + + width = gtk_widget_get_width (GTK_WIDGET (self)) - base_x; + height = gtk_widget_get_height (GTK_WIDGET (self)); + + area = GRAPHENE_RECT_INIT (base_x, 0, width, height); + selection = GRAPHENE_RECT_INIT (self->drag_start_x, + 0, + self->drag_offset_x, + height); + graphene_rect_normalize (&selection); + + if (graphene_rect_intersection (&area, &selection, &selection)) + { + double begin = (selection.origin.x - area.origin.x) / area.size.width; + double end = (selection.origin.x + selection.size.width - area.origin.x) / area.size.width; + const SysprofTimeSpan *visible = sysprof_session_get_visible_time (self->session); + gint64 visible_duration = visible->end_nsec - visible->begin_nsec; + SysprofTimeSpan to_select; + + to_select.begin_nsec = visible->begin_nsec + (begin * visible_duration); + to_select.end_nsec = visible->begin_nsec + (end * visible_duration); + + sysprof_session_select_time (self->session, &to_select); + } + } + else if (self->drag_start_x >= base_x) + { + sysprof_session_select_time (self->session, sysprof_session_get_visible_time (self->session)); + } + +cleanup: + self->drag_start_x = -1; + self->drag_start_y = -1; + self->drag_offset_x = 0; + self->drag_offset_y = 0; + + self->in_drag_selection = FALSE; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_time_scrubber_drag_update_cb (SysprofTimeScrubber *self, + double offset_x, + double offset_y, + GtkGestureDrag *drag) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); + + self->drag_offset_x = offset_x, + self->drag_offset_y = offset_y; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static gboolean +get_selected_area (SysprofTimeScrubber *self, + graphene_rect_t *area, + graphene_rect_t *selection) +{ + const SysprofTimeSpan *selected; + const SysprofTimeSpan *visible; + SysprofTimeSpan relative; + gint64 time_duration; + double begin; + double end; + int base_x; + int width; + int height; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (area != NULL); + g_assert (selection != NULL); + + if (self->session == NULL) + return FALSE; + + base_x = 0; + width = gtk_widget_get_width (GTK_WIDGET (self)) - base_x; + height = gtk_widget_get_height (GTK_WIDGET (self)); + + *area = GRAPHENE_RECT_INIT (base_x, 0, width, height); + + if (self->in_drag_selection && self->drag_offset_x != .0) + { + *selection = GRAPHENE_RECT_INIT (self->drag_start_x, + 0, + self->drag_offset_x, + height); + graphene_rect_normalize (selection); + return graphene_rect_intersection (area, selection, selection); + } + + /* If selected range == visible range, then there is no selection */ + selected = sysprof_session_get_selected_time (self->session); + visible = sysprof_session_get_visible_time (self->session); + if (memcmp (selected, visible, sizeof *selected) == 0) + return FALSE; + + time_duration = sysprof_time_span_duration (*visible); + relative = sysprof_time_span_relative_to (*selected, visible->begin_nsec); + + begin = relative.begin_nsec / (double)time_duration; + end = relative.end_nsec / (double)time_duration; + + *selection = GRAPHENE_RECT_INIT (area->origin.x + (begin * width), + 0, + (end * width) - (begin * width), + area->size.height); + + return TRUE; +} + +static void +sysprof_time_scrubber_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)widget; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + gtk_widget_measure (GTK_WIDGET (self->main), + orientation, + for_size, + minimum, + natural, + minimum_baseline, + natural_baseline); +} + +static void +sysprof_time_scrubber_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)widget; + graphene_rect_t area; + graphene_rect_t selection; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + gtk_widget_size_allocate (GTK_WIDGET (self->main), + &(GtkAllocation) {0, 0, width, height}, + baseline); + + if (get_selected_area (self, &area, &selection)) + { + graphene_point_t middle; + GtkRequisition min_req; + GtkRequisition nat_req; + + /* Position the zoom button in the center of the selected area */ + gtk_widget_get_preferred_size (GTK_WIDGET (self->zoom), &min_req, &nat_req); + graphene_rect_get_center (&selection, &middle); + gtk_widget_size_allocate (GTK_WIDGET (self->zoom), + &(GtkAllocation) { + middle.x - (min_req.width/2), + middle.y - (min_req.height/2), + min_req.width, + min_req.height + }, -1); + } + + if (gtk_widget_get_visible (GTK_WIDGET (self->timecode))) + { + GtkRequisition min_req; + GtkRequisition nat_req; + + gtk_widget_get_preferred_size (GTK_WIDGET (self->timecode), &min_req, &nat_req); + + if (self->motion_x + min_req.width < gtk_widget_get_width (GTK_WIDGET (self))) + gtk_widget_size_allocate (GTK_WIDGET (self->timecode), + &(GtkAllocation) { + self->motion_x, 0, + min_req.width, min_req.height + }, -1); + else + gtk_widget_size_allocate (GTK_WIDGET (self->timecode), + &(GtkAllocation) { + self->motion_x - min_req.width, 0, + min_req.width, min_req.height + }, -1); + } + + if (gtk_widget_get_visible (GTK_WIDGET (self->informative))) + { + GtkRequisition min_req; + GtkRequisition nat_req; + + gtk_widget_get_preferred_size (GTK_WIDGET (self->informative), &min_req, &nat_req); + + if (self->motion_x + min_req.width < gtk_widget_get_width (GTK_WIDGET (self))) + gtk_widget_size_allocate (GTK_WIDGET (self->informative), + &(GtkAllocation) { + self->motion_x, self->motion_y, + min_req.width, min_req.height + }, -1); + else + gtk_widget_size_allocate (GTK_WIDGET (self->informative), + &(GtkAllocation) { + self->motion_x - min_req.width, self->motion_y, + min_req.width, min_req.height + }, -1); + } +} + +static void +sysprof_time_scrubber_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)widget; + graphene_rect_t area; + graphene_rect_t selection; + GdkRGBA shadow_color; + GdkRGBA line_color; + GdkRGBA color; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + gtk_widget_snapshot_child (GTK_WIDGET (self), GTK_WIDGET (self->main), snapshot); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + { + GtkStyleContext *style_context = gtk_widget_get_style_context (GTK_WIDGET (self)); + gtk_style_context_get_color (style_context, &color); + + shadow_color = color; + shadow_color.alpha *= .1; + + line_color = color; + line_color.alpha *= .5; + } +G_GNUC_END_IGNORE_DEPRECATIONS + + if (get_selected_area (self, &area, &selection)) + { + gtk_snapshot_append_color (snapshot, + &shadow_color, + &GRAPHENE_RECT_INIT (area.origin.x, + area.origin.y, + selection.origin.x - area.origin.x, + area.size.height)); + + gtk_snapshot_append_color (snapshot, + &shadow_color, + &GRAPHENE_RECT_INIT (selection.origin.x + selection.size.width, + area.origin.y, + (area.origin.x + area.size.width) - (selection.origin.x + selection.size.width), + area.size.height)); + } + + if (self->motion_x != -1 && self->motion_y != -1 && self->motion_x > 0) + gtk_snapshot_append_color (snapshot, + &line_color, + &GRAPHENE_RECT_INIT (self->motion_x, 0, 1, + gtk_widget_get_height (GTK_WIDGET (self)))); + + if (gtk_widget_get_visible (GTK_WIDGET (self->zoom))) + gtk_widget_snapshot_child (GTK_WIDGET (self), GTK_WIDGET (self->zoom), snapshot); + + if (gtk_widget_get_visible (GTK_WIDGET (self->timecode))) + gtk_widget_snapshot_child (GTK_WIDGET (self), GTK_WIDGET (self->timecode), snapshot); + + if (gtk_widget_get_visible (GTK_WIDGET (self->informative))) + gtk_widget_snapshot_child (GTK_WIDGET (self), GTK_WIDGET (self->informative), snapshot); +} + +static void +notify_time_cb (SysprofTimeScrubber *self, + GParamSpec *pspec, + SysprofSession *session) +{ + const SysprofTimeSpan *visible; + const SysprofTimeSpan *selected; + gboolean button_visible; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (SYSPROF_IS_SESSION (session)); + + visible = sysprof_session_get_visible_time (session); + selected = sysprof_session_get_selected_time (session); + + button_visible = memcmp (visible, selected, sizeof *visible) != 0; + + gtk_widget_set_visible (GTK_WIDGET (self->zoom), button_visible); +} + +static void +sysprof_time_scrubber_zoom_to_selection (GtkWidget *widget, + const char *action_name, + GVariant *params) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)widget; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + if (self->session != NULL) + sysprof_session_zoom_to_selection (self->session); +} + static void sysprof_time_scrubber_dispose (GObject *object) { @@ -60,6 +539,7 @@ sysprof_time_scrubber_dispose (GObject *object) gtk_widget_unparent (child); g_clear_object (&self->session); + g_clear_object (&self->session_signals); G_OBJECT_CLASS (sysprof_time_scrubber_parent_class)->dispose (object); } @@ -112,6 +592,10 @@ sysprof_time_scrubber_class_init (SysprofTimeScrubberClass *klass) object_class->get_property = sysprof_time_scrubber_get_property; object_class->set_property = sysprof_time_scrubber_set_property; + widget_class->measure = sysprof_time_scrubber_measure; + widget_class->snapshot = sysprof_time_scrubber_snapshot; + widget_class->size_allocate = sysprof_time_scrubber_size_allocate; + properties[PROP_SESSION] = g_param_spec_object ("session", NULL, NULL, SYSPROF_TYPE_SESSION, @@ -120,9 +604,25 @@ sysprof_time_scrubber_class_init (SysprofTimeScrubberClass *klass) g_object_class_install_properties (object_class, N_PROPS, properties); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-time-scrubber.ui"); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, vbox); + + gtk_widget_class_set_css_name (widget_class, "timescrubber"); + + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, informative); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, main); gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, ruler); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, timecode); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, vbox); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, zoom); + + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_motion_enter_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_motion_leave_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_motion_cb); + + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_drag_begin_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_drag_end_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_drag_update_cb); + + gtk_widget_class_install_action (widget_class, "zoom-to-selection", NULL, sysprof_time_scrubber_zoom_to_selection); g_type_ensure (SYSPROF_TYPE_TIME_RULER); } @@ -130,6 +630,18 @@ sysprof_time_scrubber_class_init (SysprofTimeScrubberClass *klass) static void sysprof_time_scrubber_init (SysprofTimeScrubber *self) { + self->session_signals = g_signal_group_new (SYSPROF_TYPE_SESSION); + g_signal_group_connect_object (self->session_signals, + "notify::selected-time", + G_CALLBACK (notify_time_cb), + self, + G_CONNECT_SWAPPED); + g_signal_group_connect_object (self->session_signals, + "notify::visible-time", + G_CALLBACK (notify_time_cb), + self, + G_CONNECT_SWAPPED); + gtk_widget_init_template (GTK_WIDGET (self)); } @@ -149,7 +661,10 @@ sysprof_time_scrubber_set_session (SysprofTimeScrubber *self, g_return_if_fail (!session || SYSPROF_IS_SESSION (session)); if (g_set_object (&self->session, session)) - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); + { + g_signal_group_set_target (self->session_signals, session); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); + } } static void diff --git a/src/sysprof/sysprof-time-scrubber.ui b/src/sysprof/sysprof-time-scrubber.ui index 6683ccab..0188f29a 100644 --- a/src/sysprof/sysprof-time-scrubber.ui +++ b/src/sysprof/sysprof-time-scrubber.ui @@ -4,6 +4,22 @@ vertical + + + capture + + + + + + + + bubble + + + + + @@ -18,5 +34,39 @@ + + + zoom-to-selection + zoom-in-symbolic + center + center + false + + + + + + timecode + false + false + false + .5 + true + 10 + 10 + + + + + informative + false + false + false + 0 + true + +