Merge pull request #8 from jbellerb/main

Size textual UI elements relative to contained text
main
xyzz 2021-01-13 09:36:16 -05:00 committed by GitHub
commit 1e1b86b283
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 70 deletions

View File

@ -1,11 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
KEY_WIDTH = 40 KEY_SIZE_RATIO = 2.667
KEY_HEIGHT = KEY_WIDTH KEY_SPACING_RATIO = 0.267
KEY_SPACING = 4
KEYCODE_BTN_WIDTH = 50 KEYCODE_BTN_RATIO = 3.333
KEYCODE_BTN_HEIGHT = KEYCODE_BTN_WIDTH
WINDOW_WIDTH, WINDOW_HEIGHT = 1024, 768 WINDOW_WIDTH, WINDOW_HEIGHT = 1024, 768

View File

@ -9,6 +9,7 @@ from keyboard_widget import KeyboardWidget, EncoderWidget
from keycodes import keycode_label, keycode_tooltip, keycode_is_mask, find_keycode from keycodes import keycode_label, keycode_tooltip, keycode_is_mask, find_keycode
from constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE from constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE
from keymaps import KEYMAPS from keymaps import KEYMAPS
from square_button import SquareButton
from util import tr from util import tr
@ -54,8 +55,8 @@ class KeyboardContainer(QWidget):
# create new layer labels # create new layer labels
for x in range(self.keyboard.layers): for x in range(self.keyboard.layers):
btn = QPushButton(str(x)) btn = SquareButton(str(x))
btn.setFixedSize(25, 25) btn.setRelSize(1.667)
btn.setCheckable(True) btn.setCheckable(True)
btn.clicked.connect(lambda state, idx=x: self.switch_layer(idx)) btn.clicked.connect(lambda state, idx=x: self.switch_layer(idx))
self.layout_layers.addWidget(btn) self.layout_layers.addWidget(btn)

View File

