gnome-shell/js/ui/slider.js
Carlos Garnacho 8423ba44fe js: Mass move to Clutter.Event getter methods in Clutter.Actor vfuncs
These traditionally got the various ClutterEvent subtype structs as their
argument, so it was not allowed to use ClutterEvent generic getter methods
in these vfuncs. These methods used direct access to struct fields instead.

This got spoiled with the move to make ClutterEvent opaque types, since
these are no longer public structs so GNOME Shell most silently failed to
fetch the expected values from event fields. But since they are not
ClutterEvents either, the getters could not be used on them.

Mutter is changing so that these vmethods all contain an alias to the
one and only Clutter.Event type, thus lifting those barriers, and making
it possible to use the ClutterEvent methods in these vfuncs.

Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/2950
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2872>
2023-08-09 13:46:08 +02:00

212 lines
6.4 KiB
JavaScript

/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
import Atk from 'gi://Atk';
import Clutter from 'gi://Clutter';
import GObject from 'gi://GObject';
import * as BarLevel from './barLevel.js';
const SLIDER_SCROLL_STEP = 0.02; /* Slider scrolling step in % */
export const Slider = GObject.registerClass({
Signals: {
'drag-begin': {},
'drag-end': {},
},
}, class Slider extends BarLevel.BarLevel {
_init(value) {
super._init({
value,
style_class: 'slider',
can_focus: true,
reactive: true,
accessible_role: Atk.Role.SLIDER,
x_expand: true,
});
this._releaseId = 0;
this._dragging = false;
this._customAccessible.connect('get-minimum-increment', this._getMinimumIncrement.bind(this));
}
vfunc_repaint() {
super.vfunc_repaint();
// Add handle
let cr = this.get_context();
let themeNode = this.get_theme_node();
let [width, height] = this.get_surface_size();
let handleRadius = themeNode.get_length('-slider-handle-radius');
let handleBorderWidth = themeNode.get_length('-slider-handle-border-width');
let [hasHandleColor, handleBorderColor] =
themeNode.lookup_color('-slider-handle-border-color', false);
const ceiledHandleRadius = Math.ceil(handleRadius + handleBorderWidth);
const handleX = ceiledHandleRadius +
(width - 2 * ceiledHandleRadius) * this._value / this._maxValue;
const handleY = height / 2;
let color = themeNode.get_foreground_color();
Clutter.cairo_set_source_color(cr, color);
cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI);
cr.fillPreserve();
if (hasHandleColor && handleBorderWidth) {
Clutter.cairo_set_source_color(cr, handleBorderColor);
cr.setLineWidth(handleBorderWidth);
cr.stroke();
}
cr.$dispose();
}
vfunc_button_press_event() {
return this.startDragging(Clutter.get_current_event());
}
startDragging(event) {
if (this._dragging)
return Clutter.EVENT_PROPAGATE;
this._dragging = true;
let device = event.get_device();
let sequence = event.get_event_sequence();
this._grab = global.stage.grab(this);
this._grabbedDevice = device;
this._grabbedSequence = sequence;
// We need to emit 'drag-begin' before moving the handle to make
// sure that no 'notify::value' signal is emitted before this one.
this.emit('drag-begin');
let absX, absY;
[absX, absY] = event.get_coords();
this._moveHandle(absX, absY);
return Clutter.EVENT_STOP;
}
_endDragging() {
if (this._dragging) {
if (this._releaseId) {
this.disconnect(this._releaseId);
this._releaseId = 0;
}
if (this._grab) {
this._grab.dismiss();
this._grab = null;
}
this._grabbedSequence = null;
this._grabbedDevice = null;
this._dragging = false;
this.emit('drag-end');
}
return Clutter.EVENT_STOP;
}
vfunc_button_release_event() {
if (this._dragging && !this._grabbedSequence)
return this._endDragging();
return Clutter.EVENT_PROPAGATE;
}
vfunc_touch_event() {
let event = Clutter.get_current_event();
let sequence = event.get_event_sequence();
if (!this._dragging &&
event.type() == Clutter.EventType.TOUCH_BEGIN) {
this.startDragging(event);
return Clutter.EVENT_STOP;
} else if (this._grabbedSequence &&
sequence.get_slot() === this._grabbedSequence.get_slot()) {
if (event.type() == Clutter.EventType.TOUCH_UPDATE)
return this._motionEvent(this, event);
else if (event.type() == Clutter.EventType.TOUCH_END)
return this._endDragging();
}
return Clutter.EVENT_PROPAGATE;
}
scroll(event) {
let direction = event.get_scroll_direction();
let delta;
if (event.get_flags() & Clutter.EventFlags.POINTER_EMULATED)
return Clutter.EVENT_PROPAGATE;
if (direction == Clutter.ScrollDirection.DOWN) {
delta = -SLIDER_SCROLL_STEP;
} else if (direction == Clutter.ScrollDirection.UP) {
delta = SLIDER_SCROLL_STEP;
} else if (direction == Clutter.ScrollDirection.SMOOTH) {
let [, dy] = event.get_scroll_delta();
// Even though the slider is horizontal, use dy to match
// the UP/DOWN above.
delta = -dy * SLIDER_SCROLL_STEP;
}
this.value = Math.min(Math.max(0, this._value + delta), this._maxValue);
return Clutter.EVENT_STOP;
}
vfunc_scroll_event() {
return this.scroll(Clutter.get_current_event());
}
vfunc_motion_event() {
if (this._dragging && !this._grabbedSequence)
return this._motionEvent(this, Clutter.get_current_event());
return Clutter.EVENT_PROPAGATE;
}
_motionEvent(actor, event) {
let absX, absY;
[absX, absY] = event.get_coords();
this._moveHandle(absX, absY);
return Clutter.EVENT_STOP;
}
vfunc_key_press_event(event) {
let key = event.get_key_symbol();
if (key == Clutter.KEY_Right || key == Clutter.KEY_Left) {
let delta = key == Clutter.KEY_Right ? 0.1 : -0.1;
this.value = Math.max(0, Math.min(this._value + delta, this._maxValue));
return Clutter.EVENT_STOP;
}
return super.vfunc_key_press_event(event);
}
_moveHandle(absX, _absY) {
let relX, sliderX;
[sliderX] = this.get_transformed_position();
relX = absX - sliderX;
let width = this._barLevelWidth;
let handleRadius = this.get_theme_node().get_length('-slider-handle-radius');
let newvalue;
if (relX < handleRadius)
newvalue = 0;
else if (relX > width - handleRadius)
newvalue = 1;
else
newvalue = (relX - handleRadius) / (width - 2 * handleRadius);
this.value = newvalue * this._maxValue;
}
_getMinimumIncrement() {
return 0.1;
}
});