implement rendering of currently selected keyboard layout

main
Ilya Zhuravlev 2020-12-20 22:13:16 -05:00
parent 6add0a8d70
commit 5af3798445
6 changed files with 102 additions and 38 deletions

View File

@ -118,6 +118,13 @@ class Keyboard:
self.rowcol[(row, col)] = True
self.keys.append(key)
# bottom right corner determines layout index and option in this layout
key.layout_index = -1
key.layout_option = -1
if key.labels[8]:
idx, opt = key.labels[8].split(",")
key.layout_index, key.layout_option = int(idx), int(opt)
def reload_keymap(self):
""" Load current key mapping from the keyboard """

View File

@ -14,8 +14,8 @@ class KeyboardContainer(QWidget):
number_layers_changed = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
def __init__(self, layout_editor):
super().__init__()
self.layout_layers = QHBoxLayout()
layer_label = QLabel(tr("KeyboardContainer", "Layer"))
@ -26,7 +26,7 @@ class KeyboardContainer(QWidget):
layout_labels_container.addStretch()
# contains the actual keyboard
self.container = KeyboardWidget()
self.container = KeyboardWidget(layout_editor)
self.container.clicked.connect(self.on_key_clicked)
layout = QVBoxLayout()
@ -35,7 +35,6 @@ class KeyboardContainer(QWidget):
layout.setAlignment(self.container, Qt.AlignHCenter)
self.setLayout(layout)
self.keys = []
self.layer_labels = []
self.keyboard = None
self.current_layer = 0
@ -59,11 +58,6 @@ class KeyboardContainer(QWidget):
def rebuild(self, keyboard):
self.keyboard = keyboard
# delete current layout
for key in self.keys:
key.deleteLater()
self.keys = []
# get number of layers
self.rebuild_layers()
@ -79,7 +73,7 @@ class KeyboardContainer(QWidget):
label.setStyleSheet(LAYER_BTN_STYLE)
self.layer_labels[self.current_layer].setStyleSheet(ACTIVE_LAYER_BTN_STYLE)
for widget in self.container.keys:
for widget in self.container.widgets:
if widget.desc.row is not None:
code = self.keyboard.layout[(self.current_layer, widget.desc.row, widget.desc.col)]
else:

View File

@ -1,3 +1,5 @@
from collections import defaultdict
from PyQt5.QtGui import QPainter, QColor, QPainterPath, QTransform, QBrush, QPolygonF
from PyQt5.QtWidgets import QWidget, QToolTip
from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent, QRectF
@ -7,7 +9,7 @@ from constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, KEYBOARD_WIDGET_PADDIN
class KeyWidget:
def __init__(self, desc):
def __init__(self, desc, shift_x=0, shift_y=0):
self.desc = desc
self.text = ""
self.mask_text = ""
@ -17,8 +19,8 @@ class KeyWidget:
self.rotation_y = (KEY_HEIGHT + KEY_SPACING) * desc.rotation_y
self.rotation_angle = desc.rotation_angle
self.x = (KEY_WIDTH + KEY_SPACING) * desc.x
self.y = (KEY_HEIGHT + KEY_SPACING) * desc.y
self.x = shift_x + (KEY_WIDTH + KEY_SPACING) * desc.x
self.y = shift_y + (KEY_HEIGHT + KEY_SPACING) * desc.y
self.w = (KEY_WIDTH + KEY_SPACING) * desc.width - KEY_SPACING
self.h = (KEY_HEIGHT + KEY_SPACING) * desc.height - KEY_SPACING
@ -113,28 +115,81 @@ class KeyboardWidget(QWidget):
clicked = pyqtSignal()
def __init__(self):
def __init__(self, layout_editor):
super().__init__()
self.setMouseTracking(True)
self.keys = []
self.layout_editor = layout_editor
# widgets common for all layouts
self.common_widgets = []
# layout-specific widgets
self.widgets_for_layout = defaultdict(lambda: defaultdict(list))
# widgets in current layout
self.widgets = []
self.width = self.height = 0
self.active_key = None
self.active_mask = False
def set_keys(self, keys, encoders):
self.keys = []
for key in keys:
self.keys.append(KeyWidget(key))
for key in encoders:
self.keys.append(EncoderWidget(key))
self.calculate_size()
self.update()
self.common_widgets = []
self.widgets_for_layout = defaultdict(lambda: defaultdict(list))
def calculate_size(self):
self.add_keys([(x, KeyWidget) for x in keys] + [(x, EncoderWidget) for x in encoders])
self.update_layout()
def add_keys(self, keys):
top_x = top_y = 1e6
# find the global top-left position, all the keys will be shifted to the left/up by that position
for key, cls in keys:
obj = cls(key)
top_x = min(top_x, obj.x)
top_y = min(top_y, obj.y)
# obtain common widgets, that is, ones which are always displayed and require no extra transforms
for key, cls in keys:
if key.layout_index == -1:
self.common_widgets.append(cls(key, -top_x, -top_y))
# top-left position for specific layout
layout_x = defaultdict(lambda: defaultdict(lambda: 1e6))
layout_y = defaultdict(lambda: defaultdict(lambda: 1e6))
# determine top-left position for every layout option
for key, cls in keys:
if key.layout_index != -1:
obj = cls(key)
idx, opt = key.layout_index, key.layout_option
layout_x[idx][opt] = min(layout_x[idx][opt], obj.x)
layout_y[idx][opt] = min(layout_y[idx][opt], obj.y)
# obtain widgets for all layout options now that we know how to shift them
for key, cls in keys:
if key.layout_index != -1:
idx, opt = key.layout_index, key.layout_option
shift_x = layout_x[idx][opt] - layout_x[idx][0]
shift_y = layout_y[idx][opt] - layout_y[idx][0]
obj = cls(key, -shift_x - top_x, -shift_y - top_y)
self.widgets_for_layout[idx][opt].append(obj)
def update_layout(self):
""" Updates self.widgets for the currently active layout """
# determine widgets for current layout
self.widgets = []
self.widgets += self.common_widgets
for idx in self.widgets_for_layout.keys():
option = self.layout_editor.get_choice(idx)
print("index {} option {}".format(idx, option))
self.widgets += self.widgets_for_layout[idx][option]
# determine maximum width and height of container
max_w = max_h = 0
for key in self.keys:
for key in self.widgets:
p = key.polygon.boundingRect().bottomRight()
max_w = max(max_w, p.x())
max_h = max(max_h, p.y())
@ -142,6 +197,8 @@ class KeyboardWidget(QWidget):
self.width = max_w + 2 * KEYBOARD_WIDGET_PADDING
self.height = max_h + 2 * KEYBOARD_WIDGET_PADDING
self.update()
def paintEvent(self, event):
qp = QPainter()
qp.begin(self)
@ -171,7 +228,7 @@ class KeyboardWidget(QWidget):
mask_font = qp.font()
mask_font.setPointSize(mask_font.pointSize() * 0.8)
for idx, key in enumerate(self.keys):
for idx, key in enumerate(self.widgets):
qp.save()
qp.translate(key.rotation_x, key.rotation_y)
qp.rotate(key.rotation_angle)
@ -210,7 +267,7 @@ class KeyboardWidget(QWidget):
def hit_test(self, pos):
""" Returns key, hit_masked_part """
for key in self.keys:
for key in self.widgets:
if key.masked and key.mask_polygon.containsPoint(pos, Qt.OddEvenFill):
return key, True
if key.polygon.containsPoint(pos, Qt.OddEvenFill):
@ -226,9 +283,8 @@ class KeyboardWidget(QWidget):
def select_next(self):
""" Selects next key based on their order in the keymap """
# TODO: this almost certainly needs changes after multilayout support lands
keys_looped = self.keys + [self.keys[0]]
keys_looped = self.widgets + [self.widgets[0]]
for x, key in enumerate(keys_looped):
if key == self.active_key:
self.active_key = keys_looped[x + 1]

