diff options
-rw-r--r-- | lulua/data/render-svg.css | 40 | ||||
-rw-r--r-- | lulua/render.py | 172 |
2 files changed, 120 insertions, 92 deletions
diff --git a/lulua/data/render-svg.css b/lulua/data/render-svg.css index e127625..4bbfd2f 100644 --- a/lulua/data/render-svg.css +++ b/lulua/data/render-svg.css @@ -1,64 +1,64 @@ /* inkscape-compatibility, explicitly apply font settings to .label */ -svg, .button .label { +svg, .label { font-family: "IBM Plex Arabic"; font-size: 90px; font-weight: 400; } -.button.unused { +.unused { opacity: 0.6; } -.button .label .layer-1 { +.label .layer-1 { } -.button.modifier .label .layer-1 { +.modifier .label .layer-1 { font-size: 80%; } -.button .label .layer-2, .button .label .layer-3, .button .label .layer-4 { +.label .layer-2, .label .layer-3, .label .layer-4 { font-size: 80%; font-weight: 100; } -.button .label .controlchar { +.label .controlchar { font-size: 40%; font-family: sans-serif; } -.button .cap { +.cap rect { fill: #eee8d5; } -.button .cap.highlight { +.highlight circle { fill: #dc322f; /* red */ - filter: blur(50px); + filter: blur(30px); } -.button.finger-little .cap.shadow { +.cap-shadow rect.finger-little { fill: #dc322f; /* red */ } -.button.finger-ring .cap.shadow { +.cap-shadow rect.finger-ring { fill: #268bd2; /* blue */ } -.button.finger-middle .cap.shadow { +.cap-shadow rect.finger-middle { fill: #d33682; /* magenta */ } -.button.finger-index .cap.shadow { +.cap-shadow rect.finger-index { fill: #6c71c4; /* violet */ } -.button.finger-thumb .cap.shadow { +.cap-shadow rect.finger-thumb { fill: #2aa198; /* cyan */ } -.button .label { +.label { fill: #657b83; } -.button .label.shadow { +.label.shadow { fill: #fdf6e3; } -.button .controllabel { +.controllabel { stroke: #657b83; fill: none; } -.button .controllabel.shadow { +.controllabel.shadow { stroke: #fdf6e3; fill: none; } -.button .marker { +.marker line { stroke: #fdf6e3; } -.button .marker.shadow { +.marker line.shadow { stroke: #93a1a1; } diff --git a/lulua/render.py b/lulua/render.py index 51a3a9e..4fd92c3 100644 --- a/lulua/render.py +++ b/lulua/render.py @@ -64,8 +64,55 @@ class Renderer: g = svgwrite.container.Group () + # use multiple layers to get overlapping right + gCapShadow = svgwrite.container.Group () + gCapShadow.attribs['class'] = 'cap-shadow' + g.add (gCapShadow) + gCap = svgwrite.container.Group () + gCap.attribs['class'] = 'cap' + g.add (gCap) + gHighlight = svgwrite.container.Group () + gHighlight.attribs['class'] = 'highlight' + g.add (gHighlight) + gLabel = svgwrite.container.Group () + gLabel.attribs['class'] = 'label' + g.add (gLabel) + for btn in self.keyboard.keys (): - g.add (self._createButton (btn, btnToPos[btn])) + # map modifier keys to arrows, they cannot have any text + mod = frozenset ([btn]) + isModifier = self.layout.isModifier (mod) + if isModifier: + layerToArrow = {1: '⭡', 2: '⭧', 3: '⭨'} + i, layer = self.layout.modifierToLayer (mod) + buttonText = [layerToArrow[i]] + else: + buttonText = list (map (displayText, self.layout.getButtonText (btn))) + + btnWidth = self.buttonWidth (btn) + btnPos = btnToPos[btn] + + if any (buttonText): + hand, finger = self.writer.getHandFinger (btn) + extraClass = f'finger-{finger.name.lower ()} hand-{hand.name.lower ()}' + gCapShadow.add (self._drawCapShadow (btnWidth, btnPos, extraClass)) + + o = self._drawCap (btnWidth, btnPos) + if not any (buttonText): + assert 'class' not in o.attribs + o.attribs['class'] = 'unused' + gCap.add (o) + if btn.isMarked: + gCap.add (self._drawMarker (btnWidth, btnPos)) + + highlight = self.keyHighlight.get (btn.name, 0) + gHighlight.add (self._drawHighlight (highlight, btnWidth, btnPos)) + + l = self._drawLabel (buttonText, btnWidth, btnPos) + if isModifier: + assert 'class' not in l.attribs + l.attribs['class'] = 'modifier' + gLabel.add (l) return g, (width, height) @@ -105,85 +152,63 @@ class Renderer: return m, (maxWidth, maxHeight) - def buttonWidth (self, btn): - """ Calculate button width """ - return btn.width * self.settings.buttonWidth - - def _createButton (self, btn, position): - """ Render a single button """ - + def _drawCapShadow (self, width, position, extraClass=''): xoff, yoff = position settings = self.settings - width = self.buttonWidth (btn) - - hand, finger = self.writer.getHandFinger (btn) - - gclass = ['button', f'finger-{finger.name.lower ()}', f'hand-{hand.name.lower ()}'] - - g = svgwrite.container.Group () - - # map modifier keys to arrows - mod = frozenset ([btn]) - isModifier = self.layout.isModifier (mod) - if isModifier: - layerToArrow = {1: '⭡', 2: '⭧', 3: '⭨'} - i, layer = self.layout.modifierToLayer (mod) - buttonText = [layerToArrow[i]] - gclass.append ('modifier') - else: - buttonText = list (map (displayText, self.layout.getButtonText (btn))) - - # background rect if any text - if any (buttonText): - b = svgwrite.shapes.Rect ( - insert=((xoff+settings.shadowOffset), (yoff+settings.shadowOffset)), - size=(width, settings.buttonWidth), - rx=settings.rounded, - ry=settings.rounded, - class_='cap shadow') - g.add (b) - else: - gclass.append ('unused') - # main key rect - b = svgwrite.shapes.Rect ( - insert=(xoff, yoff), + return svgwrite.shapes.Rect ( + insert=((xoff+settings.shadowOffset), (yoff+settings.shadowOffset)), size=(width, settings.buttonWidth), rx=settings.rounded, ry=settings.rounded, - class_='cap') - g.add (b) - - g.attribs['class'] = ' '.join (gclass) - - # button marker - if btn.isMarked: - start = (xoff+width*0.3, yoff+settings.buttonWidth*0.9) - end = (xoff+width*0.7, yoff+settings.buttonWidth*0.9) - # its shadow - l = svgwrite.shapes.Line ( - map (lambda x: (x+settings.shadowOffset), start), - map (lambda x: (x+settings.shadowOffset), end), - stroke_width=settings.markerStroke, - class_='marker shadow') - g.add (l) - # the marker itself - l = svgwrite.shapes.Line ( - start, - end, - stroke_width=settings.markerStroke, - class_='marker') - g.add (l) - - # highlight rect - highlight = self.keyHighlight.get (btn.name, 0) - b = svgwrite.shapes.Rect ( + class_=extraClass) + + def _drawCap (self, width, position): + xoff, yoff = position + settings = self.settings + return svgwrite.shapes.Rect ( insert=(xoff, yoff), size=(width, settings.buttonWidth), rx=settings.rounded, - ry=settings.rounded, - class_='cap highlight', + ry=settings.rounded) + + def _drawMarker (self, width, position): + xoff, yoff = position + settings = self.settings + + g = svgwrite.container.Group () + g.attribs['class'] = 'marker' + + start = (xoff+width*0.3, yoff+settings.buttonWidth*0.9) + end = (xoff+width*0.7, yoff+settings.buttonWidth*0.9) + # its shadow + l = svgwrite.shapes.Line ( + map (lambda x: (x+settings.shadowOffset), start), + map (lambda x: (x+settings.shadowOffset), end), + stroke_width=settings.markerStroke, + class_='shadow') + g.add (l) + # the marker itself + l = svgwrite.shapes.Line ( + start, + end, + stroke_width=settings.markerStroke) + g.add (l) + return g + + def _drawHighlight (self, highlight, width, position): + xoff, yoff = position + settings = self.settings + # make the circle slight smaller to reduce overlap + r = min (width, settings.buttonWidth)/2*0.9 + return svgwrite.shapes.Circle ( + center=(xoff+width/2, yoff+settings.buttonWidth/2), + r=r, style=f'opacity: {highlight}') - g.add (b) + + def _drawLabel (self, buttonText, width, position): + g = svgwrite.container.Group () + xoff, yoff = position + settings = self.settings # clock-wise from bottom-left to bottom-right, offsets are relative to buttonWidth and button center textParam = [ @@ -221,9 +246,12 @@ class Renderer: else: t.add (svgwrite.text.TSpan (text, class_=style, direction='rtl')) g.add (t) - return g + def buttonWidth (self, btn): + """ Calculate button width """ + return btn.width * self.settings.buttonWidth + def unique (l, key): return dict ((key (v), v) for v in l).values () |