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.rowcol[(row, col)] = True
self.keys.append(key) 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): def reload_keymap(self):
""" Load current key mapping from the keyboard """ """ Load current key mapping from the keyboard """

View File

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

View File

@ -1,3 +1,5 @@
from collections import defaultdict
from PyQt5.QtGui import QPainter, QColor, QPainterPath, QTransform, QBrush, QPolygonF from PyQt5.QtGui import QPainter, QColor, QPainterPath, QTransform, QBrush, QPolygonF
from PyQt5.QtWidgets import QWidget, QToolTip from PyQt5.QtWidgets import QWidget, QToolTip
from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent, QRectF 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: class KeyWidget:
def __init__(self, desc): def __init__(self, desc, shift_x=0, shift_y=0):
self.desc = desc self.desc = desc
self.text = "" self.text = ""
self.mask_text = "" self.mask_text = ""
@ -17,8 +19,8 @@ class KeyWidget:
self.rotation_y = (KEY_HEIGHT + KEY_SPACING) * desc.rotation_y self.rotation_y = (KEY_HEIGHT + KEY_SPACING) * desc.rotation_y
self.rotation_angle = desc.rotation_angle self.rotation_angle = desc.rotation_angle
self.x = (KEY_WIDTH + KEY_SPACING) * desc.x self.x = shift_x + (KEY_WIDTH + KEY_SPACING) * desc.x
self.y = (KEY_HEIGHT + KEY_SPACING) * desc.y self.y = shift_y + (KEY_HEIGHT + KEY_SPACING) * desc.y
self.w = (KEY_WIDTH + KEY_SPACING) * desc.width - KEY_SPACING self.w = (KEY_WIDTH + KEY_SPACING) * desc.width - KEY_SPACING
self.h = (KEY_HEIGHT + KEY_SPACING) * desc.height - KEY_SPACING self.h = (KEY_HEIGHT + KEY_SPACING) * desc.height - KEY_SPACING
@ -113,28 +115,81 @@ class KeyboardWidget(QWidget):
clicked = pyqtSignal() clicked = pyqtSignal()
def __init__(self): def __init__(self, layout_editor):
super().__init__() super().__init__()
self.setMouseTracking(True) 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.width = self.height = 0
self.active_key = None self.active_key = None
self.active_mask = False self.active_mask = False
def set_keys(self, keys, encoders): def set_keys(self, keys, encoders):
self.keys = [] self.common_widgets = []
for key in keys: self.widgets_for_layout = defaultdict(lambda: defaultdict(list))
self.keys.append(KeyWidget(key))
for key in encoders:
self.keys.append(EncoderWidget(key))
self.calculate_size()
self.update()
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 max_w = max_h = 0
for key in self.widgets:
for key in self.keys:
p = key.polygon.boundingRect().bottomRight() p = key.polygon.boundingRect().bottomRight()
max_w = max(max_w, p.x()) max_w = max(max_w, p.x())
max_h = max(max_h, p.y()) max_h = max(max_h, p.y())
@ -142,6 +197,8 @@ class KeyboardWidget(QWidget):
self.width = max_w + 2 * KEYBOARD_WIDGET_PADDING self.width = max_w + 2 * KEYBOARD_WIDGET_PADDING
self.height = max_h + 2 * KEYBOARD_WIDGET_PADDING self.height = max_h + 2 * KEYBOARD_WIDGET_PADDING
self.update()
def paintEvent(self, event): def paintEvent(self, event):
qp = QPainter() qp = QPainter()
qp.begin(self) qp.begin(self)
@ -171,7 +228,7 @@ class KeyboardWidget(QWidget):
mask_font = qp.font() mask_font = qp.font()
mask_font.setPointSize(mask_font.pointSize() * 0.8) 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.save()
qp.translate(key.rotation_x, key.rotation_y) qp.translate(key.rotation_x, key.rotation_y)
qp.rotate(key.rotation_angle) qp.rotate(key.rotation_angle)
@ -210,7 +267,7 @@ class KeyboardWidget(QWidget):
def hit_test(self, pos): def hit_test(self, pos):
""" Returns key, hit_masked_part """ """ 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): if key.masked and key.mask_polygon.containsPoint(pos, Qt.OddEvenFill):
return key, True return key, True
if key.polygon.containsPoint(pos, Qt.OddEvenFill): if key.polygon.containsPoint(pos, Qt.OddEvenFill):
@ -226,9 +283,8 @@ class KeyboardWidget(QWidget):
def select_next(self): def select_next(self):
""" Selects next key based on their order in the keymap """ """ 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): for x, key in enumerate(keys_looped):
if key == self.active_key: if key == self.active_key:
self.active_key = keys_looped[x + 1] self.active_key = keys_looped[x + 1]

View File

@ -9,10 +9,10 @@ from vial_device import VialKeyboard
class KeymapEditor(BasicEditor): class KeymapEditor(BasicEditor):
def __init__(self, parent=None): def __init__(self, layout_editor):
super().__init__(parent) 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.keyboard_container.number_layers_changed.connect(self.on_number_layers_changed)
self.tabbed_keycodes = TabbedKeycodes() self.tabbed_keycodes = TabbedKeycodes()

View File

@ -8,7 +8,7 @@ from vial_device import VialKeyboard
class BooleanChoice: class BooleanChoice:
def __init__(self, container, label): def __init__(self, container, label):
self.choice = 0 self.choice = False
self.widget_label = QLabel(label) self.widget_label = QLabel(label)
self.widget_checkbox = QCheckBox() self.widget_checkbox = QCheckBox()
@ -22,13 +22,14 @@ class BooleanChoice:
self.widget_checkbox.deleteLater() self.widget_checkbox.deleteLater()
def pack(self): def pack(self):
return str(self.choice) return str(int(self.choice))
def unpack(self, value): def unpack(self, value):
self.change(int(value)) self.change(int(value))
def change(self, value): def change(self, value):
self.widget_checkbox.setChecked(bool(value)) self.choice = bool(value)
self.widget_checkbox.setChecked(self.choice)
class SelectChoice: class SelectChoice:
@ -111,3 +112,9 @@ class LayoutEditor(BasicEditor):
sz = len(choice.pack()) sz = len(choice.pack())
choice.unpack(value[-sz:]) choice.unpack(value[-sz:])
value = 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.combobox_devices)
layout_combobox.addWidget(self.btn_refresh_devices) layout_combobox.addWidget(self.btn_refresh_devices)
self.keymap_editor = KeymapEditor()
self.layout_editor = LayoutEditor() self.layout_editor = LayoutEditor()
self.keymap_editor = KeymapEditor(self.layout_editor)
self.firmware_flasher = FirmwareFlasher(self) self.firmware_flasher = FirmwareFlasher(self)
self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"), self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"),
@ -116,8 +116,8 @@ class MainWindow(QMainWindow):
if self.current_device is not None: if self.current_device is not None:
self.current_device.open(self.sideload_json if self.current_device.sideload else None) self.current_device.open(self.sideload_json if self.current_device.sideload else None)
for editor, lbl in self.editors: for e in [self.layout_editor, self.keymap_editor, self.firmware_flasher]:
editor.rebuild(self.current_device) e.rebuild(self.current_device)
self.refresh_tabs() self.refresh_tabs()