# 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.clock import Clock, triggered
from kivy.logger import Logger
from kivy.properties import (
ListProperty,
NumericProperty,
ObjectProperty,
StringProperty,
)
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.screenmanager import Screen
from kivy.uix.scrollview import ScrollView
from .kivygarden.texturestack import ImageStack
from .pallet import Pallet, PalletBox
from .util import load_string_once
# TODO: let the user import their own sprite art
def trigger(func):
return triggered()(func)
[docs]
class SpriteSelector(BoxLayout):
prefix = StringProperty()
pallets = ListProperty()
imgpaths = ListProperty([])
default_imgpaths = ListProperty()
preview = ObjectProperty()
def on_prefix(self, *_):
if "textbox" not in self.ids:
Clock.schedule_once(self.on_prefix, 0)
return
self.ids.textbox.text = self.prefix
def on_imgpaths(self, *_):
if not self.preview:
Logger.debug("SpriteSelector: no preview")
Clock.schedule_once(self.on_imgpaths, 0)
return
if hasattr(self, "_imgstack"):
self._imgstack.paths = self.imgpaths
else:
self._imgstack = ImageStack(paths=self.imgpaths)
self._imgstack.bind(
pos=self._position_imgstack, size=self._position_imgstack
)
self.preview.add_widget(self._imgstack)
@trigger
def _position_imgstack(self, *_):
self._imgstack.x = self.preview.center_x - self._imgstack.height / 2
self._imgstack.y = self.preview.center_y - self._imgstack.width / 2
def on_pallets(self, *_):
for pallet in self.pallets:
pallet.fbind("selection", self._upd_imgpaths)
def _upd_imgpaths(self, *_):
imgpaths = []
for pallet in self.pallets:
if pallet.selection:
for selected in pallet.selection:
imgpaths.append(
"atlas://{}/{}".format(pallet.filename, selected.text)
)
self.imgpaths = imgpaths if imgpaths else self.default_imgpaths
[docs]
class SpriteBuilder(ScrollView):
prefix = StringProperty()
imgpaths = ListProperty()
default_imgpaths = ListProperty()
data = ListProperty()
labels = ListProperty()
pallets = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(data=self._trigger_update)
def update(self, *args):
if self.data is None:
return
if not self.canvas:
Clock.schedule_once(self.update, 0)
return
if not hasattr(self, "_palbox"):
self._palbox = PalletBox(orientation="vertical", size_hint_y=None)
self.add_widget(self._palbox)
else:
self._palbox.clear_widgets()
if hasattr(self._palbox, "_bound_width"):
for uid in self._palbox._bound_width:
self._palbox.unbind_uid("width", uid)
del self._palbox._bound_width
self.labels = []
for pallet in self.pallets:
if hasattr(pallet, "_bound_minimum_height"):
pallet.unbind_uid(
"minimum_height", pallet._bound_minimum_height
)
del pallet._bound_minimum_height
if hasattr(pallet, "_bound_height"):
pallet.unbind_uid("height", pallet._bound_height)
del pallet._bound_height
self.pallets = []
for text, filename in self.data:
label = Label(text=text, size_hint=(None, None), halign="center")
label.texture_update()
label.height = label.texture.height
label.width = self._palbox.width
pallet = Pallet(filename=filename, size_hint=(None, None))
pallet.width = self._palbox.width
self._palbox._bound_width = [
self._palbox.fbind("width", label.setter("width")),
self._palbox.fbind("width", pallet.setter("width")),
]
pallet.height = pallet.minimum_height
pallet._bound_minimum_height = (
pallet.fbind("minimum_height", pallet.setter("height")),
)
pallet._bound_height = pallet.fbind(
"height", self._trigger_reheight
)
self.labels.append(label)
self.pallets.append(pallet)
n = len(self.labels)
assert n == len(self.pallets)
for i in range(0, n):
self._palbox.add_widget(self.labels[i])
self._palbox.add_widget(self.pallets[i])
_trigger_update = trigger(update)
def reheight(self, *args):
self._palbox.height = sum(
wid.height for wid in self.labels + self.pallets
)
_trigger_reheight = trigger(reheight)
[docs]
class SpriteDialog(BoxLayout):
toggle = ObjectProperty()
prefix = StringProperty()
imgpaths = ListProperty()
default_imgpaths = ListProperty()
data = ListProperty()
pallet_box_height = NumericProperty()
def pressed(self):
self.prefix = self.ids.selector.prefix
self.imgpaths = self.ids.selector.imgpaths
self.toggle()
[docs]
class PawnConfigDialog(SpriteDialog):
pass
[docs]
class SpotConfigDialog(SpriteDialog):
pass
[docs]
class PawnConfigScreen(Screen):
toggle = ObjectProperty()
data = ListProperty()
imgpaths = ListProperty()
[docs]
class SpotConfigScreen(Screen):
toggle = ObjectProperty()
data = ListProperty()
imgpaths = ListProperty()
load_string_once("""
<SpriteDialog>:
orientation: 'vertical'
SpriteBuilder:
id: builder
prefix: root.prefix
default_imgpaths: root.default_imgpaths
imgpaths: root.imgpaths
data: root.data
SpriteSelector:
id: selector
textbox: textbox
size_hint_y: 0.1
prefix: root.prefix
default_imgpaths: root.default_imgpaths
imgpaths: root.imgpaths
pallets: builder.pallets
preview: preview
TextInput:
id: textbox
multiline: False
write_tab: False
hint_text: 'Enter name prefix'
Widget:
id: preview
canvas:
Color:
rgba: 1, 1, 1, 1
Button:
text: 'OK'
on_release: root.pressed()
<PawnConfigScreen>:
name: 'pawncfg'
imgpaths: dialog.imgpaths
PawnConfigDialog:
id: dialog
toggle: root.toggle
default_imgpaths: ['atlas://rltiles/base/unseen']
data: root.data
<SpotConfigScreen>:
name: 'spotcfg'
imgpaths: dialog.imgpaths
SpotConfigDialog:
id: dialog
toggle: root.toggle
default_imgpaths: ['atlas://rltiles/floor/floor-stone']
data: root.data
""")