gnome-shell/js/ui/slider.js
Carlos Garnacho 2baa298e39 slider: Ignore left/right scroll directions
Both of these ended up with a "undefined" delta, which turned
the final value into 0 no matter the previous state.

While this was already fixed for touchpads in the previous commit
(by not forwarding emulated discrete events), this looks somewhat
out of place even for the mice that have left/right discrete scroll
actions (e.g. tilting the scroll wheel left or right).

Let these unhandled directions have a delta of 0, and focus on
vertical scroll for both smooth and discrete events.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2967>
2023-09-22 17:55:12 +02:00

219 lines
6.5 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();
const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
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 handleY = height / 2;
let handleX = ceiledHandleRadius +
(width - 2 * ceiledHandleRadius) * this._value / this._maxValue;
if (rtl)
handleX = width - handleX;
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(event) {
return this.startDragging(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(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 = 0;
if (event.get_flags() & Clutter.EventFlags.FLAG_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(event) {
return this.scroll(event);
}
vfunc_motion_event(event) {
if (this._dragging && !this._grabbedSequence)
return this._motionEvent(this, 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();
const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
let width = this._barLevelWidth;
relX = absX - sliderX;
if (rtl)
relX = width - relX;
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;
}
});