@ -4,12 +4,12 @@ from PyQt5.QtGui import QPainter, QColor, QPainterPath, QTransform, QBrush, QPol
from PyQt5.QtWidgets import QWidget, QToolTip, QApplication from PyQt5.QtWidgets import QWidget, QToolTip, QApplication
from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent, QRectF from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent, QRectF
from constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_MASK_PADDING from constants import KEY_SIZE_RATIO, KEY_SPACING_RATIO, KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_MASK_PADDING
class KeyWidget: class KeyWidget:
def __init__(self, desc, shift_x=0, shift_y=0): def __init__(self, desc, scale, shift_x=0, shift_y=0):
self.active = False self.active = False
self.masked = False self.masked = False
self.desc = desc self.desc = desc
@ -17,40 +17,50 @@ class KeyWidget:
self.mask_text = "" self.mask_text = ""
self.tooltip = "" self.tooltip = ""
self.color = None self.color = None
self.scale = 0
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.rotation_angle = desc.rotation_angle
self.shift_x = shift_x
self.shift_y = shift_y
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.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.update_position(scale, shift_x, shift_y)
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(QRectF(self.x, self.y, self.w, self.h)) def update_position(self, scale, shift_x=0, shift_y=0):
self.polygon = QPolygonF(self.bbox + [self.bbox[0]]) if self.scale != scale or self.shift_x != shift_x or self.shift_y != shift_y:
self.draw_path = self.calculate_draw_path() self.scale = scale
self.draw_path2 = self.calculate_draw_path2() size = self.scale * (KEY_SIZE_RATIO + KEY_SPACING_RATIO)
spacing = self.scale * KEY_SPACING_RATIO
# calculate areas where the inner keycode will be located self.rotation_x = size * self.desc.rotation_x
# nonmask = outer (e.g. Rsft_T) self.rotation_y = size * self.desc.rotation_y
# mask = inner (e.g. KC_A)
self.nonmask_rect = QRectF(self.x, self.y, self.w, self.h / 2) self.shift_x = shift_x
self.mask_rect = QRectF(self.x + KEYBOARD_WIDGET_MASK_PADDING, self.y + self.h / 2, self.shift_y = shift_y
self.w - 2 * KEYBOARD_WIDGET_MASK_PADDING, self.h / 2 - KEYBOARD_WIDGET_MASK_PADDING) self.x = size * self.desc.x
self.mask_bbox = self.calculate_bbox(self.mask_rect) self.y = size * self.desc.y
self.mask_polygon = QPolygonF(self.mask_bbox + [self.mask_bbox[0]]) self.w = size * self.desc.width - spacing
self.h = size * self.desc.height - spacing
self.rect = QRect(self.x, self.y, self.w, self.h)
self.x2 = self.x + size * self.desc.x2
self.y2 = self.y + size * self.desc.y2
self.w2 = size * self.desc.width2 - spacing
self.h2 = size * self.desc.height2 - spacing
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()
self.draw_path2 = self.calculate_draw_path2()
# 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): def calculate_bbox(self, rect):
x1 = rect.topLeft().x() x1 = rect.topLeft().x()
@ -140,7 +150,7 @@ class KeyboardWidget(QWidget):
self.common_widgets = [] self.common_widgets = []
# layout-specific widgets # layout-specific widgets
self.widgets_for_layout = defaultdict(lambda: defaultdict(list)) self.widgets_for_layout = []
# widgets in current layout # widgets in current layout
self.widgets = [] self.widgets = []
@ -151,59 +161,64 @@ class KeyboardWidget(QWidget):
def set_keys(self, keys, encoders): def set_keys(self, keys, encoders):
self.common_widgets = [] self.common_widgets = []
self.widgets_for_layout = defaultdict(lambda: defaultdict(list)) self.widgets_for_layout = []
self.add_keys([(x, KeyWidget) for x in keys] + [(x, EncoderWidget) for x in encoders]) self.add_keys([(x, KeyWidget) for x in keys] + [(x, EncoderWidget) for x in encoders])
self.update_layout() self.update_layout()
def add_keys(self, keys): def add_keys(self, keys):
scale_factor = self.fontMetrics().height()
for key, cls in keys:
if key.layout_index == -1:
self.common_widgets.append(cls(key, scale_factor))
else:
self.widgets_for_layout.append(cls(key, scale_factor))
def place_widgets(self):
top_x = top_y = 1e6 top_x = top_y = 1e6
scale_factor = self.fontMetrics().height()
self.widgets = []
# find the global top-left position, all the keys will be shifted to the left/up by that position # find the global top-left position, all the keys will be shifted to the left/up by that position
for key, cls in keys: for widget in self.common_widgets:
if key.layout_index == -1: widget.update_position(scale_factor)
obj = cls(key) p = widget.polygon.boundingRect().topLeft()
p = obj.polygon.boundingRect().topLeft() top_x = min(top_x, p.x())
top_x = min(top_x, p.x()) top_y = min(top_y, p.y())
top_y = min(top_y, p.y())
# obtain common widgets, that is, ones which are always displayed and require no extra transforms # place common widgets, that is, ones which are always displayed and require no extra transforms
for key, cls in keys: for widget in self.common_widgets:
if key.layout_index == -1: widget.update_position(scale_factor, -top_x + KEYBOARD_WIDGET_PADDING, -top_y + KEYBOARD_WIDGET_PADDING)
self.common_widgets.append(cls(key, -top_x + KEYBOARD_WIDGET_PADDING, -top_y + KEYBOARD_WIDGET_PADDING)) self.widgets.append(widget)
# top-left position for specific layout # top-left position for specific layout
layout_x = defaultdict(lambda: defaultdict(lambda: 1e6)) layout_x = defaultdict(lambda: defaultdict(lambda: 1e6))
layout_y = defaultdict(lambda: defaultdict(lambda: 1e6)) layout_y = defaultdict(lambda: defaultdict(lambda: 1e6))
# determine top-left position for every layout option # determine top-left position for every layout option
for key, cls in keys: for widget in self.widgets_for_layout:
if key.layout_index != -1: widget.update_position(scale_factor)
obj = cls(key) idx, opt = widget.desc.layout_index, widget.desc.layout_option
idx, opt = key.layout_index, key.layout_option p = widget.polygon.boundingRect().topLeft()
p = obj.polygon.boundingRect().topLeft() layout_x[idx][opt] = min(layout_x[idx][opt], p.x())
layout_x[idx][opt] = min(layout_x[idx][opt], p.x()) layout_y[idx][opt] = min(layout_y[idx][opt], p.y())
layout_y[idx][opt] = min(layout_y[idx][opt], p.y())
# obtain widgets for all layout options now that we know how to shift them # obtain widgets for all layout options now that we know how to shift them
for key, cls in keys: for widget in self.widgets_for_layout:
if key.layout_index != -1: idx, opt = widget.desc.layout_index, widget.desc.layout_option
idx, opt = key.layout_index, key.layout_option if opt == self.layout_editor.get_choice(idx):
shift_x = layout_x[idx][opt] - layout_x[idx][0] shift_x = layout_x[idx][opt] - layout_x[idx][0]
shift_y = layout_y[idx][opt] - layout_y[idx][0] shift_y = layout_y[idx][opt] - layout_y[idx][0]
obj = cls(key, -shift_x - top_x + KEYBOARD_WIDGET_PADDING, -shift_y - top_y + KEYBOARD_WIDGET_PADDING) widget.update_position(scale_factor, -shift_x - top_x + KEYBOARD_WIDGET_PADDING, -shift_y - top_y + KEYBOARD_WIDGET_PADDING)
self.widgets_for_layout[idx][opt].append(obj) self.widgets.append(widget)
def update_layout(self): def update_layout(self):
""" Updates self.widgets for the currently active layout """ """ Updates self.widgets for the currently active layout """
# determine widgets for current layout # determine widgets for current layout
self.widgets = [] self.place_widgets()
self.widgets += self.common_widgets
for idx in self.widgets_for_layout.keys():
option = self.layout_editor.get_choice(idx)
self.widgets += self.widgets_for_layout[idx][option]
self.widgets = list(filter(lambda w: not w.desc.decal, self.widgets)) self.widgets = list(filter(lambda w: not w.desc.decal, self.widgets))
self.widgets.sort(key=lambda w: (w.y, w.x)) self.widgets.sort(key=lambda w: (w.y, w.x))
@ -219,6 +234,7 @@ class KeyboardWidget(QWidget):
self.height = max_h + 2 * KEYBOARD_WIDGET_PADDING self.height = max_h + 2 * KEYBOARD_WIDGET_PADDING
self.update() self.update()
self.updateGeometry()
def paintEvent(self, event): def paintEvent(self, event):
qp = QPainter() qp = QPainter()
@ -314,6 +330,9 @@ class KeyboardWidget(QWidget):
self.clicked.emit() self.clicked.emit()
self.update() self.update()
def resizeEvent(self, ev):
self.update_layout()
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 """
@ -340,6 +359,8 @@ class KeyboardWidget(QWidget):
QToolTip.showText(ev.globalPos(), key.tooltip) QToolTip.showText(ev.globalPos(), key.tooltip)
else: else:
QToolTip.hideText() QToolTip.hideText()
if ev.type() == QEvent.LayoutRequest:
self.update_layout()
return super().event(ev) return super().event(ev)
def set_enabled(self, val): def set_enabled(self, val):

View File

@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QPushButton
class SquareButton(QPushButton):
def __init__(self, parent=None):
super().__init__(parent)
self.scale = 1.2
def setRelSize(self, ratio):
self.scale = ratio
self.updateGeometry()
def sizeHint(self):
size = int(round(self.fontMetrics().height() * self.scale))
return QSize(size, size)

View File

@ -1,14 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtCore import Qt, QSize, pyqtSignal
from PyQt5.QtWidgets import QTabWidget, QWidget, QPushButton, QScrollArea, QApplication from PyQt5.QtWidgets import QTabWidget, QWidget, QPushButton, QScrollArea, QApplication
from PyQt5.QtGui import QPalette from PyQt5.QtGui import QPalette
from constants import KEYCODE_BTN_WIDTH, KEYCODE_BTN_HEIGHT from constants import KEYCODE_BTN_RATIO
from flowlayout import FlowLayout from flowlayout import FlowLayout
from keycodes import keycode_tooltip, KEYCODES_BASIC, KEYCODES_ISO, KEYCODES_MACRO, KEYCODES_LAYERS, KEYCODES_QUANTUM, \ from keycodes import keycode_tooltip, KEYCODES_BASIC, KEYCODES_ISO, KEYCODES_MACRO, KEYCODES_LAYERS, KEYCODES_QUANTUM, \
KEYCODES_BACKLIGHT, KEYCODES_MEDIA, KEYCODES_SPECIAL KEYCODES_BACKLIGHT, KEYCODES_MEDIA, KEYCODES_SPECIAL
from keymaps import KEYMAPS from keymaps import KEYMAPS
from square_button import SquareButton
from util import tr from util import tr
@ -65,8 +66,8 @@ class TabbedKeycodes(QTabWidget):
buttons = [] buttons = []
for keycode in keycodes: for keycode in keycodes:
btn = QPushButton() btn = SquareButton()
btn.setFixedSize(KEYCODE_BTN_WIDTH, KEYCODE_BTN_HEIGHT) btn.setRelSize(KEYCODE_BTN_RATIO)
btn.setToolTip(keycode_tooltip(keycode.code)) btn.setToolTip(keycode_tooltip(keycode.code))
btn.clicked.connect(lambda st, k=keycode: self.keycode_changed.emit(k.code)) btn.clicked.connect(lambda st, k=keycode: self.keycode_changed.emit(k.code))
btn.keycode = keycode btn.keycode = keycode