Refactor mouse press/release handling to support 3 buttons

Change button to be an additional parameter instead of having separate
events for left/right buttons.

Fixes #3471
This commit is contained in:
Maxime Coste 2020-06-28 19:48:55 +10:00
parent fc3e5ea419
commit d3374e7e5f
6 changed files with 103 additions and 78 deletions

View File

@ -59,11 +59,8 @@ The requests that the json ui can interpret on stdin are:
* keys(String key1, String key2...): keystrokes
* resize(int rows, int columns): notify ui resize
* scroll(int amount): scroll by given line amount
* mouse(String type, int line, int column): mouse event. line and column relate to
the cursor position. type can be:
- 'move'
- 'press_left'
- 'press_right'
- 'release_left'
- 'release_right'
* mouse_move(int line, int column): line and column relate to the cursor position.
* mouse_press(String button, int line, int column): line and column relate to
cursor position, button can be 'left', 'middle' or 'right'
* mouse_release(String button, int line, int column): same.
* menu_select(int index): explicit select of given menu entry

View File

@ -92,35 +92,40 @@ struct MouseHandler
Buffer& buffer = context.buffer();
BufferCoord cursor;
auto& selections = context.selections();
constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift;
constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask;
switch ((key.modifiers & ~modifiers).value)
{
case Key::Modifiers::MousePressRight:
m_dragging = false;
cursor = context.window().buffer_coord(key.coord());
if (key.modifiers & Key::Modifiers::Control)
selections = {{selections.begin()->anchor(), cursor}};
else
selections.main() = {selections.main().anchor(), cursor};
selections.sort_and_merge_overlapping();
return true;
case Key::Modifiers::MousePressLeft:
m_dragging = true;
m_anchor = context.window().buffer_coord(key.coord());
if (not (key.modifiers & Key::Modifiers::Control))
context.selections_write_only() = { buffer, m_anchor};
else
case Key::Modifiers::MousePress:
switch (key.mouse_button())
{
size_t main = selections.size();
selections.push_back({m_anchor});
selections.set_main_index(main);
case Key::MouseButton::Right:
m_dragging = false;
cursor = context.window().buffer_coord(key.coord());
if (key.modifiers & Key::Modifiers::Control)
selections = {{selections.begin()->anchor(), cursor}};
else
selections.main() = {selections.main().anchor(), cursor};
selections.sort_and_merge_overlapping();
}
return true;
return true;
case Key::Modifiers::MouseReleaseLeft:
case Key::Modifiers::MouseReleaseRight:
case Key::MouseButton::Left:
m_dragging = true;
m_anchor = context.window().buffer_coord(key.coord());
if (not (key.modifiers & Key::Modifiers::Control))
context.selections_write_only() = { buffer, m_anchor};
else
{
size_t main = selections.size();
selections.push_back({m_anchor});
selections.set_main_index(main);
selections.sort_and_merge_overlapping();
}
return true;
default: return true;
}
case Key::Modifiers::MouseRelease:
if (not m_dragging)
return true;
m_dragging = false;

View File

