mirror of
https://github.com/clangen/musikcube.git
synced 2025-01-05 21:55:24 +00:00
Merge pull request #383 from clangen/clangen/input-latency
Lower latency input handling, especially with respect to mouse input.
This commit is contained in:
commit
7731b3573a
@ -1,3 +1,9 @@
|
||||
0.96.0
|
||||
|
||||
* improved input latency, especially related to mouse events.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
0.95.0
|
||||
|
||||
* added support for searching by regex; press `M-m` in track or filter views to
|
||||
|
@ -87,7 +87,7 @@ musikcube would not be possible without the following excellent free, open sourc
|
||||
| [boost](http://www.boost.org/) | [flac](https://xiph.org/flac/) | [alsa](https://www.alsa-project.org) | [taglib](http://taglib.org/) | [websocketpp](https://github.com/zaphoyd/websocketpp) | [rxjava](https://github.com/ReactiveX/RxJava) | [ncurses](https://www.gnu.org/software/ncurses/) |
|
||||
| [sqlite](https://www.sqlite.org/) | [ogg/vorbis](http://www.vorbis.com/) | [pulseaudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) | [glide](https://github.com/bumptech/glide) | [libmicrohttpd](https://www.gnu.org/software/libmicrohttpd/) | [rxandroid](https://github.com/ReactiveX/RxAndroid) | [pdcurses (win32a variant)](https://www.projectpluto.com/win32a.htm) |
|
||||
| [utfcpp](https://github.com/nemtrif/utfcpp) | [mad](http://www.underbit.com/products/mad/) + [nomad](https://github.com/cmus/cmus/tree/master/ip) | [core audio](https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html) | | [libcurl](https://curl.haxx.se/libcurl/) | [stetho](http://facebook.github.io/stetho/) | [recycler-fast-scroll](https://github.com/plusCubed/recycler-fast-scroll) |
|
||||
| [nlohmann json](https://github.com/nlohmann/json) | [faad2](http://www.audiocoding.com/faad2.html) | [wasapi](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455(v=vs.85).aspx) | | [libressl](https://www.libressl.org/) | [fabric](http://fabric.io) | |
|
||||
| [nlohmann json](https://github.com/nlohmann/json) | [faad2](http://www.audiocoding.com/faad2.html) | [wasapi](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455(v=vs.85).aspx) | | [libressl](https://www.libressl.org/) | [fabric](http://fabric.io) | |
|
||||
| [kissfft](http://kissfft.sourceforge.net/) | [exoplayer](https://github.com/google/ExoPlayer) | [directsound](https://msdn.microsoft.com/en-us/library/windows/desktop/ee416960(v=vs.85).aspx) | | [nv-websocket-client](https://github.com/TakahikoKawasaki/nv-websocket-client) | [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) | |
|
||||
| [sigslot](http://sigslot.sourceforge.net/) | | [waveout](https://msdn.microsoft.com/en-us/library/windows/desktop/dd743876(v=vs.85).aspx) | | [okhttp](http://square.github.io/okhttp/) | | |
|
||||
| [wcwidth.c](http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c) | | | | | | |
|
||||
|
@ -112,6 +112,125 @@ static bool isLangUtf8() {
|
||||
}
|
||||
#endif
|
||||
|
||||
/* the way curses represents mouse button state is really gross, and makes it
|
||||
annoying to process click events with low latency while also supporting double
|
||||
clicks and multiple buttons. this structure is used to maintain and manipulate
|
||||
state related to mouse buttons, and is used by the main loop of the app to
|
||||
generate button click events for Window instances to handle. beware: kludge
|
||||
and hacks below... */
|
||||
struct MouseButtonState {
|
||||
bool Update(const MEVENT& rawEvent) {
|
||||
const int state = rawEvent.bstate;
|
||||
const int x = rawEvent.x;
|
||||
const int y = rawEvent.y;
|
||||
bool result = false;
|
||||
const int64_t newTime = App::Now();
|
||||
|
||||
/* ugh... */
|
||||
const bool isDown = state & BUTTON1_PRESSED || state & BUTTON2_PRESSED || state & BUTTON3_PRESSED;
|
||||
const bool isUp = state & BUTTON1_RELEASED || state & BUTTON2_RELEASED || state & BUTTON3_RELEASED;
|
||||
|
||||
if (!isDown && !isUp) {
|
||||
/* if both of these were false we got an unexpected mouse event. let's
|
||||
go ahead and reset our state and return */
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
int newButton = 0;
|
||||
|
||||
if (state & BUTTON1_PRESSED || state & BUTTON1_RELEASED) {
|
||||
newButton = 1;
|
||||
}
|
||||
if (state & BUTTON2_PRESSED || state & BUTTON2_RELEASED) {
|
||||
newButton = 2;
|
||||
}
|
||||
if (state & BUTTON3_PRESSED || state & BUTTON3_RELEASED) {
|
||||
newButton = 3;
|
||||
}
|
||||
|
||||
/* threshold for double click, in milliseconds*/
|
||||
const bool elapsed = newTime - time > 300;
|
||||
|
||||
if ((!elapsed && lastX >= 0 && lastY >= 0) && (std::abs(x - lastX) > 2 || std::abs(y - lastY) > 2)) {
|
||||
/* cursor traveled too far, reset state and convert back to a single click */
|
||||
this->Reset();
|
||||
}
|
||||
|
||||
if (newButton != button || elapsed) {
|
||||
/* if the button changed or there was too much time between state
|
||||
changes, go ahead and reset now */
|
||||
Reset();
|
||||
}
|
||||
|
||||
if (isDown) {
|
||||
/* to improve latency response we treat the leading edge of the first
|
||||
click as the event, and don't wait for a "release" event, because that
|
||||
can take hundreds of milliseconds. we kludge the `wasDown` flag to be
|
||||
false, so the subsequent click (below) can be "correctly" recorded
|
||||
as a double click. */
|
||||
if (!this->clicked) {
|
||||
this->clicked = true;
|
||||
this->wasDown = false;
|
||||
}
|
||||
else {
|
||||
this->wasDown = true;
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
else if (clicked && wasDown && !isDown) {
|
||||
this->doubleClicked = true;
|
||||
this->wasDown = true;
|
||||
result = true;
|
||||
}
|
||||
|
||||
this->button = newButton;
|
||||
this->time = newTime;
|
||||
this->lastX = x;
|
||||
this->lastY = y;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Reset() noexcept {
|
||||
button = 0;
|
||||
state = 0;
|
||||
time = 0;
|
||||
lastX = -1;
|
||||
lastY = -1;
|
||||
wasDown = false;
|
||||
clicked = false;
|
||||
doubleClicked = false;
|
||||
}
|
||||
|
||||
mmask_t ToCursesState() noexcept {
|
||||
if (doubleClicked) {
|
||||
switch (button) {
|
||||
case 1: return BUTTON1_DOUBLE_CLICKED;
|
||||
case 2: return BUTTON2_DOUBLE_CLICKED;
|
||||
case 3: return BUTTON3_DOUBLE_CLICKED;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
else if (clicked) {
|
||||
switch (button) {
|
||||
case 1: return BUTTON1_CLICKED;
|
||||
case 2: return BUTTON2_CLICKED;
|
||||
case 3: return BUTTON3_CLICKED;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool wasDown{ false };
|
||||
bool clicked{ false };
|
||||
bool doubleClicked{ false };
|
||||
int button;
|
||||
int state;
|
||||
int lastX{ -1 }, lastY{ -1 };
|
||||
int64_t time{ -1 };
|
||||
};
|
||||
|
||||
App& App::Instance() {
|
||||
if (!instance) {
|
||||
throw std::runtime_error("app not running!");
|
||||
@ -158,6 +277,16 @@ void App::InitCurses() {
|
||||
refresh();
|
||||
curs_set(0);
|
||||
mousemask(ALL_MOUSE_EVENTS, nullptr);
|
||||
mouseinterval(0);
|
||||
|
||||
#ifndef WIN32
|
||||
set_escdelay(20);
|
||||
timeout(IDLE_TIMEOUT_MS);
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
nodelay(stdscr, true);
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
PDC_set_function_key(FUNCTION_KEY_SHUT_DOWN, 4);
|
||||
@ -172,8 +301,6 @@ void App::InitCurses() {
|
||||
this->SetIcon(this->iconId);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
set_escdelay(20);
|
||||
#endif
|
||||
|
||||
Colors::Init(this->colorMode, this->bgType);
|
||||
@ -378,7 +505,6 @@ void App::Run(ILayoutPtr layout) {
|
||||
}
|
||||
#endif
|
||||
|
||||
MEVENT mouseEvent;
|
||||
int64_t ch;
|
||||
std::string kn;
|
||||
|
||||
@ -387,6 +513,9 @@ void App::Run(ILayoutPtr layout) {
|
||||
|
||||
this->ChangeLayout(layout);
|
||||
|
||||
MEVENT rawMouseEvent;
|
||||
MouseButtonState mouseButtonState;
|
||||
|
||||
while (!this->quit && !disconnected) {
|
||||
kn = "";
|
||||
|
||||
@ -397,13 +526,14 @@ void App::Run(ILayoutPtr layout) {
|
||||
goto process;
|
||||
}
|
||||
|
||||
timeout(IDLE_TIMEOUT_MS);
|
||||
|
||||
if (this->state.input && this->state.focused->GetContent()) {
|
||||
/* if the focused window is an input, allow it to draw a cursor */
|
||||
WINDOW *c = this->state.focused->GetContent();
|
||||
keypad(c, TRUE);
|
||||
wtimeout(c, IDLE_TIMEOUT_MS);
|
||||
#ifdef WIN32
|
||||
nodelay(c, true);
|
||||
#endif
|
||||
ch = wgetch(c);
|
||||
}
|
||||
else {
|
||||
@ -435,21 +565,22 @@ process:
|
||||
}
|
||||
else if (this->mouseEnabled && kn == "KEY_MOUSE") {
|
||||
#ifdef WIN32
|
||||
if (nc_getmouse(&mouseEvent) == 0) {
|
||||
if (nc_getmouse(&rawMouseEvent) == 0) {
|
||||
#else
|
||||
if (getmouse(&mouseEvent) == 0) {
|
||||
if (getmouse(&rawMouseEvent) == 0) {
|
||||
#endif
|
||||
auto active = this->state.ActiveLayout();
|
||||
if (active) {
|
||||
using Event = IMouseHandler::Event;
|
||||
auto window = dynamic_cast<IWindow*>(active.get());
|
||||
Event event(mouseEvent, window);
|
||||
Event event(rawMouseEvent, window);
|
||||
if (event.MouseWheelDown() || event.MouseWheelUp()) {
|
||||
if (state.focused) {
|
||||
state.focused->MouseEvent(event);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (mouseButtonState.Update(rawMouseEvent)) {
|
||||
event.state = mouseButtonState.ToCursesState();
|
||||
active->MouseEvent(event);
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@
|
||||
#include <musikcore/runtime/IMessageQueue.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#define IDLE_TIMEOUT_MS 10
|
||||
#define IDLE_TIMEOUT_MS 0
|
||||
#define REDRAW_DEBOUNCE_MS 100
|
||||
#else
|
||||
#define IDLE_TIMEOUT_MS 75
|
||||
|
Loading…
Reference in New Issue
Block a user