From f4e3fbcb2ee3dccafa48274875daaef9dbaa7aeb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 29 Nov 2016 09:45:14 +0530 Subject: [PATCH] Implement rendering of selections --- glfw_constants.py | 4 ++++ kitty/char_grid.py | 56 ++++++++++++++++++++++++++++++++++++++++++++-- kitty/config.py | 2 +- kitty/constants.py | 3 +++ kitty/kitty.conf | 4 ++++ kitty/screen.c | 16 +++++++++++++ kitty/tabs.py | 4 ++-- kitty/window.py | 26 ++++++++++++--------- 8 files changed, 99 insertions(+), 16 deletions(-) diff --git a/glfw_constants.py b/glfw_constants.py index 793f0c4a9..a85792483 100644 --- a/glfw_constants.py +++ b/glfw_constants.py @@ -136,6 +136,10 @@ GLFW_KEY_RIGHT_ALT = 346 GLFW_KEY_RIGHT_SUPER = 347 GLFW_KEY_MENU = 348 GLFW_KEY_LAST = GLFW_KEY_MENU +MODIFIER_KEYS = ( + GLFW_KEY_LEFT_SHIFT, GLFW_KEY_RIGHT_SHIFT, GLFW_KEY_LEFT_ALT, + GLFW_KEY_RIGHT_ALT, GLFW_KEY_LEFT_CONTROL, GLFW_KEY_RIGHT_CONTROL, + GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER) # --- Modifiers --------------------------------------------------------------- diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 2e508cac0..9b20afeeb 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -148,10 +148,30 @@ def color_as_int(val): return val[0] << 16 | val[1] << 8 | val[2] +class Selection: + + __slots__ = tuple('in_progress start_x start_y start_scrolled_by end_x end_y end_scrolled_by'.split()) + + def __init__(self): + self.clear() + + def clear(self): + self.in_progress = False + self.start_x = self.start_y = self.end_x = self.end_y = 0 + self.start_scrolled_by = self.end_scrolled_by = 0 + + def limits(self, scrolled_by): + a = (self.start_x, self.start_y - self.start_scrolled_by + scrolled_by) + b = (self.end_x, self.end_y - self.end_scrolled_by + scrolled_by) + return (a, b) if a <= b else (b, a) + + class CharGrid: def __init__(self, screen, opts): self.buffer_lock = Lock() + self.current_selection = Selection() + self.last_rendered_selection = self.current_selection.limits(0) self.render_buf_is_dirty = True self.render_data = None self.scrolled_by = 0 @@ -170,6 +190,7 @@ class CharGrid: self.opts = opts self.original_bg = opts.background self.original_fg = opts.foreground + self.selection_foreground, self.selection_background = map(color_as_int, (opts.selection_foreground, opts.selection_background)) self.sprite_map_type = self.main_sprite_map = self.scroll_sprite_map = self.render_buf = None def update_position(self, window_geometry): @@ -187,7 +208,9 @@ class CharGrid: self.scroll_sprite_map = self.sprite_map_type() with self.buffer_lock: self.render_buf = self.sprite_map_type() + self.selection_buf = self.sprite_map_type() self.render_buf_is_dirty = True + self.current_selection.clear() def change_colors(self, changes): dirtied = False @@ -215,6 +238,7 @@ class CharGrid: def update_cell_data(self, force_full_refresh=False): sprites = tab_manager().sprites + is_dirty = self.screen.is_dirty() with sprites.lock: cursor_changed, history_line_added_count = self.screen.update_cell_data( sprites.backend, self.color_profile, addressof(self.main_sprite_map), self.default_fg, self.default_bg, force_full_refresh) @@ -226,6 +250,8 @@ class CharGrid: data = self.scroll_sprite_map if self.scrolled_by else self.main_sprite_map with self.buffer_lock: + if is_dirty: + self.current_selection.clear() memmove(self.render_buf, data, sizeof(type(data))) self.render_data = self.screen_geometry self.render_buf_is_dirty = True @@ -233,14 +259,40 @@ class CharGrid: c = self.screen.cursor self.current_cursor = Cursor(c.x, c.y, c.hidden, c.shape, c.color, c.blink) + def cell_for_pos(self, x, y): + return int(x // cell_size.width), int(y // cell_size.height) + + def update_drag(self, is_press, x, y): + with self.buffer_lock: + x, y = self.cell_for_pos(x, y) + if is_press: + self.current_selection.start_x = self.current_selection.end_x = x + self.current_selection.start_y = self.current_selection.end_y = y + self.current_selection.start_scrolled_by = self.current_selection.end_scrolled_by = self.scrolled_by + self.current_selection.in_progress = True + elif self.current_selection.in_progress: + self.current_selection.end_x = x + self.current_selection.end_y = y + self.current_selection.end_scrolled_by = self.scrolled_by + if is_press is False: + self.current_selection.in_progress = False + def prepare_for_render(self, sprites): with self.buffer_lock: sg = self.render_data if sg is None: return - if self.render_buf_is_dirty: - sprites.set_sprite_map(self.render_buf) + buf = self.render_buf + start, end = sel = self.current_selection.limits(self.scrolled_by) + if start != end: + buf = self.selection_buf + if self.render_buf_is_dirty or sel != self.last_rendered_selection: + memmove(buf, self.render_buf, sizeof(type(buf))) + self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], self.selection_foreground, self.selection_background) + if self.render_buf_is_dirty or self.last_rendered_selection != sel: + sprites.set_sprite_map(buf) self.render_buf_is_dirty = False + self.last_rendered_selection = sel return sg def render_cells(self, sg, cell_program, sprites): diff --git a/kitty/config.py b/kitty/config.py index 6901024f6..b149b7ebe 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -47,7 +47,7 @@ type_map = { 'window_border_width': float, } -for name in 'foreground background cursor active_border_color inactive_border_color'.split(): +for name in 'foreground background cursor active_border_color inactive_border_color selection_foreground selection_background'.split(): type_map[name] = lambda x: to_color(x, validate=True) for i in range(16): type_map['color%d' % i] = lambda x: to_color(x, validate=True) diff --git a/kitty/constants.py b/kitty/constants.py index 03384a6b5..c15e4a202 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -40,6 +40,9 @@ class ViewportSize: def __init__(self): self.width = self.height = 1024 + def __repr__(self): + return '(width={}, height={})'.format(self.width, self.height) + def tab_manager(): return tab_manager.manager diff --git a/kitty/kitty.conf b/kitty/kitty.conf index e7eb99826..37b65b3ae 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -4,6 +4,10 @@ term xterm-kitty foreground #dddddd # The background color background #000000 +# The foreground for selections +selection_foreground #000000 +# The background for selections +selection_background #FFFACD # The cursor color cursor #ffffff diff --git a/kitty/screen.c b/kitty/screen.c index 99cac52c9..5ffeedc8f 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1000,6 +1000,21 @@ set_scroll_cell_data(Screen *self, PyObject *args) { } Py_RETURN_NONE; } + +static PyObject* +apply_selection(Screen *self, PyObject *args) { + unsigned int fg, bg, startx, endx, starty, endy; + PyObject *l; + if (!PyArg_ParseTuple(args, "O!IIIIII", &PyLong_Type, &l, &startx, &starty, &endx, &endy, &fg, &bg)) return NULL; + if (startx >= self->columns || starty >= self->lines || endx >= self->columns || endy >= self->lines) { Py_RETURN_NONE; } + unsigned int *data = PyLong_AsVoidPtr(l), offset; + for(unsigned int i = starty * self->columns + startx; i <= endy * self->columns + endx; i++) { + offset = DATA_CELL_SIZE * i; + data[offset + 3] = fg; + data[offset + 4] = bg; + } + Py_RETURN_NONE; +} static PyObject* is_dirty(Screen *self) { PyObject *ans = self->change_tracker->dirty ? Py_True : Py_False; @@ -1068,6 +1083,7 @@ static PyMethodDef methods[] = { MND(mark_as_dirty, METH_NOARGS) MND(resize, METH_VARARGS) MND(set_scroll_cell_data, METH_VARARGS) + MND(apply_selection, METH_VARARGS) MND(in_bracketed_paste_mode, METH_NOARGS) MND(focus_tracking_enabled, METH_NOARGS) MND(mouse_button_tracking_enabled, METH_NOARGS) diff --git a/kitty/tabs.py b/kitty/tabs.py index 5fe01047d..226e9bbc8 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -302,7 +302,7 @@ class TabManager(Thread): if not passthrough: return if window: - if window.char_grid.scrolled_by: + if window.char_grid.scrolled_by and key not in glfw_constants.MODIFIER_KEYS: window.scroll_end() data = interpret_key_event(key, scancode, mods) if data: @@ -323,7 +323,7 @@ class TabManager(Thread): if w is not None: if button == glfw_constants.GLFW_MOUSE_BUTTON_1 and w is not self.active_window: pass # TODO: Switch focus to this window - w.on_mouse_button(button, action, mods) + w.on_mouse_button(window, button, action, mods) def on_mouse_move(self, window, xpos, ypos): w = self.window_for_pos(*glfw.glfwGetCursorPos(window)) diff --git a/kitty/window.py b/kitty/window.py index 5fc170155..ecbebfea2 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -10,7 +10,7 @@ from functools import partial import glfw import glfw_constants from .char_grid import CharGrid -from .constants import wakeup, tab_manager, appname, WindowGeometry +from .constants import wakeup, tab_manager, appname, WindowGeometry, queue_action from .fast_data_types import ( BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, read_bytes_dump, read_bytes ) @@ -114,15 +114,19 @@ class Window: def request_capabilities(self, q): self.write_to_child(get_capabilities(q)) - def on_mouse_button(self, button, action, mods): - # ignore_mouse_mode = mods == glfw_constants.GLFW_MOD_SHIFT + def on_mouse_button(self, window, button, action, mods): + ignore_mouse_mode = mods == glfw_constants.GLFW_MOD_SHIFT or not self.screen.mouse_button_tracking_enabled() + if button == glfw_constants.GLFW_MOUSE_BUTTON_1 and ignore_mouse_mode: + x, y = glfw.glfwGetCursorPos(window) + self.char_grid.update_drag(action == glfw_constants.GLFW_PRESS, max(0, x - self.geometry.left), max(0, y - self.geometry.top)) if action == glfw_constants.GLFW_RELEASE: if button == glfw_constants.GLFW_MOUSE_BUTTON_MIDDLE: self.paste_from_selection() return - def on_mouse_move(self, xpos, ypos): - pass + def on_mouse_move(self, x, y): + if self.char_grid.current_selection.in_progress: + self.char_grid.update_drag(None, max(0, x - self.geometry.left), max(0, y - self.geometry.top)) def on_mouse_scroll(self, x, y): pass @@ -146,22 +150,22 @@ class Window: self.paste(text) def scroll_line_up(self): - self.queue_action(self.char_grid.scroll, 'line', True) + queue_action(self.char_grid.scroll, 'line', True) def scroll_line_down(self): - self.queue_action(self.char_grid.scroll, 'line', False) + queue_action(self.char_grid.scroll, 'line', False) def scroll_page_up(self): - self.queue_action(self.char_grid.scroll, 'page', True) + queue_action(self.char_grid.scroll, 'page', True) def scroll_page_down(self): - self.queue_action(self.char_grid.scroll, 'page', False) + queue_action(self.char_grid.scroll, 'page', False) def scroll_home(self): - self.queue_action(self.char_grid.scroll, 'full', True) + queue_action(self.char_grid.scroll, 'full', True) def scroll_end(self): - self.queue_action(self.char_grid.scroll, 'full', False) + queue_action(self.char_grid.scroll, 'full', False) # }}} def dump_commands(self, *a):