View File

@ -9,10 +9,10 @@ from vial_device import VialKeyboard
class KeymapEditor(BasicEditor):
def __init__(self, parent=None):
super().__init__(parent)
def __init__(self, layout_editor):
super().__init__()
self.keyboard_container = KeyboardContainer()
self.keyboard_container = KeyboardContainer(layout_editor)
self.keyboard_container.number_layers_changed.connect(self.on_number_layers_changed)
self.tabbed_keycodes = TabbedKeycodes()

View File

@ -8,7 +8,7 @@ from vial_device import VialKeyboard
class BooleanChoice:
def __init__(self, container, label):
self.choice = 0
self.choice = False
self.widget_label = QLabel(label)
self.widget_checkbox = QCheckBox()
@ -22,13 +22,14 @@ class BooleanChoice:
self.widget_checkbox.deleteLater()
def pack(self):
return str(self.choice)
return str(int(self.choice))
def unpack(self, value):
self.change(int(value))
def change(self, value):
self.widget_checkbox.setChecked(bool(value))
self.choice = bool(value)
self.widget_checkbox.setChecked(self.choice)
class SelectChoice:
@ -111,3 +112,9 @@ class LayoutEditor(BasicEditor):
sz = len(choice.pack())
choice.unpack(value[-sz:])
value = value[:-sz]
def get_choice(self, index):
return int(self.choices[index].pack(), 2)
def set_choice(self, index, value):
self.choices[index].change(value)

View File

@ -33,8 +33,8 @@ class MainWindow(QMainWindow):
layout_combobox.addWidget(self.combobox_devices)
layout_combobox.addWidget(self.btn_refresh_devices)
self.keymap_editor = KeymapEditor()
self.layout_editor = LayoutEditor()
self.keymap_editor = KeymapEditor(self.layout_editor)
self.firmware_flasher = FirmwareFlasher(self)
self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"),
@ -116,8 +116,8 @@ class MainWindow(QMainWindow):
if self.current_device is not None:
self.current_device.open(self.sideload_json if self.current_device.sideload else None)
for editor, lbl in self.editors:
editor.rebuild(self.current_device)
for e in [self.layout_editor, self.keymap_editor, self.firmware_flasher]:
e.rebuild(self.current_device)
self.refresh_tabs()