Source code for elide.charmenu
# 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 functools import partial
from kivy.app import App
from kivy.clock import Clock, triggered
from kivy.properties import (
BooleanProperty,
ObjectProperty,
ReferenceListProperty,
)
from kivy.uix.boxlayout import BoxLayout
from lisien.proxy import CharStatProxy
from .graph.arrow import GraphArrowWidget
from .util import devour, dummynum, logwrap
def trigger(func):
return triggered()(func)
[docs]
class CharMenu(BoxLayout):
screen = ObjectProperty()
reciprocal_portal = BooleanProperty(True)
revarrow = ObjectProperty(None, allownone=True)
dummyplace = ObjectProperty()
dummything = ObjectProperty()
toggle_gridview = ObjectProperty()
toggle_timestream = ObjectProperty()
dummies = ReferenceListProperty(dummyplace, dummything)
@property
def engine(self):
if not self.screen or not self.screen.app:
raise AttributeError("Can't get engine from screen")
return self.screen.app.engine
@logwrap(section="CharMenu")
def on_screen(self, *_):
if not (
self.screen
and self.screen.boardview
and self.screen.app
and "emptyleft" in self.ids
and "emptyright" in self.ids
):
Clock.schedule_once(self.on_screen, 0)
return
app = App.get_running_app()
binds = app._bindings
self.forearrow = GraphArrowWidget(
board=self.screen.boardview.board,
origin=self.ids.emptyleft,
destination=self.ids.emptyright,
)
self.ids.portaladdbut.add_widget(self.forearrow)
if not binds["CharMenu", "emptyleft", "fore"]:
binds["CharMenu", "emptyleft", "fore"].add(
self.ids.emptyleft.fbind(
"pos", self.forearrow._trigger_repoint
)
)
if not binds["CharMenu", "emptyright", "fore"]:
binds["CharMenu", "emptyright", "fore"].add(
self.ids.emptyright.fbind(
"pos", self.forearrow._trigger_repoint
)
)
if self.reciprocal_portal:
assert self.revarrow is None
self.revarrow = GraphArrowWidget(
board=self.screen.boardview.board,
origin=self.ids.emptyright,
destination=self.ids.emptyleft,
)
self.ids.portaladdbut.add_widget(self.revarrow)
if not binds["CharMenu", "emptyleft", "rev"]:
binds["CharMenu", "emptyleft", "rev"].add(
self.ids.emptyleft.fbind(
"pos", self.revarrow._trigger_repoint
)
)
if not binds["CharMenu", "emptyright", "rev"]:
binds["CharMenu", "emptyright", "rev"].add(
self.ids.emptyright.fbind(
"pos", self.revarrow._trigger_repoint
)
)
if not binds["CharMenu", "reciprocal_portal"]:
binds["CharMenu", "reciprocal_portal"].add(
self.fbind(
"reciprocal_portal",
self.screen.boardview.setter("reciprocal_portal"),
)
)
app._unbinders.append(self.unbind_all)
def unbind_all(self):
binds = App.get_running_app()._bindings
for uid in devour(binds["CharMenu", "emptyleft", "fore"]):
self.ids.emptyleft.unbind_uid("pos", uid)
for uid in devour(binds["CharMenu", "emptyright", "fore"]):
self.ids.emptyright.unbind_uid("pos", uid)
for uid in devour(binds["CharMenu", "emptyleft", "rev"]):
self.ids.emptyleft.unbind_uid("pos", uid)
for uid in devour(binds["CharMenu", "emptyright", "rev"]):
self.ids.emptyright.unbind_uid("pos", uid)
for uid in devour(binds["CharMenu", "reciprocal_portal"]):
self.unbind_uid("reciprocal_portal", uid)
@logwrap(section="CharMenu")
def spot_from_dummy(self, dummy):
if self.screen.boardview.parent != self.screen.mainview:
return
if dummy.collide_widget(self):
return
app = App.get_running_app()
name = dummy.name
self.screen.boardview.spot_from_dummy(dummy)
graphboard = self.screen.graphboards[app.character_name]
if name not in graphboard.spot:
graphboard.add_spot(name)
gridboard = self.screen.gridboards[app.character_name]
if (
name not in gridboard.spot
and isinstance(name, tuple)
and len(name) == 2
):
gridboard.add_spot(name)
@logwrap(section="CharMenu")
def pawn_from_dummy(self, dummy):
name = dummy.name
if not self.screen.mainview.children[0].pawn_from_dummy(dummy):
return
app = App.get_running_app()
graphboard = self.screen.graphboards[app.character_name]
if name not in graphboard.pawn:
graphboard.add_pawn(name)
gridboard = self.screen.gridboards[app.character_name]
if (
name not in gridboard.pawn
and app.character.thing[name]["location"] in gridboard.spot
):
gridboard.add_pawn(name)
[docs]
@logwrap(section="CharMenu")
def toggle_chars_screen(self, *_):
"""Display or hide the list you use to switch between characters."""
# TODO: update the list of chars
App.get_running_app().chars.toggle()
[docs]
@logwrap(section="CharMenu")
def toggle_rules(self, *_):
"""Display or hide the view for constructing rules out of cards."""
app = App.get_running_app()
if app.manager.current != "rules" and not isinstance(
app.selected_proxy, CharStatProxy
):
app.rules.entity = app.selected_proxy
app.rules.rulebook = app.selected_proxy.rulebook
if isinstance(app.selected_proxy, CharStatProxy):
app.charrules.character = app.selected_proxy
app.charrules.toggle()
else:
app.rules.toggle()
[docs]
@logwrap(section="CharMenu")
def toggle_funcs_editor(self):
"""Display or hide the text editing window for functions."""
App.get_running_app().funcs.toggle()
@logwrap(section="CharMenu")
def toggle_strings_editor(self):
App.get_running_app().strings.toggle()
[docs]
@logwrap(section="CharMenu")
def toggle_spot_cfg(self):
"""Show the dialog where you select graphics and a name for a place,
or hide it if already showing.
"""
app = App.get_running_app()
if app.manager.current == "spotcfg":
dummyplace = self.screendummyplace
self.ids.placetab.remove_widget(dummyplace)
dummyplace.clear()
if self.app.spotcfg.prefix:
dummyplace.prefix = app.spotcfg.prefix
dummyplace.num = dummynum(app.character, dummyplace.prefix) + 1
if app.spotcfg.imgpaths:
dummyplace.paths = app.spotcfg.imgpaths
else:
dummyplace.paths = ["atlas://rltiles/floor/floor-stone"]
dummyplace.center = self.ids.placetab.center
self.ids.placetab.add_widget(dummyplace)
else:
app.spotcfg.prefix = self.ids.dummyplace.prefix
app.spotcfg.toggle()
[docs]
@logwrap(section="CharMenu")
def toggle_pawn_cfg(self):
"""Show or hide the pop-over where you can configure the dummy pawn"""
app = App.get_running_app()
if app.manager.current == "pawncfg":
dummything = app.dummything
self.ids.thingtab.remove_widget(dummything)
dummything.clear()
if app.pawncfg.prefix:
dummything.prefix = app.pawncfg.prefix
dummything.num = dummynum(app.character, dummything.prefix) + 1
if app.pawncfg.imgpaths:
dummything.paths = app.pawncfg.imgpaths
else:
dummything.paths = ["atlas://rltiles/base/unseen"]
self.ids.thingtab.add_widget(dummything)
else:
app.pawncfg.prefix = self.ids.dummything.prefix
app.pawncfg.toggle()
[docs]
@logwrap(section="CharMenu")
def toggle_reciprocal(self):
"""Flip my ``reciprocal_portal`` boolean, and draw (or stop drawing)
an extra arrow on the appropriate button to indicate the
fact.
"""
binds = App.get_running_app()._bindings
self.reciprocal_portal = (
self.screen.boardview.reciprocal_portal
) = not self.screen.boardview.reciprocal_portal
if self.screen.boardview.reciprocal_portal:
assert self.revarrow is None
self.revarrow = GraphArrowWidget(
board=self.screen.boardview.board,
origin=self.ids.emptyright,
destination=self.ids.emptyleft,
)
self.ids.portaladdbut.add_widget(self.revarrow)
if not binds["CharMenu", "emptyright", "rev"]:
binds["CharMenu", "emptyright", "rev"].add(
self.ids.emptyright.fbind(
"pos", self.revarrow._trigger_repoint
)
)
if not binds["CharMenu", "emptyleft", "rev"]:
binds["CharMenu", "emptyleft", "rev"].add(
self.ids.emptyleft.fbind(
"pos", self.revarrow._trigger_repoint
)
)
else:
if hasattr(self, "revarrow"):
self.ids.portaladdbut.remove_widget(self.revarrow)
self.revarrow = None
@logwrap(section="CharMenu")
def new_character(self, but):
app = App.get_running_app()
name = app.chars.ids.newname.text
try:
charn = app.engine.unpack(name)
except (TypeError, ValueError):
charn = name
app.select_character(self.app.engine.new_character(charn))
app.chars.ids.newname.text = ""
app.chars.charsview.adapter.data = list(self.engine.character.keys())
Clock.schedule_once(self.toggle_chars_screen, 0.01)
@logwrap(section="CharMenu")
def on_dummyplace(self, *_):
if not self.dummyplace.paths:
self.dummyplace.paths = ["atlas://rltiles/floor.atlas/floor-stone"]
@logwrap(section="CharMenu")
def on_dummything(self, *_):
if not self.dummything.paths:
self.dummything.paths = ["atlas://rltiles/base.atlas/unseen"]
@trigger
@logwrap(section="CharMenu")
def _trigger_deselect(self, *_):
app = App.get_running_app()
if hasattr(app.selection, "selected"):
app.selection.selected = False
app.selection = None
def close_game(self, *_):
App.get_running_app().close_game()