implement support for rotated/complex keys
parent
75a016e002
commit
eceb31a03f
|
|
@ -12,5 +12,4 @@ WINDOW_WIDTH, WINDOW_HEIGHT = 1024, 768
|
||||||
LAYER_BTN_STYLE = "border: 1px solid black; padding: 5px"
|
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"
|
||||||
|
|
||||||
KEY_NORMAL_STYLE = "background-color:white; border: 1px solid black"
|
KEYBOARD_WIDGET_PADDING = 5
|
||||||
KEY_ACTIVE_STYLE = "background-color:black; color: white; border: 1px solid black"
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ from PyQt5.QtCore import pyqtSignal, Qt
|
||||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout
|
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout
|
||||||
|
|
||||||
from clickable_label import ClickableLabel
|
from clickable_label import ClickableLabel
|
||||||
|
from keyboard_widget import KeyboardWidget
|
||||||
from keycodes import keycode_label, keycode_tooltip
|
from keycodes import keycode_label, keycode_tooltip
|
||||||
from constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE, KEY_NORMAL_STYLE, \
|
from constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE
|
||||||
KEY_ACTIVE_STYLE
|
|
||||||
from util import tr
|
from util import tr
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -28,7 +28,8 @@ class KeyboardContainer(QWidget):
|
||||||
layout_labels_container.addStretch()
|
layout_labels_container.addStretch()
|
||||||
|
|
||||||
# contains the actual keyboard
|
# contains the actual keyboard
|
||||||
self.container = QWidget()
|
self.container = KeyboardWidget()
|
||||||
|
self.container.clicked.connect(self.on_key_clicked)
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
layout.addLayout(layout_labels_container)
|
layout.addLayout(layout_labels_container)
|
||||||
|
|
@ -39,7 +40,6 @@ class KeyboardContainer(QWidget):
|
||||||
self.keys = []
|
self.keys = []
|
||||||
self.layer_labels = []
|
self.layer_labels = []
|
||||||
self.rowcol = defaultdict(list)
|
self.rowcol = defaultdict(list)
|
||||||
self.selected_key = None
|
|
||||||
self.selected_row = -1
|
self.selected_row = -1
|
||||||
self.selected_col = -1
|
self.selected_col = -1
|
||||||
self.keyboard = None
|
self.keyboard = None
|
||||||
|
|
@ -75,33 +75,11 @@ class KeyboardContainer(QWidget):
|
||||||
# prepare for fetching keymap
|
# prepare for fetching keymap
|
||||||
self.rowcol = defaultdict(list)
|
self.rowcol = defaultdict(list)
|
||||||
|
|
||||||
max_w = max_h = 0
|
self.container.set_keys(keyboard.keys)
|
||||||
|
for key in self.container.keys:
|
||||||
|
if key.desc.row is not None:
|
||||||
|
self.rowcol[(key.desc.row, key.desc.col)].append(key)
|
||||||
|
|
||||||
for key in keyboard.keys:
|
|
||||||
widget = ClickableLabel()
|
|
||||||
widget.clicked.connect(lambda w=widget: self.on_key_clicked(w))
|
|
||||||
|
|
||||||
if key.row is not None:
|
|
||||||
self.rowcol[(key.row, key.col)].append(widget)
|
|
||||||
|
|
||||||
widget.setParent(self.container)
|
|
||||||
widget.setAlignment(Qt.AlignCenter)
|
|
||||||
|
|
||||||
x = (KEY_WIDTH + KEY_SPACING) * key.x
|
|
||||||
y = (KEY_HEIGHT + KEY_SPACING) * key.y
|
|
||||||
w = (KEY_WIDTH + KEY_SPACING) * key.width - KEY_SPACING
|
|
||||||
h = (KEY_HEIGHT + KEY_SPACING) * key.height - KEY_SPACING
|
|
||||||
|
|
||||||
widget.setFixedSize(w, h)
|
|
||||||
widget.move(x, y)
|
|
||||||
widget.show()
|
|
||||||
|
|
||||||
max_w = max(max_w, x + w)
|
|
||||||
max_h = max(max_h, y + h)
|
|
||||||
|
|
||||||
self.keys.append(widget)
|
|
||||||
|
|
||||||
self.container.setFixedSize(max_w, max_h)
|
|
||||||
self.current_layer = 0
|
self.current_layer = 0
|
||||||
self.refresh_layer_display()
|
self.refresh_layer_display()
|
||||||
|
|
||||||
|
|
@ -117,15 +95,13 @@ class KeyboardContainer(QWidget):
|
||||||
text = keycode_label(code)
|
text = keycode_label(code)
|
||||||
tooltip = keycode_tooltip(code)
|
tooltip = keycode_tooltip(code)
|
||||||
for widget in widgets:
|
for widget in widgets:
|
||||||
widget.setStyleSheet(KEY_NORMAL_STYLE)
|
|
||||||
if widget == self.selected_key:
|
|
||||||
widget.setStyleSheet(KEY_ACTIVE_STYLE)
|
|
||||||
widget.setText(text)
|
widget.setText(text)
|
||||||
widget.setToolTip(tooltip)
|
widget.setToolTip(tooltip)
|
||||||
|
self.container.update()
|
||||||
|
|
||||||
def switch_layer(self, idx):
|
def switch_layer(self, idx):
|
||||||
|
self.container.deselect()
|
||||||
self.current_layer = idx
|
self.current_layer = idx
|
||||||
self.selected_key = None
|
|
||||||
self.selected_row = -1
|
self.selected_row = -1
|
||||||
self.selected_col = -1
|
self.selected_col = -1
|
||||||
self.refresh_layer_display()
|
self.refresh_layer_display()
|
||||||
|
|
@ -140,11 +116,6 @@ class KeyboardContainer(QWidget):
|
||||||
def on_key_clicked(self, widget):
|
def on_key_clicked(self, widget):
|
||||||
""" Change which key is currently selected """
|
""" Change which key is currently selected """
|
||||||
|
|
||||||
if self.selected_key == widget:
|
|
||||||
self.selected_key = None
|
|
||||||
else:
|
|
||||||
self.selected_key = widget
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
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 constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, KEYBOARD_WIDGET_PADDING
|
||||||
|
|
||||||
|
|
||||||
|
class KeyWidget:
|
||||||
|
|
||||||
|
def __init__(self, desc):
|
||||||
|
self.desc = desc
|
||||||
|
self.text = ""
|
||||||
|
self.tooltip = ""
|
||||||
|
|
||||||
|
self.rotation_x = (KEY_WIDTH + KEY_SPACING) * desc.rotation_x
|
||||||
|
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.w = (KEY_WIDTH + KEY_SPACING) * desc.width - KEY_SPACING
|
||||||
|
self.h = (KEY_HEIGHT + KEY_SPACING) * desc.height - KEY_SPACING
|
||||||
|
|
||||||
|
self.rect = QRect(self.x, self.y, self.w, self.h)
|
||||||
|
|
||||||
|
self.has2 = desc.width2 != desc.width or desc.height2 != desc.height or desc.x2 != 0 or desc.y2 != 0
|
||||||
|
|
||||||
|
self.x2 = self.x + (KEY_WIDTH + KEY_SPACING) * desc.x2
|
||||||
|
self.y2 = self.y + (KEY_WIDTH + KEY_SPACING) * desc.y2
|
||||||
|
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.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
|
||||||
|
points = [(x1, y1), (x1, y2), (x2, y2), (x2, y1)]
|
||||||
|
bbox = []
|
||||||
|
for p in points:
|
||||||
|
t = QTransform()
|
||||||
|
t.translate(KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_PADDING)
|
||||||
|
t.translate(self.rotation_x, self.rotation_y)
|
||||||
|
t.rotate(self.rotation_angle)
|
||||||
|
t.translate(-self.rotation_x, -self.rotation_y)
|
||||||
|
p = t.map(QPointF(p[0], p[1]))
|
||||||
|
bbox.append(p)
|
||||||
|
return bbox
|
||||||
|
|
||||||
|
def calculate_draw_path(self):
|
||||||
|
path = QPainterPath()
|
||||||
|
path.addRect(int(self.x), int(self.y), int(self.w), int(self.h))
|
||||||
|
|
||||||
|
# second part only considered if different from first
|
||||||
|
if self.has2:
|
||||||
|
path2 = QPainterPath()
|
||||||
|
path2.addRect(int(self.x2), int(self.y2), int(self.w2), int(self.h2))
|
||||||
|
path = path.united(path2)
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def setText(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def setToolTip(self, tooltip):
|
||||||
|
self.tooltip = tooltip
|
||||||
|
|
||||||
|
|
||||||
|
class KeyboardWidget(QWidget):
|
||||||
|
|
||||||
|
clicked = pyqtSignal(KeyWidget)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setMouseTracking(True)
|
||||||
|
|
||||||
|
self.keys = []
|
||||||
|
self.width = self.height = 0
|
||||||
|
self.active_key = None
|
||||||
|
|
||||||
|
def set_keys(self, keys):
|
||||||
|
self.keys = []
|
||||||
|
for key in keys:
|
||||||
|
self.keys.append(KeyWidget(key))
|
||||||
|
self.calculate_size()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def calculate_size(self):
|
||||||
|
max_w = max_h = 0
|
||||||
|
|
||||||
|
for key in self.keys:
|
||||||
|
p = key.polygon.boundingRect().bottomRight()
|
||||||
|
max_w = max(max_w, p.x())
|
||||||
|
max_h = max(max_h, p.y())
|
||||||
|
|
||||||
|
self.width = max_w + 2 * KEYBOARD_WIDGET_PADDING
|
||||||
|
self.height = max_h + 2 * KEYBOARD_WIDGET_PADDING
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
qp = QPainter()
|
||||||
|
qp.begin(self)
|
||||||
|
qp.setRenderHint(QPainter.Antialiasing)
|
||||||
|
|
||||||
|
# add a little padding
|
||||||
|
qp.translate(KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_PADDING)
|
||||||
|
|
||||||
|
# for regular keycaps
|
||||||
|
regular_pen = qp.pen()
|
||||||
|
regular_pen.setColor(QColor("black"))
|
||||||
|
qp.setPen(regular_pen)
|
||||||
|
|
||||||
|
regular_brush = QBrush()
|
||||||
|
regular_brush.setColor(QColor("white"))
|
||||||
|
regular_brush.setStyle(Qt.SolidPattern)
|
||||||
|
qp.setBrush(regular_brush)
|
||||||
|
|
||||||
|
# for currently selected keycap
|
||||||
|
active_pen = qp.pen()
|
||||||
|
active_pen.setColor(QColor("white"))
|
||||||
|
|
||||||
|
active_brush = QBrush()
|
||||||
|
active_brush.setColor(QColor("black"))
|
||||||
|
active_brush.setStyle(Qt.SolidPattern)
|
||||||
|
|
||||||
|
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:
|
||||||
|
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)
|
||||||
|
|
||||||
|
qp.restore()
|
||||||
|
|
||||||
|
qp.end()
|
||||||
|
|
||||||
|
def minimumSizeHint(self):
|
||||||
|
return QSize(self.width, self.height)
|
||||||
|
|
||||||
|
def hit_test(self, pos):
|
||||||
|
for key in self.keys:
|
||||||
|
if key.polygon.containsPoint(pos, Qt.OddEvenFill):
|
||||||
|
return key
|
||||||
|
return None
|
||||||
|
|
||||||
|
def mousePressEvent(self, ev):
|
||||||
|
prev_active_key = self.active_key
|
||||||
|
|
||||||
|
self.active_key = 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()
|
||||||
|
|
||||||
|
def deselect(self):
|
||||||
|
if self.active_key is not None:
|
||||||
|
self.active_key = None
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def event(self, ev):
|
||||||
|
if ev.type() == QEvent.ToolTip:
|
||||||
|
key = self.hit_test(ev.pos())
|
||||||
|
if key is not None:
|
||||||
|
QToolTip.showText(ev.globalPos(), key.tooltip)
|
||||||
|
else:
|
||||||
|
QToolTip.hideText()
|
||||||
|
return super().event(ev)
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
# Based on https://github.com/ijprest/kle-serial
|
# Based on https://github.com/ijprest/kle-serial
|
||||||
|
# & see https://github.com/ijprest/kle-serial/pull/1
|
||||||
import json
|
import json
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
|
|
@ -62,6 +63,12 @@ class Keyboard:
|
||||||
self.keys = []
|
self.keys = []
|
||||||
|
|
||||||
|
|
||||||
|
class Cluster:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.x = self.y = 0
|
||||||
|
|
||||||
|
|
||||||
class Serial:
|
class Serial:
|
||||||
|
|
||||||
labelMap = [
|
labelMap = [
|
||||||
|
|
@ -88,6 +95,7 @@ class Serial:
|
||||||
|
|
||||||
def deserialize(self, rows):
|
def deserialize(self, rows):
|
||||||
current = Key()
|
current = Key()
|
||||||
|
cluster = Cluster()
|
||||||
kbd = Keyboard()
|
kbd = Keyboard()
|
||||||
align = 4
|
align = 4
|
||||||
|
|
||||||
|
|
@ -127,9 +135,13 @@ class Serial:
|
||||||
if "r" in item:
|
if "r" in item:
|
||||||
current.rotation_angle = item["r"]
|
current.rotation_angle = item["r"]
|
||||||
if "rx" in item:
|
if "rx" in item:
|
||||||
current.rotation_x = item["rx"]
|
current.rotation_x = cluster.x = item["rx"]
|
||||||
|
current.x = cluster.x
|
||||||
|
current.y = cluster.y
|
||||||
if "ry" in item:
|
if "ry" in item:
|
||||||
current.rotation_y = item["ry"]
|
current.rotation_y = cluster.y = item["ry"]
|
||||||
|
current.x = cluster.x
|
||||||
|
current.y = cluster.y
|
||||||
if "a" in item:
|
if "a" in item:
|
||||||
align = item["a"]
|
align = item["a"]
|
||||||
if "f" in item:
|
if "f" in item:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue