From 1212ce91de7eb21d997527e45abba64f12ef18e8 Mon Sep 17 00:00:00 2001 From: June Tate-Gans Date: Sun, 9 May 2021 14:05:18 -0500 Subject: [PATCH] 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. --- g13gui/applets/clock.py | 83 +++++++++++++++++++++++++-- g13gui/bitwidgets/button.py | 95 +++++++++++++++++++++++++++++++ g13gui/bitwidgets/button_tests.py | 7 ++- g13gui/bitwidgets/graph.py | 59 +++++++++++++++++++ g13gui/bitwidgets/label.py | 19 ++++--- 5 files changed, 247 insertions(+), 16 deletions(-) create mode 100644 g13gui/bitwidgets/graph.py diff --git a/g13gui/applets/clock.py b/g13gui/applets/clock.py index 0521166..63dba9e 100644 --- a/g13gui/applets/clock.py +++ b/g13gui/applets/clock.py @@ -1,15 +1,26 @@ import gi import time +import enum +import psutil from g13gui.applet.applet import Applet +from g13gui.applet.applet import Buttons from g13gui.applet.applet import RunApplet from g13gui.bitwidgets.label import Label 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') from gi.repository import GLib +class ClockMode(enum.Enum): + HOUR_12 = 1 + HOUR_24 = 2 + + class ClockApplet(Applet): NAME = 'Clock' @@ -20,22 +31,84 @@ class ClockApplet(Applet): self._timeLabel.showAll() 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_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): - self._updateTimeLabel() + self._update() self.maybePresentScreen() return self.screen.visible def onShown(self, timestamp): - self._updateTimeLabel() + self._update() 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__': RunApplet(ClockApplet) diff --git a/g13gui/bitwidgets/button.py b/g13gui/bitwidgets/button.py index b5ab673..a178340 100644 --- a/g13gui/bitwidgets/button.py +++ b/g13gui/bitwidgets/button.py @@ -1,14 +1,18 @@ from builtins import property from g13gui.bitwidgets.widget import Widget +from g13gui.bitwidgets.label import Label from g13gui.bitwidgets.glyph import Glyph +from g13gui.bitwidgets.glyph import Glyphs from g13gui.bitwidgets.rectangle import Rectangle +from g13gui.bitwidgets.fonts import Fonts from g13gui.observer.subject import ChangeType class Button(Widget): def __init__(self, glyph, fill=True): Widget.__init__(self) + self._rect = Rectangle(*self.position, *self.bounds, fill=False) self._rect.show() self.addChild(self._rect) @@ -60,3 +64,94 @@ class Button(Widget): @glyph.setter def glyph(self, 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 diff --git a/g13gui/bitwidgets/button_tests.py b/g13gui/bitwidgets/button_tests.py index ed91d7e..0f88da8 100644 --- a/g13gui/bitwidgets/button_tests.py +++ b/g13gui/bitwidgets/button_tests.py @@ -5,8 +5,9 @@ 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.button import Glyph +from g13gui.bitwidgets.button import LabelButton +from g13gui.bitwidgets.glyph import Glyphs +from g13gui.bitwidgets.glyph import Glyph from g13gui.bitwidgets.label import Label from g13gui.bitwidgets.fonts import Fonts @@ -72,7 +73,7 @@ class ButtonTests(unittest.TestCase): def testLabelButton(self): self.dd.name = 'testLabelButton' - testButton = Label(0, 0, "Test", font=Fonts.TINY) + testButton = LabelButton("Test") self.screen.buttonBar.addChild(testButton) self.screen.buttonBar.showAll() self.screen.nextFrame() diff --git a/g13gui/bitwidgets/graph.py b/g13gui/bitwidgets/graph.py new file mode 100644 index 0000000..d3e9f63 --- /dev/null +++ b/g13gui/bitwidgets/graph.py @@ -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) diff --git a/g13gui/bitwidgets/label.py b/g13gui/bitwidgets/label.py index 55496a0..533e30e 100644 --- a/g13gui/bitwidgets/label.py +++ b/g13gui/bitwidgets/label.py @@ -21,14 +21,14 @@ class Label(Widget): 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) + 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: @@ -62,14 +62,17 @@ class Label(Widget): @text.setter def text(self, text): self.setProperty('text', text) + self.bounds = FontManager.getFont(self.font).getsize(self.text) @font.setter def font(self, font): self.setProperty('font', font) + self.bounds = FontManager.getFont(self.font).getsize(self.text) @spacing.setter def spacing(self, spacing): self.setProperty('spacing', spacing) + self.bounds = FontManager.getFont(self.font).getsize(self.text) @align.setter def align(self, align):