clock: Add 24-hour toggle, load, ram graphs

This adds a ton of new functionality to the clock widget, and exercises the
bitwidgets widgets a bit more. This adds a new button widget, called a
LabelWidget. These are pretty unsurprising -- they're just a label inside of a
button slot in the buttonbar. They do give us the ability to toggle and add
"more" glyphs in the button for more flexibility in the buttonbar.

  - Make Clock toggle between 12 and 24 hour clock modes.
  - Add CPU load and RAM load histograms.
  - Create a new button type, LabelButton.
  - Create a new widget, the Graph, which shows a histogram of values over
    time.
  - Make Button not assign values by way of accessors in the constructor.
  - Make Label actually dynamically change its bounds based upon properties.
This commit is contained in:
June Tate-Gans 2021-05-09 14:05:18 -05:00
parent d8fa49c1ed
commit 1212ce91de
5 changed files with 247 additions and 16 deletions

View File

@ -1,15 +1,26 @@
import gi import gi
import time import time
import enum
import psutil
from g13gui.applet.applet import Applet from g13gui.applet.applet import Applet
from g13gui.applet.applet import Buttons
from g13gui.applet.applet import RunApplet from g13gui.applet.applet import RunApplet
from g13gui.bitwidgets.label import Label from g13gui.bitwidgets.label import Label
from g13gui.bitwidgets.fonts import Fonts from g13gui.bitwidgets.fonts import Fonts
from g13gui.bitwidgets.button import LabelButton
from g13gui.bitwidgets.graph import Graph
from g13gui.bitwidgets import DISPLAY_WIDTH
gi.require_version('GLib', '2.0') gi.require_version('GLib', '2.0')
from gi.repository import GLib from gi.repository import GLib
class ClockMode(enum.Enum):
HOUR_12 = 1
HOUR_24 = 2
class ClockApplet(Applet): class ClockApplet(Applet):
NAME = 'Clock' NAME = 'Clock'
@ -20,22 +31,84 @@ class ClockApplet(Applet):
self._timeLabel.showAll() self._timeLabel.showAll()
self.screen.addChild(self._timeLabel) self.screen.addChild(self._timeLabel)
self._updateTimeLabel() self._modeButtons = {
ClockMode.HOUR_12: LabelButton('12 hour'),
ClockMode.HOUR_24: LabelButton('24 hour')
}
self._clockMode = ClockMode.HOUR_24
self._onModeSwitch()
def _updateTimeLabel(self): self._loadGraphToggle = LabelButton('Load', isToggleable=True)
self._ramGraphToggle = LabelButton('RAM', isToggleable=True)
self.screen.buttonBar.addChild(self._loadGraphToggle)
self.screen.buttonBar.addChild(self._ramGraphToggle)
self.screen.buttonBar.showAll()
self._loadGraph = Graph(1, 18,
DISPLAY_WIDTH // 2 - 5, 12)
self.screen.addChild(self._loadGraph)
self._ramGraph = Graph(DISPLAY_WIDTH // 2 + 5, 18,
DISPLAY_WIDTH // 2 - 7, 12)
self.screen.addChild(self._ramGraph)
self._update()
def _update(self):
(tm_year, tm_month, tm_mday, tm_hour, (tm_year, tm_month, tm_mday, tm_hour,
tm_min, tm_sec, tm_wday, tm_yday, tm_isdst) = time.localtime() tm_min, tm_sec, tm_wday, tm_yday, tm_isdst) = time.localtime()
self._timeLabel.text = '%d:%0.2d:%0.2d' % (tm_hour, tm_min, tm_sec) ampm = ''
if self._clockMode == ClockMode.HOUR_12:
ampm = ' AM'
if tm_hour >= 12:
ampm = ' PM'
if tm_hour > 12:
tm_hour -= 12
self._timeLabel.text = '%d:%0.2d:%0.2d%s' % (
tm_hour, tm_min, tm_sec, ampm)
(w, h) = self._timeLabel.bounds
x = (DISPLAY_WIDTH // 2) - (w // 2)
self._timeLabel.position = (x, 0)
self._loadGraph.addValue(psutil.cpu_percent())
self._ramGraph.addValue(psutil.virtual_memory().percent / 100)
def _pushTime(self): def _pushTime(self):
self._updateTimeLabel() self._update()
self.maybePresentScreen() self.maybePresentScreen()
return self.screen.visible return self.screen.visible
def onShown(self, timestamp): def onShown(self, timestamp):
self._updateTimeLabel() self._update()
GLib.timeout_add_seconds(1, self._pushTime) GLib.timeout_add_seconds(1, self._pushTime)
def onUpdateScreen(self):
self._update()
def _onModeSwitch(self):
if self._clockMode == ClockMode.HOUR_12:
self._clockMode = ClockMode.HOUR_24
button = self._modeButtons[ClockMode.HOUR_12]
elif self._clockMode == ClockMode.HOUR_24:
self._clockMode = ClockMode.HOUR_12
button = self._modeButtons[ClockMode.HOUR_24]
button.show()
self.screen.buttonBar.setButton(0, button)
def onKeyReleased(self, timestamp, key):
if key == Buttons.L1:
self._onModeSwitch()
elif key == Buttons.L2:
self._loadGraphToggle.toggle()
self._loadGraph.visible = self._loadGraphToggle.isOn
elif key == Buttons.L3:
self._ramGraphToggle.toggle()
self._ramGraph.visible = self._ramGraphToggle.isOn
if __name__ == '__main__': if __name__ == '__main__':
RunApplet(ClockApplet) RunApplet(ClockApplet)

View File

@ -1,14 +1,18 @@
from builtins import property from builtins import property
from g13gui.bitwidgets.widget import Widget from g13gui.bitwidgets.widget import Widget
from g13gui.bitwidgets.label import Label
from g13gui.bitwidgets.glyph import Glyph from g13gui.bitwidgets.glyph import Glyph
from g13gui.bitwidgets.glyph import Glyphs
from g13gui.bitwidgets.rectangle import Rectangle from g13gui.bitwidgets.rectangle import Rectangle
from g13gui.bitwidgets.fonts import Fonts
from g13gui.observer.subject import ChangeType from g13gui.observer.subject import ChangeType
class Button(Widget): class Button(Widget):
def __init__(self, glyph, fill=True): def __init__(self, glyph, fill=True):
Widget.__init__(self) Widget.__init__(self)
self._rect = Rectangle(*self.position, *self.bounds, fill=False) self._rect = Rectangle(*self.position, *self.bounds, fill=False)
self._rect.show() self._rect.show()
self.addChild(self._rect) self.addChild(self._rect)
@ -60,3 +64,94 @@ class Button(Widget):
@glyph.setter @glyph.setter
def glyph(self, glyph): def glyph(self, glyph):
self.setProperty('glyph', glyph) self.setProperty('glyph', glyph)
class LabelButton(Button):
def __init__(self, text, isToggleable=False, hasMore=False, fill=True):
Button.__init__(self, Glyphs.BLANK, fill)
self._isOn = False
self._isToggleable = isToggleable
self._hasMore = hasMore
self.removeChild(self._glyph)
self._glyph = Label(*self.position, text, font=Fonts.TINY)
self._glyph.show()
self.addChild(self._glyph)
self._moreGlyph = Glyph(0, 0, Glyphs.CHEVRON_RIGHT)
self.addChild(self._moreGlyph)
self._toggleGlyph = Glyph(0, 0, Glyphs.BOX)
self.addChild(self._toggleGlyph)
self.updateGlyphs()
def updateGlyphs(self):
if self.isOn:
self._toggleGlyph.glyph = Glyphs.FILLED_BOX
else:
self._toggleGlyph.glyph = Glyphs.BOX
self._moreGlyph.visible = self.hasMore
self._toggleGlyph.visible = self.isToggleable
def _updatePositionAndBounds(self, subject, changeType, key, data):
super()._updatePositionAndBounds(subject, changeType, key, data)
(x, y) = self.position
(w, h) = self.bounds
(glyphW, glyphH) = self._moreGlyph.bounds
moreX = x + w - glyphW - 1
moreY = y + (h // 2) - (glyphH // 2)
self._moreGlyph.position = (moreX, moreY)
toggleX = x + 1
toggleY = y + (h // 2) - (glyphH // 2)
self._toggleGlyph.position = (toggleX, toggleY)
(labelX, labelY) = self._glyph.position
labelY += 1
self._glyph.position = (labelX, labelY)
@property
def isToggleable(self):
return self._isToggleable
@isToggleable.setter
def isToggleable(self, value):
self.setProperty('isToggleable', value)
self.updateGlyphs()
@property
def isOn(self):
return self._isOn
@isOn.setter
def isOn(self, value):
self.setProperty('isOn', value)
self.updateGlyphs()
@property
def isOff(self):
return not self._isOn
def toggle(self):
self.setProperty('isOn', not self.isOn)
self.updateGlyphs()
@property
def hasMore(self):
return self._hasMore
@hasMore.setter
def hasMore(self, value):
self.setProperty('hasMore', value)
self.updateGlyphs()
@property
def text(self):
return self._glyph.text
@text.setter
def text(self, text):
self._glyph.text = text

View File

@ -5,8 +5,9 @@ from g13gui.bitwidgets.display import Display
from g13gui.bitwidgets.x11displaydevice import X11DisplayDevice from g13gui.bitwidgets.x11displaydevice import X11DisplayDevice
from g13gui.bitwidgets.screen import Screen from g13gui.bitwidgets.screen import Screen
from g13gui.bitwidgets.button import Button from g13gui.bitwidgets.button import Button
from g13gui.bitwidgets.button import Glyphs from g13gui.bitwidgets.button import LabelButton
from g13gui.bitwidgets.button import Glyph from g13gui.bitwidgets.glyph import Glyphs
from g13gui.bitwidgets.glyph import Glyph
from g13gui.bitwidgets.label import Label from g13gui.bitwidgets.label import Label
from g13gui.bitwidgets.fonts import Fonts from g13gui.bitwidgets.fonts import Fonts
@ -72,7 +73,7 @@ class ButtonTests(unittest.TestCase):
def testLabelButton(self): def testLabelButton(self):
self.dd.name = 'testLabelButton' self.dd.name = 'testLabelButton'
testButton = Label(0, 0, "Test", font=Fonts.TINY) testButton = LabelButton("Test")
self.screen.buttonBar.addChild(testButton) self.screen.buttonBar.addChild(testButton)
self.screen.buttonBar.showAll() self.screen.buttonBar.showAll()
self.screen.nextFrame() self.screen.nextFrame()

View File

@ -0,0 +1,59 @@
from builtins import property
from g13gui.bitwidgets.widget import Widget
from g13gui.bitwidgets.rectangle import Rectangle
class Graph(Widget):
def __init__(self, x, y, w, h):
Widget.__init__(self)
self._rect = Rectangle(x, y, w, h, fill=True)
self.addChild(self._rect)
self.position = (x, y)
self.bounds = (w, h)
self._timeseries = [0] * w
def addValue(self, value):
if value > 1 or value < 0:
value = 0
(w, h) = self.bounds
self._timeseries.append(value)
if len(self._timeseries) > w:
del self._timeseries[0]
@property
def bounds(self):
return self._bounds
@bounds.setter
def bounds(self, wh):
self.setProperty('bounds', wh)
self._rect.bounds = wh
self._timeseries = [0] * wh[0]
def draw(self, ctx):
super().draw(ctx)
(x, y) = self.position
(w, h) = self.bounds
(t, b, l, r) = (y, y + h, x, x + w)
tl = (l, t)
bl = (l, b)
br = (r, b)
if self.visible:
ctx.line(tl+bl, fill=1)
ctx.line(bl+br, fill=1)
for idx, value in enumerate(self._timeseries):
xoffs = idx + x
scaledValue = int(value * h)
yoffs = b - scaledValue
points = (xoffs, yoffs,
xoffs, b)
ctx.line(points, fill=1)

View File

@ -21,14 +21,14 @@ class Label(Widget):
align=Alignment.LEFT, align=Alignment.LEFT,
strokeWidth=0): strokeWidth=0):
Widget.__init__(self) Widget.__init__(self)
self.position = (x, y) self._position = (x, y)
self.text = text self._text = text
self.font = font self._font = font
self.fill = fill self._fill = fill
self.spacing = spacing self._spacing = spacing
self.align = align self._align = align
self.strokeWidth = strokeWidth self._strokeWidth = strokeWidth
self.bounds = FontManager.getFont(self.font).getsize(self.text) self._bounds = FontManager.getFont(self.font).getsize(self.text)
def draw(self, ctx): def draw(self, ctx):
if self._visible: if self._visible:
@ -62,14 +62,17 @@ class Label(Widget):
@text.setter @text.setter
def text(self, text): def text(self, text):
self.setProperty('text', text) self.setProperty('text', text)
self.bounds = FontManager.getFont(self.font).getsize(self.text)
@font.setter @font.setter
def font(self, font): def font(self, font):
self.setProperty('font', font) self.setProperty('font', font)
self.bounds = FontManager.getFont(self.font).getsize(self.text)
@spacing.setter @spacing.setter
def spacing(self, spacing): def spacing(self, spacing):
self.setProperty('spacing', spacing) self.setProperty('spacing', spacing)
self.bounds = FontManager.getFont(self.font).getsize(self.text)
@align.setter @align.setter
def align(self, align): def align(self, align):