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" ACTIVE_LAYER_BTN_STYLE = "border: 1px solid black; padding: 5px; background-color: black; color: white"
KEYBOARD_WIDGET_PADDING = 5 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 clickable_label import ClickableLabel
from keyboard_widget import KeyboardWidget 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 constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE
from util import tr from util import tr
@ -42,6 +42,7 @@ class KeyboardContainer(QWidget):
self.rowcol = defaultdict(list) self.rowcol = defaultdict(list)
self.selected_row = -1 self.selected_row = -1
self.selected_col = -1 self.selected_col = -1
self.selected_mask = False
self.keyboard = None self.keyboard = None
self.current_layer = 0 self.current_layer = 0
@ -94,8 +95,14 @@ class KeyboardContainer(QWidget):
code = self.keyboard.layout[(self.current_layer, row, col)] code = self.keyboard.layout[(self.current_layer, row, col)]
text = keycode_label(code) text = keycode_label(code)
tooltip = keycode_tooltip(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: for widget in widgets:
widget.masked = mask
widget.setText(text) widget.setText(text)
widget.setMaskText(mask_text)
widget.setToolTip(tooltip) widget.setToolTip(tooltip)
self.container.update() self.container.update()
@ -109,13 +116,23 @@ class KeyboardContainer(QWidget):
def set_key(self, keycode): def set_key(self, keycode):
""" Change currently selected key to provided keycode """ """ Change currently selected key to provided keycode """
if self.selected_row >= 0 and self.selected_col >= 0: l, r, c = self.current_layer, self.selected_row, self.selected_col
self.keyboard.set_key(self.current_layer, self.selected_row, self.selected_col, keycode)
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() self.refresh_layer_display()
def on_key_clicked(self, widget): def on_key_clicked(self, widget, is_mask):
""" Change which key is currently selected """ """ Change which key is currently selected """
self.selected_mask = is_mask
for (row, col), widgets in self.rowcol.items(): for (row, col), widgets in self.rowcol.items():
if widget in widgets: if widget in widgets:
self.selected_row = row self.selected_row = row

View File

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

View File

@ -4,11 +4,18 @@
class Keycode: 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.code = code
self.qmk_id = qmk_id self.qmk_id = qmk_id
self.label = label self.label = label
self.tooltip = tooltip self.tooltip = tooltip
# whether this keycode requires another sub-keycode
self.masked = masked
if masked:
self.masked_keycodes.add(code)
K = Keycode K = Keycode
@ -162,6 +169,8 @@ KEYCODES_ISO = [
KEYCODES_LAYERS = [] KEYCODES_LAYERS = []
QK_ONE_SHOT_MOD = 0x5500 QK_ONE_SHOT_MOD = 0x5500
QK_MOD_TAP = 0x6000
MOD_LCTL = 0x01 MOD_LCTL = 0x01
MOD_LSFT = 0x02 MOD_LSFT = 0x02
MOD_LALT = 0x04 MOD_LALT = 0x04
@ -206,6 +215,8 @@ KEYCODES_QUANTUM = [
K(QK_ONE_SHOT_MOD | MOD_HYPR, "OSM(MOD_HYPR)", "OSM\nHyper", K(QK_ONE_SHOT_MOD | MOD_HYPR, "OSM(MOD_HYPR)", "OSM\nHyper",
"Enable Control, Shift, Alt, and GUI for one keypress"), "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(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(0x5CD7, "KC_LSPO", "LS\n(", "Left Shift when held, ( when tapped"),
K(0x5CD8, "KC_RSPC", "RS\n)", "Right 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): def find_keycode(code):
if keycode_is_mask(code):
code = code & 0xFF00
for keycode in KEYCODES: for keycode in KEYCODES:
if keycode.code == code: if keycode.code == code:
return keycode return keycode
@ -255,6 +269,10 @@ def keycode_tooltip(code):
return tooltip return tooltip
def keycode_is_mask(code):
return (code & 0xFF00) in Keycode.masked_keycodes
def recreate_keycodes(): def recreate_keycodes():
""" Regenerates global KEYCODES array """ """ Regenerates global KEYCODES array """