initial support for masked keycodes

main
Ilya Zhuravlev 2020-10-18 00:06:00 -04:00
parent 3683ef98d1
commit 215a54fca1
4 changed files with 93 additions and 27 deletions

View File

@ -13,3 +13,4 @@ LAYER_BTN_STYLE = "border: 1px solid black; padding: 5px"
ACTIVE_LAYER_BTN_STYLE = "border: 1px solid black; padding: 5px; background-color: black; color: white"
KEYBOARD_WIDGET_PADDING = 5
KEYBOARD_WIDGET_MASK_PADDING = 3

View File

@ -7,7 +7,7 @@ from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout
from clickable_label import ClickableLabel
from keyboard_widget import KeyboardWidget
from keycodes import keycode_label, keycode_tooltip
from keycodes import keycode_label, keycode_tooltip, keycode_is_mask
from constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE
from util import tr
@ -42,6 +42,7 @@ class KeyboardContainer(QWidget):
self.rowcol = defaultdict(list)
self.selected_row = -1
self.selected_col = -1
self.selected_mask = False
self.keyboard = None
self.current_layer = 0
@ -94,8 +95,14 @@ class KeyboardContainer(QWidget):
code = self.keyboard.layout[(self.current_layer, row, col)]
text = keycode_label(code)
tooltip = keycode_tooltip(code)
mask = keycode_is_mask(code)
mask_text = keycode_label(code & 0xFF)
if mask:
text = text.split("\n")[0]
for widget in widgets:
widget.masked = mask
widget.setText(text)
widget.setMaskText(mask_text)
widget.setToolTip(tooltip)
self.container.update()
@ -109,13 +116,23 @@ class KeyboardContainer(QWidget):
def set_key(self, keycode):
""" Change currently selected key to provided keycode """
if self.selected_row >= 0 and self.selected_col >= 0:
self.keyboard.set_key(self.current_layer, self.selected_row, self.selected_col, keycode)
l, r, c = self.current_layer, self.selected_row, self.selected_col
if r >= 0 and c >= 0:
# if masked, ensure that this is a byte-sized keycode
if self.selected_mask:
if keycode > 0xFF:
return
keycode = (self.keyboard.layout[(l, r, c)] & 0xFF00) | keycode
self.keyboard.set_key(l, r, c, keycode)
self.refresh_layer_display()
def on_key_clicked(self, widget):
def on_key_clicked(self, widget, is_mask):
""" Change which key is currently selected """
self.selected_mask = is_mask
for (row, col), widgets in self.rowcol.items():
if widget in widgets:
self.selected_row = row

View File

