Start work on rendering window borders

This commit is contained in:
Kovid Goyal 2016-11-28 13:38:13 +05:30
parent 655ec36091
commit b1e39dac02
7 changed files with 228 additions and 29 deletions

116
kitty/borders.py Normal file
View File

@ -0,0 +1,116 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import ctypes
from .constants import viewport_size
from .fast_data_types import glUniform3fv, GL_TRIANGLE_STRIP, glDrawArrays
from .layout import available_height
from .utils import get_dpi
from .shaders import ShaderProgram
def as_color(c):
return c[0] / 255, c[1] / 255, c[2] / 255
def to_opengl(x, y):
return -1 + 2 * x / viewport_size.width, 1 - 2 * y / viewport_size.height
def as_rect(left, top, right, bottom, color=0):
for (x, y) in ((left, bottom), (right, bottom), (right, top), (left, top)):
x, y = to_opengl(x, y)
yield x
yield y
yield color
class BordersProgram(ShaderProgram):
def __init__(self):
ShaderProgram.__init__(self, '''\
uniform vec3 colors[3];
in vec3 vertex;
out vec3 color;
void main() {
gl_Position = vec4(vertex[0], vertex[1], 0, 1);
color = colors[uint(vertex[2])];
}
''', '''\
in vec3 color;
out vec4 final_color;
void main() {
final_color = vec4(color, 1);
}
''')
self.add_vertex_array('rects')
def send_data(self, data):
self.send_vertex_data('rects', data)
def set_colors(self, color_buf):
glUniform3fv(self.uniform_location('colors'), 3, ctypes.addressof(color_buf))
class Borders:
def __init__(self, opts):
self.is_dirty = False
self.can_render = False
dpix, dpiy = get_dpi()['logical']
dpi = (dpix + dpiy) / 2
self.border_width = round(opts.window_border_width * dpi / 72)
self.color_buf = (ctypes.c_float * 9)(
*as_color(opts.background),
*as_color(opts.active_border_color),
*as_color(opts.inactive_border_color)
)
def __call__(self, windows, active_window, draw_window_borders=True):
self.can_render = True
self.is_dirty = True
vw, vh = viewport_size.width, available_height()
if windows:
left_edge = min(w.geometry.left for w in windows)
right_edge = max(w.geometry.right for w in windows)
top_edge = min(w.geometry.top for w in windows)
bottom_edge = max(w.geometry.bottom for w in windows)
else:
left_edge = top_edge = 0
right_edge = vw
bottom_edge = vh
rects = []
if left_edge > 0:
rects.extend(as_rect(0, 0, left_edge, vh))
if top_edge > 0:
rects.extend(as_rect(0, 0, vw, top_edge))
if right_edge < vw:
rects.extend(as_rect(right_edge, 0, vw, vh))
if bottom_edge < vh:
rects.extend(as_rect(0, bottom_edge, vw, vh))
if draw_window_borders and self.border_width > 0:
bw = self.border_width
for w in windows:
g = w.geometry
color = 1 if w is active_window else 2
rects.extend(as_rect(g.left - bw, g.top - bw, g.left, g.bottom + bw, color))
rects.extend(as_rect(g.left - bw, g.top - bw, g.right + bw, g.top, color))
rects.extend(as_rect(g.right, g.top - bw, g.right + bw, g.bottom + bw, color))
rects.extend(as_rect(g.left - bw, g.bottom, g.right + bw, g.bottom + bw, color))
self.data = (ctypes.c_float * len(rects))()
for i, x in enumerate(rects):
self.data[i] = x
def render(self, program):
if not self.can_render:
return
with program:
if self.is_dirty:
program.send_data(self.data)
program.set_colors(self.color_buf)
self.is_dirty = False
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)

View File

@ -44,9 +44,10 @@ type_map = {
'cursor_blink': to_bool,
'cursor_opacity': to_opacity,
'repaint_delay': int,
'window_border_width': float,
}
for name in 'foreground background cursor'.split():
for name in 'foreground background cursor active_border_color inactive_border_color'.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)

View File

