From 527489e0de27ff5057155a980cf47782a9deb82e Mon Sep 17 00:00:00 2001 From: Tobi-wan Kenobi Date: Fri, 9 Dec 2016 12:28:39 +0100 Subject: [PATCH] [core/themes] Add "cycling" support Allow a theme to define a "cycle" of attributes that are cycled through on a widget-per-widget basis (e.g. for alternating the widget background). These cycles take precedence over the default values, but can be overridden by module-specific theme instructions. see #23 --- bumblebee/output.py | 1 + bumblebee/theme.py | 38 ++++++++++++++++++------ tests/test_theme.py | 51 +++++++++++++++++++++------------ tests/util.py | 3 ++ themes/solarized-powerline.json | 8 +++--- themes/test_cycle.json | 18 ++++++++++++ 6 files changed, 87 insertions(+), 32 deletions(-) create mode 100644 themes/test_cycle.json diff --git a/bumblebee/output.py b/bumblebee/output.py index 5d9e0df..d3ea0f4 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -57,6 +57,7 @@ class I3BarOutput(object): def begin(self): """Start one output iteration""" self._widgets = [] + self._theme.reset() def flush(self): """Flushes output""" diff --git a/bumblebee/theme.py b/bumblebee/theme.py index 8d90b3f..a4d40c6 100644 --- a/bumblebee/theme.py +++ b/bumblebee/theme.py @@ -16,6 +16,7 @@ class Theme(object): """Represents a collection of icons and colors""" def __init__(self, name): self._init(self.load(name)) + self._widget = None def _init(self, data): """Initialize theme from data structure""" @@ -23,14 +24,30 @@ class Theme(object): self._merge(data, self._load_icons(iconset)) self._theme = data self._defaults = data.get("defaults", {}) + self._cycles = self._theme.get("cycle", []) + self.reset() + + def data(self): + """Return the raw theme data""" + return self._theme + + def reset(self): + """Reset theme to initial state""" + self._cycle = self._cycles[0] if len(self._cycles) > 0 else {} + self._cycle_idx = 0 + self._widget = None def prefix(self, widget): """Return the theme prefix for a widget's full text""" - return self._get(widget, "prefix", None) + padding = self._get(widget, "padding", "") + pre = self._get(widget, "prefix", None) + return u"{}{}{}".format(padding, pre, padding) if pre else None def suffix(self, widget): """Return the theme suffix for a widget's full text""" - return self._get(widget, "suffix", None) + padding = self._get(widget, "padding", "") + suf = self._get(widget, "suffix", None) + return u"{}{}{}".format(padding, suf, padding) if suf else None def fg(self, widget): """Return the foreground color for this widget""" @@ -65,18 +82,21 @@ class Theme(object): def _get(self, widget, name, default=None): """Return the config value 'name' for 'widget'""" + + if not self._widget: + self._widget = widget + + if self._widget != widget: + self._widget = widget + self._cycle_idx = (self._cycle_idx + 1) % len(self._cycles) + self._cycle = self._cycles[self._cycle_idx] + module_theme = self._theme.get(widget.module, {}) - padding = None - if name != "padding": - padding = self._get(widget, "padding") - value = self._defaults.get(name, default) + value = self._cycle.get(name, value) value = module_theme.get(name, value) - if value and padding: - value = u"{}{}{}".format(padding, value, padding) - return value # algorithm copied from diff --git a/tests/test_theme.py b/tests/test_theme.py index 4ae9b50..b039d17 100644 --- a/tests/test_theme.py +++ b/tests/test_theme.py @@ -10,18 +10,22 @@ class TestTheme(unittest.TestCase): self.nonexistentThemeName = "no-such-theme" self.invalidThemeName = "invalid" self.validThemeName = "test" - self.someWidget = MockWidget("foo") + self.themedWidget = MockWidget("foo") self.theme = Theme(self.validThemeName) + self.cycleTheme = Theme("test_cycle") + self.anyWidget = MockWidget("bla") + self.anotherWidget = MockWidget("blub") + data = self.theme.data() self.widgetTheme = "test-widget" - self.defaultColor = "#000000" - self.defaultBgColor = "#111111" - self.widgetBgColor = "#222222" - self.defaultPrefix = "default-prefix" - self.defaultSuffix = "default-suffix" - self.widgetPrefix = "widget-prefix" - self.widgetSuffix = "widget-suffix" - self.widgetColor = "#ababab" + self.defaultColor = data["defaults"]["fg"] + self.defaultBgColor = data["defaults"]["bg"] + self.widgetColor = data[self.widgetTheme]["fg"] + self.widgetBgColor = data[self.widgetTheme]["bg"] + self.defaultPrefix = data["defaults"]["prefix"] + self.defaultSuffix = data["defaults"]["suffix"] + self.widgetPrefix = data[self.widgetTheme]["prefix"] + self.widgetSuffix = data[self.widgetTheme]["suffix"] def test_load_valid_theme(self): try: @@ -38,23 +42,32 @@ class TestTheme(unittest.TestCase): Theme(self.invalidThemeName) def test_default_prefix(self): - self.assertEquals(self.theme.prefix(self.someWidget), self.defaultPrefix) + self.assertEquals(self.theme.prefix(self.themedWidget), self.defaultPrefix) def test_default_suffix(self): - self.assertEquals(self.theme.suffix(self.someWidget), self.defaultSuffix) + self.assertEquals(self.theme.suffix(self.themedWidget), self.defaultSuffix) def test_widget_prefix(self): - self.someWidget.module = self.widgetTheme - self.assertEquals(self.theme.prefix(self.someWidget), self.widgetPrefix) + self.themedWidget.module = self.widgetTheme + self.assertEquals(self.theme.prefix(self.themedWidget), self.widgetPrefix) def test_widget_fg(self): - self.assertEquals(self.theme.fg(self.someWidget), self.defaultColor) - self.someWidget.module = self.widgetTheme - self.assertEquals(self.theme.fg(self.someWidget), self.widgetColor) + self.assertEquals(self.theme.fg(self.themedWidget), self.defaultColor) + self.themedWidget.module = self.widgetTheme + self.assertEquals(self.theme.fg(self.themedWidget), self.widgetColor) def test_widget_bg(self): - self.assertEquals(self.theme.bg(self.someWidget), self.defaultBgColor) - self.someWidget.module = self.widgetTheme - self.assertEquals(self.theme.bg(self.someWidget), self.widgetBgColor) + self.assertEquals(self.theme.bg(self.themedWidget), self.defaultBgColor) + self.themedWidget.module = self.widgetTheme + self.assertEquals(self.theme.bg(self.themedWidget), self.widgetBgColor) + + def test_reset(self): + theme = self.cycleTheme + data = theme.data() + theme.reset() + self.assertEquals(theme.fg(self.anyWidget), data["cycle"][0]["fg"]) + self.assertEquals(theme.fg(self.anotherWidget), data["cycle"][1]["fg"]) + theme.reset() + self.assertEquals(theme.fg(self.anyWidget), data["cycle"][0]["fg"]) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/util.py b/tests/util.py index 33a65d8..3a8095d 100644 --- a/tests/util.py +++ b/tests/util.py @@ -46,6 +46,9 @@ class MockTheme(object): self.attr_fg = None self.attr_bg = None + def reset(self): + pass + def prefix(self, widget): return self.attr_prefix diff --git a/themes/solarized-powerline.json b/themes/solarized-powerline.json index 1326777..22b6faf 100644 --- a/themes/solarized-powerline.json +++ b/themes/solarized-powerline.json @@ -3,10 +3,6 @@ "defaults": { "default-separators": false, "separator-block-width": 0, - "cycle": [ - { "fg": "#93a1a1", "bg": "#002b36" }, - { "fg": "#eee8d5", "bg": "#586e75" } - ], "warning": { "fg": "#002b36", "bg": "#b58900" @@ -16,6 +12,10 @@ "bg": "#dc322f" } }, + "cycle": [ + { "fg": "#93a1a1", "bg": "#002b36" }, + { "fg": "#eee8d5", "bg": "#586e75" } + ], "dnf": { "good": { "fg": "#002b36", diff --git a/themes/test_cycle.json b/themes/test_cycle.json new file mode 100644 index 0000000..5fd7e1a --- /dev/null +++ b/themes/test_cycle.json @@ -0,0 +1,18 @@ +{ + "icons": [ "test" ], + "defaults": { + "prefix": "default-prefix", + "suffix": "default-suffix", + "fg": "#000000", + "bg": "#111111" + }, + "cycle": [ + { "fg": "#aa0000" }, + { "fg": "#00aa00" }, + { "fg": "#0000aa" } + ], + "test-widget": { + "fg": "#ababab", + "bg": "#222222" + } +}