mirror of
https://github.com/jtgans/g13gui.git
synced 2025-06-20 00:14:09 -04:00
g13gui: bitwidgets: Major work
This adds a whole bunch of tests and additional widgets we can use to draw up interfaces on the g13's LCD. It also abstracts the backend a bit so we can draw to X11 rather than a g13. - Added a Button and ButtonBar class so we can start making use of those great L* buttons - Added a Label class so we can stick text all over the screen - Added a Screen class to abstract away common display elements such as the button bar, as well as send along the next frame to the display. - Created the Widget class to make a lot of common boilerplate code live somewhere useful. - Added X11DisplayDevice so we can test on desktops without needing a G13.
This commit is contained in:
parent
f257d8f11d
commit
e71d621ff7
@ -0,0 +1,2 @@
|
||||
DISPLAY_WIDTH = 160
|
||||
DISPLAY_HEIGHT = 48
|
119
g13gui/g13gui/bitwidgets/button.py
Normal file
119
g13gui/g13gui/bitwidgets/button.py
Normal file
@ -0,0 +1,119 @@
|
||||
import enum
|
||||
|
||||
from builtins import property
|
||||
from g13gui.bitwidgets import DISPLAY_WIDTH
|
||||
from g13gui.bitwidgets import DISPLAY_HEIGHT
|
||||
from g13gui.bitwidgets.widget import Widget
|
||||
from g13gui.observer import ChangeType
|
||||
|
||||
|
||||
GLYPH_WIDTH = 5
|
||||
GLYPH_HEIGHT = 5
|
||||
|
||||
|
||||
class Glyphs(enum.Enum):
|
||||
DOWN_ARROW = [(2, 0), (2, 4), (0, 2), (4, 2), (2, 4)]
|
||||
UP_ARROW = [(2, 4), (2, 0), (4, 2), (0, 2), (2, 0)]
|
||||
CHECKMARK = [(0, 3), (1, 4), (4, 1), (1, 4)]
|
||||
XMARK = [(0, 0), (4, 4), (2, 2), (4, 0), (0, 4)]
|
||||
|
||||
def transformTo(self, offsetx, offsety):
|
||||
return [(offsetx + x, offsety + y) for (x, y) in self.value]
|
||||
|
||||
|
||||
class ButtonBar(Widget):
|
||||
MAX_BUTTONS = 4
|
||||
TOP_LINE = 33
|
||||
|
||||
def __init__(self):
|
||||
Widget.__init__(self)
|
||||
self._children = [None] * ButtonBar.MAX_BUTTONS
|
||||
self.position = (0, ButtonBar.TOP_LINE)
|
||||
self.bounds = (DISPLAY_WIDTH, DISPLAY_HEIGHT - ButtonBar.TOP_LINE)
|
||||
|
||||
def button(self, buttonNum):
|
||||
return self._children[buttonNum]
|
||||
|
||||
def setButton(self, buttonNum, button):
|
||||
if self._children[buttonNum]:
|
||||
self.removeChild(self._children[buttonNum])
|
||||
|
||||
self._children[buttonNum] = button
|
||||
position = self._positionForButton(buttonNum)
|
||||
button.position = position
|
||||
button.parent = self
|
||||
|
||||
self.addChange(ChangeType.ADD, 'child', button)
|
||||
self.notifyChanged()
|
||||
|
||||
def addChild(self, button):
|
||||
buttonNum = self._children.index(None)
|
||||
if buttonNum > ButtonBar.MAX_BUTTONS:
|
||||
raise ValueError('Can\'t store another button!')
|
||||
self.setButton(buttonNum, button)
|
||||
|
||||
def removeChild(self, button):
|
||||
buttonNum = self._children.index(button)
|
||||
button = self._children[buttonNum]
|
||||
self._children[buttonNum] = None
|
||||
button.parent = None
|
||||
|
||||
self.addChange(ChangeType.REMOVE, 'child', button)
|
||||
self.notifyChanged()
|
||||
|
||||
def _positionForSlot(self, buttonNum):
|
||||
slotWidth = DISPLAY_WIDTH / ButtonBar.MAX_BUTTONS
|
||||
slotX = (buttonNum * slotWidth)
|
||||
slotY = ButtonBar.TOP_LINE + 2
|
||||
|
||||
return (slotX, slotY)
|
||||
|
||||
def _positionForButton(self, buttonNum):
|
||||
(slotX, slotY) = self._positionForSlot(buttonNum)
|
||||
slotWidth = DISPLAY_WIDTH / ButtonBar.MAX_BUTTONS
|
||||
slotHeight = DISPLAY_HEIGHT - ButtonBar.TOP_LINE - 2
|
||||
|
||||
(width, height) = self._children[buttonNum].bounds
|
||||
x_pos = int(slotX + (slotWidth / 2) - (width / 2))
|
||||
y_pos = int(slotY + (slotHeight / 2) - (height / 2))
|
||||
|
||||
return (x_pos, y_pos)
|
||||
|
||||
def draw(self, ctx):
|
||||
if self.visible:
|
||||
for child in self._children:
|
||||
if child and child.visible:
|
||||
child.draw(ctx)
|
||||
|
||||
# Top line
|
||||
ctx.line(self.position + (DISPLAY_WIDTH,
|
||||
ButtonBar.TOP_LINE),
|
||||
fill=1)
|
||||
|
||||
# Dividing lines
|
||||
for slot in range(0, ButtonBar.MAX_BUTTONS):
|
||||
position = list(self._positionForSlot(slot))
|
||||
position[0] -= 2
|
||||
ctx.line(tuple(position) + (position[0], DISPLAY_HEIGHT),
|
||||
fill=1)
|
||||
|
||||
|
||||
class Button(Widget):
|
||||
def __init__(self, glyph, fill=True):
|
||||
Widget.__init__(self)
|
||||
self.glyph = glyph
|
||||
self.fill = fill
|
||||
self.bounds = (5, 5)
|
||||
|
||||
def draw(self, ctx):
|
||||
if self._visible:
|
||||
xformedGlyph = self._glyph.transformTo(*self._position)
|
||||
ctx.line(xformedGlyph, fill=self.fill)
|
||||
|
||||
@property
|
||||
def glyph(self):
|
||||
return self._glyph
|
||||
|
||||
@glyph.setter
|
||||
def glyph(self, glyph):
|
||||
self.setProperty('glyph', glyph)
|
68
g13gui/g13gui/bitwidgets/button_tests.py
Normal file
68
g13gui/g13gui/bitwidgets/button_tests.py
Normal file
@ -0,0 +1,68 @@
|
||||
import unittest
|
||||
import time
|
||||
|
||||
from g13gui.bitwidgets.display import Display
|
||||
from g13gui.bitwidgets.x11displaydevice import X11DisplayDevice
|
||||
from g13gui.bitwidgets.screen import Screen
|
||||
from g13gui.bitwidgets.button import Button
|
||||
from g13gui.bitwidgets.button import Glyphs
|
||||
from g13gui.bitwidgets.label import Label
|
||||
from g13gui.bitwidgets.fonts import Fonts
|
||||
|
||||
|
||||
class ButtonTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.dd = X11DisplayDevice(self.__class__.__name__)
|
||||
self.dd.start()
|
||||
time.sleep(0.25)
|
||||
self.display = Display(self.dd)
|
||||
self.screen = Screen(self.display)
|
||||
|
||||
def tearDown(self):
|
||||
time.sleep(1)
|
||||
self.dd.shutdown()
|
||||
self.dd.join()
|
||||
|
||||
def testExButton(self):
|
||||
ctx = self.display.getContext()
|
||||
exButton = Button(Glyphs.XMARK)
|
||||
exButton.show()
|
||||
exButton.draw(ctx)
|
||||
upButton = Button(Glyphs.UP_ARROW)
|
||||
upButton.position = (10, 0)
|
||||
upButton.show()
|
||||
upButton.draw(ctx)
|
||||
downButton = Button(Glyphs.DOWN_ARROW)
|
||||
downButton.position = (20, 0)
|
||||
downButton.show()
|
||||
downButton.draw(ctx)
|
||||
checkButton = Button(Glyphs.CHECKMARK)
|
||||
checkButton.position = (30, 0)
|
||||
checkButton.show()
|
||||
checkButton.draw(ctx)
|
||||
|
||||
self.display.commit()
|
||||
|
||||
def testButtonBar(self):
|
||||
exButton = Button(Glyphs.XMARK)
|
||||
upButton = Button(Glyphs.UP_ARROW)
|
||||
downButton = Button(Glyphs.DOWN_ARROW)
|
||||
checkButton = Button(Glyphs.CHECKMARK)
|
||||
|
||||
self.screen.buttonBar.addChild(exButton)
|
||||
self.screen.buttonBar.addChild(upButton)
|
||||
self.screen.buttonBar.addChild(downButton)
|
||||
self.screen.buttonBar.addChild(checkButton)
|
||||
self.screen.buttonBar.show_all()
|
||||
|
||||
self.screen.nextFrame()
|
||||
|
||||
def testLabelButton(self):
|
||||
testButton = Label(0, 0, "Test", font=Fonts.TINY)
|
||||
self.screen.buttonBar.addChild(testButton)
|
||||
self.screen.buttonBar.show_all()
|
||||
self.screen.nextFrame()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,65 +1,24 @@
|
||||
import struct
|
||||
import PIL.ImageDraw
|
||||
import PIL.PyAccess
|
||||
import sys
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from g13gui.observer import Subject
|
||||
from g13gui.observer import ChangeType
|
||||
|
||||
|
||||
class DisplayMetrics(object):
|
||||
WIDTH_PIXELS = 160
|
||||
HEIGHT_PIXELS = 48
|
||||
|
||||
|
||||
LPBM_LENGTH = 960
|
||||
|
||||
|
||||
def ImageToLPBM(image):
|
||||
i = PIL.PyAccess.new(image, readonly=True)
|
||||
bio = BytesIO()
|
||||
|
||||
maxBytes = (DisplayMetrics.WIDTH_PIXELS * DisplayMetrics.HEIGHT_PIXELS // 8)
|
||||
row = 0
|
||||
col = 0
|
||||
|
||||
for byteNum in range(0, maxBytes):
|
||||
b = int()
|
||||
|
||||
if row == 40:
|
||||
maxSubrow = 3
|
||||
else:
|
||||
maxSubrow = 8
|
||||
|
||||
for subrow in range(0, maxSubrow):
|
||||
b |= i[col, row + subrow] << subrow
|
||||
|
||||
bio.write(struct.pack('<B', b))
|
||||
|
||||
col += 1
|
||||
if (col % 160) == 0:
|
||||
col = 0
|
||||
row += 8
|
||||
|
||||
return bio.getvalue()
|
||||
|
||||
|
||||
class Display(Subject):
|
||||
def __init__(self):
|
||||
self._context = None
|
||||
self._bitmap = Image.new(mode='1',
|
||||
size=(DisplayMetrics.WIDTH_PIXELS,
|
||||
DisplayMetrics.HEIGHT_PIXELS))
|
||||
def __init__(self, displayDevice):
|
||||
self._displayDevice = displayDevice
|
||||
self.clear()
|
||||
|
||||
def clear(self):
|
||||
size = self._displayDevice.dimensions
|
||||
self._bitmap = Image.new(mode='1', size=size)
|
||||
self._context = PIL.ImageDraw.Draw(self._bitmap)
|
||||
|
||||
def getContext(self):
|
||||
return PIL.ImageDraw.Draw(self._bitmap)
|
||||
return self._context
|
||||
|
||||
def commit(self):
|
||||
# convert to LPBM
|
||||
# upload to G13
|
||||
#
|
||||
pass
|
||||
self._displayDevice.update(self._bitmap)
|
||||
|
||||
def debug(self):
|
||||
self._bitmap.show()
|
||||
|
@ -1,30 +1,28 @@
|
||||
import unittest
|
||||
import time
|
||||
|
||||
import PIL.Image
|
||||
|
||||
from g13gui.bitwidgets.display import LPBM_LENGTH
|
||||
from g13gui.bitwidgets.display import ImageToLPBM
|
||||
from g13gui.bitwidgets.fonts import Fonts
|
||||
from g13gui.bitwidgets.fonts import FontManager
|
||||
from g13gui.bitwidgets.display import Display
|
||||
from g13gui.bitwidgets.display import DisplayMetrics
|
||||
from g13gui.bitwidgets.x11displaydevice import X11DisplayDevice
|
||||
|
||||
|
||||
class DisplayTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.d = Display()
|
||||
self.dd = X11DisplayDevice(self.__class__.__name__)
|
||||
self.dd.start()
|
||||
time.sleep(0.25)
|
||||
self.d = Display(self.dd)
|
||||
|
||||
def testConversion(self):
|
||||
def tearDown(self):
|
||||
time.sleep(1)
|
||||
self.dd.shutdown()
|
||||
self.dd.join()
|
||||
|
||||
def testUpdate(self):
|
||||
ctx = self.d.getContext()
|
||||
ctx.rectangle((0, 0, 160, 43), fill=1)
|
||||
ctx.text((0, 0), "Hello world!",
|
||||
font=FontManager.getFont(Fonts.HUGE), fill=0)
|
||||
result = ImageToLPBM(self.d._bitmap)
|
||||
ctx.line((0, 0)+(160, 48), fill=1)
|
||||
ctx.line((160, 0)+(0, 48), fill=1)
|
||||
self.d.commit()
|
||||
|
||||
self.assertEqual(len(result), LPBM_LENGTH)
|
||||
|
||||
with open('/run/g13d/in', 'wb') as fp:
|
||||
fp.write(result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
24
g13gui/g13gui/bitwidgets/displaydevice.py
Normal file
24
g13gui/g13gui/bitwidgets/displaydevice.py
Normal file
@ -0,0 +1,24 @@
|
||||
from builtins import property
|
||||
|
||||
|
||||
class DisplayDevice(object):
|
||||
"""Interface class to support updating displays with.
|
||||
|
||||
Note: Subclasses are expected to override the update method to display each
|
||||
frame.
|
||||
"""
|
||||
|
||||
@property
|
||||
def dimensions(self):
|
||||
"""Return the width and height of the display in pixels.
|
||||
|
||||
returns: a tuple of (x, y)
|
||||
"""
|
||||
raise NotImplementedError('Subclass did not override dimensions!')
|
||||
|
||||
def update(self, image):
|
||||
"""Update the display with the next frame.
|
||||
|
||||
image: PIL.Image, the next frame to display
|
||||
"""
|
||||
raise NotImplementedError('Subclass did not override update!')
|
@ -1,16 +1,28 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import unittest
|
||||
import time
|
||||
|
||||
from g13gui.bitwidgets.fonts import Fonts
|
||||
from g13gui.bitwidgets.fonts import FontManager
|
||||
from g13gui.bitwidgets.display import Display
|
||||
from g13gui.bitwidgets.x11displaydevice import X11DisplayDevice
|
||||
|
||||
|
||||
class FontsTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.dd = X11DisplayDevice(self.__class__.__name__)
|
||||
self.dd.start()
|
||||
time.sleep(0.25)
|
||||
self.d = Display(self.dd)
|
||||
|
||||
def tearDown(self):
|
||||
time.sleep(1)
|
||||
self.dd.shutdown()
|
||||
self.dd.join()
|
||||
|
||||
def testFontDrawing(self):
|
||||
d = Display()
|
||||
ctx = d.getContext()
|
||||
ctx = self.d.getContext()
|
||||
ctx.text((0, 0), "Hello world!",
|
||||
font=FontManager.getFont(Fonts.TINY),
|
||||
fill=(1))
|
||||
@ -26,7 +38,7 @@ class FontsTests(unittest.TestCase):
|
||||
ctx.text((0, 31), "Hello world!",
|
||||
font=FontManager.getFont(Fonts.HUGE),
|
||||
fill=(1))
|
||||
d.debug()
|
||||
self.d.commit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
80
g13gui/g13gui/bitwidgets/label.py
Normal file
80
g13gui/g13gui/bitwidgets/label.py
Normal file
@ -0,0 +1,80 @@
|
||||
import enum
|
||||
|
||||
from builtins import property
|
||||
from g13gui.bitwidgets.widget import Widget
|
||||
from g13gui.bitwidgets.fonts import Fonts
|
||||
from g13gui.bitwidgets.fonts import FontManager
|
||||
from g13gui.observer import ChangeType
|
||||
|
||||
|
||||
class Alignment(enum.Enum):
|
||||
LEFT = 'left'
|
||||
CENTER = 'center'
|
||||
RIGHT = 'right'
|
||||
|
||||
|
||||
class Label(Widget):
|
||||
def __init__(self, x, y, text,
|
||||
font=Fonts.MEDIUM,
|
||||
fill=True,
|
||||
spacing=4,
|
||||
align=Alignment.LEFT,
|
||||
strokeWidth=0):
|
||||
Widget.__init__(self)
|
||||
self.position = (x, y)
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.fill = fill
|
||||
self.spacing = spacing
|
||||
self.align = align
|
||||
self.strokeWidth = strokeWidth
|
||||
self.bounds = FontManager.getFont(self.font).getsize(self.text)
|
||||
|
||||
def draw(self, ctx):
|
||||
if self._visible:
|
||||
ctx.text(self.position, self.text,
|
||||
font=FontManager.getFont(self.font),
|
||||
fill=self.fill,
|
||||
spacing=self.spacing,
|
||||
align=self.align.value,
|
||||
stroke_width=self.strokeWidth)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._text
|
||||
|
||||
@property
|
||||
def font(self):
|
||||
return self._font
|
||||
|
||||
@property
|
||||
def spacing(self):
|
||||
return self._spacing
|
||||
|
||||
@property
|
||||
def align(self):
|
||||
return self._align
|
||||
|
||||
@property
|
||||
def strokeWidth(self):
|
||||
return self._strokeWidth
|
||||
|
||||
@text.setter
|
||||
def text(self, text):
|
||||
self.setProperty('text', text)
|
||||
|
||||
@font.setter
|
||||
def font(self, font):
|
||||
self.setProperty('font', font)
|
||||
|
||||
@spacing.setter
|
||||
def spacing(self, spacing):
|
||||
self.setProperty('spacing', spacing)
|
||||
|
||||
@align.setter
|
||||
def align(self, align):
|
||||
self.setProperty('align', align)
|
||||
|
||||
@strokeWidth.setter
|
||||
def strokeWidth(self, strokeWidth):
|
||||
self.setProperty('strokeWidth', strokeWidth)
|
30
g13gui/g13gui/bitwidgets/label_tests.py
Normal file
30
g13gui/g13gui/bitwidgets/label_tests.py
Normal file
@ -0,0 +1,30 @@
|
||||
import unittest
|
||||
import time
|
||||
|
||||
from g13gui.bitwidgets.display import Display
|
||||
from g13gui.bitwidgets.x11displaydevice import X11DisplayDevice
|
||||
from g13gui.bitwidgets.label import Label
|
||||
|
||||
|
||||
class LabelTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.dd = X11DisplayDevice(self.__class__.__name__)
|
||||
self.dd.start()
|
||||
time.sleep(0.25)
|
||||
self.d = Display(self.dd)
|
||||
|
||||
def tearDown(self):
|
||||
time.sleep(1)
|
||||
self.dd.shutdown()
|
||||
self.dd.join()
|
||||
|
||||
def testDraw(self):
|
||||
label = Label(0, 0, "Hello world!")
|
||||
ctx = self.d.getContext()
|
||||
label.show()
|
||||
label.draw(ctx)
|
||||
self.d.commit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
31
g13gui/g13gui/bitwidgets/screen.py
Normal file
31
g13gui/g13gui/bitwidgets/screen.py
Normal file
@ -0,0 +1,31 @@
|
||||
import time
|
||||
|
||||
from builtins import property
|
||||
from g13gui.bitwidgets.widget import Widget
|
||||
from g13gui.bitwidgets.button import ButtonBar
|
||||
|
||||
|
||||
MIN_NSECS_BETWEEN_FRAMES = 125000
|
||||
|
||||
|
||||
class Screen(Widget):
|
||||
def __init__(self, display):
|
||||
Widget.__init__(self)
|
||||
self._display = display
|
||||
self._buttonBar = ButtonBar()
|
||||
|
||||
self.visible = True
|
||||
|
||||
@property
|
||||
def buttonBar(self):
|
||||
return self._buttonBar
|
||||
|
||||
def draw(self, ctx):
|
||||
Widget.draw(self, ctx)
|
||||
self._buttonBar.draw(ctx)
|
||||
|
||||
def nextFrame(self):
|
||||
self._display.clear()
|
||||
ctx = self._display.getContext()
|
||||
self.draw(ctx)
|
||||
self._display.commit()
|
32
g13gui/g13gui/bitwidgets/screen_tests.py
Normal file
32
g13gui/g13gui/bitwidgets/screen_tests.py
Normal file
@ -0,0 +1,32 @@
|
||||
import unittest
|
||||
import time
|
||||
|
||||
from g13gui.bitwidgets.display import Display
|
||||
from g13gui.bitwidgets.x11displaydevice import X11DisplayDevice
|
||||
from g13gui.bitwidgets.screen import Screen
|
||||
|
||||
|
||||
class ScreenTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.dd = X11DisplayDevice(self.__class__.__name__)
|
||||
self.dd.start()
|
||||
time.sleep(0.25)
|
||||
self.d = Display(self.dd)
|
||||
self.s = Screen(self.d)
|
||||
|
||||
def tearDown(self):
|
||||
time.sleep(1)
|
||||
self.dd.shutdown()
|
||||
self.dd.join()
|
||||
|
||||
def testDraw(self):
|
||||
ctx = self.d.getContext()
|
||||
self.s.draw(ctx)
|
||||
self.d.commit()
|
||||
|
||||
def testNextFrame(self):
|
||||
self.s.nextFrame()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
101
g13gui/g13gui/bitwidgets/widget.py
Normal file
101
g13gui/g13gui/bitwidgets/widget.py
Normal file
@ -0,0 +1,101 @@
|
||||
from builtins import property
|
||||
from g13gui.observer import Subject
|
||||
from g13gui.observer import Observer
|
||||
from g13gui.observer import ChangeType
|
||||
|
||||
|
||||
class Widget(Subject, Observer):
|
||||
def __init__(self):
|
||||
Subject.__init__(self)
|
||||
Observer.__init__(self)
|
||||
|
||||
self._children = []
|
||||
self.parent = None
|
||||
self.visible = False
|
||||
self.valid = False
|
||||
self.position = (0, 0)
|
||||
self.bounds = (0, 0)
|
||||
self.fill = False
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return self._position
|
||||
|
||||
@position.setter
|
||||
def position(self, xy):
|
||||
if type(xy) != tuple or \
|
||||
len(xy) != 2 or \
|
||||
type(xy[0]) != int or \
|
||||
type(xy[1]) != int:
|
||||
raise ValueError('Position must be a tuple of length 2')
|
||||
self.setProperty('position', xy)
|
||||
|
||||
@property
|
||||
def bounds(self):
|
||||
return self._bounds
|
||||
|
||||
@bounds.setter
|
||||
def bounds(self, wh):
|
||||
if type(wh) != tuple or \
|
||||
len(wh) != 2 or \
|
||||
type(wh[0]) != int or \
|
||||
type(wh[1]) != int:
|
||||
raise ValueError('Position must be a tuple of length 2')
|
||||
self.setProperty('bounds', wh)
|
||||
|
||||
@property
|
||||
def fill(self):
|
||||
return True if self._fill else False
|
||||
|
||||
@fill.setter
|
||||
def fill(self, fill):
|
||||
fill = 1 if fill else 0
|
||||
self.setProperty('fill', fill)
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
self.setProperty('parent', parent)
|
||||
|
||||
def addChild(self, child):
|
||||
self._children.append(child)
|
||||
self._children.parent = self
|
||||
child.registerObserver(self, 'valid')
|
||||
self.addChange(ChangeType.ADD, 'child', child)
|
||||
self.notifyChange()
|
||||
|
||||
def removeChild(self, child):
|
||||
child.removeObserver(self)
|
||||
self._children.remove(child)
|
||||
child.parent = None
|
||||
self.addChange(ChangeType.REMOVE, 'child', child)
|
||||
self.notifyChange()
|
||||
|
||||
@property
|
||||
def visible(self):
|
||||
return self._visible
|
||||
|
||||
@visible.setter
|
||||
def visible(self, visible):
|
||||
self.setProperty('visible', visible)
|
||||
|
||||
def show(self):
|
||||
self.visible = True
|
||||
|
||||
def hide(self):
|
||||
self.visible = False
|
||||
|
||||
def show_all(self):
|
||||
for child in self._children:
|
||||
if child:
|
||||
child.show()
|
||||
self.visible = True
|
||||
|
||||
def draw(self, ctx):
|
||||
if self._visible:
|
||||
for child in self._children:
|
||||
if child.visible():
|
||||
child.draw(ctx)
|
79
g13gui/g13gui/bitwidgets/x11displaydevice.py
Normal file
79
g13gui/g13gui/bitwidgets/x11displaydevice.py
Normal file
@ -0,0 +1,79 @@
|
||||
import PIL
|
||||
import threading
|
||||
import queue
|
||||
from Xlib import X, display, Xutil
|
||||
from builtins import property
|
||||
|
||||
from g13gui.bitwidgets.displaydevice import DisplayDevice
|
||||
|
||||
|
||||
class X11DisplayDevice(DisplayDevice, threading.Thread):
|
||||
def __init__(self, name="BitWidgets"):
|
||||
threading.Thread.__init__(self, daemon=True)
|
||||
self._queue = queue.Queue()
|
||||
self._running = False
|
||||
self._name = name
|
||||
|
||||
def run(self):
|
||||
self._display = display.Display()
|
||||
self.createWindow()
|
||||
self._running = True
|
||||
|
||||
while self._running:
|
||||
while self._display.pending_events():
|
||||
self._display.next_event()
|
||||
|
||||
image = self._queue.get()
|
||||
if image is None:
|
||||
self._running = False
|
||||
self._display.close()
|
||||
return
|
||||
|
||||
points = []
|
||||
for x in range(0, 160):
|
||||
for y in range(0, 48):
|
||||
if image.getpixel((x, y)) == 1:
|
||||
points.append((x, y))
|
||||
|
||||
self._win.fill_rectangle(self._inversegc, 0, 0, 160, 48)
|
||||
self._win.poly_point(self._gc, X.CoordModeOrigin, points)
|
||||
|
||||
def createWindow(self):
|
||||
self._screen = self._display.screen()
|
||||
self._win = self._screen.root.create_window(
|
||||
0, 0, 160, 48, 2,
|
||||
self._screen.root_depth,
|
||||
X.InputOutput,
|
||||
X.CopyFromParent,
|
||||
background_pixel=self._screen.black_pixel,
|
||||
event_mask=(X.ExposureMask | X.StructureNotifyMask),
|
||||
colormap=X.CopyFromParent)
|
||||
self._gc = self._win.create_gc(
|
||||
foreground=self._screen.white_pixel,
|
||||
background=self._screen.black_pixel)
|
||||
self._inversegc = self._win.create_gc(
|
||||
foreground=self._screen.black_pixel,
|
||||
background=self._screen.white_pixel)
|
||||
|
||||
self._win.set_wm_name(self._name)
|
||||
self._win.set_wm_icon_name(self._name)
|
||||
self._win.set_wm_class('bitwidgets', self._name)
|
||||
self._win.set_wm_normal_hints(
|
||||
flags=(Xutil.PPosition | Xutil.PSize | Xutil.PMinSize),
|
||||
min_width=160,
|
||||
min_height=48)
|
||||
self._win.map()
|
||||
|
||||
@property
|
||||
def dimensions(self):
|
||||
return (160, 48)
|
||||
|
||||
def update(self, image):
|
||||
if not self._running:
|
||||
raise RuntimeError('X11DisplayDevice is not running -- '
|
||||
'cannot update.')
|
||||
|
||||
self._queue.put(image)
|
||||
|
||||
def shutdown(self):
|
||||
self._queue.put(None)
|
Loading…
Reference in New Issue
Block a user