CommonClient.py UI: Add info on "Server:" label hover
CommonClient.py UI: prevent freeze if UI is closed while waiting on text user input
This commit is contained in:
parent
2217a9304d
commit
7f020857d1
|
@ -1,6 +1,5 @@
|
|||
from __future__ import annotations
|
||||
import logging
|
||||
import typing
|
||||
import asyncio
|
||||
import urllib.parse
|
||||
import sys
|
||||
|
@ -107,6 +106,7 @@ class CommonContext():
|
|||
self.server_task = None
|
||||
self.server: typing.Optional[Endpoint] = None
|
||||
self.server_version = Version(0, 0, 0)
|
||||
self.hint_cost: typing.Optional[int] = None
|
||||
self.permissions = {
|
||||
"forfeit": "disabled",
|
||||
"collect": "disabled",
|
||||
|
@ -121,11 +121,11 @@ class CommonContext():
|
|||
self.auth = None
|
||||
self.seed_name = None
|
||||
|
||||
self.locations_checked: typing.Set[int] = set()
|
||||
self.locations_checked: typing.Set[int] = set() # local state
|
||||
self.locations_scouted: typing.Set[int] = set()
|
||||
self.items_received = []
|
||||
self.missing_locations: typing.Set[int] = set()
|
||||
self.checked_locations: typing.Set[int] = set()
|
||||
self.checked_locations: typing.Set[int] = set() # server state
|
||||
self.locations_info = {}
|
||||
|
||||
self.input_queue = asyncio.Queue()
|
||||
|
@ -143,6 +143,12 @@ class CommonContext():
|
|||
# execution
|
||||
self.keep_alive_task = asyncio.create_task(keep_alive(self))
|
||||
|
||||
@property
|
||||
def total_locations(self) -> typing.Optional[int]:
|
||||
"""Will return None until connected."""
|
||||
if self.checked_locations or self.missing_locations:
|
||||
return len(self.checked_locations | self.missing_locations)
|
||||
|
||||
async def connection_closed(self):
|
||||
self.auth = None
|
||||
self.items_received = []
|
||||
|
|
|
@ -23,3 +23,20 @@
|
|||
height: self.minimum_height
|
||||
orientation: 'vertical'
|
||||
spacing: dp(3)
|
||||
<ServerLabel>:
|
||||
text: "Server:"
|
||||
size_hint_x: None
|
||||
<ContainerLayout>:
|
||||
size_hint_x: 1
|
||||
size_hint_y: 1
|
||||
pos: (0, 0)
|
||||
<ServerToolTip>:
|
||||
size: self.texture_size
|
||||
pos_hint: {'center_y': 0.5, 'center_x': 0.5}
|
||||
halign: "left"
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: 0.2, 0.2, 0.2, 1
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
115
kvui.py
115
kvui.py
|
@ -6,14 +6,19 @@ import asyncio
|
|||
os.environ["KIVY_NO_CONSOLELOG"] = "1"
|
||||
os.environ["KIVY_NO_FILELOG"] = "1"
|
||||
os.environ["KIVY_NO_ARGS"] = "1"
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.core.window import Window
|
||||
from kivy.base import ExceptionHandler, ExceptionManager, Config, Clock
|
||||
from kivy.factory import Factory
|
||||
from kivy.properties import BooleanProperty, ObjectProperty
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.uix.recycleview import RecycleView
|
||||
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.progressbar import ProgressBar
|
||||
from kivy.utils import escape_markup
|
||||
|
@ -30,6 +35,102 @@ else:
|
|||
context_type = object
|
||||
|
||||
|
||||
# I was surprised to find this didn't already exist in kivy :(
|
||||
class HoverBehavior(object):
|
||||
"""from https://stackoverflow.com/a/605348110"""
|
||||
hovered = BooleanProperty(False)
|
||||
border_point = ObjectProperty(None)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.register_event_type('on_enter')
|
||||
self.register_event_type('on_leave')
|
||||
Window.bind(mouse_pos=self.on_mouse_pos)
|
||||
super(HoverBehavior, self).__init__(**kwargs)
|
||||
|
||||
def on_mouse_pos(self, *args):
|
||||
if not self.get_root_window():
|
||||
return # do proceed if I'm not displayed <=> If have no parent
|
||||
pos = args[1]
|
||||
# Next line to_widget allow to compensate for relative layout
|
||||
inside = self.collide_point(*self.to_widget(*pos))
|
||||
if self.hovered == inside:
|
||||
return # We have already done what was needed
|
||||
self.border_point = pos
|
||||
self.hovered = inside
|
||||
|
||||
if inside:
|
||||
self.dispatch("on_enter")
|
||||
else:
|
||||
self.dispatch("on_leave")
|
||||
|
||||
|
||||
Factory.register('HoverBehavior', HoverBehavior)
|
||||
|
||||
|
||||
class ServerToolTip(Label):
|
||||
pass
|
||||
|
||||
|
||||
class ServerLabel(HoverBehavior, Label):
|
||||
hover_text = """"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ServerLabel, self).__init__(*args, **kwargs)
|
||||
self.layout = FloatLayout()
|
||||
self.popuplabel = ServerToolTip(text="Test")
|
||||
self.layout.add_widget(self.popuplabel)
|
||||
|
||||
def on_enter(self):
|
||||
self.popuplabel.text = self.get_text()
|
||||
App.get_running_app().root.add_widget(self.layout)
|
||||
|
||||
def on_leave(self):
|
||||
App.get_running_app().root.remove_widget(self.layout)
|
||||
|
||||
def get_text(self):
|
||||
if self.ctx.server:
|
||||
ctx = self.ctx
|
||||
text = f"Connected to: {ctx.server_address}."
|
||||
if ctx.slot is not None:
|
||||
text += f"\nYou are Slot Number {ctx.slot} in Team Number {ctx.team}, named {ctx.player_names[ctx.slot]}."
|
||||
if ctx.items_received:
|
||||
text += f"\nYou have received {len(ctx.items_received)} items. " \
|
||||
f"You can list them in order with /received."
|
||||
if ctx.total_locations:
|
||||
text += f"\nYou have checked {len(ctx.checked_locations)} " \
|
||||
f"out of {ctx.total_locations} locations. " \
|
||||
f"You can get more info on missing checks with /missing."
|
||||
if ctx.permissions:
|
||||
text += "\nPermissions:"
|
||||
for permission_name, permission_data in ctx.permissions.items():
|
||||
text += f"\n {permission_name}: {permission_data}"
|
||||
if ctx.hint_cost is not None:
|
||||
text += f"\nA new !hint <itemname> costs {ctx.hint_cost}% of checks made. " \
|
||||
f"For you this means every {max(0, int(ctx.hint_cost * 0.01 * ctx.total_locations))} location checks."
|
||||
elif ctx.hint_cost == 0:
|
||||
text += "\n!hint is free to use."
|
||||
|
||||
else:
|
||||
text += f"\nYou are not authenticated yet."
|
||||
|
||||
return text
|
||||
|
||||
else:
|
||||
return "No current server connection. \nPlease connect to an Archipelago server."
|
||||
|
||||
@property
|
||||
def ctx(self) -> context_type:
|
||||
return App.get_running_app().ctx
|
||||
|
||||
|
||||
class MainLayout(GridLayout):
|
||||
pass
|
||||
|
||||
|
||||
class ContainerLayout(FloatLayout):
|
||||
pass
|
||||
|
||||
|
||||
class GameManager(App):
|
||||
logging_pairs = [
|
||||
("Client", "Archipelago"),
|
||||
|
@ -46,11 +147,13 @@ class GameManager(App):
|
|||
super(GameManager, self).__init__()
|
||||
|
||||
def build(self):
|
||||
self.grid = GridLayout()
|
||||
self.container = ContainerLayout()
|
||||
|
||||
self.grid = MainLayout()
|
||||
self.grid.cols = 1
|
||||
connect_layout = BoxLayout(orientation="horizontal", size_hint_y=None, height=30)
|
||||
# top part
|
||||
server_label = Label(text="Server:", size_hint_x=None)
|
||||
server_label = ServerLabel()
|
||||
connect_layout.add_widget(server_label)
|
||||
self.server_connect_bar = TextInput(text="archipelago.gg", size_hint_y=None, height=30, multiline=False)
|
||||
connect_layout.add_widget(self.server_connect_bar)
|
||||
|
@ -94,7 +197,8 @@ class GameManager(App):
|
|||
self.grid.add_widget(bottom_layout)
|
||||
self.commandprocessor("/help")
|
||||
Clock.schedule_interval(self.update_texts, 1 / 30)
|
||||
return self.grid
|
||||
self.container.add_widget(self.grid)
|
||||
return self.container
|
||||
|
||||
def update_texts(self, dt):
|
||||
if self.ctx.server:
|
||||
|
@ -118,6 +222,11 @@ class GameManager(App):
|
|||
asyncio.create_task(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", "")))
|
||||
|
||||
def on_stop(self):
|
||||
# "kill" input tasks
|
||||
for x in range(self.ctx.input_requests):
|
||||
self.ctx.input_queue.put_nowait("")
|
||||
self.ctx.input_requests = 0
|
||||
|
||||
self.ctx.exit_event.set()
|
||||
|
||||
def on_message(self, textinput: TextInput):
|
||||
|
|
Loading…
Reference in New Issue