# 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/>.
from kivy.app import App
from kivy.clock import Clock
from kivy.properties import (
DictProperty,
NumericProperty,
ObjectProperty,
StringProperty,
)
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
from kivy.uix.screenmanager import Screen
from kivy.uix.textinput import TextInput
from .statlist import BaseStatListView
from .util import load_string_once
[docs]
class ControlTypePicker(Button):
app = ObjectProperty()
key = ObjectProperty()
mainbutton = ObjectProperty()
dropdown = ObjectProperty()
set_control = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.build()
def set_value(self, k, v):
if v is None:
del self.app.selected_proxy[k]
else:
self.app.selected_proxy[k] = v
def build(self, *_):
if None in (self.key, self.set_control):
Clock.schedule_once(self.build, 0)
return
self.mainbutton = None
self.dropdown = None
self.dropdown = DropDown()
self.dropdown.bind(
on_select=lambda instance, x: self.set_control(self.key, x)
)
readoutbut = Button(
text="readout",
size_hint_y=None,
height=self.height,
background_color=(0.7, 0.7, 0.7, 1),
)
readoutbut.bind(
on_release=lambda instance: self.dropdown.select("readout")
)
self.dropdown.add_widget(readoutbut)
textinbut = Button(
text="textinput",
size_hint_y=None,
height=self.height,
background_color=(0.7, 0.7, 0.7, 1),
)
textinbut.bind(
on_release=lambda instance: self.dropdown.select("textinput")
)
self.dropdown.add_widget(textinbut)
togbut = Button(
text="togglebutton",
size_hint_y=None,
height=self.height,
background_color=(0.7, 0.7, 0.7, 1),
)
togbut.bind(
on_release=lambda instance: self.dropdown.select("togglebutton")
)
self.dropdown.add_widget(togbut)
sliderbut = Button(
text="slider",
size_hint_y=None,
height=self.height,
background_color=(0.7, 0.7, 0.7, 1),
)
sliderbut.bind(
on_release=lambda instance: self.dropdown.select("slider")
)
self.dropdown.add_widget(sliderbut)
self.bind(on_release=self.dropdown.open)
[docs]
class ConfigListItemSlider(BoxLayout):
min = NumericProperty(0.0)
max = NumericProperty(1.0)
def set_min(self, *_):
minn = float(self.ids.minimum.text)
try:
self.parent.set_config(self.parent.key, "min", minn)
self.min = minn
except ValueError:
self.ids.minimum.text = ""
def set_max(self, *_):
maxx = float(self.ids.maximum.text)
try:
self.parent.set_config(self.parent.key, "max", maxx)
self.max = maxx
self.ids.maximum.hint_text = str(maxx)
except ValueError:
self.ids.maximum.text = ""
[docs]
class ConfigListItemCustomizer(BoxLayout):
key = ObjectProperty()
control = StringProperty()
config = DictProperty()
set_config = ObjectProperty()
def on_control(self, *_):
self.clear_widgets()
if self.control == "togglebutton":
if (
"true_text" not in self.config
or "false_text" not in self.config
):
Clock.schedule_once(self.on_control, 0)
return
wid = self._toggle = ConfigListItemToggleButton(
true_text=self.config["true_text"],
false_text=self.config["false_text"],
)
self.add_widget(wid)
elif self.control == "slider":
if "min" not in self.config or "max" not in self.config:
Clock.schedule_once(self.on_control, 0)
return
wid = self._slider = ConfigListItemSlider(
min=self.config["min"], max=self.config["max"]
)
self.add_widget(wid)
def on_config(self, *_):
if hasattr(self, "_toggle"):
if "true_text" in self.config:
self._toggle.true_text = self.config["true_text"]
if "false_text" in self.config:
self._toggle.false_text = self.config["false_text"]
if hasattr(self, "_slider"):
if "min" in self.config:
self._slider.min = self.config["min"]
if "max" in self.config:
self._slider.max = self.config["max"]
[docs]
class ConfigListItem(BoxLayout):
key = ObjectProperty()
config = DictProperty()
set_control = ObjectProperty()
set_config = ObjectProperty()
deleter = ObjectProperty()
[docs]
class StatListViewConfigurator(BaseStatListView):
statlist = ObjectProperty()
_key_cfg_setters = DictProperty()
_val_text_setters = DictProperty()
_control_wids = DictProperty()
def set_control(self, key, value):
config = self.proxy.get("_config", {})
if value == "slider":
if "min" not in config:
self.set_config(key, "min", 0.0)
if "max" not in config:
self.set_config(key, "max", 1.0)
elif value == "togglebutton":
if "true_text" not in config:
self.set_config(key, "true_text", "1")
if "false_text" not in config:
self.set_config(key, "false_text", "0")
self.set_config(key, "control", value)
[docs]
def munge(self, k, v):
# makes ConfigListItem
ret = super().munge(k, v)
ret["deleter"] = self.del_key
ret["set_control"] = self.set_control
ret["set_config"] = self.set_config
return ret
[docs]
class StatScreen(Screen):
statlist = ObjectProperty()
statcfg = ObjectProperty()
toggle = ObjectProperty()
proxy = ObjectProperty()
@property
def engine(self):
return App.get_running_app().engine
[docs]
def new_stat(self):
"""Look at the key and value that the user has entered into the stat
configurator, and set them on the currently selected
entity.
"""
key = self.ids.newstatkey.text
value = self.ids.newstatval.text
if not (key and value):
# TODO implement some feedback to the effect that
# you need to enter things
return
try:
self.proxy[key] = self.engine.unpack(value)
except (TypeError, ValueError):
self.proxy[key] = value
self.ids.newstatkey.text = ""
self.ids.newstatval.text = ""
load_string_once("""
<ConfigListItemCustomization>:
pos_hint: {'x': 0, 'y': 0}
<ConfigListItemToggleButton>:
Label:
text: 'True text:'
TextInput:
id: truetext
hint_text: root.true_text
on_text_validate: root.set_true_text()
Label:
text: 'False text:'
TextInput:
id: falsetext
hint_text: root.false_text
on_text_validate: root.set_false_text()
<ConfigListItemSlider>:
Label:
text: 'Minimum:'
TextInput:
id: minimum
hint_text: str(root.min)
multiline: False
on_text_validate: root.set_min()
Label:
text: 'Maximum:'
TextInput:
id: maximum
hint_text: str(root.max)
multiline: False
on_text_validate: root.set_max()
<ConfigListItem>:
height: 30
Button:
size_hint_x: 0.4 / 3
text: 'del'
on_release: root.deleter(root.key)
Label:
size_hint_x: 0.4 / 3
text: str(root.key)
ControlTypePicker:
size_hint_x: 0.4 / 3
key: root.key
set_control: root.set_control
text: root.config['control'] if 'control' in root.config else 'readout'
ConfigListItemCustomizer:
size_hint_x: 0.6
control: root.config['control'] if 'control' in root.config else 'readout'
config: root.config
key: root.key
set_config: root.set_config
<StatScreen>:
name: 'statcfg'
statcfg: cfg
BoxLayout:
orientation: 'vertical'
StatListViewConfigurator:
viewclass: 'ConfigListItem'
id: cfg
app: app
proxy: root.proxy
statlist: root.statlist
size_hint_y: 0.95
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
BoxLayout:
orientation: 'horizontal'
size_hint_y: 0.05
TextInput:
id: newstatkey
multiline: False
write_tab: False
hint_text: 'New stat'
TextInput:
id: newstatval
multiline: False
write_tab: False
hint_text: 'Value'
Button:
id: newstatbut
text: '+'
on_release: root.new_stat()
Button:
id: closer
text: 'Close'
on_release: root.toggle()
""")