@ -101,6 +101,17 @@ Uniform4f(PyObject UNUSED *self, PyObject *args) {
CHECK_ERROR;
Py_RETURN_NONE;
}
static PyObject*
Uniform3fv(PyObject UNUSED *self, PyObject *args) {
int location;
unsigned int count;
PyObject *l;
if (!PyArg_ParseTuple(args, "iIO!", &location, &count, &PyLong_Type, &l)) return NULL;
glUniform3fv(location, count, PyLong_AsVoidPtr(l));
CHECK_ERROR;
Py_RETURN_NONE;
}
// }}}
@ -152,6 +163,17 @@ DrawArrays(PyObject UNUSED *self, PyObject *args) {
Py_RETURN_NONE;
}
static PyObject*
MultiDrawArrays(PyObject UNUSED *self, PyObject *args) {
int mode;
unsigned int draw_count;
PyObject *a, *b;
if (!PyArg_ParseTuple(args, "iO!O!I", &mode, &PyLong_Type, &a, &PyLong_Type, &b, &draw_count)) return NULL;
glMultiDrawArrays(mode, PyLong_AsVoidPtr(a), PyLong_AsVoidPtr(b), draw_count);
CHECK_ERROR;
Py_RETURN_NONE;
}
static PyObject*
DrawArraysInstanced(PyObject UNUSED *self, PyObject *args) {
int mode, first;
@ -431,14 +453,14 @@ TexSubImage3D(PyObject UNUSED *self, PyObject *args) {
}
static PyObject*
BufferData(PyObject UNUSED *self, PyObject *args) {
int target, usage;
unsigned long size;
NamedBufferData(PyObject UNUSED *self, PyObject *args) {
int usage;
unsigned long size, target;
PyObject *address;
if (!PyArg_ParseTuple(args, "ikO!i", &target, &size, &PyLong_Type, &address, &usage)) return NULL;
if (!PyArg_ParseTuple(args, "kkO!i", &target, &size, &PyLong_Type, &address, &usage)) return NULL;
void *data = PyLong_AsVoidPtr(address);
if (data == NULL) { PyErr_SetString(PyExc_TypeError, "Not a valid data pointer"); return NULL; }
glBufferData(target, size, data, usage);
glNamedBufferData(target, size, data, usage);
CHECK_ERROR;
Py_RETURN_NONE;
}
@ -520,13 +542,34 @@ Disable(PyObject UNUSED *self, PyObject *val) {
Py_RETURN_NONE;
}
static PyObject*
EnableVertexAttribArray(PyObject UNUSED *self, PyObject *val) {
long x = PyLong_AsLong(val);
glEnableVertexAttribArray(x);
CHECK_ERROR;
Py_RETURN_NONE;
}
static PyObject*
VertexAttribPointer(PyObject UNUSED *self, PyObject *args) {
unsigned int index, stride;
int type=GL_FLOAT, normalized, size;
void *offset;
PyObject *l;
if (!PyArg_ParseTuple(args, "IiipIO!", &index, &size, &type, &normalized, &stride, &PyLong_Type, &l)) return NULL;
offset = PyLong_AsVoidPtr(l);
glVertexAttribPointer(index, size, type, normalized, stride, offset);
CHECK_ERROR;
Py_RETURN_NONE;
}
int add_module_gl_constants(PyObject *module) {
#define GLC(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return 0; }
GLC(GL_VERSION);
GLC(GL_VENDOR);
GLC(GL_SHADING_LANGUAGE_VERSION);
GLC(GL_RENDERER);
GLC(GL_TRIANGLE_FAN);
GLC(GL_TRIANGLE_FAN); GLC(GL_TRIANGLE_STRIP);
GLC(GL_COLOR_BUFFER_BIT);
GLC(GL_VERTEX_SHADER);
GLC(GL_FRAGMENT_SHADER);
@ -543,9 +586,9 @@ int add_module_gl_constants(PyObject *module) {
GLC(GL_TEXTURE_WRAP_S); GLC(GL_TEXTURE_WRAP_T);
GLC(GL_UNPACK_ALIGNMENT);
GLC(GL_R8); GLC(GL_RED); GLC(GL_UNSIGNED_BYTE); GLC(GL_RGB32UI);
GLC(GL_TEXTURE_BUFFER); GLC(GL_STATIC_DRAW);
GLC(GL_TEXTURE_BUFFER); GLC(GL_STATIC_DRAW); GLC(GL_STREAM_DRAW);
GLC(GL_SRC_ALPHA); GLC(GL_ONE_MINUS_SRC_ALPHA);
GLC(GL_BLEND);
GLC(GL_BLEND); GLC(GL_FLOAT); GLC(GL_ARRAY_BUFFER);
return 1;
}
@ -562,6 +605,7 @@ int add_module_gl_constants(PyObject *module) {
METH(Uniform1i, METH_VARARGS) \
METH(Uniform2f, METH_VARARGS) \
METH(Uniform4f, METH_VARARGS) \
METH(Uniform3fv, METH_VARARGS) \
METH(GetUniformLocation, METH_VARARGS) \
METH(GetAttribLocation, METH_VARARGS) \
METH(ShaderSource, METH_VARARGS) \
@ -582,11 +626,14 @@ int add_module_gl_constants(PyObject *module) {
METH(DeleteShader, METH_O) \
METH(Enable, METH_O) \
METH(Disable, METH_O) \
METH(EnableVertexAttribArray, METH_O) \
METH(VertexAttribPointer, METH_VARARGS) \
METH(GetProgramInfoLog, METH_O) \
METH(GetShaderInfoLog, METH_O) \
METH(ActiveTexture, METH_O) \
METH(DrawArraysInstanced, METH_VARARGS) \
METH(DrawArrays, METH_VARARGS) \
METH(MultiDrawArrays, METH_VARARGS) \
METH(CreateProgram, METH_NOARGS) \
METH(AttachShader, METH_VARARGS) \
METH(BindTexture, METH_VARARGS) \
@ -597,6 +644,6 @@ int add_module_gl_constants(PyObject *module) {
METH(TexStorage3D, METH_VARARGS) \
METH(CopyImageSubData, METH_VARARGS) \
METH(TexSubImage3D, METH_VARARGS) \
METH(BufferData, METH_VARARGS) \
METH(NamedBufferData, METH_VARARGS) \
METH(BlendFunc, METH_VARARGS) \

View File

@ -31,6 +31,15 @@ scrollback_lines 2000
# that sufficient for most uses.
repaint_delay 20
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
window_border_width 2
# The color for the border of the active window
active_border_color #00ff00
# The color for the border of inactive windows
inactive_border_color #cccccc
# black
color0 #000000
color8 #4d4d4d

View File

@ -2,7 +2,11 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from .constants import WindowGeometry, viewport_size, cell_size
from .constants import WindowGeometry, viewport_size, cell_size, tab_manager
def available_height():
return viewport_size.height - tab_manager().current_tab_bar_height
def layout_dimension(length, cell_length, number_of_windows=1, border_length=0):
@ -25,6 +29,11 @@ def layout_dimension(length, cell_length, number_of_windows=1, border_length=0):
class Layout:
name = None
needs_window_borders = True
def __init__(self, opts, border_width):
self.opts = opts
self.border_width = border_width
def add_window(self, windows, window):
raise NotImplementedError()
@ -36,9 +45,10 @@ class Layout:
raise NotImplementedError()
class Stack:
class Stack(Layout):
name = 'stack'
needs_window_borders = False
def add_window(self, windows, window):
windows.appendleft(window)
@ -50,7 +60,7 @@ class Stack:
def __call__(self, windows):
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width))
ystart, ynum = next(layout_dimension(viewport_size.height, cell_size.height))
ystart, ynum = next(layout_dimension(available_height(), cell_size.height))
wg = WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_size.width * xnum, bottom=ystart + cell_size.height * ynum)
for i, w in enumerate(windows):
w.is_visible_in_layout = i == 0

View File

@ -14,14 +14,16 @@ from .fast_data_types import (
glCreateShader, glShaderSource, glCompileShader, glGetShaderiv,
GL_COMPILE_STATUS, glGetShaderInfoLog, glGetUniformLocation,
glGetAttribLocation, glUseProgram, glBindVertexArray, GL_TEXTURE0,
GL_TEXTURE1, glGetIntegerv, GL_MAX_ARRAY_TEXTURE_LAYERS, glBufferData,
GL_TEXTURE1, glGetIntegerv, GL_MAX_ARRAY_TEXTURE_LAYERS, glNamedBufferData,
GL_MAX_TEXTURE_SIZE, glDeleteTexture, GL_TEXTURE_2D_ARRAY, glGenTextures,
glBindTexture, glTexParameteri, GL_CLAMP_TO_EDGE, glDeleteBuffer,
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S,
GL_NEAREST, GL_TEXTURE_WRAP_T, glGenBuffers, GL_R8, GL_RED,
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_STATIC_DRAW, GL_TEXTURE_BUFFER,
GL_RGB32UI, glBindBuffer, glPixelStorei, glTexBuffer, glActiveTexture,
glTexStorage3D, glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD, SpriteMap
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_STATIC_DRAW, GL_STREAM_DRAW,
GL_TEXTURE_BUFFER, GL_RGB32UI, GL_FLOAT, GL_ARRAY_BUFFER, glBindBuffer,
glPixelStorei, glTexBuffer, glActiveTexture, glTexStorage3D,
glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD, SpriteMap,
glEnableVertexAttribArray, glVertexAttribPointer
)
GL_VERSION = (3, 3)
@ -143,11 +145,8 @@ class Sprites:
self.buffer_texture_id = glGenTextures(1)
self.buffer_texture_unit = GL_TEXTURE1
def set_sprite_map(self, data):
tgt = GL_TEXTURE_BUFFER
glBindBuffer(tgt, self.buffer_id)
glBufferData(tgt, sizeof(data), addressof(data), GL_STATIC_DRAW)
glBindBuffer(tgt, 0)
def set_sprite_map(self, data, usage=GL_STREAM_DRAW):
glNamedBufferData(self.buffer_id, sizeof(data), addressof(data), usage)
def __enter__(self):
self.ensure_state()
@ -175,7 +174,6 @@ class ShaderProgram:
"""
self.program_id = glCreateProgram()
self.is_active = False
vs_id = self.add_shader(vertex, GL_VERTEX_SHADER)
glAttachShader(self.program_id, vs_id)
@ -192,6 +190,22 @@ class ShaderProgram:
glDeleteShader(vs_id)
glDeleteShader(frag_id)
self.vao_id = glGenVertexArrays(1)
self.buffers = {}
def add_vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0):
glBindVertexArray(self.vao_id)
if name not in self.buffers:
self.buffers[name] = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.buffers[name])
aid = self.attribute_location(name)
glEnableVertexAttribArray(aid)
glVertexAttribPointer(aid, size, dtype, normalized, stride, offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
def send_vertex_data(self, name, data, usage=GL_STATIC_DRAW):
bufid = self.buffers[name]
glNamedBufferData(bufid, sizeof(data), addressof(data), usage)
def __hash__(self) -> int:
return self.program_id
@ -230,9 +244,7 @@ class ShaderProgram:
def __enter__(self):
glUseProgram(self.program_id)
glBindVertexArray(self.vao_id)
self.is_active = True
def __exit__(self, *args):
glUseProgram(0)
glBindVertexArray(0)
self.is_active = False

View File

@ -20,6 +20,7 @@ from .child import Child
from .constants import viewport_size, shell_path, appname, set_tab_manager, tab_manager, wakeup, cell_size
from .fast_data_types import glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, glClearColor, glClear, GL_COLOR_BUFFER_BIT
from .fonts import set_font_family
from .borders import Borders, BordersProgram
from .char_grid import cursor_shader, cell_shader
from .keys import interpret_text_event, interpret_key_event, get_shortcut
from .layout import Stack
@ -36,7 +37,8 @@ class Tab:
def __init__(self, opts, args):
self.opts, self.args = opts, args
self.windows = deque()
self.current_layout = Stack()
self.borders = Borders(opts)
self.current_layout = Stack(opts, self.borders.border_width)
@property
def is_visible(self):
@ -58,6 +60,7 @@ class Tab:
def relayout(self):
if self.windows:
self.current_layout(self.windows)
self.borders(self.windows, self.active_window, self.current_layout.needs_window_borders)
def launch_child(self, use_shell=False):
if use_shell:
@ -92,8 +95,7 @@ class Tab:
del self.windows
def render(self):
# TODO: Render window borders and clear the extra pixels
pass
self.borders.render(tab_manager().borders_program)
class TabManager(Thread):
@ -103,6 +105,7 @@ class TabManager(Thread):
def __init__(self, glfw_window, opts, args):
Thread.__init__(self, name='ChildMonitor')
self.glfw_window_title = None
self.current_tab_bar_height = 0
self.action_queue = Queue()
self.pending_resize = None
self.resize_gl_viewport = False
@ -128,12 +131,13 @@ class TabManager(Thread):
self.sprites = Sprites()
self.cell_program = ShaderProgram(*cell_shader)
self.cursor_program = ShaderProgram(*cursor_shader)
self.borders_program = BordersProgram()
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
self.sprites.do_layout(cell_size.width, cell_size.height)
self.queue_action(self.active_tab.new_window, False)
bg = opts.background
glClearColor(bg.red / 255, bg.green / 255, bg.blue / 255, 1)
glClear(GL_COLOR_BUFFER_BIT)
self.queue_action(self.active_tab.new_window, False)
def signal_received(self):
try: