From 481b9a7437b3c465d93c390bf3fe5f64382c41a3 Mon Sep 17 00:00:00 2001 From: Zachary Spector Date: Mon, 24 Aug 2015 11:19:07 -0400 Subject: [PATCH 1/8] Don't regen spot collider needlessly --- ELiDE/board/spot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ELiDE/board/spot.py b/ELiDE/board/spot.py index 3807b296b..dd83260b9 100644 --- a/ELiDE/board/spot.py +++ b/ELiDE/board/spot.py @@ -211,6 +211,8 @@ def on_touch_move(self, touch): def on_touch_up(self, touch): """Unset ``touchpos``""" + if not self.hit: + return False if self._touchpos: self.center = self._touchpos self._touchpos = [] From af2e17100efe6768cee7b1dbb50ffa5ac48a6d4b Mon Sep 17 00:00:00 2001 From: Zachary Spector Date: Mon, 24 Aug 2015 12:14:27 -0400 Subject: [PATCH 2/8] Regenerate Spot's collider more consistently. This makes collision detection on Spots more consistent. It might be expensive, but I detect no performance impact. --- ELiDE/board/spot.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ELiDE/board/spot.py b/ELiDE/board/spot.py index dd83260b9..b6b28a03a 100644 --- a/ELiDE/board/spot.py +++ b/ELiDE/board/spot.py @@ -53,6 +53,24 @@ def __init__(self, **kwargs): del kwargs['place'] super().__init__(**kwargs) self.bind(pos=self._trigger_upd_pawns_here) + self.bind( + size=self._trigger_upd_collider, + pos=self._trigger_upd_collider + ) + + def _upd_collider(self, *args): + rx = self.width / 2 + ry = self.height / 2 + if ( + not hasattr(self.collider, 'pos') or + self.collider.pos != self.center or + self.collider.rx != rx or + self.collider.ry != ry + ): + self.collider = CollideEllipse( + x=self.center_x, y=self.center_y, rx=rx, ry=ry + ) + _trigger_upd_collider = trigger(_upd_collider) def _get_pospawn_partial(self, pawn): if pawn not in self._pospawn_partials: @@ -195,10 +213,7 @@ def _upd_pawns_here(self, *args): def collide_point(self, x, y): """Check my collider.""" - if not self.collider: - self.collider = CollideEllipse( - x=self.x, y=self.y, rx=self.width/2, ry=self.height/2 - ) + self._upd_collider() return (x, y) in self.collider def on_touch_move(self, touch): @@ -217,10 +232,6 @@ def on_touch_up(self, touch): self.center = self._touchpos self._touchpos = [] self._trigger_push_pos() - (x, y) = self.center - self.collider = CollideEllipse( - x=x, y=y, rx=self.width/2, ry=self.height/2 - ) self.collided = False self.hit = False From 021845d0e50ecf4a40f7bf4192695750044ea153 Mon Sep 17 00:00:00 2001 From: Zachary Spector Date: Mon, 24 Aug 2015 19:02:17 -0400 Subject: [PATCH 3/8] sanitize pos into ints --- ELiDE/board/spot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ELiDE/board/spot.py b/ELiDE/board/spot.py index b6b28a03a..76d0436b9 100644 --- a/ELiDE/board/spot.py +++ b/ELiDE/board/spot.py @@ -113,8 +113,8 @@ def _upd_pos(self, *args): Clock.schedule_once(self._upd_pos, 0) return self.pos = ( - self.remote.get('_x', self.default_pos[0]) * self.board.width, - self.remote.get('_y', self.default_pos[1]) * self.board.height + int(self.remote.get('_x', self.default_pos[0]) * self.board.width), + int(self.remote.get('_y', self.default_pos[1]) * self.board.height) ) def listen_pos(self, *args): From ff75cff66f86de2cadbd892cb94cf97c2e2df53b Mon Sep 17 00:00:00 2001 From: Zachary Spector Date: Mon, 24 Aug 2015 19:18:05 -0400 Subject: [PATCH 4/8] Handle null rulebook correctly You could actually create a new rule in an empty rulebook in ELiDE, but the user interface wasn't reflecting the fact. Now it is. Also, no longer crash when switching from a non-empty rulebook to an empty one. --- ELiDE/rulesview.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/ELiDE/rulesview.py b/ELiDE/rulesview.py index 8769938be..3d290512e 100644 --- a/ELiDE/rulesview.py +++ b/ELiDE/rulesview.py @@ -54,7 +54,7 @@ def __init__(self, **kwargs): def on_adapter(self, *args): self.adapter.bind( on_selection_change=lambda inst: - self.set_rule(self.adapter.selection[0].rule) + self.set_rule(self.adapter.selection[0].rule if self.adapter.selection else None) ) def on_rulebook(self, *args): @@ -73,7 +73,7 @@ def set_rule(self, rule): class RulesView(FloatLayout): engine = ObjectProperty() rulebook = ObjectProperty() - rule = ObjectProperty() + rule = ObjectProperty(allownone=True) def _get_headline_text(self): # This shows the entity whose rules you're editing if you @@ -256,9 +256,6 @@ def on_rule(self, *args): def getname(o): return o if isinstance(o, str) else o.__name__ - if self.rule is None: - dbg('RulesView: no rule') - return for attrn in '_trigger_builder', '_prereq_builder', '_action_builder': if not hasattr(self, attrn): dbg('RulesView: no {}'.format(attrn)) @@ -267,6 +264,11 @@ def getname(o): self._trigger_builder.clear_widgets() self._prereq_builder.clear_widgets() self._action_builder.clear_widgets() + if self.rule is None: + dbg('RulesView: no rule') + return + if hasattr(self, '_list'): + self._list.adapter.data = list(self._list.rulebook) unused_triggers = [ Card( ud={ @@ -294,11 +296,7 @@ def getname(o): ) for trigger in self.rule.triggers ] - self._trigger_builder.unbind(decks=self._trigger_upd_unused_triggers) - self._trigger_builder.unbind(decks=self._trigger_upd_rule_triggers) self._trigger_builder.decks = [used_triggers, unused_triggers] - self._trigger_builder.bind(decks=self._trigger_upd_rule_triggers) - self._trigger_builder.bind(decks=self._trigger_upd_unused_triggers) unused_prereqs = [ Card( ud={ @@ -326,11 +324,7 @@ def getname(o): ) for prereq in self.rule.prereqs ] - self._prereq_builder.unbind(decks=self._trigger_upd_unused_prereqs) - self._prereq_builder.unbind(decks=self._trigger_upd_rule_prereqs) self._prereq_builder.decks = [used_prereqs, unused_prereqs] - self._prereq_builder.bind(decks=self._trigger_upd_rule_prereqs) - self._prereq_builder.bind(decks=self._trigger_upd_unused_prereqs) unused_actions = [ Card( ud={ @@ -358,11 +352,7 @@ def getname(o): ) for action in self.rule.actions ] - self._action_builder.unbind(decks=self._trigger_upd_rule_actions) - self._action_builder.unbind(decks=self._trigger_upd_unused_actions) self._action_builder.decks = [used_actions, unused_actions] - self._action_builder.bind(decks=self._trigger_upd_rule_actions) - self._action_builder.bind(decks=self._trigger_upd_unused_actions) def upd_rule_actions(self, *args): actions = [ @@ -469,7 +459,10 @@ def new_rule(self, *args): if self.new_rule_name in self.engine.rule: # TODO: feedback to say you already have such a rule return - self.rulebook.append(self.engine.rule.new_empty(self.new_rule_name)) + new_rule = self.engine.rule.new_empty(self.new_rule_name) + assert(new_rule is not None) + self.rulebook.append(new_rule) + self.ids.rulesview.rule = new_rule self.ids.rulename.text = '' From 1304d05d2227aacc8a578827035293bb909ddecb Mon Sep 17 00:00:00 2001 From: Zachary Spector Date: Mon, 24 Aug 2015 20:18:24 -0400 Subject: [PATCH 5/8] Dragging to move also selects what you drag. --- ELiDE/screen.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ELiDE/screen.py b/ELiDE/screen.py index 5d76e72d4..b75b2ddff 100644 --- a/ELiDE/screen.py +++ b/ELiDE/screen.py @@ -310,11 +310,10 @@ def on_touch_down(self, touch): spots = list(self.board.spots_at(*touch.pos)) if spots: self.selection_candidates = spots - if self.selection in self.selection_candidates: - self.selection_candidates.remove(self.selection) if self.portaladdbut.state == 'down': self.origspot = self.selection_candidates.pop(0) self.protodest = Dummy( + name="protodest", pos=touch.pos, size=(0, 0) ) @@ -351,13 +350,18 @@ def on_touch_move(self, touch): """ touch.push() touch.apply_transform_2d(self.ids.boardview.to_local) - if self.selection: + if self.selection in self.selection_candidates: + self.selection_candidates.remove(self.selection) + if self.selection and not self.selection_candidates: self.keep_selection = True self.selection.dispatch('on_touch_move', touch) - if self.selection_candidates: + elif self.selection_candidates: for cand in self.selection_candidates: if cand.collide_point(*touch.pos): - cand.hit = True + if hasattr(self.selection, 'selected'): + self.selection.selected = False + self.selection = cand + cand.hit = cand.selected = True touch.grab(cand) cand.dispatch('on_touch_move', touch) touch.pop() From 1be088b10840aa0afc4788a66b1057736e42e966 Mon Sep 17 00:00:00 2001 From: Zachary Spector Date: Mon, 24 Aug 2015 20:42:35 -0400 Subject: [PATCH 6/8] remove some prints and debug logging --- ELiDE/board/pawn.py | 7 ------- ELiDE/screen.py | 1 - LiSE/core.py | 1 - LiSE/rule.py | 4 ---- 4 files changed, 13 deletions(-) diff --git a/ELiDE/board/pawn.py b/ELiDE/board/pawn.py index fd4279a09..facd71e04 100644 --- a/ELiDE/board/pawn.py +++ b/ELiDE/board/pawn.py @@ -174,13 +174,6 @@ def on_touch_up(self, touch): del self._unlistened for spot in self.board.spot.values(): if self.collide_widget(spot) and spot.name != self.loc_name: - Logger.debug( - "pawn: {} will go from {} to {}".format( - self.name, - self.loc_name, - spot.name - ) - ) new_spot = spot break else: diff --git a/ELiDE/screen.py b/ELiDE/screen.py index b75b2ddff..f0bc94278 100644 --- a/ELiDE/screen.py +++ b/ELiDE/screen.py @@ -262,7 +262,6 @@ def _get_selected_remote(self): no entity is selected. """ - Logger.debug('ELiDELayout: getting remote...') if self.selection is None: return self.character.stat elif hasattr(self.selection, 'remote'): diff --git a/LiSE/core.py b/LiSE/core.py index bef40d013..834b65dab 100644 --- a/LiSE/core.py +++ b/LiSE/core.py @@ -847,7 +847,6 @@ def _follow_rules(self): (branch, tick) = self.time for (typ, character, entity, rulebook, rule) in self._poll_rules(): def follow(*args): - print('Following {}...'.format(rule)) return (rule(self, *args), rule.name, typ, rulebook) if typ in ('thing', 'place', 'portal'): diff --git a/LiSE/rule.py b/LiSE/rule.py index 084e101c0..a9e97ec6d 100644 --- a/LiSE/rule.py +++ b/LiSE/rule.py @@ -205,10 +205,6 @@ def run_actions(self, engine, *args): """Run all my actions and return a list of their results. """ - print('running actions {} for rule {}'.format( - [action.__name__ for action in self.actions], - self.name - )) curtime = engine.time r = [] for action in self.actions: From dc2bf9dce2c3456d38bac70067b479c7276f2195 Mon Sep 17 00:00:00 2001 From: Zachary Spector Date: Mon, 24 Aug 2015 20:46:33 -0400 Subject: [PATCH 7/8] LiSE entities have truth value True. --- LiSE/node.py | 4 ++++ LiSE/portal.py | 4 ++++ LiSE/proxy.py | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/LiSE/node.py b/LiSE/node.py index ac6178949..334fa5d5d 100644 --- a/LiSE/node.py +++ b/LiSE/node.py @@ -162,6 +162,10 @@ def __contains__(self, k): return True return super().__contains__(k) + def __bool__(self): + """It means something that I exist, even if I have no data but my name.""" + return True + def listener(self, f=None, stat=None): """Arrange to call a function whenever a stat changes. diff --git a/LiSE/portal.py b/LiSE/portal.py index 4971f3000..b446c701a 100644 --- a/LiSE/portal.py +++ b/LiSE/portal.py @@ -218,6 +218,10 @@ def __repr__(self): self['destination'] ) + def __bool__(self): + """It means something that I exist, even if I have no data but my name.""" + return True + @property def origin(self): """Return the Place object that is where I begin""" diff --git a/LiSE/proxy.py b/LiSE/proxy.py index 257727e68..698ef336c 100644 --- a/LiSE/proxy.py +++ b/LiSE/proxy.py @@ -1608,6 +1608,10 @@ def __getitem__(self, k): return self._charname return super().__getitem__(k) + def __bool__(self): + """It means something that I exist, even if I don't have any data yet.""" + return True + def _get_diff(self): return self._engine.handle( 'node_stat_diff', @@ -1877,6 +1881,10 @@ def __getitem__(self, k): else: return super().__getitem__(k) + def __bool__(self): + """It means something that I exist, even if I don't have any data yet.""" + return True + def listener(self, fun=None, stat=None): if None not in (fun, stat): self._engine.portal_stat_listener( @@ -2455,6 +2463,10 @@ def __init__(self, engine_proxy, charname): self.place = PlaceMapProxy(self._engine, self.name) self.stat = CharStatProxy(self._engine, self.name) + def __bool__(self): + """It means something that I exist, even if I don't have any data yet.""" + return True + def __eq__(self, other): if hasattr(other, '_engine'): oe = other._engine From 1b162a4a32dc1a1509473b9c9660207318e8b5c1 Mon Sep 17 00:00:00 2001 From: Zachary Spector Date: Tue, 25 Aug 2015 13:23:08 -0400 Subject: [PATCH 8/8] Writing new rule-function now makes it show up in rules view --- ELiDE/app.py | 1 + ELiDE/funcsed.py | 5 +++- ELiDE/rulesview.py | 58 +++++++++++++++++++++++++++++----------------- ELiDE/stores.py | 50 ++++++++++++++++++++++++++++----------- 4 files changed, 78 insertions(+), 36 deletions(-) diff --git a/ELiDE/app.py b/ELiDE/app.py index c314b7a25..535a37709 100644 --- a/ELiDE/app.py +++ b/ELiDE/app.py @@ -196,6 +196,7 @@ def tog(*args): store=self.engine.trigger, toggle=toggler('funcs') ) + funcs.bind(data=rules.rulesview._trigger_update_builders) self.select_character( self.engine.character[ diff --git a/ELiDE/funcsed.py b/ELiDE/funcsed.py index 6895f5879..37f57046c 100644 --- a/ELiDE/funcsed.py +++ b/ELiDE/funcsed.py @@ -2,7 +2,8 @@ from kivy.properties import ( NumericProperty, ObjectProperty, - StringProperty + StringProperty, + ListProperty ) from kivy.uix.screenmanager import Screen @@ -10,6 +11,7 @@ class FuncsEdScreen(Screen): funcs_ed = ObjectProperty() table = StringProperty() store = ObjectProperty() + data = ListProperty() font_name = StringProperty('Roboto-Regular') font_size = NumericProperty(12) toggle = ObjectProperty() @@ -64,6 +66,7 @@ def setport(self, active): : name: 'funcs' funcs_ed: funcs_ed + data: funcs_ed.data if funcs_ed else [] BoxLayout: orientation: 'vertical' BoxLayout: diff --git a/ELiDE/rulesview.py b/ELiDE/rulesview.py index 3d290512e..da87bf4dc 100644 --- a/ELiDE/rulesview.py +++ b/ELiDE/rulesview.py @@ -27,6 +27,11 @@ dbg = Logger.debug + +def getname(o): + return o if isinstance(o, str) else o.__name__ + + class RuleButton(ListItemButton): rule = ObjectProperty() @@ -251,24 +256,9 @@ def finalize(self, *args): size_hint=(None, None) ) ) + self.bind(rule=self._trigger_update_builders) - def on_rule(self, *args): - def getname(o): - return o if isinstance(o, str) else o.__name__ - - for attrn in '_trigger_builder', '_prereq_builder', '_action_builder': - if not hasattr(self, attrn): - dbg('RulesView: no {}'.format(attrn)) - Clock.schedule_once(self.on_rule, 0) - return - self._trigger_builder.clear_widgets() - self._prereq_builder.clear_widgets() - self._action_builder.clear_widgets() - if self.rule is None: - dbg('RulesView: no rule') - return - if hasattr(self, '_list'): - self._list.adapter.data = list(self._list.rulebook) + def update_triggers(self, *args): unused_triggers = [ Card( ud={ @@ -287,16 +277,19 @@ def getname(o): Card( ud={ 'type': 'trigger', - 'funcname': getname(trigger), + 'funcname': getname(trig), }, - headline_text=getname(trigger), + headline_text=getname(trig), show_art=False, midline_text='Trigger', - text=self.engine.trigger.plain(getname(trigger)), + text=self.engine.trigger.plain(getname(trig)), ) - for trigger in self.rule.triggers + for trig in self.rule.triggers ] self._trigger_builder.decks = [used_triggers, unused_triggers] + _trigger_update_triggers = trigger(update_triggers) + + def update_prereqs(self, *args): unused_prereqs = [ Card( ud={ @@ -325,6 +318,9 @@ def getname(o): for prereq in self.rule.prereqs ] self._prereq_builder.decks = [used_prereqs, unused_prereqs] + _trigger_update_prereqs = trigger(update_prereqs) + + def update_actions(self, *args): unused_actions = [ Card( ud={ @@ -353,6 +349,26 @@ def getname(o): for action in self.rule.actions ] self._action_builder.decks = [used_actions, unused_actions] + _trigger_update_actions = trigger(update_actions) + + def update_builders(self, *args): + for attrn in '_trigger_builder', '_prereq_builder', '_action_builder': + if not hasattr(self, attrn): + dbg('RulesView: no {}'.format(attrn)) + Clock.schedule_once(self.on_rule, 0) + return + self._trigger_builder.clear_widgets() + self._prereq_builder.clear_widgets() + self._action_builder.clear_widgets() + if self.rule is None: + dbg('RulesView: no rule') + return + if hasattr(self, '_list'): + self._list.adapter.data = list(self._list.rulebook) + self.update_triggers() + self.update_prereqs() + self.update_actions() + _trigger_update_builders = trigger(update_builders) def upd_rule_actions(self, *args): actions = [ diff --git a/ELiDE/stores.py b/ELiDE/stores.py index d5e7d549d..746d6bde6 100644 --- a/ELiDE/stores.py +++ b/ELiDE/stores.py @@ -145,6 +145,7 @@ class StoreList(FloatLayout): store = ObjectProperty() selection = ListProperty([]) saver = ObjectProperty() + adapter = ObjectProperty() def __init__(self, **kwargs): self._trigger_remake = Clock.create_trigger(self.remake) @@ -167,26 +168,26 @@ def remake(self, *args): if None in (self.store, self.table): return self.clear_widgets() - self._adapter = self.adapter_cls( + self.adapter = self.adapter_cls( table=self.table, store=self.store, loader=self._trigger_redata ) - self._adapter.bind( + self.adapter.bind( on_selection_change=self.changed_selection ) self.bind( - table=self._adapter.setter('table'), - store=self._adapter.setter('store') + table=self.adapter.setter('table'), + store=self.adapter.setter('store') ) self._listview = ListView( - adapter=self._adapter + adapter=self.adapter ) self.add_widget(self._listview) self._trigger_redata() def redata(self, *args): - self._adapter.data = self._adapter.get_data() + self.adapter.data = self.adapter.get_data() class FuncStoreList(StoreList): @@ -201,6 +202,9 @@ class StoreEditor(BoxLayout): """StoreList on the left with its editor on the right.""" table = StringProperty() store = ObjectProperty() + storelist = ObjectProperty() + adapter = ObjectProperty() + data = ListProperty([]) font_name = StringProperty('Roboto-Regular') font_size = NumericProperty(12) selection = ListProperty([]) @@ -215,28 +219,46 @@ def __init__(self, **kwargs): store=self._trigger_remake ) + def readapter(self, storelist, adapter): + if not adapter: + if self.adapter: + self.adapter.unbind(data=setter('data')) + self.data = [] + return + if self.adapter: + self.adapter.unbind(data=self.setter('data')) + self.adapter = adapter + adapter.bind(data=self.setter('data')) + def remake(self, *args): if None in (self.store, self.table): return self.clear_widgets() - self._list = self.list_cls( + self.storelist = self.list_cls( size_hint_x=0.4, table=self.table, store=self.store, saver=self.save ) - self._list.bind(selection=self.changed_selection) + if self.storelist.adapter: + self.adapter = self.storelist.adapter + self.data = self.adapter.data + self.storelist.bind( + selection=self.changed_selection, + adapter=self.readapter + ) + self.readapter(self.storelist, self.storelist.adapter) self.bind( - table=self._list.setter('table'), - store=self._list.setter('store') + table=self.storelist.setter('table'), + store=self.storelist.setter('store') ) - self.add_widget(self._list) + self.add_widget(self.storelist) self.add_editor() _trigger_remake = trigger(remake) def changed_selection(self, *args): - if self._list.selection: - self.selection = self._list.selection + if self.storelist.selection: + self.selection = self.storelist.selection self.name = self.selection[0].name self.source = self.selection[0].source @@ -244,7 +266,7 @@ def redata_reselect(self, *args): self.save() StoreDataItem.selectedness = defaultdict(lambda: False) StoreDataItem.selectedness[self.name] = True - self._list._trigger_redata() + self.storelist._trigger_redata() _trigger_redata_reselect = trigger(redata_reselect) def add_editor(self, *args):