def build(self): # load main UI styles Builder.load_file(os.path.join(os.path.dirname(__file__), 'ui.kv')) # scrollview covering the entire window root = ScrollView(size_hint=(1, None), size=(Window.width, Window.height)) Window.bind(height=root.setter('height')) view = ExamView() root.add_widget(view) return root
def menu(self): grid = GridLayout(cols=1, spacing='2dp', size_hint_y=None) content = ScrollView(size=(200, 500)) grid.bind(minimum_height=content.setter('height')) grid.height = 1500 self.options = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5] for option in self.options: btn = Button(text=str(option), size_hint_y=None, height=50) grid.add_widget(btn) content.add_widget(grid) menu_sc = BoxLayout() menu_sc.add_widget(content) return menu_sc
def build(self): newBidBtn = buildButton('Nieuwe bieding', lambda i: self.mediator.editBidding(Bidding()), size_hint=(1.0, 0.1)) encloseLyt = BoxLayout(size_hint=(1.0, 0.85)) scrlView = ScrollView(size_hint=(1.0, None), scroll_type=['content', 'bars'], bar_width='10dp') encloseLyt.bind(size=scrlView.setter('size')) listLyt = GridLayout(cols=1, spacing=10, size_hint_y=None) listLyt.bind(minimum_height=listLyt.setter('height')) keys = self.mediator.getBiddingKeys() for (key, name, contract) in reversed(keys): # ensure most recent is at the top def createCallbackSelect(key): def cb(instance): self.mediator.loadBidding(key) return cb def createCallbackDelete(key): def cb(instance): self.mediator.deleteBidding(key) return cb def createCallbackEdit(key, name): def cb(instance): dad = instance.parent.parent # GridLayout -> BoxLayout dad.clear_widgets() def setNameCallback(instance): self.mediator.changeBiddingName(key, instance.text) input = buildTextInput(setNameCallback, size_hint=(0.8, 1.0)) input.text = name dad.add_widget(input) dad.add_widget( buildButton('Klaar', lambda i: setNameCallback(input), size_hint=(0.2, 1.0))) return cb itemLyt = BoxLayout(orientation='horizontal', size_hint=(1.0, None)) btns = GridLayout(rows=1, spacing=[gap, 0], padding=[gap, 0], size_hint=(0.2, 1.0)) btns.add_widget( buildIconButton(iconPencil, createCallbackEdit(key, name))) btns.add_widget( buildIconButton(iconTrashcan, createCallbackDelete(key))) itemNameLyt = AnchorLayout() itemNameLyt.add_widget(buildMultilineLabel(name)) itemLyt.add_widget( buildButton(itemNameLyt, createCallbackSelect(key), size_hint=(0.7, 1.0))) itemLyt.add_widget(buildText(contract, size_hint=(0.1, 1.0))) itemLyt.add_widget(btns) newBidBtn.bind(height=itemLyt.setter('height')) listLyt.add_widget(itemLyt) scrlView.add_widget(listLyt) encloseLyt.add_widget(scrlView) self.rootLayout.add_widget(encloseLyt) self.rootLayout.add_widget(BoxLayout(size_hint=(1.0, 0.05))) self.rootLayout.add_widget(newBidBtn)
class MainScreen(Screen): """A master layout that contains one graph and some menus. This contains three elements: a scrollview (containing the graph), a menu, and the time control panel. This class has some support methods for handling interactions with the menu and the character sheet, but if neither of those happen, the scrollview handles touches on its own. """ manager = ObjectProperty() graphboards = DictProperty() gridboards = DictProperty() boardview = ObjectProperty() mainview = ObjectProperty() charmenu = ObjectProperty() statlist = ObjectProperty() statpanel = ObjectProperty() timepanel = ObjectProperty() kv = StringProperty() use_kv = BooleanProperty() play_speed = NumericProperty() playbut = ObjectProperty() portaladdbut = ObjectProperty() portaldirbut = ObjectProperty() dummyplace = ObjectProperty() dummything = ObjectProperty() dummies = ReferenceListProperty(dummyplace, dummything) dialoglayout = ObjectProperty() visible = BooleanProperty() _touch = ObjectProperty(None, allownone=True) rules_per_frame = BoundedNumericProperty(10, min=1) app = ObjectProperty() tmp_block = BooleanProperty(False) def on_mainview(self, *args): if None in (self.statpanel, self.charmenu, self.app) or None in (self.app.character_name, self.charmenu.portaladdbut): Clock.schedule_once(self.on_mainview, 0) return self.boardview = GraphBoardView( scale_min=0.2, scale_max=4.0, size=self.mainview.size, pos=self.mainview.pos, board=self.graphboards[self.app.character_name], adding_portal=self.charmenu.portaladdbut.state == 'down') def update_adding_portal(*args): self.boardview.adding_portal = self.charmenu.portaladdbut.state == 'down' def update_board(*args): self.boardview.board = self.graphboards[self.app.character_name] self.mainview.bind(size=self.boardview.setter('size'), pos=self.boardview.setter('pos')) self.charmenu.portaladdbut.bind(state=update_adding_portal) self.app.bind(character_name=update_board) self.calendar = Agenda(update_mode='present') self.calendar_view = ScrollView(size=self.mainview.size, pos=self.mainview.pos) self.gridview = GridBoardView( scale_min=0.2, scale_max=4.0, size=self.mainview.size, pos=self.mainview.pos, board=self.gridboards[self.app.character_name]) self.mainview.bind( size=self.calendar_view.setter('size'), pos=self.calendar_view.setter('pos'), ) self.mainview.bind(size=self.gridview.setter('size'), pos=self.gridview.setter('pos')) self.calendar_view.add_widget(self.calendar) self.mainview.add_widget(self.boardview) def on_statpanel(self, *args): if not self.app: Clock.schedule_once(self.on_statpanel, 0) return self._update_statlist() self.app.bind(selected_proxy=self._update_statlist, branch=self._update_statlist, turn=self._update_statlist, tick=self._update_statlist) @trigger def _update_statlist(self, *args): if not self.app.selected_proxy: self._update_statlist() return self.app.update_calendar(self.statpanel.statlist, past_turns=0, future_turns=0) def pull_visibility(self, *args): self.visible = self.manager.current == 'main' def on_manager(self, *args): self.pull_visibility() self.manager.bind(current=self.pull_visibility) def on_play_speed(self, *args): """Change the interval at which ``self.play`` is called to match my current ``play_speed``. """ if hasattr(self, '_play_scheduled'): Clock.unschedule(self._play_scheduled) self._play_scheduled = Clock.schedule_interval(self.play, 1.0 / self.play_speed) def remake_display(self, *args): """Remake any affected widgets after a change in my ``kv``. """ Builder.load_string(self.kv) if hasattr(self, '_kv_layout'): self.remove_widget(self._kv_layout) del self._kv_layout self._kv_layout = KvLayout() self.add_widget(self._kv_layout) _trigger_remake_display = trigger(remake_display) def on_touch_down(self, touch): if self.visible: touch.grab(self) for interceptor in (self.timepanel, self.charmenu, self.statpanel, self.dummyplace, self.dummything): if interceptor.collide_point(*touch.pos): interceptor.dispatch('on_touch_down', touch) self.boardview.keep_selection = \ self.gridview.keep_selection = True return True if self.dialoglayout.dispatch('on_touch_down', touch): return True return self.mainview.dispatch('on_touch_down', touch) def on_touch_up(self, touch): if self.timepanel.collide_point(*touch.pos): return self.timepanel.dispatch('on_touch_up', touch) elif self.charmenu.collide_point(*touch.pos): return self.charmenu.dispatch('on_touch_up', touch) elif self.statpanel.collide_point(*touch.pos): return self.statpanel.dispatch('on_touch_up', touch) return self.mainview.dispatch('on_touch_up', touch) def on_dummies(self, *args): """Give the dummies numbers such that, when appended to their names, they give a unique name for the resulting new :class:`graph.Pawn` or :class:`graph.Spot`. """ if not self.app.character: Clock.schedule_once(self.on_dummies, 0) return def renum_dummy(dummy, *args): dummy.num = dummynum(self.app.character, dummy.prefix) + 1 for dummy in self.dummies: if dummy is None or hasattr(dummy, '_numbered'): continue if dummy == self.dummything: self.app.pawncfg.bind(imgpaths=self._propagate_thing_paths) if dummy == self.dummyplace: self.app.spotcfg.bind(imgpaths=self._propagate_place_paths) dummy.num = dummynum(self.app.character, dummy.prefix) + 1 Logger.debug("MainScreen: dummy #{}".format(dummy.num)) dummy.bind(prefix=partial(renum_dummy, dummy)) dummy._numbered = True def _propagate_thing_paths(self, *args): # horrible hack self.dummything.paths = self.app.pawncfg.imgpaths def _propagate_place_paths(self, *args): # horrible hack self.dummyplace.paths = self.app.spotcfg.imgpaths def _update_from_time_travel(self, command, branch, turn, tick, result, **kwargs): self._update_from_delta(command, branch, turn, tick, result[-1]) def _update_from_delta(self, cmd, branch, turn, tick, delta, **kwargs): self.app.branch = branch self.app.turn = turn self.app.tick = tick chardelta = delta.get(self.boardview.board.character.name, {}) for unwanted in ('character_rulebook', 'avatar_rulebook', 'character_thing_rulebook', 'character_place_rulebook', 'character_portal_rulebook'): if unwanted in chardelta: del chardelta[unwanted] self.boardview.board.trigger_update_from_delta(chardelta) self.gridview.board.trigger_update_from_delta(chardelta) self.statpanel.statlist.mirror = dict(self.app.selected_proxy) def play(self, *args): """If the 'play' button is pressed, advance a turn. If you want to disable this, set ``engine.universal['block'] = True`` """ if self.playbut.state == 'normal': return self.next_turn() def _update_from_next_turn(self, command, branch, turn, tick, result): todo, deltas = result if isinstance(todo, list): self.dialoglayout.todo = todo self.dialoglayout.idx = 0 self._update_from_delta(command, branch, turn, tick, deltas) self.dialoglayout.advance_dialog() self.app.bind(branch=self.app._push_time, turn=self.app._push_time, tick=self.app._push_time) self.tmp_block = False def next_turn(self, *args): """Advance time by one turn, if it's not blocked. Block time by setting ``engine.universal['block'] = True``""" if self.tmp_block: return eng = self.app.engine dial = self.dialoglayout if eng.universal.get('block'): Logger.info( "MainScreen: next_turn blocked, delete universal['block'] to unblock" ) return if dial.idx < len(dial.todo): Logger.info( "MainScreen: not advancing time while there's a dialog") return self.tmp_block = True self.app.unbind(branch=self.app._push_time, turn=self.app._push_time, tick=self.app._push_time) eng.next_turn(cb=self._update_from_next_turn) def switch_to_calendar(self, *args): self.app.update_calendar(self.calendar) self.mainview.clear_widgets() self.mainview.add_widget(self.calendar_view) def switch_to_boardview(self, *args): self.mainview.clear_widgets() self.app.engine.handle('apply_choices', choices=[self.calendar.get_track()]) self.mainview.add_widget(self.boardview) def toggle_gridview(self, *args): if self.gridview in self.mainview.children: self.mainview.clear_widgets() self.mainview.add_widget(self.boardview) else: self.mainview.clear_widgets() self.mainview.add_widget(self.gridview) def toggle_calendar(self, *args): # TODO decide how to handle switching between >2 view types if self.boardview in self.mainview.children: self.switch_to_calendar() else: self.switch_to_boardview()
class MessageWidget(BoxLayout): def __init__(self, *args, **kwargs): super(MessageWidget, self).__init__(*args, **kwargs) self.orientation = 'vertical' self.master_layout = ScrollView(size_hint=(1, 1)) self.master_layout.bind(size=self.master_layout.setter('size')) self.message_layout = GridLayout(cols=1, spacing=10, size_hint_y=None) self.message_layout.bind( minimum_height=self.message_layout.setter('height')) self.sub_layout = GridLayout(cols=1, spacing=10, size_hint_y=None) self.sub_layout.bind(minimum_height=self.sub_layout.setter('height')) bkgrnd_color = (0, 0, 0, 1) self.set_background(self, bkgrnd_color) self.users = {} self.logger = logging.getLogger("kivy.operator.widgets.messaging") self.messages = defaultdict(list) Window.softinput_mode = 'pan' self.chatting = None self.reply = TextInput() self.main_app = App.get_running_app() self.check_xmpp() self.new_lab = Label() self.new = False def set_background(self, layout, color): """ Sets a solid color as a background. :param layout: The layout for whichever part of the screen should be set. """ layout.bind(size=self._update_rect, pos=self._update_rect) with layout.canvas.before: Color(1, 1, 1, 1) self.rect = Rectangle(size=layout.size, pos=layout.pos) def _update_rect(self, instance, value): """ Ensures that the canvas fits to the screen should the layout ever change. """ self.rect.pos = instance.pos self.rect.size = instance.size def redraw(self, event, args): """ Binds the size of the label background to the label size. """ event.bg_rect.size = event.size event.bg_rect.pos = event.pos def get_users(self): """ Pulls the list of users on the XMPP server. """ users = self.main_app.get_users() self.users = {} for user in users: self.users[user.split('@')[0]] = user self.users['Operator Group'] = '*****@*****.**' self.main_app.xmpp_log('info', 'updating user list') if not self.chatting: self.gen_menu() @run_on_ui_thread def check_xmpp(self): """ Check if xmpp is enabled. """ if self.main_app.xmpp_config_ok: self.gen_menu() else: self.disabled_menu() def disabled_menu(self): """ Creates a disabled messaging menu should xmpp be disabled. """ self.clear_widgets() sub_layout = BoxLayout(size_hint=(1, 1)) sub_layout.clear_widgets() lab = Label(text='XMPP messaging is disabled due to config errors!', size_hint=(1, 1), markup=True) lab.color = colorsys.hsv_to_rgb(0, 0, 1) with lab.canvas.before: Color(1, 1, 0, mode='hsv') lab.bg_rect = Rectangle(pos=self.pos, size=self.size) lab.bind(pos=self.redraw, size=self.redraw) sub_layout.add_widget(lab) self.add_widget(sub_layout) def gen_menu(self): """ Creates the base menu which displays all users. """ self.chatting = None self.clear_widgets() self.master_layout.clear_widgets() self.message_layout.clear_widgets() sub_layout = GridLayout(cols=1, spacing=10, size_hint_y=None) sub_layout.bind(minimum_height=sub_layout.setter('height')) sub_layout.clear_widgets() names = [] for user in self.users: names.append(user) names.sort() op_chat = Button(text='Group Chat: OPERATOR', size_hint_y=None, height=180, color=[0, 0, 0, 1], on_release=functools.partial(self.chat_panel, 'Operator Group'), background_normal='', background_color=[.5, 1, .5, 1]) sub_layout.add_widget(op_chat) for name in names: lab = Button(text=name, size_hint_y=None, height=100, color=[1, 1, 1, 1], on_release=functools.partial(self.chat_panel, name), background_normal='', background_color=[0, .3, .3, 1]) sub_layout.add_widget(lab) refresh = Button(text="Refresh Users", size_hint_y=None, on_release=lambda x: self.get_users(), height=180, color=[0, 0, 0, 1], background_normal='', background_color=[0, 1, 0, 1]) self.message_layout.add_widget(refresh) self.message_layout.add_widget(sub_layout) self.master_layout.add_widget(self.message_layout) self.add_widget(self.master_layout) def on_message_receive(self, msg): """ Whenever a message is received, it is processed according to whatever the user is currently doing. :param Message msg: The XMPP message object. """ sender = str(msg['from']).strip() text = str(msg['body']).strip() chk_sender = sender.split('@')[0] if self.chatting == chk_sender: lab = Label(text=chk_sender + ": " + text, size_hint_y=None, markup=True, halign='left') lab.bind(width=lambda s, w: s.setter('text_size')(s, (w, None))) lab.bind(texture_size=lab.setter('size')) lab.color = colorsys.hsv_to_rgb(self.name_to_txt(chk_sender), 1, 1) with lab.canvas.before: Color(name_to_bg(chk_sender), 1, 1, mode='hsv') lab.bg_rect = Rectangle(pos=self.pos, size=self.size) lab.bind(pos=self.redraw, size=self.redraw) self.sub_layout.add_widget(lab) if self.new: self.sub_layout.remove_widget(self.new_lab) self.new = False else: if self.main_app.toast_all: toast(chk_sender + ": " + text, True) vibrator.vibrate(.1) if sender.split('/')[0] in self.messages: self.main_app.xmpp_log('info', 'receiving new message from ' + sender) else: self.main_app.xmpp_log('info', 'receiving first message from ' + sender) m = Message(sender.split('@')[0], text) self.messages[sender.split('/')[0]].append(m) def on_muc_receive(self, msg): """ Whenever a group message is received, it is processed according to whatever the user is currently doing. :param Message msg: The XMPP message object. """ sender = str(msg['from']).strip() text = str(msg['body']).strip() if self.chatting == "Operator Group": lab = Label(text=sender.split('/')[1] + ": " + text, size_hint_y=None, markup=True, halign='left') lab.bind(width=lambda s, w: s.setter('text_size')(s, (w, None))) lab.bind(texture_size=lab.setter('size')) lab.color = colorsys.hsv_to_rgb( self.name_to_txt(sender.split('/')[1]), 1, 1) with lab.canvas.before: Color(name_to_bg(sender.split('/')[1]), 1, 1, mode='hsv') lab.bg_rect = Rectangle(pos=self.pos, size=self.size) lab.bind(pos=self.redraw, size=self.redraw) self.sub_layout.add_widget(lab) if self.new: self.sub_layout.remove_widget(self.new_lab) self.new = False else: toast(sender.split('/')[1] + ": " + text, True) vibrator.vibrate(.1) if sender.split('/')[0] in self.messages: self.main_app.xmpp_log('info', 'receiving new message from ' + sender) else: self.main_app.xmpp_log('info', 'receiving first message from ' + sender) m = Message(sender.split('/')[1], text) self.messages[sender.split('/')[0]].append(m) def chat_panel(self, user, event): """ Creates the actual chat screen where the messages are displayed and where the user can respond. :param str user: The username of whomever the user is chatting with. """ full_name = self.users[user] self.chatting = user self.clear_widgets() self.master_layout.clear_widgets() self.message_layout.clear_widgets() self.sub_layout.clear_widgets() if full_name in self.messages: self.new = False temp = self.messages[full_name] for msg in temp: if not msg.sender: lab = Label(text=msg.body, color=(1, 1, 1, 1), size_hint_y=None, markup=True, halign='right') with lab.canvas.before: Color(.67, .82, 1, mode='hsv') lab.bg_rect = Rectangle(pos=self.pos, size=self.size) else: lab = Label(text=msg.sender + ": " + msg.body, color=(0, 0, 0, 1), size_hint_y=None, markup=True, halign='left') lab.color = colorsys.hsv_to_rgb( self.name_to_txt(msg.sender), 1, 1) with lab.canvas.before: Color(name_to_bg(msg.sender), 1, 1, mode='hsv') lab.bg_rect = Rectangle(pos=self.pos, size=self.size) lab.bind( width=lambda s, w: s.setter('text_size')(s, (w, None))) lab.bind(texture_size=lab.setter('size')) lab.bind(pos=self.redraw, size=self.redraw) self.sub_layout.add_widget(lab) else: self.new_lab = Label(text="Start a new conversation with " + user + "!", color=(0, 0, 0, 1)) self.new = True self.sub_layout.add_widget(self.new_lab) bottom = BoxLayout(size_hint_y=None, height=130) self.reply = TextInput(hint_text="Write a message...") title = Label(text=user, halign='left', color=(0, 0, 0, 1)) send = Button(text="Send", size_hint_x=.25, on_release=functools.partial(self.send_message, full_name)) bottom.add_widget(self.reply) bottom.add_widget(send) header = BoxLayout(size_hint_y=None, height=130) back_btn = Button(text='< Recent', size_hint_x=.5, on_release=lambda x: self.gen_menu()) presence = Label(size_hint_x=.3) header.add_widget(back_btn) header.add_widget(title) header.add_widget(presence) self.message_layout.add_widget(self.sub_layout) self.master_layout.add_widget(self.message_layout) self.add_widget(header) self.add_widget(self.master_layout) self.add_widget(bottom) def send_message(self, user, event): """ When the user hits the reply button, it sends the message back to the user (or group if it is a groupchat). :param str user: The username of whomever the user is chatting with. """ msg = self.reply.text if msg: if user == self.users['Operator Group']: self.main_app.send_muc(msg, user) else: self.main_app.send_message(msg, user) m = Message(None, msg) self.messages[user].append(m) lab = Label(text=msg, size_hint_y=None, color=(1, 1, 1, 1), markup=True, halign='right') lab.bind(width=lambda s, w: s.setter('text_size')(s, (w, None))) lab.bind(texture_size=lab.setter('size')) with lab.canvas.before: Color(.67, .82, 1, mode='hsv') lab.bg_rect = Rectangle(pos=self.pos, size=self.size) lab.bind(pos=self.redraw, size=self.redraw) self.sub_layout.add_widget(lab) self.reply.text = "" if self.new: self.sub_layout.remove_widget(self.new_lab) self.new = False def name_to_txt(self, data): """ Finds the corresponding text color to the background color. :param str data: The same string that is used for the background color. """ rem = name_to_bg(data) dis = rem + 0.5 if dis > 1: dis = dis - 1 return dis