[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
This commit is contained in:
Tobi-wan Kenobi 2016-12-09 12:28:39 +01:00
parent 59fb47ae3b
commit 527489e0de
6 changed files with 87 additions and 32 deletions

View file

@ -57,6 +57,7 @@ class I3BarOutput(object):
def begin(self): def begin(self):
"""Start one output iteration""" """Start one output iteration"""
self._widgets = [] self._widgets = []
self._theme.reset()
def flush(self): def flush(self):
"""Flushes output""" """Flushes output"""

View file

@ -16,6 +16,7 @@ class Theme(object):
"""Represents a collection of icons and colors""" """Represents a collection of icons and colors"""
def __init__(self, name): def __init__(self, name):
self._init(self.load(name)) self._init(self.load(name))
self._widget = None
def _init(self, data): def _init(self, data):
"""Initialize theme from data structure""" """Initialize theme from data structure"""
@ -23,14 +24,30 @@ class Theme(object):
self._merge(data, self._load_icons(iconset)) self._merge(data, self._load_icons(iconset))
self._theme = data self._theme = data
self._defaults = data.get("defaults", {}) 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): def prefix(self, widget):
"""Return the theme prefix for a widget's full text""" """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): def suffix(self, widget):
"""Return the theme suffix for a widget's full text""" """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): def fg(self, widget):
"""Return the foreground color for this widget""" """Return the foreground color for this widget"""
@ -65,18 +82,21 @@ class Theme(object):
def _get(self, widget, name, default=None): def _get(self, widget, name, default=None):
"""Return the config value 'name' for 'widget'""" """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, {}) 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._defaults.get(name, default)
value = self._cycle.get(name, value)
value = module_theme.get(name, value) value = module_theme.get(name, value)
if value and padding:
value = u"{}{}{}".format(padding, value, padding)
return value return value
# algorithm copied from # algorithm copied from

View file

@ -10,18 +10,22 @@ class TestTheme(unittest.TestCase):
self.nonexistentThemeName = "no-such-theme" self.nonexistentThemeName = "no-such-theme"
self.invalidThemeName = "invalid" self.invalidThemeName = "invalid"
self.validThemeName = "test" self.validThemeName = "test"
self.someWidget = MockWidget("foo") self.themedWidget = MockWidget("foo")
self.theme = Theme(self.validThemeName) 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.widgetTheme = "test-widget"
self.defaultColor = "#000000" self.defaultColor = data["defaults"]["fg"]
self.defaultBgColor = "#111111" self.defaultBgColor = data["defaults"]["bg"]
self.widgetBgColor = "#222222" self.widgetColor = data[self.widgetTheme]["fg"]
self.defaultPrefix = "default-prefix" self.widgetBgColor = data[self.widgetTheme]["bg"]
self.defaultSuffix = "default-suffix" self.defaultPrefix = data["defaults"]["prefix"]
self.widgetPrefix = "widget-prefix" self.defaultSuffix = data["defaults"]["suffix"]
self.widgetSuffix = "widget-suffix" self.widgetPrefix = data[self.widgetTheme]["prefix"]
self.widgetColor = "#ababab" self.widgetSuffix = data[self.widgetTheme]["suffix"]
def test_load_valid_theme(self): def test_load_valid_theme(self):
try: try:
@ -38,23 +42,32 @@ class TestTheme(unittest.TestCase):
Theme(self.invalidThemeName) Theme(self.invalidThemeName)
def test_default_prefix(self): 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): 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): def test_widget_prefix(self):
self.someWidget.module = self.widgetTheme self.themedWidget.module = self.widgetTheme
self.assertEquals(self.theme.prefix(self.someWidget), self.widgetPrefix) self.assertEquals(self.theme.prefix(self.themedWidget), self.widgetPrefix)
def test_widget_fg(self): def test_widget_fg(self):
self.assertEquals(self.theme.fg(self.someWidget), self.defaultColor) self.assertEquals(self.theme.fg(self.themedWidget), self.defaultColor)
self.someWidget.module = self.widgetTheme self.themedWidget.module = self.widgetTheme
self.assertEquals(self.theme.fg(self.someWidget), self.widgetColor) self.assertEquals(self.theme.fg(self.themedWidget), self.widgetColor)
def test_widget_bg(self): def test_widget_bg(self):
self.assertEquals(self.theme.bg(self.someWidget), self.defaultBgColor) self.assertEquals(self.theme.bg(self.themedWidget), self.defaultBgColor)
self.someWidget.module = self.widgetTheme self.themedWidget.module = self.widgetTheme
self.assertEquals(self.theme.bg(self.someWidget), self.widgetBgColor) 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 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -46,6 +46,9 @@ class MockTheme(object):
self.attr_fg = None self.attr_fg = None
self.attr_bg = None self.attr_bg = None
def reset(self):
pass
def prefix(self, widget): def prefix(self, widget):
return self.attr_prefix return self.attr_prefix

View file

@ -3,10 +3,6 @@
"defaults": { "defaults": {
"default-separators": false, "default-separators": false,
"separator-block-width": 0, "separator-block-width": 0,
"cycle": [
{ "fg": "#93a1a1", "bg": "#002b36" },
{ "fg": "#eee8d5", "bg": "#586e75" }
],
"warning": { "warning": {
"fg": "#002b36", "fg": "#002b36",
"bg": "#b58900" "bg": "#b58900"
@ -16,6 +12,10 @@
"bg": "#dc322f" "bg": "#dc322f"
} }
}, },
"cycle": [
{ "fg": "#93a1a1", "bg": "#002b36" },
{ "fg": "#eee8d5", "bg": "#586e75" }
],
"dnf": { "dnf": {
"good": { "good": {
"fg": "#002b36", "fg": "#002b36",

18
themes/test_cycle.json Normal file
View file

@ -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"
}
}