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
|
from __future__ import annotations
|
||||||
import logging
|
import logging
|
||||||
import typing
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import sys
|
import sys
|
||||||
|
@ -92,7 +91,7 @@ class ClientCommandProcessor(CommandProcessor):
|
||||||
|
|
||||||
|
|
||||||
class CommonContext():
|
class CommonContext():
|
||||||
tags:typing.Set[str] = {"AP"}
|
tags: typing.Set[str] = {"AP"}
|
||||||
starting_reconnect_delay: int = 5
|
starting_reconnect_delay: int = 5
|
||||||
current_reconnect_delay: int = starting_reconnect_delay
|
current_reconnect_delay: int = starting_reconnect_delay
|
||||||
command_processor: int = ClientCommandProcessor
|
command_processor: int = ClientCommandProcessor
|
||||||
|
@ -107,6 +106,7 @@ class CommonContext():
|
||||||
self.server_task = None
|
self.server_task = None
|
||||||
self.server: typing.Optional[Endpoint] = None
|
self.server: typing.Optional[Endpoint] = None
|
||||||
self.server_version = Version(0, 0, 0)
|
self.server_version = Version(0, 0, 0)
|
||||||
|
self.hint_cost: typing.Optional[int] = None
|
||||||
self.permissions = {
|
self.permissions = {
|
||||||
"forfeit": "disabled",
|
"forfeit": "disabled",
|
||||||
"collect": "disabled",
|
"collect": "disabled",
|
||||||
|
@ -121,11 +121,11 @@ class CommonContext():
|
||||||
self.auth = None
|
self.auth = None
|
||||||
self.seed_name = 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.locations_scouted: typing.Set[int] = set()
|
||||||
self.items_received = []
|
self.items_received = []
|
||||||
self.missing_locations: typing.Set[int] = set()
|
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.locations_info = {}
|
||||||
|
|
||||||
self.input_queue = asyncio.Queue()
|
self.input_queue = asyncio.Queue()
|
||||||
|
@ -143,6 +143,12 @@ class CommonContext():
|
||||||
# execution
|
# execution
|
||||||
self.keep_alive_task = asyncio.create_task(keep_alive(self))
|
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):
|
async def connection_closed(self):
|
||||||
self.auth = None
|
self.auth = None
|
||||||
self.items_received = []
|
self.items_received = []
|
||||||
|
|
|
@ -22,4 +22,21 @@
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
height: self.minimum_height
|
height: self.minimum_height
|
||||||
orientation: 'vertical'
|
orientation: 'vertical'
|
||||||
spacing: dp(3)
|
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_CONSOLELOG"] = "1"
|
||||||
os.environ["KIVY_NO_FILELOG"] = "1"
|
os.environ["KIVY_NO_FILELOG"] = "1"
|
||||||
os.environ["KIVY_NO_ARGS"] = "1"
|
os.environ["KIVY_NO_ARGS"] = "1"
|
||||||
|
|
||||||
from kivy.app import App
|
from kivy.app import App
|
||||||
|
from kivy.core.window import Window
|
||||||
from kivy.base import ExceptionHandler, ExceptionManager, Config, Clock
|
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.button import Button
|
||||||
from kivy.uix.gridlayout import GridLayout
|
from kivy.uix.gridlayout import GridLayout
|
||||||
from kivy.uix.textinput import TextInput
|
from kivy.uix.textinput import TextInput
|
||||||
from kivy.uix.recycleview import RecycleView
|
from kivy.uix.recycleview import RecycleView
|
||||||
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
|
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
|
||||||
from kivy.uix.boxlayout import BoxLayout
|
from kivy.uix.boxlayout import BoxLayout
|
||||||
|
from kivy.uix.floatlayout import FloatLayout
|
||||||
from kivy.uix.label import Label
|
from kivy.uix.label import Label
|
||||||
from kivy.uix.progressbar import ProgressBar
|
from kivy.uix.progressbar import ProgressBar
|
||||||
from kivy.utils import escape_markup
|
from kivy.utils import escape_markup
|
||||||
|
@ -30,6 +35,102 @@ else:
|
||||||
context_type = object
|
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):
|
class GameManager(App):
|
||||||
logging_pairs = [
|
logging_pairs = [
|
||||||
("Client", "Archipelago"),
|
("Client", "Archipelago"),
|
||||||
|
@ -46,11 +147,13 @@ class GameManager(App):
|
||||||
super(GameManager, self).__init__()
|
super(GameManager, self).__init__()
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
self.grid = GridLayout()
|
self.container = ContainerLayout()
|
||||||
|
|
||||||
|
self.grid = MainLayout()
|
||||||
self.grid.cols = 1
|
self.grid.cols = 1
|
||||||
connect_layout = BoxLayout(orientation="horizontal", size_hint_y=None, height=30)
|
connect_layout = BoxLayout(orientation="horizontal", size_hint_y=None, height=30)
|
||||||
# top part
|
# top part
|
||||||
server_label = Label(text="Server:", size_hint_x=None)
|
server_label = ServerLabel()
|
||||||
connect_layout.add_widget(server_label)
|
connect_layout.add_widget(server_label)
|
||||||
self.server_connect_bar = TextInput(text="archipelago.gg", size_hint_y=None, height=30, multiline=False)
|
self.server_connect_bar = TextInput(text="archipelago.gg", size_hint_y=None, height=30, multiline=False)
|
||||||
connect_layout.add_widget(self.server_connect_bar)
|
connect_layout.add_widget(self.server_connect_bar)
|
||||||
|
@ -94,7 +197,8 @@ class GameManager(App):
|
||||||
self.grid.add_widget(bottom_layout)
|
self.grid.add_widget(bottom_layout)
|
||||||
self.commandprocessor("/help")
|
self.commandprocessor("/help")
|
||||||
Clock.schedule_interval(self.update_texts, 1 / 30)
|
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):
|
def update_texts(self, dt):
|
||||||
if self.ctx.server:
|
if self.ctx.server:
|
||||||
|
@ -118,6 +222,11 @@ class GameManager(App):
|
||||||
asyncio.create_task(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", "")))
|
asyncio.create_task(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", "")))
|
||||||
|
|
||||||
def on_stop(self):
|
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()
|
self.ctx.exit_event.set()
|
||||||
|
|
||||||
def on_message(self, textinput: TextInput):
|
def on_message(self, textinput: TextInput):
|
||||||
|
|
Loading…
Reference in New Issue