@ -1,8 +1,8 @@
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
from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent, QRectF
from constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, KEYBOARD_WIDGET_PADDING
from constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_MASK_PADDING
class KeyWidget:
@ -10,6 +10,7 @@ class KeyWidget:
def __init__(self, desc):
self.desc = desc
self.text = ""
self.mask_text = ""
self.tooltip = ""
self.rotation_x = (KEY_WIDTH + KEY_SPACING) * desc.rotation_x
@ -30,15 +31,24 @@ class KeyWidget:
self.w2 = (KEY_WIDTH + KEY_SPACING) * desc.width2 - KEY_SPACING
self.h2 = (KEY_HEIGHT + KEY_SPACING) * desc.height2 - KEY_SPACING
self.bbox = self.calculate_bbox()
self.bbox = self.calculate_bbox(QRectF(self.x, self.y, self.w, self.h))
self.polygon = QPolygonF(self.bbox + [self.bbox[0]])
self.draw_path = self.calculate_draw_path()
def calculate_bbox(self):
x1 = self.x
y1 = self.y
x2 = self.x + self.w
y2 = self.y + self.h
# calculate areas where the inner keycode will be located
# nonmask = outer (e.g. Rsft_T)
# mask = inner (e.g. KC_A)
self.nonmask_rect = QRectF(self.x, self.y, self.w, self.h / 2)
self.mask_rect = QRectF(self.x + KEYBOARD_WIDGET_MASK_PADDING, self.y + self.h / 2,
self.w - 2 * KEYBOARD_WIDGET_MASK_PADDING, self.h / 2 - KEYBOARD_WIDGET_MASK_PADDING)
self.mask_bbox = self.calculate_bbox(self.mask_rect)
self.mask_polygon = QPolygonF(self.mask_bbox + [self.mask_bbox[0]])
def calculate_bbox(self, rect):
x1 = rect.topLeft().x()
y1 = rect.topLeft().y()
x2 = rect.bottomRight().x()
y2 = rect.bottomRight().y()
points = [(x1, y1), (x1, y2), (x2, y2), (x2, y1)]
bbox = []
for p in points:
@ -66,13 +76,16 @@ class KeyWidget:
def setText(self, text):
self.text = text
def setMaskText(self, text):
self.mask_text = text
def setToolTip(self, tooltip):
self.tooltip = tooltip
class KeyboardWidget(QWidget):
clicked = pyqtSignal(KeyWidget)
clicked = pyqtSignal(KeyWidget, bool)
def __init__(self):
super().__init__()
@ -81,6 +94,7 @@ class KeyboardWidget(QWidget):
self.keys = []
self.width = self.height = 0
self.active_key = None
self.active_mask = False
def set_keys(self, keys):
self.keys = []
@ -126,21 +140,36 @@ class KeyboardWidget(QWidget):
active_brush.setColor(QColor("black"))
active_brush.setStyle(Qt.SolidPattern)
mask_font = qp.font()
mask_font.setPointSize(mask_font.pointSize() * 0.8)
for idx, key in enumerate(self.keys):
qp.save()
qp.translate(key.rotation_x, key.rotation_y)
qp.rotate(key.rotation_angle)
qp.translate(-key.rotation_x, -key.rotation_y)
if self.active_key == key:
if self.active_key == key and not self.active_mask:
qp.setPen(active_pen)
qp.setBrush(active_brush)
# draw the keycap
qp.drawPath(key.draw_path)
# draw the legend
qp.drawText(key.rect, Qt.AlignCenter, key.text)
# if this is a mask key, draw the inner key
if key.masked:
qp.setFont(mask_font)
qp.drawText(key.nonmask_rect, Qt.AlignCenter, key.text)
if self.active_key == key and self.active_mask:
qp.setPen(active_pen)
qp.setBrush(active_brush)
qp.drawRect(key.mask_rect)
qp.drawText(key.mask_rect, Qt.AlignCenter, key.mask_text)
else:
# draw the legend
qp.drawText(key.rect, Qt.AlignCenter, key.text)
qp.restore()
@ -150,20 +179,21 @@ class KeyboardWidget(QWidget):
return QSize(self.width, self.height)
def hit_test(self, pos):
""" Returns key, hit_masked_part """
for key in self.keys:
if key.masked and key.mask_polygon.containsPoint(pos, Qt.OddEvenFill):
return key, True
if key.polygon.containsPoint(pos, Qt.OddEvenFill):
return key
return None
return key, False
return None, False
def mousePressEvent(self, ev):
prev_active_key = self.active_key
self.active_key = self.hit_test(ev.pos())
self.active_key, self.active_mask = self.hit_test(ev.pos())
if self.active_key is not None:
self.clicked.emit(self.active_key)
if prev_active_key != self.active_key:
self.update()
self.clicked.emit(self.active_key, self.active_mask)
self.update()
def deselect(self):
if self.active_key is not None:
@ -172,7 +202,7 @@ class KeyboardWidget(QWidget):
def event(self, ev):
if ev.type() == QEvent.ToolTip:
key = self.hit_test(ev.pos())
key = self.hit_test(ev.pos())[0]
if key is not None:
QToolTip.showText(ev.globalPos(), key.tooltip)
else:

View File

@ -4,11 +4,18 @@
class Keycode:
def __init__(self, code, qmk_id, label, tooltip=None):
masked_keycodes = set()
def __init__(self, code, qmk_id, label, tooltip=None, masked=False):
self.code = code
self.qmk_id = qmk_id
self.label = label
self.tooltip = tooltip
# whether this keycode requires another sub-keycode
self.masked = masked
if masked:
self.masked_keycodes.add(code)
K = Keycode
@ -162,6 +169,8 @@ KEYCODES_ISO = [
KEYCODES_LAYERS = []
QK_ONE_SHOT_MOD = 0x5500
QK_MOD_TAP = 0x6000
MOD_LCTL = 0x01
MOD_LSFT = 0x02
MOD_LALT = 0x04
@ -206,6 +215,8 @@ KEYCODES_QUANTUM = [
K(QK_ONE_SHOT_MOD | MOD_HYPR, "OSM(MOD_HYPR)", "OSM\nHyper",
"Enable Control, Shift, Alt, and GUI for one keypress"),
K(QK_MOD_TAP | (MOD_RSFT << 8), "RSFT_T(kc)", "RSft_T\n(kc)", "Right Shift when held, kc when tapped", masked=True),
K(0x5C16, "KC_GESC", "Esc\n~", "Esc normally, but ~ when Shift or GUI is pressed"),
K(0x5CD7, "KC_LSPO", "LS\n(", "Left Shift when held, ( when tapped"),
K(0x5CD8, "KC_RSPC", "RS\n)", "Right Shift when held, ) when tapped"),
@ -232,6 +243,9 @@ K = None
def find_keycode(code):
if keycode_is_mask(code):
code = code & 0xFF00
for keycode in KEYCODES:
if keycode.code == code:
return keycode
@ -255,6 +269,10 @@ def keycode_tooltip(code):
return tooltip
def keycode_is_mask(code):
return (code & 0xFF00) in Keycode.masked_keycodes
def recreate_keycodes():
""" Regenerates global KEYCODES array """