# This file is part of Elide, frontend to Lisien, a framework for life simulation games.
# Copyright (c) Zachary Spector, public@zacharyspector.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Grid of current values for some entity. Can be changed by the
user. Autoupdates when there's a change for any reason.
"""
from functools import partial
from kivy.clock import Clock
from kivy.properties import DictProperty, ObjectProperty
from kivy.uix.recycleview import RecycleView
default_cfg = {
"control": "readout",
"true_text": "1",
"false_text": "0",
"min": 0.0,
"max": 1.0,
}
[docs]
class BaseStatListView(RecycleView):
"""Base class for widgets showing lists of stats and their values"""
proxy = ObjectProperty()
"""A proxy object representing a lisien entity"""
engine = ObjectProperty()
"""A :class:`lisien.proxy.EngineProxy` object"""
app = ObjectProperty()
"""The Kivy app object"""
_scheduled_set_value = DictProperty()
def __init__(self, **kwargs):
self._listeners = {}
super().__init__(**kwargs)
def on_proxy(self, *_):
self.proxy.connect(self._trigger_upd_data, weak=False)
self._trigger_upd_data()
[docs]
def del_key(self, k):
"""Delete the key and any configuration for it"""
if k not in self.proxy:
raise KeyError
del self.proxy[k]
if "_config" in self.proxy and k in self.proxy["_config"]:
del self.proxy["_config"][k]
[docs]
def set_value(self, k, v):
"""Set a value on the proxy, parsing it to a useful datatype if possible"""
from ast import literal_eval
if self.engine is None or self.proxy is None:
self._trigger_set_value(k, v)
return
if v is None:
del self.proxy[k]
else:
try:
vv = literal_eval(v)
except (TypeError, ValueError):
vv = v
self.proxy[k] = vv
if (k, v) in self._scheduled_set_value:
del self._scheduled_set_value[k, v]
def _trigger_set_value(self, k, v, *_):
todo = partial(self.set_value, k, v)
if (k, v) in self._scheduled_set_value:
Clock.unschedule(self._scheduled_set_value[k, v])
self._scheduled_set_value[k, v] = Clock.schedule_once(todo, 0)
[docs]
def init_config(self, key):
"""Set the configuration for the key to something that will always work"""
self.proxy["_config"].setdefault(key, default_cfg)
[docs]
def set_config(self, key, option, value):
"""Set a configuration option for a key"""
if "_config" not in self.proxy:
newopt = dict(default_cfg)
newopt[option] = value
self.proxy["_config"] = {key: newopt}
else:
if key in self.proxy["_config"]:
self.proxy["_config"][key][option] = value
else:
newopt = dict(default_cfg)
newopt[option] = value
self.proxy["_config"][key] = newopt
[docs]
def set_configs(self, key, d):
"""Set the whole configuration for a key"""
if "_config" in self.proxy:
self.proxy["_config"][key] = d
else:
self.proxy["_config"] = {key: d}
[docs]
def iter_data(self):
"""Iterate over key-value pairs that are really meant to be displayed"""
invalid = {"character", "name", "location", "rulebooks"}
for k, v in self.proxy.items():
if not (isinstance(k, str) and k[0] == "_") and k not in invalid:
yield k, v
[docs]
def munge(self, k, v):
"""Turn a key and value into a dictionary describing a widget to show"""
if "_config" in self.proxy and k in self.proxy["_config"]:
config = self.proxy["_config"][k].unwrap()
else:
config = default_cfg
return {
"key": k,
"reg": self._reg_widget,
"unreg": self._unreg_widget,
"gett": self.proxy.__getitem__,
"sett": self.set_value,
"listen": self.proxy.connect,
"unlisten": self.proxy.disconnect,
"config": config,
}
[docs]
def upd_data(self, *_):
"""Update to match new entity data"""
data = [self.munge(k, v) for k, v in self.iter_data()]
self.data = sorted(data, key=lambda d: d["key"])
def _trigger_upd_data(self, *_, **__):
if hasattr(self, "_scheduled_upd_data"):
Clock.unschedule(self._scheduled_upd_data)
self._scheduled_upd_data = Clock.schedule_once(self.upd_data, 0)
def _reg_widget(self, w, *_):
if not self.proxy:
Clock.schedule_once(partial(self._reg_widget, w), 0)
return
def listen(*_):
if w.key not in self.proxy:
return
if w.value != self.proxy[w.key]:
w.value = self.proxy[w.key]
self._listeners[w.key] = listen
self.proxy.connect(listen)
def _unreg_widget(self, w):
if w.key in self._listeners:
self.proxy.disconnect(self._listeners[w.key])