Source code for elide.spritebuilder

# 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 """)