class NotifyingViewControlComponent(ViewControlComponent): __events__ = (u'selection_scrolled', u'selection_paged') def __init__(self, track_provider=None, enable_skinning=True, *a, **k): self._track_provider = track_provider super(NotifyingViewControlComponent, self).__init__(*a, **k) self._page_tracks = ScrollComponent(self._create_track_pager(), parent=self) self.__on_tracks_changed.subject = self._track_provider self.__on_selected_track_changed.subject = self.song.view if enable_skinning: skin_scroll_buttons(self._page_tracks, u'TrackNavigation.On', u'TrackNavigation.Pressed') skin_scroll_buttons(self._scroll_tracks, u'TrackNavigation.On', u'TrackNavigation.Pressed') skin_scroll_buttons(self._scroll_scenes, u'SceneNavigation.On', u'SceneNavigation.Pressed') def set_prev_track_page_button(self, button): self._page_tracks.set_scroll_up_button(button) def set_next_track_page_button(self, button): self._page_tracks.set_scroll_down_button(button) def _create_track_scroller(self): scroller = NotifyingTrackScroller() self.register_disconnectable( ObservablePropertyAlias(self, property_host=scroller, property_name=u'scrolled', alias_name=u'selection_scrolled')) return scroller def _create_scene_scroller(self): return BasicSceneScroller() def _create_track_pager(self): pager = NotifyingTrackPager(track_provider=self._track_provider) self.register_disconnectable( ObservablePropertyAlias(self, property_host=pager, property_name=u'scrolled', alias_name=u'selection_paged')) return pager @listens(u'tracks') def __on_tracks_changed(self): self._update_track_scrollers() @listens(u'selected_track') def __on_selected_track_changed(self): self._update_track_scrollers() def _update_track_scrollers(self): self._scroll_tracks.update() self._page_tracks.update()
class ListComponent(Component): """ Component that handles a ScrollableList. If an action button is passed, it can handle an ActionList. """ __events__ = ('item_action', 'selected_item') SELECTION_DELAY = 0.5 ENCODER_FACTOR = 10.0 empty_list_message = b'' _current_action_item = None _last_action_item = None action_button = ButtonControl(color=b'Browser.Load') encoders = control_list(EncoderControl) def __init__(self, scrollable_list=None, data_sources=tuple(), *a, **k): super(ListComponent, self).__init__(*a, **k) self._data_sources = data_sources self._activation_task = task.Task() self._action_on_scroll_task = task.Task() self._scrollable_list = None self._scroller = ScrollComponent(parent=self) self._pager = ScrollComponent(parent=self) self.last_action_item = lambda: self._last_action_item self.item_formatter = DefaultItemFormatter() for c in (self._scroller, self._pager): for button in (c.scroll_up_button, c.scroll_down_button): button.color = b'List.ScrollerOn' button.pressed_color = None button.disabled_color = b'List.ScrollerOff' if scrollable_list == None: self.scrollable_list = ActionList( num_visible_items=len(data_sources)) else: self.scrollable_list = scrollable_list self._scrollable_list.num_visible_items = len(data_sources) self._delay_activation = BooleanContext() self._selected_index_float = 0.0 self._in_encoder_selection = BooleanContext(False) self._execute_action_task = self._tasks.add( task.sequence(task.delay(1), task.run(self._execute_action))) self._execute_action_task.kill() return @property def _trigger_action_on_scrolling(self): return self.action_button.is_pressed def _get_scrollable_list(self): return self._scrollable_list def _set_scrollable_list(self, new_list): if new_list != self._scrollable_list: self._scrollable_list = new_list if new_list != None: new_list.num_visible_items = len(self._data_sources) self._scroller.scrollable = new_list self._pager.scrollable = new_list.pager self._on_scroll.subject = new_list self._selected_index_float = new_list.selected_item_index else: self._scroller.scrollable = ScrollComponent.default_scrollable self._scroller.scrollable = ScrollComponent.default_pager self._on_selected_item_changed.subject = new_list self.update() return scrollable_list = property(_get_scrollable_list, _set_scrollable_list) def set_data_sources(self, sources): self._data_sources = sources if self._scrollable_list: self._scrollable_list.num_visible_items = len(sources) self._update_display() select_next_button = forward_property(b'_scroller')(b'scroll_down_button') select_prev_button = forward_property(b'_scroller')(b'scroll_up_button') next_page_button = forward_property(b'_pager')(b'scroll_down_button') prev_page_button = forward_property(b'_pager')(b'scroll_up_button') def on_enabled_changed(self): super(ListComponent, self).on_enabled_changed() if not self.is_enabled(): self._execute_action_task.kill() @listens(b'scroll') def _on_scroll(self): if self._trigger_action_on_scrolling: trigger_selected = partial(self._trigger_action, self.selected_item) self._action_on_scroll_task.kill() self._action_on_scroll_task = self._tasks.add( task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.delay(1), task.run(trigger_selected))) @listens(b'selected_item') def _on_selected_item_changed(self): self._scroller.update() self._pager.update() self._update_display() self._update_action_feedback() self._activation_task.kill() self._action_on_scroll_task.kill() if self.SELECTION_DELAY and self._delay_activation: self._activation_task = self._tasks.add( task.sequence( task.wait(self.SELECTION_DELAY), task.run( self._scrollable_list.request_notify_item_activated))) else: self._scrollable_list.request_notify_item_activated() if not self._in_encoder_selection: self._selected_index_float = float( self._scrollable_list.selected_item_index) self.notify_selected_item(self._scrollable_list.selected_item) @encoders.value def encoders(self, value, encoder): self._add_offset_to_selected_index(value) def _add_offset_to_selected_index(self, offset): if self.is_enabled() and self._scrollable_list: with self._delay_activation(): with self._in_encoder_selection(): self._selected_index_float = clamp( self._selected_index_float + offset * self.ENCODER_FACTOR, 0, len(self._scrollable_list.items)) self._scrollable_list.select_item_index_with_border( int(self._selected_index_float), 1) @action_button.pressed def action_button(self, button): if self._current_action_item == None: self._trigger_action( self.next_item if self._action_target_is_next_item( ) else self.selected_item) return def do_trigger_action(self, item): item.action() self.notify_item_action(item) def _trigger_action(self, item): if self.is_enabled() and self._can_be_used_for_action(item): if self._scrollable_list != None: self._scrollable_list.select_item(item) self._current_action_item = item self.update() self._execute_action_task.restart() return def _execute_action(self): """ Is called by the execute action task and should not be called directly use _trigger_action instead """ if self._current_action_item != None: self.do_trigger_action(self._current_action_item) self._last_action_item = self._current_action_item self._current_action_item = None self.update() return @property def selected_item(self): if self._scrollable_list != None: return self._scrollable_list.selected_item else: return @property def next_item(self): item = None if self._scrollable_list != None: all_items = self._scrollable_list.items next_index = self._scrollable_list.selected_item_index + 1 item = all_items[next_index] if in_range(next_index, 0, len(all_items)) else None return item def _can_be_used_for_action(self, item): return item != None and item.supports_action and item != self.last_action_item( ) def _action_target_is_next_item(self): return self.selected_item == self.last_action_item( ) and self._can_be_used_for_action(self.next_item) def _update_action_feedback(self): color = b'Browser.Loading' if self._current_action_item == None: if self._action_target_is_next_item(): color = b'Browser.LoadNext' elif self._can_be_used_for_action(self.selected_item): color = b'Browser.Load' else: color = b'Browser.LoadNotPossible' self.action_button.color = color return def _update_display(self): visible_items = self._scrollable_list.visible_items if self._scrollable_list else [] for index, data_source in enumerate(self._data_sources): item = visible_items[index] if index < len(visible_items) else None action_in_progress = item and item == self._current_action_item display_string = self.item_formatter(index, item, action_in_progress) data_source.set_display_string(display_string) if not visible_items and self._data_sources and self.empty_list_message: self._data_sources[0].set_display_string(self.empty_list_message) return def update(self): super(ListComponent, self).update() if self.is_enabled(): self._update_action_feedback() self._update_display()