@ -248,31 +248,30 @@ void JsonUI::eval_json(const Value& json)
m_on_key(key);
}
}
else if (method == "mouse")
else if (method == "mouse_move")
{
if (params.size() != 3)
throw invalid_rpc_request("mouse type/coordinates not specified");
if (params.size() != 2)
throw invalid_rpc_request("mouse coordinates not specified");
if (not params[0].is_a<String>())
throw invalid_rpc_request("mouse type is not a string");
else if (not params[1].is_a<int>() or
not params[2].is_a<int>())
if (not params[0].is_a<int>() or not params[1].is_a<int>())
throw invalid_rpc_request("mouse coordinates are not integers");
const StringView type = params[0].as<String>();
const Codepoint coord = encode_coord({params[1].as<int>(), params[2].as<int>()});
if (type == "move")
m_on_key({Key::Modifiers::MousePos, coord});
else if (type == "press_left")
m_on_key({Key::Modifiers::MousePressLeft, coord});
else if (type == "press_right")
m_on_key({Key::Modifiers::MousePressRight, coord});
else if (type == "release_left")
m_on_key({Key::Modifiers::MouseReleaseLeft, coord});
else if (type == "release_right")
m_on_key({Key::Modifiers::MouseReleaseRight, coord});
else
throw invalid_rpc_request(format("invalid mouse event type: {}", type));
m_on_key({Key::Modifiers::MousePos, encode_coord({params[0].as<int>(), params[1].as<int>()})});
}
else if (method == "mouse_press" or method == "mouse_release")
{
if (params.size() != 3)
throw invalid_rpc_request("mouse button/coordinates not specified");
if (not params[0].is_a<String>())
throw invalid_rpc_request("mouse button is not a string");
if (not params[1].is_a<int>() or not params[2].is_a<int>())
throw invalid_rpc_request("mouse coordinates are not integers");
auto event = method == "mouse_press" ? Key::Modifiers::MousePress : Key::Modifiers::MouseRelease;
auto button = str_to_button(params[0].as<String>());
m_on_key({event | Key::to_modifier(button), encode_coord({params[1].as<int>(), params[2].as<int>()})});
}
else if (method == "scroll")
{

View File

@ -157,21 +157,36 @@ KeyList parse_keys(StringView str)
return result;
}
StringView button_to_str(Key::MouseButton button)
{
switch (button)
{
case Key::MouseButton::Left: return "left";
case Key::MouseButton::Middle: return "middle";
case Key::MouseButton::Right: return "right";
default: kak_assert(false); throw logic_error{};
}
}
Key::MouseButton str_to_button(StringView str)
{
if (str == "left") return Key::MouseButton::Left;
if (str == "middle") return Key::MouseButton::Middle;
if (str == "right") return Key::MouseButton::Right;
throw runtime_error(format("invalid mouse button name {}", str));
}
String key_to_str(Key key)
{
const auto coord = key.coord() + DisplayCoord{1,1};
switch (key.modifiers)
switch (Key::Modifiers(key.modifiers & ~Key::Modifiers::MouseButtonMask))
{
case Key::Modifiers::MousePos:
return format("<mouse:move:{}.{}>", coord.line, coord.column);
case Key::Modifiers::MousePressLeft:
return format("<mouse:press_left:{}.{}>", coord.line, coord.column);
case Key::Modifiers::MousePressRight:
return format("<mouse:press_right:{}.{}>", coord.line, coord.column);
case Key::Modifiers::MouseReleaseLeft:
return format("<mouse:release_left:{}.{}>", coord.line, coord.column);
case Key::Modifiers::MouseReleaseRight:
return format("<mouse:release_right:{}.{}>", coord.line, coord.column);
case Key::Modifiers::MousePress:
return format("<mouse:press:{}:{}.{}>", button_to_str(key.mouse_button()), coord.line, coord.column);
case Key::Modifiers::MouseRelease:
return format("<mouse:release:{}:{}.{}>", button_to_str(key.mouse_button()), coord.line, coord.column);
case Key::Modifiers::Scroll:
return format("<scroll:{}>", static_cast<int>(key.key));
case Key::Modifiers::Resize:

View File

@ -14,6 +14,12 @@ namespace Kakoune
struct Key
{
enum class MouseButton
{
Left,
Middle,
Right
};
enum class Modifiers : int
{
None = 0,
@ -21,11 +27,11 @@ struct Key
Alt = 1 << 1,
Shift = 1 << 2,
MousePressLeft = 1 << 3,
MousePressRight = 1 << 4,
MouseReleaseLeft = 1 << 5,
MouseReleaseRight = 1 << 6,
MousePos = 1 << 7,
MousePress = 1 << 3,
MouseRelease = 1 << 4,
MousePos = 1 << 5,
MouseButtonMask= 0b11 << 6,
Scroll = 1 << 8,
Resize = 1 << 9,
MenuSelect = 1 << 10,
@ -82,6 +88,8 @@ struct Key
constexpr bool operator<(Key other) const { return val() < other.val(); }
constexpr DisplayCoord coord() const { return {(int)((key & 0xFFFF0000) >> 16), (int)(key & 0x0000FFFF)}; }
constexpr MouseButton mouse_button() { return MouseButton{((int)modifiers & (int)Modifiers::MouseButtonMask) >> 6}; }
static Modifiers to_modifier(MouseButton button) { return Key::Modifiers{((int)button << 6) & (int)Modifiers::MouseButtonMask}; }
Optional<Codepoint> codepoint() const;
};
@ -95,6 +103,8 @@ class StringView;
KeyList parse_keys(StringView str);
String key_to_str(Key key);
StringView button_to_str(Key::MouseButton button);
Key::MouseButton str_to_button(StringView str);
constexpr Key shift(Key key)
{

View File

@ -19,6 +19,7 @@
#include <csignal>
#include <sys/ioctl.h>
#include <unistd.h>
#include <strings.h>
constexpr char control(char c) { return c & 037; }
@ -669,19 +670,19 @@ Optional<Key> NCursesUI::get_next_key()
return mod;
};
auto mouse_button = [this](Key::Modifiers mod, Codepoint coord, bool left, bool release) {
auto mask = left ? 0x1 : 0x2;
auto mouse_button = [this](Key::Modifiers mod, Key::MouseButton button, Codepoint coord, bool release) {
auto mask = 1 << (int)button;
if (not release)
{
mod |= (m_mouse_state & mask) ? Key::Modifiers::MousePos : (left ? Key::Modifiers::MousePressLeft : Key::Modifiers::MousePressRight);
mod |= (m_mouse_state & mask) ? Key::Modifiers::MousePos : Key::Modifiers::MousePress;
m_mouse_state |= mask;
}
else
{
mod |= left ? Key::Modifiers::MouseReleaseLeft : Key::Modifiers::MouseReleaseRight;
mod |= Key::Modifiers::MouseRelease;
m_mouse_state &= ~mask;
}
return Key{mod, coord};
return Key{mod | Key::to_modifier(button), coord};
};
auto mouse_scroll = [this](Key::Modifiers mod, bool down) -> Key {
@ -752,17 +753,15 @@ Optional<Key> NCursesUI::get_next_key()
const int y = (sgr ? params[2] : next_char() - 32) - 1;
auto coord = encode_coord({y - content_line_offset(), x});
Key::Modifiers mod = parse_mask((b >> 2) & 0x7);
switch (b & 0x43)
switch (auto code = b & 0x43; code)
{
case 0: return mouse_button(mod, coord, true, c == 'm');
case 2: return mouse_button(mod, coord, false, c == 'm');
case 0: case 1: case 2:
return mouse_button(mod, Key::MouseButton{code}, coord, c == 'm');
case 3:
if (sgr)
return {};
if (m_mouse_state & 0x1)
return mouse_button(mod, coord, true, true);
else if (m_mouse_state & 0x2)
return mouse_button(mod, coord, false, true);
else if (int guess = ffs(m_mouse_state) - 1; 0 <= guess and guess < 3)
return mouse_button(mod, Key::MouseButton{guess}, coord, true);
break;
case 64: return mouse_scroll(mod, false);
case 65: return mouse_scroll(mod, true);